# How to add tracing and observability with Cursor

- Tool: Cursor
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Cursor Free+, Node.js/Python/Go with OpenTelemetry
- Last updated: March 2026

## TL;DR

Cursor can generate OpenTelemetry tracing instrumentation across your microservices when given proper context about your architecture and tracing conventions. This tutorial shows how to use .cursorrules for consistent span naming, reference your tracing setup with @file, and prompt Cursor to add traces to existing endpoints without breaking your code.

## Adding observability to microservices with Cursor

Adding distributed tracing to an existing codebase is repetitive but detail-sensitive work. Each service needs consistent span names, attribute conventions, and error recording. Cursor can automate this instrumentation if you provide the right context about your tracing setup and naming conventions. This tutorial walks through setting up a reusable tracing pattern that Cursor can apply across your services.

## Before you start

- Cursor installed with a microservices project
- OpenTelemetry SDK installed in your project
- A tracing backend (Jaeger, Zipkin, or Grafana Tempo) for verification
- Basic understanding of distributed tracing concepts

## Step-by-step guide

### 1. Generate a tracing setup module with Cursor

Use Cursor Chat (Cmd+L) to generate the initial OpenTelemetry configuration. This module initializes the tracer provider, sets up exporters, and creates a reusable tracer instance. Cursor needs to know your runtime, exporter type, and service name pattern.

```
// Cursor Chat prompt (Cmd+L):
// Generate an OpenTelemetry tracing setup module for a
// Node.js Express service. Use OTLP/gRPC exporter, batch
// span processor, and the W3C trace context propagator.
// Service name should come from environment variable
// OTEL_SERVICE_NAME. Export a getTracer() function.

// Expected output:
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { trace } from '@opentelemetry/api';

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317',
  }),
  instrumentations: [getNodeAutoInstrumentations()],
  serviceName: process.env.OTEL_SERVICE_NAME || 'unknown-service',
});

sdk.start();

export function getTracer(name: string) {
  return trace.getTracer(name);
}
```

**Expected result:** A reusable tracing setup module that initializes OpenTelemetry with your preferred configuration.

### 2. Add tracing rules to .cursor/rules

Create rules that define your span naming convention, required attributes, and error recording pattern. This ensures every trace Cursor generates follows the same format across all services.

```
---
description: OpenTelemetry tracing conventions
globs: "src/**/*.ts"
alwaysApply: false
---

## Tracing Rules
- Import tracer: import { getTracer } from '@/tracing/setup'
- Span naming: '{service}.{operation}' e.g. 'order-service.createOrder'
- ALWAYS add these attributes to spans:
  - 'http.method' for HTTP handlers
  - 'http.route' for the URL pattern
  - 'user.id' when available from auth context
  - 'error' boolean attribute on failures
- Record exceptions with span.recordException(error)
- Set span status to ERROR on catch blocks
- Use startActiveSpan for automatic context propagation
- NEVER create spans inside tight loops (batch them)
- End spans in finally blocks to prevent leaks
```

**Expected result:** Cursor follows consistent tracing conventions whenever it generates or modifies service code.

### 3. Add traces to an existing endpoint with Cmd+K

Open an existing route handler, select the function body, press Cmd+K, and ask Cursor to wrap it with tracing. Reference the tracing setup file so Cursor uses the correct import path.

```
// Select the handler function, press Cmd+K:
// @src/tracing/setup.ts Add OpenTelemetry tracing to this
// handler. Use startActiveSpan with the naming convention
// 'order-service.getOrder'. Add http.method, http.route,
// and user.id attributes. Record errors.

// Cursor output:
import { getTracer } from '@/tracing/setup';
import { SpanStatusCode } from '@opentelemetry/api';

const tracer = getTracer('order-service');

app.get('/orders/:id', async (req, res) => {
  return tracer.startActiveSpan('order-service.getOrder', async (span) => {
    try {
      span.setAttribute('http.method', 'GET');
      span.setAttribute('http.route', '/orders/:id');
      span.setAttribute('user.id', req.user?.id || 'anonymous');

      const order = await orderRepository.findById(req.params.id);
      if (!order) {
        span.setAttribute('error', true);
        return res.status(404).json({ error: 'Order not found' });
      }
      res.json(order);
    } catch (error) {
      span.recordException(error as Error);
      span.setStatus({ code: SpanStatusCode.ERROR });
      res.status(500).json({ error: 'Internal server error' });
    } finally {
      span.end();
    }
  });
});
```

> Pro tip: Always end spans in a finally block. Cursor sometimes forgets this, causing span leaks that distort your tracing data.

**Expected result:** The endpoint now creates a traced span with proper attributes, error recording, and cleanup.

### 4. Instrument multiple endpoints with Composer

Use Composer Agent mode (Cmd+I) to add tracing to all endpoints in a service at once. Reference the tracing setup and rules to ensure consistency.

```
// Composer prompt (Cmd+I):
// @src/tracing/setup.ts @.cursor/rules/tracing.mdc
// @src/routes/ Add OpenTelemetry tracing to all route
// handlers in the routes directory. Follow the tracing
// rules for span naming, attributes, and error recording.
// Process one file at a time and show me the diff before
// proceeding to the next file.
```

