@telemetry-dev/sdk 0.1.6 → 0.1.7
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 +18 -4
- package/dist/index.d.mts +10 -3
- package/dist/index.mjs +44 -8
- package/package.json +1 -1
- package/src/capture.ts +16 -5
- package/src/client.ts +30 -6
- package/src/config.ts +13 -3
- package/src/index.ts +1 -0
- package/src/noop.ts +2 -0
- package/src/span.ts +16 -0
package/README.md
CHANGED
|
@@ -55,11 +55,21 @@ Without an API key every call is a silent no-op — the SDK never throws into yo
|
|
|
55
55
|
|
|
56
56
|
Other `init()` options: `enabled` (kill switch), `registerGlobal`, `exportMode: "batched" |
|
|
57
57
|
"immediate"`, `captureInput` / `captureOutput`, `mask(value, { key })` redaction hook,
|
|
58
|
-
`maxAttributeLength` (
|
|
58
|
+
`maxAttributeLength` (a non-negative safe integer or `Infinity`; default 65536; `null` and
|
|
59
|
+
`undefined` select the default, while other invalid values disable telemetry and are reported
|
|
60
|
+
through `onError`; truncated values use `...[truncated]`, or its prefix when the cap is shorter than
|
|
61
|
+
the marker; the cap also applies, with the marker, to string metadata and log attributes, and any
|
|
62
|
+
other string attribute is cut at the cap without a marker, except that a cap of `0` leaves those at
|
|
63
|
+
65536), `batch`
|
|
59
64
|
(`maxExportBatchSize` 64, `scheduledDelayMillis` 1000, `maxQueueSize` 2048,
|
|
60
65
|
`exportTimeoutMillis` 30000), `spanFilter`, `resourceAttributes`, `logLevel` (SDK diagnostics,
|
|
61
66
|
default `"warn"`), `fetch`, `waitUntil`, `onError`.
|
|
62
67
|
|
|
68
|
+
`captureInput: false` and `captureOutput: false` omit captured content. Provider integrations may
|
|
69
|
+
still record non-content metadata such as response IDs and models, finish reasons, usage, timing,
|
|
70
|
+
and errors. Capture flags do not gate stop sequences, tool descriptions and definitions (`gen_ai.tool.description`, `gen_ai.tool.definitions`), caller-supplied metadata or raw attributes,
|
|
71
|
+
or exception messages and stack traces; redact those separately when needed.
|
|
72
|
+
|
|
63
73
|
Root spans remain independent by default. `init({ sessionMode: "process" })` groups otherwise
|
|
64
74
|
uncorrelated spans and logs using a fresh opaque UUID for each enabled SDK initialization.
|
|
65
75
|
Explicit and propagated session IDs take precedence, and valid parent trace context is preserved.
|
|
@@ -115,9 +125,13 @@ after the ended span passes `spanFilter`, including interrupted streams. Chunk t
|
|
|
115
125
|
histogram state and does not retain individual intervals. Pull-based timing includes consumer delay
|
|
116
126
|
between reads. An explicit timestamp uses the same monotonic millisecond clock for every chunk.
|
|
117
127
|
|
|
118
|
-
Provider integrations feature-detect
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
Provider integrations feature-detect stream APIs. With an older core SDK that lacks the stream
|
|
129
|
+
capture policy, streams are still traced with usage, model, finish reason, and timing, but output
|
|
130
|
+
retention assumes the default 65536 limit and treats a mask as configured, so incomplete output is
|
|
131
|
+
withheld. Instrumentation failures are not reported to `onError` with such a core. If only
|
|
132
|
+
`recordOutputChunk` is unavailable, tracing and reconstruction continue but chunk-interval metrics
|
|
133
|
+
are unavailable. Upgrade the core SDK with provider integrations to enable the full stream
|
|
134
|
+
telemetry.
|
|
121
135
|
|
|
122
136
|
## Serverless
|
|
123
137
|
|
package/dist/index.d.mts
CHANGED
|
@@ -34,7 +34,7 @@ interface TelemetryOptions {
|
|
|
34
34
|
captureOutput?: boolean;
|
|
35
35
|
/** Redaction hook run on captured content before stringify/truncate. */
|
|
36
36
|
mask?: MaskFn;
|
|
37
|
-
/**
|
|
37
|
+
/** Non-negative safe-integer truncation cap for content attributes; Infinity disables truncation. The cap also applies, with the marker, to string metadata and log attributes; any other string attribute is cut at the cap without a marker, except that a cap of 0 leaves those at 65536. init() reports invalid values through onError and returns a disabled client. Default 65536. */
|
|
38
38
|
maxAttributeLength?: number;
|
|
39
39
|
batch?: BatchOptions$1;
|
|
40
40
|
/** Export filter. Default when registerGlobal: only spans from this SDK's instrumentation scope. */
|
|
@@ -53,7 +53,7 @@ interface TelemetryOptions {
|
|
|
53
53
|
fetch?: typeof fetch;
|
|
54
54
|
/** Serverless extender (e.g. Cloudflare `ctx.waitUntil`). When provided, `flush()` does not await exports. */
|
|
55
55
|
waitUntil?: (p: Promise<unknown>) => void;
|
|
56
|
-
/** Receives
|
|
56
|
+
/** Receives errors raised while emitting telemetry; repeated instrumentation failures from one stream may be coalesced into one report. The SDK never throws into user code. */
|
|
57
57
|
onError?: (cause: Error) => void;
|
|
58
58
|
/** Header value for a custom telemetry.dev integration. */
|
|
59
59
|
sdkName?: string;
|
|
@@ -189,6 +189,11 @@ interface StartSpanOptions extends SpanFields {
|
|
|
189
189
|
/** Per-call override of the global captureOutput. */
|
|
190
190
|
captureOutput?: boolean;
|
|
191
191
|
}
|
|
192
|
+
interface SpanCapturePolicy {
|
|
193
|
+
readonly output: boolean;
|
|
194
|
+
readonly mask: boolean;
|
|
195
|
+
readonly maxAttributeLength: number;
|
|
196
|
+
}
|
|
192
197
|
interface SpanHandle {
|
|
193
198
|
/** Raw OTel span (interop escape hatch). */
|
|
194
199
|
readonly span: Span;
|
|
@@ -199,9 +204,11 @@ interface SpanHandle {
|
|
|
199
204
|
/** W3C traceparent for this span; null for the no-op handle. */
|
|
200
205
|
readonly traceparent: string | null;
|
|
201
206
|
readonly isRecording: boolean;
|
|
207
|
+
readonly capturePolicy?: SpanCapturePolicy;
|
|
202
208
|
update(fields: SpanFields): SpanHandle;
|
|
203
209
|
/** Records an output chunk arrival. Pull-based streams include consumer delay between pulls. */
|
|
204
210
|
recordOutputChunk(timestampMs?: number): void;
|
|
211
|
+
reportError?(cause: unknown): void;
|
|
205
212
|
end(fields?: SpanFields & {
|
|
206
213
|
endTime?: Date | number;
|
|
207
214
|
}): void;
|
|
@@ -235,4 +242,4 @@ interface ObserveOptions extends StartSpanOptions {
|
|
|
235
242
|
*/
|
|
236
243
|
declare function observe<TThis, TArgs extends unknown[], TReturn>(fn: (this: TThis, ...args: TArgs) => TReturn, options?: ObserveOptions): (...args: TArgs) => TReturn;
|
|
237
244
|
//#endregion
|
|
238
|
-
export { type AgentFields, type BatchOptions, type BoundedCaptureDetails, type BoundedCaptureOptions, type BoundedCaptureResult, type ClientOverrides, type ExportMode, type GenerationFields, type LogLevel, type LogOptions, type MaskFn, type ObserveOptions, type ParentRef, type PropagatedAttributes, SCOPE_NAME, type SdkLogLevel, type SpanFields, type SpanHandle, type SpanType, type StartSpanOptions, type TelemetryClient, type TelemetryOptions, TelemetrySpanProcessor, type TelemetrySpanProcessorOptions, type TokenUsage, type ToolFields, activeContext, boundedCapture, boundedCaptureDetails, captureEnabled, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
|
|
245
|
+
export { type AgentFields, type BatchOptions, type BoundedCaptureDetails, type BoundedCaptureOptions, type BoundedCaptureResult, type ClientOverrides, type ExportMode, type GenerationFields, type LogLevel, type LogOptions, type MaskFn, type ObserveOptions, type ParentRef, type PropagatedAttributes, SCOPE_NAME, type SdkLogLevel, type SpanCapturePolicy, type SpanFields, type SpanHandle, type SpanType, type StartSpanOptions, type TelemetryClient, type TelemetryOptions, TelemetrySpanProcessor, type TelemetrySpanProcessorOptions, type TokenUsage, type ToolFields, activeContext, boundedCapture, boundedCaptureDetails, captureEnabled, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
|
package/dist/index.mjs
CHANGED
|
@@ -5,9 +5,13 @@ import { BatchLogRecordProcessor, LoggerProvider, SimpleLogRecordProcessor } fro
|
|
|
5
5
|
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
|
|
6
6
|
import { AlsContextManager, BATCHED_METRIC_INTERVAL_MS, DEFAULT_BASE_URL, DEFAULT_BATCH, DORMANT_INTERVAL_MS, DURATION_BUCKETS, OUTPUT_CHUNK_HISTOGRAM, PROPAGATED_KEY, SCOPE_NAME, SCOPE_NAME as SCOPE_NAME$1, SCOPE_VERSION, StampingSpanProcessor, TelemetrySpanProcessor, activeContext, activeContext as activeContext$1, als, createLogExporter, createMetricExporter, createMetricsPipeline, createTraceExporter, diag, jsonAttr, omitUndefined, otlpHeaders, propagateAttributes, propagatedFromContext, reportError, resolveEnv, sessionIdOf, sessionRootTracerProvider, sessionSampler, setLogLevel, withContext, withContext as withContext$1, withSessionParent } from "@telemetry-dev/otel";
|
|
7
7
|
import { SeverityNumber } from "@opentelemetry/api-logs";
|
|
8
|
+
//#region src/config.ts
|
|
9
|
+
const DEFAULT_MAX_ATTRIBUTE_LENGTH = 65536;
|
|
8
10
|
function resolveConfig(options = {}) {
|
|
9
11
|
const env = resolveEnv();
|
|
10
12
|
if (options.sessionMode !== void 0 && options.sessionMode !== "explicit" && options.sessionMode !== "process") throw new TypeError(`Invalid sessionMode: ${String(options.sessionMode)}`);
|
|
13
|
+
const maxAttributeLength = options.maxAttributeLength == null ? DEFAULT_MAX_ATTRIBUTE_LENGTH : options.maxAttributeLength;
|
|
14
|
+
if (maxAttributeLength !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(maxAttributeLength) || maxAttributeLength < 0)) throw new RangeError("maxAttributeLength must be a non-negative safe integer or Infinity");
|
|
11
15
|
const baseUrl = (options.baseUrl ?? env.TELEMETRY_DEV_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
12
16
|
return {
|
|
13
17
|
apiKey: options.apiKey ?? env.TELEMETRY_DEV_API_KEY,
|
|
@@ -22,7 +26,7 @@ function resolveConfig(options = {}) {
|
|
|
22
26
|
captureInput: options.captureInput ?? true,
|
|
23
27
|
captureOutput: options.captureOutput ?? true,
|
|
24
28
|
mask: options.mask,
|
|
25
|
-
maxAttributeLength
|
|
29
|
+
maxAttributeLength,
|
|
26
30
|
batch: {
|
|
27
31
|
...DEFAULT_BATCH,
|
|
28
32
|
...options.batch
|
|
@@ -45,6 +49,13 @@ const NOOP_CLIENT = {
|
|
|
45
49
|
shutdown: () => Promise.resolve()
|
|
46
50
|
};
|
|
47
51
|
let activeClient;
|
|
52
|
+
function shutdownClient(client, onError) {
|
|
53
|
+
try {
|
|
54
|
+
client.shutdown().catch((error) => reportError(onError, error));
|
|
55
|
+
} catch (error) {
|
|
56
|
+
reportError(onError, error instanceof Error ? error : new Error(String(error)));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
48
59
|
function currentClient() {
|
|
49
60
|
return activeClient ?? NOOP_CLIENT;
|
|
50
61
|
}
|
|
@@ -59,20 +70,22 @@ function shutdown() {
|
|
|
59
70
|
return currentClient().shutdown();
|
|
60
71
|
}
|
|
61
72
|
function init(options, overrides) {
|
|
73
|
+
const previousClient = activeClient;
|
|
62
74
|
try {
|
|
75
|
+
setLogLevel(options?.logLevel ?? "warn");
|
|
63
76
|
return initInner(options, overrides);
|
|
64
77
|
} catch (error) {
|
|
65
78
|
reportError(options?.onError, error instanceof Error ? error : new Error(String(error)));
|
|
66
|
-
|
|
79
|
+
if (previousClient && previousClient !== NOOP_CLIENT) shutdownClient(previousClient, options?.onError);
|
|
80
|
+
if (activeClient === previousClient) activeClient = NOOP_CLIENT;
|
|
67
81
|
return NOOP_CLIENT;
|
|
68
82
|
}
|
|
69
83
|
}
|
|
70
84
|
function initInner(options, overrides) {
|
|
71
85
|
const config = resolveConfig(options);
|
|
72
|
-
setLogLevel(config.logLevel);
|
|
73
86
|
if (activeClient && activeClient !== NOOP_CLIENT) {
|
|
74
87
|
diag.warn("init() called again; replacing the previous client");
|
|
75
|
-
activeClient
|
|
88
|
+
shutdownClient(activeClient, config.onError);
|
|
76
89
|
}
|
|
77
90
|
if (!(config.enabled && Boolean(config.apiKey ?? overrides?.spanExporter))) {
|
|
78
91
|
if (config.enabled && !config.apiKey) diag.debug("no api key (apiKey option or TELEMETRY_DEV_API_KEY); telemetry disabled");
|
|
@@ -112,11 +125,12 @@ function initInner(options, overrides) {
|
|
|
112
125
|
recordMetrics: metrics ? (span) => metrics.record(span) : void 0,
|
|
113
126
|
onError: config.onError
|
|
114
127
|
});
|
|
128
|
+
const backstopLength = config.maxAttributeLength || 65536;
|
|
115
129
|
const provider = new BasicTracerProvider({
|
|
116
130
|
resource,
|
|
117
131
|
sampler: sessionSampler(config.sampler),
|
|
118
132
|
spanProcessors: [processor],
|
|
119
|
-
spanLimits: { attributeValueLengthLimit:
|
|
133
|
+
spanLimits: { attributeValueLengthLimit: backstopLength }
|
|
120
134
|
});
|
|
121
135
|
const tracer = provider.getTracer(SCOPE_NAME$1, SCOPE_VERSION);
|
|
122
136
|
let registeredTrace = false;
|
|
@@ -139,7 +153,7 @@ function initInner(options, overrides) {
|
|
|
139
153
|
const logProcessor = config.exportMode === "immediate" ? new SimpleLogRecordProcessor(logExporter) : new BatchLogRecordProcessor(logExporter);
|
|
140
154
|
const loggerProvider = new LoggerProvider({
|
|
141
155
|
resource,
|
|
142
|
-
logRecordLimits: { attributeValueLengthLimit:
|
|
156
|
+
logRecordLimits: { attributeValueLengthLimit: backstopLength },
|
|
143
157
|
processors: [logProcessor]
|
|
144
158
|
});
|
|
145
159
|
logs = {
|
|
@@ -295,7 +309,13 @@ function boundedCaptureDetails(input, options = {}) {
|
|
|
295
309
|
const converted = visit(item, depth + 1, [...path, key]);
|
|
296
310
|
if (converted === void 0) bytes = previousBytes;
|
|
297
311
|
else {
|
|
298
|
-
result
|
|
312
|
+
if (key === "__proto__") Object.defineProperty(result, key, {
|
|
313
|
+
configurable: true,
|
|
314
|
+
enumerable: true,
|
|
315
|
+
value: converted,
|
|
316
|
+
writable: true
|
|
317
|
+
});
|
|
318
|
+
else result[key] = converted;
|
|
299
319
|
emitted = true;
|
|
300
320
|
}
|
|
301
321
|
}
|
|
@@ -323,7 +343,9 @@ function boundedCapture(input, options = {}) {
|
|
|
323
343
|
}
|
|
324
344
|
function truncate(value, maxLength) {
|
|
325
345
|
if (value.length <= maxLength) return value;
|
|
326
|
-
|
|
346
|
+
const limit = Math.max(maxLength, 0);
|
|
347
|
+
const marker = TRUNCATION_MARKER.slice(0, limit);
|
|
348
|
+
return value.slice(0, limit - marker.length) + marker;
|
|
327
349
|
}
|
|
328
350
|
/** The single content funnel: mask → JSON stringify → truncate. */
|
|
329
351
|
function prepareCaptureValue(key, value, cfg) {
|
|
@@ -506,8 +528,14 @@ const NOOP_SPAN_HANDLE = {
|
|
|
506
528
|
spanId: INVALID_SPAN_CONTEXT.spanId,
|
|
507
529
|
traceparent: null,
|
|
508
530
|
isRecording: false,
|
|
531
|
+
capturePolicy: {
|
|
532
|
+
output: false,
|
|
533
|
+
mask: false,
|
|
534
|
+
maxAttributeLength: 0
|
|
535
|
+
},
|
|
509
536
|
update: () => NOOP_SPAN_HANDLE,
|
|
510
537
|
recordOutputChunk: () => {},
|
|
538
|
+
reportError: () => {},
|
|
511
539
|
end: () => {}
|
|
512
540
|
};
|
|
513
541
|
//#endregion
|
|
@@ -609,6 +637,11 @@ function createSpanHandle(core, name, options = {}) {
|
|
|
609
637
|
traceId: spanContext.traceId,
|
|
610
638
|
spanId: spanContext.spanId,
|
|
611
639
|
traceparent: `00-${spanContext.traceId}-${spanContext.spanId}-${flags}`,
|
|
640
|
+
capturePolicy: {
|
|
641
|
+
output: meta.capture.captureOutput,
|
|
642
|
+
mask: meta.capture.mask !== void 0,
|
|
643
|
+
maxAttributeLength: meta.capture.maxAttributeLength
|
|
644
|
+
},
|
|
612
645
|
get isRecording() {
|
|
613
646
|
return span.isRecording();
|
|
614
647
|
},
|
|
@@ -642,6 +675,9 @@ function createSpanHandle(core, name, options = {}) {
|
|
|
642
675
|
}
|
|
643
676
|
previousOutputChunkTime = now;
|
|
644
677
|
},
|
|
678
|
+
reportError(cause) {
|
|
679
|
+
reportError(meta.onError, cause instanceof Error ? cause : new Error(String(cause)));
|
|
680
|
+
},
|
|
645
681
|
end(fields) {
|
|
646
682
|
try {
|
|
647
683
|
if (fields) applyFields(span, meta, fields);
|
package/package.json
CHANGED
package/src/capture.ts
CHANGED
|
@@ -168,7 +168,7 @@ export function boundedCaptureDetails(
|
|
|
168
168
|
return result;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
const result
|
|
171
|
+
const result = {} as CapturedObject;
|
|
172
172
|
let emitted = false;
|
|
173
173
|
|
|
174
174
|
for (const key in value) {
|
|
@@ -213,7 +213,17 @@ export function boundedCaptureDetails(
|
|
|
213
213
|
if (converted === undefined) {
|
|
214
214
|
bytes = previousBytes;
|
|
215
215
|
} else {
|
|
216
|
-
|
|
216
|
+
if (key === "__proto__") {
|
|
217
|
+
Object.defineProperty(result, key, {
|
|
218
|
+
configurable: true,
|
|
219
|
+
enumerable: true,
|
|
220
|
+
value: converted,
|
|
221
|
+
writable: true,
|
|
222
|
+
});
|
|
223
|
+
} else {
|
|
224
|
+
result[key] = converted;
|
|
225
|
+
}
|
|
226
|
+
|
|
217
227
|
emitted = true;
|
|
218
228
|
}
|
|
219
229
|
}
|
|
@@ -243,9 +253,10 @@ export function boundedCapture(
|
|
|
243
253
|
export function truncate(value: string, maxLength: number): string {
|
|
244
254
|
if (value.length <= maxLength) return value;
|
|
245
255
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
256
|
+
const limit = Math.max(maxLength, 0);
|
|
257
|
+
const marker = TRUNCATION_MARKER.slice(0, limit);
|
|
258
|
+
|
|
259
|
+
return value.slice(0, limit - marker.length) + marker;
|
|
249
260
|
}
|
|
250
261
|
|
|
251
262
|
/** The single content funnel: mask → JSON stringify → truncate. */
|
package/src/client.ts
CHANGED
|
@@ -36,7 +36,12 @@ import {
|
|
|
36
36
|
type Transport,
|
|
37
37
|
} from "@telemetry-dev/otel";
|
|
38
38
|
|
|
39
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
DEFAULT_MAX_ATTRIBUTE_LENGTH,
|
|
41
|
+
type ResolvedConfig,
|
|
42
|
+
resolveConfig,
|
|
43
|
+
type TelemetryOptions,
|
|
44
|
+
} from "./config.ts";
|
|
40
45
|
|
|
41
46
|
/** Test seam: inject in-memory exporters instead of the OTLP fetch transport. */
|
|
42
47
|
export interface ClientOverrides {
|
|
@@ -71,6 +76,14 @@ const NOOP_CLIENT: ClientHandle = {
|
|
|
71
76
|
|
|
72
77
|
let activeClient: ClientHandle | undefined;
|
|
73
78
|
|
|
79
|
+
function shutdownClient(client: ClientHandle, onError?: (cause: Error) => void): void {
|
|
80
|
+
try {
|
|
81
|
+
void client.shutdown().catch((error: Error) => reportError(onError, error));
|
|
82
|
+
} catch (error) {
|
|
83
|
+
reportError(onError, error instanceof Error ? error : new Error(String(error)));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
74
87
|
export function currentClient(): ClientHandle {
|
|
75
88
|
return activeClient ?? NOOP_CLIENT;
|
|
76
89
|
}
|
|
@@ -90,11 +103,20 @@ export function shutdown(): Promise<void> {
|
|
|
90
103
|
}
|
|
91
104
|
|
|
92
105
|
export function init(options?: TelemetryOptions, overrides?: ClientOverrides): TelemetryClient {
|
|
106
|
+
const previousClient = activeClient;
|
|
107
|
+
|
|
93
108
|
try {
|
|
109
|
+
setLogLevel(options?.logLevel ?? "warn");
|
|
110
|
+
|
|
94
111
|
return initInner(options, overrides);
|
|
95
112
|
} catch (error) {
|
|
96
113
|
reportError(options?.onError, error instanceof Error ? error : new Error(String(error)));
|
|
97
|
-
|
|
114
|
+
|
|
115
|
+
if (previousClient && previousClient !== NOOP_CLIENT) {
|
|
116
|
+
shutdownClient(previousClient, options?.onError);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (activeClient === previousClient) activeClient = NOOP_CLIENT;
|
|
98
120
|
|
|
99
121
|
return NOOP_CLIENT;
|
|
100
122
|
}
|
|
@@ -102,12 +124,11 @@ export function init(options?: TelemetryOptions, overrides?: ClientOverrides): T
|
|
|
102
124
|
|
|
103
125
|
function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): TelemetryClient {
|
|
104
126
|
const config = resolveConfig(options);
|
|
105
|
-
setLogLevel(config.logLevel);
|
|
106
127
|
|
|
107
128
|
if (activeClient && activeClient !== NOOP_CLIENT) {
|
|
108
129
|
diag.warn("init() called again; replacing the previous client");
|
|
109
130
|
// shutdown() synchronously releases global registrations before this init claims them.
|
|
110
|
-
|
|
131
|
+
shutdownClient(activeClient, config.onError);
|
|
111
132
|
}
|
|
112
133
|
|
|
113
134
|
const enabled = config.enabled && Boolean(config.apiKey ?? overrides?.spanExporter);
|
|
@@ -167,11 +188,14 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
|
|
|
167
188
|
onError: config.onError,
|
|
168
189
|
});
|
|
169
190
|
|
|
191
|
+
// A zero cap only drops content; OpenTelemetry rejects it as a provider limit.
|
|
192
|
+
const backstopLength = config.maxAttributeLength || DEFAULT_MAX_ATTRIBUTE_LENGTH;
|
|
193
|
+
|
|
170
194
|
const provider = new BasicTracerProvider({
|
|
171
195
|
resource,
|
|
172
196
|
sampler: sessionSampler(config.sampler),
|
|
173
197
|
spanProcessors: [processor],
|
|
174
|
-
spanLimits: { attributeValueLengthLimit:
|
|
198
|
+
spanLimits: { attributeValueLengthLimit: backstopLength },
|
|
175
199
|
});
|
|
176
200
|
|
|
177
201
|
const tracer = provider.getTracer(SCOPE_NAME, SCOPE_VERSION);
|
|
@@ -220,7 +244,7 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
|
|
|
220
244
|
|
|
221
245
|
const loggerProvider = new LoggerProvider({
|
|
222
246
|
resource,
|
|
223
|
-
logRecordLimits: { attributeValueLengthLimit:
|
|
247
|
+
logRecordLimits: { attributeValueLengthLimit: backstopLength },
|
|
224
248
|
processors: [logProcessor],
|
|
225
249
|
});
|
|
226
250
|
|
package/src/config.ts
CHANGED
|
@@ -40,7 +40,7 @@ export interface TelemetryOptions {
|
|
|
40
40
|
captureOutput?: boolean;
|
|
41
41
|
/** Redaction hook run on captured content before stringify/truncate. */
|
|
42
42
|
mask?: MaskFn;
|
|
43
|
-
/**
|
|
43
|
+
/** Non-negative safe-integer truncation cap for content attributes; Infinity disables truncation. The cap also applies, with the marker, to string metadata and log attributes; any other string attribute is cut at the cap without a marker, except that a cap of 0 leaves those at 65536. init() reports invalid values through onError and returns a disabled client. Default 65536. */
|
|
44
44
|
maxAttributeLength?: number;
|
|
45
45
|
batch?: BatchOptions;
|
|
46
46
|
/** Export filter. Default when registerGlobal: only spans from this SDK's instrumentation scope. */
|
|
@@ -59,7 +59,7 @@ export interface TelemetryOptions {
|
|
|
59
59
|
fetch?: typeof fetch;
|
|
60
60
|
/** Serverless extender (e.g. Cloudflare `ctx.waitUntil`). When provided, `flush()` does not await exports. */
|
|
61
61
|
waitUntil?: (p: Promise<unknown>) => void;
|
|
62
|
-
/** Receives
|
|
62
|
+
/** Receives errors raised while emitting telemetry; repeated instrumentation failures from one stream may be coalesced into one report. The SDK never throws into user code. */
|
|
63
63
|
onError?: (cause: Error) => void;
|
|
64
64
|
/** Header value for a custom telemetry.dev integration. */
|
|
65
65
|
sdkName?: string;
|
|
@@ -104,6 +104,16 @@ export function resolveConfig(options: TelemetryOptions = {}): ResolvedConfig {
|
|
|
104
104
|
throw new TypeError(`Invalid sessionMode: ${String(options.sessionMode)}`);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
const maxAttributeLength =
|
|
108
|
+
options.maxAttributeLength == null ? DEFAULT_MAX_ATTRIBUTE_LENGTH : options.maxAttributeLength;
|
|
109
|
+
|
|
110
|
+
if (
|
|
111
|
+
maxAttributeLength !== Number.POSITIVE_INFINITY &&
|
|
112
|
+
(!Number.isSafeInteger(maxAttributeLength) || maxAttributeLength < 0)
|
|
113
|
+
) {
|
|
114
|
+
throw new RangeError("maxAttributeLength must be a non-negative safe integer or Infinity");
|
|
115
|
+
}
|
|
116
|
+
|
|
107
117
|
const baseUrl = (options.baseUrl ?? env.TELEMETRY_DEV_BASE_URL ?? DEFAULT_BASE_URL).replace(
|
|
108
118
|
/\/+$/,
|
|
109
119
|
"",
|
|
@@ -122,7 +132,7 @@ export function resolveConfig(options: TelemetryOptions = {}): ResolvedConfig {
|
|
|
122
132
|
captureInput: options.captureInput ?? true,
|
|
123
133
|
captureOutput: options.captureOutput ?? true,
|
|
124
134
|
mask: options.mask,
|
|
125
|
-
maxAttributeLength
|
|
135
|
+
maxAttributeLength,
|
|
126
136
|
batch: { ...DEFAULT_BATCH, ...options.batch },
|
|
127
137
|
spanFilter: options.spanFilter,
|
|
128
138
|
sessionRootOf: options.sessionRootOf,
|
package/src/index.ts
CHANGED
package/src/noop.ts
CHANGED
|
@@ -12,7 +12,9 @@ export const NOOP_SPAN_HANDLE: SpanHandle = {
|
|
|
12
12
|
spanId: INVALID_SPAN_CONTEXT.spanId,
|
|
13
13
|
traceparent: null,
|
|
14
14
|
isRecording: false,
|
|
15
|
+
capturePolicy: { output: false, mask: false, maxAttributeLength: 0 },
|
|
15
16
|
update: () => NOOP_SPAN_HANDLE,
|
|
16
17
|
recordOutputChunk: () => {},
|
|
18
|
+
reportError: () => {},
|
|
17
19
|
end: () => {},
|
|
18
20
|
};
|
package/src/span.ts
CHANGED
|
@@ -59,6 +59,12 @@ export interface StartSpanOptions extends SpanFields {
|
|
|
59
59
|
captureOutput?: boolean;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
+
export interface SpanCapturePolicy {
|
|
63
|
+
readonly output: boolean;
|
|
64
|
+
readonly mask: boolean;
|
|
65
|
+
readonly maxAttributeLength: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
62
68
|
export interface SpanHandle {
|
|
63
69
|
/** Raw OTel span (interop escape hatch). */
|
|
64
70
|
readonly span: Span;
|
|
@@ -69,9 +75,11 @@ export interface SpanHandle {
|
|
|
69
75
|
/** W3C traceparent for this span; null for the no-op handle. */
|
|
70
76
|
readonly traceparent: string | null;
|
|
71
77
|
readonly isRecording: boolean;
|
|
78
|
+
readonly capturePolicy?: SpanCapturePolicy;
|
|
72
79
|
update(fields: SpanFields): SpanHandle;
|
|
73
80
|
/** Records an output chunk arrival. Pull-based streams include consumer delay between pulls. */
|
|
74
81
|
recordOutputChunk(timestampMs?: number): void;
|
|
82
|
+
reportError?(cause: unknown): void;
|
|
75
83
|
end(fields?: SpanFields & { endTime?: Date | number }): void;
|
|
76
84
|
}
|
|
77
85
|
|
|
@@ -240,6 +248,11 @@ export function createSpanHandle(
|
|
|
240
248
|
traceId: spanContext.traceId,
|
|
241
249
|
spanId: spanContext.spanId,
|
|
242
250
|
traceparent: `00-${spanContext.traceId}-${spanContext.spanId}-${flags}`,
|
|
251
|
+
capturePolicy: {
|
|
252
|
+
output: meta.capture.captureOutput,
|
|
253
|
+
mask: meta.capture.mask !== undefined,
|
|
254
|
+
maxAttributeLength: meta.capture.maxAttributeLength,
|
|
255
|
+
},
|
|
243
256
|
get isRecording() {
|
|
244
257
|
return span.isRecording();
|
|
245
258
|
},
|
|
@@ -287,6 +300,9 @@ export function createSpanHandle(
|
|
|
287
300
|
|
|
288
301
|
previousOutputChunkTime = now;
|
|
289
302
|
},
|
|
303
|
+
reportError(cause) {
|
|
304
|
+
reportError(meta.onError, cause instanceof Error ? cause : new Error(String(cause)));
|
|
305
|
+
},
|
|
290
306
|
end(fields) {
|
|
291
307
|
try {
|
|
292
308
|
if (fields) applyFields(span, meta, fields);
|