Skip to content

Add OTLP logs signal to the Rust observability SDK#71

Merged
brentrager merged 1 commit into
mainfrom
logs-signal-rust
Jul 20, 2026
Merged

Add OTLP logs signal to the Rust observability SDK#71
brentrager merged 1 commit into
mainfrom
logs-signal-rust

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Problem

The SmooAI observability SDK emits traces + metrics into the product's OTLP endpoints, but not logs — so we can't join a service's log lines to the traces they belong to. This is the Rust/flagship dogfood reference for the SDK logs signal (pearls th-5dca7d / th-de3805, ADR-100); the other four languages follow this pattern.

Solution

Add the LOGS signal, sending into /v1/logs correlated to traces, reusing the existing traces/metrics OTLP setup as the template:

  • Same wiring, no new config keys — same endpoint (SMOOAI_OBSERVABILITY_ENDPOINT/v1/logs), same auth (static _TOKEN or M2M _AUTH_URL/_CLIENT_ID/_CLIENT_SECRET via the per-request AuthInjectingHttpClient), same enable/init path. Per-signal override honored via OTEL_EXPORTER_OTLP_LOGS_ENDPOINT.
  • Standard OTel appenderopentelemetry-appender-tracing (tracing→OTel log bridge) + opentelemetry-otlp LogExporter (HTTP/JSON, matching the traces exporter's transport) + opentelemetry_sdk SdkLoggerProvider. Matched the exact 0.32 versions already pinned; the only new crate is opentelemetry-appender-tracing 0.32.
  • Async-runtime BatchLogProcessor — same reactor-panic rationale as traces/metrics (SMOODEV-2045): the default thread-based processor block_ons on a bare OS thread where our reqwest-backed exporter has no Tokio reactor and aborts under panic = "abort".
  • Graceful no-op when disabled — no logs endpoint → no logger provider → tracing_appender_layer() returns None; traces/metrics behavior unchanged.

Trace/span correlation (the whole point)

severity←tracing level, body←message, resource service.name→service_name, event fields→attributes. For trace_id/span_id: the SDK logger enriches each record from the active opentelemetry::Context at emit time — which is the OTel-native context this crate's tower/gen_ai/reqwest spans already make current. So a log fired inside one of those spans carries its real W3C ids, automatically. Hosts install the bridge via the new OtelSdkHandle::tracing_appender_layer().

Verification

cargo build, cargo clippy --all-targets (clean), cargo fmt --check (clean), cargo test60 passed. New tests/logs_signal.rs:

  • log_within_active_span_carries_trace_and_span_id — asserts the emitted record's trace_id/span_id equal the active span's real ids. Correlation was actually verified, not just compiled: mutating the expected trace_id fails the test (real captured id 1732c136… vs the injected wrong value), confirming the assertion bites.
  • log_without_active_span_has_no_trace_context — no active span → no fabricated trace context.
  • Disabled-pipeline no-op covered by otel.rs unit tests (build_log_exporter is None; a fully-disabled handle yields no appender layer).

Wire format is OTLP/HTTP/JSON, matching the traces/metrics bytes the product already ingests, but the end-to-end product ingest was not exercised here (offline). No deploy.

Follow-up (now unblocked)

Dogfood wiring — api-prime / workers installing the appender layer and emitting logs through this pipeline.

🤖 Generated with Claude Code

Emit LOGS into the SmooAI observability product's /v1/logs OTLP endpoint,
correlated to traces — the Rust/flagship dogfood reference for the SDK logs
signal (th-5dca7d), matching ADR-100.

Reuses the existing traces/metrics OTLP setup as the template: same endpoint
(SMOOAI_OBSERVABILITY_ENDPOINT, logs -> /v1/logs), same auth (static token or
M2M token provider), same enable/init path, no new config keys. Built with the
standard OTel appender: opentelemetry-appender-tracing (tracing->OTel log
bridge) + opentelemetry-otlp LogExporter (HTTP/JSON, matching the traces
exporter transport) + opentelemetry_sdk SdkLoggerProvider. Uses the
async-runtime BatchLogProcessor for the same reactor-panic reason as
traces/metrics (SMOODEV-2045).

trace_id/span_id correlation: the SDK logger enriches each record from the
active opentelemetry::Context at emit time, which is the OTel-native context
this crate's tower/gen_ai/reqwest spans already make current — so a log fired
inside one of those spans carries its real W3C ids. The host installs the
bridge via the new OtelSdkHandle::tracing_appender_layer(). Graceful no-op when
no logs endpoint is configured; traces/metrics behavior unchanged.

Tests (tests/logs_signal.rs): a log emitted inside an active span carries that
span's trace_id/span_id (asserted against the real ids), and a log with no
active span carries no fabricated trace context. Disabled-pipeline no-op
covered in otel.rs unit tests.

Follow-up now unblocked: dogfood wiring (api-prime / workers installing the
appender layer and emitting logs).

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: dd72e7a

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

@brentrager
brentrager merged commit ba40366 into main Jul 20, 2026
6 checks passed
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