@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
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
|
package/dist/Testing.mjs
ADDED
|
@@ -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 };
|