Use Claude 3.5 Sonnet for designing instrumentation strategies that span multiple services, GitHub Copilot for generating boilerplate span and metric code while you code, or ChatGPT 4o for quick debugging when span attributes are missing or traces don’t correlate. This guide walks through how each tool handles OpenTelemetry context propagation, sampling decisions, and SDK configuration across Python, Go, and Node.js applications.
OpenTelemetry’s Instrumentation Challenge
Effective observability requires consistent span naming, proper trace context propagation between services, and meaningful attributes on every span. Most teams struggle with:
- Inconsistent span names across services (is it “db.query” or “database.execute”? “http.server.request” or “request.handle”?)
- Lost trace context when async operations or message queues break the call chain
- Missing attributes on critical spans (customer ID, API version, error codes)
- Sampling logic that either discards interesting errors or overwhelming the backend with noise
AI assistants can generate correct instrumentation patterns, but they need context about your system topology and observability goals. Claude excels at this design phase. Copilot works well for routine span additions. ChatGPT helps debug context propagation issues.
Claude 3.5 Sonnet: Designing Observable Services
Claude understands that instrumentation is an architectural decision. Provide your service topology and Claude generates a consistent naming scheme and context propagation strategy.
Example prompt:
I have a Node.js Express API that calls a Python background service
via RabbitMQ, which queries PostgreSQL and calls a third-party
payment API. I need distributed traces that show the full request
path from user browser to database. Design an OpenTelemetry strategy.
Claude’s output includes:
// Express middleware that extracts trace context from HTTP headers
app.use((req, res, next) => {
const tracer = getTracer('express-server');
const span = tracer.startSpan('http.server.request', {
attributes: {
'http.method': req.method,
'http.url': req.url,
'http.target': req.path,
'http.host': req.hostname,
'http.client_ip': req.ip,
'http.user_agent': req.get('user-agent'),
},
});
// Propagate context to downstream services
const baggage = propagation.getActiveBaggage() || new W3CBaggagePropagation();
baggage.setEntry('user_id', { value: req.user.id });
baggage.setEntry('api_version', { value: 'v2' });
context.with(trace.setSpan(context.active(), span), () => {
res.on('finish', () => {
span.setAttributes({
'http.status_code': res.statusCode,
'http.response_time_ms': Date.now() - req.startTime,
});
span.end();
});
next();
});
});
Claude also generates the message queue instrumentation to maintain trace context:
// RabbitMQ producer: inject trace context into message headers
async function publishJob(job) {
const tracer = getTracer('rabbitmq-producer');
const span = tracer.startSpan('messaging.publish', {
attributes: { 'messaging.system': 'rabbitmq', 'messaging.destination': 'job_queue' },
});
const carrier = {};
propagation.inject(context.active(), carrier, defaultTextMapSetter);
await channel.sendToQueue('job_queue', Buffer.from(JSON.stringify(job)), {
headers: { traceparent: carrier.traceparent },
});
span.end();
}
// Python consumer: extract trace context from message headers
def process_job(ch, method, properties, body):
from opentelemetry.trace import get_tracer
from opentelemetry.propagators import TraceContextPropagator
from opentelemetry.trace.propagation.tracecontext import TraceContextPropagator
headers = dict(properties.headers or {})
ctx = TraceContextPropagator().extract(headers)
tracer = get_tracer(__name__)
with tracer.start_as_current_span('messaging.process', attributes={
'messaging.system': 'rabbitmq',
'messaging.operation': 'receive'
}) as span:
# Process job; trace context automatically flows downstream
db_result = query_database()
call_payment_api()
Strengths of Claude:
- Understands trace context propagation semantics (W3C Trace Context, Jaeger, Zipkin differences)
- Generates consistent span naming across services
- Designs attribute schemas that support correlation queries
- Suggests sampling strategies (probabilistic, tail-based, error-based)
Limitations:
- Cannot verify against your actual deployed services
- Requires detailed system topology description to be accurate
- May not know about team-specific attribute conventions
Best for: Initial observability architecture, migrating from old instrumentation, onboarding new team members to tracing best practices.
GitHub Copilot: Routine Instrumentation While Coding
Copilot excels at generating repetitive instrumentation code. When you start typing span creation, Copilot completes the pattern.
Real example in VS Code:
# You type:
def process_payment(order_id, amount):
tracer = get_tracer(__name__)
with tracer.start_as_current_span("payment.process") as span:
# Copilot suggests:
span.set_attribute("order.id", order_id)
span.set_attribute("payment.amount", amount)
span.set_attribute("payment.currency", "USD")
# Validate order
order = fetch_order(order_id)
span.set_attribute("order.status", order.status)
Strengths:
- Fast attribute addition without leaving your editor
- Learns your team’s naming patterns over time
- Works across Go, Python, JavaScript, Java
- Completes metric collection boilerplate
Weaknesses:
- Attributes may be inconsistent with other services
- Doesn’t understand trace context propagation; you must handle this manually
- May suggest redundant attributes
- Cannot design end-to-end tracing strategies
Best for: Adding instrumentation to existing code, completing repetitive span attributes, generating metric counters and histograms.
ChatGPT 4o: Debugging Broken Traces
When traces are missing context or don’t correlate between services, ChatGPT helps diagnose the issue. It’s particularly good at explaining context propagation problems.
Example interaction:
User: "My traces show the Express service and Python worker as
separate traces. They should be connected. The job ID is in the
message but not in the trace context."
ChatGPT 4o suggests:
1. Check that RabbitMQ headers are being injected with trace context
2. Verify Python consumer is extracting from message headers, not just env vars
3. Ensure the propagator matches (W3C Trace Context vs Jaeger)
4. Check that the Python consumer uses context.with() to activate the span
ChatGPT walks through the debugging process systematically, which is faster than searching docs.
Strengths:
- Good at explaining context propagation flow
- Quickly identifies common configuration mistakes
- Suggests logging statements that help debug tracing issues
- Available on free tier (slower inference)
Weaknesses:
- Cannot see your actual code or system setup
- May suggest outdated OpenTelemetry APIs
- Lacks deep understanding of different propagators
Best for: Troubleshooting broken traces, explaining why spans aren’t correlating, understanding context propagation concepts.
Tool Comparison Table
| Feature | Claude | Copilot | ChatGPT 4o |
|---|---|---|---|
| Span naming consistency | 9/10 | 6/10 | 7/10 |
| Context propagation design | 9/10 | 3/10 | 8/10 |
| Attribute schema design | 8/10 | 7/10 | 6/10 |
| Sampling strategy advice | 8/10 | 4/10 | 7/10 |
| Real-time completion | No | Yes | No |
| Cost per developer/month | $0-20 | $10-19 | $20 (Plus) |
| Debugging broken traces | Good | Poor | Excellent |
| Multi-service topology understanding | Excellent | Fair | Good |
Instrumentation Patterns by Tool
Pattern 1: HTTP Server Instrumentation (Claude)
Claude generates the complete middleware with proper error handling:
func tracingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "http.server.request",
trace.WithAttributes(
attribute.String("http.method", r.Method),
attribute.String("http.target", r.RequestURI),
attribute.String("http.host", r.Host),
))
defer span.End()
// Propagate trace context to downstream calls
newReq := r.WithContext(ctx)
rr := &responseRecorder{ResponseWriter: w}
next.ServeHTTP(rr, newReq)
span.SetAttributes(
attribute.Int("http.status_code", rr.statusCode),
attribute.String("http.status_text", http.StatusText(rr.statusCode)),
)
})
}
Pattern 2: Database Query Instrumentation (Copilot)
Copilot quickly completes db query spans:
def execute_query(query, params):
with tracer.start_as_current_span("db.statement") as span:
span.set_attribute("db.system", "postgresql")
span.set_attribute("db.name", "orders_db")
span.set_attribute("db.operation", "SELECT")
span.set_attribute("db.statement", query[:50]) # truncate for PII
start = time.time()
result = cursor.execute(query, params)
duration = (time.time() - start) * 1000
span.set_attribute("db.client.connections.usage", 5)
return result
Pattern 3: Message Queue Instrumentation (Claude)
Claude ensures context propagation across the queue:
# Producer
def send_message(topic, message, context_data):
carrier = {}
propagation.inject(context_data, carrier, defaultTextMapSetter)
producer.send(topic, value=json.dumps({
'data': message,
'trace_context': carrier
}))
# Consumer
def consume_messages():
for msg in consumer:
payload = json.loads(msg.value)
ctx = propagation.extract(payload['trace_context'])
with tracer.start_as_current_span("queue.process", attributes={
'messaging.system': 'kafka'
}, context=ctx) as span:
process_job(payload['data'])
Sampling Strategies
Claude can design sampling strategies that balance cost and observability:
Head-based sampling (simple but loses context):
sampler = ParentBased(TraceIDRatioBased(0.1)) # 10% of all traces
Tail-based sampling (preserves errors but requires collector):
# Jaeger or OTel Collector config
exporters:
- name: jaeger
sampling:
policies:
- type: error
threshold: 1 # Always sample errors
- type: latency
threshold: 100ms # Sample slow requests
- type: probabilistic
threshold: 0.01 # 1% of other requests
Configuration Across Environments
Claude generates environment-specific configurations:
# production: verbose for errors, sparse for happy path
OTEL_TRACES_SAMPLER=jaeger_remote
OTEL_TRACES_SAMPLER_ARG=http://otel-collector:14268/sampling
# staging: 10% of all traces for cost control
OTEL_TRACES_SAMPLER=traceidratio
OTEL_TRACES_SAMPLER_ARG=0.1
# development: 100% sampling to catch all issues
OTEL_TRACES_SAMPLER=always_on
Conclusion
Use Claude for designing your observability strategy and generating context propagation patterns across multiple services. Use Copilot for routine span and metric completion while actively coding. Use ChatGPT when traces break and you need to debug context propagation issues. The combination gives you architectural rigor upfront, fast development during implementation, and effective debugging when things go wrong.