@xemahq/observability-nest
v0.1.0
Published
Layer 1 SDK — the ONE vendor-agnostic telemetry seam for Xema services. A thin facade over OpenTelemetry: services import withSpan/getMeter/getTracer from here (never @opentelemetry/* directly), so the tracing dependency lives in exactly one place. A Tele
Readme
@xemahq/observability-nest
This package belongs to Layer 1 — SDK/runtime. It depends only on Layer‑0 concerns and npm libraries (OpenTelemetry), never on a Xema service or a higher layer.
It is the one vendor‑agnostic telemetry seam for Xema services. Service code
imports withSpan / getMeter / getTracer from here and never imports
@opentelemetry/* directly, so the tracing dependency lives in exactly one
place. The engine underneath is OpenTelemetry; we add no telemetry data
model, propagation, sampling, exporter, or UI of our own — reinventing any of
that is a non‑goal.
Two swap‑seams, so you are never trapped
- Backend — everything exports OTLP to an OpenTelemetry Collector; point it at Jaeger, SigNoz, Datadog, Honeycomb, … via Collector config. No application code changes.
- SDK — a
TelemetryProvideris chosen at boot.OtelTelemetryProviderstarts the OTel SDK;NoopTelemetryProviderstarts nothing (the OTel API's built‑in no‑op stays in place). A custom provider can replace OpenTelemetry entirely. This is what makes observability optional with zero service‑code changes.
Enabled / disabled
Resolved from env (see resolveTelemetryConfig):
| Var | Meaning |
| --- | --- |
| OTEL_EXPORTER_OTLP_ENDPOINT | OTLP/HTTP base (e.g. http://otel-collector:4318). Presence turns telemetry on by default. |
| XEMA_TELEMETRY_ENABLED | true/false to force on/off regardless of endpoint. |
| XEMA_TELEMETRY_SAMPLE_RATIO | Head sample ratio in [0,1] (default 1). |
Enabled but no endpoint → fails fast (never starts blind). Endpoint set but unreachable → OTel logs export errors (visible, non‑fatal). Disabled → no SDK, no overhead.
Usage
// main.ts — FIRST statement, before NestFactory / Prisma / http server load,
// so auto-instrumentation can patch them.
import { startTelemetry } from '@xemahq/observability-nest';
startTelemetry({ serviceName: 'agent-session-api', serviceVersion: version });
// app.module.ts — DI handle + graceful-shutdown flush.
import { ObservabilityModule } from '@xemahq/observability-nest';
@Module({ imports: [ObservabilityModule.forRoot({ serviceName: 'agent-session-api' })] })
export class AppModule {}
// anywhere — instrument a unit of work.
import { withSpan } from '@xemahq/observability-nest';
await withSpan('session.launch.resolve-config', async (span) => {
span.setAttributes({ 'session.id': sessionId });
return resolveConfig();
});withSpan opens an active span (nested spans + auto‑instrumentation parent
to it), records exceptions + ERROR status on throw, and always ends the span.
When telemetry is off it is a near‑zero‑cost pass‑through.
