@xeonr/telemetry 0.0.0-stage → 0.2.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 +128 -2
- package/dist/baggage.d.ts +21 -0
- package/dist/baggage.d.ts.map +1 -0
- package/dist/baggage.js +117 -0
- package/dist/baggage.js.map +1 -0
- package/dist/boot.d.ts +7 -0
- package/dist/boot.d.ts.map +1 -0
- package/dist/boot.js +10 -0
- package/dist/boot.js.map +1 -0
- package/dist/connect.d.ts +63 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +200 -0
- package/dist/connect.js.map +1 -0
- package/dist/fetch.d.ts +19 -0
- package/dist/fetch.d.ts.map +1 -0
- package/dist/fetch.js +41 -0
- package/dist/fetch.js.map +1 -0
- package/dist/identity.d.ts +79 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +173 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/job.d.ts +38 -0
- package/dist/job.d.ts.map +1 -0
- package/dist/job.js +107 -0
- package/dist/job.js.map +1 -0
- package/dist/keys.d.ts +80 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +90 -0
- package/dist/keys.js.map +1 -0
- package/dist/keys.json +127 -0
- package/dist/log.d.ts +43 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +139 -0
- package/dist/log.js.map +1 -0
- package/dist/processor.d.ts +79 -0
- package/dist/processor.d.ts.map +1 -0
- package/dist/processor.js +163 -0
- package/dist/processor.js.map +1 -0
- package/dist/register.d.ts +2 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +34 -0
- package/dist/register.js.map +1 -0
- package/dist/sanitize.d.ts +17 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +52 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/sdk.d.ts +50 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +217 -0
- package/dist/sdk.js.map +1 -0
- package/package.json +97 -3
- package/register.cjs +3 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The span processors `init` installs, in order:
|
|
3
|
+
*
|
|
4
|
+
* 1. IdentitySpanProcessor stamps the request's identity on every span
|
|
5
|
+
* started under it (go-platform's telemetry.SpanProcessor).
|
|
6
|
+
* 2. SanitizeSpanProcessor strips queries and credentials from URL attributes
|
|
7
|
+
* and names client spans `HTTP <METHOD> <peer>` (§4.3). The http and undici
|
|
8
|
+
* spans already arrive without a query (@lntrn/otel's
|
|
9
|
+
* noQueryInstrumentationConfig, applied in `instrumentations()`); this is
|
|
10
|
+
* the backstop for every other span.
|
|
11
|
+
* 3. The processors a service adds at run time (`addSpanProcessor`), e.g. the
|
|
12
|
+
* upl-im pipeline's own trace store.
|
|
13
|
+
* 4. The OTLP batch processor, behind ExportGuard.
|
|
14
|
+
*/
|
|
15
|
+
import { context, createContextKey, metrics, SpanKind } from "@opentelemetry/api";
|
|
16
|
+
import { attributes, stateFrom } from "./identity.js";
|
|
17
|
+
import { SCOPE } from "./job.js";
|
|
18
|
+
import { stripTarget, stripUrl } from "./sanitize.js";
|
|
19
|
+
const PEER = createContextKey("xeonr.telemetry.peer");
|
|
20
|
+
/** `peer.service` (go-platform's KeyPeerService). */
|
|
21
|
+
export const KEY_PEER_SERVICE = "peer.service";
|
|
22
|
+
/** Runs `fn` with `peer` named on every HTTP client span it starts: `peer.service`, and the span name `HTTP GET <peer>`. */
|
|
23
|
+
export function withPeer(peer, fn, ctx = context.active()) {
|
|
24
|
+
return context.with(ctx.setValue(PEER, peer), fn);
|
|
25
|
+
}
|
|
26
|
+
/** @internal */
|
|
27
|
+
export function peerFrom(ctx) {
|
|
28
|
+
return ctx.getValue(PEER);
|
|
29
|
+
}
|
|
30
|
+
export class IdentitySpanProcessor {
|
|
31
|
+
onStart(span, parent) {
|
|
32
|
+
const s = stateFrom(parent);
|
|
33
|
+
if (s)
|
|
34
|
+
span.setAttributes(attributes(s.identity()));
|
|
35
|
+
}
|
|
36
|
+
onEnd() { }
|
|
37
|
+
forceFlush() {
|
|
38
|
+
return Promise.resolve();
|
|
39
|
+
}
|
|
40
|
+
shutdown() {
|
|
41
|
+
return Promise.resolve();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const URL_ATTRS = ["url.full", "http.url"];
|
|
45
|
+
const TARGET_ATTRS = ["http.target"];
|
|
46
|
+
const DROP_ATTRS = ["url.query"];
|
|
47
|
+
export class SanitizeSpanProcessor {
|
|
48
|
+
onStart(span, parent) {
|
|
49
|
+
const attrs = span.attributes;
|
|
50
|
+
for (const k of URL_ATTRS) {
|
|
51
|
+
const v = attrs[k];
|
|
52
|
+
if (typeof v === "string")
|
|
53
|
+
attrs[k] = stripUrl(v);
|
|
54
|
+
}
|
|
55
|
+
for (const k of TARGET_ATTRS) {
|
|
56
|
+
const v = attrs[k];
|
|
57
|
+
if (typeof v === "string")
|
|
58
|
+
attrs[k] = stripTarget(v);
|
|
59
|
+
}
|
|
60
|
+
for (const k of DROP_ATTRS)
|
|
61
|
+
delete attrs[k];
|
|
62
|
+
if (span.kind !== SpanKind.CLIENT)
|
|
63
|
+
return;
|
|
64
|
+
const method = attrs["http.request.method"] ?? attrs["http.method"];
|
|
65
|
+
if (typeof method !== "string")
|
|
66
|
+
return;
|
|
67
|
+
const peer = peerFrom(parent);
|
|
68
|
+
if (peer)
|
|
69
|
+
span.setAttribute(KEY_PEER_SERVICE, peer);
|
|
70
|
+
span.updateName(peer ? `HTTP ${method} ${peer}` : `HTTP ${method}`);
|
|
71
|
+
}
|
|
72
|
+
onEnd() { }
|
|
73
|
+
forceFlush() {
|
|
74
|
+
return Promise.resolve();
|
|
75
|
+
}
|
|
76
|
+
shutdown() {
|
|
77
|
+
return Promise.resolve();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Processors added after boot. A span sees the processors that were there when it started. */
|
|
81
|
+
export class DynamicSpanProcessor {
|
|
82
|
+
#processors = [];
|
|
83
|
+
#seen = new WeakMap();
|
|
84
|
+
add(p) {
|
|
85
|
+
this.#processors.push(p);
|
|
86
|
+
}
|
|
87
|
+
onStart(span, parent) {
|
|
88
|
+
if (this.#processors.length === 0)
|
|
89
|
+
return;
|
|
90
|
+
const ps = [...this.#processors];
|
|
91
|
+
this.#seen.set(span, ps);
|
|
92
|
+
for (const p of ps)
|
|
93
|
+
p.onStart(span, parent);
|
|
94
|
+
}
|
|
95
|
+
onEnd(span) {
|
|
96
|
+
for (const p of this.#seen.get(span) ?? [])
|
|
97
|
+
p.onEnd(span);
|
|
98
|
+
}
|
|
99
|
+
async forceFlush() {
|
|
100
|
+
await Promise.all(this.#processors.map((p) => p.forceFlush()));
|
|
101
|
+
}
|
|
102
|
+
async shutdown() {
|
|
103
|
+
await Promise.all(this.#processors.map((p) => p.shutdown()));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export const METRIC_ROOTLESS_CLIENT_SPANS = "xeonr_telemetry_rootless_client_spans_total";
|
|
107
|
+
/** The event attribute that keeps a span event out of the OTLP export. */
|
|
108
|
+
export const KEY_LOCAL_EVENT = "xeonr.local";
|
|
109
|
+
/**
|
|
110
|
+
* Attributes for a span event that only the service's own processors may see
|
|
111
|
+
* (`addSpanProcessor`), never the collector: payloads a product shows its own
|
|
112
|
+
* users (the upl-im pipeline's run traces), which §4.2 forbids in Lantern.
|
|
113
|
+
*
|
|
114
|
+
* ```ts
|
|
115
|
+
* span.addEvent("rpc.request", localEvent({ payload }));
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export function localEvent(attrs) {
|
|
119
|
+
return { ...attrs, [KEY_LOCAL_EVENT]: true };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Sits in front of the OTLP batch processor:
|
|
123
|
+
*
|
|
124
|
+
* - Keeps a client or producer span that has no parent out of the export
|
|
125
|
+
* (obs.rooted-spans: a database, cache or client span is never a trace
|
|
126
|
+
* root). Such a span means the work ran on a context with no span: a loop
|
|
127
|
+
* or callback not under `startJob`. The instrumentations that can refuse
|
|
128
|
+
* those spans already do (`requireParentSpan`); this catches the rest
|
|
129
|
+
* (mysql2, aws-sdk). Each one dropped is counted in
|
|
130
|
+
* xeonr_telemetry_rootless_client_spans_total{scope}, so the gap stays visible.
|
|
131
|
+
* - Drops `localEvent` events from what is exported.
|
|
132
|
+
*/
|
|
133
|
+
export class ExportGuard {
|
|
134
|
+
#inner;
|
|
135
|
+
constructor(inner) {
|
|
136
|
+
this.#inner = inner;
|
|
137
|
+
}
|
|
138
|
+
onStart(span, parent) {
|
|
139
|
+
this.#inner.onStart(span, parent);
|
|
140
|
+
}
|
|
141
|
+
onEnd(span) {
|
|
142
|
+
if ((span.kind === SpanKind.CLIENT || span.kind === SpanKind.PRODUCER) && !span.parentSpanContext) {
|
|
143
|
+
metrics
|
|
144
|
+
.getMeter(SCOPE)
|
|
145
|
+
.createCounter(METRIC_ROOTLESS_CLIENT_SPANS, { description: "Client and producer spans dropped because they had no parent." })
|
|
146
|
+
.add(1, { scope: span.instrumentationScope.name });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (span.events.some((e) => e.attributes?.[KEY_LOCAL_EVENT] === true)) {
|
|
150
|
+
const events = span.events.filter((e) => e.attributes?.[KEY_LOCAL_EVENT] !== true);
|
|
151
|
+
this.#inner.onEnd(Object.create(span, { events: { value: events } }));
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
this.#inner.onEnd(span);
|
|
155
|
+
}
|
|
156
|
+
forceFlush() {
|
|
157
|
+
return this.#inner.forceFlush();
|
|
158
|
+
}
|
|
159
|
+
shutdown() {
|
|
160
|
+
return this.#inner.shutdown();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//# sourceMappingURL=processor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"processor.js","sourceRoot":"","sources":["../src/processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,OAAO,EAAE,QAAQ,EAAgB,MAAM,oBAAoB,CAAC;AAEhG,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEtD,MAAM,IAAI,GAAG,gBAAgB,CAAC,sBAAsB,CAAC,CAAC;AAEtD,qDAAqD;AACrD,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAE/C,4HAA4H;AAC5H,MAAM,UAAU,QAAQ,CAAI,IAAY,EAAE,EAAW,EAAE,MAAe,OAAO,CAAC,MAAM,EAAE;IACrF,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,gBAAgB;AAChB,MAAM,UAAU,QAAQ,CAAC,GAAY;IACpC,OAAO,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAuB,CAAC;AACjD,CAAC;AAED,MAAM,OAAO,qBAAqB;IACjC,OAAO,CAAC,IAAU,EAAE,MAAe;QAClC,MAAM,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;QAC5B,IAAI,CAAC;YAAE,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IACrD,CAAC;IACD,KAAK,KAAU,CAAC;IAChB,UAAU;QACT,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IACD,QAAQ;QACP,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;CACD;AAED,MAAM,SAAS,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AAC3C,MAAM,YAAY,GAAG,CAAC,aAAa,CAAC,CAAC;AACrC,MAAM,UAAU,GAAG,CAAC,WAAW,CAAC,CAAC;AAEjC,MAAM,OAAO,qBAAqB;IACjC,OAAO,CAAC,IAAU,EAAE,MAAe;QAClC,MAAM,KAAK,GAAI,IAA2D,CAAC,UAAU,CAAC;QACtF,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YAC3B,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACnD,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;YAC9B,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;YACnB,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,KAAK,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,UAAU;YAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;QAE5C,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM;YAAE,OAAO;QAC1C,MAAM,MAAM,GAAG,KAAK,CAAC,qBAAqB,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC;QACpE,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO;QACvC,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,IAAI;YAAE,IAAI,CAAC,YAAY,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,MAAM,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,QAAQ,MAAM,EAAE,CAAC,CAAC;IACrE,CAAC;IACD,KAAK,KAAU,CAAC;IAChB,UAAU;QACT,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IACD,QAAQ;QACP,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;CACD;AAED,+FAA+F;AAC/F,MAAM,OAAO,oBAAoB;IACvB,WAAW,GAAoB,EAAE,CAAC;IAClC,KAAK,GAAG,IAAI,OAAO,EAAoC,CAAC;IAEjE,GAAG,CAAC,CAAgB;QACnB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,CAAC,IAAU,EAAE,MAAe;QAClC,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAC1C,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC;QACjC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,EAAE;YAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IACD,KAAK,CAAC,IAAkB;QACvB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC;IACD,KAAK,CAAC,UAAU;QACf,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAChE,CAAC;IACD,KAAK,CAAC,QAAQ;QACb,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;IAC9D,CAAC;CACD;AAED,MAAM,CAAC,MAAM,4BAA4B,GAAG,6CAA6C,CAAC;AAE1F,0EAA0E;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAG,aAAa,CAAC;AAE7C;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAoC,KAAQ;IACrE,OAAO,EAAE,GAAG,KAAK,EAAE,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,WAAW;IACd,MAAM,CAAgB;IAC/B,YAAY,KAAoB;QAC/B,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;IACrB,CAAC;IACD,OAAO,CAAC,IAAU,EAAE,MAAe;QAClC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IACD,KAAK,CAAC,IAAkB;QACvB,IAAI,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACnG,OAAO;iBACL,QAAQ,CAAC,KAAK,CAAC;iBACf,aAAa,CAAC,4BAA4B,EAAE,EAAE,WAAW,EAAE,+DAA+D,EAAE,CAAC;iBAC7H,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,oBAAoB,CAAC,IAAI,EAAE,CAAC,CAAC;YACpD,OAAO;QACR,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;YACvE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC,CAAC;YACnF,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,CAAiB,CAAC,CAAC;YACtF,OAAO;QACR,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IACD,UAAU;QACT,OAAO,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;IACjC,CAAC;IACD,QAAQ;QACP,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC/B,CAAC;CACD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":""}
|
package/dist/register.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `node --import @xeonr/telemetry/register app.js`
|
|
3
|
+
*
|
|
4
|
+
* Starts telemetry before the app loads (obs.node-telemetry):
|
|
5
|
+
* - registers OpenTelemetry's ES module hook, so the instrumentations can
|
|
6
|
+
* patch ES modules as they are imported;
|
|
7
|
+
* - calls `init()`, which reads OTEL_* and ENV;
|
|
8
|
+
* - flushes when the event loop empties. On SIGTERM/SIGINT it does nothing if
|
|
9
|
+
* the app handles the signal itself (the app calls `shutdown()` once it has
|
|
10
|
+
* drained, so the last requests' spans are kept); if not, it flushes and
|
|
11
|
+
* re-raises the signal, so the exit code is the usual one.
|
|
12
|
+
*
|
|
13
|
+
* `--require @xeonr/telemetry/register` loads the same module for a CommonJS
|
|
14
|
+
* app (Node ≥ 22.12 loads ES modules from require).
|
|
15
|
+
*
|
|
16
|
+
* Only the main thread starts telemetry: Node also runs preloads inside the
|
|
17
|
+
* module-hooks thread and in worker threads, which would start a second SDK.
|
|
18
|
+
*/
|
|
19
|
+
import { register } from "node:module";
|
|
20
|
+
import { isMainThread } from "node:worker_threads";
|
|
21
|
+
import { init, shutdown } from "./sdk.js";
|
|
22
|
+
if (isMainThread) {
|
|
23
|
+
register("@opentelemetry/instrumentation/hook.mjs", import.meta.url);
|
|
24
|
+
init();
|
|
25
|
+
process.once("beforeExit", () => void shutdown());
|
|
26
|
+
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
27
|
+
process.once(signal, () => {
|
|
28
|
+
if (process.listenerCount(signal) > 0)
|
|
29
|
+
return;
|
|
30
|
+
void shutdown().finally(() => process.kill(process.pid, signal));
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=register.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.js","sourceRoot":"","sources":["../src/register.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAE1C,IAAI,YAAY,EAAE,CAAC;IAClB,QAAQ,CAAC,yCAAyC,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrE,IAAI,EAAE,CAAC;IAEP,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;IAClD,KAAK,MAAM,MAAM,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;QACrD,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE;YACzB,IAAI,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC;gBAAE,OAAO;YAC9C,KAAK,QAAQ,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;IACJ,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What never reaches a span (observability plan §4.2 "never recorded";
|
|
3
|
+
* obs.no-pii, log.no-secrets): query strings, credentials in URLs, tokens and
|
|
4
|
+
* emails in error texts.
|
|
5
|
+
*/
|
|
6
|
+
/** Redacts tokens, credentials, emails and URL queries from a message, and caps it at 1 KiB. */
|
|
7
|
+
export declare function sanitize(msg: string): string;
|
|
8
|
+
/** An error's type and sanitised message, for an exception event or a status. */
|
|
9
|
+
export declare function sanitizeError(err: unknown): {
|
|
10
|
+
type: string;
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
/** A URL without credentials, query or fragment; "" if it does not parse. */
|
|
14
|
+
export declare function stripUrl(raw: string): string;
|
|
15
|
+
/** A path or target ("/a/b?x=1") without its query or fragment. */
|
|
16
|
+
export declare function stripTarget(raw: string): string;
|
|
17
|
+
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,gGAAgG;AAChG,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAS5C;AAED,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,GAAG,EAAE,OAAO,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAG7E;AAED,6EAA6E;AAC7E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAY5C;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG/C"}
|
package/dist/sanitize.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What never reaches a span (observability plan §4.2 "never recorded";
|
|
3
|
+
* obs.no-pii, log.no-secrets): query strings, credentials in URLs, tokens and
|
|
4
|
+
* emails in error texts.
|
|
5
|
+
*/
|
|
6
|
+
const MAX_MESSAGE = 1024;
|
|
7
|
+
// go-platform's telemetry/sanitize.go, rule for rule.
|
|
8
|
+
const JWT = /eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]*/g;
|
|
9
|
+
const BEARER = /\b(bearer|basic)\s+[A-Za-z0-9._~+/=-]+/gi;
|
|
10
|
+
const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
|
|
11
|
+
const SECRET = /\b(password|passwd|secret|token|api[_-]?key|client_secret|assertion|code)=([^\s&"']+)/gi;
|
|
12
|
+
// URLs lose their query and fragment: presigned URLs carry credentials there.
|
|
13
|
+
const URL_QUERY = /\b(https?:\/\/[^\s?#"']+)[?#][^\s"']*/gi;
|
|
14
|
+
/** Redacts tokens, credentials, emails and URL queries from a message, and caps it at 1 KiB. */
|
|
15
|
+
export function sanitize(msg) {
|
|
16
|
+
let out = msg
|
|
17
|
+
.replace(JWT, "[REDACTED]")
|
|
18
|
+
.replace(BEARER, "$1 [REDACTED]")
|
|
19
|
+
.replace(EMAIL, "[REDACTED]")
|
|
20
|
+
.replace(SECRET, "$1=[REDACTED]")
|
|
21
|
+
.replace(URL_QUERY, "$1");
|
|
22
|
+
if (out.length > MAX_MESSAGE)
|
|
23
|
+
out = out.slice(0, MAX_MESSAGE) + "…";
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
/** An error's type and sanitised message, for an exception event or a status. */
|
|
27
|
+
export function sanitizeError(err) {
|
|
28
|
+
if (err instanceof Error)
|
|
29
|
+
return { type: err.name || "Error", message: sanitize(err.message) };
|
|
30
|
+
return { type: typeof err, message: sanitize(String(err)) };
|
|
31
|
+
}
|
|
32
|
+
/** A URL without credentials, query or fragment; "" if it does not parse. */
|
|
33
|
+
export function stripUrl(raw) {
|
|
34
|
+
try {
|
|
35
|
+
const u = new URL(raw);
|
|
36
|
+
u.username = "";
|
|
37
|
+
u.password = "";
|
|
38
|
+
u.search = "";
|
|
39
|
+
u.hash = "";
|
|
40
|
+
return u.toString();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
const cut = raw.search(/[?#]/);
|
|
44
|
+
return cut < 0 ? raw : raw.slice(0, cut);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** A path or target ("/a/b?x=1") without its query or fragment. */
|
|
48
|
+
export function stripTarget(raw) {
|
|
49
|
+
const cut = raw.search(/[?#]/);
|
|
50
|
+
return cut < 0 ? raw : raw.slice(0, cut);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,GAAG,IAAI,CAAC;AAEzB,sDAAsD;AACtD,MAAM,GAAG,GAAG,0DAA0D,CAAC;AACvE,MAAM,MAAM,GAAG,0CAA0C,CAAC;AAC1D,MAAM,KAAK,GAAG,iDAAiD,CAAC;AAChE,MAAM,MAAM,GAAG,yFAAyF,CAAC;AACzG,8EAA8E;AAC9E,MAAM,SAAS,GAAG,yCAAyC,CAAC;AAE5D,gGAAgG;AAChG,MAAM,UAAU,QAAQ,CAAC,GAAW;IACnC,IAAI,GAAG,GAAG,GAAG;SACX,OAAO,CAAC,GAAG,EAAE,YAAY,CAAC;SAC1B,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC;SAChC,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC;SAC5B,OAAO,CAAC,MAAM,EAAE,eAAe,CAAC;SAChC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC3B,IAAI,GAAG,CAAC,MAAM,GAAG,WAAW;QAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,WAAW,CAAC,GAAG,GAAG,CAAC;IACpE,OAAO,GAAG,CAAC;AACZ,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,aAAa,CAAC,GAAY;IACzC,IAAI,GAAG,YAAY,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/F,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,QAAQ,CAAC,GAAW;IACnC,IAAI,CAAC;QACJ,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC;QAChB,CAAC,CAAC,QAAQ,GAAG,EAAE,CAAC;QAChB,CAAC,CAAC,MAAM,GAAG,EAAE,CAAC;QACd,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;QACZ,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACR,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC/B,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;AACF,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,WAAW,CAAC,GAAW;IACtC,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AAC1C,CAAC"}
|
package/dist/sdk.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Instrumentation } from "@opentelemetry/instrumentation";
|
|
2
|
+
import { type SpanProcessor } from "@opentelemetry/sdk-trace";
|
|
3
|
+
type Env = Record<string, string | undefined>;
|
|
4
|
+
export interface InitOptions {
|
|
5
|
+
/** `service.name` when OTEL_SERVICE_NAME is unset. */
|
|
6
|
+
serviceName?: string;
|
|
7
|
+
/** `service.namespace` when OTEL_SERVICE_NAMESPACE is unset: the owning Lantern sub-team. */
|
|
8
|
+
namespace?: string;
|
|
9
|
+
/** More instrumentations, appended to the fixed list. */
|
|
10
|
+
instrumentations?: Instrumentation[];
|
|
11
|
+
/** The environment to read. Default `process.env`. */
|
|
12
|
+
env?: Env;
|
|
13
|
+
}
|
|
14
|
+
export interface Endpoint {
|
|
15
|
+
/** What was configured, for the boot line. */
|
|
16
|
+
configured: string;
|
|
17
|
+
/** The URL handed to the gRPC exporters: its scheme decides cleartext or TLS. */
|
|
18
|
+
url: string;
|
|
19
|
+
transport: "cleartext" | "tls";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The OTLP endpoint, as go-platform's otel.Init reads it: a Go-style
|
|
23
|
+
* `host:port` or a URL. An in-cluster host, or OTEL_EXPORTER_OTLP_INSECURE=true,
|
|
24
|
+
* is cleartext (the platform collector only speaks cleartext); anything else
|
|
25
|
+
* is TLS. Undefined when OTEL_EXPORTER_OTLP_ENDPOINT is unset.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveEndpoint(env?: Env): Endpoint | undefined;
|
|
28
|
+
/** `deployment.environment`: ENV, as the Go services read it, else NODE_ENV in the fleet's spelling. */
|
|
29
|
+
export declare function environment(env?: Env): string | undefined;
|
|
30
|
+
/** The fixed instrumentation list (never auto-instrumentations-node). */
|
|
31
|
+
export declare function instrumentations(): Instrumentation[];
|
|
32
|
+
/**
|
|
33
|
+
* Starts telemetry once per process; later calls are no-ops. `register`
|
|
34
|
+
* calls it before the app loads, which is the only way to call it in a
|
|
35
|
+
* service: instrumentations patch modules as they are first imported.
|
|
36
|
+
*/
|
|
37
|
+
export declare function init(opts?: InitOptions): void;
|
|
38
|
+
/**
|
|
39
|
+
* Adds a span processor after boot (the upl-im pipeline's run-trace store).
|
|
40
|
+
* It sees spans started from now on. Throws before `init`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function addSpanProcessor(p: SpanProcessor): void;
|
|
43
|
+
/** Exports what is buffered, without stopping. For CLIs and batch runs before they exit. */
|
|
44
|
+
export declare function forceFlush(): Promise<void>;
|
|
45
|
+
/** Flushes and stops every provider. Call it last, after the server and workers have drained. Safe to call twice. */
|
|
46
|
+
export declare function shutdown(): Promise<void>;
|
|
47
|
+
/** Whether `init` has run (and so `register` was loaded). */
|
|
48
|
+
export declare function initialised(): boolean;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAUtE,OAAO,EAAuC,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAKnG,KAAK,GAAG,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAM9C,MAAM,WAAW,WAAW;IAC3B,sDAAsD;IACtD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6FAA6F;IAC7F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,gBAAgB,CAAC,EAAE,eAAe,EAAE,CAAC;IACrC,sDAAsD;IACtD,GAAG,CAAC,EAAE,GAAG,CAAC;CACV;AAED,MAAM,WAAW,QAAQ;IACxB,8CAA8C;IAC9C,UAAU,EAAE,MAAM,CAAC;IACnB,iFAAiF;IACjF,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,WAAW,GAAG,KAAK,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,GAAG,GAAE,GAAiB,GAAG,QAAQ,GAAG,SAAS,CAW5E;AAED,wGAAwG;AACxG,wBAAgB,WAAW,CAAC,GAAG,GAAE,GAAiB,GAAG,MAAM,GAAG,SAAS,CAOtE;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,IAAI,eAAe,EAAE,CA+BpD;AAaD;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,IAAI,GAAE,WAAgB,GAAG,IAAI,CAgFjD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,aAAa,GAAG,IAAI,CAGvD;AAED,4FAA4F;AAC5F,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAOhD;AAED,qHAAqH;AACrH,wBAAgB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAGxC;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,IAAI,OAAO,CAErC"}
|
package/dist/sdk.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `init()`: the one telemetry bootstrap for a Xeonr Node service
|
|
3
|
+
* (obs.node-telemetry), mirroring go-platform's otel.Init: same env vars,
|
|
4
|
+
* no-op on an empty endpoint with one Info line, one `telemetry initialised`
|
|
5
|
+
* line otherwise, shut down last.
|
|
6
|
+
*
|
|
7
|
+
* The OpenTelemetry part is `@lntrn/otel`'s `setup()`, Lantern's add-on for
|
|
8
|
+
* the stock NodeSDK. What is Xeonr's is chosen here: OTLP gRPC to the cluster
|
|
9
|
+
* collector (Lantern's own edge is OTLP/HTTP only), AlwaysOn, W3C trace
|
|
10
|
+
* context as the only propagator (baggage is sent only by this package's
|
|
11
|
+
* in-cluster helpers), the fixed instrumentation list, no log export (logs go
|
|
12
|
+
* to stdout, where the Lantern agent collects them), no HTTP RED metrics
|
|
13
|
+
* (spanmetrics derives them), and the identity and sanitising processors.
|
|
14
|
+
*/
|
|
15
|
+
import { hostname } from "node:os";
|
|
16
|
+
import { noQueryInstrumentationConfig, setup } from "@lntrn/otel";
|
|
17
|
+
import { metrics, trace } from "@opentelemetry/api";
|
|
18
|
+
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
19
|
+
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-grpc";
|
|
20
|
+
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-grpc";
|
|
21
|
+
import { HostMetrics } from "@opentelemetry/host-metrics";
|
|
22
|
+
import { AwsInstrumentation } from "@opentelemetry/instrumentation-aws-sdk";
|
|
23
|
+
import { HttpInstrumentation } from "@opentelemetry/instrumentation-http";
|
|
24
|
+
import { IORedisInstrumentation } from "@opentelemetry/instrumentation-ioredis";
|
|
25
|
+
import { MySQL2Instrumentation } from "@opentelemetry/instrumentation-mysql2";
|
|
26
|
+
import { PgInstrumentation } from "@opentelemetry/instrumentation-pg";
|
|
27
|
+
import { PinoInstrumentation } from "@opentelemetry/instrumentation-pino";
|
|
28
|
+
import { RuntimeNodeInstrumentation } from "@opentelemetry/instrumentation-runtime-node";
|
|
29
|
+
import { UndiciInstrumentation } from "@opentelemetry/instrumentation-undici";
|
|
30
|
+
import { AggregationType, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
31
|
+
import { AlwaysOnSampler, BatchSpanProcessor } from "@opentelemetry/sdk-trace";
|
|
32
|
+
import { bootLine } from "./boot.js";
|
|
33
|
+
import { SCOPE } from "./job.js";
|
|
34
|
+
import { DynamicSpanProcessor, IdentitySpanProcessor, ExportGuard, SanitizeSpanProcessor } from "./processor.js";
|
|
35
|
+
/** Bounds one export so a dead collector never stalls a request or the shutdown for long. */
|
|
36
|
+
const EXPORT_TIMEOUT_MS = 3000;
|
|
37
|
+
const DEFAULT_METRIC_INTERVAL_MS = 10_000;
|
|
38
|
+
/**
|
|
39
|
+
* The OTLP endpoint, as go-platform's otel.Init reads it: a Go-style
|
|
40
|
+
* `host:port` or a URL. An in-cluster host, or OTEL_EXPORTER_OTLP_INSECURE=true,
|
|
41
|
+
* is cleartext (the platform collector only speaks cleartext); anything else
|
|
42
|
+
* is TLS. Undefined when OTEL_EXPORTER_OTLP_ENDPOINT is unset.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveEndpoint(env = process.env) {
|
|
45
|
+
const configured = (env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || env.OTEL_EXPORTER_OTLP_ENDPOINT)?.trim();
|
|
46
|
+
if (!configured)
|
|
47
|
+
return undefined;
|
|
48
|
+
const scheme = /^([a-z][a-z0-9+.-]*):\/\//i.exec(configured)?.[1]?.toLowerCase();
|
|
49
|
+
if (scheme)
|
|
50
|
+
return { configured, url: configured, transport: scheme === "http" ? "cleartext" : "tls" };
|
|
51
|
+
const host = configured.split(":")[0]?.toLowerCase() ?? "";
|
|
52
|
+
const inClusterHost = host.endsWith(".svc") || host.endsWith(".svc.cluster.local") || (!host.includes(".") && host !== "localhost");
|
|
53
|
+
if (env.OTEL_EXPORTER_OTLP_INSECURE?.trim().toLowerCase() === "true" || inClusterHost) {
|
|
54
|
+
return { configured, url: `http://${configured}`, transport: "cleartext" };
|
|
55
|
+
}
|
|
56
|
+
return { configured, url: `https://${configured}`, transport: "tls" };
|
|
57
|
+
}
|
|
58
|
+
/** `deployment.environment`: ENV, as the Go services read it, else NODE_ENV in the fleet's spelling. */
|
|
59
|
+
export function environment(env = process.env) {
|
|
60
|
+
const e = env.ENV?.trim();
|
|
61
|
+
if (e)
|
|
62
|
+
return e;
|
|
63
|
+
const n = env.NODE_ENV?.trim().toLowerCase();
|
|
64
|
+
if (n === "production")
|
|
65
|
+
return "prod";
|
|
66
|
+
if (n === "development")
|
|
67
|
+
return "dev";
|
|
68
|
+
return n || undefined;
|
|
69
|
+
}
|
|
70
|
+
/** The fixed instrumentation list (never auto-instrumentations-node). */
|
|
71
|
+
export function instrumentations() {
|
|
72
|
+
// @lntrn/otel's hooks keep the query off http and undici spans at the
|
|
73
|
+
// source (obs.no-pii): no `url.query`, `url.full` as origin + path, no
|
|
74
|
+
// legacy `http.target`/`http.url`. SanitizeSpanProcessor stays behind them
|
|
75
|
+
// for every other span (aws-sdk, a service's own) and for credentials and
|
|
76
|
+
// fragments in URLs.
|
|
77
|
+
const noQuery = noQueryInstrumentationConfig();
|
|
78
|
+
const http = noQuery["@opentelemetry/instrumentation-http"];
|
|
79
|
+
return [
|
|
80
|
+
new HttpInstrumentation({
|
|
81
|
+
startIncomingSpanHook: http.startIncomingSpanHook,
|
|
82
|
+
// RequestOptions' fields may be undefined; the hook reads them as optional.
|
|
83
|
+
startOutgoingSpanHook: (options) => http.startOutgoingSpanHook(options),
|
|
84
|
+
// Kubelet probes are not traffic.
|
|
85
|
+
ignoreIncomingRequestHook: (req) => (req.url ?? "").split("?")[0] === "/healthz",
|
|
86
|
+
requireParentforOutgoingSpans: true,
|
|
87
|
+
}),
|
|
88
|
+
new UndiciInstrumentation({ ...noQuery["@opentelemetry/instrumentation-undici"], requireParentforSpans: true }),
|
|
89
|
+
new IORedisInstrumentation({
|
|
90
|
+
requireParentSpan: true,
|
|
91
|
+
// The command only: arguments are keys and values.
|
|
92
|
+
dbStatementSerializer: (cmd) => cmd,
|
|
93
|
+
}),
|
|
94
|
+
new MySQL2Instrumentation({ maskStatement: true }),
|
|
95
|
+
new PgInstrumentation({ requireParentSpan: true, enhancedDatabaseReporting: false }),
|
|
96
|
+
new AwsInstrumentation({ suppressInternalInstrumentation: true }),
|
|
97
|
+
new RuntimeNodeInstrumentation(),
|
|
98
|
+
// trace_id/span_id on any pino logger the service makes; never log export.
|
|
99
|
+
new PinoInstrumentation({ disableLogSending: true }),
|
|
100
|
+
];
|
|
101
|
+
}
|
|
102
|
+
/** otelhttp-style RED histograms are off: spanmetrics derives RED from the spans (§5.7). */
|
|
103
|
+
const VIEWS = [
|
|
104
|
+
"http.server.request.duration",
|
|
105
|
+
"http.client.request.duration",
|
|
106
|
+
"http.server.duration",
|
|
107
|
+
"http.client.duration",
|
|
108
|
+
].map((instrumentName) => ({ instrumentName, aggregation: { type: AggregationType.DROP } }));
|
|
109
|
+
let started;
|
|
110
|
+
let stopping;
|
|
111
|
+
/**
|
|
112
|
+
* Starts telemetry once per process; later calls are no-ops. `register`
|
|
113
|
+
* calls it before the app loads, which is the only way to call it in a
|
|
114
|
+
* service: instrumentations patch modules as they are first imported.
|
|
115
|
+
*/
|
|
116
|
+
export function init(opts = {}) {
|
|
117
|
+
if (started)
|
|
118
|
+
return;
|
|
119
|
+
const env = opts.env ?? process.env;
|
|
120
|
+
const endpoint = env.OTEL_SDK_DISABLED?.trim().toLowerCase() === "true" ? undefined : resolveEndpoint(env);
|
|
121
|
+
const serviceName = env.OTEL_SERVICE_NAME?.trim() || opts.serviceName;
|
|
122
|
+
const namespace = env.OTEL_SERVICE_NAMESPACE?.trim() || opts.namespace;
|
|
123
|
+
const serviceVersion = env.OTEL_SERVICE_VERSION?.trim();
|
|
124
|
+
const deployment = environment(env);
|
|
125
|
+
const metricInterval = Number(env.OTEL_METRIC_EXPORT_INTERVAL) || DEFAULT_METRIC_INTERVAL_MS;
|
|
126
|
+
const dynamic = new DynamicSpanProcessor();
|
|
127
|
+
const spanProcessors = [new IdentitySpanProcessor(), new SanitizeSpanProcessor(), dynamic];
|
|
128
|
+
if (endpoint) {
|
|
129
|
+
spanProcessors.push(new ExportGuard(new BatchSpanProcessor({
|
|
130
|
+
exporter: new OTLPTraceExporter({ url: endpoint.url, timeoutMillis: EXPORT_TIMEOUT_MS }),
|
|
131
|
+
exportTimeoutMillis: EXPORT_TIMEOUT_MS,
|
|
132
|
+
})));
|
|
133
|
+
}
|
|
134
|
+
const attributes = { "service.instance.id": env.SERVICE_INSTANCE_ID?.trim() || hostname() };
|
|
135
|
+
if (deployment)
|
|
136
|
+
attributes["deployment.environment"] = deployment;
|
|
137
|
+
const lantern = setup({
|
|
138
|
+
// No endpoint → `none`: providers with no exporter, so spans still
|
|
139
|
+
// get ids and log lines still carry them. With one, the exporters
|
|
140
|
+
// are this package's gRPC ones, given below.
|
|
141
|
+
target: endpoint ? "otel" : "none",
|
|
142
|
+
...(serviceName ? { serviceName } : {}),
|
|
143
|
+
...(serviceVersion ? { serviceVersion } : {}),
|
|
144
|
+
...(namespace ? { namespace } : {}),
|
|
145
|
+
...(deployment ? { environment: deployment } : {}),
|
|
146
|
+
attributes,
|
|
147
|
+
instrumentations: [...instrumentations(), ...(opts.instrumentations ?? [])],
|
|
148
|
+
sdk: {
|
|
149
|
+
sampler: new AlwaysOnSampler(),
|
|
150
|
+
textMapPropagator: new W3CTraceContextPropagator(),
|
|
151
|
+
spanProcessors,
|
|
152
|
+
metricReaders: endpoint
|
|
153
|
+
? [
|
|
154
|
+
new PeriodicExportingMetricReader({
|
|
155
|
+
exporter: new OTLPMetricExporter({ url: endpoint.url, timeoutMillis: EXPORT_TIMEOUT_MS }),
|
|
156
|
+
exportIntervalMillis: metricInterval,
|
|
157
|
+
exportTimeoutMillis: EXPORT_TIMEOUT_MS,
|
|
158
|
+
}),
|
|
159
|
+
]
|
|
160
|
+
: [],
|
|
161
|
+
views: VIEWS,
|
|
162
|
+
logRecordProcessors: [],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
// setup() must not pick a Lantern edge or device target from stray LNTRN_* vars.
|
|
166
|
+
{ ...env, LNTRN_TARGET: undefined });
|
|
167
|
+
started = { lantern, dynamic };
|
|
168
|
+
if (endpoint) {
|
|
169
|
+
// Process CPU and memory beside runtime-node's event loop and heap
|
|
170
|
+
// metrics. process.* only: system.* would report the node, not the pod.
|
|
171
|
+
new HostMetrics({ meterProvider: metrics.getMeterProvider(), name: SCOPE, metricGroups: ["process.cpu", "process.memory"] }).start();
|
|
172
|
+
}
|
|
173
|
+
if (!endpoint) {
|
|
174
|
+
bootLine("info", "telemetry export disabled", { reason: "OTEL_EXPORTER_OTLP_ENDPOINT is not set" });
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
bootLine("info", "telemetry initialised", {
|
|
178
|
+
endpoint: endpoint.configured,
|
|
179
|
+
transport: endpoint.transport,
|
|
180
|
+
protocol: "grpc",
|
|
181
|
+
sampler: "always_on",
|
|
182
|
+
metric_interval_ms: metricInterval,
|
|
183
|
+
"service.name": serviceName,
|
|
184
|
+
"service.namespace": namespace,
|
|
185
|
+
"service.version": serviceVersion,
|
|
186
|
+
"deployment.environment": deployment,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Adds a span processor after boot (the upl-im pipeline's run-trace store).
|
|
191
|
+
* It sees spans started from now on. Throws before `init`.
|
|
192
|
+
*/
|
|
193
|
+
export function addSpanProcessor(p) {
|
|
194
|
+
if (!started)
|
|
195
|
+
throw new Error("@xeonr/telemetry: addSpanProcessor before init: start the process with --import @xeonr/telemetry/register");
|
|
196
|
+
started.dynamic.add(p);
|
|
197
|
+
}
|
|
198
|
+
/** Exports what is buffered, without stopping. For CLIs and batch runs before they exit. */
|
|
199
|
+
export async function forceFlush() {
|
|
200
|
+
if (!started)
|
|
201
|
+
return;
|
|
202
|
+
const tracerProvider = trace.getTracerProvider();
|
|
203
|
+
const tp = tracerProvider.getDelegate?.() ?? tracerProvider;
|
|
204
|
+
const mp = metrics.getMeterProvider();
|
|
205
|
+
await Promise.allSettled([tp.forceFlush?.(), mp.forceFlush?.()]);
|
|
206
|
+
}
|
|
207
|
+
/** Flushes and stops every provider. Call it last, after the server and workers have drained. Safe to call twice. */
|
|
208
|
+
export function shutdown() {
|
|
209
|
+
if (!started)
|
|
210
|
+
return Promise.resolve();
|
|
211
|
+
return (stopping ??= started.lantern.shutdown());
|
|
212
|
+
}
|
|
213
|
+
/** Whether `init` has run (and so `register` was loaded). */
|
|
214
|
+
export function initialised() {
|
|
215
|
+
return started !== undefined;
|
|
216
|
+
}
|
|
217
|
+
//# sourceMappingURL=sdk.js.map
|
package/dist/sdk.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk.js","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"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"}
|