Skip to content

feat(telemetry): inject traceId/spanId into debug log files for OTel correlation #3846

@doudouOUC

Description

@doudouOUC

Background

Alibaba Cloud OpenTelemetry documentation recommends writing traceId and spanId into application logs so that trace data and log data can be correlated in backends (e.g., SLS, Grafana). Currently, qwen-code debug log files (~/.qwen/debug/{sessionId}.txt) contain timestamps and log levels, but no trace context — making it impossible to join a log line to its corresponding OTel span.

Proposed Change

Inject [trace_id=xxx span_id=yyy] into every debug log line. Priority:

  1. Active OTel span — if a real span is active (non-zero traceId), use its traceId/spanId
  2. Session-derived fallback — if no active span, derive a deterministic traceId from sessionId via SHA-256 (same algorithm as LogToSpanProcessor), and generate a stable per-session spanId
  3. No context — omit the trace fields entirely

Example output:

2026-01-24T10:30:00.000Z [DEBUG] [OTEL] [trace_id=aabb...8899 span_id=1122334455667788] OpenTelemetry SDK started successfully.
2026-01-24T10:30:01.123Z [DEBUG] [tool.Bash] [trace_id=aabb...8899 span_id=deadbeef12345678] Tool executed

Implementation

New files

  • packages/core/src/telemetry/trace-id-utils.ts — shared deriveTraceId, randomSpanId, randomHexString; used by both LogToSpanProcessor and debugLogger to ensure consistent traceIds
  • packages/core/src/telemetry/session-context.ts — neutral singleton module to pass session root context from sdk.ts to tracer.ts without circular dependency
  • packages/core/src/telemetry/tracer.tswithSpan (auto-lifecycle async wrapper), startSpanWithContext (manual lifecycle for async generators), createSessionRootContext

Modified files

  • debugLogger.tsgetTraceContext() with OTel-first priority; buildLogLine includes trace fields; fallback spanId cached per session
  • sdk.ts — call setSessionContext(createSessionRootContext(sessionId)) after SDK start; clear on shutdown
  • log-to-span-processor.ts — remove local deriveTraceId/randomHexString, import from trace-id-utils
  • loggingContentGenerator.ts — wrap generateContent in withSpan; use startSpanWithContext + runInContext for generateContentStream so the span covers the full stream lifetime
  • client.ts — wrap generateContent in withSpan
  • coreToolScheduler.ts — wrap executeSingleToolCall in withSpan; mark tool error/denial paths as SpanStatusCode.ERROR

Testing

All existing tests pass. New tests added for:

  • Trace context injection in log lines
  • OTel span priority over session fallback
  • Zero-traceId fallback behavior
  • Stable fallback spanId per session
  • deriveTraceId determinism and format

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions