@semiont/observability 0.4.21 → 0.5.0
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 +186 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# @semiont/observability
|
|
2
|
+
|
|
3
|
+
[](https://github.com/The-AI-Alliance/semiont/actions/workflows/package-tests.yml?query=branch%3Amain+is%3Asuccess+job%3A%22Test+observability%22)
|
|
4
|
+
[](https://codecov.io/gh/The-AI-Alliance/semiont?flag=observability)
|
|
5
|
+
[](https://www.npmjs.com/package/@semiont/observability)
|
|
6
|
+
[](https://www.npmjs.com/package/@semiont/observability)
|
|
7
|
+
[](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.
|
|
3
|
+
"version": "0.5.0",
|
|
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": "
|
|
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",
|