Skip to content

SMOODEV-1067d: OTel-first node Client — emit to spans natively, drop bridge - #6

Merged
brentrager merged 1 commit into
mainfrom
smoodev-1067d-otel-native-client
May 18, 2026
Merged

SMOODEV-1067d: OTel-first node Client — emit to spans natively, drop bridge#6
brentrager merged 1 commit into
mainfrom
smoodev-1067d-otel-native-client

Conversation

@brentrager

Copy link
Copy Markdown
Contributor

Summary

Rebuild Node's Client so it speaks OpenTelemetry natively instead of running a parallel Smoo-native HTTP transport with an OTel "bridge" stacked on top. The bridge pattern from #4 was the wrong architecture — OTel should be the foundation, not an add-on. @smooai/logger is now an optional nice-to-have, not load-bearing.

What changed architecturally

Before (PR #4 — wrong) After (this PR)
Smoo Client → Smoo HTTP transport → backend ingest Smoo Client → OTel span events → OTLP/HTTP → backend
bridgeClientToOtel() wraps captureException to ALSO record on OTel spans Smoo Client IS the OTel emitter on Node — no bridging
Two parallel wire formats live in the pipeline One: OTLP
@smooai/logger's CONTEXT is the canonical correlation source OTel ambient context (W3C trace context + baggage) is canonical

Why this is the right order

The user (Smoo platform owner) was explicit: do this OTel-first, with logger as a nice addition. Any external consumer of @smooai/observability who uses winston / pino / bunyan / console can now pipe readOtelCorrelation() into their format and get the same trace-id on logs, traces, and Smoo error groups — no logger lock-in.

Breaking changes

@smooai/observability 0.3 → 0.4:

  • makeNodeTransport removed (re-exported from node). No longer needed.
  • Client._registerTransport is a no-op on Node once a capture handler is registered (default in Client.init). Browser unchanged.
  • New seam: Client._registerCaptureHandler(handler | null) for advanced consumers.

@smooai/observability-otel 0.1 → 0.2:

  • bridgeClientToOtel() removed. There's nothing to bridge.
  • setupOtelSdk() and readOtelCorrelation() kept.

Browser intentionally untouched

The browser side stays on the Smoo-native Transport. OTel's browser SDK is ~150KB gzipped; for customer-facing sites we keep the lightweight Smoo client. Browser emits the same Smoo ObservabilityEvent JSON to the backend, which the next backend-ingest PR will translate into OTLP-compatible storage.

What's still coming

Phase Status
Foundation: setupOtelSdk, readOtelCorrelation (PR #4) merged
OTel-first Node Client (this PR) open
Backend ingest accepts OTLP/HTTP alongside Smoo-native not started
Browser emits OTLP-shaped JSON (so ingest becomes single-format) not started
@smooai/logger v5 writes CONTEXT to OTel baggage not started
Auto-instrumentations rollout across backend Lambdas not started

Test plan

  • 33 tests on @smooai/observability core (9 new for OTel-native capture).
  • 5 tests on @smooai/observability-otel.
  • pnpm --filter @smooai/observability typecheck clean.
  • pnpm --filter @smooai/observability-otel typecheck clean.
  • pnpm --filter @smooai/observability build clean.
  • pnpm --filter @smooai/observability-otel build clean.

🤖 Generated with Claude Code

…bridge

Rebuild Node's Client so it speaks OpenTelemetry natively instead of
running a parallel Smoo-native HTTP transport with an OTel "bridge" on
top. Every captureException / captureMessage becomes a span event on the
active OTel span (or a synthetic one when none is active), with
SpanStatusCode.ERROR for exceptions and OTLP-shaped attributes
(enduser.id, enduser.org_id, service.version, deployment.environment.name,
smoo.tag.*, smoo.event_id, smoo.level). The OTel SDK handles batching,
retry, and wire format; the Smoo SDK does not run a parallel HTTP
pipeline on Node.

@smooai/logger is now optional, not load-bearing. The Smoo SDK has no
compile-time dep on it. When present, its CONTEXT global feeds OTel
baggage (separate package). When absent, OTel ambient context is the
single source of correlation truth — winston / pino / bunyan / console
users get the same trace-id flowing through logs / traces / Smoo error
groups by reading readOtelCorrelation().

Architectural changes:

  - packages/core/src/client.ts: new _registerCaptureHandler seam.
    Routes captureException/captureMessage through the handler instead
    of the HTTP transport when registered. Browser keeps the old
    transport path because OTel browser SDK is too heavy for
    customer-facing bundles.
  - packages/core/src/node/otel-capture.ts (new): the Node OTel-native
    capture handler. Records exceptions on the active OTel span, mints
    a synthetic 'observability.captureException' span when no span is
    active (background workers, raw process listeners). Maps Scope user
    to enduser.* attributes per OTel semantic conventions.
  - packages/core/src/node/index.ts: Client.init on Node now wires
    registerOtelCapture by default. autoInstrumentation:false opts out
    of process error handlers (capture handler is always registered).
  - packages/core/src/node/transport.ts: DELETED. No longer needed —
    OTel SDK is the transport.
  - packages/otel/src/bridge-to-client.ts: DELETED. The Smoo Client
    already emits to OTel natively; there's nothing to bridge.

Tests: 33 green on core (9 new for OTel-native capture, was 24).
5 on otel package. Typecheck + build clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented May 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 167cec0

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@smooai/observability Minor
@smooai/observability-otel Minor
@smooai/observability-next Minor
@smooai/observability-react Minor

Not sure what this means? Click here to learn what changesets are.

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

@brentrager
brentrager merged commit 365b90c into main May 18, 2026
1 check 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