@semiont/observability 0.5.28 → 0.5.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/node.d.ts +3 -3
- package/dist/node.js.map +1 -1
- package/dist/process-logger.d.ts +1 -1
- package/dist/process-logger.js.map +1 -1
- package/dist/web.d.ts +1 -1
- package/dist/web.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ OpenTelemetry-based tracing and metrics for [Semiont](https://github.com/The-AI-
|
|
|
15
15
|
Semiont's observability is layered:
|
|
16
16
|
|
|
17
17
|
- **Tier 1 — `busLog`** (in [`@semiont/core`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/core)): a 5-op grep-friendly timeline at the `ITransport` contract layer (`EMIT`, `RECV`, `SSE`, `PUT`, `GET`). Always on, always free.
|
|
18
|
-
- **Tier 2 — this package**: real OpenTelemetry traces + metrics, with W3C trace-context propagation across the bus's HTTP and SSE legs so a single user action produces one trace spanning
|
|
18
|
+
- **Tier 2 — this package**: real OpenTelemetry traces + metrics, with W3C trace-context propagation across the bus's HTTP and SSE legs so a single user action produces one trace spanning Browser → gateway → worker → smelter.
|
|
19
19
|
- **Tier 3** — log correlation and dashboards.
|
|
20
20
|
|
|
21
21
|
This package does not implement any platform domain logic; it provides the spanning helpers and metric recorders the rest of the codebase calls.
|
|
@@ -31,7 +31,7 @@ npm install @semiont/observability
|
|
|
31
31
|
Initialize once at the process entry point, before any spanning code runs:
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
|
-
// worker-main.ts (or
|
|
34
|
+
// worker-main.ts (or gateway index.ts, etc.)
|
|
35
35
|
import { initObservabilityNode } from '@semiont/observability/node';
|
|
36
36
|
|
|
37
37
|
initObservabilityNode({ serviceName: 'semiont-worker' });
|
|
@@ -64,7 +64,7 @@ Configuration is via the standard `OTEL_*` env vars:
|
|
|
64
64
|
|
|
65
65
|
## Process logger (Node)
|
|
66
66
|
|
|
67
|
-
For long-lived Node entry points (
|
|
67
|
+
For long-lived Node entry points (gateway, workers, smelter), the package exposes a winston-based structured logger that auto-correlates each line with the active span:
|
|
68
68
|
|
|
69
69
|
```ts
|
|
70
70
|
// worker-main.ts (or smelter-main.ts, etc.)
|
|
@@ -82,10 +82,10 @@ Reads `LOG_LEVEL` (default `info`) and `LOG_FORMAT` (`json` default, `simple` fo
|
|
|
82
82
|
// SPA entry point (main.tsx)
|
|
83
83
|
import { initObservabilityWeb } from '@semiont/observability/web';
|
|
84
84
|
|
|
85
|
-
initObservabilityWeb({ serviceName: 'semiont-
|
|
85
|
+
initObservabilityWeb({ serviceName: 'semiont-browser' });
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
Web init wires up the same universal API plus browser-appropriate context propagation. Spans created in the SPA propagate to the
|
|
88
|
+
Web init wires up the same universal API plus browser-appropriate context propagation. Spans created in the SPA propagate to the gateway via the bus's `_trace` payload field.
|
|
89
89
|
|
|
90
90
|
## Universal API
|
|
91
91
|
|
package/dist/index.d.ts
CHANGED
|
@@ -84,7 +84,7 @@ declare function withTraceparent<T>(carrier: TraceCarrier | undefined, fn: () =>
|
|
|
84
84
|
* `withSpan` calls across handler bodies.
|
|
85
85
|
*
|
|
86
86
|
* The span's parent is the active context at the time the handler
|
|
87
|
-
* fires — which is the `bus.dispatch:<channel>` span on the
|
|
87
|
+
* fires — which is the `bus.dispatch:<channel>` span on the gateway
|
|
88
88
|
* (Subject.next runs synchronously inside the dispatch span), or the
|
|
89
89
|
* `bus.emit:<channel>` span when an actor emits to itself.
|
|
90
90
|
*/
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @semiont/observability — public API.\n *\n * Universal surface (works in Node + browser). For SDK *initialization*,\n * import from `@semiont/observability/node` or `/web` at the process entry\n * point. Everything else uses this module.\n *\n * Tier 2 of `.plans/OBSERVABILITY.md`. The public surface:\n *\n * - `withSpan(name, fn, options?)` — wrap an async block in a span;\n * `options` carries `kind` and `attrs`.\n * - `withActorSpan(actor, channel, fn, extraAttrs?)` — consumer-span\n * wrapper for bus-event handlers, with handler-duration recording.\n * - `injectTraceparent(payload)` / `extractTraceparent(payload)` — W3C\n * trace-context propagation across the SSE channel (the bus payload\n * gets a `_trace?: { traceparent }` sibling to `correlationId`).\n * - `withTraceparent(carrier, fn)` — run `fn` with the incoming\n * traceparent as the parent context.\n * - `getActiveTraceparent()` — read the active span's traceparent for\n * manual propagation (e.g. attaching to a fetch header or SSE field).\n * - `getLogTraceContext()` — active `trace_id` / `span_id` for log-line\n * correlation.\n * - Metric recorders (`recordBusEmit`, `recordHandlerDuration`,\n * `recordJobOutcome`, `recordSubscriberConnect` / `Disconnect`,\n * `recordInferenceUsage`) and gauge providers\n * (`registerJobQueueProvider`, `registerVectorIndexSizeProvider`).\n *\n * No-op when no exporter is configured: `@opentelemetry/api`'s default\n * tracer is a no-op, so `withSpan` is essentially free until\n * `initObservability*()` runs.\n */\n\nimport {\n context,\n isSpanContextValid,\n metrics,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace,\n type Attributes,\n type Counter,\n type Histogram,\n type ObservableGauge,\n type Span,\n type UpDownCounter,\n} from '@opentelemetry/api';\nimport { setBusLogTraceIdProvider } from '@semiont/core';\n\n// Wire `busLog`'s trace-id provider once at module load. When an OTel\n// SDK is initialized (and a span is active when `busLog` fires), the\n// emitted line gets a `trace=<8hex>` suffix that correlates the\n// grep-timeline with the trace UI. No-op when no SDK is active.\nsetBusLogTraceIdProvider(() => {\n const span = trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n if (!isSpanContextValid(ctx)) return undefined;\n return ctx.traceId;\n});\n\nconst TRACER_NAME = 'semiont';\n\nconst tracer = () => trace.getTracer(TRACER_NAME);\n\n// ── withSpan ───────────────────────────────────────────────────────────\n\n/**\n * Wrap an async block in a span. The span is started before `fn` runs and\n * ended after it resolves or rejects; exceptions are recorded and the span\n * status is set to ERROR. `kind` defaults to INTERNAL.\n */\nexport async function withSpan<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n options?: { kind?: SpanKind; attrs?: Attributes },\n): Promise<T> {\n const span = tracer().startSpan(name, {\n kind: options?.kind ?? SpanKind.INTERNAL,\n ...(options?.attrs ? { attributes: options.attrs } : {}),\n });\n try {\n return await context.with(trace.setSpan(context.active(), span), () => fn(span));\n } catch (err) {\n span.recordException(err as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n throw err;\n } finally {\n span.end();\n }\n}\n\n// ── Traceparent on bus payloads ────────────────────────────────────────\n\nconst TRACE_FIELD = '_trace';\n\n/**\n * Sibling of `correlationId` on bus payloads. Lives on the SSE event body\n * because SSE has no header trailer; the SDK strips it before delivering\n * the payload to subscribers. Additive — payloads without `_trace` parse\n * unchanged.\n */\nexport interface TraceCarrier {\n /** W3C `traceparent` header value (`00-<traceId>-<spanId>-<flags>`). */\n traceparent: string;\n /** W3C `tracestate` header value (vendor-specific extensions). */\n tracestate?: string;\n}\n\n/**\n * Read the active span's W3C traceparent (and tracestate). Returns\n * `undefined` if no span is active.\n */\nexport function getActiveTraceparent(): TraceCarrier | undefined {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n const traceparent = carrier['traceparent'];\n if (!traceparent) return undefined;\n return carrier['tracestate']\n ? { traceparent, tracestate: carrier['tracestate'] }\n : { traceparent };\n}\n\n/**\n * Attach the active span's trace-context to a payload object as\n * `_trace`. No-op when no span is active. Returns the same object\n * reference for chaining.\n */\nexport function injectTraceparent<T extends Record<string, unknown>>(payload: T): T {\n const carrier = getActiveTraceparent();\n if (carrier) {\n (payload as Record<string, unknown>)[TRACE_FIELD] = carrier;\n }\n return payload;\n}\n\n/**\n * Strip and return the `_trace` field from a payload. Mutates `payload`.\n * The field is internal plumbing and should not be visible to subscribers.\n */\nexport function extractTraceparent<T extends Record<string, unknown>>(\n payload: T,\n): TraceCarrier | undefined {\n const carrier = (payload as Record<string, unknown>)[TRACE_FIELD] as\n | TraceCarrier\n | undefined;\n if (carrier !== undefined) {\n delete (payload as Record<string, unknown>)[TRACE_FIELD];\n }\n if (!carrier || typeof carrier.traceparent !== 'string') return undefined;\n return carrier;\n}\n\n/**\n * Run `fn` with the given W3C traceparent set as the parent context.\n * Any spans started inside `fn` will be children of the incoming trace.\n * No-op if `carrier` is undefined.\n */\nexport function withTraceparent<T>(\n carrier: TraceCarrier | undefined,\n fn: () => T,\n): T {\n if (!carrier) return fn();\n const carrierObj: Record<string, string> = { traceparent: carrier.traceparent };\n if (carrier.tracestate) carrierObj['tracestate'] = carrier.tracestate;\n const ctx = propagation.extract(context.active(), carrierObj);\n return context.with(ctx, fn);\n}\n\n// ── Actor handler convenience ──────────────────────────────────────────\n\n/**\n * Wrap a bus-event handler in an `actor.<name>:<channel>` consumer span.\n * Used at every `eventBus.get(channel).subscribe(handler)` site inside\n * an actor (Stower, Gatherer, Matcher, Browser, Smelter), to attribute\n * each in-process subscriber's work to a span without scattering manual\n * `withSpan` calls across handler bodies.\n *\n * The span's parent is the active context at the time the handler\n * fires — which is the `bus.dispatch:<channel>` span on the backend\n * (Subject.next runs synchronously inside the dispatch span), or the\n * `bus.emit:<channel>` span when an actor emits to itself.\n */\nexport async function withActorSpan<T>(\n actor: string,\n channel: string,\n fn: (span: Span) => Promise<T> | T,\n extraAttrs?: Attributes,\n): Promise<T> {\n const start = performance.now();\n try {\n return await withSpan(`actor.${actor}:${channel}`, fn, {\n kind: SpanKind.CONSUMER,\n attrs: {\n actor,\n 'bus.channel': channel,\n ...(extraAttrs ?? {}),\n },\n });\n } finally {\n recordHandlerDuration(actor, channel, performance.now() - start);\n }\n}\n\n// ── Log correlation ────────────────────────────────────────────────────\n\n/**\n * Read the active span's `trace_id` / `span_id` for log-line correlation.\n * Tier 3 of `.plans/OBSERVABILITY.md`. Each structured log line gets\n * tagged with these so a log query in CloudWatch / Loki / Datadog can\n * jump to the trace in Tempo / Jaeger / X-Ray.\n *\n * Returns `undefined` if no span is active, or if the active span's\n * context is invalid (uninitialized SDK, no-op tracer).\n */\nexport function getLogTraceContext(): { trace_id: string; span_id: string } | undefined {\n const span = trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n if (!isSpanContextValid(ctx)) return undefined;\n return { trace_id: ctx.traceId, span_id: ctx.spanId };\n}\n\n// ── Metrics — Tier 3 ───────────────────────────────────────────────────\n\nconst METER_NAME = 'semiont';\n\nconst meter = () => metrics.getMeter(METER_NAME);\n\nlet _busEmitCounter: Counter | undefined;\nlet _handlerDurationHistogram: Histogram | undefined;\nlet _jobOutcomeCounter: Counter | undefined;\nlet _jobDurationHistogram: Histogram | undefined;\nlet _gatherDegradeCounter: Counter | undefined;\nlet _inferenceCallsCounter: Counter | undefined;\nlet _inferenceTokensCounter: Counter | undefined;\nlet _inferenceDurationHistogram: Histogram | undefined;\nlet _sseSubscribers: UpDownCounter | undefined;\nlet _jobQueueGauge: ObservableGauge | undefined;\nlet _jobQueueProvider: (() => Promise<JobQueueSnapshot> | JobQueueSnapshot) | undefined;\nlet _vectorIndexSizeGauge: ObservableGauge | undefined;\nlet _vectorIndexSizeProvider: (() => Promise<number> | number) | undefined;\n\n/** Snapshot of job-queue contents by status. Match `JobQueue.getStats()`. */\nexport interface JobQueueSnapshot {\n pending: number;\n running: number;\n complete: number;\n failed: number;\n cancelled: number;\n}\n\nfunction busEmitCounter(): Counter {\n if (!_busEmitCounter) {\n _busEmitCounter = meter().createCounter('semiont.bus.emit', {\n description: 'Bus emits by channel and scope',\n });\n }\n return _busEmitCounter;\n}\n\nfunction handlerDurationHistogram(): Histogram {\n if (!_handlerDurationHistogram) {\n _handlerDurationHistogram = meter().createHistogram('semiont.handler.duration', {\n description: 'In-process actor handler duration',\n unit: 'ms',\n });\n }\n return _handlerDurationHistogram;\n}\n\nfunction jobOutcomeCounter(): Counter {\n if (!_jobOutcomeCounter) {\n _jobOutcomeCounter = meter().createCounter('semiont.job.outcome', {\n description: 'Worker job completions by type and outcome',\n });\n }\n return _jobOutcomeCounter;\n}\n\nfunction jobDurationHistogram(): Histogram {\n if (!_jobDurationHistogram) {\n _jobDurationHistogram = meter().createHistogram('semiont.job.duration', {\n description: 'Worker job duration by type',\n unit: 'ms',\n });\n }\n return _jobDurationHistogram;\n}\n\nfunction inferenceCallsCounter(): Counter {\n if (!_inferenceCallsCounter) {\n _inferenceCallsCounter = meter().createCounter('semiont.inference.calls', {\n description: 'Inference API calls by provider, model, and outcome',\n });\n }\n return _inferenceCallsCounter;\n}\n\nfunction inferenceTokensCounter(): Counter {\n if (!_inferenceTokensCounter) {\n _inferenceTokensCounter = meter().createCounter('semiont.inference.tokens', {\n description: 'Inference token usage by provider, model, and direction',\n });\n }\n return _inferenceTokensCounter;\n}\n\nfunction inferenceDurationHistogram(): Histogram {\n if (!_inferenceDurationHistogram) {\n _inferenceDurationHistogram = meter().createHistogram('semiont.inference.duration', {\n description: 'Inference call duration by provider, model, and outcome',\n unit: 'ms',\n });\n }\n return _inferenceDurationHistogram;\n}\n\nfunction sseSubscribersCounter(): UpDownCounter {\n if (!_sseSubscribers) {\n _sseSubscribers = meter().createUpDownCounter('semiont.sse.subscribers', {\n description: 'Active SSE subscribers',\n });\n }\n return _sseSubscribers;\n}\n\n/** Increment the bus-emit counter. Called at every transport `emit` site. */\nexport function recordBusEmit(channel: string, scope?: string): void {\n busEmitCounter().add(1, {\n 'bus.channel': channel,\n ...(scope ? { 'bus.scope': scope } : {}),\n });\n}\n\n/** Record an in-process actor handler's duration. */\nexport function recordHandlerDuration(actor: string, channel: string, durationMs: number): void {\n handlerDurationHistogram().record(durationMs, {\n actor,\n 'bus.channel': channel,\n });\n}\n\n/** Record a worker job's outcome and duration. */\nexport function recordJobOutcome(jobType: string, outcome: 'completed' | 'failed', durationMs: number): void {\n jobOutcomeCounter().add(1, { 'job.type': jobType, 'job.outcome': outcome });\n jobDurationHistogram().record(durationMs, { 'job.type': jobType, 'job.outcome': outcome });\n}\n\nfunction gatherDegradeCounter(): Counter {\n if (!_gatherDegradeCounter) {\n _gatherDegradeCounter = meter().createCounter('semiont.gather.degraded', {\n description: 'Gathers that degraded because an eventually-consistent projection did not catch up within its read barrier (vectors: absent semanticContext; graph: projection-lag failure). Labeled by projection.',\n });\n }\n return _gatherDegradeCounter;\n}\n\n/**\n * Record a gather degraded by a projection read barrier: `'vectors'` — the\n * Smelter settle barrier timed out (semanticContext shipped absent);\n * `'graph'` — the Weaver applied barrier + poll floor exhausted (projection\n * lag surfaced as a distinct failure). Fleet-alertable counterpart of the\n * `[gather DEGRADED]` L4 breadcrumbs — a rising rate on either label means\n * that pipeline is not keeping up.\n */\nexport function recordGatherDegrade(projection: 'graph' | 'vectors'): void {\n gatherDegradeCounter().add(1, { projection });\n}\n\n/** Increment the SSE subscriber gauge — call on `/bus/subscribe` open. */\nexport function recordSubscriberConnect(): void {\n sseSubscribersCounter().add(1);\n}\n\n/** Decrement on disconnect. Pair with `recordSubscriberConnect`. */\nexport function recordSubscriberDisconnect(): void {\n sseSubscribersCounter().add(-1);\n}\n\n/**\n * Register a callback that returns the current job-queue snapshot.\n * Polled at the SDK's metric-collection interval. The single gauge\n * emits one observation per status (`pending`, `running`, …) tagged\n * with the `job.status` attribute. Idempotent — last registered\n * provider wins.\n */\nexport function registerJobQueueProvider(\n provider: () => Promise<JobQueueSnapshot> | JobQueueSnapshot,\n): void {\n _jobQueueProvider = provider;\n if (!_jobQueueGauge) {\n _jobQueueGauge = meter().createObservableGauge('semiont.job.queue.size', {\n description: 'Job queue size by status',\n });\n _jobQueueGauge.addCallback(async (observer) => {\n if (!_jobQueueProvider) return;\n const snap = await _jobQueueProvider();\n observer.observe(snap.pending, { 'job.status': 'pending' });\n observer.observe(snap.running, { 'job.status': 'running' });\n observer.observe(snap.complete, { 'job.status': 'complete' });\n observer.observe(snap.failed, { 'job.status': 'failed' });\n observer.observe(snap.cancelled, { 'job.status': 'cancelled' });\n });\n }\n}\n\n/**\n * Register a callback that returns the current vector-index size\n * (point count). Async to allow remote queries (Qdrant). Polled at\n * the metric-collection interval.\n */\nexport function registerVectorIndexSizeProvider(\n provider: () => Promise<number> | number,\n): void {\n _vectorIndexSizeProvider = provider;\n if (!_vectorIndexSizeGauge) {\n _vectorIndexSizeGauge = meter().createObservableGauge('semiont.vector.index.size', {\n description: 'Vector store point count',\n });\n _vectorIndexSizeGauge.addCallback(async (observer) => {\n if (_vectorIndexSizeProvider) {\n const value = await _vectorIndexSizeProvider();\n observer.observe(value);\n }\n });\n }\n}\n\n/**\n * Record an inference call. Token counts are optional — providers that\n * don't expose them (or fail before generating) record only call count\n * and duration.\n */\nexport function recordInferenceUsage(opts: {\n provider: string;\n model: string;\n durationMs: number;\n outcome: 'success' | 'error';\n inputTokens?: number;\n outputTokens?: number;\n}): void {\n const baseAttrs = {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.outcome': opts.outcome,\n };\n inferenceCallsCounter().add(1, baseAttrs);\n inferenceDurationHistogram().record(opts.durationMs, baseAttrs);\n if (opts.inputTokens != null && opts.inputTokens > 0) {\n inferenceTokensCounter().add(opts.inputTokens, {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.direction': 'input',\n });\n }\n if (opts.outputTokens != null && opts.outputTokens > 0) {\n inferenceTokensCounter().add(opts.outputTokens, {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.direction': 'output',\n });\n }\n}\n\n// ── Re-exports from @opentelemetry/api ─────────────────────────────────\n\nexport { SpanKind, SpanStatusCode, type Attributes, type Span } from '@opentelemetry/api';\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @semiont/observability — public API.\n *\n * Universal surface (works in Node + browser). For SDK *initialization*,\n * import from `@semiont/observability/node` or `/web` at the process entry\n * point. Everything else uses this module.\n *\n * Tier 2 of `.plans/OBSERVABILITY.md`. The public surface:\n *\n * - `withSpan(name, fn, options?)` — wrap an async block in a span;\n * `options` carries `kind` and `attrs`.\n * - `withActorSpan(actor, channel, fn, extraAttrs?)` — consumer-span\n * wrapper for bus-event handlers, with handler-duration recording.\n * - `injectTraceparent(payload)` / `extractTraceparent(payload)` — W3C\n * trace-context propagation across the SSE channel (the bus payload\n * gets a `_trace?: { traceparent }` sibling to `correlationId`).\n * - `withTraceparent(carrier, fn)` — run `fn` with the incoming\n * traceparent as the parent context.\n * - `getActiveTraceparent()` — read the active span's traceparent for\n * manual propagation (e.g. attaching to a fetch header or SSE field).\n * - `getLogTraceContext()` — active `trace_id` / `span_id` for log-line\n * correlation.\n * - Metric recorders (`recordBusEmit`, `recordHandlerDuration`,\n * `recordJobOutcome`, `recordSubscriberConnect` / `Disconnect`,\n * `recordInferenceUsage`) and gauge providers\n * (`registerJobQueueProvider`, `registerVectorIndexSizeProvider`).\n *\n * No-op when no exporter is configured: `@opentelemetry/api`'s default\n * tracer is a no-op, so `withSpan` is essentially free until\n * `initObservability*()` runs.\n */\n\nimport {\n context,\n isSpanContextValid,\n metrics,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace,\n type Attributes,\n type Counter,\n type Histogram,\n type ObservableGauge,\n type Span,\n type UpDownCounter,\n} from '@opentelemetry/api';\nimport { setBusLogTraceIdProvider } from '@semiont/core';\n\n// Wire `busLog`'s trace-id provider once at module load. When an OTel\n// SDK is initialized (and a span is active when `busLog` fires), the\n// emitted line gets a `trace=<8hex>` suffix that correlates the\n// grep-timeline with the trace UI. No-op when no SDK is active.\nsetBusLogTraceIdProvider(() => {\n const span = trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n if (!isSpanContextValid(ctx)) return undefined;\n return ctx.traceId;\n});\n\nconst TRACER_NAME = 'semiont';\n\nconst tracer = () => trace.getTracer(TRACER_NAME);\n\n// ── withSpan ───────────────────────────────────────────────────────────\n\n/**\n * Wrap an async block in a span. The span is started before `fn` runs and\n * ended after it resolves or rejects; exceptions are recorded and the span\n * status is set to ERROR. `kind` defaults to INTERNAL.\n */\nexport async function withSpan<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n options?: { kind?: SpanKind; attrs?: Attributes },\n): Promise<T> {\n const span = tracer().startSpan(name, {\n kind: options?.kind ?? SpanKind.INTERNAL,\n ...(options?.attrs ? { attributes: options.attrs } : {}),\n });\n try {\n return await context.with(trace.setSpan(context.active(), span), () => fn(span));\n } catch (err) {\n span.recordException(err as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n throw err;\n } finally {\n span.end();\n }\n}\n\n// ── Traceparent on bus payloads ────────────────────────────────────────\n\nconst TRACE_FIELD = '_trace';\n\n/**\n * Sibling of `correlationId` on bus payloads. Lives on the SSE event body\n * because SSE has no header trailer; the SDK strips it before delivering\n * the payload to subscribers. Additive — payloads without `_trace` parse\n * unchanged.\n */\nexport interface TraceCarrier {\n /** W3C `traceparent` header value (`00-<traceId>-<spanId>-<flags>`). */\n traceparent: string;\n /** W3C `tracestate` header value (vendor-specific extensions). */\n tracestate?: string;\n}\n\n/**\n * Read the active span's W3C traceparent (and tracestate). Returns\n * `undefined` if no span is active.\n */\nexport function getActiveTraceparent(): TraceCarrier | undefined {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n const traceparent = carrier['traceparent'];\n if (!traceparent) return undefined;\n return carrier['tracestate']\n ? { traceparent, tracestate: carrier['tracestate'] }\n : { traceparent };\n}\n\n/**\n * Attach the active span's trace-context to a payload object as\n * `_trace`. No-op when no span is active. Returns the same object\n * reference for chaining.\n */\nexport function injectTraceparent<T extends Record<string, unknown>>(payload: T): T {\n const carrier = getActiveTraceparent();\n if (carrier) {\n (payload as Record<string, unknown>)[TRACE_FIELD] = carrier;\n }\n return payload;\n}\n\n/**\n * Strip and return the `_trace` field from a payload. Mutates `payload`.\n * The field is internal plumbing and should not be visible to subscribers.\n */\nexport function extractTraceparent<T extends Record<string, unknown>>(\n payload: T,\n): TraceCarrier | undefined {\n const carrier = (payload as Record<string, unknown>)[TRACE_FIELD] as\n | TraceCarrier\n | undefined;\n if (carrier !== undefined) {\n delete (payload as Record<string, unknown>)[TRACE_FIELD];\n }\n if (!carrier || typeof carrier.traceparent !== 'string') return undefined;\n return carrier;\n}\n\n/**\n * Run `fn` with the given W3C traceparent set as the parent context.\n * Any spans started inside `fn` will be children of the incoming trace.\n * No-op if `carrier` is undefined.\n */\nexport function withTraceparent<T>(\n carrier: TraceCarrier | undefined,\n fn: () => T,\n): T {\n if (!carrier) return fn();\n const carrierObj: Record<string, string> = { traceparent: carrier.traceparent };\n if (carrier.tracestate) carrierObj['tracestate'] = carrier.tracestate;\n const ctx = propagation.extract(context.active(), carrierObj);\n return context.with(ctx, fn);\n}\n\n// ── Actor handler convenience ──────────────────────────────────────────\n\n/**\n * Wrap a bus-event handler in an `actor.<name>:<channel>` consumer span.\n * Used at every `eventBus.get(channel).subscribe(handler)` site inside\n * an actor (Stower, Gatherer, Matcher, Browser, Smelter), to attribute\n * each in-process subscriber's work to a span without scattering manual\n * `withSpan` calls across handler bodies.\n *\n * The span's parent is the active context at the time the handler\n * fires — which is the `bus.dispatch:<channel>` span on the gateway\n * (Subject.next runs synchronously inside the dispatch span), or the\n * `bus.emit:<channel>` span when an actor emits to itself.\n */\nexport async function withActorSpan<T>(\n actor: string,\n channel: string,\n fn: (span: Span) => Promise<T> | T,\n extraAttrs?: Attributes,\n): Promise<T> {\n const start = performance.now();\n try {\n return await withSpan(`actor.${actor}:${channel}`, fn, {\n kind: SpanKind.CONSUMER,\n attrs: {\n actor,\n 'bus.channel': channel,\n ...(extraAttrs ?? {}),\n },\n });\n } finally {\n recordHandlerDuration(actor, channel, performance.now() - start);\n }\n}\n\n// ── Log correlation ────────────────────────────────────────────────────\n\n/**\n * Read the active span's `trace_id` / `span_id` for log-line correlation.\n * Tier 3 of `.plans/OBSERVABILITY.md`. Each structured log line gets\n * tagged with these so a log query in CloudWatch / Loki / Datadog can\n * jump to the trace in Tempo / Jaeger / X-Ray.\n *\n * Returns `undefined` if no span is active, or if the active span's\n * context is invalid (uninitialized SDK, no-op tracer).\n */\nexport function getLogTraceContext(): { trace_id: string; span_id: string } | undefined {\n const span = trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n if (!isSpanContextValid(ctx)) return undefined;\n return { trace_id: ctx.traceId, span_id: ctx.spanId };\n}\n\n// ── Metrics — Tier 3 ───────────────────────────────────────────────────\n\nconst METER_NAME = 'semiont';\n\nconst meter = () => metrics.getMeter(METER_NAME);\n\nlet _busEmitCounter: Counter | undefined;\nlet _handlerDurationHistogram: Histogram | undefined;\nlet _jobOutcomeCounter: Counter | undefined;\nlet _jobDurationHistogram: Histogram | undefined;\nlet _gatherDegradeCounter: Counter | undefined;\nlet _inferenceCallsCounter: Counter | undefined;\nlet _inferenceTokensCounter: Counter | undefined;\nlet _inferenceDurationHistogram: Histogram | undefined;\nlet _sseSubscribers: UpDownCounter | undefined;\nlet _jobQueueGauge: ObservableGauge | undefined;\nlet _jobQueueProvider: (() => Promise<JobQueueSnapshot> | JobQueueSnapshot) | undefined;\nlet _vectorIndexSizeGauge: ObservableGauge | undefined;\nlet _vectorIndexSizeProvider: (() => Promise<number> | number) | undefined;\n\n/** Snapshot of job-queue contents by status. Match `JobQueue.getStats()`. */\nexport interface JobQueueSnapshot {\n pending: number;\n running: number;\n complete: number;\n failed: number;\n cancelled: number;\n}\n\nfunction busEmitCounter(): Counter {\n if (!_busEmitCounter) {\n _busEmitCounter = meter().createCounter('semiont.bus.emit', {\n description: 'Bus emits by channel and scope',\n });\n }\n return _busEmitCounter;\n}\n\nfunction handlerDurationHistogram(): Histogram {\n if (!_handlerDurationHistogram) {\n _handlerDurationHistogram = meter().createHistogram('semiont.handler.duration', {\n description: 'In-process actor handler duration',\n unit: 'ms',\n });\n }\n return _handlerDurationHistogram;\n}\n\nfunction jobOutcomeCounter(): Counter {\n if (!_jobOutcomeCounter) {\n _jobOutcomeCounter = meter().createCounter('semiont.job.outcome', {\n description: 'Worker job completions by type and outcome',\n });\n }\n return _jobOutcomeCounter;\n}\n\nfunction jobDurationHistogram(): Histogram {\n if (!_jobDurationHistogram) {\n _jobDurationHistogram = meter().createHistogram('semiont.job.duration', {\n description: 'Worker job duration by type',\n unit: 'ms',\n });\n }\n return _jobDurationHistogram;\n}\n\nfunction inferenceCallsCounter(): Counter {\n if (!_inferenceCallsCounter) {\n _inferenceCallsCounter = meter().createCounter('semiont.inference.calls', {\n description: 'Inference API calls by provider, model, and outcome',\n });\n }\n return _inferenceCallsCounter;\n}\n\nfunction inferenceTokensCounter(): Counter {\n if (!_inferenceTokensCounter) {\n _inferenceTokensCounter = meter().createCounter('semiont.inference.tokens', {\n description: 'Inference token usage by provider, model, and direction',\n });\n }\n return _inferenceTokensCounter;\n}\n\nfunction inferenceDurationHistogram(): Histogram {\n if (!_inferenceDurationHistogram) {\n _inferenceDurationHistogram = meter().createHistogram('semiont.inference.duration', {\n description: 'Inference call duration by provider, model, and outcome',\n unit: 'ms',\n });\n }\n return _inferenceDurationHistogram;\n}\n\nfunction sseSubscribersCounter(): UpDownCounter {\n if (!_sseSubscribers) {\n _sseSubscribers = meter().createUpDownCounter('semiont.sse.subscribers', {\n description: 'Active SSE subscribers',\n });\n }\n return _sseSubscribers;\n}\n\n/** Increment the bus-emit counter. Called at every transport `emit` site. */\nexport function recordBusEmit(channel: string, scope?: string): void {\n busEmitCounter().add(1, {\n 'bus.channel': channel,\n ...(scope ? { 'bus.scope': scope } : {}),\n });\n}\n\n/** Record an in-process actor handler's duration. */\nexport function recordHandlerDuration(actor: string, channel: string, durationMs: number): void {\n handlerDurationHistogram().record(durationMs, {\n actor,\n 'bus.channel': channel,\n });\n}\n\n/** Record a worker job's outcome and duration. */\nexport function recordJobOutcome(jobType: string, outcome: 'completed' | 'failed', durationMs: number): void {\n jobOutcomeCounter().add(1, { 'job.type': jobType, 'job.outcome': outcome });\n jobDurationHistogram().record(durationMs, { 'job.type': jobType, 'job.outcome': outcome });\n}\n\nfunction gatherDegradeCounter(): Counter {\n if (!_gatherDegradeCounter) {\n _gatherDegradeCounter = meter().createCounter('semiont.gather.degraded', {\n description: 'Gathers that degraded because an eventually-consistent projection did not catch up within its read barrier (vectors: absent semanticContext; graph: projection-lag failure). Labeled by projection.',\n });\n }\n return _gatherDegradeCounter;\n}\n\n/**\n * Record a gather degraded by a projection read barrier: `'vectors'` — the\n * Smelter settle barrier timed out (semanticContext shipped absent);\n * `'graph'` — the Weaver applied barrier + poll floor exhausted (projection\n * lag surfaced as a distinct failure). Fleet-alertable counterpart of the\n * `[gather DEGRADED]` L4 breadcrumbs — a rising rate on either label means\n * that pipeline is not keeping up.\n */\nexport function recordGatherDegrade(projection: 'graph' | 'vectors'): void {\n gatherDegradeCounter().add(1, { projection });\n}\n\n/** Increment the SSE subscriber gauge — call on `/bus/subscribe` open. */\nexport function recordSubscriberConnect(): void {\n sseSubscribersCounter().add(1);\n}\n\n/** Decrement on disconnect. Pair with `recordSubscriberConnect`. */\nexport function recordSubscriberDisconnect(): void {\n sseSubscribersCounter().add(-1);\n}\n\n/**\n * Register a callback that returns the current job-queue snapshot.\n * Polled at the SDK's metric-collection interval. The single gauge\n * emits one observation per status (`pending`, `running`, …) tagged\n * with the `job.status` attribute. Idempotent — last registered\n * provider wins.\n */\nexport function registerJobQueueProvider(\n provider: () => Promise<JobQueueSnapshot> | JobQueueSnapshot,\n): void {\n _jobQueueProvider = provider;\n if (!_jobQueueGauge) {\n _jobQueueGauge = meter().createObservableGauge('semiont.job.queue.size', {\n description: 'Job queue size by status',\n });\n _jobQueueGauge.addCallback(async (observer) => {\n if (!_jobQueueProvider) return;\n const snap = await _jobQueueProvider();\n observer.observe(snap.pending, { 'job.status': 'pending' });\n observer.observe(snap.running, { 'job.status': 'running' });\n observer.observe(snap.complete, { 'job.status': 'complete' });\n observer.observe(snap.failed, { 'job.status': 'failed' });\n observer.observe(snap.cancelled, { 'job.status': 'cancelled' });\n });\n }\n}\n\n/**\n * Register a callback that returns the current vector-index size\n * (point count). Async to allow remote queries (Qdrant). Polled at\n * the metric-collection interval.\n */\nexport function registerVectorIndexSizeProvider(\n provider: () => Promise<number> | number,\n): void {\n _vectorIndexSizeProvider = provider;\n if (!_vectorIndexSizeGauge) {\n _vectorIndexSizeGauge = meter().createObservableGauge('semiont.vector.index.size', {\n description: 'Vector store point count',\n });\n _vectorIndexSizeGauge.addCallback(async (observer) => {\n if (_vectorIndexSizeProvider) {\n const value = await _vectorIndexSizeProvider();\n observer.observe(value);\n }\n });\n }\n}\n\n/**\n * Record an inference call. Token counts are optional — providers that\n * don't expose them (or fail before generating) record only call count\n * and duration.\n */\nexport function recordInferenceUsage(opts: {\n provider: string;\n model: string;\n durationMs: number;\n outcome: 'success' | 'error';\n inputTokens?: number;\n outputTokens?: number;\n}): void {\n const baseAttrs = {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.outcome': opts.outcome,\n };\n inferenceCallsCounter().add(1, baseAttrs);\n inferenceDurationHistogram().record(opts.durationMs, baseAttrs);\n if (opts.inputTokens != null && opts.inputTokens > 0) {\n inferenceTokensCounter().add(opts.inputTokens, {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.direction': 'input',\n });\n }\n if (opts.outputTokens != null && opts.outputTokens > 0) {\n inferenceTokensCounter().add(opts.outputTokens, {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.direction': 'output',\n });\n }\n}\n\n// ── Re-exports from @opentelemetry/api ─────────────────────────────────\n\nexport { SpanKind, SpanStatusCode, type Attributes, type Span } from '@opentelemetry/api';\n"]}
|
package/dist/node.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node SDK initialization. Call once at the process entry point
|
|
3
|
-
* (
|
|
3
|
+
* (gateway `index.ts`, `worker-main.ts`, `smelter-main.ts`).
|
|
4
4
|
*
|
|
5
5
|
* Configuration is via standard `OTEL_*` env vars:
|
|
6
|
-
* - `OTEL_SERVICE_NAME` — service identity (e.g. `semiont-
|
|
6
|
+
* - `OTEL_SERVICE_NAME` — service identity (e.g. `semiont-gateway`)
|
|
7
7
|
* - `OTEL_EXPORTER_OTLP_ENDPOINT` — collector endpoint (HTTP)
|
|
8
8
|
* - `OTEL_TRACES_SAMPLER` — sampler (default: `parentbased_always_on`)
|
|
9
9
|
* - `OTEL_TRACES_SAMPLER_ARG` — sampler ratio (default: `1.0`)
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* blowing up bundles for every consumer.
|
|
26
26
|
*/
|
|
27
27
|
interface NodeObservabilityConfig {
|
|
28
|
-
/** Service identity (e.g. `semiont-
|
|
28
|
+
/** Service identity (e.g. `semiont-gateway`). Overridden by `OTEL_SERVICE_NAME`. */
|
|
29
29
|
serviceName: string;
|
|
30
30
|
/** Service version. Defaults to `0.0.0` if omitted. */
|
|
31
31
|
serviceVersion?: string;
|
package/dist/node.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/node.ts"],"names":[],"mappings":";;;;;;;;;;;AAuDA,IAAI,sBAAA;AACJ,IAAI,qBAAA;AAMJ,IAAM,iCAAA,GAAoC,GAAA;AAWnC,SAAS,sBAAsB,MAAA,EAA0C;AAC9E,EAAA,IAAI,wBAAwB,OAAO,KAAA;AACnC,EAAA,IAAI,OAAA,CAAQ,GAAA,CAAI,mBAAmB,CAAA,KAAM,QAAQ,OAAO,KAAA;AAExD,EAAA,MAAM,QAAA,GAAW,OAAA,CAAQ,GAAA,CAAI,6BAA6B,CAAA;AAC1D,EAAA,MAAM,UAAA,GAAa,OAAA,CAAQ,GAAA,CAAI,uBAAuB,CAAA,KAAM,MAAA;AAM5D,EAAA,IAAI,CAAC,QAAA,IAAY,CAAC,UAAA,EAAY,OAAO,KAAA;AAErC,EAAA,MAAM,WAAW,sBAAA,CAAuB;AAAA,IACtC,CAAC,iBAAiB,GAAG,QAAQ,GAAA,CAAI,mBAAmB,KAAK,MAAA,CAAO,WAAA;AAAA,IAChE,CAAC,oBAAoB,GAAG,MAAA,CAAO,cAAA,IAAkB;AAAA,GAClD,CAAA;AAGD,EAAA,MAAM,gBAAgB,QAAA,GAAW,IAAI,iBAAA,EAAkB,GAAI,IAAI,mBAAA,EAAoB;AACnF,EAAA,sBAAA,GAAyB,IAAI,mBAAA,CAAoB;AAAA,IAC/C,QAAA;AAAA,IACA,cAAA,EAAgB,CAAC,IAAI,kBAAA,CAAmB,aAAa,CAAC;AAAA,GACvD,CAAA;AAKD,EAAA,OAAA,CAAQ,uBAAA,CAAwB,IAAI,+BAAA,EAAgC,CAAE,QAAQ,CAAA;AAC9E,EAAA,KAAA,CAAM,wBAAwB,sBAAsB,CAAA;AAQpD,EAAA,WAAA,CAAY,mBAAA,CAAoB,IAAI,yBAAA,EAA2B,CAAA;AAG/D,EAAA,MAAM,iBAAiB,QAAA,GAAW,IAAI,kBAAA,EAAmB,GAAI,IAAI,qBAAA,EAAsB;AACvF,EAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,GAAA,CAAI,6BAA6B,CAAA;AAC7D,EAAA,MAAM,uBAAuB,WAAA,GACzB,MAAA,CAAO,QAAA,CAAS,WAAA,EAAa,EAAE,CAAA,GAC/B,iCAAA;AAEJ,EAAA,qBAAA,GAAwB,IAAI,aAAA,CAAc;AAAA,IACxC,QAAA;AAAA,IACA,OAAA,EAAS;AAAA,MACP,IAAI,6BAAA,CAA8B;AAAA,QAChC,QAAA,EAAU,cAAA;AAAA,QACV,sBAAsB,MAAA,CAAO,QAAA,CAAS,oBAAoB,CAAA,IAAK,oBAAA,GAAuB,IAClF,oBAAA,GACA;AAAA,OACL;AAAA;AACH,GACD,CAAA;AACD,EAAA,OAAA,CAAQ,uBAAuB,qBAAqB,CAAA;AAGpD,EAAA,MAAM,WAAW,MAAM;AACrB,IAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,MACV,sBAAA,EAAwB,QAAA,EAAS,CAAE,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC,CAAA;AAAA,MACjD,qBAAA,EAAuB,QAAA,EAAS,CAAE,KAAA,CAAM,MAAM;AAAA,MAAC,CAAC;AAAA,KACjD,CAAA,CAAE,OAAA,CAAQ,MAAM;AACf,MAAA,sBAAA,GAAyB,MAAA;AACzB,MAAA,qBAAA,GAAwB,MAAA;AAAA,IAC1B,CAAC,CAAA;AAAA,EACH,CAAA;AACA,EAAA,OAAA,CAAQ,IAAA,CAAK,WAAW,QAAQ,CAAA;AAChC,EAAA,OAAA,CAAQ,IAAA,CAAK,UAAU,QAAQ,CAAA;AAE/B,EAAA,OAAO,IAAA;AACT;AAGA,eAAsB,yBAAA,GAA2C;AAC/D,EAAA,MAAM,QAAQ,GAAA,CAAI;AAAA,IAChB,wBAAwB,QAAA,EAAS;AAAA,IACjC,uBAAuB,QAAA;AAAS,GACjC,CAAA;AACD,EAAA,sBAAA,GAAyB,MAAA;AACzB,EAAA,qBAAA,GAAwB,MAAA;AAC1B","file":"node.js","sourcesContent":["/**\n * Node SDK initialization. Call once at the process entry point\n * (
|
|
1
|
+
{"version":3,"sources":["../src/node.ts"],"names":[],"mappings":"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","file":"node.js","sourcesContent":["/**\n * Node SDK initialization. Call once at the process entry point\n * (gateway `index.ts`, `worker-main.ts`, `smelter-main.ts`).\n *\n * Configuration is via standard `OTEL_*` env vars:\n * - `OTEL_SERVICE_NAME` — service identity (e.g. `semiont-gateway`)\n * - `OTEL_EXPORTER_OTLP_ENDPOINT` — collector endpoint (HTTP)\n * - `OTEL_TRACES_SAMPLER` — sampler (default: `parentbased_always_on`)\n * - `OTEL_TRACES_SAMPLER_ARG` — sampler ratio (default: `1.0`)\n * - `OTEL_CONSOLE_EXPORTER=true` — dev-only: emit spans + metrics to stderr\n * - `OTEL_SDK_DISABLED=true` — skip initialization entirely\n *\n * **Off-by-default invariant**: with neither `OTEL_EXPORTER_OTLP_ENDPOINT`\n * nor `OTEL_CONSOLE_EXPORTER=true` set, this function is a no-op and the\n * `@opentelemetry/api` no-op tracer takes over. This avoids accidentally\n * flooding production stderr (and CloudWatch) when an operator deploys\n * without configuring an exporter.\n *\n * Implementation note: this module wires `BasicTracerProvider` and\n * `MeterProvider` (both from the stable `@opentelemetry/sdk-trace-base`\n * 2.x line) directly, plus `AsyncLocalStorageContextManager` for Node\n * async-context propagation. We deliberately avoid `@opentelemetry/sdk-node`\n * because its `0.x` experimental versions cross-depend on older 2.0.x\n * SDKs, forcing npm to nest duplicate copies of the stable packages and\n * blowing up bundles for every consumer.\n */\n\nimport { context, metrics, propagation, trace } from '@opentelemetry/api';\nimport { AsyncLocalStorageContextManager } from '@opentelemetry/context-async-hooks';\nimport { W3CTraceContextPropagator } from '@opentelemetry/core';\nimport { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-http';\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport {\n ConsoleMetricExporter,\n MeterProvider,\n PeriodicExportingMetricReader,\n} from '@opentelemetry/sdk-metrics';\nimport {\n BasicTracerProvider,\n BatchSpanProcessor,\n ConsoleSpanExporter,\n} from '@opentelemetry/sdk-trace-base';\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from '@opentelemetry/semantic-conventions';\n\nexport interface NodeObservabilityConfig {\n /** Service identity (e.g. `semiont-gateway`). Overridden by `OTEL_SERVICE_NAME`. */\n serviceName: string;\n /** Service version. Defaults to `0.0.0` if omitted. */\n serviceVersion?: string;\n}\n\nlet tracerProviderInstance: BasicTracerProvider | undefined;\nlet meterProviderInstance: MeterProvider | undefined;\n\n/**\n * Default metric export interval. 30s mirrors the SDK default and gives\n * operators enough granularity without flooding the collector.\n */\nconst DEFAULT_METRIC_EXPORT_INTERVAL_MS = 30_000;\n\n/**\n * Initialize OTel for the current process. Wires up both tracing and\n * metrics. Idempotent — calling twice is a no-op. Returns `true` if the\n * SDK started, `false` if disabled, no exporter is configured, or\n * already initialized.\n *\n * Metrics export at `OTEL_METRIC_EXPORT_INTERVAL` ms (default 30s) to\n * the same `OTEL_EXPORTER_OTLP_ENDPOINT` as traces.\n */\nexport function initObservabilityNode(config: NodeObservabilityConfig): boolean {\n if (tracerProviderInstance) return false;\n if (process.env['OTEL_SDK_DISABLED'] === 'true') return false;\n\n const endpoint = process.env['OTEL_EXPORTER_OTLP_ENDPOINT'];\n const useConsole = process.env['OTEL_CONSOLE_EXPORTER'] === 'true';\n\n // No exporter configured = no SDK init. The `@opentelemetry/api`\n // no-op tracer takes over; `withSpan` still runs `fn` but emits\n // nothing, `getActiveSpan()` returns a sentinel. Avoids flooding\n // production stderr when no collector endpoint is set.\n if (!endpoint && !useConsole) return false;\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: process.env['OTEL_SERVICE_NAME'] ?? config.serviceName,\n [ATTR_SERVICE_VERSION]: config.serviceVersion ?? '0.0.0',\n });\n\n // Trace SDK\n const traceExporter = endpoint ? new OTLPTraceExporter() : new ConsoleSpanExporter();\n tracerProviderInstance = new BasicTracerProvider({\n resource,\n spanProcessors: [new BatchSpanProcessor(traceExporter)],\n });\n\n // Async-context propagation across `await` boundaries — equivalent\n // to what NodeSDK installs internally, but pinned to the stable 2.x\n // cohort.\n context.setGlobalContextManager(new AsyncLocalStorageContextManager().enable());\n trace.setGlobalTracerProvider(tracerProviderInstance);\n\n // W3C trace-context propagator. Without this, `propagation.inject`\n // and `propagation.extract` walk an empty propagator chain and\n // silently do nothing — which means traceparent never makes it onto\n // outgoing HTTP headers or SSE `_trace` payloads, and cross-service\n // traces stay disconnected. NodeSDK registers this for you; bare\n // BasicTracerProvider does not.\n propagation.setGlobalPropagator(new W3CTraceContextPropagator());\n\n // Metric SDK — same exporter selection as traces.\n const metricExporter = endpoint ? new OTLPMetricExporter() : new ConsoleMetricExporter();\n const intervalRaw = process.env['OTEL_METRIC_EXPORT_INTERVAL'];\n const exportIntervalMillis = intervalRaw\n ? Number.parseInt(intervalRaw, 10)\n : DEFAULT_METRIC_EXPORT_INTERVAL_MS;\n\n meterProviderInstance = new MeterProvider({\n resource,\n readers: [\n new PeriodicExportingMetricReader({\n exporter: metricExporter,\n exportIntervalMillis: Number.isFinite(exportIntervalMillis) && exportIntervalMillis > 0\n ? exportIntervalMillis\n : DEFAULT_METRIC_EXPORT_INTERVAL_MS,\n }),\n ],\n });\n metrics.setGlobalMeterProvider(meterProviderInstance);\n\n // Flush traces + metrics on shutdown so nothing is lost on SIGTERM/SIGINT.\n const shutdown = () => {\n Promise.all([\n tracerProviderInstance?.shutdown().catch(() => {}),\n meterProviderInstance?.shutdown().catch(() => {}),\n ]).finally(() => {\n tracerProviderInstance = undefined;\n meterProviderInstance = undefined;\n });\n };\n process.once('SIGTERM', shutdown);\n process.once('SIGINT', shutdown);\n\n return true;\n}\n\n/** Force-flush + shutdown both SDKs. Test cleanup, not production. */\nexport async function shutdownObservabilityNode(): Promise<void> {\n await Promise.all([\n tracerProviderInstance?.shutdown(),\n meterProviderInstance?.shutdown(),\n ]);\n tracerProviderInstance = undefined;\n meterProviderInstance = undefined;\n}\n"]}
|
package/dist/process-logger.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Logger } from '@semiont/core';
|
|
|
3
3
|
/**
|
|
4
4
|
* Process-level structured logger for Node entry points.
|
|
5
5
|
*
|
|
6
|
-
* Used by long-lived Node processes (
|
|
6
|
+
* Used by long-lived Node processes (gateway, workers, smelter) that
|
|
7
7
|
* want JSON-structured stdout with active-span trace correlation. The
|
|
8
8
|
* `trace_id` / `span_id` fields are populated from the current OTel
|
|
9
9
|
* span context via `getLogTraceContext` — this is the same Tier 3
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/process-logger.ts"],"names":["trace"],"mappings":";;;;;AAqDA,wBAAA,CAAyB,MAAM;AAC7B,EAAA,MAAM,IAAA,GAAO,MAAM,aAAA,EAAc;AACjC,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,MAAM,GAAA,GAAM,KAAK,WAAA,EAAY;AAC7B,EAAA,IAAI,CAAC,kBAAA,CAAmB,GAAG,CAAA,EAAG,OAAO,MAAA;AACrC,EAAA,OAAO,GAAA,CAAI,OAAA;AACb,CAAC,CAAA;AA+JM,SAAS,kBAAA,GAAwE;AACtF,EAAA,MAAM,IAAA,GAAO,MAAM,aAAA,EAAc;AACjC,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,MAAM,GAAA,GAAM,KAAK,WAAA,EAAY;AAC7B,EAAA,IAAI,CAAC,kBAAA,CAAmB,GAAG,CAAA,EAAG,OAAO,MAAA;AACrC,EAAA,OAAO,EAAE,QAAA,EAAU,GAAA,CAAI,OAAA,EAAS,OAAA,EAAS,IAAI,MAAA,EAAO;AACtD;;;AC1MA,IAAM,kBAAA,GAAqB,OAAA,CAAQ,MAAA,CAAO,CAAC,IAAA,KAAS;AAClD,EAAA,MAAMA,SAAQ,kBAAA,EAAmB;AACjC,EAAA,IAAIA,MAAAA,EAAO;AACT,IAAA,IAAA,CAAK,WAAWA,MAAAA,CAAM,QAAA;AACtB,IAAA,IAAA,CAAK,UAAUA,MAAAA,CAAM,OAAA;AAAA,EACvB;AACA,EAAA,OAAO,IAAA;AACT,CAAC,CAAA,EAAE;AAEI,SAAS,oBAAoB,SAAA,EAA2B;AAC7D,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,SAAA,IAAa,MAAA;AACvC,EAAA,MAAM,SAAS,OAAA,CAAQ,GAAA,CAAI,UAAA,KAAe,QAAA,GACtC,QAAQ,MAAA,CAAO,OAAA;AAAA,IACb,QAAQ,MAAA,CAAO,SAAA,CAAU,EAAE,MAAA,EAAQ,uBAAuB,CAAA;AAAA,IAC1D,QAAQ,MAAA,CAAO,MAAA,CAAO,EAAE,KAAA,EAAO,MAAM,CAAA;AAAA,IACrC,kBAAA;AAAA,IACA,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,GAAA,EAAK,OAAA,EAAS,SAAA,EAAW,GAAG,IAAA,EAAK,KAAM;AACrE,MAAA,MAAM,OAAA,GAAU,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,GAAS,CAAA,GAAI,CAAA,CAAA,EAAI,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,CAAA,CAAA,GAAK,EAAA;AAC5E,MAAA,OAAO,CAAA,EAAG,SAAS,CAAA,EAAA,EAAK,GAAA,CAAI,WAAA,EAAa,CAAA,GAAA,EAAM,SAAS,CAAA,EAAA,EAAK,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA;AAAA,IAChF,CAAC;AAAA,GACH,GACA,QAAQ,MAAA,CAAO,OAAA;AAAA,IACb,OAAA,CAAQ,OAAO,SAAA,EAAU;AAAA,IACzB,QAAQ,MAAA,CAAO,MAAA,CAAO,EAAE,KAAA,EAAO,MAAM,CAAA;AAAA,IACrC,kBAAA;AAAA,IACA,OAAA,CAAQ,OAAO,IAAA;AAAK,GACtB;AAEJ,EAAA,MAAM,MAAA,GAAS,QAAQ,YAAA,CAAa;AAAA,IAClC,KAAA;AAAA,IACA,WAAA,EAAa,EAAE,SAAA,EAAU;AAAA,IACzB,MAAA;AAAA,IACA,YAAY,CAAC,IAAI,OAAA,CAAQ,UAAA,CAAW,SAAS;AAAA,GAC9C,CAAA;AAED,EAAA,OAAO,MAAA;AACT","file":"process-logger.js","sourcesContent":["/**\n * @semiont/observability — public API.\n *\n * Universal surface (works in Node + browser). For SDK *initialization*,\n * import from `@semiont/observability/node` or `/web` at the process entry\n * point. Everything else uses this module.\n *\n * Tier 2 of `.plans/OBSERVABILITY.md`. The public surface:\n *\n * - `withSpan(name, fn, options?)` — wrap an async block in a span;\n * `options` carries `kind` and `attrs`.\n * - `withActorSpan(actor, channel, fn, extraAttrs?)` — consumer-span\n * wrapper for bus-event handlers, with handler-duration recording.\n * - `injectTraceparent(payload)` / `extractTraceparent(payload)` — W3C\n * trace-context propagation across the SSE channel (the bus payload\n * gets a `_trace?: { traceparent }` sibling to `correlationId`).\n * - `withTraceparent(carrier, fn)` — run `fn` with the incoming\n * traceparent as the parent context.\n * - `getActiveTraceparent()` — read the active span's traceparent for\n * manual propagation (e.g. attaching to a fetch header or SSE field).\n * - `getLogTraceContext()` — active `trace_id` / `span_id` for log-line\n * correlation.\n * - Metric recorders (`recordBusEmit`, `recordHandlerDuration`,\n * `recordJobOutcome`, `recordSubscriberConnect` / `Disconnect`,\n * `recordInferenceUsage`) and gauge providers\n * (`registerJobQueueProvider`, `registerVectorIndexSizeProvider`).\n *\n * No-op when no exporter is configured: `@opentelemetry/api`'s default\n * tracer is a no-op, so `withSpan` is essentially free until\n * `initObservability*()` runs.\n */\n\nimport {\n context,\n isSpanContextValid,\n metrics,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace,\n type Attributes,\n type Counter,\n type Histogram,\n type ObservableGauge,\n type Span,\n type UpDownCounter,\n} from '@opentelemetry/api';\nimport { setBusLogTraceIdProvider } from '@semiont/core';\n\n// Wire `busLog`'s trace-id provider once at module load. When an OTel\n// SDK is initialized (and a span is active when `busLog` fires), the\n// emitted line gets a `trace=<8hex>` suffix that correlates the\n// grep-timeline with the trace UI. No-op when no SDK is active.\nsetBusLogTraceIdProvider(() => {\n const span = trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n if (!isSpanContextValid(ctx)) return undefined;\n return ctx.traceId;\n});\n\nconst TRACER_NAME = 'semiont';\n\nconst tracer = () => trace.getTracer(TRACER_NAME);\n\n// ── withSpan ───────────────────────────────────────────────────────────\n\n/**\n * Wrap an async block in a span. The span is started before `fn` runs and\n * ended after it resolves or rejects; exceptions are recorded and the span\n * status is set to ERROR. `kind` defaults to INTERNAL.\n */\nexport async function withSpan<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n options?: { kind?: SpanKind; attrs?: Attributes },\n): Promise<T> {\n const span = tracer().startSpan(name, {\n kind: options?.kind ?? SpanKind.INTERNAL,\n ...(options?.attrs ? { attributes: options.attrs } : {}),\n });\n try {\n return await context.with(trace.setSpan(context.active(), span), () => fn(span));\n } catch (err) {\n span.recordException(err as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n throw err;\n } finally {\n span.end();\n }\n}\n\n// ── Traceparent on bus payloads ────────────────────────────────────────\n\nconst TRACE_FIELD = '_trace';\n\n/**\n * Sibling of `correlationId` on bus payloads. Lives on the SSE event body\n * because SSE has no header trailer; the SDK strips it before delivering\n * the payload to subscribers. Additive — payloads without `_trace` parse\n * unchanged.\n */\nexport interface TraceCarrier {\n /** W3C `traceparent` header value (`00-<traceId>-<spanId>-<flags>`). */\n traceparent: string;\n /** W3C `tracestate` header value (vendor-specific extensions). */\n tracestate?: string;\n}\n\n/**\n * Read the active span's W3C traceparent (and tracestate). Returns\n * `undefined` if no span is active.\n */\nexport function getActiveTraceparent(): TraceCarrier | undefined {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n const traceparent = carrier['traceparent'];\n if (!traceparent) return undefined;\n return carrier['tracestate']\n ? { traceparent, tracestate: carrier['tracestate'] }\n : { traceparent };\n}\n\n/**\n * Attach the active span's trace-context to a payload object as\n * `_trace`. No-op when no span is active. Returns the same object\n * reference for chaining.\n */\nexport function injectTraceparent<T extends Record<string, unknown>>(payload: T): T {\n const carrier = getActiveTraceparent();\n if (carrier) {\n (payload as Record<string, unknown>)[TRACE_FIELD] = carrier;\n }\n return payload;\n}\n\n/**\n * Strip and return the `_trace` field from a payload. Mutates `payload`.\n * The field is internal plumbing and should not be visible to subscribers.\n */\nexport function extractTraceparent<T extends Record<string, unknown>>(\n payload: T,\n): TraceCarrier | undefined {\n const carrier = (payload as Record<string, unknown>)[TRACE_FIELD] as\n | TraceCarrier\n | undefined;\n if (carrier !== undefined) {\n delete (payload as Record<string, unknown>)[TRACE_FIELD];\n }\n if (!carrier || typeof carrier.traceparent !== 'string') return undefined;\n return carrier;\n}\n\n/**\n * Run `fn` with the given W3C traceparent set as the parent context.\n * Any spans started inside `fn` will be children of the incoming trace.\n * No-op if `carrier` is undefined.\n */\nexport function withTraceparent<T>(\n carrier: TraceCarrier | undefined,\n fn: () => T,\n): T {\n if (!carrier) return fn();\n const carrierObj: Record<string, string> = { traceparent: carrier.traceparent };\n if (carrier.tracestate) carrierObj['tracestate'] = carrier.tracestate;\n const ctx = propagation.extract(context.active(), carrierObj);\n return context.with(ctx, fn);\n}\n\n// ── Actor handler convenience ──────────────────────────────────────────\n\n/**\n * Wrap a bus-event handler in an `actor.<name>:<channel>` consumer span.\n * Used at every `eventBus.get(channel).subscribe(handler)` site inside\n * an actor (Stower, Gatherer, Matcher, Browser, Smelter), to attribute\n * each in-process subscriber's work to a span without scattering manual\n * `withSpan` calls across handler bodies.\n *\n * The span's parent is the active context at the time the handler\n * fires — which is the `bus.dispatch:<channel>` span on the backend\n * (Subject.next runs synchronously inside the dispatch span), or the\n * `bus.emit:<channel>` span when an actor emits to itself.\n */\nexport async function withActorSpan<T>(\n actor: string,\n channel: string,\n fn: (span: Span) => Promise<T> | T,\n extraAttrs?: Attributes,\n): Promise<T> {\n const start = performance.now();\n try {\n return await withSpan(`actor.${actor}:${channel}`, fn, {\n kind: SpanKind.CONSUMER,\n attrs: {\n actor,\n 'bus.channel': channel,\n ...(extraAttrs ?? {}),\n },\n });\n } finally {\n recordHandlerDuration(actor, channel, performance.now() - start);\n }\n}\n\n// ── Log correlation ────────────────────────────────────────────────────\n\n/**\n * Read the active span's `trace_id` / `span_id` for log-line correlation.\n * Tier 3 of `.plans/OBSERVABILITY.md`. Each structured log line gets\n * tagged with these so a log query in CloudWatch / Loki / Datadog can\n * jump to the trace in Tempo / Jaeger / X-Ray.\n *\n * Returns `undefined` if no span is active, or if the active span's\n * context is invalid (uninitialized SDK, no-op tracer).\n */\nexport function getLogTraceContext(): { trace_id: string; span_id: string } | undefined {\n const span = trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n if (!isSpanContextValid(ctx)) return undefined;\n return { trace_id: ctx.traceId, span_id: ctx.spanId };\n}\n\n// ── Metrics — Tier 3 ───────────────────────────────────────────────────\n\nconst METER_NAME = 'semiont';\n\nconst meter = () => metrics.getMeter(METER_NAME);\n\nlet _busEmitCounter: Counter | undefined;\nlet _handlerDurationHistogram: Histogram | undefined;\nlet _jobOutcomeCounter: Counter | undefined;\nlet _jobDurationHistogram: Histogram | undefined;\nlet _gatherDegradeCounter: Counter | undefined;\nlet _inferenceCallsCounter: Counter | undefined;\nlet _inferenceTokensCounter: Counter | undefined;\nlet _inferenceDurationHistogram: Histogram | undefined;\nlet _sseSubscribers: UpDownCounter | undefined;\nlet _jobQueueGauge: ObservableGauge | undefined;\nlet _jobQueueProvider: (() => Promise<JobQueueSnapshot> | JobQueueSnapshot) | undefined;\nlet _vectorIndexSizeGauge: ObservableGauge | undefined;\nlet _vectorIndexSizeProvider: (() => Promise<number> | number) | undefined;\n\n/** Snapshot of job-queue contents by status. Match `JobQueue.getStats()`. */\nexport interface JobQueueSnapshot {\n pending: number;\n running: number;\n complete: number;\n failed: number;\n cancelled: number;\n}\n\nfunction busEmitCounter(): Counter {\n if (!_busEmitCounter) {\n _busEmitCounter = meter().createCounter('semiont.bus.emit', {\n description: 'Bus emits by channel and scope',\n });\n }\n return _busEmitCounter;\n}\n\nfunction handlerDurationHistogram(): Histogram {\n if (!_handlerDurationHistogram) {\n _handlerDurationHistogram = meter().createHistogram('semiont.handler.duration', {\n description: 'In-process actor handler duration',\n unit: 'ms',\n });\n }\n return _handlerDurationHistogram;\n}\n\nfunction jobOutcomeCounter(): Counter {\n if (!_jobOutcomeCounter) {\n _jobOutcomeCounter = meter().createCounter('semiont.job.outcome', {\n description: 'Worker job completions by type and outcome',\n });\n }\n return _jobOutcomeCounter;\n}\n\nfunction jobDurationHistogram(): Histogram {\n if (!_jobDurationHistogram) {\n _jobDurationHistogram = meter().createHistogram('semiont.job.duration', {\n description: 'Worker job duration by type',\n unit: 'ms',\n });\n }\n return _jobDurationHistogram;\n}\n\nfunction inferenceCallsCounter(): Counter {\n if (!_inferenceCallsCounter) {\n _inferenceCallsCounter = meter().createCounter('semiont.inference.calls', {\n description: 'Inference API calls by provider, model, and outcome',\n });\n }\n return _inferenceCallsCounter;\n}\n\nfunction inferenceTokensCounter(): Counter {\n if (!_inferenceTokensCounter) {\n _inferenceTokensCounter = meter().createCounter('semiont.inference.tokens', {\n description: 'Inference token usage by provider, model, and direction',\n });\n }\n return _inferenceTokensCounter;\n}\n\nfunction inferenceDurationHistogram(): Histogram {\n if (!_inferenceDurationHistogram) {\n _inferenceDurationHistogram = meter().createHistogram('semiont.inference.duration', {\n description: 'Inference call duration by provider, model, and outcome',\n unit: 'ms',\n });\n }\n return _inferenceDurationHistogram;\n}\n\nfunction sseSubscribersCounter(): UpDownCounter {\n if (!_sseSubscribers) {\n _sseSubscribers = meter().createUpDownCounter('semiont.sse.subscribers', {\n description: 'Active SSE subscribers',\n });\n }\n return _sseSubscribers;\n}\n\n/** Increment the bus-emit counter. Called at every transport `emit` site. */\nexport function recordBusEmit(channel: string, scope?: string): void {\n busEmitCounter().add(1, {\n 'bus.channel': channel,\n ...(scope ? { 'bus.scope': scope } : {}),\n });\n}\n\n/** Record an in-process actor handler's duration. */\nexport function recordHandlerDuration(actor: string, channel: string, durationMs: number): void {\n handlerDurationHistogram().record(durationMs, {\n actor,\n 'bus.channel': channel,\n });\n}\n\n/** Record a worker job's outcome and duration. */\nexport function recordJobOutcome(jobType: string, outcome: 'completed' | 'failed', durationMs: number): void {\n jobOutcomeCounter().add(1, { 'job.type': jobType, 'job.outcome': outcome });\n jobDurationHistogram().record(durationMs, { 'job.type': jobType, 'job.outcome': outcome });\n}\n\nfunction gatherDegradeCounter(): Counter {\n if (!_gatherDegradeCounter) {\n _gatherDegradeCounter = meter().createCounter('semiont.gather.degraded', {\n description: 'Gathers that degraded because an eventually-consistent projection did not catch up within its read barrier (vectors: absent semanticContext; graph: projection-lag failure). Labeled by projection.',\n });\n }\n return _gatherDegradeCounter;\n}\n\n/**\n * Record a gather degraded by a projection read barrier: `'vectors'` — the\n * Smelter settle barrier timed out (semanticContext shipped absent);\n * `'graph'` — the Weaver applied barrier + poll floor exhausted (projection\n * lag surfaced as a distinct failure). Fleet-alertable counterpart of the\n * `[gather DEGRADED]` L4 breadcrumbs — a rising rate on either label means\n * that pipeline is not keeping up.\n */\nexport function recordGatherDegrade(projection: 'graph' | 'vectors'): void {\n gatherDegradeCounter().add(1, { projection });\n}\n\n/** Increment the SSE subscriber gauge — call on `/bus/subscribe` open. */\nexport function recordSubscriberConnect(): void {\n sseSubscribersCounter().add(1);\n}\n\n/** Decrement on disconnect. Pair with `recordSubscriberConnect`. */\nexport function recordSubscriberDisconnect(): void {\n sseSubscribersCounter().add(-1);\n}\n\n/**\n * Register a callback that returns the current job-queue snapshot.\n * Polled at the SDK's metric-collection interval. The single gauge\n * emits one observation per status (`pending`, `running`, …) tagged\n * with the `job.status` attribute. Idempotent — last registered\n * provider wins.\n */\nexport function registerJobQueueProvider(\n provider: () => Promise<JobQueueSnapshot> | JobQueueSnapshot,\n): void {\n _jobQueueProvider = provider;\n if (!_jobQueueGauge) {\n _jobQueueGauge = meter().createObservableGauge('semiont.job.queue.size', {\n description: 'Job queue size by status',\n });\n _jobQueueGauge.addCallback(async (observer) => {\n if (!_jobQueueProvider) return;\n const snap = await _jobQueueProvider();\n observer.observe(snap.pending, { 'job.status': 'pending' });\n observer.observe(snap.running, { 'job.status': 'running' });\n observer.observe(snap.complete, { 'job.status': 'complete' });\n observer.observe(snap.failed, { 'job.status': 'failed' });\n observer.observe(snap.cancelled, { 'job.status': 'cancelled' });\n });\n }\n}\n\n/**\n * Register a callback that returns the current vector-index size\n * (point count). Async to allow remote queries (Qdrant). Polled at\n * the metric-collection interval.\n */\nexport function registerVectorIndexSizeProvider(\n provider: () => Promise<number> | number,\n): void {\n _vectorIndexSizeProvider = provider;\n if (!_vectorIndexSizeGauge) {\n _vectorIndexSizeGauge = meter().createObservableGauge('semiont.vector.index.size', {\n description: 'Vector store point count',\n });\n _vectorIndexSizeGauge.addCallback(async (observer) => {\n if (_vectorIndexSizeProvider) {\n const value = await _vectorIndexSizeProvider();\n observer.observe(value);\n }\n });\n }\n}\n\n/**\n * Record an inference call. Token counts are optional — providers that\n * don't expose them (or fail before generating) record only call count\n * and duration.\n */\nexport function recordInferenceUsage(opts: {\n provider: string;\n model: string;\n durationMs: number;\n outcome: 'success' | 'error';\n inputTokens?: number;\n outputTokens?: number;\n}): void {\n const baseAttrs = {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.outcome': opts.outcome,\n };\n inferenceCallsCounter().add(1, baseAttrs);\n inferenceDurationHistogram().record(opts.durationMs, baseAttrs);\n if (opts.inputTokens != null && opts.inputTokens > 0) {\n inferenceTokensCounter().add(opts.inputTokens, {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.direction': 'input',\n });\n }\n if (opts.outputTokens != null && opts.outputTokens > 0) {\n inferenceTokensCounter().add(opts.outputTokens, {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.direction': 'output',\n });\n }\n}\n\n// ── Re-exports from @opentelemetry/api ─────────────────────────────────\n\nexport { SpanKind, SpanStatusCode, type Attributes, type Span } from '@opentelemetry/api';\n","/**\n * Process-level structured logger for Node entry points.\n *\n * Used by long-lived Node processes (backend, workers, smelter) that\n * want JSON-structured stdout with active-span trace correlation. The\n * `trace_id` / `span_id` fields are populated from the current OTel\n * span context via `getLogTraceContext` — this is the same Tier 3\n * correlation that lets a grep through stdout line up with the trace\n * UI without manual stitching.\n *\n * Reads `LOG_LEVEL` (default `info`) and `LOG_FORMAT` (`json` default,\n * `simple` for human-friendly dev output).\n *\n * Co-located with `getLogTraceContext` deliberately: this is the\n * only reasonably-shaped consumer of that helper, and putting them in\n * the same package keeps the trace-id wiring in one place.\n */\n\nimport winston from 'winston';\nimport type { Logger } from '@semiont/core';\nimport { getLogTraceContext } from './index.js';\n\nconst traceContextFormat = winston.format((info) => {\n const trace = getLogTraceContext();\n if (trace) {\n info.trace_id = trace.trace_id;\n info.span_id = trace.span_id;\n }\n return info;\n})();\n\nexport function createProcessLogger(component: string): Logger {\n const level = process.env.LOG_LEVEL ?? 'info';\n const format = process.env.LOG_FORMAT === 'simple'\n ? winston.format.combine(\n winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),\n winston.format.errors({ stack: true }),\n traceContextFormat,\n winston.format.printf(({ level: lvl, message, timestamp, ...meta }) => {\n const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';\n return `${timestamp} [${lvl.toUpperCase()}] [${component}] ${message}${metaStr}`;\n }),\n )\n : winston.format.combine(\n winston.format.timestamp(),\n winston.format.errors({ stack: true }),\n traceContextFormat,\n winston.format.json(),\n );\n\n const logger = winston.createLogger({\n level,\n defaultMeta: { component },\n format,\n transports: [new winston.transports.Console()],\n });\n\n return logger;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/process-logger.ts"],"names":["trace"],"mappings":";;;;;AAqDA,wBAAA,CAAyB,MAAM;AAC7B,EAAA,MAAM,IAAA,GAAO,MAAM,aAAA,EAAc;AACjC,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,MAAM,GAAA,GAAM,KAAK,WAAA,EAAY;AAC7B,EAAA,IAAI,CAAC,kBAAA,CAAmB,GAAG,CAAA,EAAG,OAAO,MAAA;AACrC,EAAA,OAAO,GAAA,CAAI,OAAA;AACb,CAAC,CAAA;AA+JM,SAAS,kBAAA,GAAwE;AACtF,EAAA,MAAM,IAAA,GAAO,MAAM,aAAA,EAAc;AACjC,EAAA,IAAI,CAAC,MAAM,OAAO,MAAA;AAClB,EAAA,MAAM,GAAA,GAAM,KAAK,WAAA,EAAY;AAC7B,EAAA,IAAI,CAAC,kBAAA,CAAmB,GAAG,CAAA,EAAG,OAAO,MAAA;AACrC,EAAA,OAAO,EAAE,QAAA,EAAU,GAAA,CAAI,OAAA,EAAS,OAAA,EAAS,IAAI,MAAA,EAAO;AACtD;;;AC1MA,IAAM,kBAAA,GAAqB,OAAA,CAAQ,MAAA,CAAO,CAAC,IAAA,KAAS;AAClD,EAAA,MAAMA,SAAQ,kBAAA,EAAmB;AACjC,EAAA,IAAIA,MAAAA,EAAO;AACT,IAAA,IAAA,CAAK,WAAWA,MAAAA,CAAM,QAAA;AACtB,IAAA,IAAA,CAAK,UAAUA,MAAAA,CAAM,OAAA;AAAA,EACvB;AACA,EAAA,OAAO,IAAA;AACT,CAAC,CAAA,EAAE;AAEI,SAAS,oBAAoB,SAAA,EAA2B;AAC7D,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,GAAA,CAAI,SAAA,IAAa,MAAA;AACvC,EAAA,MAAM,SAAS,OAAA,CAAQ,GAAA,CAAI,UAAA,KAAe,QAAA,GACtC,QAAQ,MAAA,CAAO,OAAA;AAAA,IACb,QAAQ,MAAA,CAAO,SAAA,CAAU,EAAE,MAAA,EAAQ,uBAAuB,CAAA;AAAA,IAC1D,QAAQ,MAAA,CAAO,MAAA,CAAO,EAAE,KAAA,EAAO,MAAM,CAAA;AAAA,IACrC,kBAAA;AAAA,IACA,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,CAAC,EAAE,KAAA,EAAO,GAAA,EAAK,OAAA,EAAS,SAAA,EAAW,GAAG,IAAA,EAAK,KAAM;AACrE,MAAA,MAAM,OAAA,GAAU,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,MAAA,GAAS,CAAA,GAAI,CAAA,CAAA,EAAI,IAAA,CAAK,SAAA,CAAU,IAAI,CAAC,CAAA,CAAA,GAAK,EAAA;AAC5E,MAAA,OAAO,CAAA,EAAG,SAAS,CAAA,EAAA,EAAK,GAAA,CAAI,WAAA,EAAa,CAAA,GAAA,EAAM,SAAS,CAAA,EAAA,EAAK,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA;AAAA,IAChF,CAAC;AAAA,GACH,GACA,QAAQ,MAAA,CAAO,OAAA;AAAA,IACb,OAAA,CAAQ,OAAO,SAAA,EAAU;AAAA,IACzB,QAAQ,MAAA,CAAO,MAAA,CAAO,EAAE,KAAA,EAAO,MAAM,CAAA;AAAA,IACrC,kBAAA;AAAA,IACA,OAAA,CAAQ,OAAO,IAAA;AAAK,GACtB;AAEJ,EAAA,MAAM,MAAA,GAAS,QAAQ,YAAA,CAAa;AAAA,IAClC,KAAA;AAAA,IACA,WAAA,EAAa,EAAE,SAAA,EAAU;AAAA,IACzB,MAAA;AAAA,IACA,YAAY,CAAC,IAAI,OAAA,CAAQ,UAAA,CAAW,SAAS;AAAA,GAC9C,CAAA;AAED,EAAA,OAAO,MAAA;AACT","file":"process-logger.js","sourcesContent":["/**\n * @semiont/observability — public API.\n *\n * Universal surface (works in Node + browser). For SDK *initialization*,\n * import from `@semiont/observability/node` or `/web` at the process entry\n * point. Everything else uses this module.\n *\n * Tier 2 of `.plans/OBSERVABILITY.md`. The public surface:\n *\n * - `withSpan(name, fn, options?)` — wrap an async block in a span;\n * `options` carries `kind` and `attrs`.\n * - `withActorSpan(actor, channel, fn, extraAttrs?)` — consumer-span\n * wrapper for bus-event handlers, with handler-duration recording.\n * - `injectTraceparent(payload)` / `extractTraceparent(payload)` — W3C\n * trace-context propagation across the SSE channel (the bus payload\n * gets a `_trace?: { traceparent }` sibling to `correlationId`).\n * - `withTraceparent(carrier, fn)` — run `fn` with the incoming\n * traceparent as the parent context.\n * - `getActiveTraceparent()` — read the active span's traceparent for\n * manual propagation (e.g. attaching to a fetch header or SSE field).\n * - `getLogTraceContext()` — active `trace_id` / `span_id` for log-line\n * correlation.\n * - Metric recorders (`recordBusEmit`, `recordHandlerDuration`,\n * `recordJobOutcome`, `recordSubscriberConnect` / `Disconnect`,\n * `recordInferenceUsage`) and gauge providers\n * (`registerJobQueueProvider`, `registerVectorIndexSizeProvider`).\n *\n * No-op when no exporter is configured: `@opentelemetry/api`'s default\n * tracer is a no-op, so `withSpan` is essentially free until\n * `initObservability*()` runs.\n */\n\nimport {\n context,\n isSpanContextValid,\n metrics,\n propagation,\n SpanKind,\n SpanStatusCode,\n trace,\n type Attributes,\n type Counter,\n type Histogram,\n type ObservableGauge,\n type Span,\n type UpDownCounter,\n} from '@opentelemetry/api';\nimport { setBusLogTraceIdProvider } from '@semiont/core';\n\n// Wire `busLog`'s trace-id provider once at module load. When an OTel\n// SDK is initialized (and a span is active when `busLog` fires), the\n// emitted line gets a `trace=<8hex>` suffix that correlates the\n// grep-timeline with the trace UI. No-op when no SDK is active.\nsetBusLogTraceIdProvider(() => {\n const span = trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n if (!isSpanContextValid(ctx)) return undefined;\n return ctx.traceId;\n});\n\nconst TRACER_NAME = 'semiont';\n\nconst tracer = () => trace.getTracer(TRACER_NAME);\n\n// ── withSpan ───────────────────────────────────────────────────────────\n\n/**\n * Wrap an async block in a span. The span is started before `fn` runs and\n * ended after it resolves or rejects; exceptions are recorded and the span\n * status is set to ERROR. `kind` defaults to INTERNAL.\n */\nexport async function withSpan<T>(\n name: string,\n fn: (span: Span) => Promise<T> | T,\n options?: { kind?: SpanKind; attrs?: Attributes },\n): Promise<T> {\n const span = tracer().startSpan(name, {\n kind: options?.kind ?? SpanKind.INTERNAL,\n ...(options?.attrs ? { attributes: options.attrs } : {}),\n });\n try {\n return await context.with(trace.setSpan(context.active(), span), () => fn(span));\n } catch (err) {\n span.recordException(err as Error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: err instanceof Error ? err.message : String(err),\n });\n throw err;\n } finally {\n span.end();\n }\n}\n\n// ── Traceparent on bus payloads ────────────────────────────────────────\n\nconst TRACE_FIELD = '_trace';\n\n/**\n * Sibling of `correlationId` on bus payloads. Lives on the SSE event body\n * because SSE has no header trailer; the SDK strips it before delivering\n * the payload to subscribers. Additive — payloads without `_trace` parse\n * unchanged.\n */\nexport interface TraceCarrier {\n /** W3C `traceparent` header value (`00-<traceId>-<spanId>-<flags>`). */\n traceparent: string;\n /** W3C `tracestate` header value (vendor-specific extensions). */\n tracestate?: string;\n}\n\n/**\n * Read the active span's W3C traceparent (and tracestate). Returns\n * `undefined` if no span is active.\n */\nexport function getActiveTraceparent(): TraceCarrier | undefined {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n const traceparent = carrier['traceparent'];\n if (!traceparent) return undefined;\n return carrier['tracestate']\n ? { traceparent, tracestate: carrier['tracestate'] }\n : { traceparent };\n}\n\n/**\n * Attach the active span's trace-context to a payload object as\n * `_trace`. No-op when no span is active. Returns the same object\n * reference for chaining.\n */\nexport function injectTraceparent<T extends Record<string, unknown>>(payload: T): T {\n const carrier = getActiveTraceparent();\n if (carrier) {\n (payload as Record<string, unknown>)[TRACE_FIELD] = carrier;\n }\n return payload;\n}\n\n/**\n * Strip and return the `_trace` field from a payload. Mutates `payload`.\n * The field is internal plumbing and should not be visible to subscribers.\n */\nexport function extractTraceparent<T extends Record<string, unknown>>(\n payload: T,\n): TraceCarrier | undefined {\n const carrier = (payload as Record<string, unknown>)[TRACE_FIELD] as\n | TraceCarrier\n | undefined;\n if (carrier !== undefined) {\n delete (payload as Record<string, unknown>)[TRACE_FIELD];\n }\n if (!carrier || typeof carrier.traceparent !== 'string') return undefined;\n return carrier;\n}\n\n/**\n * Run `fn` with the given W3C traceparent set as the parent context.\n * Any spans started inside `fn` will be children of the incoming trace.\n * No-op if `carrier` is undefined.\n */\nexport function withTraceparent<T>(\n carrier: TraceCarrier | undefined,\n fn: () => T,\n): T {\n if (!carrier) return fn();\n const carrierObj: Record<string, string> = { traceparent: carrier.traceparent };\n if (carrier.tracestate) carrierObj['tracestate'] = carrier.tracestate;\n const ctx = propagation.extract(context.active(), carrierObj);\n return context.with(ctx, fn);\n}\n\n// ── Actor handler convenience ──────────────────────────────────────────\n\n/**\n * Wrap a bus-event handler in an `actor.<name>:<channel>` consumer span.\n * Used at every `eventBus.get(channel).subscribe(handler)` site inside\n * an actor (Stower, Gatherer, Matcher, Browser, Smelter), to attribute\n * each in-process subscriber's work to a span without scattering manual\n * `withSpan` calls across handler bodies.\n *\n * The span's parent is the active context at the time the handler\n * fires — which is the `bus.dispatch:<channel>` span on the gateway\n * (Subject.next runs synchronously inside the dispatch span), or the\n * `bus.emit:<channel>` span when an actor emits to itself.\n */\nexport async function withActorSpan<T>(\n actor: string,\n channel: string,\n fn: (span: Span) => Promise<T> | T,\n extraAttrs?: Attributes,\n): Promise<T> {\n const start = performance.now();\n try {\n return await withSpan(`actor.${actor}:${channel}`, fn, {\n kind: SpanKind.CONSUMER,\n attrs: {\n actor,\n 'bus.channel': channel,\n ...(extraAttrs ?? {}),\n },\n });\n } finally {\n recordHandlerDuration(actor, channel, performance.now() - start);\n }\n}\n\n// ── Log correlation ────────────────────────────────────────────────────\n\n/**\n * Read the active span's `trace_id` / `span_id` for log-line correlation.\n * Tier 3 of `.plans/OBSERVABILITY.md`. Each structured log line gets\n * tagged with these so a log query in CloudWatch / Loki / Datadog can\n * jump to the trace in Tempo / Jaeger / X-Ray.\n *\n * Returns `undefined` if no span is active, or if the active span's\n * context is invalid (uninitialized SDK, no-op tracer).\n */\nexport function getLogTraceContext(): { trace_id: string; span_id: string } | undefined {\n const span = trace.getActiveSpan();\n if (!span) return undefined;\n const ctx = span.spanContext();\n if (!isSpanContextValid(ctx)) return undefined;\n return { trace_id: ctx.traceId, span_id: ctx.spanId };\n}\n\n// ── Metrics — Tier 3 ───────────────────────────────────────────────────\n\nconst METER_NAME = 'semiont';\n\nconst meter = () => metrics.getMeter(METER_NAME);\n\nlet _busEmitCounter: Counter | undefined;\nlet _handlerDurationHistogram: Histogram | undefined;\nlet _jobOutcomeCounter: Counter | undefined;\nlet _jobDurationHistogram: Histogram | undefined;\nlet _gatherDegradeCounter: Counter | undefined;\nlet _inferenceCallsCounter: Counter | undefined;\nlet _inferenceTokensCounter: Counter | undefined;\nlet _inferenceDurationHistogram: Histogram | undefined;\nlet _sseSubscribers: UpDownCounter | undefined;\nlet _jobQueueGauge: ObservableGauge | undefined;\nlet _jobQueueProvider: (() => Promise<JobQueueSnapshot> | JobQueueSnapshot) | undefined;\nlet _vectorIndexSizeGauge: ObservableGauge | undefined;\nlet _vectorIndexSizeProvider: (() => Promise<number> | number) | undefined;\n\n/** Snapshot of job-queue contents by status. Match `JobQueue.getStats()`. */\nexport interface JobQueueSnapshot {\n pending: number;\n running: number;\n complete: number;\n failed: number;\n cancelled: number;\n}\n\nfunction busEmitCounter(): Counter {\n if (!_busEmitCounter) {\n _busEmitCounter = meter().createCounter('semiont.bus.emit', {\n description: 'Bus emits by channel and scope',\n });\n }\n return _busEmitCounter;\n}\n\nfunction handlerDurationHistogram(): Histogram {\n if (!_handlerDurationHistogram) {\n _handlerDurationHistogram = meter().createHistogram('semiont.handler.duration', {\n description: 'In-process actor handler duration',\n unit: 'ms',\n });\n }\n return _handlerDurationHistogram;\n}\n\nfunction jobOutcomeCounter(): Counter {\n if (!_jobOutcomeCounter) {\n _jobOutcomeCounter = meter().createCounter('semiont.job.outcome', {\n description: 'Worker job completions by type and outcome',\n });\n }\n return _jobOutcomeCounter;\n}\n\nfunction jobDurationHistogram(): Histogram {\n if (!_jobDurationHistogram) {\n _jobDurationHistogram = meter().createHistogram('semiont.job.duration', {\n description: 'Worker job duration by type',\n unit: 'ms',\n });\n }\n return _jobDurationHistogram;\n}\n\nfunction inferenceCallsCounter(): Counter {\n if (!_inferenceCallsCounter) {\n _inferenceCallsCounter = meter().createCounter('semiont.inference.calls', {\n description: 'Inference API calls by provider, model, and outcome',\n });\n }\n return _inferenceCallsCounter;\n}\n\nfunction inferenceTokensCounter(): Counter {\n if (!_inferenceTokensCounter) {\n _inferenceTokensCounter = meter().createCounter('semiont.inference.tokens', {\n description: 'Inference token usage by provider, model, and direction',\n });\n }\n return _inferenceTokensCounter;\n}\n\nfunction inferenceDurationHistogram(): Histogram {\n if (!_inferenceDurationHistogram) {\n _inferenceDurationHistogram = meter().createHistogram('semiont.inference.duration', {\n description: 'Inference call duration by provider, model, and outcome',\n unit: 'ms',\n });\n }\n return _inferenceDurationHistogram;\n}\n\nfunction sseSubscribersCounter(): UpDownCounter {\n if (!_sseSubscribers) {\n _sseSubscribers = meter().createUpDownCounter('semiont.sse.subscribers', {\n description: 'Active SSE subscribers',\n });\n }\n return _sseSubscribers;\n}\n\n/** Increment the bus-emit counter. Called at every transport `emit` site. */\nexport function recordBusEmit(channel: string, scope?: string): void {\n busEmitCounter().add(1, {\n 'bus.channel': channel,\n ...(scope ? { 'bus.scope': scope } : {}),\n });\n}\n\n/** Record an in-process actor handler's duration. */\nexport function recordHandlerDuration(actor: string, channel: string, durationMs: number): void {\n handlerDurationHistogram().record(durationMs, {\n actor,\n 'bus.channel': channel,\n });\n}\n\n/** Record a worker job's outcome and duration. */\nexport function recordJobOutcome(jobType: string, outcome: 'completed' | 'failed', durationMs: number): void {\n jobOutcomeCounter().add(1, { 'job.type': jobType, 'job.outcome': outcome });\n jobDurationHistogram().record(durationMs, { 'job.type': jobType, 'job.outcome': outcome });\n}\n\nfunction gatherDegradeCounter(): Counter {\n if (!_gatherDegradeCounter) {\n _gatherDegradeCounter = meter().createCounter('semiont.gather.degraded', {\n description: 'Gathers that degraded because an eventually-consistent projection did not catch up within its read barrier (vectors: absent semanticContext; graph: projection-lag failure). Labeled by projection.',\n });\n }\n return _gatherDegradeCounter;\n}\n\n/**\n * Record a gather degraded by a projection read barrier: `'vectors'` — the\n * Smelter settle barrier timed out (semanticContext shipped absent);\n * `'graph'` — the Weaver applied barrier + poll floor exhausted (projection\n * lag surfaced as a distinct failure). Fleet-alertable counterpart of the\n * `[gather DEGRADED]` L4 breadcrumbs — a rising rate on either label means\n * that pipeline is not keeping up.\n */\nexport function recordGatherDegrade(projection: 'graph' | 'vectors'): void {\n gatherDegradeCounter().add(1, { projection });\n}\n\n/** Increment the SSE subscriber gauge — call on `/bus/subscribe` open. */\nexport function recordSubscriberConnect(): void {\n sseSubscribersCounter().add(1);\n}\n\n/** Decrement on disconnect. Pair with `recordSubscriberConnect`. */\nexport function recordSubscriberDisconnect(): void {\n sseSubscribersCounter().add(-1);\n}\n\n/**\n * Register a callback that returns the current job-queue snapshot.\n * Polled at the SDK's metric-collection interval. The single gauge\n * emits one observation per status (`pending`, `running`, …) tagged\n * with the `job.status` attribute. Idempotent — last registered\n * provider wins.\n */\nexport function registerJobQueueProvider(\n provider: () => Promise<JobQueueSnapshot> | JobQueueSnapshot,\n): void {\n _jobQueueProvider = provider;\n if (!_jobQueueGauge) {\n _jobQueueGauge = meter().createObservableGauge('semiont.job.queue.size', {\n description: 'Job queue size by status',\n });\n _jobQueueGauge.addCallback(async (observer) => {\n if (!_jobQueueProvider) return;\n const snap = await _jobQueueProvider();\n observer.observe(snap.pending, { 'job.status': 'pending' });\n observer.observe(snap.running, { 'job.status': 'running' });\n observer.observe(snap.complete, { 'job.status': 'complete' });\n observer.observe(snap.failed, { 'job.status': 'failed' });\n observer.observe(snap.cancelled, { 'job.status': 'cancelled' });\n });\n }\n}\n\n/**\n * Register a callback that returns the current vector-index size\n * (point count). Async to allow remote queries (Qdrant). Polled at\n * the metric-collection interval.\n */\nexport function registerVectorIndexSizeProvider(\n provider: () => Promise<number> | number,\n): void {\n _vectorIndexSizeProvider = provider;\n if (!_vectorIndexSizeGauge) {\n _vectorIndexSizeGauge = meter().createObservableGauge('semiont.vector.index.size', {\n description: 'Vector store point count',\n });\n _vectorIndexSizeGauge.addCallback(async (observer) => {\n if (_vectorIndexSizeProvider) {\n const value = await _vectorIndexSizeProvider();\n observer.observe(value);\n }\n });\n }\n}\n\n/**\n * Record an inference call. Token counts are optional — providers that\n * don't expose them (or fail before generating) record only call count\n * and duration.\n */\nexport function recordInferenceUsage(opts: {\n provider: string;\n model: string;\n durationMs: number;\n outcome: 'success' | 'error';\n inputTokens?: number;\n outputTokens?: number;\n}): void {\n const baseAttrs = {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.outcome': opts.outcome,\n };\n inferenceCallsCounter().add(1, baseAttrs);\n inferenceDurationHistogram().record(opts.durationMs, baseAttrs);\n if (opts.inputTokens != null && opts.inputTokens > 0) {\n inferenceTokensCounter().add(opts.inputTokens, {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.direction': 'input',\n });\n }\n if (opts.outputTokens != null && opts.outputTokens > 0) {\n inferenceTokensCounter().add(opts.outputTokens, {\n 'inference.provider': opts.provider,\n 'inference.model': opts.model,\n 'inference.direction': 'output',\n });\n }\n}\n\n// ── Re-exports from @opentelemetry/api ─────────────────────────────────\n\nexport { SpanKind, SpanStatusCode, type Attributes, type Span } from '@opentelemetry/api';\n","/**\n * Process-level structured logger for Node entry points.\n *\n * Used by long-lived Node processes (gateway, workers, smelter) that\n * want JSON-structured stdout with active-span trace correlation. The\n * `trace_id` / `span_id` fields are populated from the current OTel\n * span context via `getLogTraceContext` — this is the same Tier 3\n * correlation that lets a grep through stdout line up with the trace\n * UI without manual stitching.\n *\n * Reads `LOG_LEVEL` (default `info`) and `LOG_FORMAT` (`json` default,\n * `simple` for human-friendly dev output).\n *\n * Co-located with `getLogTraceContext` deliberately: this is the\n * only reasonably-shaped consumer of that helper, and putting them in\n * the same package keeps the trace-id wiring in one place.\n */\n\nimport winston from 'winston';\nimport type { Logger } from '@semiont/core';\nimport { getLogTraceContext } from './index.js';\n\nconst traceContextFormat = winston.format((info) => {\n const trace = getLogTraceContext();\n if (trace) {\n info.trace_id = trace.trace_id;\n info.span_id = trace.span_id;\n }\n return info;\n})();\n\nexport function createProcessLogger(component: string): Logger {\n const level = process.env.LOG_LEVEL ?? 'info';\n const format = process.env.LOG_FORMAT === 'simple'\n ? winston.format.combine(\n winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),\n winston.format.errors({ stack: true }),\n traceContextFormat,\n winston.format.printf(({ level: lvl, message, timestamp, ...meta }) => {\n const metaStr = Object.keys(meta).length > 0 ? ` ${JSON.stringify(meta)}` : '';\n return `${timestamp} [${lvl.toUpperCase()}] [${component}] ${message}${metaStr}`;\n }),\n )\n : winston.format.combine(\n winston.format.timestamp(),\n winston.format.errors({ stack: true }),\n traceContextFormat,\n winston.format.json(),\n );\n\n const logger = winston.createLogger({\n level,\n defaultMeta: { component },\n format,\n transports: [new winston.transports.Console()],\n });\n\n return logger;\n}\n"]}
|
package/dist/web.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Web SDK initialization. Call once at the SPA bootstrap (e.g.
|
|
3
|
-
* `apps/
|
|
3
|
+
* `apps/browser/src/main.tsx`).
|
|
4
4
|
*
|
|
5
5
|
* Configuration for the browser doesn't read env vars; the SPA passes
|
|
6
6
|
* config explicitly. CORS-allowed OTLP endpoints only — operators
|
package/dist/web.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/web.ts"],"names":[],"mappings":";;;;;;AA4CA,IAAI,gBAAA;AAMG,SAAS,qBAAqB,MAAA,EAAyC;AAC5E,EAAA,IAAI,kBAAkB,OAAO,KAAA;AAC7B,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,IAAW,OAAA,CAAQ,OAAO,YAAY,CAAA;AAC7D,EAAA,IAAI,CAAC,SAAS,OAAO,KAAA;AAErB,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,YAAA,GACpB,IAAI,iBAAA,CAAkB;AAAA,IACpB,KAAK,MAAA,CAAO,YAAA;AAAA,IACZ,GAAI,OAAO,WAAA,GAAc,EAAE,SAAS,MAAA,CAAO,WAAA,KAAgB;AAAC,GAC7D,CAAA,GACD,IAAI,mBAAA,EAAoB;AAE5B,EAAA,MAAM,WAAW,sBAAA,CAAuB;AAAA,IACtC,CAAC,iBAAiB,GAAG,MAAA,CAAO,WAAA;AAAA,IAC5B,CAAC,oBAAoB,GAAG,MAAA,CAAO,cAAA,IAAkB;AAAA,GAClD,CAAA;AAED,EAAA,gBAAA,GAAmB,IAAI,iBAAA,CAAkB;AAAA,IACvC,QAAA;AAAA,IACA,cAAA,EAAgB,CAAC,IAAI,kBAAA,CAAmB,QAAQ,CAAC;AAAA,GAClD,CAAA;AAED,EAAA,gBAAA,CAAiB,QAAA,EAAS;AAC1B,EAAA,OAAO,IAAA;AACT","file":"web.js","sourcesContent":["/**\n * Web SDK initialization. Call once at the SPA bootstrap (e.g.\n * `apps/
|
|
1
|
+
{"version":3,"sources":["../src/web.ts"],"names":[],"mappings":";;;;;;AA4CA,IAAI,gBAAA;AAMG,SAAS,qBAAqB,MAAA,EAAyC;AAC5E,EAAA,IAAI,kBAAkB,OAAO,KAAA;AAC7B,EAAA,MAAM,OAAA,GAAU,MAAA,CAAO,OAAA,IAAW,OAAA,CAAQ,OAAO,YAAY,CAAA;AAC7D,EAAA,IAAI,CAAC,SAAS,OAAO,KAAA;AAErB,EAAA,MAAM,QAAA,GAAW,MAAA,CAAO,YAAA,GACpB,IAAI,iBAAA,CAAkB;AAAA,IACpB,KAAK,MAAA,CAAO,YAAA;AAAA,IACZ,GAAI,OAAO,WAAA,GAAc,EAAE,SAAS,MAAA,CAAO,WAAA,KAAgB;AAAC,GAC7D,CAAA,GACD,IAAI,mBAAA,EAAoB;AAE5B,EAAA,MAAM,WAAW,sBAAA,CAAuB;AAAA,IACtC,CAAC,iBAAiB,GAAG,MAAA,CAAO,WAAA;AAAA,IAC5B,CAAC,oBAAoB,GAAG,MAAA,CAAO,cAAA,IAAkB;AAAA,GAClD,CAAA;AAED,EAAA,gBAAA,GAAmB,IAAI,iBAAA,CAAkB;AAAA,IACvC,QAAA;AAAA,IACA,cAAA,EAAgB,CAAC,IAAI,kBAAA,CAAmB,QAAQ,CAAC;AAAA,GAClD,CAAA;AAED,EAAA,gBAAA,CAAiB,QAAA,EAAS;AAC1B,EAAA,OAAO,IAAA;AACT","file":"web.js","sourcesContent":["/**\n * Web SDK initialization. Call once at the SPA bootstrap (e.g.\n * `apps/browser/src/main.tsx`).\n *\n * Configuration for the browser doesn't read env vars; the SPA passes\n * config explicitly. CORS-allowed OTLP endpoints only — operators\n * typically run a collector that exposes a CORS-enabled `/v1/traces`\n * endpoint.\n *\n * Without an `otlpEndpoint`, falls back to a console exporter (visible\n * in DevTools).\n */\n\nimport { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';\nimport { resourceFromAttributes } from '@opentelemetry/resources';\nimport {\n BatchSpanProcessor,\n ConsoleSpanExporter,\n WebTracerProvider,\n} from '@opentelemetry/sdk-trace-web';\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from '@opentelemetry/semantic-conventions';\n\nexport interface WebObservabilityConfig {\n /** Service identity (e.g. `semiont-frontend`). */\n serviceName: string;\n /** Service version. */\n serviceVersion?: string;\n /**\n * OTLP HTTP endpoint (e.g. `https://collector.example.com/v1/traces`).\n * If omitted, the SDK uses a console exporter.\n */\n otlpEndpoint?: string;\n /** Optional headers (e.g. SaaS APM auth). */\n otlpHeaders?: Record<string, string>;\n /**\n * Force on (`true`) or force off (`false`). When omitted, the SDK\n * initializes if `otlpEndpoint` is present, otherwise off.\n */\n enabled?: boolean;\n}\n\nlet providerInstance: WebTracerProvider | undefined;\n\n/**\n * Initialize OTel for the SPA. Idempotent. Returns `true` if the SDK\n * started, `false` if disabled or already initialized.\n */\nexport function initObservabilityWeb(config: WebObservabilityConfig): boolean {\n if (providerInstance) return false;\n const enabled = config.enabled ?? Boolean(config.otlpEndpoint);\n if (!enabled) return false;\n\n const exporter = config.otlpEndpoint\n ? new OTLPTraceExporter({\n url: config.otlpEndpoint,\n ...(config.otlpHeaders ? { headers: config.otlpHeaders } : {}),\n })\n : new ConsoleSpanExporter();\n\n const resource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: config.serviceName,\n [ATTR_SERVICE_VERSION]: config.serviceVersion ?? '0.0.0',\n });\n\n providerInstance = new WebTracerProvider({\n resource,\n spanProcessors: [new BatchSpanProcessor(exporter)],\n });\n\n providerInstance.register();\n return true;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@semiont/observability",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.29",
|
|
4
4
|
"description": "OpenTelemetry-based tracing for Semiont — Tier 2 of OBSERVABILITY.md. Process-init helpers (Node + Web), withSpan helper, W3C traceparent inject/extract for bus payloads. No-op when no exporter is configured.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
"author": "Semiont Team",
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"rollup": "^4.
|
|
58
|
+
"rollup": "^4.63.0",
|
|
59
59
|
"rollup-plugin-dts": "^6.4.1",
|
|
60
60
|
"tsup": "^8.5.1",
|
|
61
61
|
"typescript": "^6.0.2",
|
|
62
|
-
"vitest": "^4.1.
|
|
62
|
+
"vitest": "^4.1.11"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
65
|
"@opentelemetry/api": "^1.9.1",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
73
73
|
"@opentelemetry/sdk-trace-web": "^2.10.0",
|
|
74
74
|
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
75
|
-
"@semiont/core": "0.5.
|
|
75
|
+
"@semiont/core": "0.5.29",
|
|
76
76
|
"winston": "^3.17.0"
|
|
77
77
|
}
|
|
78
78
|
}
|