install
source · Clone the upstream repo
git clone https://github.com/Intense-Visions/harness-engineering
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/codex/otel-logging-pattern" ~/.claude/skills/intense-visions-harness-engineering-otel-logging-pattern-88a50b && rm -rf "$T"
manifest:
agents/skills/codex/otel-logging-pattern/SKILL.mdsource content
OpenTelemetry Logging Pattern
Correlate structured logs with distributed traces using OpenTelemetry context for unified observability
When to Use
- Connecting log entries to the trace that generated them
- Querying logs for a specific trace ID to understand request flow
- Migrating from plain text logs to structured, correlated logs
- Sending logs to the same backend as traces and metrics via OTLP
Instructions
- Use structured logging (JSON) with a library like Pino or Winston.
- Inject trace context (trace ID, span ID) into every log entry automatically.
- Use OpenTelemetry's log bridge API or instrument your existing logger to include context.
- Log at appropriate levels: ERROR for failures, WARN for degradation, INFO for significant events, DEBUG for troubleshooting.
- Include relevant attributes in log entries (request ID, user ID, operation name).
- Send logs via OTLP exporter to correlate with traces in your observability backend.
// logger/otel-logger.ts import pino from 'pino'; import { trace, context } from '@opentelemetry/api'; function getTraceContext(): Record<string, string> { const span = trace.getActiveSpan(); if (!span) return {}; const spanContext = span.spanContext(); return { trace_id: spanContext.traceId, span_id: spanContext.spanId, trace_flags: `0${spanContext.traceFlags.toString(16)}`, }; } export const logger = pino({ level: process.env.LOG_LEVEL || 'info', formatters: { log(object) { // Automatically inject trace context into every log entry return { ...object, ...getTraceContext() }; }, }, serializers: { err: pino.stdSerializers.err, }, }); // Create child loggers for specific modules export function createLogger(module: string) { return logger.child({ module }); }
// Usage in service code import { createLogger } from '../logger/otel-logger'; const log = createLogger('order-service'); export async function createOrder(userId: string, items: OrderItem[]): Promise<Order> { log.info({ userId, itemCount: items.length }, 'Creating order'); try { const order = await db.orders.create({ userId, items }); log.info({ orderId: order.id, userId }, 'Order created successfully'); return order; } catch (error) { log.error({ err: error, userId }, 'Failed to create order'); throw error; } } // Output (with trace context automatically injected): // {"level":"info","time":1700000000,"module":"order-service", // "trace_id":"abc123","span_id":"def456","trace_flags":"01", // "userId":"u1","itemCount":3,"msg":"Creating order"}
Details
OpenTelemetry Logs SDK (direct): For sending logs via OTLP without a logging library bridge:
import { logs, SeverityNumber } from '@opentelemetry/api-logs'; const logger = logs.getLogger('order-service'); logger.emit({ severityNumber: SeverityNumber.INFO, severityText: 'INFO', body: 'Order created', attributes: { 'order.id': orderId, 'user.id': userId }, });
Pino + OpenTelemetry instrumentation:
// Auto-instrumentation adds trace context to Pino logs import { PinoInstrumentation } from '@opentelemetry/instrumentation-pino'; const pinoInstrumentation = new PinoInstrumentation({ logHook: (span, record) => { record['service.name'] = 'order-service'; }, });
Winston integration:
import winston from 'winston'; import { trace } from '@opentelemetry/api'; const otelFormat = winston.format((info) => { const span = trace.getActiveSpan(); if (span) { const ctx = span.spanContext(); info.trace_id = ctx.traceId; info.span_id = ctx.spanId; } return info; }); const logger = winston.createLogger({ format: winston.format.combine(otelFormat(), winston.format.json()), transports: [new winston.transports.Console()], });
Log levels and when to use them:
- ERROR: Something failed and needs attention. Include error details and context.
- WARN: Something unexpected happened but the operation continued (fallback used, retry needed).
- INFO: Significant business events (order created, user logged in, payment processed).
- DEBUG: Detailed troubleshooting information (query parameters, cache hits, decision branches).
Correlation in observability backends: With trace ID in logs, you can: search Grafana Loki by trace ID, click from a Jaeger trace to its logs, filter Datadog logs by trace, and build Kibana dashboards that link logs to traces.
Source
https://opentelemetry.io/docs/concepts/signals/logs/
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.