@nwire/telemetry-otel 0.9.1 → 0.10.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/dist/exporter.d.ts +2 -2
- package/dist/exporter.js +2 -1
- package/dist/otel-types.d.ts +0 -1
- package/dist/otel-types.js +0 -1
- package/dist/telemetry-otel.d.ts +0 -1
- package/dist/telemetry-otel.js +0 -1
- package/package.json +4 -4
- package/dist/exporter.d.ts.map +0 -1
- package/dist/exporter.js.map +0 -1
- package/dist/otel-types.d.ts.map +0 -1
- package/dist/otel-types.js.map +0 -1
- package/dist/telemetry-otel.d.ts.map +0 -1
- package/dist/telemetry-otel.js.map +0 -1
- package/src/__tests__/correlation-e2e.test.ts +0 -381
- package/src/__tests__/exporter.test.ts +0 -256
- package/src/exporter.ts +0 -416
- package/src/otel-types.ts +0 -42
- package/src/telemetry-otel.ts +0 -18
package/dist/exporter.d.ts
CHANGED
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
* Returns a detach function — calling it unsubscribes and closes any
|
|
40
40
|
* still-open spans with an `unsubscribed` event.
|
|
41
41
|
*/
|
|
42
|
-
import type { Runtime
|
|
42
|
+
import type { Runtime } from "@nwire/app";
|
|
43
|
+
import type { ForgeTelemetry as Telemetry } from "@nwire/forge";
|
|
43
44
|
import type { OtelTracer } from "./otel-types.js";
|
|
44
45
|
export interface AttachOtelExporterOptions {
|
|
45
46
|
/** OTel Tracer instance from `trace.getTracer(name, version?)`. */
|
|
@@ -63,4 +64,3 @@ export interface AttachOtelExporterOptions {
|
|
|
63
64
|
readonly kinds?: readonly Telemetry["kind"][];
|
|
64
65
|
}
|
|
65
66
|
export declare function attachOtelExporter(runtime: Runtime, options: AttachOtelExporterOptions): () => void;
|
|
66
|
-
//# sourceMappingURL=exporter.d.ts.map
|
package/dist/exporter.js
CHANGED
|
@@ -54,6 +54,7 @@ export function attachOtelExporter(runtime, options) {
|
|
|
54
54
|
if (allowed && !allowed.has(rec.kind))
|
|
55
55
|
return;
|
|
56
56
|
try {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
58
|
route(rec);
|
|
58
59
|
}
|
|
59
60
|
catch (err) {
|
|
@@ -64,6 +65,7 @@ export function attachOtelExporter(runtime, options) {
|
|
|
64
65
|
console.error("[telemetry-otel] export failed:", err);
|
|
65
66
|
}
|
|
66
67
|
});
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
67
69
|
function route(rec) {
|
|
68
70
|
switch (rec.kind) {
|
|
69
71
|
case "action.dispatched":
|
|
@@ -345,4 +347,3 @@ function setAttrs(span, attrs) {
|
|
|
345
347
|
}
|
|
346
348
|
}
|
|
347
349
|
}
|
|
348
|
-
//# sourceMappingURL=exporter.js.map
|
package/dist/otel-types.d.ts
CHANGED
package/dist/otel-types.js
CHANGED
package/dist/telemetry-otel.d.ts
CHANGED
package/dist/telemetry-otel.js
CHANGED
package/package.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nwire/telemetry-otel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "OpenTelemetry bridge for the Nwire canonical telemetry stream. Translates every Telemetry record into OTLP spans + events. Plug in any OTEL exporter (Datadog, Honeycomb, Tempo, Vector → GreptimeDB).",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"files": [
|
|
8
8
|
"dist",
|
|
9
|
-
"src",
|
|
10
9
|
"LICENSE"
|
|
11
10
|
],
|
|
12
11
|
"type": "module",
|
|
@@ -22,13 +21,14 @@
|
|
|
22
21
|
"access": "public"
|
|
23
22
|
},
|
|
24
23
|
"dependencies": {
|
|
25
|
-
"@nwire/
|
|
24
|
+
"@nwire/app": "0.10.0",
|
|
25
|
+
"@nwire/forge": "0.10.0"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"typescript": "^5.6.0",
|
|
29
29
|
"vitest": "^4.0.18",
|
|
30
30
|
"zod": "^4.0.0",
|
|
31
|
-
"@nwire/messages": "0.
|
|
31
|
+
"@nwire/messages": "0.10.0"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsc && node ../../scripts/fix-dist-extensions.mjs dist",
|
package/dist/exporter.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.d.ts","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAmB,MAAM,cAAc,CAAC;AACxE,OAAO,KAAK,EAAE,UAAU,EAA4B,MAAM,cAAc,CAAC;AAEzE,MAAM,WAAW,yBAAyB;IACxC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;CAC/C;AAUD,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,yBAAyB,GACjC,MAAM,IAAI,CA4SZ"}
|
package/dist/exporter.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"exporter.js","sourceRoot":"","sources":["../src/exporter.ts"],"names":[],"mappings":"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"}
|
package/dist/otel-types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"otel-types.d.ts","sourceRoot":"","sources":["../src/otel-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAC7C,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEvC,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,QAAQ;IACvB,WAAW,CAAC,IAAI,WAAW,CAAC;IAC5B,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAC7E,aAAa,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC;IAChE,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,QAAQ,GAAG,IAAI,CAAC;IACnB,eAAe,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAAC;IAC/C,SAAS,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,cAAc,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,QAAQ,GAAG,IAAI,CAAC;IAC/E,GAAG,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,CACP,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,QAAQ,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,GACA,QAAQ,CAAC;CACb"}
|
package/dist/otel-types.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"otel-types.js","sourceRoot":"","sources":["../src/otel-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-otel.d.ts","sourceRoot":"","sources":["../src/telemetry-otel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAChF,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry-otel.js","sourceRoot":"","sources":["../src/telemetry-otel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,kBAAkB,EAAkC,MAAM,YAAY,CAAC"}
|
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Correlation E2E — depth-5 causation chain through @nwire/forge + the
|
|
3
|
-
* OTel bridge, optionally end-to-end through Vector → GreptimeDB.
|
|
4
|
-
*
|
|
5
|
-
* ## What this test proves
|
|
6
|
-
*
|
|
7
|
-
* Action.1 → Step1Done → Workflow.1 dispatches Action.2 → Step2Done
|
|
8
|
-
* → Workflow.2 dispatches Action.3 → … five levels deep.
|
|
9
|
-
*
|
|
10
|
-
* Every level carries the SAME correlationId (the trace handle) and
|
|
11
|
-
* chained causationIds (each action's envelope.causationId points at
|
|
12
|
-
* the previous level's event.messageId). The exporter must:
|
|
13
|
-
*
|
|
14
|
-
* 1. Open exactly one span per dispatched action (5 spans).
|
|
15
|
-
* 2. Stitch event.published as a span event on the open action span.
|
|
16
|
-
* 3. Preserve correlationId on every span so a trace query returns
|
|
17
|
-
* the entire chain.
|
|
18
|
-
* 4. Carry causationId so parent-child reconstruction is possible
|
|
19
|
-
* downstream.
|
|
20
|
-
*
|
|
21
|
-
* ## Two run modes
|
|
22
|
-
*
|
|
23
|
-
* - DEFAULT (no env): fake tracer, in-memory. Asserts every invariant
|
|
24
|
-
* above. Runs in `pnpm test`. Docker-free.
|
|
25
|
-
*
|
|
26
|
-
* - RUN_INTEGRATION=1: additionally pushes the captured spans as
|
|
27
|
-
* JSON-encoded log records to GreptimeDB's HTTP log-ingest endpoint
|
|
28
|
-
* (the same Vector → Greptime path documented in vector.toml), then
|
|
29
|
-
* queries Greptime's HTTP SQL endpoint to confirm the round-trip.
|
|
30
|
-
* Requires `docker compose -f docker-compose.yml -f docker-compose.telemetry.yml up -d`.
|
|
31
|
-
*
|
|
32
|
-
* ## What this does NOT catch
|
|
33
|
-
*
|
|
34
|
-
* - Network partitions between Vector and Greptime (Vector buffers;
|
|
35
|
-
* this test doesn't exercise the buffer drain).
|
|
36
|
-
* - Sampling drops at the SDK level (we don't run the real BatchSpanProcessor).
|
|
37
|
-
* - Schema drift between OTLP and Greptime's storage model (we use the
|
|
38
|
-
* log-events path, not the OTLP-protobuf path; see docs/internals/logger-correlation-e2e.md).
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
import { describe, it, expect } from "vitest";
|
|
42
|
-
import { z } from "zod";
|
|
43
|
-
import * as forge from "@nwire/forge";
|
|
44
|
-
import { defineEvent, type EventDefinition } from "@nwire/messages";
|
|
45
|
-
import { attachOtelExporter } from "../telemetry-otel";
|
|
46
|
-
import type { OtelTracer, OtelSpan } from "../telemetry-otel";
|
|
47
|
-
|
|
48
|
-
// ─── Captured-span fake tracer ────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
interface CapturedSpan {
|
|
51
|
-
name: string;
|
|
52
|
-
attributes: Record<string, unknown>;
|
|
53
|
-
events: Array<{ name: string; attrs?: Record<string, unknown> }>;
|
|
54
|
-
status: { code: number; message?: string } | null;
|
|
55
|
-
ended: boolean;
|
|
56
|
-
startTime?: number | Date;
|
|
57
|
-
endTime?: number | Date;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function makeCapturingTracer(): { tracer: OtelTracer; spans: CapturedSpan[] } {
|
|
61
|
-
const spans: CapturedSpan[] = [];
|
|
62
|
-
const tracer: OtelTracer = {
|
|
63
|
-
startSpan(name, options) {
|
|
64
|
-
const rec: CapturedSpan = {
|
|
65
|
-
name,
|
|
66
|
-
attributes: { ...(options?.attributes ?? {}) },
|
|
67
|
-
events: [],
|
|
68
|
-
status: null,
|
|
69
|
-
ended: false,
|
|
70
|
-
startTime: options?.startTime,
|
|
71
|
-
};
|
|
72
|
-
spans.push(rec);
|
|
73
|
-
const span: OtelSpan = {
|
|
74
|
-
setAttribute(k, v) {
|
|
75
|
-
rec.attributes[k] = v;
|
|
76
|
-
},
|
|
77
|
-
setAttributes(attrs) {
|
|
78
|
-
Object.assign(rec.attributes, attrs);
|
|
79
|
-
},
|
|
80
|
-
addEvent(eventName, attrs) {
|
|
81
|
-
rec.events.push({ name: eventName, attrs });
|
|
82
|
-
},
|
|
83
|
-
recordException() {
|
|
84
|
-
/* noop */
|
|
85
|
-
},
|
|
86
|
-
setStatus(s) {
|
|
87
|
-
rec.status = { ...s };
|
|
88
|
-
},
|
|
89
|
-
end(endTime) {
|
|
90
|
-
rec.ended = true;
|
|
91
|
-
rec.endTime = endTime;
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
return span;
|
|
95
|
-
},
|
|
96
|
-
};
|
|
97
|
-
return { tracer, spans };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ─── Domain: depth-5 chain ────────────────────────────────────────────
|
|
101
|
-
//
|
|
102
|
-
// step1 → Step1Done → workflow12 → step2 → Step2Done → workflow23 → … →
|
|
103
|
-
// step5. Five actions, four workflows in between, five events emitted.
|
|
104
|
-
|
|
105
|
-
const DEPTH = 5;
|
|
106
|
-
|
|
107
|
-
interface ChainModule {
|
|
108
|
-
app: forge.AppDefinition;
|
|
109
|
-
actions: forge.ActionDefinition[];
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function makeChain(): ChainModule {
|
|
113
|
-
const events: EventDefinition[] = [];
|
|
114
|
-
const actions: forge.ActionDefinition[] = [];
|
|
115
|
-
|
|
116
|
-
for (let i = 1; i <= DEPTH; i += 1) {
|
|
117
|
-
const ev = defineEvent({
|
|
118
|
-
name: `chain.step-${i}-done`,
|
|
119
|
-
description: `Step ${i} completed.`,
|
|
120
|
-
schema: z.object({ step: z.number(), payload: z.string() }),
|
|
121
|
-
});
|
|
122
|
-
events.push(ev);
|
|
123
|
-
|
|
124
|
-
const action = forge.defineAction({
|
|
125
|
-
name: `chain.step-${i}`,
|
|
126
|
-
description: `Run step ${i} of the chain.`,
|
|
127
|
-
schema: z.object({ payload: z.string() }),
|
|
128
|
-
emits: [ev],
|
|
129
|
-
handler: async (input) => ev({ step: i, payload: input.payload }),
|
|
130
|
-
});
|
|
131
|
-
actions.push(action);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Wire the workflows: event_i triggers action_{i+1}.
|
|
135
|
-
const workflows: forge.WorkflowDefinition[] = [];
|
|
136
|
-
for (let i = 1; i < DEPTH; i += 1) {
|
|
137
|
-
const sourceEvent = events[i - 1];
|
|
138
|
-
const nextAction = actions[i];
|
|
139
|
-
const wf = forge.defineWorkflow(`chain-${i}-to-${i + 1}`, ({ on, send }) => {
|
|
140
|
-
on(sourceEvent, async (ev) => {
|
|
141
|
-
await send(nextAction, { payload: `from-step-${ev.step}` });
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
workflows.push(wf);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const mod = forge.defineModule("chain", {
|
|
148
|
-
events,
|
|
149
|
-
actions,
|
|
150
|
-
workflows,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
const app = forge.defineApp("chain-e2e", { modules: [mod] });
|
|
154
|
-
return { app, actions };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// ─── In-memory invariant assertions ───────────────────────────────────
|
|
158
|
-
|
|
159
|
-
describe("@nwire/telemetry-otel — depth-5 correlation chain", () => {
|
|
160
|
-
it("emits one action span per chain level with shared correlationId", async () => {
|
|
161
|
-
const { app, actions } = makeChain();
|
|
162
|
-
const built = app.create();
|
|
163
|
-
await built.start();
|
|
164
|
-
|
|
165
|
-
const { tracer, spans } = makeCapturingTracer();
|
|
166
|
-
const detach = attachOtelExporter(built.runtime, { tracer });
|
|
167
|
-
|
|
168
|
-
await built.runtime.dispatch(actions[0], { payload: "kickoff" });
|
|
169
|
-
// Workflows run async on the in-memory bus; give the event loop a
|
|
170
|
-
// few ticks to drain all five levels.
|
|
171
|
-
await waitForChain(spans, DEPTH);
|
|
172
|
-
detach();
|
|
173
|
-
|
|
174
|
-
const actionSpans = spans.filter((s) => /^nwire\.action chain\.step-\d/.test(s.name));
|
|
175
|
-
expect(actionSpans, "must observe exactly DEPTH action spans").toHaveLength(DEPTH);
|
|
176
|
-
|
|
177
|
-
// Every action span carries the same correlationId — that's the trace key.
|
|
178
|
-
const correlationIds = new Set(
|
|
179
|
-
actionSpans.map((s) => String(s.attributes["nwire.correlation_id"])),
|
|
180
|
-
);
|
|
181
|
-
expect(correlationIds.size, "all spans share one correlationId").toBe(1);
|
|
182
|
-
|
|
183
|
-
// Each span ends OK.
|
|
184
|
-
for (const s of actionSpans) {
|
|
185
|
-
expect(s.ended).toBe(true);
|
|
186
|
-
expect(s.status?.code, `${s.name} must complete OK`).toBe(1);
|
|
187
|
-
}
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it("preserves parent-child causation across the chain", async () => {
|
|
191
|
-
const { app, actions } = makeChain();
|
|
192
|
-
const built = app.create();
|
|
193
|
-
await built.start();
|
|
194
|
-
|
|
195
|
-
const { tracer, spans } = makeCapturingTracer();
|
|
196
|
-
const detach = attachOtelExporter(built.runtime, { tracer });
|
|
197
|
-
|
|
198
|
-
await built.runtime.dispatch(actions[0], { payload: "kickoff" });
|
|
199
|
-
await waitForChain(spans, DEPTH);
|
|
200
|
-
detach();
|
|
201
|
-
|
|
202
|
-
const actionSpans = spans
|
|
203
|
-
.filter((s) => /^nwire\.action chain\.step-\d/.test(s.name))
|
|
204
|
-
.sort((a, b) => stepNumber(a.name) - stepNumber(b.name));
|
|
205
|
-
|
|
206
|
-
// Step 1 is the root — its causationId equals its own messageId (or
|
|
207
|
-
// is empty / matches the kickoff envelope). Every subsequent step's
|
|
208
|
-
// causationId should point at the messageId of the previous step's
|
|
209
|
-
// EMITTED event. We can't see message ids directly here, but we can
|
|
210
|
-
// assert causationId is non-empty AND differs from its own messageId
|
|
211
|
-
// (i.e. it's truly chained, not root-like).
|
|
212
|
-
for (let i = 1; i < actionSpans.length; i += 1) {
|
|
213
|
-
const span = actionSpans[i];
|
|
214
|
-
const msgId = String(span.attributes["nwire.message_id"]);
|
|
215
|
-
const causationId = String(span.attributes["nwire.causation_id"]);
|
|
216
|
-
expect(msgId, `step ${i + 1} must have messageId`).toBeTruthy();
|
|
217
|
-
expect(causationId, `step ${i + 1} must have causationId`).toBeTruthy();
|
|
218
|
-
expect(
|
|
219
|
-
causationId,
|
|
220
|
-
`step ${i + 1} causationId must differ from its own messageId`,
|
|
221
|
-
).not.toEqual(msgId);
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
|
|
225
|
-
it("attaches event.published as a span event on each open action span", async () => {
|
|
226
|
-
const { app, actions } = makeChain();
|
|
227
|
-
const built = app.create();
|
|
228
|
-
await built.start();
|
|
229
|
-
|
|
230
|
-
const { tracer, spans } = makeCapturingTracer();
|
|
231
|
-
const detach = attachOtelExporter(built.runtime, { tracer });
|
|
232
|
-
|
|
233
|
-
await built.runtime.dispatch(actions[0], { payload: "kickoff" });
|
|
234
|
-
await waitForChain(spans, DEPTH);
|
|
235
|
-
detach();
|
|
236
|
-
|
|
237
|
-
const actionSpans = spans.filter((s) => /^nwire\.action chain\.step-\d/.test(s.name));
|
|
238
|
-
for (const s of actionSpans) {
|
|
239
|
-
const eventPublished = s.events.filter((e) => e.name === "event.published");
|
|
240
|
-
expect(
|
|
241
|
-
eventPublished.length,
|
|
242
|
-
`${s.name} must record at least one event.published`,
|
|
243
|
-
).toBeGreaterThanOrEqual(1);
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// ─── Round-trip through Vector → GreptimeDB (gated) ───────────────────
|
|
249
|
-
|
|
250
|
-
describe.skipIf(!process.env.RUN_INTEGRATION)(
|
|
251
|
-
"@nwire/telemetry-otel — round-trip through GreptimeDB",
|
|
252
|
-
() => {
|
|
253
|
-
const GREPTIME_HTTP = process.env.GREPTIMEDB_HTTP ?? "http://localhost:4000";
|
|
254
|
-
const TABLE = `nwire_corr_e2e_${Date.now()}`;
|
|
255
|
-
|
|
256
|
-
it("ships the chain to Greptime and queries it back", async () => {
|
|
257
|
-
// 1. Probe Greptime — fail fast if compose isn't up.
|
|
258
|
-
const health = await fetch(`${GREPTIME_HTTP}/health`).catch(() => null);
|
|
259
|
-
if (!health || !health.ok) {
|
|
260
|
-
throw new Error(
|
|
261
|
-
`GreptimeDB not reachable at ${GREPTIME_HTTP}. Start it with:\n` +
|
|
262
|
-
` docker compose -f docker-compose.yml -f docker-compose.telemetry.yml up -d greptimedb vector`,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// 2. Run the chain + capture spans.
|
|
267
|
-
const { app, actions } = makeChain();
|
|
268
|
-
const built = app.create();
|
|
269
|
-
await built.start();
|
|
270
|
-
const { tracer, spans } = makeCapturingTracer();
|
|
271
|
-
attachOtelExporter(built.runtime, { tracer });
|
|
272
|
-
|
|
273
|
-
const rootCorrelationId = await dispatchAndGetCorrelationId(built.runtime, actions[0]);
|
|
274
|
-
await waitForChain(spans, DEPTH);
|
|
275
|
-
|
|
276
|
-
const actionSpans = spans.filter((s) => /^nwire\.action chain\.step-\d/.test(s.name));
|
|
277
|
-
|
|
278
|
-
// 3. Ship to Greptime via the log-events endpoint (same path Vector uses).
|
|
279
|
-
const payload = actionSpans.map((s) => ({
|
|
280
|
-
span_name: s.name,
|
|
281
|
-
correlation_id: String(s.attributes["nwire.correlation_id"]),
|
|
282
|
-
message_id: String(s.attributes["nwire.message_id"]),
|
|
283
|
-
causation_id: String(s.attributes["nwire.causation_id"]),
|
|
284
|
-
action: String(s.attributes["nwire.action"]),
|
|
285
|
-
status_code: s.status?.code ?? -1,
|
|
286
|
-
event_count: s.events.length,
|
|
287
|
-
// Greptime auto-derives `ts` from the first time-typed column;
|
|
288
|
-
// we provide one explicitly so the table is well-formed.
|
|
289
|
-
ts: Date.now(),
|
|
290
|
-
}));
|
|
291
|
-
const ndjson = payload.map((r) => JSON.stringify(r)).join("\n");
|
|
292
|
-
// Greptime ≥ 0.10 requires an explicit pipeline; `greptime_identity`
|
|
293
|
-
// is the built-in passthrough that maps NDJSON keys 1:1 to columns.
|
|
294
|
-
const ingestUrl =
|
|
295
|
-
`${GREPTIME_HTTP}/v1/events/logs` +
|
|
296
|
-
`?db=public&table=${TABLE}&pipeline_name=greptime_identity`;
|
|
297
|
-
const ingest = await fetch(ingestUrl, {
|
|
298
|
-
method: "POST",
|
|
299
|
-
headers: { "content-type": "application/x-ndjson" },
|
|
300
|
-
body: ndjson,
|
|
301
|
-
});
|
|
302
|
-
expect(ingest.ok, `ingest: ${ingest.status} ${await ingest.text()}`).toBe(true);
|
|
303
|
-
|
|
304
|
-
// 4. Query Greptime for our chain.
|
|
305
|
-
// Brief settle wait — Greptime flushes async.
|
|
306
|
-
await sleep(500);
|
|
307
|
-
const sql = encodeURIComponent(
|
|
308
|
-
`SELECT span_name, correlation_id, message_id, causation_id ` +
|
|
309
|
-
`FROM ${TABLE} WHERE correlation_id = '${rootCorrelationId}'`,
|
|
310
|
-
);
|
|
311
|
-
const queryUrl = `${GREPTIME_HTTP}/v1/sql?db=public&sql=${sql}`;
|
|
312
|
-
const queryResp = await fetch(queryUrl);
|
|
313
|
-
expect(queryResp.ok).toBe(true);
|
|
314
|
-
const body = (await queryResp.json()) as {
|
|
315
|
-
output: Array<{
|
|
316
|
-
records: { rows: unknown[][]; schema: { column_schemas: Array<{ name: string }> } };
|
|
317
|
-
}>;
|
|
318
|
-
};
|
|
319
|
-
const rows = body.output?.[0]?.records?.rows ?? [];
|
|
320
|
-
expect(rows.length, `expected ${DEPTH} rows for correlation ${rootCorrelationId}`).toBe(
|
|
321
|
-
DEPTH,
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
// Every row must carry the root correlation id.
|
|
325
|
-
const corrIdx = body.output[0].records.schema.column_schemas.findIndex(
|
|
326
|
-
(c) => c.name === "correlation_id",
|
|
327
|
-
);
|
|
328
|
-
for (const row of rows) {
|
|
329
|
-
expect(row[corrIdx]).toBe(rootCorrelationId);
|
|
330
|
-
}
|
|
331
|
-
}, 30_000);
|
|
332
|
-
},
|
|
333
|
-
);
|
|
334
|
-
|
|
335
|
-
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
336
|
-
|
|
337
|
-
function stepNumber(spanName: string): number {
|
|
338
|
-
const m = spanName.match(/step-(\d)/);
|
|
339
|
-
return m ? Number(m[1]) : -1;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function sleep(ms: number): Promise<void> {
|
|
343
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Wait for at least `levels` action spans to have been observed AND
|
|
348
|
-
* for each of them to be `ended`. Polls every 5ms with a 2s cap so
|
|
349
|
-
* tests fail loudly if the chain doesn't complete instead of hanging.
|
|
350
|
-
*/
|
|
351
|
-
async function waitForChain(spans: CapturedSpan[], levels: number): Promise<void> {
|
|
352
|
-
const start = Date.now();
|
|
353
|
-
while (Date.now() - start < 2_000) {
|
|
354
|
-
const actionSpans = spans.filter((s) => /^nwire\.action chain\.step-\d/.test(s.name));
|
|
355
|
-
if (actionSpans.length >= levels && actionSpans.every((s) => s.ended)) return;
|
|
356
|
-
await sleep(5);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Dispatch and surface the resulting envelope's correlationId. We can't
|
|
362
|
-
* read the envelope directly from `dispatch()`'s return value, so we
|
|
363
|
-
* peek at the next-emitted telemetry record. In normal use this is what
|
|
364
|
-
* Studio's correlation tooling does too.
|
|
365
|
-
*/
|
|
366
|
-
async function dispatchAndGetCorrelationId(
|
|
367
|
-
runtime: forge.Runtime,
|
|
368
|
-
action: forge.ActionDefinition,
|
|
369
|
-
): Promise<string> {
|
|
370
|
-
let captured: string | null = null;
|
|
371
|
-
const off = runtime.onTelemetry((rec) => {
|
|
372
|
-
if (captured) return;
|
|
373
|
-
if (rec.kind === "action.dispatched" && "envelope" in rec) {
|
|
374
|
-
captured = rec.envelope.correlationId;
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
await runtime.dispatch(action, { payload: "kickoff" });
|
|
378
|
-
off();
|
|
379
|
-
if (!captured) throw new Error("no correlationId captured");
|
|
380
|
-
return captured;
|
|
381
|
-
}
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@nwire/telemetry-otel` — exporter end-to-end against a fake tracer.
|
|
3
|
-
*
|
|
4
|
-
* Exercises real runtime dispatch + external call paths, then asserts the
|
|
5
|
-
* spans + events the bridge produced. The fake tracer records every
|
|
6
|
-
* startSpan / span method call so tests can inspect them.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect } from "vitest";
|
|
10
|
-
import { z } from "zod";
|
|
11
|
-
import * as forge from "@nwire/forge";
|
|
12
|
-
import { attachOtelExporter } from "../telemetry-otel";
|
|
13
|
-
import type { OtelTracer, OtelSpan } from "../telemetry-otel";
|
|
14
|
-
|
|
15
|
-
interface RecordedSpan {
|
|
16
|
-
name: string;
|
|
17
|
-
attributes: Record<string, unknown>;
|
|
18
|
-
events: Array<{ name: string; attrs?: Record<string, unknown> }>;
|
|
19
|
-
status: { code: number; message?: string } | null;
|
|
20
|
-
ended: boolean;
|
|
21
|
-
endTime?: number | Date;
|
|
22
|
-
startTime?: number | Date;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function makeFakeTracer(): { tracer: OtelTracer; spans: RecordedSpan[] } {
|
|
26
|
-
const spans: RecordedSpan[] = [];
|
|
27
|
-
const tracer: OtelTracer = {
|
|
28
|
-
startSpan(name, options) {
|
|
29
|
-
const rec: RecordedSpan = {
|
|
30
|
-
name,
|
|
31
|
-
attributes: { ...(options?.attributes ?? {}) },
|
|
32
|
-
events: [],
|
|
33
|
-
status: null,
|
|
34
|
-
ended: false,
|
|
35
|
-
startTime: options?.startTime,
|
|
36
|
-
};
|
|
37
|
-
spans.push(rec);
|
|
38
|
-
const span: OtelSpan = {
|
|
39
|
-
setAttribute(k, v) {
|
|
40
|
-
rec.attributes[k] = v;
|
|
41
|
-
},
|
|
42
|
-
setAttributes(attrs) {
|
|
43
|
-
Object.assign(rec.attributes, attrs);
|
|
44
|
-
},
|
|
45
|
-
addEvent(eventName, attrs) {
|
|
46
|
-
rec.events.push({ name: eventName, attrs });
|
|
47
|
-
},
|
|
48
|
-
recordException() {
|
|
49
|
-
/* noop */
|
|
50
|
-
},
|
|
51
|
-
setStatus(s) {
|
|
52
|
-
rec.status = { ...s };
|
|
53
|
-
},
|
|
54
|
-
end(endTime) {
|
|
55
|
-
rec.ended = true;
|
|
56
|
-
rec.endTime = endTime;
|
|
57
|
-
},
|
|
58
|
-
};
|
|
59
|
-
return span;
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
return { tracer, spans };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const SubmitInput = z.object({ who: z.string() });
|
|
66
|
-
|
|
67
|
-
const submit = forge.defineAction({
|
|
68
|
-
name: "demo.submit",
|
|
69
|
-
schema: SubmitInput,
|
|
70
|
-
emits: [],
|
|
71
|
-
handler: async () => ({ eventName: "demo.submitted", payload: { who: "Avi" } }),
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const demoModule = forge.defineModule("demo", { actions: [submit] });
|
|
75
|
-
const demoApp = forge.defineApp("demo-app", { modules: [demoModule] });
|
|
76
|
-
|
|
77
|
-
describe("attachOtelExporter", () => {
|
|
78
|
-
it("opens + closes a span for a successful dispatch", async () => {
|
|
79
|
-
const app = demoApp.create();
|
|
80
|
-
await app.start();
|
|
81
|
-
const { tracer, spans } = makeFakeTracer();
|
|
82
|
-
const detach = attachOtelExporter(app.runtime, { tracer });
|
|
83
|
-
|
|
84
|
-
await app.runtime.dispatch(submit, { who: "Avi" });
|
|
85
|
-
detach();
|
|
86
|
-
|
|
87
|
-
const action = spans.find((s) => s.name.includes("action demo.submit"));
|
|
88
|
-
expect(action).toBeDefined();
|
|
89
|
-
expect(action!.ended).toBe(true);
|
|
90
|
-
expect(action!.status?.code).toBe(1); // OK
|
|
91
|
-
expect(action!.attributes["nwire.action"]).toBe("demo.submit");
|
|
92
|
-
expect(typeof action!.attributes["nwire.message_id"]).toBe("string");
|
|
93
|
-
expect(action!.attributes["nwire.action.emitted_events"]).toBe("demo.submitted");
|
|
94
|
-
// event.published should appear as a span event on the action span (default mode).
|
|
95
|
-
expect(action!.events.some((e) => e.name === "event.published")).toBe(true);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("attaches event.published as a span event when eventsAsSpans=false", async () => {
|
|
99
|
-
const app = demoApp.create();
|
|
100
|
-
await app.start();
|
|
101
|
-
const { tracer, spans } = makeFakeTracer();
|
|
102
|
-
attachOtelExporter(app.runtime, { tracer });
|
|
103
|
-
await app.runtime.dispatch(submit, { who: "Avi" });
|
|
104
|
-
|
|
105
|
-
const actionSpans = spans.filter((s) => s.name.includes("action demo.submit"));
|
|
106
|
-
expect(actionSpans).toHaveLength(1);
|
|
107
|
-
// No standalone event span.
|
|
108
|
-
expect(spans.some((s) => s.name.includes("event demo.submitted"))).toBe(false);
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
it("opens a standalone span per event when eventsAsSpans=true", async () => {
|
|
112
|
-
const app = demoApp.create();
|
|
113
|
-
await app.start();
|
|
114
|
-
const { tracer, spans } = makeFakeTracer();
|
|
115
|
-
attachOtelExporter(app.runtime, { tracer, eventsAsSpans: true });
|
|
116
|
-
await app.runtime.dispatch(submit, { who: "Avi" });
|
|
117
|
-
|
|
118
|
-
expect(spans.some((s) => s.name.includes("event demo.submitted"))).toBe(true);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("filters by kind when `kinds` is set", async () => {
|
|
122
|
-
const app = demoApp.create();
|
|
123
|
-
await app.start();
|
|
124
|
-
const { tracer, spans } = makeFakeTracer();
|
|
125
|
-
attachOtelExporter(app.runtime, {
|
|
126
|
-
tracer,
|
|
127
|
-
kinds: ["action.dispatched", "action.completed"],
|
|
128
|
-
});
|
|
129
|
-
await app.runtime.dispatch(submit, { who: "Avi" });
|
|
130
|
-
|
|
131
|
-
// No event-published span events should appear since we filtered them out.
|
|
132
|
-
const actionSpan = spans.find((s) => s.name.includes("action demo.submit"))!;
|
|
133
|
-
expect(actionSpan.events.some((e) => e.name === "event.published")).toBe(false);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("emits ad-hoc spans for query.executed", async () => {
|
|
137
|
-
const SkillState = z.object({ items: z.array(z.string()).default([]) });
|
|
138
|
-
const Skills = forge.defineProjection<{ items: string[] }>("skills", {
|
|
139
|
-
listens: [],
|
|
140
|
-
initial: () => ({ items: [] }),
|
|
141
|
-
on: {},
|
|
142
|
-
});
|
|
143
|
-
const listSkills = forge.defineQuery(Skills, {
|
|
144
|
-
name: "skills.list",
|
|
145
|
-
schema: z.object({}),
|
|
146
|
-
execute: (state) => state.items,
|
|
147
|
-
});
|
|
148
|
-
void SkillState;
|
|
149
|
-
const queryModule = forge.defineModule("queries", {
|
|
150
|
-
projections: [Skills],
|
|
151
|
-
queries: [listSkills],
|
|
152
|
-
});
|
|
153
|
-
const queryApp = forge.defineApp("query-app", { modules: [queryModule] });
|
|
154
|
-
const app = queryApp.create();
|
|
155
|
-
await app.start();
|
|
156
|
-
|
|
157
|
-
const { tracer, spans } = makeFakeTracer();
|
|
158
|
-
attachOtelExporter(app.runtime, { tracer });
|
|
159
|
-
await app.runtime.query("skills.list", {}, "");
|
|
160
|
-
|
|
161
|
-
const querySpan = spans.find((s) => s.name.includes("query skills.list"));
|
|
162
|
-
expect(querySpan).toBeDefined();
|
|
163
|
-
expect(querySpan!.attributes["nwire.query.name"]).toBe("skills.list");
|
|
164
|
-
expect(typeof querySpan!.attributes["nwire.query.duration_ms"]).toBe("number");
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("traces external calls as child spans", async () => {
|
|
168
|
-
const Notify = forge.defineExternalCall({
|
|
169
|
-
name: "notify",
|
|
170
|
-
target: { provider: "x", endpoint: "/ping" },
|
|
171
|
-
request: z.object({ to: z.string() }),
|
|
172
|
-
response: z.object({ ok: z.boolean() }),
|
|
173
|
-
});
|
|
174
|
-
const send = forge.defineAction({
|
|
175
|
-
name: "ops.send",
|
|
176
|
-
schema: z.object({ to: z.string() }),
|
|
177
|
-
handler: async (input, ctx) => {
|
|
178
|
-
await ctx.externalCall(Notify, { to: input.to });
|
|
179
|
-
return undefined;
|
|
180
|
-
},
|
|
181
|
-
});
|
|
182
|
-
const opsModule = forge.defineModule("ops", {
|
|
183
|
-
actions: [send],
|
|
184
|
-
externalCalls: [Notify],
|
|
185
|
-
});
|
|
186
|
-
const opsApp = forge.defineApp("ops-app", { modules: [opsModule] });
|
|
187
|
-
const app = opsApp.create();
|
|
188
|
-
await app.start();
|
|
189
|
-
app.runtime.registerExternalCallExecutor(Notify, async () => ({ ok: true }));
|
|
190
|
-
|
|
191
|
-
const { tracer, spans } = makeFakeTracer();
|
|
192
|
-
attachOtelExporter(app.runtime, { tracer });
|
|
193
|
-
|
|
194
|
-
await app.runtime.dispatch(send, { to: "avi" });
|
|
195
|
-
|
|
196
|
-
const ext = spans.find((s) => s.name.includes("external notify"));
|
|
197
|
-
expect(ext).toBeDefined();
|
|
198
|
-
expect(ext!.ended).toBe(true);
|
|
199
|
-
expect(ext!.status?.code).toBe(1); // OK
|
|
200
|
-
expect(ext!.attributes["nwire.external.call"]).toBe("notify");
|
|
201
|
-
expect(ext!.attributes["nwire.external.target"]).toBe("x//ping");
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it("closes external-call span with ERROR on failure", async () => {
|
|
205
|
-
const Notify = forge.defineExternalCall({
|
|
206
|
-
name: "flaky",
|
|
207
|
-
target: { provider: "x", endpoint: "/boom" },
|
|
208
|
-
request: z.object({}),
|
|
209
|
-
});
|
|
210
|
-
const send = forge.defineAction({
|
|
211
|
-
name: "ops.boom",
|
|
212
|
-
schema: z.object({}),
|
|
213
|
-
handler: async (_input, ctx) => {
|
|
214
|
-
try {
|
|
215
|
-
await ctx.externalCall(Notify, {});
|
|
216
|
-
} catch {
|
|
217
|
-
/* expected */
|
|
218
|
-
}
|
|
219
|
-
return undefined;
|
|
220
|
-
},
|
|
221
|
-
});
|
|
222
|
-
const opsModule = forge.defineModule("ops2", {
|
|
223
|
-
actions: [send],
|
|
224
|
-
externalCalls: [Notify],
|
|
225
|
-
});
|
|
226
|
-
const opsApp = forge.defineApp("ops2-app", { modules: [opsModule] });
|
|
227
|
-
const app = opsApp.create();
|
|
228
|
-
await app.start();
|
|
229
|
-
app.runtime.registerExternalCallExecutor(Notify, async () => {
|
|
230
|
-
throw new Error("nope");
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
const { tracer, spans } = makeFakeTracer();
|
|
234
|
-
attachOtelExporter(app.runtime, { tracer });
|
|
235
|
-
await app.runtime.dispatch(send, {});
|
|
236
|
-
|
|
237
|
-
const ext = spans.find((s) => s.name.includes("external flaky"));
|
|
238
|
-
expect(ext).toBeDefined();
|
|
239
|
-
expect(ext!.status?.code).toBe(2); // ERROR
|
|
240
|
-
expect(ext!.attributes["nwire.error.message"]).toBe("nope");
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
it("detach() unsubscribes and closes still-open spans", async () => {
|
|
244
|
-
const app = demoApp.create();
|
|
245
|
-
await app.start();
|
|
246
|
-
const { tracer, spans } = makeFakeTracer();
|
|
247
|
-
const detach = attachOtelExporter(app.runtime, { tracer });
|
|
248
|
-
|
|
249
|
-
// Detach mid-flight; the exporter should still flush.
|
|
250
|
-
detach();
|
|
251
|
-
// After detach, new events should NOT produce more spans.
|
|
252
|
-
const before = spans.length;
|
|
253
|
-
await app.runtime.dispatch(submit, { who: "Dina" });
|
|
254
|
-
expect(spans.length).toBe(before);
|
|
255
|
-
});
|
|
256
|
-
});
|
package/src/exporter.ts
DELETED
|
@@ -1,416 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `attachOtelExporter(runtime, options)` — translates the canonical
|
|
3
|
-
* Nwire telemetry stream into OpenTelemetry spans + events.
|
|
4
|
-
*
|
|
5
|
-
* import { trace } from "@opentelemetry/api";
|
|
6
|
-
* import { attachOtelExporter } from "@nwire/telemetry-otel";
|
|
7
|
-
*
|
|
8
|
-
* const tracer = trace.getTracer("amit");
|
|
9
|
-
* const detach = attachOtelExporter(app.runtime, { tracer });
|
|
10
|
-
*
|
|
11
|
-
* Every Telemetry kind gets a sensible OTel mapping:
|
|
12
|
-
*
|
|
13
|
-
* action.dispatched → open span "action {name}" (parent for the dispatch)
|
|
14
|
-
* action.completed → close that span with ok status
|
|
15
|
-
* action.failed → addEvent "action.failed" (attempt + error)
|
|
16
|
-
* dlq.recorded → close span with error status + dlq event
|
|
17
|
-
* event.published → addEvent "event.published" on the open action span
|
|
18
|
-
* OR ad-hoc span if no open parent
|
|
19
|
-
* actor.transitioned → addEvent "actor.transitioned"
|
|
20
|
-
* projection.folded → addEvent "projection.folded"
|
|
21
|
-
* reaction.fired → child span "reaction {sourceEvent}"
|
|
22
|
-
* reaction.failed → addEvent on the parent + recordException
|
|
23
|
-
* query.executed → ad-hoc span "query {name}"
|
|
24
|
-
* timer.scheduled / .fired → addEvent
|
|
25
|
-
* external.call.started → open child span "external {call}"
|
|
26
|
-
* external.call.completed → close it with ok
|
|
27
|
-
* external.call.failed → close it with error + addEvent per attempt
|
|
28
|
-
* inbound.webhook.received → ad-hoc span "webhook {name}"
|
|
29
|
-
* outbox.flushed → addEvent (could become a metric later)
|
|
30
|
-
* inbox.dedup.hit → addEvent
|
|
31
|
-
* queue.job.{enqueued,started,completed} → ad-hoc span "queue {queue}/{jobId}"
|
|
32
|
-
* cron.fired → ad-hoc span "cron {name}"
|
|
33
|
-
*
|
|
34
|
-
* Spans are correlated by `envelope.messageId` (when present) — open spans
|
|
35
|
-
* are tracked in a Map so completion records can look them up. If a
|
|
36
|
-
* close-record arrives with no matching open span (e.g. listener attached
|
|
37
|
-
* mid-flight), we emit an orphan span with `{ status: ERROR, orphan: true }`.
|
|
38
|
-
*
|
|
39
|
-
* Returns a detach function — calling it unsubscribes and closes any
|
|
40
|
-
* still-open spans with an `unsubscribed` event.
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
import type { Runtime, Telemetry, SerializedError } from "@nwire/forge";
|
|
44
|
-
import type { OtelTracer, OtelSpan, SpanStatusCode } from "./otel-types";
|
|
45
|
-
|
|
46
|
-
export interface AttachOtelExporterOptions {
|
|
47
|
-
/** OTel Tracer instance from `trace.getTracer(name, version?)`. */
|
|
48
|
-
readonly tracer: OtelTracer;
|
|
49
|
-
/**
|
|
50
|
-
* Prefix to apply to span names. Default `'nwire.'` so traces in OTLP
|
|
51
|
-
* backends stand out. Pass empty string to disable.
|
|
52
|
-
*/
|
|
53
|
-
readonly spanNamePrefix?: string;
|
|
54
|
-
/**
|
|
55
|
-
* Custom event-published handling. By default, we attach published
|
|
56
|
-
* events as span events on the open action span. If you prefer each
|
|
57
|
-
* event as its own span, set `eventsAsSpans: true`.
|
|
58
|
-
*/
|
|
59
|
-
readonly eventsAsSpans?: boolean;
|
|
60
|
-
/**
|
|
61
|
-
* Filter which telemetry kinds to forward. Default: all. Pass an array
|
|
62
|
-
* to opt in to specific kinds (e.g. for high-volume production traffic
|
|
63
|
-
* you might keep only action.* + external.call.*).
|
|
64
|
-
*/
|
|
65
|
-
readonly kinds?: readonly Telemetry["kind"][];
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const OK: SpanStatusCode = 1;
|
|
69
|
-
const ERROR: SpanStatusCode = 2;
|
|
70
|
-
|
|
71
|
-
interface OpenSpan {
|
|
72
|
-
readonly span: OtelSpan;
|
|
73
|
-
readonly startedAt: number;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function attachOtelExporter(
|
|
77
|
-
runtime: Runtime,
|
|
78
|
-
options: AttachOtelExporterOptions,
|
|
79
|
-
): () => void {
|
|
80
|
-
const tracer = options.tracer;
|
|
81
|
-
const prefix = options.spanNamePrefix ?? "nwire.";
|
|
82
|
-
const eventsAsSpans = options.eventsAsSpans ?? false;
|
|
83
|
-
const allowed: ReadonlySet<string> | null = options.kinds ? new Set(options.kinds) : null;
|
|
84
|
-
|
|
85
|
-
// Open spans, keyed by envelope.messageId (action) or call+messageId
|
|
86
|
-
// pair (external call). When the matching completion arrives we close.
|
|
87
|
-
const actionSpans = new Map<string, OpenSpan>();
|
|
88
|
-
const externalCallSpans = new Map<string, OpenSpan>();
|
|
89
|
-
|
|
90
|
-
const unsubscribe = runtime.onTelemetry((rec) => {
|
|
91
|
-
if (allowed && !allowed.has(rec.kind)) return;
|
|
92
|
-
try {
|
|
93
|
-
route(rec);
|
|
94
|
-
} catch (err) {
|
|
95
|
-
// Don't let exporter errors break the runtime — the framework's
|
|
96
|
-
// emit() already guards listener throws, but we add explicit
|
|
97
|
-
// logging here too.
|
|
98
|
-
// eslint-disable-next-line no-console
|
|
99
|
-
console.error("[telemetry-otel] export failed:", err);
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
function route(rec: Telemetry): void {
|
|
104
|
-
switch (rec.kind) {
|
|
105
|
-
case "action.dispatched":
|
|
106
|
-
openActionSpan(rec);
|
|
107
|
-
return;
|
|
108
|
-
case "action.completed":
|
|
109
|
-
closeActionSpan(rec, OK, undefined, {
|
|
110
|
-
"nwire.action.duration_ms": rec.durationMs,
|
|
111
|
-
"nwire.action.emitted_events": rec.emittedEvents.join(","),
|
|
112
|
-
});
|
|
113
|
-
return;
|
|
114
|
-
case "action.failed":
|
|
115
|
-
addEventToActionSpan(rec.envelope.messageId, "action.failed", {
|
|
116
|
-
"nwire.action.attempt": rec.attempt,
|
|
117
|
-
"nwire.action.max_attempts": rec.maxAttempts,
|
|
118
|
-
"nwire.action.will_retry": rec.willRetry,
|
|
119
|
-
...errorAttrs(rec.error),
|
|
120
|
-
});
|
|
121
|
-
return;
|
|
122
|
-
case "dlq.recorded":
|
|
123
|
-
closeActionSpan(rec, ERROR, rec.error.message, {
|
|
124
|
-
"nwire.dlq.attempts": rec.attempts,
|
|
125
|
-
...errorAttrs(rec.error),
|
|
126
|
-
});
|
|
127
|
-
return;
|
|
128
|
-
case "event.published":
|
|
129
|
-
if (eventsAsSpans) {
|
|
130
|
-
adhocSpan(`event ${rec.event.eventName}`, rec.ts, {
|
|
131
|
-
"nwire.event.name": rec.event.eventName,
|
|
132
|
-
"nwire.event.source": rec.source,
|
|
133
|
-
...envelopeAttrs(rec.envelope),
|
|
134
|
-
});
|
|
135
|
-
} else {
|
|
136
|
-
// Event records carry a DERIVED envelope: messageId is the
|
|
137
|
-
// event's own, causationId is the action that emitted it. So
|
|
138
|
-
// we look up the action span by causationId.
|
|
139
|
-
addEventToActionSpan(rec.envelope.causationId, "event.published", {
|
|
140
|
-
"nwire.event.name": rec.event.eventName,
|
|
141
|
-
"nwire.event.source": rec.source,
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
return;
|
|
145
|
-
case "actor.transitioned":
|
|
146
|
-
addEventToActionSpan(rec.envelope.causationId, "actor.transitioned", {
|
|
147
|
-
"nwire.actor": rec.actor,
|
|
148
|
-
"nwire.actor.key": rec.key,
|
|
149
|
-
"nwire.actor.from": rec.from,
|
|
150
|
-
"nwire.actor.to": rec.to,
|
|
151
|
-
"nwire.actor.event": rec.triggeringEvent,
|
|
152
|
-
});
|
|
153
|
-
return;
|
|
154
|
-
case "projection.folded":
|
|
155
|
-
addEventToActionSpan(rec.envelope.causationId, "projection.folded", {
|
|
156
|
-
"nwire.projection": rec.projection,
|
|
157
|
-
"nwire.event.name": rec.event,
|
|
158
|
-
"nwire.projection.duration_ms": rec.durationMs,
|
|
159
|
-
});
|
|
160
|
-
return;
|
|
161
|
-
case "reaction.fired":
|
|
162
|
-
adhocSpan(`reaction ${rec.sourceEvent}`, rec.ts, {
|
|
163
|
-
"nwire.reaction.source_event": rec.sourceEvent,
|
|
164
|
-
"nwire.reaction.duration_ms": rec.durationMs,
|
|
165
|
-
...envelopeAttrs(rec.envelope),
|
|
166
|
-
});
|
|
167
|
-
return;
|
|
168
|
-
case "reaction.failed":
|
|
169
|
-
addEventToActionSpan(rec.envelope.causationId, "reaction.failed", {
|
|
170
|
-
"nwire.reaction.source_event": rec.sourceEvent,
|
|
171
|
-
...errorAttrs(rec.error),
|
|
172
|
-
});
|
|
173
|
-
return;
|
|
174
|
-
case "query.executed":
|
|
175
|
-
adhocSpan(`query ${rec.query}`, rec.ts, {
|
|
176
|
-
"nwire.query.name": rec.query,
|
|
177
|
-
"nwire.query.duration_ms": rec.durationMs,
|
|
178
|
-
"nwire.tenant": rec.tenant,
|
|
179
|
-
});
|
|
180
|
-
return;
|
|
181
|
-
case "timer.scheduled":
|
|
182
|
-
case "timer.fired":
|
|
183
|
-
adhocSpan(rec.kind, rec.ts, {
|
|
184
|
-
"nwire.actor": rec.actor,
|
|
185
|
-
"nwire.actor.key": rec.key,
|
|
186
|
-
"nwire.timer.name": rec.timer,
|
|
187
|
-
"nwire.timer.action": rec.action,
|
|
188
|
-
"nwire.tenant": rec.tenant,
|
|
189
|
-
...(rec.kind === "timer.fired"
|
|
190
|
-
? { "nwire.timer.late_by_ms": rec.lateByMs }
|
|
191
|
-
: { "nwire.timer.fire_at": rec.fireAt }),
|
|
192
|
-
});
|
|
193
|
-
return;
|
|
194
|
-
case "external.call.started":
|
|
195
|
-
openExternalCallSpan(rec);
|
|
196
|
-
return;
|
|
197
|
-
case "external.call.completed":
|
|
198
|
-
closeExternalCallSpan(rec, OK, undefined, {
|
|
199
|
-
"nwire.external.duration_ms": rec.durationMs,
|
|
200
|
-
...(rec.status !== undefined ? { "nwire.external.status": rec.status } : {}),
|
|
201
|
-
});
|
|
202
|
-
return;
|
|
203
|
-
case "external.call.failed":
|
|
204
|
-
closeExternalCallSpan(rec, ERROR, rec.error.message, {
|
|
205
|
-
"nwire.external.attempt": rec.attempt,
|
|
206
|
-
"nwire.external.will_retry": rec.willRetry,
|
|
207
|
-
...errorAttrs(rec.error),
|
|
208
|
-
});
|
|
209
|
-
return;
|
|
210
|
-
case "inbound.webhook.received":
|
|
211
|
-
adhocSpan(`webhook ${rec.webhook}`, rec.ts, {
|
|
212
|
-
"nwire.webhook.name": rec.webhook,
|
|
213
|
-
"nwire.webhook.source": rec.source,
|
|
214
|
-
"nwire.webhook.signature_valid": rec.signatureValid,
|
|
215
|
-
"nwire.webhook.dedup_hit": rec.dedupHit,
|
|
216
|
-
...(rec.routedTo ? { "nwire.webhook.routed_to": rec.routedTo } : {}),
|
|
217
|
-
});
|
|
218
|
-
return;
|
|
219
|
-
case "outbox.flushed":
|
|
220
|
-
adhocSpan(`outbox ${rec.outbox} flush`, rec.ts, {
|
|
221
|
-
"nwire.outbox.name": rec.outbox,
|
|
222
|
-
"nwire.outbox.events": rec.events,
|
|
223
|
-
"nwire.outbox.failed": rec.failed,
|
|
224
|
-
"nwire.outbox.duration_ms": rec.durationMs,
|
|
225
|
-
});
|
|
226
|
-
return;
|
|
227
|
-
case "inbox.dedup.hit":
|
|
228
|
-
adhocSpan(`inbox ${rec.inbox} dedup`, rec.ts, {
|
|
229
|
-
"nwire.inbox.name": rec.inbox,
|
|
230
|
-
"nwire.inbox.message_id": rec.messageId,
|
|
231
|
-
"nwire.inbox.first_seen_at": rec.firstSeenAt,
|
|
232
|
-
});
|
|
233
|
-
return;
|
|
234
|
-
case "queue.job.enqueued":
|
|
235
|
-
case "queue.job.started":
|
|
236
|
-
case "queue.job.completed":
|
|
237
|
-
adhocSpan(`queue ${rec.queue}/${rec.kind.replace("queue.job.", "")}`, rec.ts, {
|
|
238
|
-
"nwire.queue.name": rec.queue,
|
|
239
|
-
"nwire.queue.job_id": rec.jobId,
|
|
240
|
-
...(rec.kind === "queue.job.enqueued" && rec.delay !== undefined
|
|
241
|
-
? { "nwire.queue.delay": rec.delay }
|
|
242
|
-
: {}),
|
|
243
|
-
...(rec.kind === "queue.job.started" ? { "nwire.queue.waited_ms": rec.waitedMs } : {}),
|
|
244
|
-
...(rec.kind === "queue.job.completed"
|
|
245
|
-
? { "nwire.queue.duration_ms": rec.durationMs, "nwire.queue.ok": rec.ok }
|
|
246
|
-
: {}),
|
|
247
|
-
});
|
|
248
|
-
return;
|
|
249
|
-
case "cron.fired":
|
|
250
|
-
adhocSpan(`cron ${rec.cronName}`, rec.ts, {
|
|
251
|
-
"nwire.cron.name": rec.cronName,
|
|
252
|
-
"nwire.cron.schedule": rec.schedule,
|
|
253
|
-
"nwire.cron.expected": rec.expected,
|
|
254
|
-
"nwire.cron.actual": rec.actual,
|
|
255
|
-
"nwire.cron.late_by_ms": rec.lateByMs,
|
|
256
|
-
});
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
function openActionSpan(rec: Extract<Telemetry, { kind: "action.dispatched" }>): void {
|
|
262
|
-
const span = tracer.startSpan(`${prefix}action ${rec.action}`, {
|
|
263
|
-
startTime: new Date(rec.ts),
|
|
264
|
-
attributes: {
|
|
265
|
-
"nwire.action": rec.action,
|
|
266
|
-
...envelopeAttrs(rec.envelope),
|
|
267
|
-
"nwire.app": rec.appName,
|
|
268
|
-
},
|
|
269
|
-
});
|
|
270
|
-
actionSpans.set(rec.envelope.messageId, { span, startedAt: Date.now() });
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function closeActionSpan(
|
|
274
|
-
rec: { ts: string; envelope: { messageId: string }; appName: string },
|
|
275
|
-
status: SpanStatusCode,
|
|
276
|
-
msg: string | undefined,
|
|
277
|
-
attrs: Record<string, unknown>,
|
|
278
|
-
): void {
|
|
279
|
-
const open = actionSpans.get(rec.envelope.messageId);
|
|
280
|
-
if (!open) {
|
|
281
|
-
// Orphan — close-record without an open span. Emit an ad-hoc record
|
|
282
|
-
// so we don't silently drop data.
|
|
283
|
-
adhocSpan(`${prefix}action.orphan`, rec.ts, {
|
|
284
|
-
"nwire.orphan": true,
|
|
285
|
-
"nwire.app": rec.appName,
|
|
286
|
-
...attrs,
|
|
287
|
-
});
|
|
288
|
-
return;
|
|
289
|
-
}
|
|
290
|
-
actionSpans.delete(rec.envelope.messageId);
|
|
291
|
-
setAttrs(open.span, attrs);
|
|
292
|
-
open.span.setStatus({ code: status, message: msg });
|
|
293
|
-
open.span.end(new Date(rec.ts));
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function addEventToActionSpan(
|
|
297
|
-
messageId: string,
|
|
298
|
-
name: string,
|
|
299
|
-
attrs: Record<string, unknown>,
|
|
300
|
-
): void {
|
|
301
|
-
const open = actionSpans.get(messageId);
|
|
302
|
-
if (open) {
|
|
303
|
-
open.span.addEvent(name, attrs);
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
// No parent — emit standalone ad-hoc.
|
|
307
|
-
adhocSpan(name, new Date().toISOString(), attrs);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
function openExternalCallSpan(rec: Extract<Telemetry, { kind: "external.call.started" }>): void {
|
|
311
|
-
const span = tracer.startSpan(`${prefix}external ${rec.call}`, {
|
|
312
|
-
startTime: new Date(rec.ts),
|
|
313
|
-
attributes: {
|
|
314
|
-
"nwire.external.call": rec.call,
|
|
315
|
-
"nwire.external.target": rec.target,
|
|
316
|
-
...(rec.idempotencyKey ? { "nwire.external.idempotency_key": rec.idempotencyKey } : {}),
|
|
317
|
-
...(rec.envelope ? envelopeAttrs(rec.envelope) : {}),
|
|
318
|
-
"nwire.app": rec.appName,
|
|
319
|
-
},
|
|
320
|
-
});
|
|
321
|
-
// Key by call + correlation; multiple concurrent calls with same name
|
|
322
|
-
// would collide, so include messageId if available.
|
|
323
|
-
const key = externalCallKey(rec.call, rec.envelope?.messageId);
|
|
324
|
-
externalCallSpans.set(key, { span, startedAt: Date.now() });
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
function closeExternalCallSpan(
|
|
328
|
-
rec:
|
|
329
|
-
| Extract<Telemetry, { kind: "external.call.completed" }>
|
|
330
|
-
| Extract<Telemetry, { kind: "external.call.failed" }>,
|
|
331
|
-
status: SpanStatusCode,
|
|
332
|
-
msg: string | undefined,
|
|
333
|
-
attrs: Record<string, unknown>,
|
|
334
|
-
): void {
|
|
335
|
-
const key = externalCallKey(rec.call, rec.envelope?.messageId);
|
|
336
|
-
const open = externalCallSpans.get(key);
|
|
337
|
-
if (!open) {
|
|
338
|
-
adhocSpan(`${prefix}external.orphan ${rec.call}`, rec.ts, {
|
|
339
|
-
"nwire.orphan": true,
|
|
340
|
-
"nwire.external.call": rec.call,
|
|
341
|
-
"nwire.external.target": rec.target,
|
|
342
|
-
...attrs,
|
|
343
|
-
});
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
externalCallSpans.delete(key);
|
|
347
|
-
setAttrs(open.span, attrs);
|
|
348
|
-
open.span.setStatus({ code: status, message: msg });
|
|
349
|
-
open.span.end(new Date(rec.ts));
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function externalCallKey(call: string, messageId: string | undefined): string {
|
|
353
|
-
return `${call}::${messageId ?? "no-envelope"}`;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
function adhocSpan(name: string, ts: string, attrs: Record<string, unknown>): void {
|
|
357
|
-
const startTime = new Date(ts);
|
|
358
|
-
const span = tracer.startSpan(name.startsWith(prefix) ? name : `${prefix}${name}`, {
|
|
359
|
-
startTime,
|
|
360
|
-
attributes: attrs,
|
|
361
|
-
});
|
|
362
|
-
span.end(startTime);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return () => {
|
|
366
|
-
unsubscribe();
|
|
367
|
-
// Close any still-open spans so the exporter flushes them.
|
|
368
|
-
for (const { span } of actionSpans.values()) {
|
|
369
|
-
span.addEvent("nwire.unsubscribed");
|
|
370
|
-
span.end();
|
|
371
|
-
}
|
|
372
|
-
for (const { span } of externalCallSpans.values()) {
|
|
373
|
-
span.addEvent("nwire.unsubscribed");
|
|
374
|
-
span.end();
|
|
375
|
-
}
|
|
376
|
-
actionSpans.clear();
|
|
377
|
-
externalCallSpans.clear();
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function envelopeAttrs(envelope: {
|
|
382
|
-
messageId: string;
|
|
383
|
-
correlationId: string;
|
|
384
|
-
causationId: string;
|
|
385
|
-
tenant?: string;
|
|
386
|
-
userId?: string;
|
|
387
|
-
timestamp: string;
|
|
388
|
-
}): Record<string, unknown> {
|
|
389
|
-
return {
|
|
390
|
-
"nwire.message_id": envelope.messageId,
|
|
391
|
-
"nwire.correlation_id": envelope.correlationId,
|
|
392
|
-
"nwire.causation_id": envelope.causationId,
|
|
393
|
-
...(envelope.tenant ? { "nwire.tenant": envelope.tenant } : {}),
|
|
394
|
-
...(envelope.userId ? { "nwire.user_id": envelope.userId } : {}),
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function errorAttrs(err: SerializedError): Record<string, unknown> {
|
|
399
|
-
return {
|
|
400
|
-
"nwire.error.name": err.name,
|
|
401
|
-
"nwire.error.message": err.message,
|
|
402
|
-
...(err.stack ? { "nwire.error.stack": err.stack } : {}),
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
function setAttrs(span: OtelSpan, attrs: Record<string, unknown>): void {
|
|
407
|
-
if (span.setAttributes) {
|
|
408
|
-
span.setAttributes(attrs);
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
for (const [k, v] of Object.entries(attrs)) {
|
|
412
|
-
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
413
|
-
span.setAttribute(k, v);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
package/src/otel-types.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal duck-typed surface of `@opentelemetry/api`'s Tracer + Span. We
|
|
3
|
-
* never import the package — consumers bring their own version and pass a
|
|
4
|
-
* `Tracer` in. This keeps `@nwire/telemetry-otel` zero-dep at runtime
|
|
5
|
-
* (only `@nwire/forge` for the Telemetry type).
|
|
6
|
-
*
|
|
7
|
-
* The fields are the subset we actually use. If a consumer passes a real
|
|
8
|
-
* `@opentelemetry/api` Tracer, structural typing makes it compatible.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export type SpanKind = 0 | 1 | 2 | 3 | 4 | 5;
|
|
12
|
-
export type SpanStatusCode = 0 | 1 | 2;
|
|
13
|
-
|
|
14
|
-
export interface SpanContext {
|
|
15
|
-
traceId: string;
|
|
16
|
-
spanId: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface OtelSpan {
|
|
20
|
-
spanContext?(): SpanContext;
|
|
21
|
-
setAttribute(key: string, value: string | number | boolean): OtelSpan | void;
|
|
22
|
-
setAttributes?(attrs: Record<string, unknown>): OtelSpan | void;
|
|
23
|
-
addEvent(
|
|
24
|
-
name: string,
|
|
25
|
-
attrs?: Record<string, unknown>,
|
|
26
|
-
timestamp?: number | Date,
|
|
27
|
-
): OtelSpan | void;
|
|
28
|
-
recordException(err: unknown): OtelSpan | void;
|
|
29
|
-
setStatus(status: { code: SpanStatusCode; message?: string }): OtelSpan | void;
|
|
30
|
-
end(endTime?: number | Date): void;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface OtelTracer {
|
|
34
|
-
startSpan(
|
|
35
|
-
name: string,
|
|
36
|
-
options?: {
|
|
37
|
-
attributes?: Record<string, unknown>;
|
|
38
|
-
kind?: SpanKind;
|
|
39
|
-
startTime?: number | Date;
|
|
40
|
-
},
|
|
41
|
-
): OtelSpan;
|
|
42
|
-
}
|
package/src/telemetry-otel.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `@nwire/telemetry-otel` — OpenTelemetry bridge for the canonical Nwire
|
|
3
|
-
* telemetry stream. Subscribes to `runtime.onTelemetry` and translates
|
|
4
|
-
* every record into OTel spans + events.
|
|
5
|
-
*
|
|
6
|
-
* import { trace } from "@opentelemetry/api";
|
|
7
|
-
* import { attachOtelExporter } from "@nwire/telemetry-otel";
|
|
8
|
-
*
|
|
9
|
-
* const detach = attachOtelExporter(app.runtime, {
|
|
10
|
-
* tracer: trace.getTracer("amit"),
|
|
11
|
-
* });
|
|
12
|
-
*
|
|
13
|
-
* Pairs cleanly with Vector → GreptimeDB (and any other OTLP backend —
|
|
14
|
-
* Datadog, Honeycomb, Tempo, Jaeger).
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
export { attachOtelExporter, type AttachOtelExporterOptions } from "./exporter";
|
|
18
|
-
export type { OtelTracer, OtelSpan, SpanContext, SpanKind, SpanStatusCode } from "./otel-types";
|