**Expected result:** Cursor instruments each route file with consistent tracing, waiting for approval between files.

### 5. Verify traces in your tracing backend

Start your service and send a few test requests. Open your tracing backend (Jaeger, Grafana Tempo) to verify the spans appear with correct names and attributes. Use Cursor Chat to troubleshoot if traces are missing.

```
// Terminal: Start the service with tracing enabled
OTEL_SERVICE_NAME=order-service \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
npm run dev

// Send a test request
curl http://localhost:3000/orders/123

// If traces are missing, ask Cursor:
// Cmd+L: @src/tracing/setup.ts My traces are not appearing
// in Jaeger at localhost:16686. The service starts without
// errors. What could be wrong?
```

**Expected result:** Traces appear in your backend with correct service name, span names, and attributes.

## Complete code example

File: `src/tracing/setup.ts`

```typescript
import { NodeSDK } from '@opentelemetry/sdk-node';
import {
  getNodeAutoInstrumentations,
} from '@opentelemetry/auto-instrumentations-node';
import {
  OTLPTraceExporter,
} from '@opentelemetry/exporter-trace-otlp-grpc';
import {
  BatchSpanProcessor,
} from '@opentelemetry/sdk-trace-base';
import { trace, SpanStatusCode } from '@opentelemetry/api';
import { W3CTraceContextPropagator } from '@opentelemetry/core';

const exporter = new OTLPTraceExporter({
  url:
    process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||
    'http://localhost:4317',
});

const sdk = new NodeSDK({
  traceExporter: exporter,
  spanProcessor: new BatchSpanProcessor(exporter, {
    maxExportBatchSize: 512,
    scheduledDelayMillis: 5000,
  }),
  textMapPropagator: new W3CTraceContextPropagator(),
  instrumentations: [
    getNodeAutoInstrumentations({
      '@opentelemetry/instrumentation-fs': { enabled: false },
    }),
  ],
  serviceName:
    process.env.OTEL_SERVICE_NAME || 'unknown-service',
});

sdk.start();

process.on('SIGTERM', () => {
  sdk.shutdown().then(() => process.exit(0));
});

export function getTracer(name: string) {
  return trace.getTracer(name, '1.0.0');
}

export { SpanStatusCode };
```

## Common mistakes

- **Forgetting to end spans in finally blocks** — If an exception is thrown before span.end(), the span leaks and never appears in your tracing backend, or appears with an incorrect duration. Fix: Add 'End spans in finally blocks to prevent leaks' to your .cursor/rules and verify every Cursor-generated span has a finally block.
- **Creating spans inside loops** — Cursor may add a span inside a forEach or map loop, creating thousands of spans per request and overwhelming your tracing backend. Fix: Add a rule: 'NEVER create spans inside tight loops. Create one span for the batch operation with item count as an attribute.'
- **Inconsistent span naming across services** — Without naming rules, Cursor generates different conventions per file, making traces hard to search and filter. Fix: Define the naming convention in .cursor/rules: 'Span naming: {service}.{operation}' and reference the rules in every tracing prompt.

## Best practices

- Define span naming conventions in .cursor/rules for consistent instrumentation across services
- Always reference @src/tracing/setup.ts when prompting Cursor to add traces
- End all spans in finally blocks to prevent resource leaks
- Use startActiveSpan for automatic context propagation between functions
- Add standard HTTP attributes (method, route, status_code) to all request spans
- Disable file system instrumentation to reduce noise in traces
- Use Composer Agent mode to instrument multiple endpoints consistently in one session

## Frequently asked questions

### Does Cursor understand OpenTelemetry APIs?

Yes. Cursor's training data includes OpenTelemetry documentation and examples. However, the API changed significantly between versions, so specify your OpenTelemetry SDK version in .cursorrules to avoid outdated API usage.

### Should I use auto-instrumentation or manual spans?

Use both. Auto-instrumentation covers HTTP, database, and gRPC calls automatically. Add manual spans for business-logic operations like 'processPayment' or 'validateOrder' that auto-instrumentation cannot detect.

### How do I propagate trace context across services?

OpenTelemetry's W3C TraceContext propagator handles this automatically for HTTP calls. For message queues, you need to manually inject/extract context. Ask Cursor: 'Show how to propagate trace context through an SQS message.'

### Will adding tracing to every endpoint slow my service?

The overhead is minimal (microseconds per span) when using a batch span processor. The BatchSpanProcessor buffers spans and sends them in bulk, adding negligible latency to request handling.

### Can Cursor add tracing to Python or Go services too?

Yes. Update your .cursor/rules with the language-specific OpenTelemetry API patterns. For Python, reference the opentelemetry-python SDK. For Go, reference the go.opentelemetry.io/otel package.

---

Source: https://www.rapidevelopers.com/cursor-tutorial/how-to-direct-cursor-ai-to-add-distributed-tracing-e-g-opentelemetry-in-microservices-code
© RapidDev — https://www.rapidevelopers.com/cursor-tutorial/how-to-direct-cursor-ai-to-add-distributed-tracing-e-g-opentelemetry-in-microservices-code
