Skip to content

Correlate Python logs to active OTel span + bridge to stdlib logging#169

Open
brentrager wants to merge 1 commit into
mainfrom
py-otel-log-correlation
Open

Correlate Python logs to active OTel span + bridge to stdlib logging#169
brentrager wants to merge 1 commit into
mainfrom
py-otel-log-correlation

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

The Python logger fabricated a uuid trace id per context, so its logs never correlated to real OTel traces. And its lines never flowed through the stdlib logging facade, so an installed @smooai/observability LoggingHandler never saw them. (Pearl th-de3805; matches ADR-100.)

Solution

  • Correlation: _build_record now replaces the fabricated uuid with the active OTel span's real W3C trace_id/span_id (get_current_span().get_span_context()), adding a spanId field. Falls back to the prior uuid behavior only when no span is active.
  • Bridge: every emitted line is forwarded through a dedicated stdlib logger (smooai_logger) so an observability LoggingHandler on root turns it into an OTLP log record — correlated to the active span, which that handler reads. A NullHandler keeps it a true no-op when observability isn't installed. The file-rotation logger no longer propagates its pretty ANSI blob to root (would otherwise leak into the bridge).
  • Depends on opentelemetry-api only — never on @smooai/observability (circular).

Tests (verified, not just compiled)

python/tests/test_otel_correlation.py, api-only NonRecordingSpan:

  • log inside a span carries that span's trace_id/span_id
  • no span → uuid fallback, no spanId
  • bridged line reaches the stdlib root logger
  • bridged record is emitted inside the span context (so an obs handler correlates it)

Full Python suite: 58 passed, ruff lint + format clean, basedpyright 0 errors.

🤖 Generated with Claude Code

Replaces the fabricated uuid trace id with the ACTIVE OTel span's real W3C
trace_id/span_id when a span is active (get_current_span().get_span_context()),
falling back to the prior uuid behavior only when no span is active. Adds a
spanId field to the record.

Also bridges every emitted line through a dedicated stdlib `logging` logger
(smooai_logger) so an installed @smooai/observability LoggingHandler on the root
logger turns each line into an OTLP log record — correlated to the active span,
which that handler reads. NullHandler keeps it a true no-op when observability
isn't installed. The file-rotation logger no longer propagates its pretty ANSI
blob to root (would otherwise leak into the OTLP bridge).

Depends on opentelemetry-api ONLY — never on @smooai/observability (circular).
Verified with an api-only NonRecordingSpan: a log inside the span carries that
span's ids; no span → uuid fallback; the bridged line reaches root within the
span context.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S2bM94GAnVjYSSv1x7HKRB
@changeset-bot

changeset-bot Bot commented Jul 20, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 9be42bc

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant