SMOODEV-1067d: OTel-first node Client — emit to spans natively, drop bridge - #6
Merged
Merged
Conversation
…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 detectedLatest commit: 167cec0 The changes in this PR will be included in the next version bump. This PR includes changesets to release 4 packages
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/loggeris now an optional nice-to-have, not load-bearing.What changed architecturally
bridgeClientToOtel()wraps captureException to ALSO record on OTel spans@smooai/logger's CONTEXT is the canonical correlation sourceWhy 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/observabilitywho uses winston / pino / bunyan / console can now pipereadOtelCorrelation()into their format and get the same trace-id on logs, traces, and Smoo error groups — no logger lock-in.Breaking changes
@smooai/observability0.3 → 0.4:makeNodeTransportremoved (re-exported fromnode). No longer needed.Client._registerTransportis a no-op on Node once a capture handler is registered (default inClient.init). Browser unchanged.Client._registerCaptureHandler(handler | null)for advanced consumers.@smooai/observability-otel0.1 → 0.2:bridgeClientToOtel()removed. There's nothing to bridge.setupOtelSdk()andreadOtelCorrelation()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
ObservabilityEventJSON to the backend, which the next backend-ingest PR will translate into OTLP-compatible storage.What's still coming
setupOtelSdk,readOtelCorrelation(PR #4)@smooai/loggerv5 writes CONTEXT to OTel baggageTest plan
@smooai/observabilitycore (9 new for OTel-native capture).@smooai/observability-otel.pnpm --filter @smooai/observability typecheckclean.pnpm --filter @smooai/observability-otel typecheckclean.pnpm --filter @smooai/observability buildclean.pnpm --filter @smooai/observability-otel buildclean.🤖 Generated with Claude Code