Telemetry export streams your team's usage metrics and logs outward to a collector you run yourself. The mechanism is server-side: the data is sent by the service rather than by developers' machines, so neither installing agents on workstations nor editing settings for each person is required. The entry bar looks like this: an HTTPS endpoint accepting OTLP/HTTP binary protobuf on /v1/metrics and /v1/logs, authentication by bearer token or API key, and reachability of that endpoint from the public internet. The capability belongs to the enterprise level and is in beta.
The naive view usually goes like this: enter the address, flip the switch, and the data will flow on its own, as from any other source inside the perimeter. The expectation is reasonable, because that is how most internal observability pipelines behave: the collector sits nearby, the network is yours, the format survives almost anything, and if something did not arrive it shows up as an empty chart a minute after enabling.
The expectation breaks in several places at once. The base address is entered without paths: /v1/metrics and /v1/logs are appended automatically, and an address entered together with them will lead somewhere other than you think. There is one format - binary protobuf; a pipeline configured to accept JSON will silently accept nothing. Traffic leaves from a fixed set of egress addresses, and a collector behind an access list has to be opened up in advance, otherwise the very first stream runs into the firewall. Finally, the built-in connection test confirms the address and the credentials, but it does not confirm that your pipeline will parse the records and that you will later find them in your own store.
The order of work is therefore fixed: create the destination with a base address and authentication headers, test the connection, enable the export - the stream starts in about a minute. Headers are stored encrypted. Changing credentials is done by editing the destination, and the change propagates in about half a minute; disabling or deleting a destination drops the data in flight. Hence the rotation rule: the key is changed by editing rather than by a delete-and-recreate pair, otherwise the window between the two actions becomes a hole in the series.
The contents of the export are defined in advance and split into signal families, each of which is switched off separately: model usage, tool calls, events of skills, hooks and plugins, and the cloud agent lifecycle. This split matters more than it seems: it sets the unit of negotiation with legal and security, because what gets turned off is a whole family rather than individual fields. Below is that map: on the left the record names, in the middle what they carry, on the right the family that switches the record on and off.
Metrics arrive as monotonic sums with delta temporality: tokens by type, of which there are four - input, output, cache read and cache creation; calls to builtin tools and to MCP tools; a cost estimate in dollars. The word estimate is load-bearing here: this is not a bill. With your own provider key the estimate reflects only the service's token rate and knows nothing about what the provider will charge. Logs carry model call summaries, error events without the raw message text, billing corrections, skill activations, hook completions, plugin installs and the cloud agent lifecycle: environment setup, artifacts, pull request creation and MCP authorization failures. Prompt content, code and traces are not sent at all.
| Record | What it carries | Family |
|---|---|---|
| cursor.token.usage | Tokens by type: input, output, cache read, cache creation | model_usage |
| cursor.cost.usage | A best-effort spend estimate in dollars, not a bill | model_usage |
| cursor.api.request, cursor.api.error, cursor.api.correction | Model call summary, error without raw text, billing correction | model_usage |
| cursor.tool.calls | Calls to builtin tools and to MCP tools | tool_calls |
| cursor.skill.activated, cursor.hook.execution_complete, cursor.plugin.installed | Skill activation, hook completion, plugin install | skills_hooks_plugins |
| cursor.cloud_agent.setup, .artifact, .pull_request, .mcp_auth_error | Environment setup, artifacts, pull requests, MCP authorization failure | cloud_agents |
Joining records is deliberately inconvenient, and the reason is cardinality. Metrics carry no correlation identifiers: if every point of a series carried a conversation number, the number of series would grow with the number of sessions and the store would suffocate for nothing. So the link is looked for in the logs. The session key is cursor.conversation.id, the identifier of a chat or a cloud agent. The request-level key for reconciliation is cursor.usage_event.id. The identifier of an individual call is cursor.request.id, optional. And cursor.event.id, which is good only for removing duplicates and nothing else. Resource attributes carry the service name, the team identifier, an optional user identifier and the surface a record came from.
The delivery guarantees of the two signals differ, and this is a price paid silently. Logs are delivered at least once, with a retry window of about a week: duplicates are inevitable, they are removed by the event key, and a pipeline unable to remove them will count double. Metrics are delivered at most once and without retries: after a failure the series keeps a gap, and there is nothing to fill it with. There will be no data for the time before the destination was created - history is not backfilled. And one more detail breaks naive processing: corrections arrive later than the requests they amend, so order is restored by record timestamp rather than by arrival time.
The format change policy is stated plainly: the surface is additive, unknown attributes, events and enum values are to be accepted silently, and renames and removals get an explicit notice. The telemetry scope version is 0.1.0, the stage is beta, and the format may change before general availability. There is one practical conclusion: parsing is built tolerant, without a rigid schema that fails on an unfamiliar field. The result is verified neither by the switch nor by the connection test, but by an end-to-end pass: do real work, find the record in your own store by the session key, compare the number of events against what you expect, and make sure a repeated delivery does not double the count.
The typical failures are predictable. Entering the address together with the paths and getting the wrong receiver. Configuring JSON intake and waiting for data that will never come. Forgetting to open the collector to the fixed egress addresses. Treating a successful connection test as proof that records are parsed. Rotating the key by deleting the destination and losing what was in flight. Taking the cost estimate for a bill, especially with your own provider key. Building reports on metrics while expecting per-conversation analysis, which requires the logs. And pinning a rigid parsing schema onto a format declared additive and still in beta.