{"slug": "cursor-ide-observability-monitoring-with-opentelemetry", "title": "Cursor IDE Observability & Monitoring with OpenTelemetry", "summary": "SigNoz has published a guide for monitoring Cursor, the AI-native code editor, using OpenTelemetry to trace agent activity, token usage, and tool failures. The setup uses the open-source opentelemetry-hooks runner to convert Cursor's agent hook events into OTLP traces, which can be viewed in SigNoz's traces explorer. This provides visibility into token spend, tool performance, and per-repository breakdowns, which Cursor's built-in console lacks.", "body_md": "## What is Cursor IDE Observability?\n\n[Cursor](https://cursor.com/) is an AI-native code editor whose agent plans and executes multi-step work: it reads files, runs shell commands, calls MCP servers, and edits code on your behalf. Cursor's own usage console shows what your team spent. What it does not show is what the agent actually did to spend it: which tools ran, how long each took, and which ones failed.\n\nThis guide closes that gap using [OpenTelemetry](https://opentelemetry.io/). Cursor's agent hooks fire on every step of the agent loop, and a hook runner turns those events into OTLP traces. The result is one trace per agent turn carrying both token counts and the full tool sequence, on any Cursor plan.\n\nWith Cursor IDE observability in SigNoz, you can see how many tokens your team is spending and on which models, how long a turn actually takes end to end, which tools the agent reaches for most, which ones fail and why, and how all of that breaks down per repository and branch.\n\n## Prerequisites\n\n- SigNoz setup (choose one):\n[SigNoz Cloud account](https://signoz.io/teams/)with an active ingestion key- Self-hosted SigNoz instance\n\n- Cursor IDE on macOS or Linux\n- Python 3.11 or later\n\n## Monitor Cursor IDE with OpenTelemetry\n\nCursor has no built-in OTLP exporter outside its Enterprise plan, and that path emits metrics and logs only, with no traces. Instead, this setup uses Cursor's agent hooks with [opentelemetry-hooks](https://github.com/o11y-dev/opentelemetry-hooks), an MIT-licensed runner that converts hook events into OTLP spans.\n\n**Step 1:** Install the hook runner in its own environment so it does not touch your system Python.\n\n```\npython3 -m venv ~/.local/opt/otel-hook\n~/.local/opt/otel-hook/bin/pip install opentelemetry-hooks\n```\n\n**Step 2:** Register it with Cursor. This writes `~/.cursor/hooks.json`\n\n.\n\n```\n~/.local/opt/otel-hook/bin/otel-hook setup --agent cursor\n```\n\n**Step 3:** Add the event that carries token counts.\n\n``` python\npython3 - <<'EOF'\nimport json, os\np = os.path.expanduser(\"~/.cursor/hooks.json\")\nd = json.load(open(p))\nh = d.get(\"hooks\", d)\nh[\"afterAgentResponse\"] = json.loads(json.dumps(h[\"stop\"]))\njson.dump(d, open(p, \"w\"), indent=2)\nprint(\"events registered:\", len(h))\nEOF\n```\n\nYou should see `events registered: 16`\n\n. If `otel-hook`\n\nis not on your `PATH`\n\n, rewrite the commands in `~/.cursor/hooks.json`\n\nto the absolute path of the binary, otherwise Cursor silently fails to run the hook.\n\n**Step 4:** Point the exporter at SigNoz. Edit `~/.local/share/opentelemetry-hooks/otel_config.json`\n\n, keeping the other keys already in the file.\n\n```\n{\n  \"OTEL_EXPORTER_OTLP_ENDPOINT\": \"https://ingest.<region>.signoz.cloud:443\",\n  \"OTEL_EXPORTER_OTLP_PROTOCOL\": \"grpc\",\n  \"OTEL_EXPORTER_OTLP_HEADERS\": \"signoz-ingestion-key=<your-ingestion-key>\",\n  \"OTEL_EXPORTER_OTLP_INSECURE\": \"false\",\n  \"IDE_OTEL_BATCH_ON_STOP\": \"true\"\n}\n```\n\n**Verify these values:**\n\n`<region>`\n\n: Your[SigNoz Cloud region](https://signoz.io/docs/ingestion/signoz-cloud/overview/#endpoint).`<your-ingestion-key>`\n\n: Your SigNoz[ingestion key](https://signoz.io/docs/ingestion/signoz-cloud/keys/).\n\nThe file holds your ingestion key, so restrict it with `chmod 600 ~/.local/share/opentelemetry-hooks/otel_config.json`\n\n.\n\n**Step 5:** Quit Cursor completely and reopen it. Cursor reads `~/.cursor/hooks.json`\n\nonly at startup, so a running instance will not pick up the change. Confirm the hooks are active under **Customize > Hooks**, then send a prompt that uses a tool.\n\n## View Cursor IDE Traces in SigNoz\n\nAllow a few minutes after your first prompt, then open the [Traces explorer](https://signoz.io/docs/userguide/traces/) and filter on `service.name = 'ide-agent'`\n\n.\n\nOpening a turn shows the full waterfall, with the agent's tool sequence nested under the turn and the `gen_ai.*`\n\nattributes on the right.\n\n## Attributes Worth Knowing\n\nA turn produces a `gen_ai.client.generation`\n\nspan with one `gen_ai.client.hook.*`\n\nchild per hook event. Four details decide whether your queries are correct.\n\n**Token counts arrive twice.**`afterAgentResponse`\n\nand`Stop`\n\nboth carry identical`gen_ai.usage.input_tokens`\n\nand`gen_ai.usage.output_tokens`\n\n. Filter to one of them or every token figure doubles.**Turn duration is on** The`gen_ai.client.generation`\n\n.`Stop`\n\nspan measures 1 ms on every turn and is a marker, not a measurement.**Tool failures are** A single failed tool call also sets error status on its`PostToolUseFailure`\n\n.`PreToolUse`\n\nand`PostToolUse`\n\nspans, so counting error status roughly doubles the failure rate.The runner hardcodes it for every agent it supports, so group by`service.name`\n\nis always`ide-agent`\n\n.`gen_ai.client.name`\n\n(`cursor`\n\n) when you run more than one agent.\n\nEvery span also carries `gen_ai.request.model`\n\n, `gen_ai.client.session_id`\n\n, `gen_ai.client.generation_id`\n\n, and, when the runner resolves it, `vcs.repository.name`\n\nand `vcs.ref.head.name`\n\n.\n\n## View Cursor IDE Logs in SigNoz\n\nThe runner emits logs alongside the traces, one record per tool call, shell execution, and MCP call. Open the [Logs Explorer](https://signoz.io/docs/userguide/logs_query_builder/) and filter with `service.name = 'ide-agent'`\n\nto see the event stream.\n\nLogs carry detail the spans do not. Open a record to see it in the attributes.\n\nUseful attributes include `gen_ai.client.command`\n\nand `gen_ai.client.shell.stdout`\n\nfor shell executions, `gen_ai.client.tool_name`\n\nand `gen_ai.client.duration_ms`\n\nfor tool calls, and `gen_ai.client.hook.event`\n\nto separate the event types. Every record also carries `trace_id`\n\nand `span_id`\n\n, so a log line links back to the exact span in the turn's waterfall.\n\n## Cursor IDE Observability Dashboard\n\nThe [Cursor IDE dashboard](https://signoz.io/docs/dashboards/dashboard-templates/cursor-dashboard/) turns these spans into token spend by model, turn and tool latency, tool mix, and failure tracking.\n\n## Troubleshooting Cursor IDE Observability\n\n### No spans arrive at all\n\nCheck delivery locally before assuming the export is broken:\n\n```\ncat ~/.local/share/opentelemetry-hooks/.state/delivery_health.json\n```\n\nA `last_success_at_ns`\n\nnewer than `last_failure_at_ns`\n\nmeans export is working and you are looking at ingestion delay. Allow 2 to 4 minutes before re-checking SigNoz.\n\nIf there is no success timestamp, confirm Cursor was fully restarted after Step 2, and that **Customize > Hooks** lists the registered events.\n\n### Spans arrive but every token panel is empty\n\n`afterAgentResponse`\n\nis not registered. Re-run Step 3 and confirm it prints `events registered: 16`\n\n, then restart Cursor. A restart is required whenever the registered event set changes.\n\n### Traces show a \"missing span\" placeholder\n\nThis is expected. The runner parents each turn to a session span that it never emits, so SigNoz shows the turn under a missing parent. The turn and its children are complete and every query works normally.\n\n### Repository shows as N/A on some spans\n\nThe runner resolves repository context once per session and does not always succeed, so `vcs.repository.name`\n\ncan be absent even when the workspace is a git repository. Those spans are missing attribution rather than running outside a repository, which matters if you group token spend by repository.\n\n### Nothing is emitted from the Cursor CLI\n\nThis cannot be fixed by configuration. The Cursor CLI omits the `beforeSubmitPrompt`\n\n, `afterAgentResponse`\n\n, and `stop`\n\nhooks in non-interactive mode, and the runner builds a turn's spans only when `Stop`\n\narrives. Use the Cursor IDE, or query Cursor's Admin API for team-level token and cost totals without per-tool detail.\n\n## Related integrations\n\nInstrument the other AI coding agents your team runs, using the same OpenTelemetry pipeline:\n\n[Monitor Claude Code with OpenTelemetry](https://signoz.io/docs/claude-code-monitoring/)[Monitor OpenAI Codex with OpenTelemetry](https://signoz.io/docs/codex-monitoring/)[OpenCode observability with OpenTelemetry](https://signoz.io/docs/opencode-observability/)[Monitor GitHub Copilot with OpenTelemetry](https://signoz.io/docs/github-copilot-monitoring/)\n\nBrowse [all LLM observability integrations](https://signoz.io/docs/llm-observability/) to instrument the rest of your stack.", "url": "https://wpnews.pro/news/cursor-ide-observability-monitoring-with-opentelemetry", "canonical_source": "https://signoz.io/docs/cursor-observability", "published_at": "2026-08-27 00:00:00+00:00", "updated_at": "2026-08-28 06:47:57.008074+00:00", "lang": "en", "topics": ["developer-tools", "ai-tools", "ai-agents"], "entities": ["SigNoz", "Cursor", "OpenTelemetry", "opentelemetry-hooks"], "alternates": {"html": "https://wpnews.pro/news/cursor-ide-observability-monitoring-with-opentelemetry", "markdown": "https://wpnews.pro/news/cursor-ide-observability-monitoring-with-opentelemetry.md", "text": "https://wpnews.pro/news/cursor-ide-observability-monitoring-with-opentelemetry.txt", "jsonld": "https://wpnews.pro/news/cursor-ide-observability-monitoring-with-opentelemetry.jsonld"}}