Skip to content

Logging and monitoring setup in Cursor means having Cursor’s agent generate four observability layers plus dashboards for a service: structured JSON logs, Prometheus metrics, OpenTelemetry traces, and alert rules. The agent writes each layer quickly because the conventions are well documented; trust a layer only once a test, promtool, or a trace smoke test proves it.

This page is for developers who own a Node.js or TypeScript service and the tech leads who set the observability conventions for a team. The pager fires at 02:00 for “high error rate on orders API”. The logs say Internal Server Error with no request ID, no route, and no user, and nothing ties the spike on the dashboard to a line of code. The fix is not more logging. It is logs, metrics, and traces that share identifiers, and alerts that fire on user-facing symptoms.

What you get from an agent-built observability stack

Section titled “What you get from an agent-built observability stack”
  • A project rule that fixes your field names, label sets, and log levels before the agent writes any code.
  • A pino logger with request context, redaction, and trace_id on every line.
  • prom-client metrics with a bounded label set, plus a test that fails when someone adds an unbounded label.
  • OpenTelemetry tracing that loads before your app and propagates W3C traceparent across services.
  • Prometheus alert rules with unit tests in promtool, so you know each alert fires before production does.
  • Cursor connected to Sentry or Grafana through MCP, so the agent can read real telemetry when you debug.

The examples use Express on Node.js. The prompts carry over to Fastify, NestJS, or Next.js route handlers once you swap the framework name and the route-pattern accessor (for example, Fastify’s routeOptions.url).

In what order should the agent build observability?

Section titled “In what order should the agent build observability?”

Build the layers in dependency order: conventions, then logs, metrics, traces, alerts, and dashboards last. Alerts query metric names, and dashboards query both, so generating them first gives you rules that point at series that do not exist. Each step below ends with a gate. Do not start the next step until the gate is green.

  1. Write the conventions down as a project rule. Without one, the agent picks field names per file: requestId in the middleware, req_id in the orders service, request_id in the tests. A rule makes every later prompt consistent. See project rules in Cursor for where rules live and how they load.

  2. Ask for a plan before code. Switch the agent to Plan Mode and have it inventory what already exists. Plan Mode “creates detailed implementation plans before writing any code” (Cursor docs, checked 2026-08-28), which keeps the agent from bolting a second logger onto a service that already has one.

  3. Generate one layer per conversation. A fresh conversation per layer keeps the context small and the diff reviewable. Commit after each gate passes, so a bad layer reverts cleanly with git revert.

Generate structured logging with request context

Section titled “Generate structured logging with request context”

Logs are the first layer because every other layer references their identifiers. The prompt below asks for the logger, the request middleware, and the test that proves both in one pass.

Gate: the three tests pass, and the sample line contains request_id and no secret. The concurrency test is the one that matters: AsyncLocalStorage leakage between requests is invisible in a single-request manual check.

To add business context later, ask for a child logger per unit of work rather than new fields in every call: “In src/services/orders.ts, create a child logger with order_id and customer_id at the start of processOrder and use it for every log line in that function.” Business IDs belong in logs, where each line is one event, and never in metric labels.

Add Prometheus metrics without a cardinality explosion

Section titled “Add Prometheus metrics without a cardinality explosion”

Metrics answer “how often and how slow” in aggregate. The label set is the real design decision. Each distinct combination of label values creates a new time series, so one user_id label turns a counter into a series per user.

Gate: the series count test passes. The unmatched case catches the most common generated bug: using req.path or req.originalUrl as the label, which works in a demo and creates one series per URL under a scanner’s traffic.

Wire OpenTelemetry tracing across services

Section titled “Wire OpenTelemetry tracing across services”

Tracing links one user request across services and ties it back to the log lines through trace_id. The Node.js SDK has one hard rule the agent often misses: it must load before express, http, and database clients are imported, or auto-instrumentation patches nothing and you get empty traces with no error.

Gate: the smoke test passes. It proves three things you would otherwise check by hand across three tools: the SDK loaded in time, the logger reads the active span, and propagation works on outgoing calls. The package names above are current on npm as of 2026-09-26 (@opentelemetry/sdk-node 0.222.0, @opentelemetry/auto-instrumentations-node 0.80.0).

Alert on symptoms users feel, such as error ratio and latency, not on causes such as CPU. The agent writes Prometheus rule syntax well. The gap is thresholds that were never exercised. promtool test rules closes that gap: it feeds synthetic series into the rules and asserts which alerts fire at which minute.

Run the gate in a terminal. The prom/prometheus image ships promtool, so you do not need a local Prometheus install:

