@davidilie/telemetry-node 0.2.0 → 0.4.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 +28 -8
- package/dist/{client-BzoFCwmQ.d.cts → client-DYrSJX10.d.cts} +3 -2
- package/dist/{client-BzoFCwmQ.d.mts → client-DYrSJX10.d.mts} +3 -2
- package/dist/index.cjs +66 -1
- package/dist/index.d.cts +13 -3
- package/dist/index.d.mts +13 -3
- package/dist/index.mjs +66 -2
- package/dist/register.cjs +10 -4
- package/dist/register.d.cts +1 -1
- package/dist/register.d.mts +1 -1
- package/dist/register.mjs +10 -4
- package/dist/{sampler-ND4YGh_7.mjs → sampler-80dS1PU-.mjs} +73 -55
- package/dist/{sampler-CGBWEZod.cjs → sampler-vVC2xXgy.cjs} +72 -54
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -47,12 +47,24 @@ process.once("SIGTERM", async () => {
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
`registerNodeTelemetry` installs one `NodeSDK` per JavaScript global. Repeated
|
|
50
|
-
calls return the first client and ignore later configuration
|
|
51
|
-
|
|
50
|
+
calls return the first client and ignore later configuration. Registration is
|
|
51
|
+
terminal after shutdown: restart the process to register again. OTel retains
|
|
52
|
+
global provider references after shutdown, so another registration would
|
|
53
|
+
otherwise silently use a stopped provider. Do not register this package beside another provider.
|
|
52
54
|
|
|
53
55
|
The optional `sdk` property accepts `NodeSDKConfiguration` fields other than
|
|
54
56
|
`resource` and `serviceName`. Use it for maintained instrumentations, readers,
|
|
55
57
|
processors, or exporters when environment-based defaults are insufficient.
|
|
58
|
+
Resource detectors default to an empty list, excluding automatic host/process
|
|
59
|
+
metadata and `OTEL_RESOURCE_ATTRIBUTES` / `OTEL_SERVICE_NAME` overrides. Set
|
|
60
|
+
`sdk.resourceDetectors` only for reviewed detectors: their output bypasses core
|
|
61
|
+
redaction and may override the configured resource identity.
|
|
62
|
+
|
|
63
|
+
Wrap a custom trace exporter in the exported `RedactingSpanExporter` to apply
|
|
64
|
+
core privacy bounds to completed automatic spans as well. It redacts copies
|
|
65
|
+
of span names, attributes, events, links, and status messages, leaving the
|
|
66
|
+
original spans and trace context intact. Resource and instrumentation-scope
|
|
67
|
+
metadata remain trusted configuration; configure those separately.
|
|
56
68
|
|
|
57
69
|
## Export configuration
|
|
58
70
|
|
|
@@ -60,7 +72,6 @@ The standalone SDK follows standard OpenTelemetry environment variables. For
|
|
|
60
72
|
Alloy's private OTLP/HTTP receiver:
|
|
61
73
|
|
|
62
74
|
```sh
|
|
63
|
-
OTEL_SERVICE_NAME=email-worker
|
|
64
75
|
OTEL_EXPORTER_OTLP_ENDPOINT=http://alloy.observability.svc.cluster.local:4318
|
|
65
76
|
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
|
|
66
77
|
```
|
|
@@ -165,10 +176,17 @@ collected into the same backend. Disable one route deliberately.
|
|
|
165
176
|
|
|
166
177
|
## Spans and context
|
|
167
178
|
|
|
168
|
-
The
|
|
169
|
-
is active during the operation
|
|
179
|
+
The Node adapter supplies active-span execution to the shared core `withSpan`.
|
|
180
|
+
The span is active during the operation, including asynchronous work, while
|
|
181
|
+
core applies context, consent, sampling, and shutdown checks. Use it when events/logs need the same trace.
|
|
170
182
|
`client.startSpan()` creates a span but does not make it active.
|
|
171
183
|
|
|
184
|
+
The callback receives a safe span handle, including `traceContext()`.
|
|
185
|
+
`client.traceWebhook({ destination: "discord.sales" }, send)` tracks one
|
|
186
|
+
outgoing POST under the active parent. See the
|
|
187
|
+
[webhook guide](https://github.com/DavidIlie/davidapps-telemetry/blob/main/docs/webhooks.md)
|
|
188
|
+
for privacy, delivery outcomes, metrics, and Grafana Discord notifications.
|
|
189
|
+
|
|
172
190
|
Both methods accept `{ kind: "internal" | "server" | "client" | "producer" |
|
|
173
191
|
"consumer" }` after attributes. Manual span handles also expose
|
|
174
192
|
`setStatus("unset" | "ok" | "error", message?)`.
|
|
@@ -211,9 +229,11 @@ context; `startSpan` does not.
|
|
|
211
229
|
apply; set one layer to `1` if multiplied rates are not intentional.
|
|
212
230
|
- State changes affect new work and do not erase an already exported or queued
|
|
213
231
|
provider batch.
|
|
214
|
-
- `flush()` drains core work,
|
|
215
|
-
|
|
216
|
-
|
|
232
|
+
- `flush()` drains core work, then calls the registered trace, log, and metric
|
|
233
|
+
providers' public `forceFlush()` methods when available. This flushes all
|
|
234
|
+
pending work on those providers, not only this client's records. Failures
|
|
235
|
+
are reported through `onError` without rejecting the client promise.
|
|
236
|
+
`shutdown()` on the `/register` client shuts down the `NodeSDK` and its processors.
|
|
217
237
|
- Only a client returned by `/register` owns and shuts down the installed
|
|
218
238
|
`NodeSDK`. A client created from the root entrypoint does not own the
|
|
219
239
|
framework provider.
|
|
@@ -9,6 +9,7 @@ declare class OpenTelemetrySpan implements TelemetrySpan {
|
|
|
9
9
|
recordException(error: unknown, attributes?: Attributes$1): void;
|
|
10
10
|
setStatus(status: TelemetrySpanStatus, message?: string): this;
|
|
11
11
|
end(): void;
|
|
12
|
+
traceContext(): TraceContext | undefined;
|
|
12
13
|
}
|
|
13
14
|
interface OpenTelemetryAdapterConfig {
|
|
14
15
|
resource: TelemetryResource;
|
|
@@ -31,7 +32,8 @@ declare class OpenTelemetryAdapter implements TelemetryAdapter {
|
|
|
31
32
|
send(signal: TelemetrySignal): void;
|
|
32
33
|
startSpan(name: string, attributes?: Attributes$1, options?: TelemetrySpanOptions): TelemetrySpan;
|
|
33
34
|
currentTraceContext(): TraceContext | undefined;
|
|
34
|
-
withActiveSpan<T>(name: string, operation: () => T | Promise<T>, attributes?: Attributes$1, options?: TelemetrySpanOptions): Promise<T>;
|
|
35
|
+
withActiveSpan<T>(name: string, operation: (span: TelemetrySpan) => T | Promise<T>, attributes?: Attributes$1, options?: TelemetrySpanOptions): Promise<T>;
|
|
36
|
+
flush(): Promise<void>;
|
|
35
37
|
shutdown(): Promise<void>;
|
|
36
38
|
}
|
|
37
39
|
declare function createOpenTelemetryAdapter(config: OpenTelemetryAdapterConfig): OpenTelemetryAdapter;
|
|
@@ -78,7 +80,6 @@ declare class NodeTelemetryClient extends TelemetryClient {
|
|
|
78
80
|
constructor(config: NodeTelemetryInternalConfig);
|
|
79
81
|
setEnabled(enabled: boolean): void;
|
|
80
82
|
setConsent(consent: "granted" | "denied" | "pending"): void;
|
|
81
|
-
withSpan<T>(name: string, operation: () => T | Promise<T>, attributes?: Attributes$1, options?: TelemetrySpanOptions): Promise<T>;
|
|
82
83
|
}
|
|
83
84
|
declare function createNodeTelemetry(config: NodeTelemetryConfig): NodeTelemetryClient;
|
|
84
85
|
//#endregion
|
|
@@ -9,6 +9,7 @@ declare class OpenTelemetrySpan implements TelemetrySpan {
|
|
|
9
9
|
recordException(error: unknown, attributes?: Attributes$1): void;
|
|
10
10
|
setStatus(status: TelemetrySpanStatus, message?: string): this;
|
|
11
11
|
end(): void;
|
|
12
|
+
traceContext(): TraceContext | undefined;
|
|
12
13
|
}
|
|
13
14
|
interface OpenTelemetryAdapterConfig {
|
|
14
15
|
resource: TelemetryResource;
|
|
@@ -31,7 +32,8 @@ declare class OpenTelemetryAdapter implements TelemetryAdapter {
|
|
|
31
32
|
send(signal: TelemetrySignal): void;
|
|
32
33
|
startSpan(name: string, attributes?: Attributes$1, options?: TelemetrySpanOptions): TelemetrySpan;
|
|
33
34
|
currentTraceContext(): TraceContext | undefined;
|
|
34
|
-
withActiveSpan<T>(name: string, operation: () => T | Promise<T>, attributes?: Attributes$1, options?: TelemetrySpanOptions): Promise<T>;
|
|
35
|
+
withActiveSpan<T>(name: string, operation: (span: TelemetrySpan) => T | Promise<T>, attributes?: Attributes$1, options?: TelemetrySpanOptions): Promise<T>;
|
|
36
|
+
flush(): Promise<void>;
|
|
35
37
|
shutdown(): Promise<void>;
|
|
36
38
|
}
|
|
37
39
|
declare function createOpenTelemetryAdapter(config: OpenTelemetryAdapterConfig): OpenTelemetryAdapter;
|
|
@@ -78,7 +80,6 @@ declare class NodeTelemetryClient extends TelemetryClient {
|
|
|
78
80
|
constructor(config: NodeTelemetryInternalConfig);
|
|
79
81
|
setEnabled(enabled: boolean): void;
|
|
80
82
|
setConsent(consent: "granted" | "denied" | "pending"): void;
|
|
81
|
-
withSpan<T>(name: string, operation: () => T | Promise<T>, attributes?: Attributes$1, options?: TelemetrySpanOptions): Promise<T>;
|
|
82
83
|
}
|
|
83
84
|
declare function createNodeTelemetry(config: NodeTelemetryConfig): NodeTelemetryClient;
|
|
84
85
|
//#endregion
|
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,74 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_sampler = require("./sampler-
|
|
2
|
+
const require_sampler = require("./sampler-vVC2xXgy.cjs");
|
|
3
|
+
let _davidilie_telemetry_core = require("@davidilie/telemetry-core");
|
|
4
|
+
//#region src/exporter.ts
|
|
5
|
+
function redactSpan(span) {
|
|
6
|
+
const name = span.name.replace(/https?:\/\/[^\s<>"']+/gi, (value) => {
|
|
7
|
+
try {
|
|
8
|
+
return new URL(value).origin;
|
|
9
|
+
} catch {
|
|
10
|
+
return "[REDACTED_URL]";
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
name: (0, _davidilie_telemetry_core.redactText)(name, 256),
|
|
15
|
+
kind: span.kind,
|
|
16
|
+
spanContext: () => span.spanContext(),
|
|
17
|
+
...span.parentSpanContext ? { parentSpanContext: span.parentSpanContext } : {},
|
|
18
|
+
startTime: span.startTime,
|
|
19
|
+
endTime: span.endTime,
|
|
20
|
+
duration: span.duration,
|
|
21
|
+
ended: span.ended,
|
|
22
|
+
resource: span.resource,
|
|
23
|
+
instrumentationScope: span.instrumentationScope,
|
|
24
|
+
droppedAttributesCount: span.droppedAttributesCount,
|
|
25
|
+
droppedEventsCount: span.droppedEventsCount,
|
|
26
|
+
droppedLinksCount: span.droppedLinksCount,
|
|
27
|
+
attributes: require_sampler.toOtelAttributes((0, _davidilie_telemetry_core.sanitizeAttributes)(span.attributes)),
|
|
28
|
+
status: {
|
|
29
|
+
code: span.status.code,
|
|
30
|
+
...span.status.message ? { message: (0, _davidilie_telemetry_core.redactText)(span.status.message) } : {}
|
|
31
|
+
},
|
|
32
|
+
events: span.events.map((event) => ({
|
|
33
|
+
...event,
|
|
34
|
+
name: (0, _davidilie_telemetry_core.redactText)(event.name, 256),
|
|
35
|
+
attributes: require_sampler.toOtelAttributes((0, _davidilie_telemetry_core.sanitizeAttributes)(event.attributes))
|
|
36
|
+
})),
|
|
37
|
+
links: span.links.map((link) => ({
|
|
38
|
+
...link,
|
|
39
|
+
attributes: require_sampler.toOtelAttributes((0, _davidilie_telemetry_core.sanitizeAttributes)(link.attributes))
|
|
40
|
+
}))
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Apply core privacy bounds to completed spans without mutating another processor's data. */
|
|
44
|
+
var RedactingSpanExporter = class {
|
|
45
|
+
delegate;
|
|
46
|
+
constructor(delegate) {
|
|
47
|
+
this.delegate = delegate;
|
|
48
|
+
}
|
|
49
|
+
export(spans, done) {
|
|
50
|
+
let sanitized;
|
|
51
|
+
try {
|
|
52
|
+
sanitized = spans.map(redactSpan);
|
|
53
|
+
} catch {
|
|
54
|
+
done({ code: 1 });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.delegate.export(sanitized, done);
|
|
58
|
+
}
|
|
59
|
+
shutdown() {
|
|
60
|
+
return this.delegate.shutdown();
|
|
61
|
+
}
|
|
62
|
+
forceFlush() {
|
|
63
|
+
return this.delegate.forceFlush?.() ?? Promise.resolve();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
//#endregion
|
|
3
67
|
exports.DynamicTelemetrySampler = require_sampler.DynamicTelemetrySampler;
|
|
4
68
|
exports.NodeTelemetryClient = require_sampler.NodeTelemetryClient;
|
|
5
69
|
exports.OpenTelemetryAdapter = require_sampler.OpenTelemetryAdapter;
|
|
6
70
|
exports.OpenTelemetrySpan = require_sampler.OpenTelemetrySpan;
|
|
71
|
+
exports.RedactingSpanExporter = RedactingSpanExporter;
|
|
7
72
|
exports.createNodeTelemetry = require_sampler.createNodeTelemetry;
|
|
8
73
|
exports.createOpenTelemetryAdapter = require_sampler.createOpenTelemetryAdapter;
|
|
9
74
|
exports.currentTraceContext = require_sampler.currentTraceContext;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as OpenTelemetryAdapter, c as createOpenTelemetryAdapter, d as startSpan, f as withSpan, i as MeasurementMode, l as currentTraceContext, n as NodeTelemetryConfig, o as OpenTelemetryAdapterConfig, r as createNodeTelemetry, s as OpenTelemetrySpan, t as NodeTelemetryClient, u as recordException } from "./client-
|
|
1
|
+
import { a as OpenTelemetryAdapter, c as createOpenTelemetryAdapter, d as startSpan, f as withSpan, i as MeasurementMode, l as currentTraceContext, n as NodeTelemetryConfig, o as OpenTelemetryAdapterConfig, r as createNodeTelemetry, s as OpenTelemetrySpan, t as NodeTelemetryClient, u as recordException } from "./client-DYrSJX10.cjs";
|
|
2
2
|
import { AttributeValue, Attributes, Context, Link, SpanKind } from "@opentelemetry/api";
|
|
3
3
|
import { AttributeValue as AttributeValue$1, Attributes as Attributes$1, TelemetryResource } from "@davidilie/telemetry-core";
|
|
4
|
-
import { Sampler, SamplingResult } from "@opentelemetry/sdk-trace-base";
|
|
4
|
+
import { ReadableSpan, Sampler, SamplingResult, SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
5
5
|
//#region src/attributes.d.ts
|
|
6
6
|
declare function toOtelAttributeValue(value: AttributeValue$1): AttributeValue;
|
|
7
7
|
declare function toOtelAttributes(attributes?: Attributes$1): Attributes;
|
|
@@ -31,4 +31,14 @@ declare class DynamicTelemetrySampler implements Sampler {
|
|
|
31
31
|
toString(): string;
|
|
32
32
|
}
|
|
33
33
|
//#endregion
|
|
34
|
-
|
|
34
|
+
//#region src/exporter.d.ts
|
|
35
|
+
/** Apply core privacy bounds to completed spans without mutating another processor's data. */
|
|
36
|
+
declare class RedactingSpanExporter implements SpanExporter {
|
|
37
|
+
private readonly delegate;
|
|
38
|
+
constructor(delegate: SpanExporter);
|
|
39
|
+
export(spans: ReadableSpan[], done: Parameters<SpanExporter["export"]>[1]): void;
|
|
40
|
+
shutdown(): Promise<void>;
|
|
41
|
+
forceFlush(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { DynamicTelemetrySampler, type MeasurementMode, NodeTelemetryClient, type NodeTelemetryConfig, OpenTelemetryAdapter, type OpenTelemetryAdapterConfig, OpenTelemetrySpan, RedactingSpanExporter, type TelemetrySamplingState, createNodeTelemetry, createOpenTelemetryAdapter, currentTraceContext, recordException, startSpan, telemetryResourceAttributes, toOtelAttributeValue, toOtelAttributes, withSpan };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as OpenTelemetryAdapter, c as createOpenTelemetryAdapter, d as startSpan, f as withSpan, i as MeasurementMode, l as currentTraceContext, n as NodeTelemetryConfig, o as OpenTelemetryAdapterConfig, r as createNodeTelemetry, s as OpenTelemetrySpan, t as NodeTelemetryClient, u as recordException } from "./client-
|
|
1
|
+
import { a as OpenTelemetryAdapter, c as createOpenTelemetryAdapter, d as startSpan, f as withSpan, i as MeasurementMode, l as currentTraceContext, n as NodeTelemetryConfig, o as OpenTelemetryAdapterConfig, r as createNodeTelemetry, s as OpenTelemetrySpan, t as NodeTelemetryClient, u as recordException } from "./client-DYrSJX10.mjs";
|
|
2
2
|
import { AttributeValue, Attributes, Context, Link, SpanKind } from "@opentelemetry/api";
|
|
3
3
|
import { AttributeValue as AttributeValue$1, Attributes as Attributes$1, TelemetryResource } from "@davidilie/telemetry-core";
|
|
4
|
-
import { Sampler, SamplingResult } from "@opentelemetry/sdk-trace-base";
|
|
4
|
+
import { ReadableSpan, Sampler, SamplingResult, SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
5
5
|
//#region src/attributes.d.ts
|
|
6
6
|
declare function toOtelAttributeValue(value: AttributeValue$1): AttributeValue;
|
|
7
7
|
declare function toOtelAttributes(attributes?: Attributes$1): Attributes;
|
|
@@ -31,4 +31,14 @@ declare class DynamicTelemetrySampler implements Sampler {
|
|
|
31
31
|
toString(): string;
|
|
32
32
|
}
|
|
33
33
|
//#endregion
|
|
34
|
-
|
|
34
|
+
//#region src/exporter.d.ts
|
|
35
|
+
/** Apply core privacy bounds to completed spans without mutating another processor's data. */
|
|
36
|
+
declare class RedactingSpanExporter implements SpanExporter {
|
|
37
|
+
private readonly delegate;
|
|
38
|
+
constructor(delegate: SpanExporter);
|
|
39
|
+
export(spans: ReadableSpan[], done: Parameters<SpanExporter["export"]>[1]): void;
|
|
40
|
+
shutdown(): Promise<void>;
|
|
41
|
+
forceFlush(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { DynamicTelemetrySampler, type MeasurementMode, NodeTelemetryClient, type NodeTelemetryConfig, OpenTelemetryAdapter, type OpenTelemetryAdapterConfig, OpenTelemetrySpan, RedactingSpanExporter, type TelemetrySamplingState, createNodeTelemetry, createOpenTelemetryAdapter, currentTraceContext, recordException, startSpan, telemetryResourceAttributes, toOtelAttributeValue, toOtelAttributes, withSpan };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,66 @@
|
|
|
1
|
-
import { a as OpenTelemetrySpan, c as recordException, d as telemetryResourceAttributes, f as toOtelAttributeValue, i as OpenTelemetryAdapter, l as startSpan, n as NodeTelemetryClient, o as createOpenTelemetryAdapter, p as toOtelAttributes, r as createNodeTelemetry, s as currentTraceContext, t as DynamicTelemetrySampler, u as withSpan } from "./sampler-
|
|
2
|
-
|
|
1
|
+
import { a as OpenTelemetrySpan, c as recordException, d as telemetryResourceAttributes, f as toOtelAttributeValue, i as OpenTelemetryAdapter, l as startSpan, n as NodeTelemetryClient, o as createOpenTelemetryAdapter, p as toOtelAttributes, r as createNodeTelemetry, s as currentTraceContext, t as DynamicTelemetrySampler, u as withSpan } from "./sampler-80dS1PU-.mjs";
|
|
2
|
+
import { redactText, sanitizeAttributes } from "@davidilie/telemetry-core";
|
|
3
|
+
//#region src/exporter.ts
|
|
4
|
+
function redactSpan(span) {
|
|
5
|
+
const name = span.name.replace(/https?:\/\/[^\s<>"']+/gi, (value) => {
|
|
6
|
+
try {
|
|
7
|
+
return new URL(value).origin;
|
|
8
|
+
} catch {
|
|
9
|
+
return "[REDACTED_URL]";
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
name: redactText(name, 256),
|
|
14
|
+
kind: span.kind,
|
|
15
|
+
spanContext: () => span.spanContext(),
|
|
16
|
+
...span.parentSpanContext ? { parentSpanContext: span.parentSpanContext } : {},
|
|
17
|
+
startTime: span.startTime,
|
|
18
|
+
endTime: span.endTime,
|
|
19
|
+
duration: span.duration,
|
|
20
|
+
ended: span.ended,
|
|
21
|
+
resource: span.resource,
|
|
22
|
+
instrumentationScope: span.instrumentationScope,
|
|
23
|
+
droppedAttributesCount: span.droppedAttributesCount,
|
|
24
|
+
droppedEventsCount: span.droppedEventsCount,
|
|
25
|
+
droppedLinksCount: span.droppedLinksCount,
|
|
26
|
+
attributes: toOtelAttributes(sanitizeAttributes(span.attributes)),
|
|
27
|
+
status: {
|
|
28
|
+
code: span.status.code,
|
|
29
|
+
...span.status.message ? { message: redactText(span.status.message) } : {}
|
|
30
|
+
},
|
|
31
|
+
events: span.events.map((event) => ({
|
|
32
|
+
...event,
|
|
33
|
+
name: redactText(event.name, 256),
|
|
34
|
+
attributes: toOtelAttributes(sanitizeAttributes(event.attributes))
|
|
35
|
+
})),
|
|
36
|
+
links: span.links.map((link) => ({
|
|
37
|
+
...link,
|
|
38
|
+
attributes: toOtelAttributes(sanitizeAttributes(link.attributes))
|
|
39
|
+
}))
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Apply core privacy bounds to completed spans without mutating another processor's data. */
|
|
43
|
+
var RedactingSpanExporter = class {
|
|
44
|
+
delegate;
|
|
45
|
+
constructor(delegate) {
|
|
46
|
+
this.delegate = delegate;
|
|
47
|
+
}
|
|
48
|
+
export(spans, done) {
|
|
49
|
+
let sanitized;
|
|
50
|
+
try {
|
|
51
|
+
sanitized = spans.map(redactSpan);
|
|
52
|
+
} catch {
|
|
53
|
+
done({ code: 1 });
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.delegate.export(sanitized, done);
|
|
57
|
+
}
|
|
58
|
+
shutdown() {
|
|
59
|
+
return this.delegate.shutdown();
|
|
60
|
+
}
|
|
61
|
+
forceFlush() {
|
|
62
|
+
return this.delegate.forceFlush?.() ?? Promise.resolve();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
//#endregion
|
|
66
|
+
export { DynamicTelemetrySampler, NodeTelemetryClient, OpenTelemetryAdapter, OpenTelemetrySpan, RedactingSpanExporter, createNodeTelemetry, createOpenTelemetryAdapter, currentTraceContext, recordException, startSpan, telemetryResourceAttributes, toOtelAttributeValue, toOtelAttributes, withSpan };
|
package/dist/register.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_sampler = require("./sampler-
|
|
2
|
+
const require_sampler = require("./sampler-vVC2xXgy.cjs");
|
|
3
3
|
let _davidilie_telemetry_core = require("@davidilie/telemetry-core");
|
|
4
4
|
let _opentelemetry_sdk_trace_base = require("@opentelemetry/sdk-trace-base");
|
|
5
5
|
let _opentelemetry_resources = require("@opentelemetry/resources");
|
|
@@ -13,7 +13,10 @@ function traceSampleRate(config) {
|
|
|
13
13
|
function registerNodeTelemetry(config) {
|
|
14
14
|
const sharedGlobal = globalThis;
|
|
15
15
|
const existing = sharedGlobal[REGISTRATION];
|
|
16
|
-
if (existing)
|
|
16
|
+
if (existing) {
|
|
17
|
+
if (existing.stopped) throw new Error("Node telemetry registration is terminal after shutdown; restart the process to register again");
|
|
18
|
+
return existing.client;
|
|
19
|
+
}
|
|
17
20
|
const resource = (0, _davidilie_telemetry_core.sanitizeResource)(config.resource);
|
|
18
21
|
const sampler = new require_sampler.DynamicTelemetrySampler(config.sdk?.sampler ?? new _opentelemetry_sdk_trace_base.ParentBasedSampler({ root: new _opentelemetry_sdk_trace_base.TraceIdRatioBasedSampler(traceSampleRate(config)) }), {
|
|
19
22
|
enabled: config.enabled ?? true,
|
|
@@ -21,6 +24,7 @@ function registerNodeTelemetry(config) {
|
|
|
21
24
|
});
|
|
22
25
|
const sdk = new _opentelemetry_sdk_node.NodeSDK({
|
|
23
26
|
...config.sdk,
|
|
27
|
+
resourceDetectors: config.sdk?.resourceDetectors ?? [],
|
|
24
28
|
sampler,
|
|
25
29
|
resource: (0, _opentelemetry_resources.resourceFromAttributes)(require_sampler.telemetryResourceAttributes(resource)),
|
|
26
30
|
serviceName: resource.serviceName
|
|
@@ -37,13 +41,15 @@ function registerNodeTelemetry(config) {
|
|
|
37
41
|
},
|
|
38
42
|
measurementMode: config.measurementMode ?? (config.sdk?.metricReaders?.length ? "metrics" : "spans"),
|
|
39
43
|
shutdown: async () => {
|
|
44
|
+
const registration = sharedGlobal[REGISTRATION];
|
|
45
|
+
if (registration?.client === client) registration.stopped = true;
|
|
40
46
|
await sdk.shutdown();
|
|
41
|
-
if (sharedGlobal[REGISTRATION]?.client === client) delete sharedGlobal[REGISTRATION];
|
|
42
47
|
}
|
|
43
48
|
});
|
|
44
49
|
sharedGlobal[REGISTRATION] = {
|
|
45
50
|
client,
|
|
46
|
-
sdk
|
|
51
|
+
sdk,
|
|
52
|
+
stopped: false
|
|
47
53
|
};
|
|
48
54
|
return client;
|
|
49
55
|
}
|
package/dist/register.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as NodeTelemetryConfig, t as NodeTelemetryClient } from "./client-
|
|
1
|
+
import { n as NodeTelemetryConfig, t as NodeTelemetryClient } from "./client-DYrSJX10.cjs";
|
|
2
2
|
import { NodeSDKConfiguration, NodeSDKConfiguration as NodeSDKConfiguration$1 } from "@opentelemetry/sdk-node";
|
|
3
3
|
//#region src/register.d.ts
|
|
4
4
|
interface RegisterNodeTelemetryConfig extends NodeTelemetryConfig {
|
package/dist/register.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as NodeTelemetryConfig, t as NodeTelemetryClient } from "./client-
|
|
1
|
+
import { n as NodeTelemetryConfig, t as NodeTelemetryClient } from "./client-DYrSJX10.mjs";
|
|
2
2
|
import { NodeSDKConfiguration, NodeSDKConfiguration as NodeSDKConfiguration$1 } from "@opentelemetry/sdk-node";
|
|
3
3
|
//#region src/register.d.ts
|
|
4
4
|
interface RegisterNodeTelemetryConfig extends NodeTelemetryConfig {
|
package/dist/register.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as telemetryResourceAttributes, n as NodeTelemetryClient, t as DynamicTelemetrySampler } from "./sampler-
|
|
1
|
+
import { d as telemetryResourceAttributes, n as NodeTelemetryClient, t as DynamicTelemetrySampler } from "./sampler-80dS1PU-.mjs";
|
|
2
2
|
import { sanitizeResource } from "@davidilie/telemetry-core";
|
|
3
3
|
import { ParentBasedSampler, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
|
|
4
4
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
@@ -12,7 +12,10 @@ function traceSampleRate(config) {
|
|
|
12
12
|
function registerNodeTelemetry(config) {
|
|
13
13
|
const sharedGlobal = globalThis;
|
|
14
14
|
const existing = sharedGlobal[REGISTRATION];
|
|
15
|
-
if (existing)
|
|
15
|
+
if (existing) {
|
|
16
|
+
if (existing.stopped) throw new Error("Node telemetry registration is terminal after shutdown; restart the process to register again");
|
|
17
|
+
return existing.client;
|
|
18
|
+
}
|
|
16
19
|
const resource = sanitizeResource(config.resource);
|
|
17
20
|
const sampler = new DynamicTelemetrySampler(config.sdk?.sampler ?? new ParentBasedSampler({ root: new TraceIdRatioBasedSampler(traceSampleRate(config)) }), {
|
|
18
21
|
enabled: config.enabled ?? true,
|
|
@@ -20,6 +23,7 @@ function registerNodeTelemetry(config) {
|
|
|
20
23
|
});
|
|
21
24
|
const sdk = new NodeSDK({
|
|
22
25
|
...config.sdk,
|
|
26
|
+
resourceDetectors: config.sdk?.resourceDetectors ?? [],
|
|
23
27
|
sampler,
|
|
24
28
|
resource: resourceFromAttributes(telemetryResourceAttributes(resource)),
|
|
25
29
|
serviceName: resource.serviceName
|
|
@@ -36,13 +40,15 @@ function registerNodeTelemetry(config) {
|
|
|
36
40
|
},
|
|
37
41
|
measurementMode: config.measurementMode ?? (config.sdk?.metricReaders?.length ? "metrics" : "spans"),
|
|
38
42
|
shutdown: async () => {
|
|
43
|
+
const registration = sharedGlobal[REGISTRATION];
|
|
44
|
+
if (registration?.client === client) registration.stopped = true;
|
|
39
45
|
await sdk.shutdown();
|
|
40
|
-
if (sharedGlobal[REGISTRATION]?.client === client) delete sharedGlobal[REGISTRATION];
|
|
41
46
|
}
|
|
42
47
|
});
|
|
43
48
|
sharedGlobal[REGISTRATION] = {
|
|
44
49
|
client,
|
|
45
|
-
sdk
|
|
50
|
+
sdk,
|
|
51
|
+
stopped: false
|
|
46
52
|
};
|
|
47
53
|
return client;
|
|
48
54
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SpanKind, SpanStatusCode, context, metrics, trace } from "@opentelemetry/api";
|
|
1
|
+
import { SpanKind, SpanStatusCode, context, isSpanContextValid, metrics, trace } from "@opentelemetry/api";
|
|
2
2
|
import { SeverityNumber, logs } from "@opentelemetry/api-logs";
|
|
3
3
|
import { TelemetryClient, redactText, sanitizeAttributes, sanitizeResource, sanitizeSignal } from "@davidilie/telemetry-core";
|
|
4
4
|
import { SamplingDecision } from "@opentelemetry/sdk-trace-base";
|
|
@@ -38,6 +38,22 @@ function telemetryResourceAttributes(resource) {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
//#endregion
|
|
41
|
+
//#region src/flush.ts
|
|
42
|
+
/** Flush registered SDK providers using their public lifecycle methods. */
|
|
43
|
+
async function flushOpenTelemetryProviders() {
|
|
44
|
+
let tracerProvider = trace.getTracerProvider();
|
|
45
|
+
if ("getDelegate" in tracerProvider && typeof tracerProvider.getDelegate === "function") tracerProvider = tracerProvider.getDelegate();
|
|
46
|
+
const providers = [
|
|
47
|
+
tracerProvider,
|
|
48
|
+
logs.getLoggerProvider(),
|
|
49
|
+
metrics.getMeterProvider()
|
|
50
|
+
];
|
|
51
|
+
const errors = (await Promise.allSettled(providers.map(async (provider) => {
|
|
52
|
+
if ("forceFlush" in provider && typeof provider.forceFlush === "function") await provider.forceFlush();
|
|
53
|
+
}))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
54
|
+
if (errors.length > 0) throw new AggregateError(errors, "OpenTelemetry provider flush failed");
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
41
57
|
//#region src/adapter.ts
|
|
42
58
|
const severityNumbers = {
|
|
43
59
|
debug: SeverityNumber.DEBUG,
|
|
@@ -46,13 +62,17 @@ const severityNumbers = {
|
|
|
46
62
|
error: SeverityNumber.ERROR
|
|
47
63
|
};
|
|
48
64
|
function exceptionForOtel(error) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
try {
|
|
66
|
+
if (error instanceof Error) {
|
|
67
|
+
const safe = new Error(redactText(error.message), { ...error.cause !== void 0 ? { cause: redactText(String(error.cause)) } : {} });
|
|
68
|
+
safe.name = redactText(error.name, 256);
|
|
69
|
+
if (error.stack) safe.stack = redactText(error.stack, 16384);
|
|
70
|
+
return safe;
|
|
71
|
+
}
|
|
72
|
+
return redactText(typeof error === "string" ? error : String(error));
|
|
73
|
+
} catch {
|
|
74
|
+
return "Unserializable exception";
|
|
54
75
|
}
|
|
55
|
-
return redactText(typeof error === "string" ? error : String(error));
|
|
56
76
|
}
|
|
57
77
|
function signalAttributes(signal) {
|
|
58
78
|
return {
|
|
@@ -100,6 +120,14 @@ var OpenTelemetrySpan = class {
|
|
|
100
120
|
end() {
|
|
101
121
|
this.#span.end();
|
|
102
122
|
}
|
|
123
|
+
traceContext() {
|
|
124
|
+
const value = this.#span.spanContext();
|
|
125
|
+
return isSpanContextValid(value) ? {
|
|
126
|
+
traceId: value.traceId,
|
|
127
|
+
spanId: value.spanId,
|
|
128
|
+
traceFlags: value.traceFlags
|
|
129
|
+
} : void 0;
|
|
130
|
+
}
|
|
103
131
|
};
|
|
104
132
|
var OpenTelemetryAdapter = class {
|
|
105
133
|
#instrumentationName;
|
|
@@ -112,6 +140,7 @@ var OpenTelemetryAdapter = class {
|
|
|
112
140
|
#logSampleRate;
|
|
113
141
|
#shutdown;
|
|
114
142
|
#histograms = /* @__PURE__ */ new Map();
|
|
143
|
+
#meterProvider;
|
|
115
144
|
constructor(config) {
|
|
116
145
|
this.#resource = sanitizeResource(config.resource);
|
|
117
146
|
this.#instrumentationName = config.instrumentationName ?? this.#resource.serviceName;
|
|
@@ -129,7 +158,7 @@ var OpenTelemetryAdapter = class {
|
|
|
129
158
|
switch (signal.type) {
|
|
130
159
|
case "event": {
|
|
131
160
|
const activeSpan = trace.getActiveSpan();
|
|
132
|
-
if (activeSpan) {
|
|
161
|
+
if (activeSpan?.isRecording()) {
|
|
133
162
|
activeSpan.addEvent(signal.name, attributes, new Date(signal.timestamp));
|
|
134
163
|
return;
|
|
135
164
|
}
|
|
@@ -144,22 +173,23 @@ var OpenTelemetryAdapter = class {
|
|
|
144
173
|
}
|
|
145
174
|
case "exception": {
|
|
146
175
|
const activeSpan = trace.getActiveSpan();
|
|
147
|
-
const
|
|
176
|
+
const recordingSpan = activeSpan?.isRecording() ? activeSpan : void 0;
|
|
177
|
+
const span = recordingSpan ?? this.#tracer().startSpan("exception", {
|
|
148
178
|
kind: SpanKind.INTERNAL,
|
|
149
179
|
attributes,
|
|
150
180
|
startTime: new Date(signal.timestamp)
|
|
151
181
|
});
|
|
152
|
-
span.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
message: signal.exception.message,
|
|
156
|
-
...signal.exception.stack ? {
|
|
157
|
-
});
|
|
182
|
+
span.addEvent("exception", {
|
|
183
|
+
...attributes,
|
|
184
|
+
"exception.type": signal.exception.name,
|
|
185
|
+
"exception.message": signal.exception.message,
|
|
186
|
+
...signal.exception.stack ? { "exception.stacktrace": signal.exception.stack } : {}
|
|
187
|
+
}, new Date(signal.timestamp));
|
|
158
188
|
span.setStatus({
|
|
159
189
|
code: SpanStatusCode.ERROR,
|
|
160
190
|
message: signal.exception.message
|
|
161
191
|
});
|
|
162
|
-
if (!
|
|
192
|
+
if (!recordingSpan) span.end(new Date(signal.timestamp));
|
|
163
193
|
return;
|
|
164
194
|
}
|
|
165
195
|
case "log":
|
|
@@ -200,11 +230,16 @@ var OpenTelemetryAdapter = class {
|
|
|
200
230
|
}).end(new Date(signal.timestamp));
|
|
201
231
|
if (this.#measurementMode === "spans") return;
|
|
202
232
|
if (!VALID_INSTRUMENT_NAME.test(signal.name)) return;
|
|
233
|
+
const meterProvider = metrics.getMeterProvider();
|
|
234
|
+
if (meterProvider !== this.#meterProvider) {
|
|
235
|
+
this.#histograms.clear();
|
|
236
|
+
this.#meterProvider = meterProvider;
|
|
237
|
+
}
|
|
203
238
|
const key = `${signal.name}\u0000${signal.unit ?? ""}`;
|
|
204
239
|
let histogram = this.#histograms.get(key);
|
|
205
240
|
if (!histogram) {
|
|
206
241
|
if (this.#histograms.size >= this.#maxMetricInstruments) return;
|
|
207
|
-
histogram =
|
|
242
|
+
histogram = meterProvider.getMeter(this.#instrumentationName, this.#instrumentationVersion).createHistogram(signal.name, { ...signal.unit ? { unit: signal.unit } : {} });
|
|
208
243
|
this.#histograms.set(key, histogram);
|
|
209
244
|
}
|
|
210
245
|
const metricAttributes = Object.fromEntries(Object.entries(signal.attributes).filter(([name]) => this.#metricAttributeAllowlist.has(name)));
|
|
@@ -239,7 +274,7 @@ var OpenTelemetryAdapter = class {
|
|
|
239
274
|
}, async (span) => {
|
|
240
275
|
operationRan = true;
|
|
241
276
|
try {
|
|
242
|
-
operationResult = await operation();
|
|
277
|
+
operationResult = await operation(new OpenTelemetrySpan(span));
|
|
243
278
|
operationSucceeded = true;
|
|
244
279
|
} catch (error) {
|
|
245
280
|
operationError = error;
|
|
@@ -261,7 +296,10 @@ var OpenTelemetryAdapter = class {
|
|
|
261
296
|
}
|
|
262
297
|
if (operationSucceeded) return operationResult;
|
|
263
298
|
if (operationRan) throw operationError;
|
|
264
|
-
|
|
299
|
+
throw new Error("OpenTelemetry tracer did not invoke the span operation");
|
|
300
|
+
}
|
|
301
|
+
flush() {
|
|
302
|
+
return flushOpenTelemetryProviders();
|
|
265
303
|
}
|
|
266
304
|
async shutdown() {
|
|
267
305
|
await this.#shutdown?.();
|
|
@@ -275,7 +313,7 @@ function createOpenTelemetryAdapter(config) {
|
|
|
275
313
|
}
|
|
276
314
|
function currentTraceContext() {
|
|
277
315
|
const spanContext = trace.getSpan(context.active())?.spanContext();
|
|
278
|
-
if (!spanContext) return void 0;
|
|
316
|
+
if (!spanContext || !isSpanContextValid(spanContext)) return void 0;
|
|
279
317
|
return {
|
|
280
318
|
traceId: spanContext.traceId,
|
|
281
319
|
spanId: spanContext.spanId,
|
|
@@ -312,22 +350,25 @@ async function withSpan(name, operation, attributes = {}, options = {}) {
|
|
|
312
350
|
});
|
|
313
351
|
}
|
|
314
352
|
function recordException(error, attributes = {}) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
353
|
+
let ownedSpan;
|
|
354
|
+
try {
|
|
355
|
+
const span = trace.getActiveSpan() ?? (ownedSpan = trace.getTracer("@davidilie/telemetry-node").startSpan("exception"));
|
|
356
|
+
const exception = exceptionForOtel(error);
|
|
357
|
+
span.setAttributes(toOtelAttributes(sanitizeAttributes(attributes)));
|
|
358
|
+
span.recordException(exception);
|
|
359
|
+
span.setStatus({
|
|
360
|
+
code: SpanStatusCode.ERROR,
|
|
361
|
+
message: typeof exception === "string" ? exception : exception.message
|
|
362
|
+
});
|
|
363
|
+
} catch {} finally {
|
|
364
|
+
try {
|
|
365
|
+
ownedSpan?.end();
|
|
366
|
+
} catch {}
|
|
367
|
+
}
|
|
324
368
|
}
|
|
325
369
|
//#endregion
|
|
326
370
|
//#region src/client.ts
|
|
327
371
|
var NodeTelemetryClient = class extends TelemetryClient {
|
|
328
|
-
#adapter;
|
|
329
|
-
#sampleRate;
|
|
330
|
-
#onError;
|
|
331
372
|
#onCollectionStateChange;
|
|
332
373
|
#enabled;
|
|
333
374
|
#consent;
|
|
@@ -353,9 +394,6 @@ var NodeTelemetryClient = class extends TelemetryClient {
|
|
|
353
394
|
...config.debug !== void 0 ? { debug: config.debug } : {},
|
|
354
395
|
...config.onError ? { onError: config.onError } : {}
|
|
355
396
|
});
|
|
356
|
-
this.#adapter = adapter;
|
|
357
|
-
this.#sampleRate = config.providerManagedSampling ? 1 : Number.isFinite(config.sampleRate) ? Math.min(1, Math.max(0, config.sampleRate ?? 1)) : 1;
|
|
358
|
-
this.#onError = config.onError;
|
|
359
397
|
this.#onCollectionStateChange = config.onCollectionStateChange;
|
|
360
398
|
this.#enabled = config.enabled ?? true;
|
|
361
399
|
this.#consent = config.consent ?? "granted";
|
|
@@ -376,26 +414,6 @@ var NodeTelemetryClient = class extends TelemetryClient {
|
|
|
376
414
|
consent: this.#consent
|
|
377
415
|
});
|
|
378
416
|
}
|
|
379
|
-
async withSpan(name, operation, attributes = {}, options = {}) {
|
|
380
|
-
if (!this.#enabled || this.#consent !== "granted" || Math.random() >= this.#sampleRate) return operation();
|
|
381
|
-
let operationStarted = false;
|
|
382
|
-
try {
|
|
383
|
-
return await this.#adapter.withActiveSpan(redactText(name, 256), () => {
|
|
384
|
-
operationStarted = true;
|
|
385
|
-
return operation();
|
|
386
|
-
}, sanitizeAttributes(attributes), options);
|
|
387
|
-
} catch (error) {
|
|
388
|
-
if (operationStarted) throw error;
|
|
389
|
-
this.#reportAdapterError(error, { operation: "startSpan" });
|
|
390
|
-
return operation();
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
#reportAdapterError(error, context) {
|
|
394
|
-
if (!this.#onError) return;
|
|
395
|
-
try {
|
|
396
|
-
Promise.resolve(this.#onError(error, context)).catch(() => void 0);
|
|
397
|
-
} catch {}
|
|
398
|
-
}
|
|
399
417
|
};
|
|
400
418
|
function createNodeTelemetry(config) {
|
|
401
419
|
return new NodeTelemetryClient(config);
|
|
@@ -38,6 +38,22 @@ function telemetryResourceAttributes(resource) {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
//#endregion
|
|
41
|
+
//#region src/flush.ts
|
|
42
|
+
/** Flush registered SDK providers using their public lifecycle methods. */
|
|
43
|
+
async function flushOpenTelemetryProviders() {
|
|
44
|
+
let tracerProvider = _opentelemetry_api.trace.getTracerProvider();
|
|
45
|
+
if ("getDelegate" in tracerProvider && typeof tracerProvider.getDelegate === "function") tracerProvider = tracerProvider.getDelegate();
|
|
46
|
+
const providers = [
|
|
47
|
+
tracerProvider,
|
|
48
|
+
_opentelemetry_api_logs.logs.getLoggerProvider(),
|
|
49
|
+
_opentelemetry_api.metrics.getMeterProvider()
|
|
50
|
+
];
|
|
51
|
+
const errors = (await Promise.allSettled(providers.map(async (provider) => {
|
|
52
|
+
if ("forceFlush" in provider && typeof provider.forceFlush === "function") await provider.forceFlush();
|
|
53
|
+
}))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
|
|
54
|
+
if (errors.length > 0) throw new AggregateError(errors, "OpenTelemetry provider flush failed");
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
41
57
|
//#region src/adapter.ts
|
|
42
58
|
const severityNumbers = {
|
|
43
59
|
debug: _opentelemetry_api_logs.SeverityNumber.DEBUG,
|
|
@@ -46,13 +62,17 @@ const severityNumbers = {
|
|
|
46
62
|
error: _opentelemetry_api_logs.SeverityNumber.ERROR
|
|
47
63
|
};
|
|
48
64
|
function exceptionForOtel(error) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
65
|
+
try {
|
|
66
|
+
if (error instanceof Error) {
|
|
67
|
+
const safe = new Error((0, _davidilie_telemetry_core.redactText)(error.message), { ...error.cause !== void 0 ? { cause: (0, _davidilie_telemetry_core.redactText)(String(error.cause)) } : {} });
|
|
68
|
+
safe.name = (0, _davidilie_telemetry_core.redactText)(error.name, 256);
|
|
69
|
+
if (error.stack) safe.stack = (0, _davidilie_telemetry_core.redactText)(error.stack, 16384);
|
|
70
|
+
return safe;
|
|
71
|
+
}
|
|
72
|
+
return (0, _davidilie_telemetry_core.redactText)(typeof error === "string" ? error : String(error));
|
|
73
|
+
} catch {
|
|
74
|
+
return "Unserializable exception";
|
|
54
75
|
}
|
|
55
|
-
return (0, _davidilie_telemetry_core.redactText)(typeof error === "string" ? error : String(error));
|
|
56
76
|
}
|
|
57
77
|
function signalAttributes(signal) {
|
|
58
78
|
return {
|
|
@@ -100,6 +120,14 @@ var OpenTelemetrySpan = class {
|
|
|
100
120
|
end() {
|
|
101
121
|
this.#span.end();
|
|
102
122
|
}
|
|
123
|
+
traceContext() {
|
|
124
|
+
const value = this.#span.spanContext();
|
|
125
|
+
return (0, _opentelemetry_api.isSpanContextValid)(value) ? {
|
|
126
|
+
traceId: value.traceId,
|
|
127
|
+
spanId: value.spanId,
|
|
128
|
+
traceFlags: value.traceFlags
|
|
129
|
+
} : void 0;
|
|
130
|
+
}
|
|
103
131
|
};
|
|
104
132
|
var OpenTelemetryAdapter = class {
|
|
105
133
|
#instrumentationName;
|
|
@@ -112,6 +140,7 @@ var OpenTelemetryAdapter = class {
|
|
|
112
140
|
#logSampleRate;
|
|
113
141
|
#shutdown;
|
|
114
142
|
#histograms = /* @__PURE__ */ new Map();
|
|
143
|
+
#meterProvider;
|
|
115
144
|
constructor(config) {
|
|
116
145
|
this.#resource = (0, _davidilie_telemetry_core.sanitizeResource)(config.resource);
|
|
117
146
|
this.#instrumentationName = config.instrumentationName ?? this.#resource.serviceName;
|
|
@@ -129,7 +158,7 @@ var OpenTelemetryAdapter = class {
|
|
|
129
158
|
switch (signal.type) {
|
|
130
159
|
case "event": {
|
|
131
160
|
const activeSpan = _opentelemetry_api.trace.getActiveSpan();
|
|
132
|
-
if (activeSpan) {
|
|
161
|
+
if (activeSpan?.isRecording()) {
|
|
133
162
|
activeSpan.addEvent(signal.name, attributes, new Date(signal.timestamp));
|
|
134
163
|
return;
|
|
135
164
|
}
|
|
@@ -144,22 +173,23 @@ var OpenTelemetryAdapter = class {
|
|
|
144
173
|
}
|
|
145
174
|
case "exception": {
|
|
146
175
|
const activeSpan = _opentelemetry_api.trace.getActiveSpan();
|
|
147
|
-
const
|
|
176
|
+
const recordingSpan = activeSpan?.isRecording() ? activeSpan : void 0;
|
|
177
|
+
const span = recordingSpan ?? this.#tracer().startSpan("exception", {
|
|
148
178
|
kind: _opentelemetry_api.SpanKind.INTERNAL,
|
|
149
179
|
attributes,
|
|
150
180
|
startTime: new Date(signal.timestamp)
|
|
151
181
|
});
|
|
152
|
-
span.
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
message: signal.exception.message,
|
|
156
|
-
...signal.exception.stack ? {
|
|
157
|
-
});
|
|
182
|
+
span.addEvent("exception", {
|
|
183
|
+
...attributes,
|
|
184
|
+
"exception.type": signal.exception.name,
|
|
185
|
+
"exception.message": signal.exception.message,
|
|
186
|
+
...signal.exception.stack ? { "exception.stacktrace": signal.exception.stack } : {}
|
|
187
|
+
}, new Date(signal.timestamp));
|
|
158
188
|
span.setStatus({
|
|
159
189
|
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
160
190
|
message: signal.exception.message
|
|
161
191
|
});
|
|
162
|
-
if (!
|
|
192
|
+
if (!recordingSpan) span.end(new Date(signal.timestamp));
|
|
163
193
|
return;
|
|
164
194
|
}
|
|
165
195
|
case "log":
|
|
@@ -200,11 +230,16 @@ var OpenTelemetryAdapter = class {
|
|
|
200
230
|
}).end(new Date(signal.timestamp));
|
|
201
231
|
if (this.#measurementMode === "spans") return;
|
|
202
232
|
if (!VALID_INSTRUMENT_NAME.test(signal.name)) return;
|
|
233
|
+
const meterProvider = _opentelemetry_api.metrics.getMeterProvider();
|
|
234
|
+
if (meterProvider !== this.#meterProvider) {
|
|
235
|
+
this.#histograms.clear();
|
|
236
|
+
this.#meterProvider = meterProvider;
|
|
237
|
+
}
|
|
203
238
|
const key = `${signal.name}\u0000${signal.unit ?? ""}`;
|
|
204
239
|
let histogram = this.#histograms.get(key);
|
|
205
240
|
if (!histogram) {
|
|
206
241
|
if (this.#histograms.size >= this.#maxMetricInstruments) return;
|
|
207
|
-
histogram =
|
|
242
|
+
histogram = meterProvider.getMeter(this.#instrumentationName, this.#instrumentationVersion).createHistogram(signal.name, { ...signal.unit ? { unit: signal.unit } : {} });
|
|
208
243
|
this.#histograms.set(key, histogram);
|
|
209
244
|
}
|
|
210
245
|
const metricAttributes = Object.fromEntries(Object.entries(signal.attributes).filter(([name]) => this.#metricAttributeAllowlist.has(name)));
|
|
@@ -239,7 +274,7 @@ var OpenTelemetryAdapter = class {
|
|
|
239
274
|
}, async (span) => {
|
|
240
275
|
operationRan = true;
|
|
241
276
|
try {
|
|
242
|
-
operationResult = await operation();
|
|
277
|
+
operationResult = await operation(new OpenTelemetrySpan(span));
|
|
243
278
|
operationSucceeded = true;
|
|
244
279
|
} catch (error) {
|
|
245
280
|
operationError = error;
|
|
@@ -261,7 +296,10 @@ var OpenTelemetryAdapter = class {
|
|
|
261
296
|
}
|
|
262
297
|
if (operationSucceeded) return operationResult;
|
|
263
298
|
if (operationRan) throw operationError;
|
|
264
|
-
|
|
299
|
+
throw new Error("OpenTelemetry tracer did not invoke the span operation");
|
|
300
|
+
}
|
|
301
|
+
flush() {
|
|
302
|
+
return flushOpenTelemetryProviders();
|
|
265
303
|
}
|
|
266
304
|
async shutdown() {
|
|
267
305
|
await this.#shutdown?.();
|
|
@@ -275,7 +313,7 @@ function createOpenTelemetryAdapter(config) {
|
|
|
275
313
|
}
|
|
276
314
|
function currentTraceContext() {
|
|
277
315
|
const spanContext = _opentelemetry_api.trace.getSpan(_opentelemetry_api.context.active())?.spanContext();
|
|
278
|
-
if (!spanContext) return void 0;
|
|
316
|
+
if (!spanContext || !(0, _opentelemetry_api.isSpanContextValid)(spanContext)) return void 0;
|
|
279
317
|
return {
|
|
280
318
|
traceId: spanContext.traceId,
|
|
281
319
|
spanId: spanContext.spanId,
|
|
@@ -312,22 +350,25 @@ async function withSpan(name, operation, attributes = {}, options = {}) {
|
|
|
312
350
|
});
|
|
313
351
|
}
|
|
314
352
|
function recordException(error, attributes = {}) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
353
|
+
let ownedSpan;
|
|
354
|
+
try {
|
|
355
|
+
const span = _opentelemetry_api.trace.getActiveSpan() ?? (ownedSpan = _opentelemetry_api.trace.getTracer("@davidilie/telemetry-node").startSpan("exception"));
|
|
356
|
+
const exception = exceptionForOtel(error);
|
|
357
|
+
span.setAttributes(toOtelAttributes((0, _davidilie_telemetry_core.sanitizeAttributes)(attributes)));
|
|
358
|
+
span.recordException(exception);
|
|
359
|
+
span.setStatus({
|
|
360
|
+
code: _opentelemetry_api.SpanStatusCode.ERROR,
|
|
361
|
+
message: typeof exception === "string" ? exception : exception.message
|
|
362
|
+
});
|
|
363
|
+
} catch {} finally {
|
|
364
|
+
try {
|
|
365
|
+
ownedSpan?.end();
|
|
366
|
+
} catch {}
|
|
367
|
+
}
|
|
324
368
|
}
|
|
325
369
|
//#endregion
|
|
326
370
|
//#region src/client.ts
|
|
327
371
|
var NodeTelemetryClient = class extends _davidilie_telemetry_core.TelemetryClient {
|
|
328
|
-
#adapter;
|
|
329
|
-
#sampleRate;
|
|
330
|
-
#onError;
|
|
331
372
|
#onCollectionStateChange;
|
|
332
373
|
#enabled;
|
|
333
374
|
#consent;
|
|
@@ -353,9 +394,6 @@ var NodeTelemetryClient = class extends _davidilie_telemetry_core.TelemetryClien
|
|
|
353
394
|
...config.debug !== void 0 ? { debug: config.debug } : {},
|
|
354
395
|
...config.onError ? { onError: config.onError } : {}
|
|
355
396
|
});
|
|
356
|
-
this.#adapter = adapter;
|
|
357
|
-
this.#sampleRate = config.providerManagedSampling ? 1 : Number.isFinite(config.sampleRate) ? Math.min(1, Math.max(0, config.sampleRate ?? 1)) : 1;
|
|
358
|
-
this.#onError = config.onError;
|
|
359
397
|
this.#onCollectionStateChange = config.onCollectionStateChange;
|
|
360
398
|
this.#enabled = config.enabled ?? true;
|
|
361
399
|
this.#consent = config.consent ?? "granted";
|
|
@@ -376,26 +414,6 @@ var NodeTelemetryClient = class extends _davidilie_telemetry_core.TelemetryClien
|
|
|
376
414
|
consent: this.#consent
|
|
377
415
|
});
|
|
378
416
|
}
|
|
379
|
-
async withSpan(name, operation, attributes = {}, options = {}) {
|
|
380
|
-
if (!this.#enabled || this.#consent !== "granted" || Math.random() >= this.#sampleRate) return operation();
|
|
381
|
-
let operationStarted = false;
|
|
382
|
-
try {
|
|
383
|
-
return await this.#adapter.withActiveSpan((0, _davidilie_telemetry_core.redactText)(name, 256), () => {
|
|
384
|
-
operationStarted = true;
|
|
385
|
-
return operation();
|
|
386
|
-
}, (0, _davidilie_telemetry_core.sanitizeAttributes)(attributes), options);
|
|
387
|
-
} catch (error) {
|
|
388
|
-
if (operationStarted) throw error;
|
|
389
|
-
this.#reportAdapterError(error, { operation: "startSpan" });
|
|
390
|
-
return operation();
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
#reportAdapterError(error, context) {
|
|
394
|
-
if (!this.#onError) return;
|
|
395
|
-
try {
|
|
396
|
-
Promise.resolve(this.#onError(error, context)).catch(() => void 0);
|
|
397
|
-
} catch {}
|
|
398
|
-
}
|
|
399
417
|
};
|
|
400
418
|
function createNodeTelemetry(config) {
|
|
401
419
|
return new NodeTelemetryClient(config);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davidilie/telemetry-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "OpenTelemetry-backed telemetry for Node.js services.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"analytics",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"clean": "rm -rf dist coverage"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@davidilie/telemetry-core": "0.
|
|
68
|
+
"@davidilie/telemetry-core": "0.4.0",
|
|
69
69
|
"@opentelemetry/api-logs": "0.221.0",
|
|
70
70
|
"@opentelemetry/resources": "2.10.0",
|
|
71
71
|
"@opentelemetry/sdk-node": "0.221.0",
|