What is Cursor IDE Observability? #
Cursor 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.
This guide closes that gap using OpenTelemetry. 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.
With 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.
Prerequisites #
-
SigNoz setup (choose one): SigNoz Cloud accountwith an active ingestion key- Self-hosted SigNoz instance
-
Cursor IDE on macOS or Linux
-
Python 3.11 or later
Monitor Cursor IDE with OpenTelemetry #
Cursor 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, an MIT-licensed runner that converts hook events into OTLP spans.
Step 1: Install the hook runner in its own environment so it does not touch your system Python.
python3 -m venv ~/.local/opt/otel-hook
~/.local/opt/otel-hook/bin/pip install opentelemetry-hooks
Step 2: Register it with Cursor. This writes ~/.cursor/hooks.json
.
~/.local/opt/otel-hook/bin/otel-hook setup --agent cursor
Step 3: Add the event that carries token counts.
python3 - <<'EOF'
import json, os
p = os.path.expanduser("~/.cursor/hooks.json")
d = json.load(open(p))
h = d.get("hooks", d)
h["afterAgentResponse"] = json.loads(json.dumps(h["stop"]))
json.dump(d, open(p, "w"), indent=2)
print("events registered:", len(h))
EOF
You should see events registered: 16
. If otel-hook
is not on your PATH
, rewrite the commands in ~/.cursor/hooks.json
to the absolute path of the binary, otherwise Cursor silently fails to run the hook.
Step 4: Point the exporter at SigNoz. Edit ~/.local/share/opentelemetry-hooks/otel_config.json
, keeping the other keys already in the file.
{
"OTEL_EXPORTER_OTLP_ENDPOINT": "https://ingest.<region>.signoz.cloud:443",
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
"OTEL_EXPORTER_OTLP_HEADERS": "signoz-ingestion-key=<your-ingestion-key>",
"OTEL_EXPORTER_OTLP_INSECURE": "false",
"IDE_OTEL_BATCH_ON_STOP": "true"
}
Verify these values:
<region>
: YourSigNoz Cloud region.<your-ingestion-key>
: Your SigNozingestion key.
The file holds your ingestion key, so restrict it with chmod 600 ~/.local/share/opentelemetry-hooks/otel_config.json
.
Step 5: Quit Cursor completely and reopen it. Cursor reads ~/.cursor/hooks.json
only 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.
View Cursor IDE Traces in SigNoz #
Allow a few minutes after your first prompt, then open the Traces explorer and filter on service.name = 'ide-agent'
.
Opening a turn shows the full waterfall, with the agent's tool sequence nested under the turn and the gen_ai.*
attributes on the right.
Attributes Worth Knowing #
A turn produces a gen_ai.client.generation
span with one gen_ai.client.hook.*
child per hook event. Four details decide whether your queries are correct.
Token counts arrive twice.afterAgentResponse
andStop
both carry identicalgen_ai.usage.input_tokens
andgen_ai.usage.output_tokens
. Filter to one of them or every token figure doubles.Turn duration is on Thegen_ai.client.generation
.Stop
span 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 itsPostToolUseFailure
.PreToolUse
andPostToolUse
spans, so counting error status roughly doubles the failure rate.The runner hardcodes it for every agent it supports, so group byservice.name
is alwayside-agent
.gen_ai.client.name
(cursor
) when you run more than one agent.
Every span also carries gen_ai.request.model
, gen_ai.client.session_id
, gen_ai.client.generation_id
, and, when the runner resolves it, vcs.repository.name
and vcs.ref.head.name
.
View Cursor IDE Logs in SigNoz #
The runner emits logs alongside the traces, one record per tool call, shell execution, and MCP call. Open the Logs Explorer and filter with service.name = 'ide-agent'
to see the event stream.
Logs carry detail the spans do not. Open a record to see it in the attributes.
Useful attributes include gen_ai.client.command
and gen_ai.client.shell.stdout
for shell executions, gen_ai.client.tool_name
and gen_ai.client.duration_ms
for tool calls, and gen_ai.client.hook.event
to separate the event types. Every record also carries trace_id
and span_id
, so a log line links back to the exact span in the turn's waterfall.
Cursor IDE Observability Dashboard #
The Cursor IDE dashboard turns these spans into token spend by model, turn and tool latency, tool mix, and failure tracking.
Troubleshooting Cursor IDE Observability #
No spans arrive at all
Check delivery locally before assuming the export is broken:
cat ~/.local/share/opentelemetry-hooks/.state/delivery_health.json
A last_success_at_ns
newer than last_failure_at_ns
means export is working and you are looking at ingestion delay. Allow 2 to 4 minutes before re-checking SigNoz.
If there is no success timestamp, confirm Cursor was fully restarted after Step 2, and that Customize > Hooks lists the registered events.
Spans arrive but every token panel is empty
afterAgentResponse
is not registered. Re-run Step 3 and confirm it prints events registered: 16
, then restart Cursor. A restart is required whenever the registered event set changes.
Traces show a "missing span" placeholder
This 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.
Repository shows as N/A on some spans
The runner resolves repository context once per session and does not always succeed, so vcs.repository.name
can 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.
Nothing is emitted from the Cursor CLI
This cannot be fixed by configuration. The Cursor CLI omits the beforeSubmitPrompt
, afterAgentResponse
, and stop
hooks in non-interactive mode, and the runner builds a turn's spans only when Stop
arrives. Use the Cursor IDE, or query Cursor's Admin API for team-level token and cost totals without per-tool detail.
Related integrations #
Instrument the other AI coding agents your team runs, using the same OpenTelemetry pipeline:
Monitor Claude Code with OpenTelemetryMonitor OpenAI Codex with OpenTelemetryOpenCode observability with OpenTelemetryMonitor GitHub Copilot with OpenTelemetry
Browse all LLM observability integrations to instrument the rest of your stack.