Terminal window
docker run --rm -v "$PWD/monitoring:/m" --entrypoint promtool \
prom/prometheus check rules /m/alerts/api.yml /m/alerts/business.yml
docker run --rm -v "$PWD/monitoring:/m" --entrypoint promtool \
prom/prometheus test rules /m/alerts/tests/api_test.yml

Gate: both commands exit 0. Add them to CI next to your unit tests so a later edit to a metric name breaks the build, not the pager. CI/CD with Cursor shows where that job goes.

Dashboards come last and are the lowest-risk layer, because a wrong panel misleads a person, while a wrong alert wakes one. Ask for a Grafana dashboard JSON with an environment and service template variable and one row each for traffic, errors, latency, and saturation, and tell the agent to use only the metric names in src/lib/metrics.ts. Import it into a staging Grafana and check that every panel returns data.

Let Cursor read your production telemetry through MCP

Section titled “Let Cursor read your production telemetry through MCP”

Generated instrumentation is half the loop. The other half is the agent reading what that instrumentation reports when something breaks. Cursor reads MCP servers from .cursor/mcp.json in the project or ~/.cursor/mcp.json globally. Sentry’s official remote server uses OAuth, so the config holds no secret:

{
"mcpServers": {
"sentry": { "url": "https://mcp.sentry.dev/mcp" }
}
}

With that in place, a debugging prompt can start from real data instead of a pasted stack trace:

For metrics and logs, Grafana Labs publishes an official server (uvx mcp-grafana, PyPI 1.6.0 on 2026-09-26) that queries Prometheus and Loki; start it with --disable-write so the agent can read dashboards but not change them. Keep its service account token in your environment, never in a committed mcp.json. Setup for both servers across tools is in MCP setup for Cursor and observability MCP servers.

Cursor Automations can also start a Cloud Agent from a Sentry or PagerDuty event (Cursor docs, checked 2026-10-12). That is useful for a first-pass diagnosis on a pull request, but the fix still goes through your normal tests and review. See Cloud Agents and Automations before you wire an alert to an agent.

How do you verify generated observability code without reading it all?

Section titled “How do you verify generated observability code without reading it all?”

Each layer has a behavioural check that runs in seconds. Together they replace a line-by-line review of a few hundred lines of instrumentation.

LayerWhat can go wrong silentlyThe check that catches it
LogsSecrets logged; request_id leaks across requestsRedaction and concurrency tests on captured output
MetricsUnbounded labels; wrong route labelSeries-count test and allowed-label-keys test on app-defined metrics
TracesSDK loads too late; no propagationtraceparent smoke test with an in-memory exporter
AlertsRule never fires, or fires on noisepromtool test rules with positive and negative cases
DashboardsPanels query metrics that do not existImport into staging Grafana; every panel shows data

The developer who opens the pull request owns the tests. The tech lead owns the project rule and signs off on the alert thresholds and severities, because those decide who gets woken up. What you still read by eye is short: the rule file, the label list, and the alert thresholds.

Traces are empty, and nothing errors. The OpenTelemetry SDK was imported after express or http, so it patched nothing. A second cause, for an ESM build: the SDK loads first but the loader hook is missing, so ESM imports are never patched. Recovery: move the import to --import or --require in the start command, add --experimental-loader=@opentelemetry/instrumentation/hook.mjs if the app is ESM, as in the tracing prompt, and rerun the smoke test.

Metrics storage bills jump after a deploy. A label carries a raw URL, an ID, or an error message. Recovery: query your Prometheus for the series count per metric (count by (__name__)({__name__=~"http_.*"})), find the offending label, and add that label key to the forbidden list in the project rule so the next generation does not repeat it. The label-keys test keeps it out afterwards.

The agent wrote custom span middleware on top of auto-instrumentation. Every request produces two nested server spans with the same name. Recovery: delete the custom middleware and keep withSpan only for business steps. The tracing prompt says this up front because the agent adds it by default.

Attribute names do not match your backend’s queries. Generated code often uses the deprecated http.method and http.status_code attributes. Recovery: state the current conventions in the project rule, as in the conventions prompt, and ask the agent to migrate existing spans in one pass.

Alerts fire every day and get muted. Thresholds were chosen without data. Recovery: set new alerts to severity: ticket for the first two weeks, compare their firing history with real incidents, then promote only the ones that matched to page. Add a promtool case for every threshold you change.

Production logs are too verbose. The agent logged at info inside loops. Recovery: grep for logger calls inside for and map bodies, move them to debug, and confirm LOG_LEVEL defaults to info when unset, so production never logs debug unless someone sets it on purpose.

Where to go next with Cursor and observability

Section titled “Where to go next with Cursor and observability”