Claude Code OpenTelemetry Tracing (2026)

OpenTelemetry has become the industry standard for observability, providing vendor-neutral APIs, SDKs, and tools for collecting distributed traces, metrics, and logs. When combined with Claude Code’s AI assistance, you can rapidly implement comprehensive tracing in your applications without deep prior knowledge of OpenTelemetry internals.

This guide walks you through setting up OpenTelemetry tracing instrumentation using Claude Code as your coding partner.

Why OpenTelemetry Matters for Modern Applications

Modern applications often consist of multiple microservices communicating across networks. When something goes wrong, pinpointing the exact location of a failure can feel like finding a needle in a haystack. OpenTelemetry solves this by providing distributed tracing, a way to follow a request as it travels through your entire system.

Traditional debugging often involves adding log statements, restarting services, and hoping you captured enough information. With OpenTelemetry, every request gets a unique trace ID that follows it through all services, making it trivial to see exactly where time is being spent and where errors occur.

Claude Code accelerates your OpenTelemetry journey by generating boilerplate code, explaining complex concepts, and helping you debug tracing issues when they arise.

Setting Up OpenTelemetry with Claude Code

Initial Project Configuration

Start by describing your tracing needs to Claude. Be specific about your language, framework, and what you want to achieve:

/opentelemetry Set up OpenTelemetry tracing for a Node.js Express API. I want to trace HTTP requests, database queries, and external API calls.

Claude will generate the initial setup, typically including package installation and basic configuration. For Node.js, this might look like:

npm install @opentelemetry/api @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node @opentelemetry/exporter-trace-otlp-http

Creating the Tracing Setup File

