@semiont/observability 0.4.21 → 0.4.22

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.
Files changed (2) hide show
  1. package/README.md +186 -0
  2. package/package.json +2 -2
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # @semiont/observability
2
+
3
+ [![Tests](https://github.com/The-AI-Alliance/semiont/actions/workflows/package-tests.yml/badge.svg)](https://github.com/The-AI-Alliance/semiont/actions/workflows/package-tests.yml?query=branch%3Amain+is%3Asuccess+job%3A%22Test+observability%22)
4
+ [![codecov](https://codecov.io/gh/The-AI-Alliance/semiont/graph/badge.svg?flag=observability)](https://codecov.io/gh/The-AI-Alliance/semiont?flag=observability)
5
+ [![npm version](https://img.shields.io/npm/v/@semiont/observability.svg)](https://www.npmjs.com/package/@semiont/observability)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@semiont/observability.svg)](https://www.npmjs.com/package/@semiont/observability)
7
+ [![License](https://img.shields.io/npm/l/@semiont/observability.svg)](https://github.com/The-AI-Alliance/semiont/blob/main/LICENSE)
8
+
9
+ OpenTelemetry-based tracing and metrics for [Semiont](https://github.com/The-AI-Alliance/semiont). Tier 2 of the Semiont observability stack: process-init helpers (Node + Web), a thin `withSpan` wrapper, W3C trace-context propagation across the bus, and a small set of metric recorders for the platform's hot paths.
10
+
11
+ > **Off by default.** With no `OTEL_EXPORTER_OTLP_ENDPOINT` (or `OTEL_CONSOLE_EXPORTER=true`) set, every API in this package becomes a no-op via the `@opentelemetry/api` no-op tracer. You pay nothing in production unless you opt in.
12
+
13
+ ## Architecture context
14
+
15
+ Semiont's observability is layered:
16
+
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 frontend → backend → worker → smelter.
19
+ - **Tier 3** — log correlation and dashboards.
20
+
21
+ This package does not implement any platform domain logic; it provides the spanning helpers and metric recorders the rest of the codebase calls.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @semiont/observability
27
+ ```
28
+
29
+ ## Quick start (Node)
30
+
31
+ Initialize once at the process entry point, before any spanning code runs:
32
+
33
+ ```ts
34
+ // worker-main.ts (or backend index.ts, etc.)
35
+ import { initObservabilityNode } from '@semiont/observability/node';
36
+
37
+ initObservabilityNode({ serviceName: 'semiont-worker' });
38
+
39
+ // ...rest of process startup
40
+ ```
41
+
42
+ Then use the universal API anywhere:
43
+
44
+ ```ts
45
+ import { withSpan } from '@semiont/observability';
46
+
47
+ await withSpan('handle-request', async (span) => {
48
+ span.setAttribute('user.id', userId);
49
+ return await doWork();
50
+ });
51
+ ```
52
+
53
+ Configuration is via the standard `OTEL_*` env vars:
54
+
55
+ | Variable | Purpose |
56
+ |---|---|
57
+ | `OTEL_EXPORTER_OTLP_ENDPOINT` | Collector HTTP endpoint (e.g. `http://jaeger:4318`) |
58
+ | `OTEL_SERVICE_NAME` | Overrides the `serviceName` passed to `init` |
59
+ | `OTEL_TRACES_SAMPLER` | Sampler (default: `parentbased_always_on`) |
60
+ | `OTEL_TRACES_SAMPLER_ARG` | Sampler ratio (default: `1.0`) |
61
+ | `OTEL_METRIC_EXPORT_INTERVAL` | Metric export interval ms (default: 30000) |
62
+ | `OTEL_CONSOLE_EXPORTER=true` | Dev-only: emit spans + metrics to stderr |
63
+ | `OTEL_SDK_DISABLED=true` | Skip initialization entirely |
64
+
65
+ ## Quick start (Web)
66
+
67
+ ```ts
68
+ // SPA entry point (main.tsx)
69
+ import { initObservabilityWeb } from '@semiont/observability/web';
70
+
71
+ initObservabilityWeb({ serviceName: 'semiont-frontend' });
72
+ ```
73
+
74
+ Web init wires up the same universal API plus browser-appropriate context propagation. Spans created in the SPA propagate to the backend via the bus's `_trace` payload field.
75
+
76
+ ## Universal API
77
+
78
+ Everything below is from the main `@semiont/observability` import — works identically in Node and the browser.
79
+
80
+ ### Spans
81
+
82
+ ```ts
83
+ import { withSpan, withActorSpan, SpanKind } from '@semiont/observability';
84
+
85
+ // Generic async wrapper
86
+ await withSpan('parse-document', () => parser.parse(buf));
87
+
88
+ // With kind + attributes
89
+ await withSpan(
90
+ 'job:reference-annotation',
91
+ () => runJob(job),
92
+ { kind: SpanKind.CONSUMER, attrs: { 'job.id': job.id } },
93
+ );
94
+
95
+ // Actor handler wrapper — used by the bus dispatcher to standardize
96
+ // span names across StowerVM, BrowserVM, GathererVM, MatcherVM, SmelterVM.
97
+ await withActorSpan('stower', 'mark:create', () => handler(payload));
98
+ ```
99
+
100
+ ### Trace-context propagation
101
+
102
+ W3C `traceparent` propagation is automatic for HTTP requests routed through the bus. For payloads that cross SSE (or any non-HTTP channel), use the explicit helpers:
103
+
104
+ ```ts
105
+ import {
106
+ injectTraceparent,
107
+ extractTraceparent,
108
+ withTraceparent,
109
+ getActiveTraceparent,
110
+ } from '@semiont/observability';
111
+
112
+ // Sender side: stamp the active trace onto the bus payload.
113
+ const wirePayload = injectTraceparent(payload);
114
+
115
+ // Receiver side: extract and continue the trace.
116
+ const traceparent = extractTraceparent(incoming);
117
+ await withTraceparent(traceparent, () =>
118
+ withSpan('handle-incoming', () => process(incoming)),
119
+ );
120
+ ```
121
+
122
+ ### Log correlation
123
+
124
+ Add the active trace-id and span-id to every log line so log search and the trace UI link up:
125
+
126
+ ```ts
127
+ import { getLogTraceContext } from '@semiont/observability';
128
+
129
+ logger.info({ ...getLogTraceContext(), msg: 'job started' });
130
+ // → { trace_id: '4e3...', span_id: 'a1b...', msg: 'job started' }
131
+ ```
132
+
133
+ ### Metrics
134
+
135
+ Hot-path metric recorders. The names and label conventions are picked to match Tier 3 dashboards:
136
+
137
+ ```ts
138
+ import {
139
+ recordBusEmit,
140
+ recordHandlerDuration,
141
+ recordJobOutcome,
142
+ recordSubscriberConnect,
143
+ recordSubscriberDisconnect,
144
+ recordInferenceUsage,
145
+ } from '@semiont/observability';
146
+
147
+ recordBusEmit('mark:create', 'browse');
148
+ recordHandlerDuration('stower', 'mark:create', durationMs);
149
+ recordJobOutcome('reference-annotation', 'completed', durationMs);
150
+ recordSubscriberConnect();
151
+ recordInferenceUsage({ model: 'gemma3:27b', inputTokens: 412, outputTokens: 87 });
152
+ ```
153
+
154
+ ### Provider registration
155
+
156
+ Long-lived snapshots (job queue depth, vector index size) are gauges, registered via callback so the SDK can pull at metric-export time:
157
+
158
+ ```ts
159
+ import {
160
+ registerJobQueueProvider,
161
+ registerVectorIndexSizeProvider,
162
+ } from '@semiont/observability';
163
+
164
+ registerJobQueueProvider(() => ({
165
+ pending: jobs.pending.size,
166
+ running: jobs.running.size,
167
+ }));
168
+
169
+ registerVectorIndexSizeProvider(() => qdrant.getCollectionInfo());
170
+ ```
171
+
172
+ ## Implementation notes
173
+
174
+ This package wires `BasicTracerProvider` and `MeterProvider` (stable `@opentelemetry/sdk-trace-base` 2.x line) directly, plus `AsyncLocalStorageContextManager` for Node async-context propagation. It deliberately avoids `@opentelemetry/sdk-node` because that package's experimental 0.x versions cross-depend on older 2.0.x SDK lines, forcing npm to nest duplicate copies of the stable packages and bloating consumer bundles.
175
+
176
+ `initObservabilityNode` is idempotent — calling twice is a no-op and returns `false` on the second call. Both providers shut down cleanly on `SIGTERM` / `SIGINT`.
177
+
178
+ ## License
179
+
180
+ Apache-2.0 — see [LICENSE](https://github.com/The-AI-Alliance/semiont/blob/main/LICENSE).
181
+
182
+ ## Related packages
183
+
184
+ - [`@semiont/core`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/core) — Tier 1 `busLog`, domain types
185
+ - [`@semiont/sdk`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/sdk) — high-level Semiont client, the primary consumer of this package's spanning helpers
186
+ - [`@semiont/api-client`](https://github.com/The-AI-Alliance/semiont/tree/main/packages/api-client) — HTTP transport, propagates `traceparent` on every request
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@semiont/observability",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
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",
@@ -56,7 +56,7 @@
56
56
  },
57
57
  "dependencies": {
58
58
  "@opentelemetry/api": "^1.9.0",
59
- "@semiont/core": "0.4.21",
59
+ "@semiont/core": "*",
60
60
  "@opentelemetry/context-async-hooks": "^2.7.0",
61
61
  "@opentelemetry/core": "^2.7.0",
62
62
  "@opentelemetry/exporter-metrics-otlp-http": "^0.215.0",