@vercube/telemetry 1.3.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/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/Api.d.mts +1 -0
- package/dist/Api.mjs +2 -0
- package/dist/Attributes-QVHj8ZgV.mjs +48 -0
- package/dist/Attributes.d.mts +47 -0
- package/dist/Attributes.mjs +2 -0
- package/dist/Instrument.d.mts +132 -0
- package/dist/Instrument.mjs +103 -0
- package/dist/Otlp.d.mts +31 -0
- package/dist/Otlp.mjs +52 -0
- package/dist/Sdk.d.mts +156 -0
- package/dist/Sdk.mjs +229 -0
- package/dist/SpanUtils-t3NCeswB.mjs +147 -0
- package/dist/Testing.d.mts +53 -0
- package/dist/Testing.mjs +63 -0
- package/dist/VercubeContextManager-n4jHlB-w.mjs +197 -0
- package/dist/index.d.mts +125 -0
- package/dist/index.mjs +1085 -0
- package/package.json +99 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { ROOT_CONTEXT, TraceFlags, createTraceState, isSpanContextValid, trace } from "@opentelemetry/api";
|
|
2
|
+
//#region src/Common/Propagation.ts
|
|
3
|
+
/** Header carrying the sampled parent span, per W3C Trace Context. */
|
|
4
|
+
const TRACEPARENT_HEADER = "traceparent";
|
|
5
|
+
/** Header carrying vendor-specific trace state, per W3C Trace Context. */
|
|
6
|
+
const TRACESTATE_HEADER = "tracestate";
|
|
7
|
+
/**
|
|
8
|
+
* `version-traceid-spanid-flags`, with anything after the flags tolerated so
|
|
9
|
+
* that a future version of the spec still parses as version `00` would.
|
|
10
|
+
*/
|
|
11
|
+
const TRACEPARENT_REGEX = /^([\da-f]{2})-([\da-f]{32})-([\da-f]{16})-([\da-f]{2})(-.*)?$/;
|
|
12
|
+
/** All-zero ids are explicitly invalid. */
|
|
13
|
+
const INVALID_TRACE_ID = "0".repeat(32);
|
|
14
|
+
const INVALID_SPAN_ID = "0".repeat(16);
|
|
15
|
+
/**
|
|
16
|
+
* W3C Trace Context propagator.
|
|
17
|
+
*
|
|
18
|
+
* The reference implementation lives in `@opentelemetry/core`, which is an SDK
|
|
19
|
+
* package. Propagation has to work with nothing but `@opentelemetry/api`
|
|
20
|
+
* installed - a service that only forwards trace context should not need an
|
|
21
|
+
* exporter - so the format is implemented here instead. It is two headers and a
|
|
22
|
+
* fixed-width string.
|
|
23
|
+
*/
|
|
24
|
+
var W3CTraceContextPropagator = class {
|
|
25
|
+
/**
|
|
26
|
+
* Writes the active span context into the carrier.
|
|
27
|
+
*
|
|
28
|
+
* @param context - The context to read the active span from
|
|
29
|
+
* @param carrier - The carrier to write into
|
|
30
|
+
* @param setter - Writer for the carrier
|
|
31
|
+
*/
|
|
32
|
+
inject(context, carrier, setter) {
|
|
33
|
+
const spanContext = trace.getSpanContext(context);
|
|
34
|
+
if (!spanContext || !isSpanContextValid(spanContext)) return;
|
|
35
|
+
const flags = (spanContext.traceFlags ?? TraceFlags.NONE).toString(16).padStart(2, "0");
|
|
36
|
+
setter.set(carrier, TRACEPARENT_HEADER, `00-${spanContext.traceId}-${spanContext.spanId}-${flags}`);
|
|
37
|
+
const state = spanContext.traceState?.serialize();
|
|
38
|
+
if (state) setter.set(carrier, TRACESTATE_HEADER, state);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Reads a remote span context out of the carrier.
|
|
42
|
+
*
|
|
43
|
+
* @param context - The context to extend
|
|
44
|
+
* @param carrier - The carrier to read from
|
|
45
|
+
* @param getter - Reader for the carrier
|
|
46
|
+
* @returns The context, with the remote span context attached when the headers were valid
|
|
47
|
+
*/
|
|
48
|
+
extract(context, carrier, getter) {
|
|
49
|
+
const header = firstValue(getter.get(carrier, TRACEPARENT_HEADER));
|
|
50
|
+
if (header === void 0) return context;
|
|
51
|
+
const match = TRACEPARENT_REGEX.exec(header.trim());
|
|
52
|
+
if (!match) return context;
|
|
53
|
+
const [, version, traceId, spanId, flags] = match;
|
|
54
|
+
if (version === "ff" || traceId === INVALID_TRACE_ID || spanId === INVALID_SPAN_ID) return context;
|
|
55
|
+
const state = firstValue(getter.get(carrier, TRACESTATE_HEADER));
|
|
56
|
+
return trace.setSpanContext(context, {
|
|
57
|
+
traceId,
|
|
58
|
+
spanId,
|
|
59
|
+
traceFlags: Number.parseInt(flags, 16) & TraceFlags.SAMPLED,
|
|
60
|
+
isRemote: true,
|
|
61
|
+
traceState: state ? createTraceState(state) : void 0
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The header names this propagator manages.
|
|
66
|
+
*
|
|
67
|
+
* @returns The list of header names
|
|
68
|
+
*/
|
|
69
|
+
fields() {
|
|
70
|
+
return [TRACEPARENT_HEADER, TRACESTATE_HEADER];
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Reads headers off a `Headers` object or a plain record.
|
|
75
|
+
*/
|
|
76
|
+
const headersGetter = {
|
|
77
|
+
keys(carrier) {
|
|
78
|
+
if (carrier instanceof Headers) return [...carrier.keys()];
|
|
79
|
+
return carrier ? Object.keys(carrier) : [];
|
|
80
|
+
},
|
|
81
|
+
get(carrier, key) {
|
|
82
|
+
if (carrier instanceof Headers) return carrier.get(key) ?? void 0;
|
|
83
|
+
const value = carrier?.[key];
|
|
84
|
+
return typeof value === "string" ? value : void 0;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Writes headers to a `Headers` object or a plain record.
|
|
89
|
+
*/
|
|
90
|
+
const headersSetter = { set(carrier, key, value) {
|
|
91
|
+
if (carrier instanceof Headers) {
|
|
92
|
+
carrier.set(key, value);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (carrier && typeof carrier === "object") carrier[key] = value;
|
|
96
|
+
} };
|
|
97
|
+
/**
|
|
98
|
+
* Normalizes the `string | string[] | undefined` a getter may return.
|
|
99
|
+
*
|
|
100
|
+
* @param value - The raw getter result
|
|
101
|
+
* @returns The first value, or undefined
|
|
102
|
+
*/
|
|
103
|
+
function firstValue(value) {
|
|
104
|
+
if (Array.isArray(value)) return value[0];
|
|
105
|
+
return value ?? void 0;
|
|
106
|
+
}
|
|
107
|
+
//#endregion
|
|
108
|
+
//#region src/Context/VercubeContextManager.ts
|
|
109
|
+
/**
|
|
110
|
+
* OpenTelemetry context manager backed by Vercube's request context.
|
|
111
|
+
*
|
|
112
|
+
* OpenTelemetry ships `AsyncLocalStorageContextManager`, which allocates its
|
|
113
|
+
* own `AsyncLocalStorage`. Vercube already opens one frame per request for
|
|
114
|
+
* {@link RequestContext}, and a second async-context frame costs a measurable
|
|
115
|
+
* slice of throughput on a framework whose fast path is otherwise
|
|
116
|
+
* allocation-free. This manager stores the OpenTelemetry context inside the
|
|
117
|
+
* frame that already exists.
|
|
118
|
+
*
|
|
119
|
+
* Nested frames share the request's root frame, so values written with
|
|
120
|
+
* `RequestContext.set()` from inside a span are still visible after it ends.
|
|
121
|
+
*/
|
|
122
|
+
var VercubeContextManager = class {
|
|
123
|
+
/** The request context this manager stores OpenTelemetry contexts in. */
|
|
124
|
+
fRequestContext;
|
|
125
|
+
/** Whether the manager is currently active. */
|
|
126
|
+
fEnabled = false;
|
|
127
|
+
/**
|
|
128
|
+
* @param requestContext - The application's request context service
|
|
129
|
+
*/
|
|
130
|
+
constructor(requestContext) {
|
|
131
|
+
this.fRequestContext = requestContext;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* The context active in the current async frame.
|
|
135
|
+
*
|
|
136
|
+
* @returns The active context, or the root context outside any frame
|
|
137
|
+
*/
|
|
138
|
+
active() {
|
|
139
|
+
if (!this.fEnabled) return ROOT_CONTEXT;
|
|
140
|
+
return this.fRequestContext.getOtelContext() ?? ROOT_CONTEXT;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Runs `fn` with `context` active.
|
|
144
|
+
*
|
|
145
|
+
* The result is passed through unchanged, so a synchronous callback stays
|
|
146
|
+
* synchronous - the request fast path depends on it.
|
|
147
|
+
*
|
|
148
|
+
* @param context - The context to activate
|
|
149
|
+
* @param fn - The function to run
|
|
150
|
+
* @param thisArg - `this` for the call
|
|
151
|
+
* @param args - Arguments for the call
|
|
152
|
+
* @returns Whatever `fn` returned
|
|
153
|
+
*/
|
|
154
|
+
with(context, fn, thisArg, ...args) {
|
|
155
|
+
if (!this.fEnabled) return fn.call(thisArg, ...args);
|
|
156
|
+
return this.fRequestContext.runWithOtelContext(context, () => fn.call(thisArg, ...args));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Binds a target to a context.
|
|
160
|
+
*
|
|
161
|
+
* Only functions are bound. OpenTelemetry's own manager additionally rebinds
|
|
162
|
+
* every listener of an `EventEmitter`; nothing in Vercube relies on that, and
|
|
163
|
+
* doing it would mean reaching into Node's emitter internals.
|
|
164
|
+
*
|
|
165
|
+
* @param context - The context to bind to
|
|
166
|
+
* @param target - The value to bind
|
|
167
|
+
* @returns The bound value, or the value unchanged when it is not a function
|
|
168
|
+
*/
|
|
169
|
+
bind(context, target) {
|
|
170
|
+
if (typeof target !== "function") return target;
|
|
171
|
+
const fn = target;
|
|
172
|
+
const activate = (run) => this.with(context, run);
|
|
173
|
+
return function bound(...args) {
|
|
174
|
+
return activate(() => fn.apply(this, args));
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Activates the manager.
|
|
179
|
+
*
|
|
180
|
+
* @returns This manager
|
|
181
|
+
*/
|
|
182
|
+
enable() {
|
|
183
|
+
this.fEnabled = true;
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Deactivates the manager. Everything falls back to the root context.
|
|
188
|
+
*
|
|
189
|
+
* @returns This manager
|
|
190
|
+
*/
|
|
191
|
+
disable() {
|
|
192
|
+
this.fEnabled = false;
|
|
193
|
+
return this;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
//#endregion
|
|
197
|
+
export { headersSetter as i, W3CTraceContextPropagator as n, headersGetter as r, VercubeContextManager as t };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { Context, Meter, Span, SpanOptions, Tracer } from "@opentelemetry/api";
|
|
2
|
+
import { App, BasePlugin, ConfigTypes, TelemetryTypes } from "@vercube/core";
|
|
3
|
+
//#region src/Common/Telemetry.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Dependency-injection token and public API for telemetry.
|
|
6
|
+
*
|
|
7
|
+
* Injected the same way as `Logger`:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* class InvoiceService {
|
|
11
|
+
* @Inject(Telemetry)
|
|
12
|
+
* private gTelemetry!: Telemetry;
|
|
13
|
+
*
|
|
14
|
+
* public async refund(id: string) {
|
|
15
|
+
* return this.gTelemetry.span('invoice.refund', (span) => {
|
|
16
|
+
* span.setAttribute('invoice.id', id);
|
|
17
|
+
* return this.doRefund(id);
|
|
18
|
+
* });
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* The token is only bound when telemetry is enabled, so inject it with
|
|
24
|
+
* `@InjectOptional` from code that must also run without it.
|
|
25
|
+
*/
|
|
26
|
+
export declare abstract class Telemetry {
|
|
27
|
+
/** Tracer for the application's own spans. */
|
|
28
|
+
abstract get tracer(): Tracer;
|
|
29
|
+
/** Meter for the application's own instruments. */
|
|
30
|
+
abstract get meter(): Meter;
|
|
31
|
+
/**
|
|
32
|
+
* Runs `fn` inside a new span that ends when the work settles.
|
|
33
|
+
*
|
|
34
|
+
* The return value is passed through unchanged, so wrapping synchronous code
|
|
35
|
+
* does not make it asynchronous.
|
|
36
|
+
*
|
|
37
|
+
* @param name - Span name
|
|
38
|
+
* @param fn - The work to trace
|
|
39
|
+
* @param options - Span kind, attributes and links
|
|
40
|
+
* @returns Whatever `fn` returned
|
|
41
|
+
*/
|
|
42
|
+
abstract span<T>(name: string, fn: (span: Span) => T, options?: SpanOptions): T;
|
|
43
|
+
/** The span currently active on this async execution path, if any. */
|
|
44
|
+
abstract activeSpan(): Span | undefined;
|
|
45
|
+
/** Trace id of the active span, in lowercase hex. */
|
|
46
|
+
abstract get traceId(): string | undefined;
|
|
47
|
+
/** Span id of the active span, in lowercase hex. */
|
|
48
|
+
abstract get spanId(): string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Writes W3C trace context headers for the active span into a carrier, so a
|
|
51
|
+
* downstream service continues the same trace.
|
|
52
|
+
*
|
|
53
|
+
* @param carrier - `Headers` or a plain header record to write into
|
|
54
|
+
*/
|
|
55
|
+
abstract inject(carrier: Headers | Record<string, string>): void;
|
|
56
|
+
/**
|
|
57
|
+
* Reads W3C trace context headers into a context usable as a span parent.
|
|
58
|
+
*
|
|
59
|
+
* @param headers - Incoming headers
|
|
60
|
+
* @returns A context carrying the remote parent
|
|
61
|
+
*/
|
|
62
|
+
abstract extract(headers: Headers | Record<string, string>): Context;
|
|
63
|
+
/**
|
|
64
|
+
* Registers work to run on {@link Telemetry.flush}.
|
|
65
|
+
*
|
|
66
|
+
* @param flush - Flushes whatever the caller has buffered
|
|
67
|
+
*/
|
|
68
|
+
abstract onFlush(flush: () => Promise<void>): void;
|
|
69
|
+
/**
|
|
70
|
+
* Pushes out everything buffered by batching drains and exporters.
|
|
71
|
+
*
|
|
72
|
+
* Call it before a process that may be frozen or killed goes away - a
|
|
73
|
+
* serverless invocation, a graceful shutdown - or telemetry produced at the
|
|
74
|
+
* very end of its life never leaves.
|
|
75
|
+
*/
|
|
76
|
+
abstract flush(): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/Plugins/TelemetryPlugin.d.ts
|
|
80
|
+
/**
|
|
81
|
+
* Activates OpenTelemetry instrumentation for the application.
|
|
82
|
+
*
|
|
83
|
+
* Register it in `vercube.config.ts`:
|
|
84
|
+
*
|
|
85
|
+
* ```ts
|
|
86
|
+
* export default defineConfig({
|
|
87
|
+
* telemetry: true,
|
|
88
|
+
* plugins: [TelemetryPlugin],
|
|
89
|
+
* });
|
|
90
|
+
* ```
|
|
91
|
+
*
|
|
92
|
+
* The plugin only wires the OpenTelemetry **API**: it registers a context
|
|
93
|
+
* manager and a W3C propagator, binds the {@link Telemetry} token and installs
|
|
94
|
+
* the hooks core calls into. Producing actual spans additionally requires a
|
|
95
|
+
* `TracerProvider`, which either the application registers through the standard
|
|
96
|
+
* OpenTelemetry SDK, `@vercube/telemetry/sdk`, or `@vercube/devtools`.
|
|
97
|
+
*
|
|
98
|
+
* Options given at registration time win over the `telemetry` field of the
|
|
99
|
+
* application config.
|
|
100
|
+
*/
|
|
101
|
+
export declare class TelemetryPlugin extends BasePlugin<TelemetryTypes.Options> {
|
|
102
|
+
/** @inheritdoc */
|
|
103
|
+
name: string;
|
|
104
|
+
/**
|
|
105
|
+
* Starts watching container construction.
|
|
106
|
+
*
|
|
107
|
+
* This runs while the config is still being loaded, which is the only phase
|
|
108
|
+
* early enough: by the time `use()` runs the container has already built a
|
|
109
|
+
* good part of the application. Registering the plugin through
|
|
110
|
+
* `defineConfig({ plugins })` rather than `app.addPlugin()` is therefore what
|
|
111
|
+
* makes bootstrap spans complete.
|
|
112
|
+
*
|
|
113
|
+
* @param config - The merged configuration
|
|
114
|
+
* @param options - Options overriding the `telemetry` config field
|
|
115
|
+
*/
|
|
116
|
+
configure(config: ConfigTypes.Config, options?: TelemetryTypes.Options): void;
|
|
117
|
+
/**
|
|
118
|
+
* Installs telemetry into the running application.
|
|
119
|
+
*
|
|
120
|
+
* @param app - The application
|
|
121
|
+
* @param options - Options overriding the `telemetry` config field
|
|
122
|
+
*/
|
|
123
|
+
use(app: App, options?: TelemetryTypes.Options): void | Promise<void>;
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|