Claude can generate a proper tracing initialization file tailored to your needs:

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');
const { Resource } = require('@opentelemetry/resources');
const { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } = require('@opentelemetry/semantic-conventions');
const sdk = new NodeSDK({
 resource: new Resource({
 [ATTR_SERVICE_NAME]: 'your-service-name',
 [ATTR_SERVICE_VERSION]: '1.0.0',
 }),
 traceExporter: new OTLPTraceExporter(),
 instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start()
 .then(() => console.log('Tracing initialized'))
 .catch((error) => console.error('Error initializing tracing', error));
process.on('SIGTERM', () => {
 sdk.shutdown()
 .then(() => console.log('Tracing terminated'))
 .catch((error) => console.error('Error terminating tracing', error));
});

Notice that this code uses standard JavaScript, not Liquid template syntax, so no raw tag is needed.

Manual Instrumentation with Custom Spans

While auto-instrumentation covers many common frameworks, you’ll often need to create custom spans for business logic that frameworks don’t automatically capture.

Creating Spans for Business Logic

Describe your business operations to Claude and ask for custom span creation:

/opentelemetry Create spans for our order processing workflow that includes validation, inventory check, payment processing, and notification sending.

Claude will generate code like this:

const { trace, SpanKind, StatusCode } = require('@opentelemetry/api');
const { meter } = require('@opentelemetry/api');
const tracer = trace.getTracer('order-service');
const orderMeter = meter.getMeter('order-service');
async function processOrder(orderData) {
 return tracer.startActiveSpan('processOrder', async (span) => {
 try {
 // Add order ID to trace for correlation
 span.setAttribute('order.id', orderData.id);
 span.setAttribute('order.total', orderData.total);
 // Validation span
 const validationSpan = tracer.startSpan('validateOrder', {
 kind: SpanKind.INTERNAL,
 parent: span,
 });
 const isValid = await validateOrder(orderData);
 validationSpan.setAttribute('validation.result', isValid);
 validationSpan.end();
 
 if (!isValid) {
 span.setStatus({ code: StatusCode.ERROR, message: 'Order validation failed' });
 span.end();
 return { success: false, error: 'Validation failed' };
 }
 // Inventory check span
 const inventorySpan = tracer.startSpan('checkInventory', {
 kind: SpanKind.CLIENT,
 parent: span,
 });
 const inventoryResult = await checkInventory(orderData.items);
 inventorySpan.setAttribute('inventory.available', inventoryResult.available);
 inventorySpan.end();
 if (!inventoryResult.available) {
 span.setStatus({ code: StatusCode.ERROR, message: 'Insufficient inventory' });
 span.end();
 return { success: false, error: 'Insufficient inventory' };
 }
 // Payment processing span
 const paymentSpan = tracer.startSpan('processPayment', {
 kind: SpanKind.CLIENT,
 parent: span,
 });
 const paymentResult = await processPayment(orderData.payment);
 paymentSpan.setAttribute('payment.id', paymentResult.transactionId);
 paymentSpan.setAttribute('payment.status', paymentResult.status);
 paymentSpan.end();
 // Notification span
 const notificationSpan = tracer.startSpan('sendNotification', {
 kind: SpanKind.PRODUCER,
 parent: span,
 });
 await sendNotification(orderData.customerId, 'order_confirmed');
 notificationSpan.end();
 span.setAttribute('order.status', 'completed');
 span.end();
 return { success: true, orderId: orderData.id };
 
 } catch (error) {
 span.setStatus({ 
 code: StatusCode.ERROR, 
 message: error.message 
 });
 span.recordException(error);
 span.end();
 throw error;
 }
 });
}

Adding Custom Attributes

Make your traces more useful by adding relevant attributes:

function addUserContext(span, user) {
 span.setAttribute('user.id', user.id);
 span.setAttribute('user.email', user.email);
 span.setAttribute('user.tier', user.subscriptionTier);
}
function addRequestContext(span, request) {
 span.setAttribute('http.method', request.method);
 span.setAttribute('http.url', request.url);
 span.setAttribute('http.route', request.route?.path || 'unknown');
 span.setAttribute('http.status_code', response.statusCode);
}

Tracing Database Operations

Database queries are often the biggest source of latency. OpenTelemetry auto-instrumentation captures many queries automatically, but custom spans provide more context.

Tracing with Detailed Query Information

/opentelemetry Add detailed tracing for PostgreSQL queries including query text, execution time, and row counts.
const { trace, SpanKind } = require('@opentelemetry/api');
const dbTracer = trace.getTracer('database');
async function tracedQuery(pool, text, params) {
 const span = dbTracer.startSpan('database.query', {
 kind: SpanKind.CLIENT,
 attributes: {
 'db.system': 'postgresql',
 'db.statement': text,
 'db.operation': text.split(' ')[0].toUpperCase(),
 },
 });
 const startTime = Date.now();
 try {
 const result = await pool.query(text, params);
 span.setAttribute('db.row_count', result.rowCount);
 span.setAttribute('db.execution_time_ms', Date.now() - startTime);
 return result;
 } catch (error) {
 span.setStatus({
 code: StatusCode.ERROR,
 message: error.message,
 });
 span.recordException(error);
 throw error;
 } finally {
 span.end();
 }
}

Context Propagation

When requests span multiple services, trace context must propagate through headers.

W3C Trace Context

The W3C Trace Context standard is now the default:

const { propagation, ROOT_CONTEXT } = require('@opentelemetry/api');
// Extract trace context from incoming request
function extractTraceContext(req) {
 const carrier = {
 traceparent: req.headers['traceparent'],
 tracestate: req.headers['tracestate'],
 };
 return propagation.extract(ROOT_CONTEXT, carrier);
}
// Add trace context to outgoing requests
function injectTraceContext(outgoingOptions) {
 propagation.inject(
 trace.getActiveSpan().spanContext(),
 outgoingOptions.headers || (outgoingOptions.headers = {})
 );
 return outgoingOptions;
}
// Usage with HTTP client
async function callDownstreamService(url, data) {
 const span = trace.getActiveSpan();
 const outgoing = injectTraceContext({
 method: 'POST',
 url,
 headers: {},
 });
 
 return fetch(url, {
 ...outgoing,
 body: JSON.stringify(data),
 });
}

Custom Propagators

For systems using custom headers:

const { TextMapPropagator, W3C_TRACE_CONTEXT_PARENT_HEADER } = require('@opentelemetry/api');
class CustomTracePropagator extends TextMapPropagator {
 inject(context, carrier) {
 const spanContext = context.getValue(SPAN_KEY);
 if (!spanContext) return;
 
 carrier['x-trace-id'] = spanContext.traceId;
 carrier['x-span-id'] = spanContext.spanId;
 }
 extract(context, carrier) {
 const traceId = carrier['x-trace-id'];
 const spanId = carrier['x-span-id'];
 
 if (!traceId || !spanId) return context;
 
 const spanContext = new SpanContext({
 traceId: TraceId.fromHex(traceId),
 spanId: SpanId.fromHex(spanId),
 traceFlags: TraceFlags.SAMPLED,
 });
 
 return context.setValue(SPAN_KEY, spanContext);
 }
}

Sampling Strategies

High-throughput applications may need sampling to control trace volume.

Common Sampling Strategies

const { AlwaysSample, AlwaysOffSampler, ParentBasedSampler } = require('@opentelemetry/sdk-trace-base');
// Always sample for development
const devSampler = AlwaysSample;
// Production: only sample 10% of traces
const prodSampler = new ParentBasedSampler({
 root: new TraceIdRatioBased(0.1),
});
// Sample based on specific criteria
const customSampler = new ParentBasedSampler({
 root: new TraceIdRatioBased(0.1),
 onRootSpanStart: (rootSpan) => {
 // Always sample API requests
 if (rootSpan.attributes['http.url']?.includes('/api/')) {
 return AlwaysSample;
 }
 // Skip health checks
 if (rootSpan.attributes['http.url']?.includes('/health')) {
 return AlwaysOffSampler;
 }
 return new TraceIdRatioBased(0.1);
 },
});

Integration with Claude Code for Debugging

When traces reveal performance issues, Claude can help analyze and resolve them.

Analyzing Trace Data

Share your trace data with Claude for analysis:

/opentelemetry Analyze this trace data showing 3 second latency in our checkout flow. The spans show: validateOrder (50ms), checkInventory (2800ms), processPayment (100ms), sendNotification (50ms). What's causing the bottleneck?

Claude will identify that checkInventory is the bottleneck and suggest optimizations like caching inventory data or using asynchronous processing.

Troubleshooting Common Issues

Common problems Claude can help debug:

  • Missing traces: Check if sampling is too aggressive or if span export is failing
  • Incomplete context: Verify propagator configuration across services
  • Performance overhead: Reduce attribute cardinality, adjust sampling rate
  • Export failures: Verify OTLP endpoint connectivity and authentication

Best Practices

Naming Conventions

Use consistent, meaningful span names:

// Good: descriptive, consistent naming
'processOrder'
'database.query'
'http.post:/api/checkout'
// Bad: dynamic values in span names
`processOrder-${orderId}` // Creates too many unique span names
`query-${Math.random()}` // Absolutely forbidden

Attribute Guidelines

// Use semantic conventions for standard attributes
const { SemanticAttributes } = require('@opentelemetry/semantic-conventions');
span.setAttribute(SemanticAttributes.DB_SYSTEM, 'redis');
span.setAttribute(SemanticAttributes.DB_STATEMENT, 'GET user:123');
span.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET');
span.setAttribute(SemanticAttributes.HTTP_URL, 'https://api.example.com/users');
// Avoid high-cardinality values as attributes
// Bad: span.setAttribute('user.email', user.email); // Too many unique values
// Good: span.setAttribute('user.id', user.id);

Performance Considerations

// Don't create spans in tight loops
// Instead, batch operations
async function processItems(items) {
 const span = tracer.startSpan('processItems');
 try {
 const batchSpan = tracer.startSpan('batchProcessing', { parent: span });
 // Process all items in batch
 await processBatch(items);
 batchSpan.end();
 } finally {
 span.end();
 }
}
// Use span.addEvent for logging-like information
span.addEvent('Processing item', {
 'item.id': itemId,
 'item.status': 'started',
});

Conclusion

OpenTelemetry tracing provides visibility into your application’s behavior across service boundaries. With Claude Code as your partner, you can rapidly implement comprehensive instrumentation without becoming an OpenTelemetry expert. The key is starting simple with auto-instrumentation, then adding custom spans for your specific business logic.

Remember to iterate: start with basic setup, add meaningful attributes, implement proper context propagation, and refine with sampling strategies as your observability needs grow.


Try it: Paste your error into our Error Diagnostic for an instant fix.

I'm a solo developer in Vietnam. 50K Chrome extension users. $500K+ on Upwork. 5 Claude Max subscriptions running agent fleets in parallel. These are my actual CLAUDE.md templates, orchestration configs, and prompts. Not a course. Not theory. The files I copy into every project before I write a line of code. **[See what's inside →](https://zovo.one/lifetime?utm_source=ccg&utm_medium=cta-default&utm_campaign=claude-code-opentelemetry-tracing-instrumentation-guide)** $99 once. Free forever. 47/500 founding spots left.

Related Reading

Built by theluckystrike. More at zovo.one

Find the right skill → Browse 155+ skills in our Skill Finder.

Quick setup → Launch your project with our Project Starter.