@fougere/observability 0.3.0-alpha.0 → 0.5.0-alpha.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 +29 -0
- package/dist/Beat.d.ts +23 -0
- package/dist/Beat.d.ts.map +1 -0
- package/dist/Beat.js +35 -0
- package/dist/Beat.js.map +1 -0
- package/dist/Endpoint.d.ts +15 -0
- package/dist/Endpoint.d.ts.map +1 -0
- package/dist/Endpoint.js +33 -0
- package/dist/Endpoint.js.map +1 -0
- package/dist/extension.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +8 -19
- package/dist/logs.js.map +1 -1
- package/dist/metrics.js.map +1 -1
- package/dist/otlp.d.ts +0 -9
- package/dist/otlp.d.ts.map +1 -1
- package/dist/otlp.js +17 -31
- package/dist/otlp.js.map +1 -1
- package/package.json +8 -7
- package/src/Beat.ts +34 -0
- package/src/Endpoint.ts +30 -0
- package/src/context/als.ts +15 -0
- package/src/context/none.ts +21 -0
- package/src/context/port.ts +26 -0
- package/src/extension.ts +113 -0
- package/src/index.ts +223 -0
- package/src/logs.ts +136 -0
- package/src/metrics.ts +0 -0
- package/src/otlp.ts +108 -0
- package/src/traceparent.ts +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# @fougere/observability
|
|
2
|
+
> One span per operation, and the four signals derived from it
|
|
3
|
+
Optional in the strong sense: core holds no tracing code at all, only a `trace` field on
|
|
4
|
+
the invocation that it carries and never reads. An app that does not install this pays
|
|
5
|
+
nothing.
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
extensions: [observability({ service: 'blog', otlp: 'http://localhost:4318' })],
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The parent rides the **invocation**, not a header — so the trace crosses whatever the
|
|
12
|
+
topology became, and the difference between the two spans of a split call is what the wire
|
|
13
|
+
cost. Rate, errors and duration are one metric (a histogram of durations, dimensioned by
|
|
14
|
+
the op and by its verdict); saturation is counted by the middleware. OTLP over HTTP in the
|
|
15
|
+
JSON encoding — no protobuf, no dependency.
|
|
16
|
+
|
|
17
|
+
`rpc.topology` is declared here, not by core: an app that never installed this refuses the
|
|
18
|
+
op by name, which is the whole degradation a reader needs.
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add @fougere/observability
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
Part of [Fougere](https://github.com/chok/fougere) — one schema, a gradient from
|
|
28
|
+
monolith to distributed, the same user code.
|
|
29
|
+
Reference documentation: [the site](https://fougere.dev/) (en/fr).
|
package/dist/Beat.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How often a batch leaves, and how the beat stops.
|
|
3
|
+
*
|
|
4
|
+
* `flushMs: 0` means "nobody is on a timer here, I will say when" — and on a Worker it is
|
|
5
|
+
* not a preference, it is the only legal form: Cloudflare REFUSES a deployment whose
|
|
6
|
+
* module scope sets a timeout ("Disallowed operation called within global scope"), and an
|
|
7
|
+
* app built at module scope builds its exporter there. Measured 2026-08-23, the deploy
|
|
8
|
+
* failed with error 10021. The isolate is frozen at the response anyway, so the timer
|
|
9
|
+
* could never have fired; `ctx.waitUntil(flushTelemetry())` is what sends.
|
|
10
|
+
*/
|
|
11
|
+
export declare class Beat {
|
|
12
|
+
private readonly timer;
|
|
13
|
+
private readonly run;
|
|
14
|
+
private constructor();
|
|
15
|
+
/**
|
|
16
|
+
* `unref` so a buffered record never keeps a process alive: exporting is something the
|
|
17
|
+
* process does on its way, never a reason for it to stay.
|
|
18
|
+
*/
|
|
19
|
+
static every(ms: number | undefined, run: () => Promise<void>): Beat;
|
|
20
|
+
/** Stop the timer, then send what is left — the order a process about to exit needs. */
|
|
21
|
+
stop(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=Beat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Beat.d.ts","sourceRoot":"","sources":["../src/Beat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,qBAAa,IAAI;IAEb,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAFtB,OAAO,eAGH;IAEJ;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAMnE;IAED,wFAAwF;IAClF,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1B;CACF"}
|
package/dist/Beat.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How often a batch leaves, and how the beat stops.
|
|
3
|
+
*
|
|
4
|
+
* `flushMs: 0` means "nobody is on a timer here, I will say when" — and on a Worker it is
|
|
5
|
+
* not a preference, it is the only legal form: Cloudflare REFUSES a deployment whose
|
|
6
|
+
* module scope sets a timeout ("Disallowed operation called within global scope"), and an
|
|
7
|
+
* app built at module scope builds its exporter there. Measured 2026-08-23, the deploy
|
|
8
|
+
* failed with error 10021. The isolate is frozen at the response anyway, so the timer
|
|
9
|
+
* could never have fired; `ctx.waitUntil(flushTelemetry())` is what sends.
|
|
10
|
+
*/
|
|
11
|
+
export class Beat {
|
|
12
|
+
timer;
|
|
13
|
+
run;
|
|
14
|
+
constructor(timer, run) {
|
|
15
|
+
this.timer = timer;
|
|
16
|
+
this.run = run;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `unref` so a buffered record never keeps a process alive: exporting is something the
|
|
20
|
+
* process does on its way, never a reason for it to stay.
|
|
21
|
+
*/
|
|
22
|
+
static every(ms, run) {
|
|
23
|
+
const every = ms ?? 1_000;
|
|
24
|
+
const timer = every > 0 ? setInterval(() => void run(), every) : undefined;
|
|
25
|
+
timer?.unref?.();
|
|
26
|
+
return new Beat(timer, run);
|
|
27
|
+
}
|
|
28
|
+
/** Stop the timer, then send what is left — the order a process about to exit needs. */
|
|
29
|
+
async stop() {
|
|
30
|
+
if (this.timer)
|
|
31
|
+
clearInterval(this.timer);
|
|
32
|
+
await this.run();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=Beat.js.map
|
package/dist/Beat.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Beat.js","sourceRoot":"","sources":["../src/Beat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,OAAO,IAAI;IAEI,KAAK;IACL,GAAG;IAFtB,YACmB,KAAiD,EACjD,GAAwB;qBADxB,KAAK;mBACL,GAAG;IACnB,CAAC;IAEJ;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,EAAsB,EAAE,GAAwB;QAC3D,MAAM,KAAK,GAAG,EAAE,IAAI,KAAK,CAAC;QAC1B,MAAM,KAAK,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,KAAK,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC3E,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC;QAEjB,OAAO,IAAI,IAAI,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,wFAAwF;IACxF,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,KAAK;YAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a batch goes, and what a failure to send is allowed to do — nothing.
|
|
3
|
+
*
|
|
4
|
+
* A trace must never break the call it describes, so a refusal is reported through
|
|
5
|
+
* `onError` or not at all, and `post` resolves either way.
|
|
6
|
+
*/
|
|
7
|
+
export declare class Endpoint {
|
|
8
|
+
private readonly url;
|
|
9
|
+
private readonly onError;
|
|
10
|
+
private constructor();
|
|
11
|
+
static at(url: string, onError?: (err: unknown) => void): Endpoint;
|
|
12
|
+
/** The OTLP/HTTP encoding: JSON over a plain POST, so no protobuf and no dependency. */
|
|
13
|
+
post(body: unknown): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=Endpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Endpoint.d.ts","sourceRoot":"","sources":["../src/Endpoint.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,qBAAa,QAAQ;IAEjB,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAF1B,OAAO,eAGH;IAEJ,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,CAEjE;IAED,wFAAwF;IAClF,IAAI,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAWvC;CACF"}
|
package/dist/Endpoint.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a batch goes, and what a failure to send is allowed to do — nothing.
|
|
3
|
+
*
|
|
4
|
+
* A trace must never break the call it describes, so a refusal is reported through
|
|
5
|
+
* `onError` or not at all, and `post` resolves either way.
|
|
6
|
+
*/
|
|
7
|
+
export class Endpoint {
|
|
8
|
+
url;
|
|
9
|
+
onError;
|
|
10
|
+
constructor(url, onError) {
|
|
11
|
+
this.url = url;
|
|
12
|
+
this.onError = onError;
|
|
13
|
+
}
|
|
14
|
+
static at(url, onError) {
|
|
15
|
+
return new Endpoint(url, onError);
|
|
16
|
+
}
|
|
17
|
+
/** The OTLP/HTTP encoding: JSON over a plain POST, so no protobuf and no dependency. */
|
|
18
|
+
async post(body) {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(this.url, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'content-type': 'application/json' },
|
|
23
|
+
body: JSON.stringify(body),
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok)
|
|
26
|
+
this.onError?.(new Error(`${this.url} answered HTTP ${response.status}`));
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
this.onError?.(err);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=Endpoint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Endpoint.js","sourceRoot":"","sources":["../src/Endpoint.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,OAAO,QAAQ;IAEA,GAAG;IACH,OAAO;IAF1B,YACmB,GAAW,EACX,OAA6C;mBAD7C,GAAG;uBACH,OAAO;IACvB,CAAC;IAEJ,MAAM,CAAC,EAAE,CAAC,GAAW,EAAE,OAAgC;QACrD,OAAO,IAAI,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,wFAAwF;IACxF,KAAK,CAAC,IAAI,CAAC,IAAa;QACtB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE;gBACrC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;aAC3B,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,kBAAkB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC9F,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;CACF"}
|
package/dist/extension.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,KAAK,EAA4B,MAAM,eAAe,CAAC;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAiBjC;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,OAAO,GAAyB,EAAE;IAC9D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,SAAS,CAAC;IAC7C;;;;;OAKG;IACH,MAAM,OAAO,GAAG,IAAI,OAAO,
|
|
1
|
+
{"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,SAAS,EAAoB,MAAM,kBAAkB,CAAC;AAEhG,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AA0CnE;;;;;;;;;;;GAWG;AAEH,0CAA0C;AAC1C,MAAM,UAAU,WAAW;IACzB,OAAO,YAAY,CAAC,OAAO,EAAW,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,KAAK,GAAe,EAAE,CAAC;AAE7B;;;GAGG;AACH,IAAI,MAAM,GAAG,CAAC,CAAC;AAEf,MAAM,UAAU,WAAW;IACzB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/logs.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"logs.d.ts","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAU/C,8EAA8E;AAC9E,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,uBAAuB;IACvB,IAAI,EAAE,CAAC,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;IAClC,iCAAiC;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,4CAA4C;IAC5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAC/C;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,WAAW,CA2BtD"}
|
package/dist/logs.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
* The logger emits a structured record and nothing more (`onLog` in core). Attaching the
|
|
10
10
|
* trace, naming the severity and speaking OTLP all happen here.
|
|
11
11
|
*/
|
|
12
|
+
import { Beat } from './Beat.js';
|
|
13
|
+
import { Endpoint } from './Endpoint.js';
|
|
12
14
|
import { currentSpan } from './index.js';
|
|
13
15
|
/** OTLP severity numbers — the scale is 1–24, these are the canonical rungs. */
|
|
14
16
|
const SEVERITY = {
|
|
@@ -18,31 +20,19 @@ const SEVERITY = {
|
|
|
18
20
|
error: { number: 17, text: 'ERROR' },
|
|
19
21
|
};
|
|
20
22
|
export function logs(options) {
|
|
21
|
-
const
|
|
23
|
+
const collector = Endpoint.at(options.url ?? 'http://localhost:4318/v1/logs', options.onError);
|
|
22
24
|
const floor = options.minimum ? SEVERITY[options.minimum].number : 0;
|
|
23
25
|
let buffer = [];
|
|
26
|
+
// Nothing leaves when nothing was written — unlike the span exporter, which publishes
|
|
27
|
+
// its metrics on every beat regardless.
|
|
24
28
|
async function flush() {
|
|
25
29
|
if (buffer.length === 0)
|
|
26
30
|
return;
|
|
27
31
|
const batch = buffer;
|
|
28
32
|
buffer = [];
|
|
29
|
-
|
|
30
|
-
const response = await fetch(url, {
|
|
31
|
-
method: 'POST',
|
|
32
|
-
headers: { 'content-type': 'application/json' },
|
|
33
|
-
body: JSON.stringify(payload(options.service, batch)),
|
|
34
|
-
});
|
|
35
|
-
if (!response.ok)
|
|
36
|
-
options.onError?.(new Error(`${url} answered HTTP ${response.status}`));
|
|
37
|
-
}
|
|
38
|
-
catch (err) {
|
|
39
|
-
options.onError?.(err);
|
|
40
|
-
}
|
|
33
|
+
await collector.post(payload(options.service, batch));
|
|
41
34
|
}
|
|
42
|
-
const
|
|
43
|
-
// Same rule as the span exporter: a timer at module scope makes a Worker undeployable.
|
|
44
|
-
const timer = every > 0 ? setInterval(() => void flush(), every) : undefined;
|
|
45
|
-
timer?.unref?.();
|
|
35
|
+
const beat = Beat.every(options.flushMs, flush);
|
|
46
36
|
return {
|
|
47
37
|
// The span is read HERE, while the line is being written — not at flush time, when
|
|
48
38
|
// the call it belongs to is long over and the context is somebody else's.
|
|
@@ -53,8 +43,7 @@ export function logs(options) {
|
|
|
53
43
|
buffer.push({ ...record, traceId: span?.traceId, spanId: span?.spanId });
|
|
54
44
|
},
|
|
55
45
|
flush,
|
|
56
|
-
stop:
|
|
57
|
-
clearInterval(timer); await flush(); },
|
|
46
|
+
stop: () => beat.stop(),
|
|
58
47
|
};
|
|
59
48
|
}
|
|
60
49
|
/** One batch, as OTLP/JSON spells logs. */
|
package/dist/logs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logs.js","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,gFAAgF;AAChF,MAAM,QAAQ,GAAqD;IACjE,KAAK,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE;IACnC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE;IACjC,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;IAClC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;CACrC,CAAC;AAkCF,MAAM,UAAU,IAAI,CAAC,OAAoB;IACvC,MAAM,
|
|
1
|
+
{"version":3,"file":"logs.js","sourceRoot":"","sources":["../src/logs.ts"],"names":[],"mappings":"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"}
|
package/dist/metrics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
|
package/dist/otlp.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OTLP export — the spans this process finished, in the one shape every collector reads.
|
|
3
|
-
*
|
|
4
|
-
* OTLP has a JSON encoding over plain HTTP, so this needs no protobuf and no dependency:
|
|
5
|
-
* a POST to `/v1/traces`, and Jaeger, Tempo, Datadog or Honeycomb ingest it as-is.
|
|
6
|
-
*
|
|
7
|
-
* It is a sink and nothing more — `onSpan(otlp({...}).sink)` is the whole wiring. What
|
|
8
|
-
* a span IS was decided by the middleware; this file only renames its fields.
|
|
9
|
-
*/
|
|
10
1
|
import type { SpanSink } from './index.js';
|
|
11
2
|
import { type Metrics } from './metrics.js';
|
|
12
3
|
export interface OtlpOptions {
|
package/dist/otlp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otlp.d.ts","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"otlp.d.ts","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAgB,QAAQ,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAkB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAE5D,MAAM,WAAW,WAAW;IAC1B,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+FAA+F;IAC/F,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;IACjC;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oEAAoE;IACpE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,wBAAwB;IACxB,IAAI,EAAE,QAAQ,CAAC;IACf,4EAA4E;IAC5E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,4CAA4C;IAC5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAMD,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,YAAY,CA2BvD"}
|
package/dist/otlp.js
CHANGED
|
@@ -1,52 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTLP export — the spans this process finished, in the one shape every collector reads.
|
|
3
|
+
*
|
|
4
|
+
* OTLP has a JSON encoding over plain HTTP, so this needs no protobuf and no dependency:
|
|
5
|
+
* a POST to `/v1/traces`, and Jaeger, Tempo, Datadog or Honeycomb ingest it as-is.
|
|
6
|
+
*
|
|
7
|
+
* It is a sink and nothing more — `onSpan(otlp({...}).sink)` is the whole wiring. What
|
|
8
|
+
* a span IS was decided by the middleware; this file only renames its fields.
|
|
9
|
+
*/
|
|
10
|
+
import { Beat } from './Beat.js';
|
|
11
|
+
import { Endpoint } from './Endpoint.js';
|
|
1
12
|
import { metricsPayload } from './metrics.js';
|
|
2
13
|
/** OTLP status codes: 0 unset, 1 ok, 2 error. */
|
|
3
14
|
const OK = 1;
|
|
4
15
|
const ERROR = 2;
|
|
5
16
|
export function otlp(options) {
|
|
6
17
|
const url = options.url ?? 'http://localhost:4318/v1/traces';
|
|
7
|
-
const
|
|
18
|
+
const traces = Endpoint.at(url, options.onError);
|
|
19
|
+
const metrics = Endpoint.at(options.metricsUrl ?? url.replace(/\/v1\/traces$/, '/v1/metrics'), options.onError);
|
|
8
20
|
let buffer = [];
|
|
9
|
-
async function post(to, body) {
|
|
10
|
-
try {
|
|
11
|
-
const response = await fetch(to, {
|
|
12
|
-
method: 'POST',
|
|
13
|
-
headers: { 'content-type': 'application/json' },
|
|
14
|
-
body: JSON.stringify(body),
|
|
15
|
-
});
|
|
16
|
-
if (!response.ok)
|
|
17
|
-
options.onError?.(new Error(`${to} answered HTTP ${response.status}`));
|
|
18
|
-
}
|
|
19
|
-
catch (err) {
|
|
20
|
-
options.onError?.(err);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
21
|
async function flush() {
|
|
24
22
|
const batch = buffer;
|
|
25
23
|
buffer = [];
|
|
26
24
|
// Metrics go on every beat even when no span finished: a gauge that stops being
|
|
27
25
|
// published reads as "gone", not as "idle".
|
|
28
26
|
await Promise.all([
|
|
29
|
-
batch.length > 0 ? post(
|
|
30
|
-
options.metrics ? post(
|
|
27
|
+
batch.length > 0 ? traces.post(payload(options.service, batch)) : Promise.resolve(),
|
|
28
|
+
options.metrics ? metrics.post(metricsPayload(options.service, options.metrics.snapshot())) : Promise.resolve(),
|
|
31
29
|
]);
|
|
32
30
|
}
|
|
33
|
-
|
|
34
|
-
// is not a preference, it is the only legal form: Cloudflare REFUSES a deployment whose
|
|
35
|
-
// module scope sets a timeout ("Disallowed operation called within global scope"), and
|
|
36
|
-
// an app built at module scope builds its exporter there. Measured 2026-08-23, the
|
|
37
|
-
// deploy failed with error 10021. The isolate is frozen at the response anyway, so the
|
|
38
|
-
// timer could never have fired; `ctx.waitUntil(flushTelemetry())` is what sends.
|
|
39
|
-
//
|
|
40
|
-
// `unref` so a buffered span never keeps a process alive: exporting is something the
|
|
41
|
-
// process does on its way, never a reason for it to stay.
|
|
42
|
-
const every = options.flushMs ?? 1_000;
|
|
43
|
-
const timer = every > 0 ? setInterval(() => void flush(), every) : undefined;
|
|
44
|
-
timer?.unref?.();
|
|
31
|
+
const beat = Beat.every(options.flushMs, flush);
|
|
45
32
|
return {
|
|
46
33
|
sink: (span) => { buffer.push(span); },
|
|
47
34
|
flush,
|
|
48
|
-
stop:
|
|
49
|
-
clearInterval(timer); await flush(); },
|
|
35
|
+
stop: () => beat.stop(),
|
|
50
36
|
};
|
|
51
37
|
}
|
|
52
38
|
/** One batch, as OTLP/JSON spells it — trace and span ids stay hex, times are nanos. */
|
package/dist/otlp.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"otlp.js","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"otlp.js","sourceRoot":"","sources":["../src/otlp.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/observability",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0-alpha.0",
|
|
4
4
|
"description": "Optional observability for Fougere: one span per operation, W3C Trace Context across any transport.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -27,17 +27,18 @@
|
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
|
-
"dist"
|
|
30
|
+
"dist",
|
|
31
|
+
"src"
|
|
31
32
|
],
|
|
32
33
|
"peerDependencies": {
|
|
33
|
-
"@fougere/core": "^0.
|
|
34
|
+
"@fougere/core": "^0.5.0-alpha.0"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"vitest": "^4.1.0",
|
|
37
|
-
"@fougere/container": "0.
|
|
38
|
-
"@fougere/
|
|
39
|
-
"@fougere/
|
|
40
|
-
"@fougere/
|
|
38
|
+
"@fougere/container": "0.5.0-alpha.0",
|
|
39
|
+
"@fougere/transport-http": "0.5.0-alpha.0",
|
|
40
|
+
"@fougere/core": "0.5.0-alpha.0",
|
|
41
|
+
"@fougere/schema": "0.5.0-alpha.0"
|
|
41
42
|
},
|
|
42
43
|
"publishConfig": {
|
|
43
44
|
"access": "public"
|
package/src/Beat.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How often a batch leaves, and how the beat stops.
|
|
3
|
+
*
|
|
4
|
+
* `flushMs: 0` means "nobody is on a timer here, I will say when" — and on a Worker it is
|
|
5
|
+
* not a preference, it is the only legal form: Cloudflare REFUSES a deployment whose
|
|
6
|
+
* module scope sets a timeout ("Disallowed operation called within global scope"), and an
|
|
7
|
+
* app built at module scope builds its exporter there. Measured 2026-08-23, the deploy
|
|
8
|
+
* failed with error 10021. The isolate is frozen at the response anyway, so the timer
|
|
9
|
+
* could never have fired; `ctx.waitUntil(flushTelemetry())` is what sends.
|
|
10
|
+
*/
|
|
11
|
+
export class Beat {
|
|
12
|
+
private constructor(
|
|
13
|
+
private readonly timer: ReturnType<typeof setInterval> | undefined,
|
|
14
|
+
private readonly run: () => Promise<void>,
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* `unref` so a buffered record never keeps a process alive: exporting is something the
|
|
19
|
+
* process does on its way, never a reason for it to stay.
|
|
20
|
+
*/
|
|
21
|
+
static every(ms: number | undefined, run: () => Promise<void>): Beat {
|
|
22
|
+
const every = ms ?? 1_000;
|
|
23
|
+
const timer = every > 0 ? setInterval(() => void run(), every) : undefined;
|
|
24
|
+
timer?.unref?.();
|
|
25
|
+
|
|
26
|
+
return new Beat(timer, run);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Stop the timer, then send what is left — the order a process about to exit needs. */
|
|
30
|
+
async stop(): Promise<void> {
|
|
31
|
+
if (this.timer) clearInterval(this.timer);
|
|
32
|
+
await this.run();
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/Endpoint.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a batch goes, and what a failure to send is allowed to do — nothing.
|
|
3
|
+
*
|
|
4
|
+
* A trace must never break the call it describes, so a refusal is reported through
|
|
5
|
+
* `onError` or not at all, and `post` resolves either way.
|
|
6
|
+
*/
|
|
7
|
+
export class Endpoint {
|
|
8
|
+
private constructor(
|
|
9
|
+
private readonly url: string,
|
|
10
|
+
private readonly onError: ((err: unknown) => void) | undefined,
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
static at(url: string, onError?: (err: unknown) => void): Endpoint {
|
|
14
|
+
return new Endpoint(url, onError);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The OTLP/HTTP encoding: JSON over a plain POST, so no protobuf and no dependency. */
|
|
18
|
+
async post(body: unknown): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(this.url, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: { 'content-type': 'application/json' },
|
|
23
|
+
body: JSON.stringify(body),
|
|
24
|
+
});
|
|
25
|
+
if (!response.ok) this.onError?.(new Error(`${this.url} answered HTTP ${response.status}`));
|
|
26
|
+
} catch (err) {
|
|
27
|
+
this.onError?.(err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The default: a real `AsyncLocalStorage`, so a call with no wire above it still finds
|
|
3
|
+
* its parent in the stack it is running inside.
|
|
4
|
+
*/
|
|
5
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
6
|
+
import type { SpanContext } from '../traceparent.js';
|
|
7
|
+
import type { TraceContext } from './port.js';
|
|
8
|
+
|
|
9
|
+
const store = new AsyncLocalStorage<SpanContext>();
|
|
10
|
+
|
|
11
|
+
export const traceContext: TraceContext = {
|
|
12
|
+
ambient: true,
|
|
13
|
+
current: <T extends SpanContext>() => store.getStore() as T | undefined,
|
|
14
|
+
within: (span, fn) => store.run(span, fn),
|
|
15
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* workerd with no flag: there is no ambient store, so there is no parent to find.
|
|
3
|
+
*
|
|
4
|
+
* NOT a module-level variable standing in for one. An isolate serves many requests at
|
|
5
|
+
* once and a shared slot would attribute one call's span to another — a trace that is
|
|
6
|
+
* wrong is worse than a trace that is absent, which is the same answer the emission
|
|
7
|
+
* chain gives one file over.
|
|
8
|
+
*
|
|
9
|
+
* What is lost is exactly this: a call that crossed NO wire starts its own trace. An
|
|
10
|
+
* arriving call is untouched — it reads `traceparent` off the invocation — and so is a
|
|
11
|
+
* call to a frond behind `remotes:`, which crosses one. `nodejs_als` in
|
|
12
|
+
* `compatibility_flags` restores the rest, and the boot names it.
|
|
13
|
+
*/
|
|
14
|
+
import type { SpanContext } from '../traceparent.js';
|
|
15
|
+
import type { TraceContext } from './port.js';
|
|
16
|
+
|
|
17
|
+
export const traceContext: TraceContext = {
|
|
18
|
+
ambient: false,
|
|
19
|
+
current: <T extends SpanContext>(): T | undefined => undefined,
|
|
20
|
+
within: (_span, fn) => fn(),
|
|
21
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where the step running right now is kept — the one thing a Worker cannot do.
|
|
3
|
+
*
|
|
4
|
+
* A trace has TWO ways to name its parent, and only one of them is ambient: an arriving
|
|
5
|
+
* call carries `traceparent` on the invocation, which every transport moves and which
|
|
6
|
+
* needs nothing from the runtime. This is the other way — the parent of a call that
|
|
7
|
+
* crossed no wire, which is in the stack and nowhere else.
|
|
8
|
+
*
|
|
9
|
+
* `node:async_hooks` does not exist on workerd unless the deployment asks for it, so
|
|
10
|
+
* `#trace-context` resolves to two realizations: the real store by default, and one that
|
|
11
|
+
* answers "no parent here" under the `workerd` condition. What that costs is stated at
|
|
12
|
+
* boot rather than discovered in a trace viewer — see `traceContext.ambient`.
|
|
13
|
+
*/
|
|
14
|
+
import type { SpanContext } from '../traceparent.js';
|
|
15
|
+
|
|
16
|
+
export interface TraceContext {
|
|
17
|
+
/**
|
|
18
|
+
* Whether a parent can be found without a wire. False means every in-process call
|
|
19
|
+
* starts its own trace, and the boot says so.
|
|
20
|
+
*/
|
|
21
|
+
readonly ambient: boolean;
|
|
22
|
+
/** The step running here and now, when one can be known. */
|
|
23
|
+
current<T extends SpanContext>(): T | undefined;
|
|
24
|
+
/** Run `fn` with `span` as the step in scope. */
|
|
25
|
+
within<T extends SpanContext, R>(span: T, fn: () => Promise<R>): Promise<R>;
|
|
26
|
+
}
|
package/src/extension.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This package's own ascent and descent, in one value.
|
|
3
|
+
*
|
|
4
|
+
* Four gestures in a fixed order — the middleware, the accumulator, the sinks, the wire op
|
|
5
|
+
* — is a boot sequence, and a host had to know it: `demos/observability` wrote it out and
|
|
6
|
+
* hand-rolled its own `stop()` beside it, which is a `down` under another name. Declared as
|
|
7
|
+
* an extension, the order is stated once here and the release is not the host's to remember.
|
|
8
|
+
*
|
|
9
|
+
* It is not a frond and cannot become one: a frond may move behind `remotes:`, and this
|
|
10
|
+
* reads the process it runs in — moved, it would report the observer instead of the observed.
|
|
11
|
+
*/
|
|
12
|
+
import { loggerMiddleware, Logger, onLog, type App, type Extension } from '@fougere/core';
|
|
13
|
+
import { traceContext } from '#trace-context';
|
|
14
|
+
import { registerFlush } from './index.js';
|
|
15
|
+
import { trace, onSpan } from './index.js';
|
|
16
|
+
import { metrics, serveTopology } from './metrics.js';
|
|
17
|
+
import { otlp } from './otlp.js';
|
|
18
|
+
import { logs } from './logs.js';
|
|
19
|
+
|
|
20
|
+
export interface ObservabilityOptions {
|
|
21
|
+
/** What a dashboard groups this process by, and the name a log line carries. */
|
|
22
|
+
service?: string;
|
|
23
|
+
/**
|
|
24
|
+
* An OTLP collector's base URL — SigNoz, Jaeger, Tempo all read the same one.
|
|
25
|
+
*
|
|
26
|
+
* Absent, nothing leaves the process and `rpc.topology` still answers: a panel asking
|
|
27
|
+
* the app what shape it is in needs no collector.
|
|
28
|
+
*/
|
|
29
|
+
otlp?: string;
|
|
30
|
+
flushMs?: number;
|
|
31
|
+
/** A collector that cannot be reached is the app's business, not its failure. */
|
|
32
|
+
onError?: (error: unknown) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Observe this process — one member of the ascent.
|
|
37
|
+
*
|
|
38
|
+
* `up` wires in the order that matters and `down` withdraws in reverse, which is the whole
|
|
39
|
+
* reason the two halves are one value: `onSpan` and `onLog` already RETURN their withdrawal
|
|
40
|
+
* and nothing was calling it, so a discarded app kept feeding the sinks of the app that
|
|
41
|
+
* replaced it. That is invisible until the ring turns, and then it doubles every metric.
|
|
42
|
+
*/
|
|
43
|
+
export function observability(options: ObservabilityOptions = {}): Extension {
|
|
44
|
+
const service = options.service ?? 'fougere';
|
|
45
|
+
/**
|
|
46
|
+
* Per APP, not per extension. A host declares its extensions once (`configureFougere`),
|
|
47
|
+
* so the same instance goes up on the new app before the old one is released: one shared
|
|
48
|
+
* list meant the old app's `down` withdrew — and erased — the new app's sinks too, after
|
|
49
|
+
* which every counter stayed frozen and nothing said so.
|
|
50
|
+
*/
|
|
51
|
+
const undoing = new WeakMap<App, (() => void | Promise<void>)[]>();
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
name: 'observability',
|
|
55
|
+
up(app: App) {
|
|
56
|
+
const undo: (() => void | Promise<void>)[] = [];
|
|
57
|
+
undoing.set(app, undo);
|
|
58
|
+
// Order matters: `trace()` opens the span that every log line written inside the
|
|
59
|
+
// call will carry. Installed the other way round, the lines leave uncorrelated.
|
|
60
|
+
app.use(trace());
|
|
61
|
+
app.use(loggerMiddleware(new Logger(service)));
|
|
62
|
+
|
|
63
|
+
// Said once, here, because the alternative is finding it in a trace viewer three
|
|
64
|
+
// weeks later: without an ambient context a call that crossed NO wire cannot name
|
|
65
|
+
// its parent, so it starts its own trace. What still works is stated too — a
|
|
66
|
+
// warning that only names the loss reads as "tracing is broken", and it is not.
|
|
67
|
+
if (!traceContext.ambient) {
|
|
68
|
+
new Logger(service).warn(
|
|
69
|
+
'no async context: a call that crosses no wire starts its own trace '
|
|
70
|
+
+ '(an emission subscriber, a handler reaching another frond in this process). '
|
|
71
|
+
+ 'An arriving call and a call to a frond behind `remotes:` are unaffected — both carry '
|
|
72
|
+
+ 'traceparent on the invocation. Add "nodejs_als" to compatibility_flags to restore the rest.',
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const measured = metrics(app);
|
|
77
|
+
undo.push(onSpan(measured.sink));
|
|
78
|
+
serveTopology(app, measured);
|
|
79
|
+
|
|
80
|
+
if (!options.otlp) return;
|
|
81
|
+
const base = options.otlp.replace(/\/$/, '');
|
|
82
|
+
const telemetry = otlp({
|
|
83
|
+
service,
|
|
84
|
+
url: `${base}/v1/traces`,
|
|
85
|
+
metricsUrl: `${base}/v1/metrics`,
|
|
86
|
+
metrics: measured,
|
|
87
|
+
...(options.flushMs === undefined ? {} : { flushMs: options.flushMs }),
|
|
88
|
+
...(options.onError ? { onError: options.onError } : {}),
|
|
89
|
+
});
|
|
90
|
+
const written = logs({
|
|
91
|
+
service,
|
|
92
|
+
url: `${base}/v1/logs`,
|
|
93
|
+
...(options.flushMs === undefined ? {} : { flushMs: options.flushMs }),
|
|
94
|
+
...(options.onError ? { onError: options.onError } : {}),
|
|
95
|
+
});
|
|
96
|
+
// The timer is not the only way out: an isolate is frozen when it answers, so a
|
|
97
|
+
// host with no time between requests calls `flushTelemetry()` inside `ctx.waitUntil`.
|
|
98
|
+
undo.push(
|
|
99
|
+
onSpan(telemetry.sink), onLog(written.sink),
|
|
100
|
+
registerFlush(() => telemetry.flush()), registerFlush(() => written.flush()),
|
|
101
|
+
() => telemetry.stop(), () => written.stop(),
|
|
102
|
+
);
|
|
103
|
+
},
|
|
104
|
+
async down(app: App) {
|
|
105
|
+
// Reverse, and the exporters' `stop()` is in here: it flushes what is buffered, so a
|
|
106
|
+
// released app publishes what it measured instead of dropping its last window.
|
|
107
|
+
const undo = undoing.get(app);
|
|
108
|
+
if (!undo) return;
|
|
109
|
+
undoing.delete(app);
|
|
110
|
+
for (const step of undo.reverse()) await step();
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fougere/observability — one span per operation, and the trace that survives a wire.
|
|
3
|
+
*
|
|
4
|
+
* Named for the subject and not for today's reading of it: a span carries an op's
|
|
5
|
+
* duration and its verdict, which is the matter of a metric as much as of a trace, and
|
|
6
|
+
* an exporter for either hangs off the same `onSpan`.
|
|
7
|
+
*
|
|
8
|
+
* Optional in the strong sense: core holds no tracing code at all, only a `trace` field
|
|
9
|
+
* on the invocation that it carries and never reads. Everything else is here, behind
|
|
10
|
+
* `app.use(trace())`, and an app that does not install it pays nothing.
|
|
11
|
+
*
|
|
12
|
+
* It is an ordinary app middleware because an operation ALREADY has a lifecycle and it
|
|
13
|
+
* is that one — a second hook system would be a second answer to a settled question.
|
|
14
|
+
* The same middleware runs on both halves of a split: at the door a call arrives at, and
|
|
15
|
+
* at the stand-in it leaves from. That is why the numbers line up across processes, and
|
|
16
|
+
* why the difference between the two spans is what the wire cost.
|
|
17
|
+
*/
|
|
18
|
+
import { traceContext } from '#trace-context';
|
|
19
|
+
import type { AppMiddleware } from '@fougere/core';
|
|
20
|
+
import { parseTraceparent, traceparentOf, randomHex, type SpanContext } from './traceparent.js';
|
|
21
|
+
|
|
22
|
+
export { traceparentOf, parseTraceparent } from './traceparent.js';
|
|
23
|
+
export type { SpanContext } from './traceparent.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A step while it runs. `SpanContext` is the part that TRAVELS (W3C Trace Context, three
|
|
27
|
+
* fields, nothing else); the frond is ours and stays in this process — which is enough,
|
|
28
|
+
* because the only place an edge is knowable is the side that made the call.
|
|
29
|
+
*/
|
|
30
|
+
interface Running extends SpanContext {
|
|
31
|
+
frond: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A step that has finished, and what it did. */
|
|
35
|
+
export interface FinishedSpan extends SpanContext {
|
|
36
|
+
parentId: string | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* The frond of the step this one is nested in, when it is a DIFFERENT one — an edge of
|
|
39
|
+
* the call graph, `shop → catalog`.
|
|
40
|
+
*
|
|
41
|
+
* Only the caller can say it: a traceparent carries no frond, so a receiver knows it
|
|
42
|
+
* has a parent and not whose. That is not a gap — the caller already published the edge.
|
|
43
|
+
*/
|
|
44
|
+
callerFrond: string | undefined;
|
|
45
|
+
/** Which frond owned the op — the deployment unit, so the first thing a reader groups by. */
|
|
46
|
+
frond: string | undefined;
|
|
47
|
+
entity: string;
|
|
48
|
+
operation: string;
|
|
49
|
+
/**
|
|
50
|
+
* When it started, in epoch milliseconds — an INSTANT, not an offset.
|
|
51
|
+
*
|
|
52
|
+
* `performance.now()` measures the duration below and nothing else: it counts from
|
|
53
|
+
* this process's own start, so two processes' numbers cannot be put on one timeline,
|
|
54
|
+
* which is the only thing a trace is for.
|
|
55
|
+
*/
|
|
56
|
+
startedAt: number;
|
|
57
|
+
ms: number;
|
|
58
|
+
/** The FougereError code when it refused, absent when it answered. */
|
|
59
|
+
error: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type SpanSink = (span: FinishedSpan) => void;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The step running here and now.
|
|
66
|
+
*
|
|
67
|
+
* It exists for the call the wire cannot describe: a handler reaching a second frond
|
|
68
|
+
* builds a fresh invocation, so the parent is not on that call — it is in the context
|
|
69
|
+
* the first one is still running inside.
|
|
70
|
+
*
|
|
71
|
+
* Behind `#trace-context` because that context is a runtime capability, not ours: a
|
|
72
|
+
* Worker has none unless the deployment asks. `traceContext.ambient` says which one is
|
|
73
|
+
* running, and `trace()` reports it once at install rather than letting a reader
|
|
74
|
+
* discover it in a trace viewer.
|
|
75
|
+
*/
|
|
76
|
+
|
|
77
|
+
/** The step running right now, if any. */
|
|
78
|
+
export function currentSpan(): SpanContext | undefined {
|
|
79
|
+
return traceContext.current<Running>();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Who takes the spans this process finishes, consulted at every end like the log level.
|
|
84
|
+
*
|
|
85
|
+
* A list rather than one: a span is the source of a trace AND of a metric, so the two
|
|
86
|
+
* exporters read the same value rather than the middleware producing it twice.
|
|
87
|
+
* Returns the way to withdraw.
|
|
88
|
+
*/
|
|
89
|
+
const sinks: SpanSink[] = [];
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Calls running right now — the saturation signal, and the only one a FINISHED span
|
|
93
|
+
* cannot carry. Counted at the same two moments the span is opened and closed.
|
|
94
|
+
*/
|
|
95
|
+
let active = 0;
|
|
96
|
+
|
|
97
|
+
export function activeCalls(): number {
|
|
98
|
+
return active;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Every exporter this process installed, so something can make them send NOW.
|
|
103
|
+
*
|
|
104
|
+
* An exporter buffers and flushes on a timer, which assumes a process that lives between
|
|
105
|
+
* two requests. An isolate does not: it is frozen the moment it answers, so the timer
|
|
106
|
+
* never fires and what it held is lost. `ctx.waitUntil(flushTelemetry())` is the whole
|
|
107
|
+
* remedy — the platform's way of saying "this work outlives the response".
|
|
108
|
+
*/
|
|
109
|
+
const flushes: (() => Promise<void>)[] = [];
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Send what is buffered, now. Safe to call where there is nothing to send.
|
|
113
|
+
*
|
|
114
|
+
* Module-level like `sinks`, and for the same reason: an exporter belongs to the PROCESS,
|
|
115
|
+
* and the handler that wants to flush it holds no reference to the extension that built it.
|
|
116
|
+
*/
|
|
117
|
+
export async function flushTelemetry(): Promise<void> {
|
|
118
|
+
// Every one, and the refusals together — a flush that abandons the rest loses the
|
|
119
|
+
// windows after it, which is the answer `app.deliver` gives for the same shape.
|
|
120
|
+
const failed = await Promise.allSettled(flushes.map((send) => send()));
|
|
121
|
+
const refused = failed.filter((r): r is PromiseRejectedResult => r.status === 'rejected');
|
|
122
|
+
if (refused.length > 0) throw new AggregateError(refused.map((r) => r.reason), 'telemetry flush');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Declare an exporter's flush, and answer the way to withdraw it — like `onSpan`. */
|
|
126
|
+
export function registerFlush(send: () => Promise<void>): () => void {
|
|
127
|
+
flushes.push(send);
|
|
128
|
+
return () => {
|
|
129
|
+
const at = flushes.indexOf(send);
|
|
130
|
+
if (at >= 0) flushes.splice(at, 1);
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function onSpan(next: SpanSink): () => void {
|
|
135
|
+
sinks.push(next);
|
|
136
|
+
return () => {
|
|
137
|
+
const at = sinks.indexOf(next);
|
|
138
|
+
if (at >= 0) sinks.splice(at, 1);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* The middleware. `app.use(trace())` traces every operation; `app.use('post', trace())`
|
|
144
|
+
* traces one entity's.
|
|
145
|
+
*
|
|
146
|
+
* Nothing is opened while no sink is set: observing is a decision, and the cost of one
|
|
147
|
+
* nobody asked for is zero rather than small.
|
|
148
|
+
*/
|
|
149
|
+
export function trace(): AppMiddleware {
|
|
150
|
+
return (ctx, next) => {
|
|
151
|
+
if (sinks.length === 0) return next();
|
|
152
|
+
|
|
153
|
+
// The wire first, the ambient context second: an arriving call names its parent on
|
|
154
|
+
// the invocation, an outgoing one inherits from the call it is made inside.
|
|
155
|
+
const inherited = parseTraceparent(ctx.invocation?.trace);
|
|
156
|
+
const ambient = traceContext.current<Running>();
|
|
157
|
+
const parent = inherited ?? ambient;
|
|
158
|
+
const span: Running = {
|
|
159
|
+
traceId: parent?.traceId ?? randomHex(16),
|
|
160
|
+
spanId: randomHex(8),
|
|
161
|
+
sampled: parent?.sampled ?? true,
|
|
162
|
+
frond: ctx.frond,
|
|
163
|
+
};
|
|
164
|
+
// An edge exists only when the parent is IN this process and belongs to another frond.
|
|
165
|
+
// `inherited` won means the parent is across a wire, and it did not name its frond.
|
|
166
|
+
const callerFrond =
|
|
167
|
+
!inherited && ambient?.frond && ambient.frond !== ctx.frond ? ambient.frond : undefined;
|
|
168
|
+
|
|
169
|
+
// What this call hands to whatever it reaches next. Whether that is a transport or
|
|
170
|
+
// nothing at all is not this middleware's business — it writes the field, the
|
|
171
|
+
// invocation travels, and every transport carries it because every transport
|
|
172
|
+
// carries the invocation.
|
|
173
|
+
if (ctx.invocation) ctx.invocation = { ...ctx.invocation, trace: traceparentOf(span) };
|
|
174
|
+
|
|
175
|
+
// Both, and they are not the same measurement: the wall clock says WHEN so two
|
|
176
|
+
// processes land on one timeline, the monotonic one says HOW LONG without being
|
|
177
|
+
// moved by an NTP correction mid-call.
|
|
178
|
+
const startedAt = Date.now();
|
|
179
|
+
const start = performance.now();
|
|
180
|
+
active += 1;
|
|
181
|
+
const finish = (error: string | undefined) => {
|
|
182
|
+
active -= 1;
|
|
183
|
+
const done: FinishedSpan = {
|
|
184
|
+
...span,
|
|
185
|
+
parentId: parent?.spanId,
|
|
186
|
+
callerFrond,
|
|
187
|
+
frond: ctx.frond,
|
|
188
|
+
entity: ctx.entity,
|
|
189
|
+
operation: ctx.operation,
|
|
190
|
+
startedAt,
|
|
191
|
+
ms: performance.now() - start,
|
|
192
|
+
error,
|
|
193
|
+
};
|
|
194
|
+
// A sink that throws is a broken exporter, never a broken call.
|
|
195
|
+
for (const take of sinks) { try { take(done); } catch { /* observing never refuses */ } }
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
return traceContext.within(span, async () => {
|
|
199
|
+
try {
|
|
200
|
+
const result = await next();
|
|
201
|
+
finish(undefined);
|
|
202
|
+
return result;
|
|
203
|
+
} catch (err) {
|
|
204
|
+
finish(codeOf(err));
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function codeOf(err: unknown): string {
|
|
212
|
+
const code = (err as { code?: unknown })?.code;
|
|
213
|
+
return typeof code === 'string' ? code : ((err as Error)?.name ?? 'error');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { otlp } from './otlp.js';
|
|
217
|
+
export type { OtlpOptions, OtlpExporter } from './otlp.js';
|
|
218
|
+
export { metrics, metricsPayload, serveTopology } from './metrics.js';
|
|
219
|
+
export type { Metrics, MetricsSnapshot, TopologyReport, FrondPlacement, Edge } from './metrics.js';
|
|
220
|
+
export { logs } from './logs.js';
|
|
221
|
+
export type { LogsOptions, LogExporter, CapturedLog } from './logs.js';
|
|
222
|
+
export { observability } from './extension.js';
|
|
223
|
+
export type { ObservabilityOptions } from './extension.js';
|
package/src/logs.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log export — the third signal, and the only one whose value is entirely in its links.
|
|
3
|
+
*
|
|
4
|
+
* A log shipped without a trace id is a log stored somewhere else. What makes it the third
|
|
5
|
+
* pillar is landing on a trace and reading the lines that call produced, so every record
|
|
6
|
+
* leaves with the span it was written inside — which this package knows and the logger
|
|
7
|
+
* deliberately does not.
|
|
8
|
+
*
|
|
9
|
+
* The logger emits a structured record and nothing more (`onLog` in core). Attaching the
|
|
10
|
+
* trace, naming the severity and speaking OTLP all happen here.
|
|
11
|
+
*/
|
|
12
|
+
import { Beat } from './Beat.js';
|
|
13
|
+
import { Endpoint } from './Endpoint.js';
|
|
14
|
+
import { currentSpan } from './index.js';
|
|
15
|
+
import type { LogRecord } from '@fougere/core';
|
|
16
|
+
|
|
17
|
+
/** OTLP severity numbers — the scale is 1–24, these are the canonical rungs. */
|
|
18
|
+
const SEVERITY: Record<string, { number: number; text: string }> = {
|
|
19
|
+
debug: { number: 5, text: 'DEBUG' },
|
|
20
|
+
info: { number: 9, text: 'INFO' },
|
|
21
|
+
warn: { number: 13, text: 'WARN' },
|
|
22
|
+
error: { number: 17, text: 'ERROR' },
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** A record, plus what it could only be told at the moment it was written. */
|
|
26
|
+
export interface CapturedLog extends LogRecord {
|
|
27
|
+
traceId: string | undefined;
|
|
28
|
+
spanId: string | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface LogExporter {
|
|
32
|
+
/** Hand to `onLog`. */
|
|
33
|
+
sink: (record: LogRecord) => void;
|
|
34
|
+
/** Send what is buffered now. */
|
|
35
|
+
flush(): Promise<void>;
|
|
36
|
+
/** Stop the timer and send what is left. */
|
|
37
|
+
stop(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface LogsOptions {
|
|
41
|
+
/** Which service these lines belong to. */
|
|
42
|
+
service: string;
|
|
43
|
+
/** Collector endpoint. Default: the OTLP/HTTP convention on localhost. */
|
|
44
|
+
url?: string;
|
|
45
|
+
/** How often a batch leaves. Default: every second. */
|
|
46
|
+
flushMs?: number;
|
|
47
|
+
/** Told when a batch could not be sent. Default: silence. */
|
|
48
|
+
onError?: (err: unknown) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Drop anything below this level before it leaves the process. Absent means "whatever
|
|
51
|
+
* the logger let through" — `setLogLevel` has already filtered, and a second threshold
|
|
52
|
+
* here would be a second place where the level lives.
|
|
53
|
+
*/
|
|
54
|
+
minimum?: 'debug' | 'info' | 'warn' | 'error';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function logs(options: LogsOptions): LogExporter {
|
|
58
|
+
const collector = Endpoint.at(options.url ?? 'http://localhost:4318/v1/logs', options.onError);
|
|
59
|
+
const floor = options.minimum ? SEVERITY[options.minimum].number : 0;
|
|
60
|
+
let buffer: CapturedLog[] = [];
|
|
61
|
+
|
|
62
|
+
// Nothing leaves when nothing was written — unlike the span exporter, which publishes
|
|
63
|
+
// its metrics on every beat regardless.
|
|
64
|
+
async function flush(): Promise<void> {
|
|
65
|
+
if (buffer.length === 0) return;
|
|
66
|
+
const batch = buffer;
|
|
67
|
+
buffer = [];
|
|
68
|
+
await collector.post(payload(options.service, batch));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const beat = Beat.every(options.flushMs, flush);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
// The span is read HERE, while the line is being written — not at flush time, when
|
|
75
|
+
// the call it belongs to is long over and the context is somebody else's.
|
|
76
|
+
sink: (record) => {
|
|
77
|
+
if (SEVERITY[record.level].number < floor) return;
|
|
78
|
+
const span = currentSpan();
|
|
79
|
+
buffer.push({ ...record, traceId: span?.traceId, spanId: span?.spanId });
|
|
80
|
+
},
|
|
81
|
+
flush,
|
|
82
|
+
stop: () => beat.stop(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** One batch, as OTLP/JSON spells logs. */
|
|
87
|
+
function payload(service: string, records: CapturedLog[]) {
|
|
88
|
+
const attr = (key: string, value: string) => ({ key, value: { stringValue: value } });
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
resourceLogs: [
|
|
92
|
+
{
|
|
93
|
+
resource: { attributes: [attr('service.name', service)] },
|
|
94
|
+
scopeLogs: [
|
|
95
|
+
{
|
|
96
|
+
scope: { name: '@fougere/observability' },
|
|
97
|
+
logRecords: records.map((record) => {
|
|
98
|
+
const severity = SEVERITY[record.level];
|
|
99
|
+
return {
|
|
100
|
+
timeUnixNano: `${record.at * 1e6}`,
|
|
101
|
+
observedTimeUnixNano: `${record.at * 1e6}`,
|
|
102
|
+
severityNumber: severity.number,
|
|
103
|
+
severityText: severity.text,
|
|
104
|
+
body: { stringValue: bodyOf(record) },
|
|
105
|
+
attributes: [attr('logger.name', record.name)],
|
|
106
|
+
// Absent when the line was written outside any call — a boot line, a
|
|
107
|
+
// shutdown line. Absent is the honest answer, not a zeroed id.
|
|
108
|
+
...(record.traceId ? { traceId: record.traceId } : {}),
|
|
109
|
+
...(record.spanId ? { spanId: record.spanId } : {}),
|
|
110
|
+
};
|
|
111
|
+
}),
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The message with its arguments folded in. A log call takes extras the way `console`
|
|
121
|
+
* does, and a collector stores one body — so they are joined rather than dropped.
|
|
122
|
+
*/
|
|
123
|
+
function bodyOf(record: CapturedLog): string {
|
|
124
|
+
if (record.args.length === 0) return record.message;
|
|
125
|
+
return [record.message, ...record.args.map(readable)].join(' ');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readable(value: unknown): string {
|
|
129
|
+
if (typeof value === 'string') return value;
|
|
130
|
+
if (value instanceof Error) return `${value.name}: ${value.message}`;
|
|
131
|
+
try {
|
|
132
|
+
return JSON.stringify(value) ?? String(value);
|
|
133
|
+
} catch {
|
|
134
|
+
return String(value);
|
|
135
|
+
}
|
|
136
|
+
}
|
package/src/metrics.ts
ADDED
|
Binary file
|
package/src/otlp.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTLP export — the spans this process finished, in the one shape every collector reads.
|
|
3
|
+
*
|
|
4
|
+
* OTLP has a JSON encoding over plain HTTP, so this needs no protobuf and no dependency:
|
|
5
|
+
* a POST to `/v1/traces`, and Jaeger, Tempo, Datadog or Honeycomb ingest it as-is.
|
|
6
|
+
*
|
|
7
|
+
* It is a sink and nothing more — `onSpan(otlp({...}).sink)` is the whole wiring. What
|
|
8
|
+
* a span IS was decided by the middleware; this file only renames its fields.
|
|
9
|
+
*/
|
|
10
|
+
import { Beat } from './Beat.js';
|
|
11
|
+
import { Endpoint } from './Endpoint.js';
|
|
12
|
+
import type { FinishedSpan, SpanSink } from './index.js';
|
|
13
|
+
import { metricsPayload, type Metrics } from './metrics.js';
|
|
14
|
+
|
|
15
|
+
export interface OtlpOptions {
|
|
16
|
+
/** Which service these spans belong to — what a dashboard groups by. */
|
|
17
|
+
service: string;
|
|
18
|
+
/** Collector endpoint. Default: the OTLP/HTTP convention on localhost. */
|
|
19
|
+
url?: string;
|
|
20
|
+
/** How often a full batch leaves. Default: every second. */
|
|
21
|
+
flushMs?: number;
|
|
22
|
+
/** Told when a batch could not be sent. Default: silence — a trace must never break a call. */
|
|
23
|
+
onError?: (err: unknown) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Publish these metrics on the same beat.
|
|
26
|
+
*
|
|
27
|
+
* Their endpoint defaults to the traces one with its last segment swapped — the OTLP
|
|
28
|
+
* convention when a single collector takes both. Name `metricsUrl` when they are two:
|
|
29
|
+
* traces and metrics are stored by different engines, and a deployment is free to run
|
|
30
|
+
* one of each rather than a collector in front.
|
|
31
|
+
*/
|
|
32
|
+
metrics?: Metrics;
|
|
33
|
+
/** Where metrics go when it is not the same collector as traces. */
|
|
34
|
+
metricsUrl?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OtlpExporter {
|
|
38
|
+
/** Hand to `onSpan`. */
|
|
39
|
+
sink: SpanSink;
|
|
40
|
+
/** Send what is buffered now — a process about to exit has to call this. */
|
|
41
|
+
flush(): Promise<void>;
|
|
42
|
+
/** Stop the timer and send what is left. */
|
|
43
|
+
stop(): Promise<void>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** OTLP status codes: 0 unset, 1 ok, 2 error. */
|
|
47
|
+
const OK = 1;
|
|
48
|
+
const ERROR = 2;
|
|
49
|
+
|
|
50
|
+
export function otlp(options: OtlpOptions): OtlpExporter {
|
|
51
|
+
const url = options.url ?? 'http://localhost:4318/v1/traces';
|
|
52
|
+
const traces = Endpoint.at(url, options.onError);
|
|
53
|
+
const metrics = Endpoint.at(
|
|
54
|
+
options.metricsUrl ?? url.replace(/\/v1\/traces$/, '/v1/metrics'),
|
|
55
|
+
options.onError,
|
|
56
|
+
);
|
|
57
|
+
let buffer: FinishedSpan[] = [];
|
|
58
|
+
|
|
59
|
+
async function flush(): Promise<void> {
|
|
60
|
+
const batch = buffer;
|
|
61
|
+
buffer = [];
|
|
62
|
+
// Metrics go on every beat even when no span finished: a gauge that stops being
|
|
63
|
+
// published reads as "gone", not as "idle".
|
|
64
|
+
await Promise.all([
|
|
65
|
+
batch.length > 0 ? traces.post(payload(options.service, batch)) : Promise.resolve(),
|
|
66
|
+
options.metrics ? metrics.post(metricsPayload(options.service, options.metrics.snapshot())) : Promise.resolve(),
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const beat = Beat.every(options.flushMs, flush);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
sink: (span) => { buffer.push(span); },
|
|
74
|
+
flush,
|
|
75
|
+
stop: () => beat.stop(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** One batch, as OTLP/JSON spells it — trace and span ids stay hex, times are nanos. */
|
|
80
|
+
function payload(service: string, spans: FinishedSpan[]) {
|
|
81
|
+
return {
|
|
82
|
+
resourceSpans: [
|
|
83
|
+
{
|
|
84
|
+
resource: { attributes: [{ key: 'service.name', value: { stringValue: service } }] },
|
|
85
|
+
scopeSpans: [
|
|
86
|
+
{
|
|
87
|
+
scope: { name: '@fougere/observability' },
|
|
88
|
+
spans: spans.map((span) => ({
|
|
89
|
+
traceId: span.traceId,
|
|
90
|
+
spanId: span.spanId,
|
|
91
|
+
...(span.parentId ? { parentSpanId: span.parentId } : {}),
|
|
92
|
+
name: `${span.entity}.${span.operation}`,
|
|
93
|
+
kind: 1,
|
|
94
|
+
startTimeUnixNano: nanos(span.startedAt),
|
|
95
|
+
endTimeUnixNano: nanos(span.startedAt + span.ms),
|
|
96
|
+
status: span.error ? { code: ERROR, message: span.error } : { code: OK },
|
|
97
|
+
})),
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Epoch milliseconds → the int64 nanoseconds OTLP wants, as a string. */
|
|
106
|
+
function nanos(ms: number): string {
|
|
107
|
+
return `${Math.round(ms * 1e6)}`;
|
|
108
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `traceparent` — the one form a trace takes between two processes (W3C Trace Context).
|
|
3
|
+
*
|
|
4
|
+
* A standard rather than ours, so any collector reads it. It rides `InvocationContext.trace`
|
|
5
|
+
* and never a header: a header is HTTP's alone, and the same call over a socket would have
|
|
6
|
+
* arrived untraced.
|
|
7
|
+
*
|
|
8
|
+
* Pure — no runtime, no node builtin. What holds the CURRENT trace is `index.ts`, and the
|
|
9
|
+
* line between the two is the line between a format and the thing that carries it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** A step in a trace, as both halves of a split agree on it. */
|
|
13
|
+
export interface SpanContext {
|
|
14
|
+
/** The whole call, across every process. 32 hex. */
|
|
15
|
+
traceId: string;
|
|
16
|
+
/** This step. 16 hex. */
|
|
17
|
+
spanId: string;
|
|
18
|
+
/** Whether the collector was asked to keep it. */
|
|
19
|
+
sampled: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const TRACEPARENT = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/;
|
|
23
|
+
const NO_TRACE = '0'.repeat(32);
|
|
24
|
+
const NO_SPAN = '0'.repeat(16);
|
|
25
|
+
|
|
26
|
+
/** The header a caller writes so the other side joins this trace. */
|
|
27
|
+
export function traceparentOf(span: SpanContext): string {
|
|
28
|
+
return `00-${span.traceId}-${span.spanId}-${span.sampled ? '01' : '00'}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The header as a context, or nothing — a malformed one is ignored, never fatal. */
|
|
32
|
+
export function parseTraceparent(header: string | undefined): SpanContext | undefined {
|
|
33
|
+
const found = typeof header === 'string' ? TRACEPARENT.exec(header.trim()) : null;
|
|
34
|
+
if (!found) return undefined;
|
|
35
|
+
|
|
36
|
+
const [, traceId, spanId, flags] = found;
|
|
37
|
+
if (traceId === NO_TRACE || spanId === NO_SPAN) return undefined;
|
|
38
|
+
return { traceId, spanId, sampled: (parseInt(flags, 16) & 1) === 1 };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const HEX = '0123456789abcdef';
|
|
42
|
+
|
|
43
|
+
/** A trace id (16) or a span id (8), as the spec sizes them. */
|
|
44
|
+
export function randomHex(bytes: number): string {
|
|
45
|
+
const buffer = new Uint8Array(bytes);
|
|
46
|
+
crypto.getRandomValues(buffer);
|
|
47
|
+
let out = '';
|
|
48
|
+
for (const byte of buffer) out += HEX[byte >> 4] + HEX[byte & 15];
|
|
49
|
+
return out;
|
|
50
|
+
}
|