@m4ike1/ion-telemetry 0.1.0 → 0.1.1
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 +32 -2
- package/dist/index.d.ts +23 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -6
- package/dist/index.js.map +1 -1
- package/dist/memory.d.ts +13 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +120 -21
- package/dist/memory.js.map +1 -1
- package/dist/noop.d.ts.map +1 -1
- package/dist/noop.js +9 -1
- package/dist/noop.js.map +1 -1
- package/dist/redaction.d.ts +7 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +30 -0
- package/dist/redaction.js.map +1 -0
- package/dist/testing/conformance.d.ts.map +1 -1
- package/dist/testing/conformance.js +178 -1
- package/dist/testing/conformance.js.map +1 -1
- package/dist/testing/index.d.ts +1 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -0
- package/dist/testing/index.js.map +1 -1
- package/package.json +1 -1
package/dist/memory.js
CHANGED
|
@@ -1,7 +1,44 @@
|
|
|
1
1
|
import { NOOP_TELEMETRY_CONTEXT } from "./noop.js";
|
|
2
|
+
import { redactAttributes } from "./redaction.js";
|
|
2
3
|
function copyAttributeValue(value) {
|
|
3
4
|
return Array.isArray(value) ? [...value] : value;
|
|
4
5
|
}
|
|
6
|
+
function isPromiseLike(value) {
|
|
7
|
+
if (value === null || (typeof value !== "object" && typeof value !== "function"))
|
|
8
|
+
return false;
|
|
9
|
+
return "then" in value && typeof value.then === "function";
|
|
10
|
+
}
|
|
11
|
+
function normalizeTraceparent(traceparent) {
|
|
12
|
+
if (typeof traceparent !== "string")
|
|
13
|
+
return undefined;
|
|
14
|
+
const match = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(?:-.+)?$/.exec(traceparent);
|
|
15
|
+
if (!match || match[1] === "ff" || match[2] === "0".repeat(32) || match[3] === "0".repeat(16)) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
if (match[1] === "00" && match[0].length !== 55)
|
|
19
|
+
return undefined;
|
|
20
|
+
return traceparent;
|
|
21
|
+
}
|
|
22
|
+
function randomNonZeroHex(byteLength) {
|
|
23
|
+
let value;
|
|
24
|
+
do {
|
|
25
|
+
const bytes = new Uint8Array(byteLength);
|
|
26
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
27
|
+
value = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
28
|
+
} while (value === "0".repeat(byteLength * 2));
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
function createTraceparent(parentTraceparent) {
|
|
32
|
+
const traceId = parentTraceparent?.slice(3, 35) ?? randomNonZeroHex(16);
|
|
33
|
+
const parentSpanId = parentTraceparent?.slice(36, 52);
|
|
34
|
+
let spanId;
|
|
35
|
+
do {
|
|
36
|
+
spanId = randomNonZeroHex(8);
|
|
37
|
+
} while (spanId === parentSpanId);
|
|
38
|
+
const inheritedFlags = parentTraceparent ? Number.parseInt(parentTraceparent.slice(53, 55), 16) : 1;
|
|
39
|
+
const traceFlags = (inheritedFlags & 1) === 1 ? "01" : "00";
|
|
40
|
+
return `00-${traceId}-${spanId}-${traceFlags}`;
|
|
41
|
+
}
|
|
5
42
|
function copyAttributes(attributes) {
|
|
6
43
|
const copy = {};
|
|
7
44
|
if (!attributes)
|
|
@@ -44,28 +81,57 @@ function settleSpan(state, span, failed, error) {
|
|
|
44
81
|
if (failed && !span.explicitStatus)
|
|
45
82
|
span.status = automaticErrorStatus(error);
|
|
46
83
|
span.settled = true;
|
|
84
|
+
span.schema = undefined;
|
|
47
85
|
span.endSequence = state.nextEndSequence++;
|
|
86
|
+
try {
|
|
87
|
+
const endedAt = state.now();
|
|
88
|
+
if (Number.isFinite(endedAt)) {
|
|
89
|
+
span.endedAt = endedAt;
|
|
90
|
+
span.duration = Math.max(0, endedAt - span.startedAt);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// Clock failures are passive. Settlement still completes without timing fields.
|
|
95
|
+
}
|
|
48
96
|
}
|
|
49
|
-
function createSpan(state, parent, options) {
|
|
97
|
+
function createSpan(state, parent, options, extractedTraceparent) {
|
|
98
|
+
const startedAt = state.now();
|
|
99
|
+
if (!Number.isFinite(startedAt))
|
|
100
|
+
throw new Error("Telemetry clock returned a non-finite timestamp");
|
|
101
|
+
const parentTraceparent = parent?.traceparent ?? normalizeTraceparent(options.traceparent) ?? extractedTraceparent ?? null;
|
|
102
|
+
const traceparent = createTraceparent(parentTraceparent ?? undefined);
|
|
50
103
|
const name = options.name;
|
|
51
|
-
const
|
|
104
|
+
const schema = options.schema;
|
|
105
|
+
const attributes = schema ? redactAttributes(options.attributes, schema) : copyAttributes(options.attributes);
|
|
106
|
+
const id = state.nextSpanId++;
|
|
52
107
|
return {
|
|
53
|
-
id
|
|
108
|
+
id,
|
|
54
109
|
parentId: parent?.id ?? null,
|
|
110
|
+
parentTraceparent,
|
|
111
|
+
traceparent,
|
|
55
112
|
name,
|
|
56
113
|
attributes,
|
|
114
|
+
schema,
|
|
57
115
|
events: [],
|
|
58
116
|
status: { status: "ok" },
|
|
59
117
|
explicitStatus: false,
|
|
60
118
|
settled: false,
|
|
119
|
+
startedAt,
|
|
61
120
|
};
|
|
62
121
|
}
|
|
63
|
-
function startInMemorySpan(state, parent, options, callback) {
|
|
122
|
+
function startInMemorySpan(state, parent, options, callback, extractedTraceparent) {
|
|
64
123
|
if (parent?.settled)
|
|
65
124
|
return NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);
|
|
125
|
+
try {
|
|
126
|
+
if (options.enabled === false)
|
|
127
|
+
return NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);
|
|
131
|
+
}
|
|
66
132
|
let recordedSpan;
|
|
67
133
|
try {
|
|
68
|
-
recordedSpan = createSpan(state, parent, options);
|
|
134
|
+
recordedSpan = createSpan(state, parent, options, extractedTraceparent);
|
|
69
135
|
state.spans.push(recordedSpan);
|
|
70
136
|
}
|
|
71
137
|
catch {
|
|
@@ -73,11 +139,16 @@ function startInMemorySpan(state, parent, options, callback) {
|
|
|
73
139
|
}
|
|
74
140
|
const span = {
|
|
75
141
|
startSpan: (childOptions, childCallback) => startInMemorySpan(state, recordedSpan, childOptions, childCallback),
|
|
142
|
+
extract: (traceparent) => createInMemoryContext(state, normalizeTraceparent(traceparent)),
|
|
143
|
+
inject: (injectedSpan) => state.spanTraceparents.get(injectedSpan),
|
|
76
144
|
addEvent(name, attributes) {
|
|
77
145
|
if (recordedSpan.settled)
|
|
78
146
|
return;
|
|
79
147
|
try {
|
|
80
|
-
|
|
148
|
+
const eventAttributes = recordedSpan.schema
|
|
149
|
+
? redactAttributes(attributes, recordedSpan.schema)
|
|
150
|
+
: copyAttributes(attributes);
|
|
151
|
+
recordedSpan.events.push({ name, attributes: eventAttributes });
|
|
81
152
|
}
|
|
82
153
|
catch {
|
|
83
154
|
// Recording is passive. Ignore malformed or unreadable telemetry payloads.
|
|
@@ -87,7 +158,8 @@ function startInMemorySpan(state, parent, options, callback) {
|
|
|
87
158
|
if (recordedSpan.settled)
|
|
88
159
|
return;
|
|
89
160
|
try {
|
|
90
|
-
recordedSpan.
|
|
161
|
+
const nextAttributes = recordedSpan.schema ? redactAttributes(attributes, recordedSpan.schema) : attributes;
|
|
162
|
+
recordedSpan.attributes = mergeAttributes(recordedSpan.attributes, nextAttributes);
|
|
91
163
|
}
|
|
92
164
|
catch {
|
|
93
165
|
// Recording is passive. Ignore malformed or unreadable telemetry payloads.
|
|
@@ -105,40 +177,64 @@ function startInMemorySpan(state, parent, options, callback) {
|
|
|
105
177
|
}
|
|
106
178
|
},
|
|
107
179
|
};
|
|
108
|
-
|
|
180
|
+
state.spanTraceparents.set(span, recordedSpan.traceparent);
|
|
109
181
|
try {
|
|
110
|
-
result = callback(span);
|
|
182
|
+
const result = callback(span);
|
|
183
|
+
if (isPromiseLike(result)) {
|
|
184
|
+
return Promise.resolve(result).then((value) => {
|
|
185
|
+
settleSpan(state, recordedSpan, false);
|
|
186
|
+
return value;
|
|
187
|
+
}, (error) => {
|
|
188
|
+
settleSpan(state, recordedSpan, true, error);
|
|
189
|
+
throw error;
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
settleSpan(state, recordedSpan, false);
|
|
193
|
+
return result;
|
|
111
194
|
}
|
|
112
195
|
catch (error) {
|
|
113
196
|
settleSpan(state, recordedSpan, true, error);
|
|
114
197
|
return Promise.reject(error);
|
|
115
198
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
199
|
+
}
|
|
200
|
+
function createInMemoryContext(state, extractedTraceparent) {
|
|
201
|
+
return {
|
|
202
|
+
startSpan: (options, callback) => startInMemorySpan(state, undefined, options, callback, extractedTraceparent),
|
|
203
|
+
extract: (traceparent) => createInMemoryContext(state, normalizeTraceparent(traceparent)),
|
|
204
|
+
inject: (span) => state.spanTraceparents.get(span),
|
|
205
|
+
};
|
|
123
206
|
}
|
|
124
207
|
/**
|
|
125
208
|
* Backend-neutral reference implementation that records spans in process memory.
|
|
126
209
|
* Create a fresh instance to isolate tests or independent recording scopes.
|
|
127
210
|
*/
|
|
128
211
|
export class InMemoryTelemetryContext {
|
|
129
|
-
state
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
212
|
+
state;
|
|
213
|
+
constructor(options = {}) {
|
|
214
|
+
this.state = {
|
|
215
|
+
spans: [],
|
|
216
|
+
nextSpanId: 1,
|
|
217
|
+
nextEndSequence: 1,
|
|
218
|
+
now: options.now ?? Date.now,
|
|
219
|
+
spanTraceparents: new WeakMap(),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
134
222
|
startSpan(options, callback) {
|
|
135
223
|
return startInMemorySpan(this.state, undefined, options, callback);
|
|
136
224
|
}
|
|
225
|
+
extract(traceparent) {
|
|
226
|
+
return createInMemoryContext(this.state, normalizeTraceparent(traceparent));
|
|
227
|
+
}
|
|
228
|
+
inject(span) {
|
|
229
|
+
return this.state.spanTraceparents.get(span);
|
|
230
|
+
}
|
|
137
231
|
/** Returns detached snapshots in span-start order. */
|
|
138
232
|
getSpans() {
|
|
139
233
|
return this.state.spans.map((span) => ({
|
|
140
234
|
id: span.id,
|
|
141
235
|
parentId: span.parentId,
|
|
236
|
+
parentTraceparent: span.parentTraceparent,
|
|
237
|
+
traceparent: span.traceparent,
|
|
142
238
|
name: span.name,
|
|
143
239
|
attributes: copyAttributes(span.attributes),
|
|
144
240
|
events: span.events.map((event) => ({
|
|
@@ -147,6 +243,9 @@ export class InMemoryTelemetryContext {
|
|
|
147
243
|
})),
|
|
148
244
|
status: copyStatus(span.status),
|
|
149
245
|
settled: span.settled,
|
|
246
|
+
startedAt: span.startedAt,
|
|
247
|
+
...(span.endedAt === undefined ? {} : { endedAt: span.endedAt }),
|
|
248
|
+
...(span.duration === undefined ? {} : { duration: span.duration }),
|
|
150
249
|
...(span.endSequence === undefined ? {} : { endSequence: span.endSequence }),
|
|
151
250
|
}));
|
|
152
251
|
}
|
package/dist/memory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAttributeValue,\n\tSpanAttributes,\n\tSpanOptions,\n\tSpanStatus,\n\tTelemetryContext,\n\tTelemetrySpan,\n} from \"./index.ts\";\nimport { NOOP_TELEMETRY_CONTEXT } from \"./noop.ts\";\n\nexport interface RecordedTelemetryEvent {\n\treadonly name: string;\n\treadonly attributes: Readonly<SpanAttributes>;\n}\n\nexport interface RecordedTelemetrySpan {\n\treadonly id: number;\n\treadonly parentId: number | null;\n\treadonly name: string;\n\treadonly attributes: Readonly<SpanAttributes>;\n\treadonly events: readonly RecordedTelemetryEvent[];\n\treadonly status: SpanStatus;\n\treadonly settled: boolean;\n\treadonly endSequence?: number;\n}\n\ninterface MutableRecordedTelemetryEvent {\n\tname: string;\n\tattributes: SpanAttributes;\n}\n\ninterface MutableRecordedTelemetrySpan {\n\tid: number;\n\tparentId: number | null;\n\tname: string;\n\tattributes: SpanAttributes;\n\tevents: MutableRecordedTelemetryEvent[];\n\tstatus: SpanStatus;\n\texplicitStatus: boolean;\n\tsettled: boolean;\n\tendSequence?: number;\n}\n\ninterface InMemoryTelemetryState {\n\tspans: MutableRecordedTelemetrySpan[];\n\tnextSpanId: number;\n\tnextEndSequence: number;\n}\n\nfunction copyAttributeValue(value: AttributeValue): AttributeValue {\n\treturn Array.isArray(value) ? ([...value] as AttributeValue) : value;\n}\n\nfunction copyAttributes(attributes?: SpanAttributes): SpanAttributes {\n\tconst copy: SpanAttributes = {};\n\tif (!attributes) return copy;\n\tfor (const [name, value] of Object.entries(attributes)) {\n\t\tif (value !== undefined) copy[name] = copyAttributeValue(value);\n\t}\n\treturn copy;\n}\n\nfunction mergeAttributes(current: SpanAttributes, attributes: SpanAttributes): SpanAttributes {\n\tconst merged = copyAttributes(current);\n\tfor (const [name, value] of Object.entries(attributes)) {\n\t\tif (value !== undefined) merged[name] = copyAttributeValue(value);\n\t}\n\treturn merged;\n}\n\nfunction copyStatus(status: SpanStatus): SpanStatus {\n\tif (status.status === \"ok\") return { status: \"ok\" };\n\treturn status.error\n\t\t? { status: \"error\", error: { name: status.error.name, message: status.error.message } }\n\t\t: { status: \"error\" };\n}\n\nfunction automaticErrorStatus(error: unknown): SpanStatus {\n\ttry {\n\t\tif (error instanceof Error) {\n\t\t\treturn { status: \"error\", error: { name: error.name, message: error.message } };\n\t\t}\n\t} catch {\n\t\t// Error inspection is passive. Fall through to an error status without details.\n\t}\n\treturn { status: \"error\" };\n}\n\nfunction settleSpan(\n\tstate: InMemoryTelemetryState,\n\tspan: MutableRecordedTelemetrySpan,\n\tfailed: boolean,\n\terror?: unknown,\n): void {\n\tif (span.settled) return;\n\tif (failed && !span.explicitStatus) span.status = automaticErrorStatus(error);\n\tspan.settled = true;\n\tspan.endSequence = state.nextEndSequence++;\n}\n\nfunction createSpan(\n\tstate: InMemoryTelemetryState,\n\tparent: MutableRecordedTelemetrySpan | undefined,\n\toptions: SpanOptions,\n): MutableRecordedTelemetrySpan {\n\tconst name = options.name;\n\tconst attributes = copyAttributes(options.attributes);\n\treturn {\n\t\tid: state.nextSpanId++,\n\t\tparentId: parent?.id ?? null,\n\t\tname,\n\t\tattributes,\n\t\tevents: [],\n\t\tstatus: { status: \"ok\" },\n\t\texplicitStatus: false,\n\t\tsettled: false,\n\t};\n}\n\nfunction startInMemorySpan<T>(\n\tstate: InMemoryTelemetryState,\n\tparent: MutableRecordedTelemetrySpan | undefined,\n\toptions: SpanOptions,\n\tcallback: (span: TelemetrySpan) => T | Promise<T>,\n): Promise<T> {\n\tif (parent?.settled) return NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);\n\n\tlet recordedSpan: MutableRecordedTelemetrySpan;\n\ttry {\n\t\trecordedSpan = createSpan(state, parent, options);\n\t\tstate.spans.push(recordedSpan);\n\t} catch {\n\t\treturn NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);\n\t}\n\n\tconst span: TelemetrySpan = {\n\t\tstartSpan: <Result>(\n\t\t\tchildOptions: SpanOptions,\n\t\t\tchildCallback: (child: TelemetrySpan) => Result | Promise<Result>,\n\t\t) => startInMemorySpan(state, recordedSpan, childOptions, childCallback),\n\t\taddEvent(name, attributes) {\n\t\t\tif (recordedSpan.settled) return;\n\t\t\ttry {\n\t\t\t\trecordedSpan.events.push({ name, attributes: copyAttributes(attributes) });\n\t\t\t} catch {\n\t\t\t\t// Recording is passive. Ignore malformed or unreadable telemetry payloads.\n\t\t\t}\n\t\t},\n\t\tsetAttributes(attributes) {\n\t\t\tif (recordedSpan.settled) return;\n\t\t\ttry {\n\t\t\t\trecordedSpan.attributes = mergeAttributes(recordedSpan.attributes, attributes);\n\t\t\t} catch {\n\t\t\t\t// Recording is passive. Ignore malformed or unreadable telemetry payloads.\n\t\t\t}\n\t\t},\n\t\tsetStatus(status) {\n\t\t\tif (recordedSpan.settled) return;\n\t\t\ttry {\n\t\t\t\trecordedSpan.status = copyStatus(status);\n\t\t\t\trecordedSpan.explicitStatus = true;\n\t\t\t} catch {\n\t\t\t\t// Recording is passive. Ignore malformed or unreadable telemetry payloads.\n\t\t\t}\n\t\t},\n\t};\n\n\tlet result: T | Promise<T>;\n\ttry {\n\t\tresult = callback(span);\n\t} catch (error) {\n\t\tsettleSpan(state, recordedSpan, true, error);\n\t\treturn Promise.reject(error);\n\t}\n\n\treturn Promise.resolve(result).then(\n\t\t(value) => {\n\t\t\tsettleSpan(state, recordedSpan, false);\n\t\t\treturn value;\n\t\t},\n\t\t(error: unknown) => {\n\t\t\tsettleSpan(state, recordedSpan, true, error);\n\t\t\tthrow error;\n\t\t},\n\t);\n}\n\n/**\n * Backend-neutral reference implementation that records spans in process memory.\n * Create a fresh instance to isolate tests or independent recording scopes.\n */\nexport class InMemoryTelemetryContext implements TelemetryContext {\n\tprivate readonly state: InMemoryTelemetryState = {\n\t\tspans: [],\n\t\tnextSpanId: 1,\n\t\tnextEndSequence: 1,\n\t};\n\n\tstartSpan<T>(options: SpanOptions, callback: (span: TelemetrySpan) => T | Promise<T>): Promise<T> {\n\t\treturn startInMemorySpan(this.state, undefined, options, callback);\n\t}\n\n\t/** Returns detached snapshots in span-start order. */\n\tgetSpans(): readonly RecordedTelemetrySpan[] {\n\t\treturn this.state.spans.map((span) => ({\n\t\t\tid: span.id,\n\t\t\tparentId: span.parentId,\n\t\t\tname: span.name,\n\t\t\tattributes: copyAttributes(span.attributes),\n\t\t\tevents: span.events.map((event) => ({\n\t\t\t\tname: event.name,\n\t\t\t\tattributes: copyAttributes(event.attributes),\n\t\t\t})),\n\t\t\tstatus: copyStatus(span.status),\n\t\t\tsettled: span.settled,\n\t\t\t...(span.endSequence === undefined ? {} : { endSequence: span.endSequence }),\n\t\t}));\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tAttributeValue,\n\tSpanAttributes,\n\tSpanOptions,\n\tSpanStatus,\n\tTelemetryContext,\n\tTelemetrySpan,\n\tTelemetrySpanDefinition,\n} from \"./index.ts\";\nimport { NOOP_TELEMETRY_CONTEXT } from \"./noop.ts\";\nimport { redactAttributes } from \"./redaction.ts\";\n\nexport interface RecordedTelemetryEvent {\n\treadonly name: string;\n\treadonly attributes: Readonly<SpanAttributes>;\n}\n\nexport interface RecordedTelemetrySpan {\n\treadonly id: number;\n\treadonly parentId: number | null;\n\treadonly parentTraceparent?: string | null;\n\treadonly traceparent?: string;\n\treadonly name: string;\n\treadonly attributes: Readonly<SpanAttributes>;\n\treadonly events: readonly RecordedTelemetryEvent[];\n\treadonly status: SpanStatus;\n\treadonly settled: boolean;\n\treadonly startedAt?: number;\n\treadonly endedAt?: number;\n\treadonly duration?: number;\n\treadonly endSequence?: number;\n}\n\ninterface MutableRecordedTelemetryEvent {\n\tname: string;\n\tattributes: SpanAttributes;\n}\n\ninterface MutableRecordedTelemetrySpan {\n\tid: number;\n\tparentId: number | null;\n\tparentTraceparent: string | null;\n\ttraceparent: string;\n\tname: string;\n\tattributes: SpanAttributes;\n\tschema?: TelemetrySpanDefinition;\n\tevents: MutableRecordedTelemetryEvent[];\n\tstatus: SpanStatus;\n\texplicitStatus: boolean;\n\tsettled: boolean;\n\tstartedAt: number;\n\tendedAt?: number;\n\tduration?: number;\n\tendSequence?: number;\n}\n\nexport interface InMemoryTelemetryContextOptions {\n\t/** Returns epoch milliseconds. Defaults to Date.now. */\n\tnow?: () => number;\n}\n\ninterface InMemoryTelemetryState {\n\tspans: MutableRecordedTelemetrySpan[];\n\tnextSpanId: number;\n\tnextEndSequence: number;\n\tnow: () => number;\n\tspanTraceparents: WeakMap<TelemetrySpan, string>;\n}\n\nfunction copyAttributeValue(value: AttributeValue): AttributeValue {\n\treturn Array.isArray(value) ? ([...value] as AttributeValue) : value;\n}\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n\tif (value === null || (typeof value !== \"object\" && typeof value !== \"function\")) return false;\n\treturn \"then\" in value && typeof value.then === \"function\";\n}\n\nfunction normalizeTraceparent(traceparent: string | undefined): string | undefined {\n\tif (typeof traceparent !== \"string\") return undefined;\n\tconst match = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})(?:-.+)?$/.exec(traceparent);\n\tif (!match || match[1] === \"ff\" || match[2] === \"0\".repeat(32) || match[3] === \"0\".repeat(16)) {\n\t\treturn undefined;\n\t}\n\tif (match[1] === \"00\" && match[0].length !== 55) return undefined;\n\treturn traceparent;\n}\n\nfunction randomNonZeroHex(byteLength: number): string {\n\tlet value: string;\n\tdo {\n\t\tconst bytes = new Uint8Array(byteLength);\n\t\tglobalThis.crypto.getRandomValues(bytes);\n\t\tvalue = Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n\t} while (value === \"0\".repeat(byteLength * 2));\n\treturn value;\n}\n\nfunction createTraceparent(parentTraceparent: string | undefined): string {\n\tconst traceId = parentTraceparent?.slice(3, 35) ?? randomNonZeroHex(16);\n\tconst parentSpanId = parentTraceparent?.slice(36, 52);\n\tlet spanId: string;\n\tdo {\n\t\tspanId = randomNonZeroHex(8);\n\t} while (spanId === parentSpanId);\n\tconst inheritedFlags = parentTraceparent ? Number.parseInt(parentTraceparent.slice(53, 55), 16) : 1;\n\tconst traceFlags = (inheritedFlags & 1) === 1 ? \"01\" : \"00\";\n\treturn `00-${traceId}-${spanId}-${traceFlags}`;\n}\n\nfunction copyAttributes(attributes?: SpanAttributes): SpanAttributes {\n\tconst copy: SpanAttributes = {};\n\tif (!attributes) return copy;\n\tfor (const [name, value] of Object.entries(attributes)) {\n\t\tif (value !== undefined) copy[name] = copyAttributeValue(value);\n\t}\n\treturn copy;\n}\n\nfunction mergeAttributes(current: SpanAttributes, attributes: SpanAttributes): SpanAttributes {\n\tconst merged = copyAttributes(current);\n\tfor (const [name, value] of Object.entries(attributes)) {\n\t\tif (value !== undefined) merged[name] = copyAttributeValue(value);\n\t}\n\treturn merged;\n}\n\nfunction copyStatus(status: SpanStatus): SpanStatus {\n\tif (status.status === \"ok\") return { status: \"ok\" };\n\treturn status.error\n\t\t? { status: \"error\", error: { name: status.error.name, message: status.error.message } }\n\t\t: { status: \"error\" };\n}\n\nfunction automaticErrorStatus(error: unknown): SpanStatus {\n\ttry {\n\t\tif (error instanceof Error) {\n\t\t\treturn { status: \"error\", error: { name: error.name, message: error.message } };\n\t\t}\n\t} catch {\n\t\t// Error inspection is passive. Fall through to an error status without details.\n\t}\n\treturn { status: \"error\" };\n}\n\nfunction settleSpan(\n\tstate: InMemoryTelemetryState,\n\tspan: MutableRecordedTelemetrySpan,\n\tfailed: boolean,\n\terror?: unknown,\n): void {\n\tif (span.settled) return;\n\tif (failed && !span.explicitStatus) span.status = automaticErrorStatus(error);\n\tspan.settled = true;\n\tspan.schema = undefined;\n\tspan.endSequence = state.nextEndSequence++;\n\ttry {\n\t\tconst endedAt = state.now();\n\t\tif (Number.isFinite(endedAt)) {\n\t\t\tspan.endedAt = endedAt;\n\t\t\tspan.duration = Math.max(0, endedAt - span.startedAt);\n\t\t}\n\t} catch {\n\t\t// Clock failures are passive. Settlement still completes without timing fields.\n\t}\n}\n\nfunction createSpan(\n\tstate: InMemoryTelemetryState,\n\tparent: MutableRecordedTelemetrySpan | undefined,\n\toptions: SpanOptions,\n\textractedTraceparent: string | undefined,\n): MutableRecordedTelemetrySpan {\n\tconst startedAt = state.now();\n\tif (!Number.isFinite(startedAt)) throw new Error(\"Telemetry clock returned a non-finite timestamp\");\n\tconst parentTraceparent =\n\t\tparent?.traceparent ?? normalizeTraceparent(options.traceparent) ?? extractedTraceparent ?? null;\n\tconst traceparent = createTraceparent(parentTraceparent ?? undefined);\n\tconst name = options.name;\n\tconst schema = options.schema;\n\tconst attributes = schema ? redactAttributes(options.attributes, schema) : copyAttributes(options.attributes);\n\tconst id = state.nextSpanId++;\n\treturn {\n\t\tid,\n\t\tparentId: parent?.id ?? null,\n\t\tparentTraceparent,\n\t\ttraceparent,\n\t\tname,\n\t\tattributes,\n\t\tschema,\n\t\tevents: [],\n\t\tstatus: { status: \"ok\" },\n\t\texplicitStatus: false,\n\t\tsettled: false,\n\t\tstartedAt,\n\t};\n}\n\nfunction startInMemorySpan<T>(\n\tstate: InMemoryTelemetryState,\n\tparent: MutableRecordedTelemetrySpan | undefined,\n\toptions: SpanOptions,\n\tcallback: (span: TelemetrySpan) => T | Promise<T>,\n\textractedTraceparent?: string,\n): T | Promise<T> {\n\tif (parent?.settled) return NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);\n\ttry {\n\t\tif (options.enabled === false) return NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);\n\t} catch {\n\t\treturn NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);\n\t}\n\n\tlet recordedSpan: MutableRecordedTelemetrySpan;\n\ttry {\n\t\trecordedSpan = createSpan(state, parent, options, extractedTraceparent);\n\t\tstate.spans.push(recordedSpan);\n\t} catch {\n\t\treturn NOOP_TELEMETRY_CONTEXT.startSpan(options, callback);\n\t}\n\n\tconst span: TelemetrySpan = {\n\t\tstartSpan: <Result>(\n\t\t\tchildOptions: SpanOptions,\n\t\t\tchildCallback: (child: TelemetrySpan) => Result | Promise<Result>,\n\t\t) => startInMemorySpan(state, recordedSpan, childOptions, childCallback),\n\t\textract: (traceparent) => createInMemoryContext(state, normalizeTraceparent(traceparent)),\n\t\tinject: (injectedSpan) => state.spanTraceparents.get(injectedSpan),\n\t\taddEvent(name, attributes) {\n\t\t\tif (recordedSpan.settled) return;\n\t\t\ttry {\n\t\t\t\tconst eventAttributes = recordedSpan.schema\n\t\t\t\t\t? redactAttributes(attributes, recordedSpan.schema)\n\t\t\t\t\t: copyAttributes(attributes);\n\t\t\t\trecordedSpan.events.push({ name, attributes: eventAttributes });\n\t\t\t} catch {\n\t\t\t\t// Recording is passive. Ignore malformed or unreadable telemetry payloads.\n\t\t\t}\n\t\t},\n\t\tsetAttributes(attributes) {\n\t\t\tif (recordedSpan.settled) return;\n\t\t\ttry {\n\t\t\t\tconst nextAttributes = recordedSpan.schema ? redactAttributes(attributes, recordedSpan.schema) : attributes;\n\t\t\t\trecordedSpan.attributes = mergeAttributes(recordedSpan.attributes, nextAttributes);\n\t\t\t} catch {\n\t\t\t\t// Recording is passive. Ignore malformed or unreadable telemetry payloads.\n\t\t\t}\n\t\t},\n\t\tsetStatus(status) {\n\t\t\tif (recordedSpan.settled) return;\n\t\t\ttry {\n\t\t\t\trecordedSpan.status = copyStatus(status);\n\t\t\t\trecordedSpan.explicitStatus = true;\n\t\t\t} catch {\n\t\t\t\t// Recording is passive. Ignore malformed or unreadable telemetry payloads.\n\t\t\t}\n\t\t},\n\t};\n\tstate.spanTraceparents.set(span, recordedSpan.traceparent);\n\n\ttry {\n\t\tconst result = callback(span);\n\t\tif (isPromiseLike(result)) {\n\t\t\treturn Promise.resolve(result).then(\n\t\t\t\t(value) => {\n\t\t\t\t\tsettleSpan(state, recordedSpan, false);\n\t\t\t\t\treturn value;\n\t\t\t\t},\n\t\t\t\t(error: unknown) => {\n\t\t\t\t\tsettleSpan(state, recordedSpan, true, error);\n\t\t\t\t\tthrow error;\n\t\t\t\t},\n\t\t\t) as Promise<T>;\n\t\t}\n\t\tsettleSpan(state, recordedSpan, false);\n\t\treturn result;\n\t} catch (error) {\n\t\tsettleSpan(state, recordedSpan, true, error);\n\t\treturn Promise.reject(error);\n\t}\n}\n\nfunction createInMemoryContext(state: InMemoryTelemetryState, extractedTraceparent?: string): TelemetryContext {\n\treturn {\n\t\tstartSpan: (options, callback) => startInMemorySpan(state, undefined, options, callback, extractedTraceparent),\n\t\textract: (traceparent) => createInMemoryContext(state, normalizeTraceparent(traceparent)),\n\t\tinject: (span) => state.spanTraceparents.get(span),\n\t};\n}\n\n/**\n * Backend-neutral reference implementation that records spans in process memory.\n * Create a fresh instance to isolate tests or independent recording scopes.\n */\nexport class InMemoryTelemetryContext implements TelemetryContext {\n\tprivate readonly state: InMemoryTelemetryState;\n\n\tconstructor(options: InMemoryTelemetryContextOptions = {}) {\n\t\tthis.state = {\n\t\t\tspans: [],\n\t\t\tnextSpanId: 1,\n\t\t\tnextEndSequence: 1,\n\t\t\tnow: options.now ?? Date.now,\n\t\t\tspanTraceparents: new WeakMap(),\n\t\t};\n\t}\n\n\tstartSpan<T>(options: SpanOptions, callback: (span: TelemetrySpan) => T | Promise<T>): T | Promise<T> {\n\t\treturn startInMemorySpan(this.state, undefined, options, callback);\n\t}\n\n\textract(traceparent: string | undefined): TelemetryContext {\n\t\treturn createInMemoryContext(this.state, normalizeTraceparent(traceparent));\n\t}\n\n\tinject(span: TelemetrySpan): string | undefined {\n\t\treturn this.state.spanTraceparents.get(span);\n\t}\n\n\t/** Returns detached snapshots in span-start order. */\n\tgetSpans(): readonly RecordedTelemetrySpan[] {\n\t\treturn this.state.spans.map((span) => ({\n\t\t\tid: span.id,\n\t\t\tparentId: span.parentId,\n\t\t\tparentTraceparent: span.parentTraceparent,\n\t\t\ttraceparent: span.traceparent,\n\t\t\tname: span.name,\n\t\t\tattributes: copyAttributes(span.attributes),\n\t\t\tevents: span.events.map((event) => ({\n\t\t\t\tname: event.name,\n\t\t\t\tattributes: copyAttributes(event.attributes),\n\t\t\t})),\n\t\t\tstatus: copyStatus(span.status),\n\t\t\tsettled: span.settled,\n\t\t\tstartedAt: span.startedAt,\n\t\t\t...(span.endedAt === undefined ? {} : { endedAt: span.endedAt }),\n\t\t\t...(span.duration === undefined ? {} : { duration: span.duration }),\n\t\t\t...(span.endSequence === undefined ? {} : { endSequence: span.endSequence }),\n\t\t}));\n\t}\n}\n"]}
|
package/dist/noop.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noop.d.ts","sourceRoot":"","sources":["../src/noop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,gBAAgB,EAAiB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"noop.d.ts","sourceRoot":"","sources":["../src/noop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAe,gBAAgB,EAAiB,MAAM,YAAY,CAAC;AA0B/E,8EAA8E;AAC9E,eAAO,MAAM,sBAAsB,EAAE,gBAAoC,CAAC","sourcesContent":["import type { SpanOptions, TelemetryContext, TelemetrySpan } from \"./index.ts\";\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n\tif (value === null || (typeof value !== \"object\" && typeof value !== \"function\")) return false;\n\treturn \"then\" in value && typeof value.then === \"function\";\n}\n\nfunction startNoopSpan<T>(_options: SpanOptions, callback: (span: TelemetrySpan) => T | Promise<T>): T | Promise<T> {\n\ttry {\n\t\tconst result = callback(noopTelemetrySpan);\n\t\treturn isPromiseLike(result) ? (Promise.resolve(result) as Promise<T>) : result;\n\t} catch (error) {\n\t\treturn Promise.reject(error);\n\t}\n}\n\nconst noopTelemetrySpan: TelemetrySpan = {\n\tstartSpan: startNoopSpan,\n\textract: () => noopTelemetrySpan,\n\tinject: () => undefined,\n\taddEvent: () => {},\n\tsetAttributes: () => {},\n\tsetStatus: () => {},\n};\nObject.freeze(noopTelemetrySpan);\n\n/** Shared telemetry context used when an application does not provide one. */\nexport const NOOP_TELEMETRY_CONTEXT: TelemetryContext = noopTelemetrySpan;\n"]}
|
package/dist/noop.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
function isPromiseLike(value) {
|
|
2
|
+
if (value === null || (typeof value !== "object" && typeof value !== "function"))
|
|
3
|
+
return false;
|
|
4
|
+
return "then" in value && typeof value.then === "function";
|
|
5
|
+
}
|
|
1
6
|
function startNoopSpan(_options, callback) {
|
|
2
7
|
try {
|
|
3
|
-
|
|
8
|
+
const result = callback(noopTelemetrySpan);
|
|
9
|
+
return isPromiseLike(result) ? Promise.resolve(result) : result;
|
|
4
10
|
}
|
|
5
11
|
catch (error) {
|
|
6
12
|
return Promise.reject(error);
|
|
@@ -8,6 +14,8 @@ function startNoopSpan(_options, callback) {
|
|
|
8
14
|
}
|
|
9
15
|
const noopTelemetrySpan = {
|
|
10
16
|
startSpan: startNoopSpan,
|
|
17
|
+
extract: () => noopTelemetrySpan,
|
|
18
|
+
inject: () => undefined,
|
|
11
19
|
addEvent: () => { },
|
|
12
20
|
setAttributes: () => { },
|
|
13
21
|
setStatus: () => { },
|
package/dist/noop.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noop.js","sourceRoot":"","sources":["../src/noop.ts"],"names":[],"mappings":"AAEA,SAAS,aAAa,CAAI,QAAqB,EAAE,QAAiD,
|
|
1
|
+
{"version":3,"file":"noop.js","sourceRoot":"","sources":["../src/noop.ts"],"names":[],"mappings":"AAEA,SAAS,aAAa,CAAC,KAAc,EAAiC;IACrE,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,UAAU,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/F,OAAO,MAAM,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,CAAC;AAAA,CAC3D;AAED,SAAS,aAAa,CAAI,QAAqB,EAAE,QAAiD,EAAkB;IACnH,IAAI,CAAC;QACJ,MAAM,MAAM,GAAG,QAAQ,CAAC,iBAAiB,CAAC,CAAC;QAC3C,OAAO,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,OAAO,CAAC,OAAO,CAAC,MAAM,CAAgB,CAAC,CAAC,CAAC,MAAM,CAAC;IACjF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC;AAAA,CACD;AAED,MAAM,iBAAiB,GAAkB;IACxC,SAAS,EAAE,aAAa;IACxB,OAAO,EAAE,GAAG,EAAE,CAAC,iBAAiB;IAChC,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS;IACvB,QAAQ,EAAE,GAAG,EAAE,CAAC,EAAC,CAAC;IAClB,aAAa,EAAE,GAAG,EAAE,CAAC,EAAC,CAAC;IACvB,SAAS,EAAE,GAAG,EAAE,CAAC,EAAC,CAAC;CACnB,CAAC;AACF,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAEjC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,sBAAsB,GAAqB,iBAAiB,CAAC","sourcesContent":["import type { SpanOptions, TelemetryContext, TelemetrySpan } from \"./index.ts\";\n\nfunction isPromiseLike(value: unknown): value is PromiseLike<unknown> {\n\tif (value === null || (typeof value !== \"object\" && typeof value !== \"function\")) return false;\n\treturn \"then\" in value && typeof value.then === \"function\";\n}\n\nfunction startNoopSpan<T>(_options: SpanOptions, callback: (span: TelemetrySpan) => T | Promise<T>): T | Promise<T> {\n\ttry {\n\t\tconst result = callback(noopTelemetrySpan);\n\t\treturn isPromiseLike(result) ? (Promise.resolve(result) as Promise<T>) : result;\n\t} catch (error) {\n\t\treturn Promise.reject(error);\n\t}\n}\n\nconst noopTelemetrySpan: TelemetrySpan = {\n\tstartSpan: startNoopSpan,\n\textract: () => noopTelemetrySpan,\n\tinject: () => undefined,\n\taddEvent: () => {},\n\tsetAttributes: () => {},\n\tsetStatus: () => {},\n};\nObject.freeze(noopTelemetrySpan);\n\n/** Shared telemetry context used when an application does not provide one. */\nexport const NOOP_TELEMETRY_CONTEXT: TelemetryContext = noopTelemetrySpan;\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SpanAttributes, TelemetrySpanDefinition } from "./index.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Returns a detached attribute bag without values declared sensitive anywhere in the span definition.
|
|
4
|
+
* Unknown attributes are retained because the low-level contract remains open.
|
|
5
|
+
*/
|
|
6
|
+
export declare function redactAttributes(attributes: SpanAttributes | undefined, schema: TelemetrySpanDefinition): SpanAttributes;
|
|
7
|
+
//# sourceMappingURL=redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,cAAc,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAe1F;;;GAGG;AACH,wBAAgB,gBAAgB,CAC/B,UAAU,EAAE,cAAc,GAAG,SAAS,EACtC,MAAM,EAAE,uBAAuB,GAC7B,cAAc,CAQhB","sourcesContent":["import type { AttributeValue, SpanAttributes, TelemetrySpanDefinition } from \"./index.ts\";\n\nfunction copyAttributeValue(value: AttributeValue): AttributeValue {\n\treturn Array.isArray(value) ? ([...value] as AttributeValue) : value;\n}\n\nfunction isSensitiveAttribute(name: string, schema: TelemetrySpanDefinition): boolean {\n\tif (schema.startAttributes[name]?.sensitive === true) return true;\n\tif (schema.endAttributes[name]?.sensitive === true) return true;\n\tfor (const event of Object.values(schema.events ?? {})) {\n\t\tif (event.attributes[name]?.sensitive === true) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Returns a detached attribute bag without values declared sensitive anywhere in the span definition.\n * Unknown attributes are retained because the low-level contract remains open.\n */\nexport function redactAttributes(\n\tattributes: SpanAttributes | undefined,\n\tschema: TelemetrySpanDefinition,\n): SpanAttributes {\n\tconst redacted: SpanAttributes = {};\n\tif (!attributes) return redacted;\n\tfor (const [name, value] of Object.entries(attributes)) {\n\t\tif (value === undefined || isSensitiveAttribute(name, schema)) continue;\n\t\tredacted[name] = copyAttributeValue(value);\n\t}\n\treturn redacted;\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function copyAttributeValue(value) {
|
|
2
|
+
return Array.isArray(value) ? [...value] : value;
|
|
3
|
+
}
|
|
4
|
+
function isSensitiveAttribute(name, schema) {
|
|
5
|
+
if (schema.startAttributes[name]?.sensitive === true)
|
|
6
|
+
return true;
|
|
7
|
+
if (schema.endAttributes[name]?.sensitive === true)
|
|
8
|
+
return true;
|
|
9
|
+
for (const event of Object.values(schema.events ?? {})) {
|
|
10
|
+
if (event.attributes[name]?.sensitive === true)
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Returns a detached attribute bag without values declared sensitive anywhere in the span definition.
|
|
17
|
+
* Unknown attributes are retained because the low-level contract remains open.
|
|
18
|
+
*/
|
|
19
|
+
export function redactAttributes(attributes, schema) {
|
|
20
|
+
const redacted = {};
|
|
21
|
+
if (!attributes)
|
|
22
|
+
return redacted;
|
|
23
|
+
for (const [name, value] of Object.entries(attributes)) {
|
|
24
|
+
if (value === undefined || isSensitiveAttribute(name, schema))
|
|
25
|
+
continue;
|
|
26
|
+
redacted[name] = copyAttributeValue(value);
|
|
27
|
+
}
|
|
28
|
+
return redacted;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAEA,SAAS,kBAAkB,CAAC,KAAqB,EAAkB;IAClE,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAE,CAAC,GAAG,KAAK,CAAoB,CAAC,CAAC,CAAC,KAAK,CAAC;AAAA,CACrE;AAED,SAAS,oBAAoB,CAAC,IAAY,EAAE,MAA+B,EAAW;IACrF,IAAI,MAAM,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAClE,IAAI,MAAM,CAAC,aAAa,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAChE,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,SAAS,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IAC7D,CAAC;IACD,OAAO,KAAK,CAAC;AAAA,CACb;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC/B,UAAsC,EACtC,MAA+B,EACd;IACjB,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,IAAI,CAAC,UAAU;QAAE,OAAO,QAAQ,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;QACxD,IAAI,KAAK,KAAK,SAAS,IAAI,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC;YAAE,SAAS;QACxE,QAAQ,CAAC,IAAI,CAAC,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,QAAQ,CAAC;AAAA,CAChB","sourcesContent":["import type { AttributeValue, SpanAttributes, TelemetrySpanDefinition } from \"./index.ts\";\n\nfunction copyAttributeValue(value: AttributeValue): AttributeValue {\n\treturn Array.isArray(value) ? ([...value] as AttributeValue) : value;\n}\n\nfunction isSensitiveAttribute(name: string, schema: TelemetrySpanDefinition): boolean {\n\tif (schema.startAttributes[name]?.sensitive === true) return true;\n\tif (schema.endAttributes[name]?.sensitive === true) return true;\n\tfor (const event of Object.values(schema.events ?? {})) {\n\t\tif (event.attributes[name]?.sensitive === true) return true;\n\t}\n\treturn false;\n}\n\n/**\n * Returns a detached attribute bag without values declared sensitive anywhere in the span definition.\n * Unknown attributes are retained because the low-level contract remains open.\n */\nexport function redactAttributes(\n\tattributes: SpanAttributes | undefined,\n\tschema: TelemetrySpanDefinition,\n): SpanAttributes {\n\tconst redacted: SpanAttributes = {};\n\tif (!attributes) return redacted;\n\tfor (const [name, value] of Object.entries(attributes)) {\n\t\tif (value === undefined || isSensitiveAttribute(name, schema)) continue;\n\t\tredacted[name] = copyAttributeValue(value);\n\t}\n\treturn redacted;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../../src/testing/conformance.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,+BAA+B,EAE/B,8BAA8B,EAC9B,MAAM,YAAY,CAAC;AAoDpB,oFAAoF;AACpF,wBAAgB,iCAAiC,CAChD,OAAO,EAAE,8BAA8B,GACrC,SAAS,+BAA+B,EAAE,CA4P5C","sourcesContent":["import { deepStrictEqual, doesNotThrow, fail, ok, strictEqual } from \"node:assert/strict\";\nimport type { SpanAttributes, SpanOptions, SpanStatus, TelemetrySpan } from \"../index.ts\";\nimport type { RecordedTelemetrySpan } from \"../memory.ts\";\nimport type {\n\tTelemetryAdapterConformanceCase,\n\tTelemetryAdapterFixture,\n\tTelemetryAdapterFixtureFactory,\n} from \"./types.ts\";\n\ntype ConformanceTest = (fixture: TelemetryAdapterFixture) => Promise<void>;\n\nfunction createCase(\n\tfactory: TelemetryAdapterFixtureFactory,\n\tgroup: string,\n\tname: string,\n\ttest: ConformanceTest,\n): TelemetryAdapterConformanceCase {\n\treturn {\n\t\tgroup,\n\t\tname,\n\t\tasync run() {\n\t\t\tawait using fixture = await factory();\n\t\t\tawait test(fixture);\n\t\t},\n\t};\n}\n\nfunction findSpan(spans: readonly RecordedTelemetrySpan[], name: string): RecordedTelemetrySpan {\n\tconst span = spans.find((candidate) => candidate.name === name);\n\tok(span, `Expected recorded span ${name}`);\n\treturn span;\n}\n\nasync function rejectsWithSameValue(operation: Promise<unknown>, expected: unknown): Promise<void> {\n\ttry {\n\t\tawait operation;\n\t\tfail(\"Expected operation to reject\");\n\t} catch (error) {\n\t\tstrictEqual(error, expected);\n\t}\n}\n\nfunction unreadable<T extends object>(value: T): T {\n\treturn new Proxy(value, {\n\t\tget: () => {\n\t\t\tthrow new Error(\"read\");\n\t\t},\n\t\tgetOwnPropertyDescriptor: () => {\n\t\t\tthrow new Error(\"inspect\");\n\t\t},\n\t\tgetPrototypeOf: () => {\n\t\t\tthrow new Error(\"prototype\");\n\t\t},\n\t\townKeys: () => {\n\t\t\tthrow new Error(\"enumerate\");\n\t\t},\n\t});\n}\n\n/** Creates runner-independent cases for the callback telemetry adapter contract. */\nexport function createTelemetryAdapterConformance(\n\tfactory: TelemetryAdapterFixtureFactory,\n): readonly TelemetryAdapterConformanceCase[] {\n\treturn [\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"callback lifecycle\",\n\t\t\t\"admits once synchronously and preserves the result\",\n\t\t\tasync (fixture) => {\n\t\t\t\tlet admitted = false;\n\t\t\t\tlet calls = 0;\n\t\t\t\tconst expected = { value: 42 };\n\t\t\t\tconst result = fixture.context.startSpan({ name: \"success\" }, () => {\n\t\t\t\t\tadmitted = true;\n\t\t\t\t\tcalls++;\n\t\t\t\t\treturn expected;\n\t\t\t\t});\n\n\t\t\t\tstrictEqual(admitted, true);\n\t\t\t\tstrictEqual(calls, 1);\n\t\t\t\tstrictEqual(await result, expected);\n\t\t\t\tdeepStrictEqual(findSpan(await fixture.getSpans(), \"success\").status, { status: \"ok\" });\n\t\t\t\tstrictEqual(findSpan(await fixture.getSpans(), \"success\").settled, true);\n\t\t\t},\n\t\t),\n\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"callback lifecycle\",\n\t\t\t\"preserves synchronous and asynchronous rejection values\",\n\t\t\tasync (fixture) => {\n\t\t\t\tconst syncError = new Error(\"sync\");\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"sync-error\" }, () => {\n\t\t\t\t\t\tthrow syncError;\n\t\t\t\t\t}),\n\t\t\t\t\tsyncError,\n\t\t\t\t);\n\n\t\t\t\tconst asyncError = { kind: \"async\" };\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"async-error\" }, async () => {\n\t\t\t\t\t\tthrow asyncError;\n\t\t\t\t\t}),\n\t\t\t\t\tasyncError,\n\t\t\t\t);\n\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"undefined-error\" }, () => Promise.reject(undefined)),\n\t\t\t\t\tundefined,\n\t\t\t\t);\n\n\t\t\t\tconst unreadableError = unreadable({ kind: \"unreadable\" });\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"unreadable-error\" }, () => {\n\t\t\t\t\t\tthrow unreadableError;\n\t\t\t\t\t}),\n\t\t\t\t\tunreadableError,\n\t\t\t\t);\n\n\t\t\t\tconst asyncUnreadableError = unreadable({ kind: \"async-unreadable\" });\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"async-unreadable-error\" }, () =>\n\t\t\t\t\t\tPromise.reject(asyncUnreadableError),\n\t\t\t\t\t),\n\t\t\t\t\tasyncUnreadableError,\n\t\t\t\t);\n\n\t\t\t\tconst spans = await fixture.getSpans();\n\t\t\t\tfor (const name of [\n\t\t\t\t\t\"sync-error\",\n\t\t\t\t\t\"async-error\",\n\t\t\t\t\t\"undefined-error\",\n\t\t\t\t\t\"unreadable-error\",\n\t\t\t\t\t\"async-unreadable-error\",\n\t\t\t\t]) {\n\t\t\t\t\tstrictEqual(findSpan(spans, name).status.status, \"error\");\n\t\t\t\t}\n\t\t\t},\n\t\t),\n\n\t\tcreateCase(factory, \"status\", \"uses last explicit status without automatic overwrite\", async (fixture) => {\n\t\t\tawait fixture.context.startSpan({ name: \"last-status\" }, (span) => {\n\t\t\t\tspan.setStatus({ status: \"error\", error: { name: \"Expected\", message: \"first\" } });\n\t\t\t\tspan.setStatus({ status: \"ok\" });\n\t\t\t});\n\n\t\t\tconst thrown = new Error(\"after explicit status\");\n\t\t\tawait rejectsWithSameValue(\n\t\t\t\tfixture.context.startSpan({ name: \"explicit-before-throw\" }, (span) => {\n\t\t\t\t\tspan.setStatus({ status: \"ok\" });\n\t\t\t\t\tthrow thrown;\n\t\t\t\t}),\n\t\t\t\tthrown,\n\t\t\t);\n\n\t\t\tconst rejected = new Error(\"after async explicit status\");\n\t\t\tawait rejectsWithSameValue(\n\t\t\t\tfixture.context.startSpan({ name: \"explicit-before-rejection\" }, (span) => {\n\t\t\t\t\tspan.setStatus({ status: \"error\", error: { name: \"Expected\", message: \"async failure\" } });\n\t\t\t\t\treturn Promise.reject(rejected);\n\t\t\t\t}),\n\t\t\t\trejected,\n\t\t\t);\n\n\t\t\tawait fixture.context.startSpan({ name: \"expected-failure\" }, (span) => {\n\t\t\t\tspan.setStatus({ status: \"error\", error: { name: \"Expected\", message: \"returned failure\" } });\n\t\t\t\treturn { ok: false };\n\t\t\t});\n\n\t\t\tconst spans = await fixture.getSpans();\n\t\t\tdeepStrictEqual(findSpan(spans, \"last-status\").status, { status: \"ok\" });\n\t\t\tdeepStrictEqual(findSpan(spans, \"explicit-before-throw\").status, { status: \"ok\" });\n\t\t\tdeepStrictEqual(findSpan(spans, \"explicit-before-rejection\").status, {\n\t\t\t\tstatus: \"error\",\n\t\t\t\terror: { name: \"Expected\", message: \"async failure\" },\n\t\t\t});\n\t\t\tdeepStrictEqual(findSpan(spans, \"expected-failure\").status, {\n\t\t\t\tstatus: \"error\",\n\t\t\t\terror: { name: \"Expected\", message: \"returned failure\" },\n\t\t\t});\n\t\t}),\n\n\t\tcreateCase(factory, \"recording\", \"merges attributes and records ordered events\", async (fixture) => {\n\t\t\tawait fixture.context.startSpan(\n\t\t\t\t{\n\t\t\t\t\tname: \"recording\",\n\t\t\t\t\tattributes: { start: \"value\", overwrite: \"start\", ignored: undefined },\n\t\t\t\t},\n\t\t\t\t(span) => {\n\t\t\t\t\tspan.setAttributes({ count: 1, overwrite: \"middle\" });\n\t\t\t\t\tspan.setAttributes({ count: undefined, overwrite: \"end\" });\n\t\t\t\t\tspan.addEvent(\"first\", { index: 1, ignored: undefined });\n\t\t\t\t\tspan.addEvent(\"second\", { index: 2 });\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst span = findSpan(await fixture.getSpans(), \"recording\");\n\t\t\tdeepStrictEqual(span.attributes, { start: \"value\", overwrite: \"end\", count: 1 });\n\t\t\tdeepStrictEqual(span.events, [\n\t\t\t\t{ name: \"first\", attributes: { index: 1 } },\n\t\t\t\t{ name: \"second\", attributes: { index: 2 } },\n\t\t\t]);\n\t\t}),\n\n\t\tcreateCase(factory, \"recording\", \"ignores failed attribute calls atomically\", async (fixture) => {\n\t\t\tawait fixture.context.startSpan({ name: \"atomic-attributes\", attributes: { retained: \"value\" } }, (span) => {\n\t\t\t\tconst attributes: SpanAttributes = {\n\t\t\t\t\tpartial: \"must not survive\",\n\t\t\t\t\tunreadable: unreadable([\"value\"]),\n\t\t\t\t};\n\t\t\t\tdoesNotThrow(() => span.setAttributes(attributes));\n\t\t\t});\n\n\t\t\tdeepStrictEqual(findSpan(await fixture.getSpans(), \"atomic-attributes\").attributes, {\n\t\t\t\tretained: \"value\",\n\t\t\t});\n\t\t}),\n\n\t\tcreateCase(factory, \"recording\", \"makes calls after settlement inert\", async (fixture) => {\n\t\t\tlet settledSpan: TelemetrySpan | undefined;\n\t\t\tawait fixture.context.startSpan({ name: \"settled\", attributes: { value: \"initial\" } }, (span) => {\n\t\t\t\tsettledSpan = span;\n\t\t\t});\n\t\t\tconst capturedSpan = settledSpan;\n\t\t\tif (!capturedSpan) throw new Error(\"Expected callback span\");\n\n\t\t\tdoesNotThrow(() => capturedSpan.setAttributes({ value: \"late\" }));\n\t\t\tdoesNotThrow(() => capturedSpan.addEvent(\"late\", { value: true }));\n\t\t\tdoesNotThrow(() => capturedSpan.setStatus({ status: \"error\" }));\n\t\t\tlet childAdmitted = false;\n\t\t\tconst childResult = capturedSpan.startSpan({ name: \"late-child\" }, () => {\n\t\t\t\tchildAdmitted = true;\n\t\t\t\treturn 7;\n\t\t\t});\n\t\t\tstrictEqual(childAdmitted, true);\n\t\t\tstrictEqual(await childResult, 7);\n\n\t\t\tconst spans = await fixture.getSpans();\n\t\t\tstrictEqual(spans.length, 1);\n\t\t\tdeepStrictEqual(spans[0]?.attributes, { value: \"initial\" });\n\t\t\tdeepStrictEqual(spans[0]?.events, []);\n\t\t\tdeepStrictEqual(spans[0]?.status, { status: \"ok\" });\n\t\t}),\n\n\t\tcreateCase(factory, \"parentage\", \"records nested and concurrent child relationships\", async (fixture) => {\n\t\t\tlet releaseFirst: (() => void) | undefined;\n\t\t\tconst firstGate = new Promise<void>((resolve) => {\n\t\t\t\treleaseFirst = resolve;\n\t\t\t});\n\n\t\t\tawait fixture.context.startSpan({ name: \"parent\" }, async (parent) => {\n\t\t\t\tconst first = parent.startSpan({ name: \"first-child\" }, async () => {\n\t\t\t\t\tawait firstGate;\n\t\t\t\t});\n\t\t\t\tconst second = parent.startSpan({ name: \"second-child\" }, () => \"done\");\n\t\t\t\tstrictEqual(await second, \"done\");\n\t\t\t\treleaseFirst?.();\n\t\t\t\tawait first;\n\t\t\t});\n\n\t\t\tconst spans = await fixture.getSpans();\n\t\t\tconst parent = findSpan(spans, \"parent\");\n\t\t\tconst first = findSpan(spans, \"first-child\");\n\t\t\tconst second = findSpan(spans, \"second-child\");\n\t\t\tstrictEqual(parent.parentId, null);\n\t\t\tstrictEqual(first.parentId, parent.id);\n\t\t\tstrictEqual(second.parentId, parent.id);\n\t\t\tok(second.endSequence !== undefined && first.endSequence !== undefined && parent.endSequence !== undefined);\n\t\t\tok(second.endSequence < first.endSequence);\n\t\t\tok(first.endSequence < parent.endSequence);\n\t\t}),\n\n\t\tcreateCase(factory, \"passivity\", \"suppresses unreadable telemetry payload failures\", async (fixture) => {\n\t\t\tlet calls = 0;\n\t\t\tconst options = unreadable<SpanOptions>({ name: \"unreadable-options\", attributes: { secret: \"value\" } });\n\t\t\tconst result = fixture.context.startSpan(options, () => {\n\t\t\t\tcalls++;\n\t\t\t\treturn 9;\n\t\t\t});\n\n\t\t\tstrictEqual(calls, 1);\n\t\t\tstrictEqual(await result, 9);\n\t\t\tdeepStrictEqual(await fixture.getSpans(), []);\n\n\t\t\tawait fixture.context.startSpan({ name: \"unreadable-recording\" }, (span) => {\n\t\t\t\tconst attributes = unreadable<SpanAttributes>({ secret: \"value\" });\n\t\t\t\tconst status = unreadable<SpanStatus>({ status: \"ok\" });\n\t\t\t\tdoesNotThrow(() => span.setAttributes(attributes));\n\t\t\t\tdoesNotThrow(() => span.addEvent(\"unreadable-event\", attributes));\n\t\t\t\tdoesNotThrow(() => span.setStatus(status));\n\t\t\t});\n\n\t\t\tconst recorded = await fixture.getSpans();\n\t\t\tstrictEqual(recorded.length, 1);\n\t\t\tdeepStrictEqual(recorded[0]?.attributes, {});\n\t\t\tdeepStrictEqual(recorded[0]?.events, []);\n\t\t\tdeepStrictEqual(recorded[0]?.status, { status: \"ok\" });\n\t\t}),\n\n\t\tcreateCase(factory, \"passivity\", \"ignores failed status calls atomically\", async (fixture) => {\n\t\t\tconst rejection = new Error(\"rejected after unreadable status\");\n\t\t\tawait rejectsWithSameValue(\n\t\t\t\tfixture.context.startSpan({ name: \"unreadable-status\" }, (span) => {\n\t\t\t\t\tconst status = unreadable<SpanStatus>({ status: \"ok\" });\n\t\t\t\t\tdoesNotThrow(() => span.setStatus(status));\n\t\t\t\t\treturn Promise.reject(rejection);\n\t\t\t\t}),\n\t\t\t\trejection,\n\t\t\t);\n\n\t\t\tstrictEqual(findSpan(await fixture.getSpans(), \"unreadable-status\").status.status, \"error\");\n\t\t}),\n\t];\n}\n"]}
|
|
1
|
+
{"version":3,"file":"conformance.d.ts","sourceRoot":"","sources":["../../src/testing/conformance.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,+BAA+B,EAE/B,8BAA8B,EAC9B,MAAM,YAAY,CAAC;AAwGpB,oFAAoF;AACpF,wBAAgB,iCAAiC,CAChD,OAAO,EAAE,8BAA8B,GACrC,SAAS,+BAA+B,EAAE,CA8Y5C","sourcesContent":["import { deepStrictEqual, doesNotThrow, fail, notDeepStrictEqual, ok, strictEqual } from \"node:assert/strict\";\nimport type { SpanAttributes, SpanOptions, SpanStatus, TelemetrySpan, TelemetrySpanDefinition } from \"../index.ts\";\nimport type { RecordedTelemetrySpan } from \"../memory.ts\";\nimport type {\n\tTelemetryAdapterConformanceCase,\n\tTelemetryAdapterFixture,\n\tTelemetryAdapterFixtureFactory,\n} from \"./types.ts\";\n\nconst sensitiveSpanSchema = {\n\tdescription: \"Span with sensitive attributes\",\n\tparents: { kind: \"any\" },\n\tstartAttributes: {\n\t\tstartSecret: { type: \"string\", required: true, sensitive: true, description: \"Sensitive start value\" },\n\t\tstartSecretNumber: { type: \"number\", required: true, sensitive: true, description: \"Sensitive number\" },\n\t\tstartSecretBoolean: { type: \"boolean\", required: true, sensitive: true, description: \"Sensitive boolean\" },\n\t\tstartSecretArray: { type: \"string[]\", required: true, sensitive: true, description: \"Sensitive string array\" },\n\t\tstartSecretNumberArray: {\n\t\t\ttype: \"number[]\",\n\t\t\trequired: true,\n\t\t\tsensitive: true,\n\t\t\tdescription: \"Sensitive number array\",\n\t\t},\n\t\tstartSecretBooleanArray: {\n\t\t\ttype: \"boolean[]\",\n\t\t\trequired: true,\n\t\t\tsensitive: true,\n\t\t\tdescription: \"Sensitive boolean array\",\n\t\t},\n\t\tstartSafe: { type: \"string\", required: true, sensitive: false, description: \"Safe start value\" },\n\t},\n\tendAttributes: {\n\t\tendSecret: { type: \"string\", sensitive: true, description: \"Sensitive end value\" },\n\t\tendSecretNumber: { type: \"number\", sensitive: true, description: \"Sensitive end number\" },\n\t},\n\tevents: {\n\t\tresult: {\n\t\t\tdescription: \"Result event\",\n\t\t\tattributes: {\n\t\t\t\teventSecret: { type: \"string\", required: true, sensitive: true, description: \"Sensitive event value\" },\n\t\t\t\teventSecretBoolean: {\n\t\t\t\t\ttype: \"boolean\",\n\t\t\t\t\trequired: true,\n\t\t\t\t\tsensitive: true,\n\t\t\t\t\tdescription: \"Sensitive event boolean\",\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t},\n\tstatus: { default: \"ok\", errorWhen: \"The operation fails\" },\n} satisfies TelemetrySpanDefinition;\n\ntype ConformanceTest = (fixture: TelemetryAdapterFixture) => Promise<void>;\n\nfunction assertTraceparent(value: string | undefined): asserts value is string {\n\tok(value, \"Expected a traceparent\");\n\tconst match = /^00-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/.exec(value);\n\tok(match, \"Expected a valid W3C traceparent\");\n\tok(match[1] !== \"0\".repeat(32), \"Expected a non-zero trace ID\");\n\tok(match[2] !== \"0\".repeat(16), \"Expected a non-zero span ID\");\n\tok(match[3] === \"00\" || match[3] === \"01\", \"Expected only the sampled trace flag\");\n}\n\nfunction createCase(\n\tfactory: TelemetryAdapterFixtureFactory,\n\tgroup: string,\n\tname: string,\n\ttest: ConformanceTest,\n): TelemetryAdapterConformanceCase {\n\treturn {\n\t\tgroup,\n\t\tname,\n\t\tasync run() {\n\t\t\tawait using fixture = await factory();\n\t\t\tawait test(fixture);\n\t\t},\n\t};\n}\n\nfunction findSpan(spans: readonly RecordedTelemetrySpan[], name: string): RecordedTelemetrySpan {\n\tconst span = spans.find((candidate) => candidate.name === name);\n\tok(span, `Expected recorded span ${name}`);\n\treturn span;\n}\n\nasync function rejectsWithSameValue<Result>(operation: Result | Promise<Result>, expected: unknown): Promise<void> {\n\ttry {\n\t\tawait operation;\n\t\tfail(\"Expected operation to reject\");\n\t} catch (error) {\n\t\tstrictEqual(error, expected);\n\t}\n}\n\nfunction unreadable<T extends object>(value: T): T {\n\treturn new Proxy(value, {\n\t\tget: () => {\n\t\t\tthrow new Error(\"read\");\n\t\t},\n\t\tgetOwnPropertyDescriptor: () => {\n\t\t\tthrow new Error(\"inspect\");\n\t\t},\n\t\tgetPrototypeOf: () => {\n\t\t\tthrow new Error(\"prototype\");\n\t\t},\n\t\townKeys: () => {\n\t\t\tthrow new Error(\"enumerate\");\n\t\t},\n\t});\n}\n\n/** Creates runner-independent cases for the callback telemetry adapter contract. */\nexport function createTelemetryAdapterConformance(\n\tfactory: TelemetryAdapterFixtureFactory,\n): readonly TelemetryAdapterConformanceCase[] {\n\treturn [\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"callback lifecycle\",\n\t\t\t\"admits once synchronously and preserves the result\",\n\t\t\tasync (fixture) => {\n\t\t\t\tlet admitted = false;\n\t\t\t\tlet calls = 0;\n\t\t\t\tconst expected = { value: 42 };\n\t\t\t\tconst result = fixture.context.startSpan({ name: \"success\" }, () => {\n\t\t\t\t\tadmitted = true;\n\t\t\t\t\tcalls++;\n\t\t\t\t\treturn expected;\n\t\t\t\t});\n\n\t\t\t\tok(!(result instanceof Promise), \"Expected a synchronous callback result to remain synchronous\");\n\t\t\t\tstrictEqual(admitted, true);\n\t\t\t\tstrictEqual(calls, 1);\n\t\t\t\tstrictEqual(await result, expected);\n\t\t\t\tdeepStrictEqual(findSpan(await fixture.getSpans(), \"success\").status, { status: \"ok\" });\n\t\t\t\tstrictEqual(findSpan(await fixture.getSpans(), \"success\").settled, true);\n\t\t\t},\n\t\t),\n\n\t\tcreateCase(\n\t\t\tfactory,\n\t\t\t\"callback lifecycle\",\n\t\t\t\"preserves synchronous and asynchronous rejection values\",\n\t\t\tasync (fixture) => {\n\t\t\t\tconst syncError = new Error(\"sync\");\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"sync-error\" }, () => {\n\t\t\t\t\t\tthrow syncError;\n\t\t\t\t\t}),\n\t\t\t\t\tsyncError,\n\t\t\t\t);\n\n\t\t\t\tconst asyncError = { kind: \"async\" };\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"async-error\" }, async () => {\n\t\t\t\t\t\tthrow asyncError;\n\t\t\t\t\t}),\n\t\t\t\t\tasyncError,\n\t\t\t\t);\n\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"undefined-error\" }, () => Promise.reject(undefined)),\n\t\t\t\t\tundefined,\n\t\t\t\t);\n\n\t\t\t\tconst unreadableError = unreadable({ kind: \"unreadable\" });\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"unreadable-error\" }, () => {\n\t\t\t\t\t\tthrow unreadableError;\n\t\t\t\t\t}),\n\t\t\t\t\tunreadableError,\n\t\t\t\t);\n\n\t\t\t\tconst asyncUnreadableError = unreadable({ kind: \"async-unreadable\" });\n\t\t\t\tawait rejectsWithSameValue(\n\t\t\t\t\tfixture.context.startSpan({ name: \"async-unreadable-error\" }, () =>\n\t\t\t\t\t\tPromise.reject(asyncUnreadableError),\n\t\t\t\t\t),\n\t\t\t\t\tasyncUnreadableError,\n\t\t\t\t);\n\n\t\t\t\tconst spans = await fixture.getSpans();\n\t\t\t\tfor (const name of [\n\t\t\t\t\t\"sync-error\",\n\t\t\t\t\t\"async-error\",\n\t\t\t\t\t\"undefined-error\",\n\t\t\t\t\t\"unreadable-error\",\n\t\t\t\t\t\"async-unreadable-error\",\n\t\t\t\t]) {\n\t\t\t\t\tstrictEqual(findSpan(spans, name).status.status, \"error\");\n\t\t\t\t}\n\t\t\t},\n\t\t),\n\n\t\tcreateCase(factory, \"status\", \"uses last explicit status without automatic overwrite\", async (fixture) => {\n\t\t\tawait fixture.context.startSpan({ name: \"last-status\" }, (span) => {\n\t\t\t\tspan.setStatus({ status: \"error\", error: { name: \"Expected\", message: \"first\" } });\n\t\t\t\tspan.setStatus({ status: \"ok\" });\n\t\t\t});\n\n\t\t\tconst thrown = new Error(\"after explicit status\");\n\t\t\tawait rejectsWithSameValue(\n\t\t\t\tfixture.context.startSpan({ name: \"explicit-before-throw\" }, (span) => {\n\t\t\t\t\tspan.setStatus({ status: \"ok\" });\n\t\t\t\t\tthrow thrown;\n\t\t\t\t}),\n\t\t\t\tthrown,\n\t\t\t);\n\n\t\t\tconst rejected = new Error(\"after async explicit status\");\n\t\t\tawait rejectsWithSameValue(\n\t\t\t\tfixture.context.startSpan({ name: \"explicit-before-rejection\" }, (span) => {\n\t\t\t\t\tspan.setStatus({ status: \"error\", error: { name: \"Expected\", message: \"async failure\" } });\n\t\t\t\t\treturn Promise.reject(rejected);\n\t\t\t\t}),\n\t\t\t\trejected,\n\t\t\t);\n\n\t\t\tawait fixture.context.startSpan({ name: \"expected-failure\" }, (span) => {\n\t\t\t\tspan.setStatus({ status: \"error\", error: { name: \"Expected\", message: \"returned failure\" } });\n\t\t\t\treturn { ok: false };\n\t\t\t});\n\n\t\t\tconst spans = await fixture.getSpans();\n\t\t\tdeepStrictEqual(findSpan(spans, \"last-status\").status, { status: \"ok\" });\n\t\t\tdeepStrictEqual(findSpan(spans, \"explicit-before-throw\").status, { status: \"ok\" });\n\t\t\tdeepStrictEqual(findSpan(spans, \"explicit-before-rejection\").status, {\n\t\t\t\tstatus: \"error\",\n\t\t\t\terror: { name: \"Expected\", message: \"async failure\" },\n\t\t\t});\n\t\t\tdeepStrictEqual(findSpan(spans, \"expected-failure\").status, {\n\t\t\t\tstatus: \"error\",\n\t\t\t\terror: { name: \"Expected\", message: \"returned failure\" },\n\t\t\t});\n\t\t}),\n\n\t\tcreateCase(factory, \"recording\", \"merges attributes and records ordered events\", async (fixture) => {\n\t\t\tawait fixture.context.startSpan(\n\t\t\t\t{\n\t\t\t\t\tname: \"recording\",\n\t\t\t\t\tattributes: { start: \"value\", overwrite: \"start\", ignored: undefined },\n\t\t\t\t},\n\t\t\t\t(span) => {\n\t\t\t\t\tspan.setAttributes({ count: 1, overwrite: \"middle\" });\n\t\t\t\t\tspan.setAttributes({ count: undefined, overwrite: \"end\" });\n\t\t\t\t\tspan.addEvent(\"first\", { index: 1, ignored: undefined });\n\t\t\t\t\tspan.addEvent(\"second\", { index: 2 });\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst span = findSpan(await fixture.getSpans(), \"recording\");\n\t\t\tdeepStrictEqual(span.attributes, { start: \"value\", overwrite: \"end\", count: 1 });\n\t\t\tdeepStrictEqual(span.events, [\n\t\t\t\t{ name: \"first\", attributes: { index: 1 } },\n\t\t\t\t{ name: \"second\", attributes: { index: 2 } },\n\t\t\t]);\n\t\t}),\n\n\t\tcreateCase(factory, \"recording\", \"ignores failed attribute calls atomically\", async (fixture) => {\n\t\t\tawait fixture.context.startSpan({ name: \"atomic-attributes\", attributes: { retained: \"value\" } }, (span) => {\n\t\t\t\tconst attributes: SpanAttributes = {\n\t\t\t\t\tpartial: \"must not survive\",\n\t\t\t\t\tunreadable: unreadable([\"value\"]),\n\t\t\t\t};\n\t\t\t\tdoesNotThrow(() => span.setAttributes(attributes));\n\t\t\t});\n\n\t\t\tdeepStrictEqual(findSpan(await fixture.getSpans(), \"atomic-attributes\").attributes, {\n\t\t\t\tretained: \"value\",\n\t\t\t});\n\t\t}),\n\n\t\tcreateCase(factory, \"recording\", \"makes calls after settlement inert\", async (fixture) => {\n\t\t\tlet settledSpan: TelemetrySpan | undefined;\n\t\t\tawait fixture.context.startSpan({ name: \"settled\", attributes: { value: \"initial\" } }, (span) => {\n\t\t\t\tsettledSpan = span;\n\t\t\t});\n\t\t\tconst capturedSpan = settledSpan;\n\t\t\tif (!capturedSpan) throw new Error(\"Expected callback span\");\n\n\t\t\tdoesNotThrow(() => capturedSpan.setAttributes({ value: \"late\" }));\n\t\t\tdoesNotThrow(() => capturedSpan.addEvent(\"late\", { value: true }));\n\t\t\tdoesNotThrow(() => capturedSpan.setStatus({ status: \"error\" }));\n\t\t\tlet childAdmitted = false;\n\t\t\tconst childResult = capturedSpan.startSpan({ name: \"late-child\" }, () => {\n\t\t\t\tchildAdmitted = true;\n\t\t\t\treturn 7;\n\t\t\t});\n\t\t\tstrictEqual(childAdmitted, true);\n\t\t\tok(!(childResult instanceof Promise), \"Expected a synchronous child result to remain synchronous\");\n\t\t\tstrictEqual(await childResult, 7);\n\n\t\t\tconst spans = await fixture.getSpans();\n\t\t\tstrictEqual(spans.length, 1);\n\t\t\tdeepStrictEqual(spans[0]?.attributes, { value: \"initial\" });\n\t\t\tdeepStrictEqual(spans[0]?.events, []);\n\t\t\tdeepStrictEqual(spans[0]?.status, { status: \"ok\" });\n\t\t}),\n\n\t\tcreateCase(factory, \"parentage\", \"records nested and concurrent child relationships\", async (fixture) => {\n\t\t\tlet releaseFirst: (() => void) | undefined;\n\t\t\tconst firstGate = new Promise<void>((resolve) => {\n\t\t\t\treleaseFirst = resolve;\n\t\t\t});\n\n\t\t\tawait fixture.context.startSpan({ name: \"parent\" }, async (parent) => {\n\t\t\t\tconst first = parent.startSpan({ name: \"first-child\" }, async () => {\n\t\t\t\t\tawait firstGate;\n\t\t\t\t});\n\t\t\t\tconst second = parent.startSpan({ name: \"second-child\" }, () => \"done\");\n\t\t\t\tstrictEqual(await second, \"done\");\n\t\t\t\treleaseFirst?.();\n\t\t\t\tawait first;\n\t\t\t});\n\n\t\t\tconst spans = await fixture.getSpans();\n\t\t\tconst parent = findSpan(spans, \"parent\");\n\t\t\tconst first = findSpan(spans, \"first-child\");\n\t\t\tconst second = findSpan(spans, \"second-child\");\n\t\t\tstrictEqual(parent.parentId, null);\n\t\t\tstrictEqual(first.parentId, parent.id);\n\t\t\tstrictEqual(second.parentId, parent.id);\n\t\t\tok(second.endSequence !== undefined && first.endSequence !== undefined && parent.endSequence !== undefined);\n\t\t\tok(second.endSequence < first.endSequence);\n\t\t\tok(first.endSequence < parent.endSequence);\n\t\t}),\n\n\t\tcreateCase(factory, \"privacy\", \"redacts schema-declared sensitive attributes\", async (fixture) => {\n\t\t\tconst startSecret = \"start-secret-value\";\n\t\t\tconst startSecretNumber = 42;\n\t\t\tconst startSecretBoolean = true;\n\t\t\tconst startSecretArray = [\"one\", \"two\"];\n\t\t\tconst startSecretNumberArray = [1, 2];\n\t\t\tconst startSecretBooleanArray = [true, false];\n\t\t\tconst endSecret = \"end-secret-value\";\n\t\t\tconst endSecretNumber = 84;\n\t\t\tconst eventSecret = \"event-secret-value\";\n\t\t\tconst eventSecretBoolean = false;\n\t\t\tawait fixture.context.startSpan(\n\t\t\t\t{\n\t\t\t\t\tname: \"sensitive\",\n\t\t\t\t\tschema: sensitiveSpanSchema,\n\t\t\t\t\tattributes: {\n\t\t\t\t\t\tstartSecret,\n\t\t\t\t\t\tstartSecretNumber,\n\t\t\t\t\t\tstartSecretBoolean,\n\t\t\t\t\t\tstartSecretArray,\n\t\t\t\t\t\tstartSecretNumberArray,\n\t\t\t\t\t\tstartSecretBooleanArray,\n\t\t\t\t\t\tstartSafe: \"retained\",\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t(span) => {\n\t\t\t\t\tspan.setAttributes({ endSecret, endSecretNumber });\n\t\t\t\t\tspan.addEvent(\"result\", { eventSecret, eventSecretBoolean });\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tconst span = findSpan(await fixture.getSpans(), \"sensitive\");\n\t\t\tnotDeepStrictEqual(span.attributes.startSecret, startSecret);\n\t\t\tnotDeepStrictEqual(span.attributes.startSecretNumber, startSecretNumber);\n\t\t\tnotDeepStrictEqual(span.attributes.startSecretBoolean, startSecretBoolean);\n\t\t\tnotDeepStrictEqual(span.attributes.startSecretArray, startSecretArray);\n\t\t\tnotDeepStrictEqual(span.attributes.startSecretNumberArray, startSecretNumberArray);\n\t\t\tnotDeepStrictEqual(span.attributes.startSecretBooleanArray, startSecretBooleanArray);\n\t\t\tnotDeepStrictEqual(span.attributes.endSecret, endSecret);\n\t\t\tnotDeepStrictEqual(span.attributes.endSecretNumber, endSecretNumber);\n\t\t\tdeepStrictEqual(span.attributes.startSafe, \"retained\");\n\t\t\tconst event = span.events.find((candidate) => candidate.name === \"result\");\n\t\t\tok(event);\n\t\t\tnotDeepStrictEqual(event.attributes.eventSecret, eventSecret);\n\t\t\tnotDeepStrictEqual(event.attributes.eventSecretBoolean, eventSecretBoolean);\n\t\t}),\n\n\t\tcreateCase(factory, \"recording\", \"skips disabled spans without skipping callbacks\", async (fixture) => {\n\t\t\tlet admitted = false;\n\t\t\tconst result = fixture.context.startSpan({ name: \"disabled\", enabled: false }, () => {\n\t\t\t\tadmitted = true;\n\t\t\t\treturn 17;\n\t\t\t});\n\n\t\t\tstrictEqual(admitted, true);\n\t\t\tstrictEqual(result, 17);\n\t\t\tdeepStrictEqual(await fixture.getSpans(), []);\n\t\t}),\n\n\t\tcreateCase(factory, \"timing\", \"records a sane elapsed duration\", async (fixture) => {\n\t\t\tawait fixture.context.startSpan({ name: \"timed\" }, async () => {\n\t\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, 10));\n\t\t\t});\n\n\t\t\tconst span = findSpan(await fixture.getSpans(), \"timed\");\n\t\t\tok(span.startedAt !== undefined);\n\t\t\tok(Number.isFinite(span.startedAt));\n\t\t\tok(span.endedAt !== undefined);\n\t\t\tok(Number.isFinite(span.endedAt));\n\t\t\tok(span.duration !== undefined);\n\t\t\tok(Number.isFinite(span.duration));\n\t\t\tok(span.endedAt >= span.startedAt, \"Expected a non-decreasing clock\");\n\t\t\tok(span.duration > 0, \"Expected a positive elapsed duration\");\n\t\t\tok(span.duration < 60_000, \"Expected an implausibly long duration to fail\");\n\t\t\tstrictEqual(span.duration, span.endedAt - span.startedAt);\n\t\t}),\n\n\t\tcreateCase(factory, \"parentage\", \"preserves W3C parentage across a context boundary\", async (fixture) => {\n\t\t\tawait fixture.context.startSpan({ name: \"boundary-parent\" }, async (parent) => {\n\t\t\t\tconst traceparent = fixture.context.inject(parent);\n\t\t\t\tassertTraceparent(traceparent);\n\t\t\t\tconst remote = fixture.context.extract(traceparent);\n\t\t\t\tawait remote.startSpan({ name: \"boundary-child\" }, (child) => {\n\t\t\t\t\tconst childTraceparent = remote.inject(child);\n\t\t\t\t\tassertTraceparent(childTraceparent);\n\t\t\t\t\tstrictEqual(childTraceparent.slice(0, 35), traceparent.slice(0, 35));\n\t\t\t\t\tnotDeepStrictEqual(childTraceparent.slice(36, 52), traceparent.slice(36, 52));\n\t\t\t\t});\n\t\t\t\tconst invalidFromSpan = parent.extract(\"not-a-traceparent\");\n\t\t\t\tawait invalidFromSpan.startSpan({ name: \"invalid-span-extract\" }, () => undefined);\n\t\t\t\tconst missingFromSpan = parent.extract(undefined);\n\t\t\t\tawait missingFromSpan.startSpan({ name: \"missing-span-extract\" }, () => undefined);\n\t\t\t});\n\n\t\t\tconst spans = await fixture.getSpans();\n\t\t\tconst parent = findSpan(spans, \"boundary-parent\");\n\t\t\tconst child = findSpan(spans, \"boundary-child\");\n\t\t\tassertTraceparent(parent.traceparent);\n\t\t\tassertTraceparent(child.traceparent);\n\t\t\tstrictEqual(child.parentId, null);\n\t\t\tstrictEqual(child.parentTraceparent, parent.traceparent);\n\t\t\tstrictEqual(child.traceparent.slice(0, 35), parent.traceparent.slice(0, 35));\n\n\t\t\tconst invalid = fixture.context.extract(\"not-a-traceparent\");\n\t\t\tawait invalid.startSpan({ name: \"invalid-boundary\" }, () => undefined);\n\t\t\tstrictEqual(findSpan(await fixture.getSpans(), \"invalid-boundary\").parentTraceparent, null);\n\n\t\t\tconst unsampled = fixture.context.extract(\"00-11111111111111111111111111111111-2222222222222222-00\");\n\t\t\tawait unsampled.startSpan({ name: \"unsampled-boundary\" }, (child) => {\n\t\t\t\tconst childTraceparent = unsampled.inject(child);\n\t\t\t\tassertTraceparent(childTraceparent);\n\t\t\t\tstrictEqual(childTraceparent.slice(0, 35), \"00-11111111111111111111111111111111\");\n\t\t\t\tstrictEqual(childTraceparent.slice(53), \"00\");\n\t\t\t});\n\n\t\t\tconst reservedFlags = fixture.context.extract(\"00-77777777777777777777777777777777-8888888888888888-03\");\n\t\t\tawait reservedFlags.startSpan({ name: \"reserved-flags-boundary\" }, (child) => {\n\t\t\t\tconst childTraceparent = reservedFlags.inject(child);\n\t\t\t\tassertTraceparent(childTraceparent);\n\t\t\t\tstrictEqual(childTraceparent.slice(53), \"01\");\n\t\t\t});\n\n\t\t\tconst future = fixture.context.extract(\n\t\t\t\t\"01-33333333333333333333333333333333-4444444444444444-03-future-extension\",\n\t\t\t);\n\t\t\tawait future.startSpan({ name: \"future-boundary\" }, (child) => {\n\t\t\t\tconst childTraceparent = future.inject(child);\n\t\t\t\tassertTraceparent(childTraceparent);\n\t\t\t\tstrictEqual(childTraceparent.slice(0, 35), \"00-33333333333333333333333333333333\");\n\t\t\t\tstrictEqual(childTraceparent.slice(53), \"01\");\n\t\t\t});\n\n\t\t\tconst directParent = \"00-55555555555555555555555555555555-6666666666666666-01\";\n\t\t\tawait fixture.context.startSpan({ name: \"direct-boundary\", traceparent: directParent }, (child) => {\n\t\t\t\tconst childTraceparent = fixture.context.inject(child);\n\t\t\t\tassertTraceparent(childTraceparent);\n\t\t\t\tstrictEqual(childTraceparent.slice(0, 35), directParent.slice(0, 35));\n\t\t\t});\n\t\t\tstrictEqual(findSpan(await fixture.getSpans(), \"direct-boundary\").parentTraceparent, directParent);\n\n\t\t\tstrictEqual(findSpan(await fixture.getSpans(), \"invalid-span-extract\").parentTraceparent, null);\n\t\t\tstrictEqual(findSpan(await fixture.getSpans(), \"missing-span-extract\").parentTraceparent, null);\n\t\t}),\n\n\t\tcreateCase(factory, \"passivity\", \"suppresses unreadable telemetry payload failures\", async (fixture) => {\n\t\t\tlet calls = 0;\n\t\t\tconst options = unreadable<SpanOptions>({ name: \"unreadable-options\", attributes: { secret: \"value\" } });\n\t\t\tconst result = fixture.context.startSpan(options, () => {\n\t\t\t\tcalls++;\n\t\t\t\treturn 9;\n\t\t\t});\n\n\t\t\tstrictEqual(calls, 1);\n\t\t\tstrictEqual(await result, 9);\n\t\t\tdeepStrictEqual(await fixture.getSpans(), []);\n\n\t\t\tawait fixture.context.startSpan({ name: \"unreadable-recording\" }, (span) => {\n\t\t\t\tconst attributes = unreadable<SpanAttributes>({ secret: \"value\" });\n\t\t\t\tconst status = unreadable<SpanStatus>({ status: \"ok\" });\n\t\t\t\tdoesNotThrow(() => span.setAttributes(attributes));\n\t\t\t\tdoesNotThrow(() => span.addEvent(\"unreadable-event\", attributes));\n\t\t\t\tdoesNotThrow(() => span.setStatus(status));\n\t\t\t});\n\n\t\t\tconst recorded = await fixture.getSpans();\n\t\t\tstrictEqual(recorded.length, 1);\n\t\t\tdeepStrictEqual(recorded[0]?.attributes, {});\n\t\t\tdeepStrictEqual(recorded[0]?.events, []);\n\t\t\tdeepStrictEqual(recorded[0]?.status, { status: \"ok\" });\n\t\t}),\n\n\t\tcreateCase(factory, \"passivity\", \"ignores failed status calls atomically\", async (fixture) => {\n\t\t\tconst rejection = new Error(\"rejected after unreadable status\");\n\t\t\tawait rejectsWithSameValue(\n\t\t\t\tfixture.context.startSpan({ name: \"unreadable-status\" }, (span) => {\n\t\t\t\t\tconst status = unreadable<SpanStatus>({ status: \"ok\" });\n\t\t\t\t\tdoesNotThrow(() => span.setStatus(status));\n\t\t\t\t\treturn Promise.reject(rejection);\n\t\t\t\t}),\n\t\t\t\trejection,\n\t\t\t);\n\n\t\t\tstrictEqual(findSpan(await fixture.getSpans(), \"unreadable-status\").status.status, \"error\");\n\t\t}),\n\t];\n}\n"]}
|