@btravstack/observability 0.17.0 → 0.19.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/index.cjs +11 -4
- package/dist/index.mjs +11 -4
- package/dist/otel.cjs +70 -16
- package/dist/otel.d.cts +3 -1
- package/dist/otel.d.mts +3 -1
- package/dist/otel.mjs +72 -18
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -60,15 +60,22 @@ const jsonSink = (stream = process.stdout) => (line) => {
|
|
|
60
60
|
};
|
|
61
61
|
stream.write(`${safeStringify(rendered)}\n`);
|
|
62
62
|
};
|
|
63
|
+
const serialises = (key, value) => {
|
|
64
|
+
try {
|
|
65
|
+
JSON.stringify({ [key]: value });
|
|
66
|
+
return true;
|
|
67
|
+
} catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
63
71
|
const safeStringify = (rendered) => {
|
|
64
72
|
try {
|
|
65
73
|
return JSON.stringify(rendered);
|
|
66
74
|
} catch {
|
|
75
|
+
const kept = Object.entries(rendered).filter(([key, value]) => serialises(key, value));
|
|
67
76
|
return JSON.stringify({
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
message: rendered["message"],
|
|
71
|
-
cause: "[unserialisable]"
|
|
77
|
+
...Object.fromEntries(kept),
|
|
78
|
+
unserialisable: Object.keys(rendered).filter((key) => !serialises(key, rendered[key]))
|
|
72
79
|
});
|
|
73
80
|
}
|
|
74
81
|
};
|
package/dist/index.mjs
CHANGED
|
@@ -59,15 +59,22 @@ const jsonSink = (stream = process.stdout) => (line) => {
|
|
|
59
59
|
};
|
|
60
60
|
stream.write(`${safeStringify(rendered)}\n`);
|
|
61
61
|
};
|
|
62
|
+
const serialises = (key, value) => {
|
|
63
|
+
try {
|
|
64
|
+
JSON.stringify({ [key]: value });
|
|
65
|
+
return true;
|
|
66
|
+
} catch {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
};
|
|
62
70
|
const safeStringify = (rendered) => {
|
|
63
71
|
try {
|
|
64
72
|
return JSON.stringify(rendered);
|
|
65
73
|
} catch {
|
|
74
|
+
const kept = Object.entries(rendered).filter(([key, value]) => serialises(key, value));
|
|
66
75
|
return JSON.stringify({
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
message: rendered["message"],
|
|
70
|
-
cause: "[unserialisable]"
|
|
76
|
+
...Object.fromEntries(kept),
|
|
77
|
+
unserialisable: Object.keys(rendered).filter((key) => !serialises(key, rendered[key]))
|
|
71
78
|
});
|
|
72
79
|
}
|
|
73
80
|
};
|
package/dist/otel.cjs
CHANGED
|
@@ -7,6 +7,38 @@ let unthrown = require("unthrown");
|
|
|
7
7
|
//#region src/otel.ts
|
|
8
8
|
var OtelSdk = class extends (0, _btravstack_di.Port)("OtelSdk") {};
|
|
9
9
|
/**
|
|
10
|
+
* OTel semantic conventions' duration buckets, in seconds. The SDK's default
|
|
11
|
+
* boundaries were drawn for milliseconds and would put every operation under a
|
|
12
|
+
* second into the first bucket.
|
|
13
|
+
*/
|
|
14
|
+
const SECONDS_BUCKETS = [
|
|
15
|
+
.005,
|
|
16
|
+
.01,
|
|
17
|
+
.025,
|
|
18
|
+
.05,
|
|
19
|
+
.075,
|
|
20
|
+
.1,
|
|
21
|
+
.25,
|
|
22
|
+
.5,
|
|
23
|
+
.75,
|
|
24
|
+
1,
|
|
25
|
+
2.5,
|
|
26
|
+
5,
|
|
27
|
+
7.5,
|
|
28
|
+
10
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Each open unit's span, by the record it was opened for — so an operation
|
|
32
|
+
* inside the unit parents on it while the observer still injects nothing.
|
|
33
|
+
* Weak, so an ended unit costs nothing once its record is gone.
|
|
34
|
+
*/
|
|
35
|
+
const unitSpans = /* @__PURE__ */ new WeakMap();
|
|
36
|
+
const unitAttributes = (unit) => unit === void 0 ? {} : {
|
|
37
|
+
"btravstack.unit_id": unit.unitId,
|
|
38
|
+
"btravstack.trace_id": unit.traceId,
|
|
39
|
+
...unit.tenantId === void 0 ? {} : { "btravstack.tenant_id": unit.tenantId }
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
10
42
|
* The OTel starter: a module providing `Tracer` and `Meter` over a `NodeSDK`
|
|
11
43
|
* opened with the scope and flushed on close. Compose it beside
|
|
12
44
|
* `observability()` in a root's `imports`.
|
|
@@ -54,16 +86,25 @@ const otel = (options) => (0, _btravstack_di.Module)("Otel")({
|
|
|
54
86
|
};
|
|
55
87
|
return ({ component, name, attributes, details, traced }) => {
|
|
56
88
|
const meter = _opentelemetry_api.metrics.getMeter("@btravstack/observability");
|
|
57
|
-
const
|
|
58
|
-
span?.setAttributes({
|
|
59
|
-
...attributes,
|
|
60
|
-
...details
|
|
61
|
-
});
|
|
89
|
+
const unit = (0, _btravstack_core.currentUnit)();
|
|
62
90
|
const startedAt = performance.now();
|
|
91
|
+
const open = (startTime) => {
|
|
92
|
+
const parent = unit === void 0 ? void 0 : unitSpans.get(unit);
|
|
93
|
+
const opened = tracer.startSpan(`${component}.${name}`, startTime === void 0 ? {} : { startTime }, parent === void 0 ? void 0 : _opentelemetry_api.trace.setSpan(_opentelemetry_api.context.active(), parent));
|
|
94
|
+
opened.setAttributes({
|
|
95
|
+
...attributes,
|
|
96
|
+
...details,
|
|
97
|
+
...unitAttributes(unit)
|
|
98
|
+
});
|
|
99
|
+
return opened;
|
|
100
|
+
};
|
|
101
|
+
const deferred = traced !== false && unit !== void 0 && !unitSpans.has(unit);
|
|
102
|
+
const started = traced === false || deferred ? void 0 : open();
|
|
63
103
|
const operations = instrument(counters, component, () => meter.createCounter(`btravstack.${component}.operations`, { description: `${component} operations, by operation and outcome` }));
|
|
64
104
|
const duration = instrument(durations, component, () => meter.createHistogram(`btravstack.${component}.duration`, {
|
|
65
105
|
description: `${component} operation duration`,
|
|
66
|
-
unit: "
|
|
106
|
+
unit: "s",
|
|
107
|
+
advice: { explicitBucketBoundaries: SECONDS_BUCKETS }
|
|
67
108
|
}));
|
|
68
109
|
return ({ outcome, attributes: settled }) => {
|
|
69
110
|
const all = {
|
|
@@ -72,9 +113,15 @@ const otel = (options) => (0, _btravstack_di.Module)("Otel")({
|
|
|
72
113
|
outcome
|
|
73
114
|
};
|
|
74
115
|
operations.add(1, all);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
span
|
|
116
|
+
const endedAt = performance.now();
|
|
117
|
+
duration.record((endedAt - startedAt) / 1e3, all);
|
|
118
|
+
const finish = (span) => {
|
|
119
|
+
if (outcome === "error") span?.setStatus({ code: _btravstack_core.SPAN_STATUS.error });
|
|
120
|
+
span?.end(endedAt);
|
|
121
|
+
};
|
|
122
|
+
if (!deferred) finish(started);
|
|
123
|
+
else if (unitSpans.has(unit)) finish(open(startedAt));
|
|
124
|
+
else queueMicrotask(() => finish(open(startedAt)));
|
|
78
125
|
};
|
|
79
126
|
};
|
|
80
127
|
}
|
|
@@ -114,7 +161,9 @@ var UnitSpan = class extends (0, _btravstack_di.Port)("UnitSpan") {};
|
|
|
114
161
|
* and `onStop` ends it on every path out.
|
|
115
162
|
*
|
|
116
163
|
* The correlation is the ambient record's own, carried as attributes so a span
|
|
117
|
-
* joins the same query the logger's lines answer.
|
|
164
|
+
* joins the same query the logger's lines answer. Every operation observed
|
|
165
|
+
* inside the unit — a cache read, a stored object, a sent mail — is this
|
|
166
|
+
* span's CHILD and carries the same attributes. The remote PARENT is
|
|
118
167
|
* deliberately not reconstructed: `UnitMeta.traceId` carries the inbound trace
|
|
119
168
|
* id alone, so this correlates by attribute rather than pretending to a W3C
|
|
120
169
|
* parent-child edge it cannot prove.
|
|
@@ -129,14 +178,19 @@ const UnitSpanModule = (0, _btravstack_di.Module)("UnitSpan")({
|
|
|
129
178
|
sync: ({ tracer }) => {
|
|
130
179
|
const unit = (0, _btravstack_core.currentUnit)();
|
|
131
180
|
const span = tracer.startSpan("unit");
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"btravstack.trace_id": unit.traceId,
|
|
135
|
-
...unit.tenantId === void 0 ? {} : { "btravstack.tenant_id": unit.tenantId }
|
|
136
|
-
});
|
|
181
|
+
span.setAttributes(unitAttributes(unit));
|
|
182
|
+
if (unit !== void 0 && "spanContext" in span) unitSpans.set(unit, span);
|
|
137
183
|
return span;
|
|
138
184
|
},
|
|
139
|
-
onStop: (span) =>
|
|
185
|
+
onStop: (span) => {
|
|
186
|
+
const unit = (0, _btravstack_core.currentUnit)();
|
|
187
|
+
if (unit?.signal.aborted === true) span.setStatus({
|
|
188
|
+
code: _btravstack_core.SPAN_STATUS.error,
|
|
189
|
+
message: "aborted"
|
|
190
|
+
});
|
|
191
|
+
else if (unit !== void 0 && (0, _btravstack_core.unitOutcome)() !== "ok") span.setStatus({ code: _btravstack_core.SPAN_STATUS.error });
|
|
192
|
+
span.end();
|
|
193
|
+
}
|
|
140
194
|
})],
|
|
141
195
|
exports: [UnitSpan]
|
|
142
196
|
});
|
package/dist/otel.d.cts
CHANGED
|
@@ -39,7 +39,9 @@ export declare class UnitSpan extends UnitSpan_base<Span> {}
|
|
|
39
39
|
* and `onStop` ends it on every path out.
|
|
40
40
|
*
|
|
41
41
|
* The correlation is the ambient record's own, carried as attributes so a span
|
|
42
|
-
* joins the same query the logger's lines answer.
|
|
42
|
+
* joins the same query the logger's lines answer. Every operation observed
|
|
43
|
+
* inside the unit — a cache read, a stored object, a sent mail — is this
|
|
44
|
+
* span's CHILD and carries the same attributes. The remote PARENT is
|
|
43
45
|
* deliberately not reconstructed: `UnitMeta.traceId` carries the inbound trace
|
|
44
46
|
* id alone, so this correlates by attribute rather than pretending to a W3C
|
|
45
47
|
* parent-child edge it cannot prove.
|
package/dist/otel.d.mts
CHANGED
|
@@ -39,7 +39,9 @@ export declare class UnitSpan extends UnitSpan_base<Span> {}
|
|
|
39
39
|
* and `onStop` ends it on every path out.
|
|
40
40
|
*
|
|
41
41
|
* The correlation is the ambient record's own, carried as attributes so a span
|
|
42
|
-
* joins the same query the logger's lines answer.
|
|
42
|
+
* joins the same query the logger's lines answer. Every operation observed
|
|
43
|
+
* inside the unit — a cache read, a stored object, a sent mail — is this
|
|
44
|
+
* span's CHILD and carries the same attributes. The remote PARENT is
|
|
43
45
|
* deliberately not reconstructed: `UnitMeta.traceId` carries the inbound trace
|
|
44
46
|
* id alone, so this correlates by attribute rather than pretending to a W3C
|
|
45
47
|
* parent-child edge it cannot prove.
|
package/dist/otel.mjs
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
|
-
import { Instrumentations, Meter, Observers, SPAN_STATUS, Tracer, currentUnit } from "@btravstack/core";
|
|
1
|
+
import { Instrumentations, Meter, Observers, SPAN_STATUS, Tracer, currentUnit, unitOutcome } from "@btravstack/core";
|
|
2
2
|
import { Module, Port, Provider } from "@btravstack/di";
|
|
3
|
-
import { metrics, trace } from "@opentelemetry/api";
|
|
3
|
+
import { context, metrics, trace } from "@opentelemetry/api";
|
|
4
4
|
import { NodeSDK } from "@opentelemetry/sdk-node";
|
|
5
5
|
import { fromSafePromise } from "unthrown";
|
|
6
6
|
//#region src/otel.ts
|
|
7
7
|
var OtelSdk = class extends Port("OtelSdk") {};
|
|
8
8
|
/**
|
|
9
|
+
* OTel semantic conventions' duration buckets, in seconds. The SDK's default
|
|
10
|
+
* boundaries were drawn for milliseconds and would put every operation under a
|
|
11
|
+
* second into the first bucket.
|
|
12
|
+
*/
|
|
13
|
+
const SECONDS_BUCKETS = [
|
|
14
|
+
.005,
|
|
15
|
+
.01,
|
|
16
|
+
.025,
|
|
17
|
+
.05,
|
|
18
|
+
.075,
|
|
19
|
+
.1,
|
|
20
|
+
.25,
|
|
21
|
+
.5,
|
|
22
|
+
.75,
|
|
23
|
+
1,
|
|
24
|
+
2.5,
|
|
25
|
+
5,
|
|
26
|
+
7.5,
|
|
27
|
+
10
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Each open unit's span, by the record it was opened for — so an operation
|
|
31
|
+
* inside the unit parents on it while the observer still injects nothing.
|
|
32
|
+
* Weak, so an ended unit costs nothing once its record is gone.
|
|
33
|
+
*/
|
|
34
|
+
const unitSpans = /* @__PURE__ */ new WeakMap();
|
|
35
|
+
const unitAttributes = (unit) => unit === void 0 ? {} : {
|
|
36
|
+
"btravstack.unit_id": unit.unitId,
|
|
37
|
+
"btravstack.trace_id": unit.traceId,
|
|
38
|
+
...unit.tenantId === void 0 ? {} : { "btravstack.tenant_id": unit.tenantId }
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
9
41
|
* The OTel starter: a module providing `Tracer` and `Meter` over a `NodeSDK`
|
|
10
42
|
* opened with the scope and flushed on close. Compose it beside
|
|
11
43
|
* `observability()` in a root's `imports`.
|
|
@@ -53,16 +85,25 @@ const otel = (options) => Module("Otel")({
|
|
|
53
85
|
};
|
|
54
86
|
return ({ component, name, attributes, details, traced }) => {
|
|
55
87
|
const meter = metrics.getMeter("@btravstack/observability");
|
|
56
|
-
const
|
|
57
|
-
span?.setAttributes({
|
|
58
|
-
...attributes,
|
|
59
|
-
...details
|
|
60
|
-
});
|
|
88
|
+
const unit = currentUnit();
|
|
61
89
|
const startedAt = performance.now();
|
|
90
|
+
const open = (startTime) => {
|
|
91
|
+
const parent = unit === void 0 ? void 0 : unitSpans.get(unit);
|
|
92
|
+
const opened = tracer.startSpan(`${component}.${name}`, startTime === void 0 ? {} : { startTime }, parent === void 0 ? void 0 : trace.setSpan(context.active(), parent));
|
|
93
|
+
opened.setAttributes({
|
|
94
|
+
...attributes,
|
|
95
|
+
...details,
|
|
96
|
+
...unitAttributes(unit)
|
|
97
|
+
});
|
|
98
|
+
return opened;
|
|
99
|
+
};
|
|
100
|
+
const deferred = traced !== false && unit !== void 0 && !unitSpans.has(unit);
|
|
101
|
+
const started = traced === false || deferred ? void 0 : open();
|
|
62
102
|
const operations = instrument(counters, component, () => meter.createCounter(`btravstack.${component}.operations`, { description: `${component} operations, by operation and outcome` }));
|
|
63
103
|
const duration = instrument(durations, component, () => meter.createHistogram(`btravstack.${component}.duration`, {
|
|
64
104
|
description: `${component} operation duration`,
|
|
65
|
-
unit: "
|
|
105
|
+
unit: "s",
|
|
106
|
+
advice: { explicitBucketBoundaries: SECONDS_BUCKETS }
|
|
66
107
|
}));
|
|
67
108
|
return ({ outcome, attributes: settled }) => {
|
|
68
109
|
const all = {
|
|
@@ -71,9 +112,15 @@ const otel = (options) => Module("Otel")({
|
|
|
71
112
|
outcome
|
|
72
113
|
};
|
|
73
114
|
operations.add(1, all);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
span
|
|
115
|
+
const endedAt = performance.now();
|
|
116
|
+
duration.record((endedAt - startedAt) / 1e3, all);
|
|
117
|
+
const finish = (span) => {
|
|
118
|
+
if (outcome === "error") span?.setStatus({ code: SPAN_STATUS.error });
|
|
119
|
+
span?.end(endedAt);
|
|
120
|
+
};
|
|
121
|
+
if (!deferred) finish(started);
|
|
122
|
+
else if (unitSpans.has(unit)) finish(open(startedAt));
|
|
123
|
+
else queueMicrotask(() => finish(open(startedAt)));
|
|
77
124
|
};
|
|
78
125
|
};
|
|
79
126
|
}
|
|
@@ -113,7 +160,9 @@ var UnitSpan = class extends Port("UnitSpan") {};
|
|
|
113
160
|
* and `onStop` ends it on every path out.
|
|
114
161
|
*
|
|
115
162
|
* The correlation is the ambient record's own, carried as attributes so a span
|
|
116
|
-
* joins the same query the logger's lines answer.
|
|
163
|
+
* joins the same query the logger's lines answer. Every operation observed
|
|
164
|
+
* inside the unit — a cache read, a stored object, a sent mail — is this
|
|
165
|
+
* span's CHILD and carries the same attributes. The remote PARENT is
|
|
117
166
|
* deliberately not reconstructed: `UnitMeta.traceId` carries the inbound trace
|
|
118
167
|
* id alone, so this correlates by attribute rather than pretending to a W3C
|
|
119
168
|
* parent-child edge it cannot prove.
|
|
@@ -128,14 +177,19 @@ const UnitSpanModule = Module("UnitSpan")({
|
|
|
128
177
|
sync: ({ tracer }) => {
|
|
129
178
|
const unit = currentUnit();
|
|
130
179
|
const span = tracer.startSpan("unit");
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
"btravstack.trace_id": unit.traceId,
|
|
134
|
-
...unit.tenantId === void 0 ? {} : { "btravstack.tenant_id": unit.tenantId }
|
|
135
|
-
});
|
|
180
|
+
span.setAttributes(unitAttributes(unit));
|
|
181
|
+
if (unit !== void 0 && "spanContext" in span) unitSpans.set(unit, span);
|
|
136
182
|
return span;
|
|
137
183
|
},
|
|
138
|
-
onStop: (span) =>
|
|
184
|
+
onStop: (span) => {
|
|
185
|
+
const unit = currentUnit();
|
|
186
|
+
if (unit?.signal.aborted === true) span.setStatus({
|
|
187
|
+
code: SPAN_STATUS.error,
|
|
188
|
+
message: "aborted"
|
|
189
|
+
});
|
|
190
|
+
else if (unit !== void 0 && unitOutcome() !== "ok") span.setStatus({ code: SPAN_STATUS.error });
|
|
191
|
+
span.end();
|
|
192
|
+
}
|
|
139
193
|
})],
|
|
140
194
|
exports: [UnitSpan]
|
|
141
195
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@btravstack/observability",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Observability for @btravstack/core: a strict Logger port correlated with the kernel's units, a dependency-free JSON sink, and the lifecycle events as log lines",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dependency-injection",
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"./package.json": "./package.json"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
|
-
"@btravstack/config": "0.
|
|
69
|
-
"@btravstack/core": "0.
|
|
70
|
-
"@btravstack/di": "0.
|
|
71
|
-
"@btravstack/testing": "0.
|
|
68
|
+
"@btravstack/config": "0.19.0",
|
|
69
|
+
"@btravstack/core": "0.19.0",
|
|
70
|
+
"@btravstack/di": "0.19.0",
|
|
71
|
+
"@btravstack/testing": "0.19.0",
|
|
72
72
|
"@btravstack/tsconfig": "0.4.0",
|
|
73
73
|
"@opentelemetry/api": "1.9.1",
|
|
74
74
|
"@opentelemetry/sdk-node": "0.222.0",
|
|
@@ -83,9 +83,9 @@
|
|
|
83
83
|
"vitest": "5.0.3"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
|
-
"@btravstack/config": "^0.
|
|
87
|
-
"@btravstack/core": "^0.
|
|
88
|
-
"@btravstack/di": "^0.
|
|
86
|
+
"@btravstack/config": "^0.19.0",
|
|
87
|
+
"@btravstack/core": "^0.19.0",
|
|
88
|
+
"@btravstack/di": "^0.19.0",
|
|
89
89
|
"@opentelemetry/api": "^1.9.0",
|
|
90
90
|
"@opentelemetry/sdk-node": "^0.222.0",
|
|
91
91
|
"pino": "^10",
|