@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/dist/Sdk.d.mts ADDED
@@ -0,0 +1,156 @@
1
+ import { Context } from "@opentelemetry/api";
2
+ import { TelemetryTypes } from "@vercube/core";
3
+ import { AggregationTemporality, IMetricReader, IMetricReader as IMetricReader$1, InMemoryMetricExporter, MeterProvider, MeterProvider as MeterProvider$1, PeriodicExportingMetricReader, PushMetricExporter, ResourceMetrics } from "@opentelemetry/sdk-metrics";
4
+ import { BatchSpanProcessor, NodeTracerProvider, NodeTracerProvider as NodeTracerProvider$1, Sampler, SpanExporter as SpanExporter$1, SpanProcessor as SpanProcessor$1 } from "@opentelemetry/sdk-trace-node";
5
+ import { BasicTracerProvider, InMemorySpanExporter, ReadableSpan, ReadableSpan as ReadableSpan$1, SimpleSpanProcessor, Span as SdkSpan, Span as Span$1, SpanExporter, SpanProcessor, SpanProcessor as SpanProcessor$2 } from "@opentelemetry/sdk-trace-base";
6
+ //#region src/Sdk/Composite.d.ts
7
+ /**
8
+ * A span processor that other processors can be added to after the tracer
9
+ * provider has already been built.
10
+ *
11
+ * `BasicTracerProvider` takes its processors at construction and never exposes
12
+ * them again, which makes "the application configured OTLP export and devtools
13
+ * also wants to see the spans" unsolvable: whoever builds the provider second
14
+ * wins. Registering a single composite instead, and letting packages add to it
15
+ * whenever they initialise, removes the ordering problem entirely.
16
+ */
17
+ export declare class CompositeSpanProcessor implements SpanProcessor$2 {
18
+ /** The processors this one fans out to. */
19
+ private readonly fProcessors;
20
+ /**
21
+ * Adds a processor.
22
+ *
23
+ * @param processor - The processor to add
24
+ * @returns A function that removes it again
25
+ */
26
+ add(processor: SpanProcessor$2): () => void;
27
+ /** Number of registered processors. */
28
+ get size(): number;
29
+ /** @inheritdoc */
30
+ onStart(span: Span$1, parentContext: Context): void;
31
+ /** @inheritdoc */
32
+ onEnd(span: ReadableSpan$1): void;
33
+ /** @inheritdoc */
34
+ forceFlush(): Promise<void>;
35
+ /** @inheritdoc */
36
+ shutdown(): Promise<void>;
37
+ }
38
+ //#endregion
39
+ //#region src/Sdk.d.ts
40
+ /**
41
+ * Options for {@link startNodeTelemetry}.
42
+ */
43
+ export interface NodeTelemetryOptions {
44
+ /** `service.name` resource attribute. */
45
+ serviceName?: string;
46
+ /** `service.version` resource attribute. */
47
+ serviceVersion?: string;
48
+ /** `deployment.environment.name` resource attribute. */
49
+ environment?: string;
50
+ /** Extra resource attributes. */
51
+ resourceAttributes?: Record<string, string | number | boolean>;
52
+ /** Head sampling strategy. Defaults to `parent`. */
53
+ sampler?: TelemetryTypes.Sampler;
54
+ /**
55
+ * OTLP/HTTP endpoint, e.g. `http://localhost:4318`.
56
+ *
57
+ * Defaults to `OTEL_EXPORTER_OTLP_ENDPOINT`. When neither is set and no
58
+ * `exporter` or `spanProcessors` are given, nothing is exported.
59
+ */
60
+ endpoint?: string;
61
+ /** Headers sent with every OTLP request, for authenticated collectors. */
62
+ headers?: Record<string, string>;
63
+ /** A ready-made exporter, used instead of the OTLP one. */
64
+ exporter?: SpanExporter$1;
65
+ /** Extra span processors, appended after the exporting one. */
66
+ spanProcessors?: SpanProcessor$1[];
67
+ }
68
+ /**
69
+ * Handle returned by {@link startNodeTelemetry}.
70
+ */
71
+ export interface NodeTelemetry {
72
+ /** The registered provider. */
73
+ provider: NodeTracerProvider$1;
74
+ /** Flushes pending spans and shuts the provider down. */
75
+ shutdown(): Promise<void>;
76
+ }
77
+ /**
78
+ * Wires a Node tracer provider and registers it as the global one.
79
+ *
80
+ * `@vercube/telemetry` on its own only speaks the OpenTelemetry **API**, which
81
+ * means no spans are recorded until something registers a provider. This is the
82
+ * batteries-included way to do that:
83
+ *
84
+ * ```ts
85
+ * import { startNodeTelemetry } from '@vercube/telemetry/sdk';
86
+ *
87
+ * const telemetry = await startNodeTelemetry({
88
+ * serviceName: 'checkout',
89
+ * endpoint: 'http://localhost:4318',
90
+ * });
91
+ * ```
92
+ *
93
+ * The context manager and the propagator are deliberately **not** registered
94
+ * here: `TelemetryPlugin` already installed ones backed by Vercube's request
95
+ * context, and letting the SDK replace them would open a second
96
+ * `AsyncLocalStorage` per request.
97
+ *
98
+ * Call it before `createApp()` so bootstrap work is traced too.
99
+ *
100
+ * @param options - Resource, sampling and exporter settings
101
+ * @returns Handle to the registered provider
102
+ */
103
+ export declare function startNodeTelemetry(options?: NodeTelemetryOptions): Promise<NodeTelemetry>;
104
+ /**
105
+ * Adds a span processor, whether or not a tracer provider exists yet.
106
+ *
107
+ * This is how a package can see spans without owning the SDK setup: devtools
108
+ * adds its recorder here, and it works the same whether the application called
109
+ * {@link startNodeTelemetry} first, later, or never.
110
+ *
111
+ * @param processor - The processor to add
112
+ * @returns A function that removes it again
113
+ */
114
+ export declare function addSpanProcessor(processor: SpanProcessor$1): () => void;
115
+ /**
116
+ * Registers a tracer provider around the shared processor, unless one has
117
+ * already been created here.
118
+ *
119
+ * @param options - Resource and sampling settings, used only on first call
120
+ * @returns Handle to the registered provider
121
+ */
122
+ export declare function ensureTracerProvider(options?: NodeTelemetryOptions): NodeTelemetry;
123
+ /**
124
+ * Registers a metric reader.
125
+ *
126
+ * Must be called before {@link ensureMeterProvider}. `MeterProvider` takes its
127
+ * readers at construction, a reader cannot be bound to a second provider, and
128
+ * the metrics API has no proxy meter - so the provider is built once, and
129
+ * instruments have to be created after it exists.
130
+ *
131
+ * Registering the same reader twice is a no-op, which matters because a plugin
132
+ * config phase can run more than once per process.
133
+ *
134
+ * @param reader - The reader to register
135
+ * @returns A function that unregisters it again
136
+ */
137
+ export declare function addMetricReader(reader: IMetricReader$1): () => void;
138
+ /**
139
+ * Registers a meter provider around the readers added so far, unless one has
140
+ * already been created here.
141
+ *
142
+ * @param options - Resource settings, used only on first call
143
+ * @returns The registered provider
144
+ */
145
+ export declare function ensureMeterProvider(options?: NodeTelemetryOptions): MeterProvider$1;
146
+ /**
147
+ * Drops every registered provider and reader.
148
+ *
149
+ * Meant for tests and for tearing an application down; a process that builds a
150
+ * second application afterwards starts from a clean slate.
151
+ *
152
+ * @returns Resolves once the providers have shut down
153
+ */
154
+ export declare function resetTelemetryProviders(): Promise<void>;
155
+ //#endregion
156
+ export { AggregationTemporality, BasicTracerProvider, BatchSpanProcessor, type IMetricReader, InMemoryMetricExporter, InMemorySpanExporter, MeterProvider, NodeTracerProvider, PeriodicExportingMetricReader, type PushMetricExporter, type ReadableSpan, type ResourceMetrics, type Sampler, type SdkSpan, SimpleSpanProcessor, type SpanExporter, type SpanProcessor };
package/dist/Sdk.mjs ADDED
@@ -0,0 +1,229 @@
1
+ import { createOtlpTraceExporter } from "./Otlp.mjs";
2
+ import { metrics, trace } from "@opentelemetry/api";
3
+ import { resourceFromAttributes } from "@opentelemetry/resources";
4
+ import { AggregationTemporality, InMemoryMetricExporter, MeterProvider, MeterProvider as MeterProvider$1, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
5
+ import { AlwaysOffSampler, AlwaysOnSampler, BatchSpanProcessor, BatchSpanProcessor as BatchSpanProcessor$1, NodeTracerProvider, NodeTracerProvider as NodeTracerProvider$1, ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-node";
6
+ import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
7
+ //#region src/Sdk/Composite.ts
8
+ /**
9
+ * A span processor that other processors can be added to after the tracer
10
+ * provider has already been built.
11
+ *
12
+ * `BasicTracerProvider` takes its processors at construction and never exposes
13
+ * them again, which makes "the application configured OTLP export and devtools
14
+ * also wants to see the spans" unsolvable: whoever builds the provider second
15
+ * wins. Registering a single composite instead, and letting packages add to it
16
+ * whenever they initialise, removes the ordering problem entirely.
17
+ */
18
+ var CompositeSpanProcessor = class {
19
+ /** The processors this one fans out to. */
20
+ fProcessors = [];
21
+ /**
22
+ * Adds a processor.
23
+ *
24
+ * @param processor - The processor to add
25
+ * @returns A function that removes it again
26
+ */
27
+ add(processor) {
28
+ this.fProcessors.push(processor);
29
+ return () => {
30
+ const index = this.fProcessors.indexOf(processor);
31
+ if (index !== -1) this.fProcessors.splice(index, 1);
32
+ };
33
+ }
34
+ /** Number of registered processors. */
35
+ get size() {
36
+ return this.fProcessors.length;
37
+ }
38
+ /** @inheritdoc */
39
+ onStart(span, parentContext) {
40
+ for (const processor of this.fProcessors) processor.onStart(span, parentContext);
41
+ }
42
+ /** @inheritdoc */
43
+ onEnd(span) {
44
+ for (const processor of this.fProcessors) processor.onEnd(span);
45
+ }
46
+ /** @inheritdoc */
47
+ async forceFlush() {
48
+ await Promise.all(this.fProcessors.map((processor) => processor.forceFlush()));
49
+ }
50
+ /** @inheritdoc */
51
+ async shutdown() {
52
+ await Promise.all(this.fProcessors.map((processor) => processor.shutdown()));
53
+ }
54
+ };
55
+ //#endregion
56
+ //#region src/Sdk.ts
57
+ /**
58
+ * Wires a Node tracer provider and registers it as the global one.
59
+ *
60
+ * `@vercube/telemetry` on its own only speaks the OpenTelemetry **API**, which
61
+ * means no spans are recorded until something registers a provider. This is the
62
+ * batteries-included way to do that:
63
+ *
64
+ * ```ts
65
+ * import { startNodeTelemetry } from '@vercube/telemetry/sdk';
66
+ *
67
+ * const telemetry = await startNodeTelemetry({
68
+ * serviceName: 'checkout',
69
+ * endpoint: 'http://localhost:4318',
70
+ * });
71
+ * ```
72
+ *
73
+ * The context manager and the propagator are deliberately **not** registered
74
+ * here: `TelemetryPlugin` already installed ones backed by Vercube's request
75
+ * context, and letting the SDK replace them would open a second
76
+ * `AsyncLocalStorage` per request.
77
+ *
78
+ * Call it before `createApp()` so bootstrap work is traced too.
79
+ *
80
+ * @param options - Resource, sampling and exporter settings
81
+ * @returns Handle to the registered provider
82
+ */
83
+ async function startNodeTelemetry(options = {}) {
84
+ const exporter = options.exporter ?? await createOtlpExporter(options);
85
+ if (exporter) addSpanProcessor(new BatchSpanProcessor$1(exporter));
86
+ for (const processor of options.spanProcessors ?? []) addSpanProcessor(processor);
87
+ return ensureTracerProvider(options);
88
+ }
89
+ /** The one processor every provider created here is built around. */
90
+ const composite = new CompositeSpanProcessor();
91
+ /** The provider created by {@link ensureTracerProvider}, if any. */
92
+ let started;
93
+ /**
94
+ * Adds a span processor, whether or not a tracer provider exists yet.
95
+ *
96
+ * This is how a package can see spans without owning the SDK setup: devtools
97
+ * adds its recorder here, and it works the same whether the application called
98
+ * {@link startNodeTelemetry} first, later, or never.
99
+ *
100
+ * @param processor - The processor to add
101
+ * @returns A function that removes it again
102
+ */
103
+ function addSpanProcessor(processor) {
104
+ return composite.add(processor);
105
+ }
106
+ /**
107
+ * Registers a tracer provider around the shared processor, unless one has
108
+ * already been created here.
109
+ *
110
+ * @param options - Resource and sampling settings, used only on first call
111
+ * @returns Handle to the registered provider
112
+ */
113
+ function ensureTracerProvider(options = {}) {
114
+ if (started) return started;
115
+ const provider = new NodeTracerProvider$1({
116
+ resource: resourceFromAttributes({
117
+ "service.name": options.serviceName ?? process.env.OTEL_SERVICE_NAME ?? "vercube",
118
+ "service.version": options.serviceVersion,
119
+ "deployment.environment.name": options.environment ?? process.env.NODE_ENV,
120
+ ...options.resourceAttributes
121
+ }),
122
+ sampler: toSampler(options.sampler),
123
+ spanProcessors: [composite]
124
+ });
125
+ provider.register({
126
+ contextManager: null,
127
+ propagator: null
128
+ });
129
+ started = {
130
+ provider,
131
+ shutdown: async () => {
132
+ started = void 0;
133
+ await provider.shutdown();
134
+ trace.disable();
135
+ }
136
+ };
137
+ return started;
138
+ }
139
+ /**
140
+ * Builds the OTLP/HTTP exporter, when an endpoint is configured.
141
+ *
142
+ * @param options - The telemetry options
143
+ * @returns The exporter, or undefined when no endpoint is configured
144
+ */
145
+ async function createOtlpExporter(options) {
146
+ const endpoint = options.endpoint ?? process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
147
+ if (!endpoint) return;
148
+ return createOtlpTraceExporter({
149
+ endpoint,
150
+ headers: options.headers
151
+ });
152
+ }
153
+ /**
154
+ * Translates the framework's sampler shorthand into an SDK sampler.
155
+ *
156
+ * @param sampler - The configured strategy
157
+ * @returns The SDK sampler
158
+ */
159
+ function toSampler(sampler = "parent") {
160
+ if (sampler === "always") return new AlwaysOnSampler();
161
+ if (sampler === "never") return new AlwaysOffSampler();
162
+ if (sampler === "parent") return new ParentBasedSampler({ root: new AlwaysOnSampler() });
163
+ return new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(sampler.ratio) });
164
+ }
165
+ /** Metric readers registered before the meter provider was built. */
166
+ const metricReaders = [];
167
+ /** The meter provider created by {@link ensureMeterProvider}, if any. */
168
+ let meterProvider;
169
+ /**
170
+ * Registers a metric reader.
171
+ *
172
+ * Must be called before {@link ensureMeterProvider}. `MeterProvider` takes its
173
+ * readers at construction, a reader cannot be bound to a second provider, and
174
+ * the metrics API has no proxy meter - so the provider is built once, and
175
+ * instruments have to be created after it exists.
176
+ *
177
+ * Registering the same reader twice is a no-op, which matters because a plugin
178
+ * config phase can run more than once per process.
179
+ *
180
+ * @param reader - The reader to register
181
+ * @returns A function that unregisters it again
182
+ */
183
+ function addMetricReader(reader) {
184
+ if (!metricReaders.includes(reader)) metricReaders.push(reader);
185
+ return () => {
186
+ const index = metricReaders.indexOf(reader);
187
+ if (index !== -1) metricReaders.splice(index, 1);
188
+ };
189
+ }
190
+ /**
191
+ * Registers a meter provider around the readers added so far, unless one has
192
+ * already been created here.
193
+ *
194
+ * @param options - Resource settings, used only on first call
195
+ * @returns The registered provider
196
+ */
197
+ function ensureMeterProvider(options = {}) {
198
+ if (meterProvider) return meterProvider;
199
+ meterProvider = new MeterProvider$1({
200
+ resource: resourceFromAttributes({
201
+ "service.name": options.serviceName ?? process.env.OTEL_SERVICE_NAME ?? "vercube",
202
+ "service.version": options.serviceVersion,
203
+ "deployment.environment.name": options.environment ?? process.env.NODE_ENV,
204
+ ...options.resourceAttributes
205
+ }),
206
+ readers: metricReaders
207
+ });
208
+ metrics.setGlobalMeterProvider(meterProvider);
209
+ return meterProvider;
210
+ }
211
+ /**
212
+ * Drops every registered provider and reader.
213
+ *
214
+ * Meant for tests and for tearing an application down; a process that builds a
215
+ * second application afterwards starts from a clean slate.
216
+ *
217
+ * @returns Resolves once the providers have shut down
218
+ */
219
+ async function resetTelemetryProviders() {
220
+ const provider = meterProvider;
221
+ meterProvider = void 0;
222
+ metricReaders.length = 0;
223
+ started = void 0;
224
+ metrics.disable();
225
+ trace.disable();
226
+ await provider?.shutdown().catch(() => {});
227
+ }
228
+ //#endregion
229
+ export { AggregationTemporality, BasicTracerProvider, BatchSpanProcessor, CompositeSpanProcessor, InMemoryMetricExporter, InMemorySpanExporter, MeterProvider, NodeTracerProvider, PeriodicExportingMetricReader, SimpleSpanProcessor, addMetricReader, addSpanProcessor, ensureMeterProvider, ensureTracerProvider, resetTelemetryProviders, startNodeTelemetry };
@@ -0,0 +1,147 @@
1
+ import { r as HTTP_RESPONSE_STATUS_CODE, t as ERROR_TYPE } from "./Attributes-QVHj8ZgV.mjs";
2
+ import { SpanStatusCode, context, trace } from "@opentelemetry/api";
3
+ //#region src/Common/SpanUtils.ts
4
+ /** Lowest HTTP status code that marks a *server* span as failed. */
5
+ const SERVER_ERROR_STATUS = 500;
6
+ /**
7
+ * Starts a span, runs `fn` inside it and ends it once the work settles.
8
+ *
9
+ * The result of `fn` is returned unchanged. That matters more than it looks:
10
+ * a Vercube route without middlewares produces its `Response` synchronously,
11
+ * and wrapping it in a promise would add a microtask to every request.
12
+ *
13
+ * @param tracer - Tracer to start the span on
14
+ * @param name - Span name
15
+ * @param options - Span options (kind, attributes, links)
16
+ * @param parent - Context the span is a child of
17
+ * @param fn - The work to trace
18
+ * @param onSettle - Called with the outcome just before the span ends. Returning a
19
+ * promise keeps the span open until it settles, without extending its duration.
20
+ * @returns Whatever `fn` returned
21
+ */
22
+ function runInSpan(tracer, name, options, parent, fn, onSettle) {
23
+ const span = tracer.startSpan(name, options, parent);
24
+ const settle = (value, error) => {
25
+ if (error === void 0) completeSpan(span, value);
26
+ else failSpan(span, error);
27
+ const pending = onSettle?.(span, value, error);
28
+ if (pending !== void 0) {
29
+ const endTime = Date.now();
30
+ pending.then(() => span.end(endTime), () => span.end(endTime));
31
+ return;
32
+ }
33
+ span.end();
34
+ };
35
+ return context.with(trace.setSpan(parent, span), () => {
36
+ let result;
37
+ try {
38
+ result = fn(span);
39
+ } catch (error) {
40
+ settle(void 0, error ?? /* @__PURE__ */ new Error("Unknown error"));
41
+ throw error;
42
+ }
43
+ if (isPromiseLike(result)) return result.then((value) => {
44
+ settle(value, void 0);
45
+ return value;
46
+ }, (error) => {
47
+ settle(void 0, error ?? /* @__PURE__ */ new Error("Unknown error"));
48
+ throw error;
49
+ });
50
+ settle(result, void 0);
51
+ return result;
52
+ });
53
+ }
54
+ /**
55
+ * Applies the outcome of a successful call to a span.
56
+ *
57
+ * When the value is a `Response` the HTTP status is recorded, and a 5xx marks
58
+ * the span as failed - a 4xx does not, because on a server span it describes
59
+ * the caller's request rather than a fault of the handler.
60
+ *
61
+ * @param span - The span to update
62
+ * @param value - The value the traced work produced
63
+ */
64
+ function completeSpan(span, value) {
65
+ if (!(value instanceof Response)) return;
66
+ span.setAttribute(HTTP_RESPONSE_STATUS_CODE, value.status);
67
+ if (value.status >= SERVER_ERROR_STATUS) span.setStatus({ code: SpanStatusCode.ERROR });
68
+ }
69
+ /**
70
+ * Records a thrown value on a span and marks the span as failed, whatever the
71
+ * error looks like.
72
+ *
73
+ * This is the variant for spans that carry no HTTP status semantics - the
74
+ * `CLIENT`, `PRODUCER` and `CONSUMER` spans an instrumented package produces.
75
+ * `failSpan` is the server-span variant and deliberately behaves differently;
76
+ * see the comment there before merging the two.
77
+ *
78
+ * @param span - The span to update
79
+ * @param error - The thrown value
80
+ */
81
+ function recordFailure(span, error) {
82
+ span.recordException(error);
83
+ span.setAttribute(ERROR_TYPE, errorType(error));
84
+ span.setStatus({
85
+ code: SpanStatusCode.ERROR,
86
+ message: errorMessage(error)
87
+ });
88
+ }
89
+ /**
90
+ * Records a thrown value on a *server* span and marks it failed only when the
91
+ * failure is the server's own.
92
+ *
93
+ * @param span - The span to update
94
+ * @param error - The thrown value
95
+ */
96
+ function failSpan(span, error) {
97
+ if (httpStatusOf(error) < SERVER_ERROR_STATUS) {
98
+ span.recordException(error);
99
+ span.setAttribute(ERROR_TYPE, errorType(error));
100
+ return;
101
+ }
102
+ recordFailure(span, error);
103
+ }
104
+ /**
105
+ * Reads the HTTP status a thrown value carries, if any.
106
+ *
107
+ * Duck-typed rather than imported: the framework's `HttpError` lives in core,
108
+ * and an application is free to throw its own error type with a `status`.
109
+ *
110
+ * @param error - The thrown value
111
+ * @returns The status, or 500 when the value carries none
112
+ */
113
+ function httpStatusOf(error) {
114
+ const status = error?.status ?? error?.statusCode;
115
+ return typeof status === "number" ? status : SERVER_ERROR_STATUS;
116
+ }
117
+ /**
118
+ * Names the type of a thrown value for the `error.type` attribute.
119
+ *
120
+ * @param error - The thrown value
121
+ * @returns The error type name
122
+ */
123
+ function errorType(error) {
124
+ if (!(error instanceof Error)) return typeof error;
125
+ if (error.name && error.name !== "Error") return error.name;
126
+ return error.constructor?.name || "Error";
127
+ }
128
+ /**
129
+ * Extracts a message from a thrown value.
130
+ *
131
+ * @param error - The thrown value
132
+ * @returns The message, or an empty string
133
+ */
134
+ function errorMessage(error) {
135
+ return error instanceof Error ? error.message : String(error ?? "");
136
+ }
137
+ /**
138
+ * Whether a value can be awaited.
139
+ *
140
+ * @param value - The value to test
141
+ * @returns True for thenables
142
+ */
143
+ function isPromiseLike(value) {
144
+ return value instanceof Promise || typeof value?.then === "function";
145
+ }
146
+ //#endregion
147
+ export { recordFailure as a, isPromiseLike as i, errorType as n, runInSpan as o, failSpan as r, errorMessage as t };
@@ -0,0 +1,53 @@
1
+ import { MeterProvider, ResourceMetrics } from "@opentelemetry/sdk-metrics";
2
+ import { BasicTracerProvider, InMemorySpanExporter, ReadableSpan } from "@opentelemetry/sdk-trace-base";
3
+ //#region src/Testing.d.ts
4
+ /**
5
+ * A tracer provider that keeps finished spans in memory.
6
+ */
7
+ export interface TestTelemetry {
8
+ /** The registered provider. */
9
+ provider: BasicTracerProvider;
10
+ /** The registered meter provider. */
11
+ meterProvider: MeterProvider;
12
+ /** The exporter holding the finished spans. */
13
+ exporter: InMemorySpanExporter;
14
+ /** Finished spans, oldest first. */
15
+ spans(): ReadableSpan[];
16
+ /** Finds a finished span by name. */
17
+ span(name: string): ReadableSpan | undefined;
18
+ /** Clears the collected spans. */
19
+ reset(): void;
20
+ /**
21
+ * Waits for spans whose end was deferred - body capture defers it - to be
22
+ * exported, then flushes the provider.
23
+ */
24
+ settle(): Promise<void>;
25
+ /**
26
+ * Collects the registered instruments and returns what they reported.
27
+ *
28
+ * @returns The collected metrics, newest batch last
29
+ */
30
+ collect(): Promise<ResourceMetrics[]>;
31
+ /** Unregisters the providers and releases their resources. */
32
+ shutdown(): Promise<void>;
33
+ }
34
+ /**
35
+ * Registers an in-memory tracer provider as the global one.
36
+ *
37
+ * Spans are exported synchronously when they end - `SimpleSpanProcessor`, not
38
+ * `BatchSpanProcessor` - so a test can assert on them immediately after the
39
+ * traced call returns.
40
+ *
41
+ * ```ts
42
+ * const telemetry = createTestTelemetry();
43
+ * afterEach(() => telemetry.reset());
44
+ * afterAll(() => telemetry.shutdown());
45
+ *
46
+ * await app.fetch(new Request('http://localhost/users/1'));
47
+ * expect(telemetry.span('GET /users/:id')).toBeDefined();
48
+ * ```
49
+ *
50
+ * @returns Handle to the registered provider and its collected spans
51
+ */
52
+ export declare function createTestTelemetry(): TestTelemetry;
53
+ //#endregion
@@ -0,0 +1,63 @@
1
+ import { n as W3CTraceContextPropagator, t as VercubeContextManager } from "./VercubeContextManager-n4jHlB-w.mjs";
2
+ import { context, metrics, propagation, trace } from "@opentelemetry/api";
3
+ import { RequestContext } from "@vercube/core";
4
+ import { AggregationTemporality, InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
5
+ import { BasicTracerProvider, InMemorySpanExporter, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
6
+ //#region src/Testing.ts
7
+ /**
8
+ * Registers an in-memory tracer provider as the global one.
9
+ *
10
+ * Spans are exported synchronously when they end - `SimpleSpanProcessor`, not
11
+ * `BatchSpanProcessor` - so a test can assert on them immediately after the
12
+ * traced call returns.
13
+ *
14
+ * ```ts
15
+ * const telemetry = createTestTelemetry();
16
+ * afterEach(() => telemetry.reset());
17
+ * afterAll(() => telemetry.shutdown());
18
+ *
19
+ * await app.fetch(new Request('http://localhost/users/1'));
20
+ * expect(telemetry.span('GET /users/:id')).toBeDefined();
21
+ * ```
22
+ *
23
+ * @returns Handle to the registered provider and its collected spans
24
+ */
25
+ function createTestTelemetry() {
26
+ const exporter = new InMemorySpanExporter();
27
+ const provider = new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] });
28
+ const metricExporter = new InMemoryMetricExporter(AggregationTemporality.CUMULATIVE);
29
+ const metricReader = new PeriodicExportingMetricReader({
30
+ exporter: metricExporter,
31
+ exportIntervalMillis: 2147483647
32
+ });
33
+ const meterProvider = new MeterProvider({ readers: [metricReader] });
34
+ trace.setGlobalTracerProvider(provider);
35
+ metrics.setGlobalMeterProvider(meterProvider);
36
+ context.setGlobalContextManager(new VercubeContextManager(new RequestContext()).enable());
37
+ propagation.setGlobalPropagator(new W3CTraceContextPropagator());
38
+ return {
39
+ provider,
40
+ meterProvider,
41
+ exporter,
42
+ spans: () => exporter.getFinishedSpans(),
43
+ span: (name) => exporter.getFinishedSpans().find((span) => span.name === name),
44
+ reset: () => exporter.reset(),
45
+ settle: async () => {
46
+ await new Promise((resolve) => setImmediate(resolve));
47
+ await provider.forceFlush();
48
+ },
49
+ collect: async () => {
50
+ await metricReader.forceFlush();
51
+ return metricExporter.getMetrics();
52
+ },
53
+ shutdown: async () => {
54
+ trace.disable();
55
+ metrics.disable();
56
+ context.disable();
57
+ propagation.disable();
58
+ await Promise.all([provider.shutdown(), meterProvider.shutdown()]);
59
+ }
60
+ };
61
+ }
62
+ //#endregion
63
+ export { createTestTelemetry };