@nifrajs/otel 1.13.0 → 2.1.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 +1 -2
- package/dist/effects.d.ts +24 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +129 -0
- package/dist/effects.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics.d.ts +75 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +0 -0
- package/dist/metrics.js.map +1 -0
- package/dist/otlp.d.ts +42 -0
- package/dist/otlp.d.ts.map +1 -0
- package/dist/otlp.js +129 -0
- package/dist/otlp.js.map +1 -0
- package/dist/span.d.ts +1 -3
- package/dist/span.d.ts.map +1 -1
- package/dist/span.js.map +1 -1
- package/dist/tracing.d.ts +5 -5
- package/dist/tracing.d.ts.map +1 -1
- package/dist/tracing.js +3 -2
- package/dist/tracing.js.map +1 -1
- package/package.json +12 -2
- package/src/effects.ts +146 -0
- package/src/index.ts +7 -2
- package/src/metrics.ts +0 -0
- package/src/otlp.ts +164 -0
- package/src/span.ts +1 -4
- package/src/tracing.ts +5 -5
package/README.md
CHANGED
|
@@ -61,8 +61,7 @@ inventing trace identity.
|
|
|
61
61
|
|
|
62
62
|
## Adapters
|
|
63
63
|
|
|
64
|
-
Implement `ObservationAdapter` to send spans wherever you collect them
|
|
65
|
-
backwards-compatible type alias:
|
|
64
|
+
Implement `ObservationAdapter` to send spans wherever you collect them:
|
|
66
65
|
|
|
67
66
|
```ts
|
|
68
67
|
interface ObservationAdapter {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Token-only OpenTelemetry spans for capability admission, execution, compensation, and reconciliation. */
|
|
2
|
+
import type { EffectLifecycleObserver } from "@nifrajs/core/effect-lifecycle";
|
|
3
|
+
import type { IdentityPlugin } from "@nifrajs/core/server";
|
|
4
|
+
import type { ObservationAdapter } from "./span.js";
|
|
5
|
+
export interface EffectTracingOptions {
|
|
6
|
+
readonly exporter?: ObservationAdapter;
|
|
7
|
+
readonly adapters?: readonly ObservationAdapter[];
|
|
8
|
+
/** Maximum unmatched started events retained at once. Default 10,000. */
|
|
9
|
+
readonly maxActive?: number;
|
|
10
|
+
/** Opportunistically expire unmatched events older than this many milliseconds. Default 5 min. */
|
|
11
|
+
readonly maxActiveAgeMs?: number;
|
|
12
|
+
/** Injectable wall clock for deterministic retention tests. */
|
|
13
|
+
readonly now?: () => number;
|
|
14
|
+
}
|
|
15
|
+
export interface EffectTracingPlugin extends IdentityPlugin {
|
|
16
|
+
/** Pass the same observer to `createSagaEngine({ observer })` and reconciliation helpers. */
|
|
17
|
+
readonly observer: EffectLifecycleObserver;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Installs child effect spans on subsequent routes. The observer consumes only the constrained
|
|
21
|
+
* `EffectLifecycleEvent` contract; request/business payloads and error text cannot enter an export.
|
|
22
|
+
*/
|
|
23
|
+
export declare function effectTracing(options?: EffectTracingOptions): EffectTracingPlugin;
|
|
24
|
+
//# sourceMappingURL=effects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.d.ts","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":"AAAA,4GAA4G;AAE5G,OAAO,KAAK,EAAwB,uBAAuB,EAAE,MAAM,gCAAgC,CAAA;AACnG,OAAO,KAAK,EAAa,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAErE,OAAO,KAAK,EAAkB,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAEnE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACjD,yEAAyE;IACzE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,kGAAkG;IAClG,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC,+DAA+D;IAC/D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,mBAAoB,SAAQ,cAAc;IACzD,6FAA6F;IAC7F,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAA;CAC3C;AAoBD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,mBAAmB,CAoGrF"}
|
package/dist/effects.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/** Token-only OpenTelemetry spans for capability admission, execution, compensation, and reconciliation. */
|
|
2
|
+
import { createObservationLifecycle } from "./lifecycle.js";
|
|
3
|
+
function attributesOf(event) {
|
|
4
|
+
const attributes = {
|
|
5
|
+
"nifra.effect.id": event.effectId,
|
|
6
|
+
"nifra.effect.capability": event.capability,
|
|
7
|
+
"nifra.effect.stage": event.stage,
|
|
8
|
+
"nifra.effect.phase": event.phase,
|
|
9
|
+
};
|
|
10
|
+
if (event.target !== undefined)
|
|
11
|
+
attributes["nifra.effect.target"] = event.target;
|
|
12
|
+
if (event.digest !== undefined)
|
|
13
|
+
attributes["nifra.effect.digest"] = event.digest;
|
|
14
|
+
if (event.attempt !== undefined)
|
|
15
|
+
attributes["nifra.effect.attempt"] = event.attempt;
|
|
16
|
+
if (event.errorCode !== undefined)
|
|
17
|
+
attributes["nifra.effect.error_code"] = event.errorCode;
|
|
18
|
+
if (event.durationMs !== undefined)
|
|
19
|
+
attributes["nifra.effect.duration_ms"] = event.durationMs;
|
|
20
|
+
for (const [axis, value] of Object.entries(event.cost ?? {})) {
|
|
21
|
+
attributes[`nifra.effect.cost.${axis}`] = value;
|
|
22
|
+
}
|
|
23
|
+
return attributes;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Installs child effect spans on subsequent routes. The observer consumes only the constrained
|
|
27
|
+
* `EffectLifecycleEvent` contract; request/business payloads and error text cannot enter an export.
|
|
28
|
+
*/
|
|
29
|
+
export function effectTracing(options = {}) {
|
|
30
|
+
const adapters = [
|
|
31
|
+
...(options.exporter === undefined ? [] : [options.exporter]),
|
|
32
|
+
...(options.adapters ?? []),
|
|
33
|
+
];
|
|
34
|
+
const maxActive = options.maxActive ?? 10_000;
|
|
35
|
+
const maxActiveAgeMs = options.maxActiveAgeMs ?? 5 * 60_000;
|
|
36
|
+
if (!Number.isSafeInteger(maxActive) || maxActive < 1) {
|
|
37
|
+
throw new RangeError("effect tracing maxActive must be a positive safe integer");
|
|
38
|
+
}
|
|
39
|
+
if (!Number.isSafeInteger(maxActiveAgeMs) || maxActiveAgeMs < 1) {
|
|
40
|
+
throw new RangeError("effect tracing maxActiveAgeMs must be a positive safe integer");
|
|
41
|
+
}
|
|
42
|
+
const now = options.now ?? Date.now;
|
|
43
|
+
const lifecycle = createObservationLifecycle({ adapters });
|
|
44
|
+
const active = new Map();
|
|
45
|
+
let nextSweepAt = Number.POSITIVE_INFINITY;
|
|
46
|
+
const keyOf = (event) => `${event.effectId}\n${event.stage}`;
|
|
47
|
+
const evict = (key, errorCode) => {
|
|
48
|
+
const entry = active.get(key);
|
|
49
|
+
if (entry === undefined)
|
|
50
|
+
return;
|
|
51
|
+
active.delete(key);
|
|
52
|
+
entry.observation.setAttributes({
|
|
53
|
+
"nifra.effect.phase": "ambiguous",
|
|
54
|
+
"nifra.effect.error_code": errorCode,
|
|
55
|
+
});
|
|
56
|
+
entry.observation.end({ status: "error" });
|
|
57
|
+
};
|
|
58
|
+
const sweep = () => {
|
|
59
|
+
const at = now();
|
|
60
|
+
if (!Number.isFinite(at) || at < nextSweepAt)
|
|
61
|
+
return at;
|
|
62
|
+
// `active` iterates in insertion order and entries carry a monotonic `at`, so the front is the
|
|
63
|
+
// oldest / earliest-expiring. Evict the expired prefix and stop at the first live entry: its expiry
|
|
64
|
+
// is the earliest remaining, i.e. the next sweep deadline. A sweep is therefore O(evicted), not
|
|
65
|
+
// O(active.size). (A backwards clock only defers an eviction to the size cap - the hard bound; age
|
|
66
|
+
// eviction is opportunistic.)
|
|
67
|
+
nextSweepAt = Number.POSITIVE_INFINITY;
|
|
68
|
+
for (const [key, entry] of active) {
|
|
69
|
+
const expiresAt = entry.at + maxActiveAgeMs;
|
|
70
|
+
if (at >= expiresAt) {
|
|
71
|
+
evict(key, "observation_expired");
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
nextSweepAt = expiresAt;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return at;
|
|
79
|
+
};
|
|
80
|
+
const observer = (event) => {
|
|
81
|
+
const at = sweep();
|
|
82
|
+
const key = keyOf(event);
|
|
83
|
+
if (event.phase === "started") {
|
|
84
|
+
if (active.has(key))
|
|
85
|
+
return;
|
|
86
|
+
while (active.size >= maxActive) {
|
|
87
|
+
const oldest = active.keys().next().value;
|
|
88
|
+
if (oldest === undefined)
|
|
89
|
+
break;
|
|
90
|
+
evict(oldest, "observation_evicted");
|
|
91
|
+
}
|
|
92
|
+
const startedAt = Number.isFinite(at) ? at : event.at;
|
|
93
|
+
active.set(key, Object.freeze({
|
|
94
|
+
observation: lifecycle.start({
|
|
95
|
+
name: `nifra.effect.${event.stage}`,
|
|
96
|
+
parent: event.trace ?? null,
|
|
97
|
+
attributes: attributesOf(event),
|
|
98
|
+
}),
|
|
99
|
+
at: startedAt,
|
|
100
|
+
}));
|
|
101
|
+
nextSweepAt = Math.min(nextSweepAt, startedAt + maxActiveAgeMs);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const entry = active.get(key);
|
|
105
|
+
let observation;
|
|
106
|
+
if (entry === undefined) {
|
|
107
|
+
observation = lifecycle.start({
|
|
108
|
+
name: `nifra.effect.${event.stage}`,
|
|
109
|
+
parent: event.trace ?? null,
|
|
110
|
+
attributes: attributesOf(event),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
active.delete(key);
|
|
115
|
+
observation = entry.observation;
|
|
116
|
+
observation.setAttributes(attributesOf(event));
|
|
117
|
+
}
|
|
118
|
+
observation.end({ status: event.phase === "succeeded" ? "ok" : "error" });
|
|
119
|
+
};
|
|
120
|
+
const apply = (app) => {
|
|
121
|
+
app.observeCapability(observer);
|
|
122
|
+
return app;
|
|
123
|
+
};
|
|
124
|
+
return Object.assign(apply, {
|
|
125
|
+
pluginName: "nifra:effect-tracing",
|
|
126
|
+
observer,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=effects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effects.js","sourceRoot":"","sources":["../src/effects.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
* @nifrajs/otel — distributed tracing for nifra. The `tracing()` plugin continues (or starts) a W3C
|
|
3
3
|
* trace per request, opens an OpenTelemetry-semantic-convention span, and exposes `c.trace` so you
|
|
4
4
|
* can forward the trace to downstream services with `traceHeaders(c.trace)`. Spans go to a pluggable
|
|
5
|
-
* `
|
|
5
|
+
* `ObservationAdapter` — bridge to the OpenTelemetry SDK, or log with `consoleSpanExporter`. No SDK
|
|
6
6
|
* bundled; edge-safe.
|
|
7
7
|
*
|
|
8
8
|
* import { tracing, traceHeaders, consoleSpanExporter } from "@nifrajs/otel"
|
|
9
9
|
* app.use(tracing({ exporter: consoleSpanExporter(), serviceName: "orders-api" }))
|
|
10
10
|
*/
|
|
11
11
|
export { causalitySpanLink } from "./causality.js";
|
|
12
|
+
export { type EffectTracingOptions, type EffectTracingPlugin, effectTracing, } from "./effects.js";
|
|
12
13
|
export { type ActiveObservation, createObservationLifecycle, type EndObservation, type ObservationClock, type ObservationContext, type ObservationLifecycle, type ObservationLifecycleOptions, type ObservationParent, type StartObservation, } from "./lifecycle.js";
|
|
13
|
-
export { type
|
|
14
|
+
export { type OtlpExporter, type OtlpExporterOptions, otlpExporter } from "./otlp.js";
|
|
15
|
+
export { type AttributeValue, combineObservationAdapters, consoleSpanExporter, type NifraSpan, type ObservationAdapter, type ObservationLink, type SpanStatus, } from "./span.js";
|
|
14
16
|
export { formatTraceparent, generateSpanId, generateTraceId, type ParsedTraceparent, parseTraceparent, } from "./traceparent.js";
|
|
15
17
|
export { type TraceContext, type TracingOptions, traceHeaders, tracing } from "./tracing.js";
|
|
16
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EACL,KAAK,iBAAiB,EACtB,0BAA0B,EAC1B,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EACL,KAAK,cAAc,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,aAAa,GACd,MAAM,cAAc,CAAA;AACrB,OAAO,EACL,KAAK,iBAAiB,EACtB,0BAA0B,EAC1B,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,2BAA2B,EAChC,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,mBAAmB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACrF,OAAO,EACL,KAAK,cAAc,EACnB,0BAA0B,EAC1B,mBAAmB,EACnB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,UAAU,GAChB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,KAAK,iBAAiB,EACtB,gBAAgB,GACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
* @nifrajs/otel — distributed tracing for nifra. The `tracing()` plugin continues (or starts) a W3C
|
|
3
3
|
* trace per request, opens an OpenTelemetry-semantic-convention span, and exposes `c.trace` so you
|
|
4
4
|
* can forward the trace to downstream services with `traceHeaders(c.trace)`. Spans go to a pluggable
|
|
5
|
-
* `
|
|
5
|
+
* `ObservationAdapter` — bridge to the OpenTelemetry SDK, or log with `consoleSpanExporter`. No SDK
|
|
6
6
|
* bundled; edge-safe.
|
|
7
7
|
*
|
|
8
8
|
* import { tracing, traceHeaders, consoleSpanExporter } from "@nifrajs/otel"
|
|
9
9
|
* app.use(tracing({ exporter: consoleSpanExporter(), serviceName: "orders-api" }))
|
|
10
10
|
*/
|
|
11
11
|
export { causalitySpanLink } from "./causality.js";
|
|
12
|
+
export { effectTracing, } from "./effects.js";
|
|
12
13
|
export { createObservationLifecycle, } from "./lifecycle.js";
|
|
14
|
+
export { otlpExporter } from "./otlp.js";
|
|
13
15
|
export { combineObservationAdapters, consoleSpanExporter, } from "./span.js";
|
|
14
16
|
export { formatTraceparent, generateSpanId, generateTraceId, parseTraceparent, } from "./traceparent.js";
|
|
15
17
|
export { traceHeaders, tracing } from "./tracing.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAEL,0BAA0B,GAQ3B,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAEL,0BAA0B,EAC1B,mBAAmB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAGL,aAAa,GACd,MAAM,cAAc,CAAA;AACrB,OAAO,EAEL,0BAA0B,GAQ3B,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAA+C,YAAY,EAAE,MAAM,WAAW,CAAA;AACrF,OAAO,EAEL,0BAA0B,EAC1B,mBAAmB,GAKpB,MAAM,WAAW,CAAA;AAClB,OAAO,EACL,iBAAiB,EACjB,cAAc,EACd,eAAe,EAEf,gBAAgB,GACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAA0C,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RED metrics for nifra as an opt-in `.use(metrics())` plugin, exposed in Prometheus text format at
|
|
3
|
+
* `/metrics`. Dependency-free and in-process: an atomic counter/gauge/histogram registry with a
|
|
4
|
+
* Prometheus renderer, plus automatic per-request rate/errors/duration series labeled by the matched
|
|
5
|
+
* route TEMPLATE (not the raw path, so `/users/:id` is one series, not one per id).
|
|
6
|
+
*
|
|
7
|
+
* import { metrics } from "@nifrajs/otel/metrics"
|
|
8
|
+
* const app = server().use(metrics()).get("/users/:id", handler)
|
|
9
|
+
* // GET /metrics → nifra_http_requests_total{method="GET",route="/users/:id",status="200"} 5 …
|
|
10
|
+
*
|
|
11
|
+
* `c` is not touched; apps that want custom app metrics create their own registry and pass it in
|
|
12
|
+
* (`metrics({ registry })`), then register series on it - they render at the same `/metrics`.
|
|
13
|
+
*/
|
|
14
|
+
import { type NifraPlugin } from "@nifrajs/core/server";
|
|
15
|
+
type Labels = Readonly<Record<string, string>>;
|
|
16
|
+
/** A monotonically increasing counter (requests, errors). */
|
|
17
|
+
export declare class Counter {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly help: string;
|
|
20
|
+
private readonly series;
|
|
21
|
+
constructor(name: string, help: string);
|
|
22
|
+
inc(labels?: Labels, by?: number): void;
|
|
23
|
+
render(): string;
|
|
24
|
+
}
|
|
25
|
+
/** A value that can go up and down (in-flight requests, queue depth). */
|
|
26
|
+
export declare class Gauge {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly help: string;
|
|
29
|
+
private readonly series;
|
|
30
|
+
constructor(name: string, help: string);
|
|
31
|
+
private at;
|
|
32
|
+
inc(labels?: Labels, by?: number): void;
|
|
33
|
+
dec(labels?: Labels, by?: number): void;
|
|
34
|
+
set(labels: Labels, value: number): void;
|
|
35
|
+
render(): string;
|
|
36
|
+
}
|
|
37
|
+
/** Latency-style distribution over fixed buckets (seconds). Renders Prometheus cumulative buckets. */
|
|
38
|
+
export declare class Histogram {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly help: string;
|
|
41
|
+
private readonly series;
|
|
42
|
+
private readonly buckets;
|
|
43
|
+
constructor(name: string, help: string, buckets: readonly number[]);
|
|
44
|
+
observe(value: number, labels?: Labels): void;
|
|
45
|
+
render(): string;
|
|
46
|
+
}
|
|
47
|
+
/** A collection of metrics that renders one Prometheus exposition document. */
|
|
48
|
+
export declare class MetricsRegistry {
|
|
49
|
+
private readonly counters;
|
|
50
|
+
private readonly gauges;
|
|
51
|
+
private readonly histograms;
|
|
52
|
+
counter(name: string, help?: string): Counter;
|
|
53
|
+
gauge(name: string, help?: string): Gauge;
|
|
54
|
+
histogram(name: string, buckets: readonly number[], help?: string): Histogram;
|
|
55
|
+
/** The Prometheus text exposition of every registered series. */
|
|
56
|
+
render(): string;
|
|
57
|
+
}
|
|
58
|
+
/** Create a standalone registry to register custom app metrics on, shared into `metrics({ registry })`. */
|
|
59
|
+
export declare function createMetricsRegistry(): MetricsRegistry;
|
|
60
|
+
export interface MetricsOptions {
|
|
61
|
+
/** Where to expose the Prometheus text. Default `/metrics`. This route is excluded from the metrics. */
|
|
62
|
+
readonly path?: string;
|
|
63
|
+
/** Reuse a registry (so custom app metrics render alongside the RED series). Default: a fresh one. */
|
|
64
|
+
readonly registry?: MetricsRegistry;
|
|
65
|
+
/** Histogram buckets in SECONDS for request duration. Default OpenTelemetry latency buckets. */
|
|
66
|
+
readonly buckets?: readonly number[];
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Enable RED metrics + a `/metrics` Prometheus endpoint. Records `nifra_http_requests_total`,
|
|
70
|
+
* `nifra_http_request_duration_seconds`, and `nifra_http_requests_in_flight`, labeled by method,
|
|
71
|
+
* matched route template, and status. Apply once (named-plugin dedupe).
|
|
72
|
+
*/
|
|
73
|
+
export declare function metrics(options?: MetricsOptions): NifraPlugin;
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAgB,KAAK,WAAW,EAAwB,MAAM,sBAAsB,CAAA;AAU3F,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;AA8B9C,6DAA6D;AAC7D,qBAAa,OAAO;IAGhB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IAHvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;gBAE/C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;IAIvB,GAAG,CAAC,MAAM,GAAE,MAAW,EAAE,EAAE,SAAI,GAAG,IAAI;IAMtC,MAAM,IAAI,MAAM;CAMjB;AAED,yEAAyE;AACzE,qBAAa,KAAK;IAGd,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IAHvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;gBAE/C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM;IAIvB,OAAO,CAAC,EAAE;IASV,GAAG,CAAC,MAAM,GAAE,MAAW,EAAE,EAAE,SAAI,GAAG,IAAI;IAGtC,GAAG,CAAC,MAAM,GAAE,MAAW,EAAE,EAAE,SAAI,GAAG,IAAI;IAGtC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAGxC,MAAM,IAAI,MAAM;CAMjB;AASD,sGAAsG;AACtG,qBAAa,SAAS;IAIlB,QAAQ,CAAC,IAAI,EAAE,MAAM;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM;IAJvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqC;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAEvB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACrB,OAAO,EAAE,SAAS,MAAM,EAAE;IAK5B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,GAAE,MAAW,GAAG,IAAI;IAajD,MAAM,IAAI,MAAM;CAcjB;AAED,+EAA+E;AAC/E,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA+B;IAE1D,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAO,GAAG,OAAO;IAQ3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,SAAO,GAAG,KAAK;IAQvC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,SAAO,GAAG,SAAS;IAQ3E,iEAAiE;IACjE,MAAM,IAAI,MAAM;CAOjB;AAED,2GAA2G;AAC3G,wBAAgB,qBAAqB,IAAI,eAAe,CAEvD;AAKD,MAAM,WAAW,cAAc;IAC7B,wGAAwG;IACxG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,sGAAsG;IACtG,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,CAAA;IACnC,gGAAgG;IAChG,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACrC;AAOD;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,GAAG,WAAW,CAkEjE"}
|
package/dist/metrics.js
ADDED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
|
package/dist/otlp.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dependency-free OTLP/HTTP (JSON) span exporter. Ships completed spans to any OpenTelemetry
|
|
3
|
+
* collector (`.../v1/traces`) with in-process batching, matching the package's no-SDK stance: it
|
|
4
|
+
* speaks the wire protocol directly over `fetch`, so it runs on Bun, Node, Deno, and workerd alike.
|
|
5
|
+
*
|
|
6
|
+
* Wire mapping follows the OTLP/JSON encoding: ids stay hex, times become Unix-nanosecond strings,
|
|
7
|
+
* attributes become typed `KeyValue`s. Export is fail-open (a collector outage never throws into a
|
|
8
|
+
* request); delivery failures reach `onError`.
|
|
9
|
+
*/
|
|
10
|
+
import type { ObservationAdapter } from "./span.js";
|
|
11
|
+
export interface OtlpExporterOptions {
|
|
12
|
+
/** The collector's traces endpoint, e.g. `http://localhost:4318/v1/traces`. */
|
|
13
|
+
readonly url: string;
|
|
14
|
+
/** Extra headers on every export (auth tokens, tenant ids). */
|
|
15
|
+
readonly headers?: Record<string, string>;
|
|
16
|
+
/** `service.name` resource attribute. Default `"nifra"`. */
|
|
17
|
+
readonly serviceName?: string;
|
|
18
|
+
/** Batching knobs. */
|
|
19
|
+
readonly batch?: {
|
|
20
|
+
/** Flush once this many spans are queued. Must be a positive safe integer. Default 512. */
|
|
21
|
+
readonly maxBatch?: number;
|
|
22
|
+
/** Hard cap on the queue; oldest spans are dropped past it. Positive safe integer; default 2048. */
|
|
23
|
+
readonly maxQueue?: number;
|
|
24
|
+
/** Periodic flush interval (ms). Must be a positive safe integer. Default 5000. */
|
|
25
|
+
readonly flushIntervalMs?: number;
|
|
26
|
+
};
|
|
27
|
+
/** Called when an export POST fails or spans are dropped. Fail-open: never throws into a request. */
|
|
28
|
+
readonly onError?: (error: unknown) => void;
|
|
29
|
+
/** Override `fetch` (tests, a proxy agent). Defaults to the global. */
|
|
30
|
+
readonly fetch?: (input: string, init: RequestInit) => Promise<{
|
|
31
|
+
ok: boolean;
|
|
32
|
+
status: number;
|
|
33
|
+
}>;
|
|
34
|
+
}
|
|
35
|
+
export interface OtlpExporter extends ObservationAdapter {
|
|
36
|
+
/** Send everything queued now. Await it in a graceful shutdown so no span is lost. */
|
|
37
|
+
flush(): Promise<void>;
|
|
38
|
+
/** Stop the periodic timer and flush a final time. Call on server stop. */
|
|
39
|
+
shutdown(): Promise<void>;
|
|
40
|
+
}
|
|
41
|
+
export declare function otlpExporter(options: OtlpExporterOptions): OtlpExporter;
|
|
42
|
+
//# sourceMappingURL=otlp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp.d.ts","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAA6B,kBAAkB,EAAc,MAAM,WAAW,CAAA;AAE1F,MAAM,WAAW,mBAAmB;IAClC,+EAA+E;IAC/E,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACzC,4DAA4D;IAC5D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,sBAAsB;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE;QACf,2FAA2F;QAC3F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;QAC1B,oGAAoG;QACpG,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;QAC1B,mFAAmF;QACnF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;KAClC,CAAA;IACD,qGAAqG;IACrG,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAC3C,uEAAuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAChG;AAED,MAAM,WAAW,YAAa,SAAQ,kBAAkB;IACtD,sFAAsF;IACtF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,2EAA2E;IAC3E,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CAC1B;AAiDD,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CA2EvE"}
|
package/dist/otlp.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dependency-free OTLP/HTTP (JSON) span exporter. Ships completed spans to any OpenTelemetry
|
|
3
|
+
* collector (`.../v1/traces`) with in-process batching, matching the package's no-SDK stance: it
|
|
4
|
+
* speaks the wire protocol directly over `fetch`, so it runs on Bun, Node, Deno, and workerd alike.
|
|
5
|
+
*
|
|
6
|
+
* Wire mapping follows the OTLP/JSON encoding: ids stay hex, times become Unix-nanosecond strings,
|
|
7
|
+
* attributes become typed `KeyValue`s. Export is fail-open (a collector outage never throws into a
|
|
8
|
+
* request); delivery failures reach `onError`.
|
|
9
|
+
*/
|
|
10
|
+
const STATUS_CODE = { unset: 0, ok: 1, error: 2 };
|
|
11
|
+
function assertPositiveSafeInteger(value, option) {
|
|
12
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
13
|
+
throw new RangeError(`otlpExporter: ${option} must be a positive safe integer; received ${String(value)}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function anyValue(value) {
|
|
17
|
+
if (typeof value === "boolean")
|
|
18
|
+
return { boolValue: value };
|
|
19
|
+
if (typeof value === "string")
|
|
20
|
+
return { stringValue: value };
|
|
21
|
+
// OTLP/JSON carries 64-bit ints as strings; non-integers use doubleValue.
|
|
22
|
+
return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };
|
|
23
|
+
}
|
|
24
|
+
function keyValues(attributes) {
|
|
25
|
+
return Object.entries(attributes).map(([key, value]) => ({ key, value: anyValue(value) }));
|
|
26
|
+
}
|
|
27
|
+
const MS_TO_NANOS = 1_000_000;
|
|
28
|
+
function toOtlpSpan(span) {
|
|
29
|
+
const end = span.endTime ?? span.startTime;
|
|
30
|
+
return {
|
|
31
|
+
traceId: span.traceId,
|
|
32
|
+
spanId: span.spanId,
|
|
33
|
+
...(span.parentSpanId !== undefined ? { parentSpanId: span.parentSpanId } : {}),
|
|
34
|
+
name: span.name,
|
|
35
|
+
kind: 2, // SPAN_KIND_SERVER
|
|
36
|
+
startTimeUnixNano: String(span.startTime * MS_TO_NANOS),
|
|
37
|
+
endTimeUnixNano: String(end * MS_TO_NANOS),
|
|
38
|
+
attributes: keyValues(span.attributes),
|
|
39
|
+
status: { code: STATUS_CODE[span.status] },
|
|
40
|
+
...(span.links !== undefined && span.links.length > 0
|
|
41
|
+
? {
|
|
42
|
+
links: span.links.map((link) => ({
|
|
43
|
+
traceId: link.traceId,
|
|
44
|
+
spanId: link.spanId,
|
|
45
|
+
...(link.attributes !== undefined ? { attributes: keyValues(link.attributes) } : {}),
|
|
46
|
+
})),
|
|
47
|
+
}
|
|
48
|
+
: {}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export function otlpExporter(options) {
|
|
52
|
+
const maxBatch = options.batch?.maxBatch ?? 512;
|
|
53
|
+
const maxQueue = options.batch?.maxQueue ?? 2048;
|
|
54
|
+
const flushIntervalMs = options.batch?.flushIntervalMs ?? 5000;
|
|
55
|
+
assertPositiveSafeInteger(maxBatch, "batch.maxBatch");
|
|
56
|
+
assertPositiveSafeInteger(maxQueue, "batch.maxQueue");
|
|
57
|
+
assertPositiveSafeInteger(flushIntervalMs, "batch.flushIntervalMs");
|
|
58
|
+
const doFetch = options.fetch ?? globalThis.fetch;
|
|
59
|
+
const resourceAttributes = keyValues({ "service.name": options.serviceName ?? "nifra" });
|
|
60
|
+
const queue = [];
|
|
61
|
+
let timer;
|
|
62
|
+
const report = (error) => {
|
|
63
|
+
try {
|
|
64
|
+
options.onError?.(error);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// an onError that itself throws must not break export
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const send = async (spans) => {
|
|
71
|
+
if (spans.length === 0 || doFetch === undefined)
|
|
72
|
+
return;
|
|
73
|
+
const body = JSON.stringify({
|
|
74
|
+
resourceSpans: [
|
|
75
|
+
{
|
|
76
|
+
resource: { attributes: resourceAttributes },
|
|
77
|
+
scopeSpans: [{ scope: { name: "nifra" }, spans: spans.map(toOtlpSpan) }],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
try {
|
|
82
|
+
const res = await doFetch(options.url, {
|
|
83
|
+
method: "POST",
|
|
84
|
+
headers: { "content-type": "application/json", ...options.headers },
|
|
85
|
+
body,
|
|
86
|
+
});
|
|
87
|
+
if (!res.ok)
|
|
88
|
+
report(new Error(`otlp_export_failed_${res.status}`));
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
report(error);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const flush = () => {
|
|
95
|
+
if (queue.length === 0)
|
|
96
|
+
return Promise.resolve();
|
|
97
|
+
return send(queue.splice(0, queue.length));
|
|
98
|
+
};
|
|
99
|
+
const ensureTimer = () => {
|
|
100
|
+
if (timer !== undefined)
|
|
101
|
+
return;
|
|
102
|
+
timer = setInterval(() => {
|
|
103
|
+
void flush();
|
|
104
|
+
}, flushIntervalMs);
|
|
105
|
+
timer.unref?.(); // never keep the process alive for the exporter
|
|
106
|
+
};
|
|
107
|
+
return {
|
|
108
|
+
onEnd(span) {
|
|
109
|
+
queue.push(span);
|
|
110
|
+
if (queue.length > maxQueue) {
|
|
111
|
+
queue.shift(); // drop the oldest under sustained backpressure rather than grow unbounded
|
|
112
|
+
report(new Error("otlp_queue_overflow_span_dropped"));
|
|
113
|
+
}
|
|
114
|
+
if (queue.length >= maxBatch)
|
|
115
|
+
void flush();
|
|
116
|
+
else
|
|
117
|
+
ensureTimer();
|
|
118
|
+
},
|
|
119
|
+
flush,
|
|
120
|
+
async shutdown() {
|
|
121
|
+
if (timer !== undefined) {
|
|
122
|
+
clearInterval(timer);
|
|
123
|
+
timer = undefined;
|
|
124
|
+
}
|
|
125
|
+
await flush();
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=otlp.js.map
|
package/dist/otlp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp.js","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"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"}
|
package/dist/span.d.ts
CHANGED
|
@@ -46,8 +46,6 @@ export interface ObservationAdapter {
|
|
|
46
46
|
onStart?(span: NifraSpan): void;
|
|
47
47
|
onEnd(span: NifraSpan): void;
|
|
48
48
|
}
|
|
49
|
-
/** Backwards-compatible name for an observation sink. */
|
|
50
|
-
export type SpanExporter = ObservationAdapter;
|
|
51
49
|
/**
|
|
52
50
|
* Fan out lifecycle notifications to several adapters. Each adapter is isolated: an exception in
|
|
53
51
|
* one sink cannot prevent the remaining sinks from observing the span.
|
|
@@ -55,5 +53,5 @@ export type SpanExporter = ObservationAdapter;
|
|
|
55
53
|
export declare function combineObservationAdapters(adapters: readonly ObservationAdapter[]): ObservationAdapter;
|
|
56
54
|
/** A no-frills exporter that logs each completed span as one structured line. Useful in dev or as a
|
|
57
55
|
* starting point before wiring a real backend. */
|
|
58
|
-
export declare function consoleSpanExporter(log?: (line: string) => void):
|
|
56
|
+
export declare function consoleSpanExporter(log?: (line: string) => void): ObservationAdapter;
|
|
59
57
|
//# sourceMappingURL=span.d.ts.map
|
package/dist/span.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"span.d.ts","sourceRoot":"","sources":["../src/span.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAA;AACjD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAEtD,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;CAC/D;AAED,8DAA8D;AAC9D,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mCAAmC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,UAAU,CAAA;IAClB,2CAA2C;IAC3C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACnD,iGAAiG;IACjG,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;CAC5C;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;IAC/B,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;CAC7B;AAED
|
|
1
|
+
{"version":3,"file":"span.d.ts","sourceRoot":"","sources":["../src/span.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,IAAI,GAAG,OAAO,CAAA;AACjD,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAEtD,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAA;CAC/D;AAED,8DAA8D;AAC9D,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,mDAAmD;IACnD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,mCAAmC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,qDAAqD;IACrD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,UAAU,CAAA;IAClB,2CAA2C;IAC3C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACnD,iGAAiG;IACjG,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;CAC5C;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;IAC/B,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAAA;CAC7B;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,GACtC,kBAAkB,CAqBpB;AAED;kDACkD;AAClD,wBAAgB,mBAAmB,CACjC,GAAG,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAEtB,GACA,kBAAkB,CAgBpB"}
|
package/dist/span.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"span.js","sourceRoot":"","sources":["../src/span.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"span.js","sourceRoot":"","sources":["../src/span.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA+CH;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAAuC;IAEvC,OAAO;QACL,OAAO,CAAC,IAAI;YACV,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACH,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,CAAA;gBACzB,CAAC;gBAAC,MAAM,CAAC;oBACP,wCAAwC;gBAC1C,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI;YACR,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACH,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACrB,CAAC;gBAAC,MAAM,CAAC;oBACP,wCAAwC;gBAC1C,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED;kDACkD;AAClD,MAAM,UAAU,mBAAmB,CACjC,MAA8B,CAAC,CAAC,EAAE,EAAE;IAClC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AAChB,CAAC;IAED,OAAO;QACL,KAAK,CAAC,IAAI;YACR,GAAG,CACD,IAAI,CAAC,SAAS,CAAC;gBACb,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,GAAG,IAAI,CAAC,UAAU;aACnB,CAAC,CACH,CAAA;QACH,CAAC;KACF,CAAA;AACH,CAAC"}
|
package/dist/tracing.d.ts
CHANGED
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
* The `tracing()` plugin — establishes a span per request, propagates W3C trace context, and exposes
|
|
3
3
|
* `c.trace` for forwarding the trace to downstream services. OpenTelemetry-compatible by wire format
|
|
4
4
|
* (traceparent) and attribute names (HTTP semantic conventions); the span itself is exported through
|
|
5
|
-
* your {@link
|
|
5
|
+
* your {@link ObservationAdapter} (bridge to the OTel SDK, or log via `consoleSpanExporter`).
|
|
6
6
|
*/
|
|
7
|
-
import { type CausalityContext, type CausalityRecorder } from "@nifrajs/core";
|
|
7
|
+
import { type CausalityContext, type CausalityRecorder } from "@nifrajs/core/causality";
|
|
8
8
|
import { type ObservationContext } from "./lifecycle.js";
|
|
9
|
-
import { type ObservationAdapter
|
|
9
|
+
import { type ObservationAdapter } from "./span.js";
|
|
10
10
|
/** The trace context exposed on the handler `c.trace` (typed, threaded via `derive`). */
|
|
11
11
|
export type TraceContext = ObservationContext;
|
|
12
12
|
export interface TracingOptions {
|
|
13
13
|
/** Where spans are sent. Default: {@link consoleSpanExporter}. */
|
|
14
|
-
readonly exporter?:
|
|
14
|
+
readonly exporter?: ObservationAdapter;
|
|
15
15
|
/** Additional observation adapters (DevTools, a private redacting backend, metrics, …). */
|
|
16
16
|
readonly adapters?: readonly ObservationAdapter[];
|
|
17
17
|
/** Sets the `service.name` attribute on every span. */
|
|
@@ -50,5 +50,5 @@ export declare function traceHeaders(trace: TraceContext, causality?: CausalityC
|
|
|
50
50
|
* // in a handler: fetch(url, { headers: traceHeaders(c.trace) }) // continue the trace downstream
|
|
51
51
|
* ```
|
|
52
52
|
*/
|
|
53
|
-
export declare function tracing(options?: TracingOptions): import("@nifrajs/core").NifraPlugin<import("@nifrajs/core").AnyServer, import("@nifrajs/core").Server<any, any>>;
|
|
53
|
+
export declare function tracing(options?: TracingOptions): import("@nifrajs/core/server").NifraPlugin<import("@nifrajs/core/server").AnyServer, import("@nifrajs/core/server").Server<any, any>>;
|
|
54
54
|
//# sourceMappingURL=tracing.d.ts.map
|
package/dist/tracing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAKvB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAExE,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG,kBAAkB,CAAA;AAE7C,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAA;IACtC,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAA;IACjD,uDAAuD;IACvD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE;QACnB,QAAQ,CAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAA;QACrC,sDAAsD;QACtD,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;QAC3B;;;WAGG;QACH,QAAQ,CAAC,aAAa,CAAC,EAAE,CACvB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,gBAAgB,KACtB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAChC,CAAA;CACF;AAED;uDACuD;AACvD,wBAAgB,YAAY,CAC1B,KAAK,EAAE,YAAY,EACnB,SAAS,CAAC,EAAE,gBAAgB,GAC3B;IAAE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAKrE;AAUD;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,cAAmB,yIAqGnD"}
|
package/dist/tracing.js
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* The `tracing()` plugin — establishes a span per request, propagates W3C trace context, and exposes
|
|
3
3
|
* `c.trace` for forwarding the trace to downstream services. OpenTelemetry-compatible by wire format
|
|
4
4
|
* (traceparent) and attribute names (HTTP semantic conventions); the span itself is exported through
|
|
5
|
-
* your {@link
|
|
5
|
+
* your {@link ObservationAdapter} (bridge to the OTel SDK, or log via `consoleSpanExporter`).
|
|
6
6
|
*/
|
|
7
|
-
import { causalityHeaders, continueCausality,
|
|
7
|
+
import { causalityHeaders, continueCausality, readCausalityHeaders, startCausality, } from "@nifrajs/core/causality";
|
|
8
|
+
import { definePlugin } from "@nifrajs/core/server";
|
|
8
9
|
import { createObservationLifecycle, } from "./lifecycle.js";
|
|
9
10
|
import { consoleSpanExporter } from "./span.js";
|
|
10
11
|
/** Spread into an outgoing `fetch`/`ctx.api` call's headers to continue the trace downstream:
|
package/dist/tracing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tracing.js","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAGL,gBAAgB,EAChB,iBAAiB,EACjB,
|
|
1
|
+
{"version":3,"file":"tracing.js","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/otel",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Distributed tracing for nifra — W3C traceparent/tracestate propagation + OpenTelemetry-semantic-convention spans via a pluggable exporter. Bridge to the OpenTelemetry SDK or log spans directly; no SDK bundled. Edge-safe.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -24,6 +24,16 @@
|
|
|
24
24
|
"bun": "./src/index.ts",
|
|
25
25
|
"types": "./dist/index.d.ts",
|
|
26
26
|
"default": "./dist/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./metrics": {
|
|
29
|
+
"bun": "./src/metrics.ts",
|
|
30
|
+
"types": "./dist/metrics.d.ts",
|
|
31
|
+
"default": "./dist/metrics.js"
|
|
32
|
+
},
|
|
33
|
+
"./effects": {
|
|
34
|
+
"bun": "./src/effects.ts",
|
|
35
|
+
"types": "./dist/effects.d.ts",
|
|
36
|
+
"default": "./dist/effects.js"
|
|
27
37
|
}
|
|
28
38
|
},
|
|
29
39
|
"files": [
|
|
@@ -39,7 +49,7 @@
|
|
|
39
49
|
"access": "public"
|
|
40
50
|
},
|
|
41
51
|
"peerDependencies": {
|
|
42
|
-
"@nifrajs/core": "^1.
|
|
52
|
+
"@nifrajs/core": "^2.1.0"
|
|
43
53
|
},
|
|
44
54
|
"devDependencies": {
|
|
45
55
|
"@nifrajs/core": "workspace:*"
|
package/src/effects.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/** Token-only OpenTelemetry spans for capability admission, execution, compensation, and reconciliation. */
|
|
2
|
+
|
|
3
|
+
import type { EffectLifecycleEvent, EffectLifecycleObserver } from "@nifrajs/core/effect-lifecycle"
|
|
4
|
+
import type { AnyServer, IdentityPlugin } from "@nifrajs/core/server"
|
|
5
|
+
import { type ActiveObservation, createObservationLifecycle } from "./lifecycle.ts"
|
|
6
|
+
import type { AttributeValue, ObservationAdapter } from "./span.ts"
|
|
7
|
+
|
|
8
|
+
export interface EffectTracingOptions {
|
|
9
|
+
readonly exporter?: ObservationAdapter
|
|
10
|
+
readonly adapters?: readonly ObservationAdapter[]
|
|
11
|
+
/** Maximum unmatched started events retained at once. Default 10,000. */
|
|
12
|
+
readonly maxActive?: number
|
|
13
|
+
/** Opportunistically expire unmatched events older than this many milliseconds. Default 5 min. */
|
|
14
|
+
readonly maxActiveAgeMs?: number
|
|
15
|
+
/** Injectable wall clock for deterministic retention tests. */
|
|
16
|
+
readonly now?: () => number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface EffectTracingPlugin extends IdentityPlugin {
|
|
20
|
+
/** Pass the same observer to `createSagaEngine({ observer })` and reconciliation helpers. */
|
|
21
|
+
readonly observer: EffectLifecycleObserver
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function attributesOf(event: EffectLifecycleEvent): Record<string, AttributeValue> {
|
|
25
|
+
const attributes: Record<string, AttributeValue> = {
|
|
26
|
+
"nifra.effect.id": event.effectId,
|
|
27
|
+
"nifra.effect.capability": event.capability,
|
|
28
|
+
"nifra.effect.stage": event.stage,
|
|
29
|
+
"nifra.effect.phase": event.phase,
|
|
30
|
+
}
|
|
31
|
+
if (event.target !== undefined) attributes["nifra.effect.target"] = event.target
|
|
32
|
+
if (event.digest !== undefined) attributes["nifra.effect.digest"] = event.digest
|
|
33
|
+
if (event.attempt !== undefined) attributes["nifra.effect.attempt"] = event.attempt
|
|
34
|
+
if (event.errorCode !== undefined) attributes["nifra.effect.error_code"] = event.errorCode
|
|
35
|
+
if (event.durationMs !== undefined) attributes["nifra.effect.duration_ms"] = event.durationMs
|
|
36
|
+
for (const [axis, value] of Object.entries(event.cost ?? {})) {
|
|
37
|
+
attributes[`nifra.effect.cost.${axis}`] = value
|
|
38
|
+
}
|
|
39
|
+
return attributes
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Installs child effect spans on subsequent routes. The observer consumes only the constrained
|
|
44
|
+
* `EffectLifecycleEvent` contract; request/business payloads and error text cannot enter an export.
|
|
45
|
+
*/
|
|
46
|
+
export function effectTracing(options: EffectTracingOptions = {}): EffectTracingPlugin {
|
|
47
|
+
const adapters = [
|
|
48
|
+
...(options.exporter === undefined ? [] : [options.exporter]),
|
|
49
|
+
...(options.adapters ?? []),
|
|
50
|
+
]
|
|
51
|
+
const maxActive = options.maxActive ?? 10_000
|
|
52
|
+
const maxActiveAgeMs = options.maxActiveAgeMs ?? 5 * 60_000
|
|
53
|
+
if (!Number.isSafeInteger(maxActive) || maxActive < 1) {
|
|
54
|
+
throw new RangeError("effect tracing maxActive must be a positive safe integer")
|
|
55
|
+
}
|
|
56
|
+
if (!Number.isSafeInteger(maxActiveAgeMs) || maxActiveAgeMs < 1) {
|
|
57
|
+
throw new RangeError("effect tracing maxActiveAgeMs must be a positive safe integer")
|
|
58
|
+
}
|
|
59
|
+
const now = options.now ?? Date.now
|
|
60
|
+
const lifecycle = createObservationLifecycle({ adapters })
|
|
61
|
+
const active = new Map<string, { readonly observation: ActiveObservation; readonly at: number }>()
|
|
62
|
+
let nextSweepAt = Number.POSITIVE_INFINITY
|
|
63
|
+
const keyOf = (event: EffectLifecycleEvent): string => `${event.effectId}\n${event.stage}`
|
|
64
|
+
|
|
65
|
+
const evict = (key: string, errorCode: "observation_evicted" | "observation_expired"): void => {
|
|
66
|
+
const entry = active.get(key)
|
|
67
|
+
if (entry === undefined) return
|
|
68
|
+
active.delete(key)
|
|
69
|
+
entry.observation.setAttributes({
|
|
70
|
+
"nifra.effect.phase": "ambiguous",
|
|
71
|
+
"nifra.effect.error_code": errorCode,
|
|
72
|
+
})
|
|
73
|
+
entry.observation.end({ status: "error" })
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const sweep = (): number => {
|
|
77
|
+
const at = now()
|
|
78
|
+
if (!Number.isFinite(at) || at < nextSweepAt) return at
|
|
79
|
+
// `active` iterates in insertion order and entries carry a monotonic `at`, so the front is the
|
|
80
|
+
// oldest / earliest-expiring. Evict the expired prefix and stop at the first live entry: its expiry
|
|
81
|
+
// is the earliest remaining, i.e. the next sweep deadline. A sweep is therefore O(evicted), not
|
|
82
|
+
// O(active.size). (A backwards clock only defers an eviction to the size cap - the hard bound; age
|
|
83
|
+
// eviction is opportunistic.)
|
|
84
|
+
nextSweepAt = Number.POSITIVE_INFINITY
|
|
85
|
+
for (const [key, entry] of active) {
|
|
86
|
+
const expiresAt = entry.at + maxActiveAgeMs
|
|
87
|
+
if (at >= expiresAt) {
|
|
88
|
+
evict(key, "observation_expired")
|
|
89
|
+
} else {
|
|
90
|
+
nextSweepAt = expiresAt
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return at
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const observer: EffectLifecycleObserver = (event) => {
|
|
98
|
+
const at = sweep()
|
|
99
|
+
const key = keyOf(event)
|
|
100
|
+
if (event.phase === "started") {
|
|
101
|
+
if (active.has(key)) return
|
|
102
|
+
while (active.size >= maxActive) {
|
|
103
|
+
const oldest = active.keys().next().value as string | undefined
|
|
104
|
+
if (oldest === undefined) break
|
|
105
|
+
evict(oldest, "observation_evicted")
|
|
106
|
+
}
|
|
107
|
+
const startedAt = Number.isFinite(at) ? at : event.at
|
|
108
|
+
active.set(
|
|
109
|
+
key,
|
|
110
|
+
Object.freeze({
|
|
111
|
+
observation: lifecycle.start({
|
|
112
|
+
name: `nifra.effect.${event.stage}`,
|
|
113
|
+
parent: event.trace ?? null,
|
|
114
|
+
attributes: attributesOf(event),
|
|
115
|
+
}),
|
|
116
|
+
at: startedAt,
|
|
117
|
+
}),
|
|
118
|
+
)
|
|
119
|
+
nextSweepAt = Math.min(nextSweepAt, startedAt + maxActiveAgeMs)
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
const entry = active.get(key)
|
|
123
|
+
let observation: ActiveObservation
|
|
124
|
+
if (entry === undefined) {
|
|
125
|
+
observation = lifecycle.start({
|
|
126
|
+
name: `nifra.effect.${event.stage}`,
|
|
127
|
+
parent: event.trace ?? null,
|
|
128
|
+
attributes: attributesOf(event),
|
|
129
|
+
})
|
|
130
|
+
} else {
|
|
131
|
+
active.delete(key)
|
|
132
|
+
observation = entry.observation
|
|
133
|
+
observation.setAttributes(attributesOf(event))
|
|
134
|
+
}
|
|
135
|
+
observation.end({ status: event.phase === "succeeded" ? "ok" : "error" })
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const apply = <S extends AnyServer>(app: S): S => {
|
|
139
|
+
app.observeCapability(observer)
|
|
140
|
+
return app
|
|
141
|
+
}
|
|
142
|
+
return Object.assign(apply, {
|
|
143
|
+
pluginName: "nifra:effect-tracing",
|
|
144
|
+
observer,
|
|
145
|
+
}) as EffectTracingPlugin
|
|
146
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @nifrajs/otel — distributed tracing for nifra. The `tracing()` plugin continues (or starts) a W3C
|
|
3
3
|
* trace per request, opens an OpenTelemetry-semantic-convention span, and exposes `c.trace` so you
|
|
4
4
|
* can forward the trace to downstream services with `traceHeaders(c.trace)`. Spans go to a pluggable
|
|
5
|
-
* `
|
|
5
|
+
* `ObservationAdapter` — bridge to the OpenTelemetry SDK, or log with `consoleSpanExporter`. No SDK
|
|
6
6
|
* bundled; edge-safe.
|
|
7
7
|
*
|
|
8
8
|
* import { tracing, traceHeaders, consoleSpanExporter } from "@nifrajs/otel"
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
export { causalitySpanLink } from "./causality.ts"
|
|
13
|
+
export {
|
|
14
|
+
type EffectTracingOptions,
|
|
15
|
+
type EffectTracingPlugin,
|
|
16
|
+
effectTracing,
|
|
17
|
+
} from "./effects.ts"
|
|
13
18
|
export {
|
|
14
19
|
type ActiveObservation,
|
|
15
20
|
createObservationLifecycle,
|
|
@@ -21,6 +26,7 @@ export {
|
|
|
21
26
|
type ObservationParent,
|
|
22
27
|
type StartObservation,
|
|
23
28
|
} from "./lifecycle.ts"
|
|
29
|
+
export { type OtlpExporter, type OtlpExporterOptions, otlpExporter } from "./otlp.ts"
|
|
24
30
|
export {
|
|
25
31
|
type AttributeValue,
|
|
26
32
|
combineObservationAdapters,
|
|
@@ -28,7 +34,6 @@ export {
|
|
|
28
34
|
type NifraSpan,
|
|
29
35
|
type ObservationAdapter,
|
|
30
36
|
type ObservationLink,
|
|
31
|
-
type SpanExporter,
|
|
32
37
|
type SpanStatus,
|
|
33
38
|
} from "./span.ts"
|
|
34
39
|
export {
|
package/src/metrics.ts
ADDED
|
Binary file
|
package/src/otlp.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dependency-free OTLP/HTTP (JSON) span exporter. Ships completed spans to any OpenTelemetry
|
|
3
|
+
* collector (`.../v1/traces`) with in-process batching, matching the package's no-SDK stance: it
|
|
4
|
+
* speaks the wire protocol directly over `fetch`, so it runs on Bun, Node, Deno, and workerd alike.
|
|
5
|
+
*
|
|
6
|
+
* Wire mapping follows the OTLP/JSON encoding: ids stay hex, times become Unix-nanosecond strings,
|
|
7
|
+
* attributes become typed `KeyValue`s. Export is fail-open (a collector outage never throws into a
|
|
8
|
+
* request); delivery failures reach `onError`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { AttributeValue, NifraSpan, ObservationAdapter, SpanStatus } from "./span.ts"
|
|
12
|
+
|
|
13
|
+
export interface OtlpExporterOptions {
|
|
14
|
+
/** The collector's traces endpoint, e.g. `http://localhost:4318/v1/traces`. */
|
|
15
|
+
readonly url: string
|
|
16
|
+
/** Extra headers on every export (auth tokens, tenant ids). */
|
|
17
|
+
readonly headers?: Record<string, string>
|
|
18
|
+
/** `service.name` resource attribute. Default `"nifra"`. */
|
|
19
|
+
readonly serviceName?: string
|
|
20
|
+
/** Batching knobs. */
|
|
21
|
+
readonly batch?: {
|
|
22
|
+
/** Flush once this many spans are queued. Must be a positive safe integer. Default 512. */
|
|
23
|
+
readonly maxBatch?: number
|
|
24
|
+
/** Hard cap on the queue; oldest spans are dropped past it. Positive safe integer; default 2048. */
|
|
25
|
+
readonly maxQueue?: number
|
|
26
|
+
/** Periodic flush interval (ms). Must be a positive safe integer. Default 5000. */
|
|
27
|
+
readonly flushIntervalMs?: number
|
|
28
|
+
}
|
|
29
|
+
/** Called when an export POST fails or spans are dropped. Fail-open: never throws into a request. */
|
|
30
|
+
readonly onError?: (error: unknown) => void
|
|
31
|
+
/** Override `fetch` (tests, a proxy agent). Defaults to the global. */
|
|
32
|
+
readonly fetch?: (input: string, init: RequestInit) => Promise<{ ok: boolean; status: number }>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface OtlpExporter extends ObservationAdapter {
|
|
36
|
+
/** Send everything queued now. Await it in a graceful shutdown so no span is lost. */
|
|
37
|
+
flush(): Promise<void>
|
|
38
|
+
/** Stop the periodic timer and flush a final time. Call on server stop. */
|
|
39
|
+
shutdown(): Promise<void>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const STATUS_CODE: Readonly<Record<SpanStatus, number>> = { unset: 0, ok: 1, error: 2 }
|
|
43
|
+
|
|
44
|
+
function assertPositiveSafeInteger(value: number, option: string): void {
|
|
45
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
46
|
+
throw new RangeError(
|
|
47
|
+
`otlpExporter: ${option} must be a positive safe integer; received ${String(value)}`,
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function anyValue(value: AttributeValue): Record<string, unknown> {
|
|
53
|
+
if (typeof value === "boolean") return { boolValue: value }
|
|
54
|
+
if (typeof value === "string") return { stringValue: value }
|
|
55
|
+
// OTLP/JSON carries 64-bit ints as strings; non-integers use doubleValue.
|
|
56
|
+
return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function keyValues(attributes: Readonly<Record<string, AttributeValue>>): unknown[] {
|
|
60
|
+
return Object.entries(attributes).map(([key, value]) => ({ key, value: anyValue(value) }))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const MS_TO_NANOS = 1_000_000
|
|
64
|
+
|
|
65
|
+
function toOtlpSpan(span: NifraSpan): Record<string, unknown> {
|
|
66
|
+
const end = span.endTime ?? span.startTime
|
|
67
|
+
return {
|
|
68
|
+
traceId: span.traceId,
|
|
69
|
+
spanId: span.spanId,
|
|
70
|
+
...(span.parentSpanId !== undefined ? { parentSpanId: span.parentSpanId } : {}),
|
|
71
|
+
name: span.name,
|
|
72
|
+
kind: 2, // SPAN_KIND_SERVER
|
|
73
|
+
startTimeUnixNano: String(span.startTime * MS_TO_NANOS),
|
|
74
|
+
endTimeUnixNano: String(end * MS_TO_NANOS),
|
|
75
|
+
attributes: keyValues(span.attributes),
|
|
76
|
+
status: { code: STATUS_CODE[span.status] },
|
|
77
|
+
...(span.links !== undefined && span.links.length > 0
|
|
78
|
+
? {
|
|
79
|
+
links: span.links.map((link) => ({
|
|
80
|
+
traceId: link.traceId,
|
|
81
|
+
spanId: link.spanId,
|
|
82
|
+
...(link.attributes !== undefined ? { attributes: keyValues(link.attributes) } : {}),
|
|
83
|
+
})),
|
|
84
|
+
}
|
|
85
|
+
: {}),
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function otlpExporter(options: OtlpExporterOptions): OtlpExporter {
|
|
90
|
+
const maxBatch = options.batch?.maxBatch ?? 512
|
|
91
|
+
const maxQueue = options.batch?.maxQueue ?? 2048
|
|
92
|
+
const flushIntervalMs = options.batch?.flushIntervalMs ?? 5000
|
|
93
|
+
assertPositiveSafeInteger(maxBatch, "batch.maxBatch")
|
|
94
|
+
assertPositiveSafeInteger(maxQueue, "batch.maxQueue")
|
|
95
|
+
assertPositiveSafeInteger(flushIntervalMs, "batch.flushIntervalMs")
|
|
96
|
+
const doFetch = options.fetch ?? (globalThis.fetch as OtlpExporterOptions["fetch"])
|
|
97
|
+
const resourceAttributes = keyValues({ "service.name": options.serviceName ?? "nifra" })
|
|
98
|
+
|
|
99
|
+
const queue: NifraSpan[] = []
|
|
100
|
+
let timer: ReturnType<typeof setInterval> | undefined
|
|
101
|
+
|
|
102
|
+
const report = (error: unknown): void => {
|
|
103
|
+
try {
|
|
104
|
+
options.onError?.(error)
|
|
105
|
+
} catch {
|
|
106
|
+
// an onError that itself throws must not break export
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const send = async (spans: NifraSpan[]): Promise<void> => {
|
|
111
|
+
if (spans.length === 0 || doFetch === undefined) return
|
|
112
|
+
const body = JSON.stringify({
|
|
113
|
+
resourceSpans: [
|
|
114
|
+
{
|
|
115
|
+
resource: { attributes: resourceAttributes },
|
|
116
|
+
scopeSpans: [{ scope: { name: "nifra" }, spans: spans.map(toOtlpSpan) }],
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
})
|
|
120
|
+
try {
|
|
121
|
+
const res = await doFetch(options.url, {
|
|
122
|
+
method: "POST",
|
|
123
|
+
headers: { "content-type": "application/json", ...options.headers },
|
|
124
|
+
body,
|
|
125
|
+
})
|
|
126
|
+
if (!res.ok) report(new Error(`otlp_export_failed_${res.status}`))
|
|
127
|
+
} catch (error) {
|
|
128
|
+
report(error)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const flush = (): Promise<void> => {
|
|
133
|
+
if (queue.length === 0) return Promise.resolve()
|
|
134
|
+
return send(queue.splice(0, queue.length))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const ensureTimer = (): void => {
|
|
138
|
+
if (timer !== undefined) return
|
|
139
|
+
timer = setInterval(() => {
|
|
140
|
+
void flush()
|
|
141
|
+
}, flushIntervalMs)
|
|
142
|
+
;(timer as { unref?: () => void }).unref?.() // never keep the process alive for the exporter
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
onEnd(span) {
|
|
147
|
+
queue.push(span)
|
|
148
|
+
if (queue.length > maxQueue) {
|
|
149
|
+
queue.shift() // drop the oldest under sustained backpressure rather than grow unbounded
|
|
150
|
+
report(new Error("otlp_queue_overflow_span_dropped"))
|
|
151
|
+
}
|
|
152
|
+
if (queue.length >= maxBatch) void flush()
|
|
153
|
+
else ensureTimer()
|
|
154
|
+
},
|
|
155
|
+
flush,
|
|
156
|
+
async shutdown() {
|
|
157
|
+
if (timer !== undefined) {
|
|
158
|
+
clearInterval(timer)
|
|
159
|
+
timer = undefined
|
|
160
|
+
}
|
|
161
|
+
await flush()
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
}
|
package/src/span.ts
CHANGED
|
@@ -51,9 +51,6 @@ export interface ObservationAdapter {
|
|
|
51
51
|
onEnd(span: NifraSpan): void
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/** Backwards-compatible name for an observation sink. */
|
|
55
|
-
export type SpanExporter = ObservationAdapter
|
|
56
|
-
|
|
57
54
|
/**
|
|
58
55
|
* Fan out lifecycle notifications to several adapters. Each adapter is isolated: an exception in
|
|
59
56
|
* one sink cannot prevent the remaining sinks from observing the span.
|
|
@@ -89,7 +86,7 @@ export function consoleSpanExporter(
|
|
|
89
86
|
log: (line: string) => void = (l) => {
|
|
90
87
|
console.log(l)
|
|
91
88
|
},
|
|
92
|
-
):
|
|
89
|
+
): ObservationAdapter {
|
|
93
90
|
return {
|
|
94
91
|
onEnd(span) {
|
|
95
92
|
log(
|
package/src/tracing.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The `tracing()` plugin — establishes a span per request, propagates W3C trace context, and exposes
|
|
3
3
|
* `c.trace` for forwarding the trace to downstream services. OpenTelemetry-compatible by wire format
|
|
4
4
|
* (traceparent) and attribute names (HTTP semantic conventions); the span itself is exported through
|
|
5
|
-
* your {@link
|
|
5
|
+
* your {@link ObservationAdapter} (bridge to the OTel SDK, or log via `consoleSpanExporter`).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
@@ -10,23 +10,23 @@ import {
|
|
|
10
10
|
type CausalityRecorder,
|
|
11
11
|
causalityHeaders,
|
|
12
12
|
continueCausality,
|
|
13
|
-
definePlugin,
|
|
14
13
|
readCausalityHeaders,
|
|
15
14
|
startCausality,
|
|
16
|
-
} from "@nifrajs/core"
|
|
15
|
+
} from "@nifrajs/core/causality"
|
|
16
|
+
import { definePlugin } from "@nifrajs/core/server"
|
|
17
17
|
import {
|
|
18
18
|
type ActiveObservation,
|
|
19
19
|
createObservationLifecycle,
|
|
20
20
|
type ObservationContext,
|
|
21
21
|
} from "./lifecycle.ts"
|
|
22
|
-
import { consoleSpanExporter, type ObservationAdapter
|
|
22
|
+
import { consoleSpanExporter, type ObservationAdapter } from "./span.ts"
|
|
23
23
|
|
|
24
24
|
/** The trace context exposed on the handler `c.trace` (typed, threaded via `derive`). */
|
|
25
25
|
export type TraceContext = ObservationContext
|
|
26
26
|
|
|
27
27
|
export interface TracingOptions {
|
|
28
28
|
/** Where spans are sent. Default: {@link consoleSpanExporter}. */
|
|
29
|
-
readonly exporter?:
|
|
29
|
+
readonly exporter?: ObservationAdapter
|
|
30
30
|
/** Additional observation adapters (DevTools, a private redacting backend, metrics, …). */
|
|
31
31
|
readonly adapters?: readonly ObservationAdapter[]
|
|
32
32
|
/** Sets the `service.name` attribute on every span. */
|