@telemetry-dev/sdk 0.1.0 → 0.1.2
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 +9 -0
- package/dist/index.d.mts +35 -13
- package/dist/index.mjs +75 -45
- package/package.json +2 -2
- package/src/attrs.ts +6 -6
- package/src/capture.ts +3 -3
- package/src/client.ts +13 -6
- package/src/config.ts +21 -4
- package/src/index.ts +11 -1
- package/src/logs.ts +5 -5
- package/src/observe.ts +8 -8
- package/src/span.ts +91 -24
package/README.md
CHANGED
|
@@ -60,6 +60,11 @@ Other `init()` options: `enabled` (kill switch), `registerGlobal`, `exportMode:
|
|
|
60
60
|
`exportTimeoutMillis` 30000), `spanFilter`, `resourceAttributes`, `logLevel` (SDK diagnostics,
|
|
61
61
|
default `"warn"`), `fetch`, `waitUntil`, `onError`.
|
|
62
62
|
|
|
63
|
+
`sampler` accepts an OpenTelemetry `Sampler` and overrides `OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG`.
|
|
64
|
+
The default is parent-based always-on. Session roots keep the configured root policy.
|
|
65
|
+
Nested spans and explicitly parented spans keep the real parent's policy.
|
|
66
|
+
The SDK installs the [session sampling wrapper](../otel/README.md#correlation-attributes) on its provider. This includes the global provider that Eve uses.
|
|
67
|
+
|
|
63
68
|
## API
|
|
64
69
|
|
|
65
70
|
- `init(options?)` — create the client (isolated OTel TracerProvider by default).
|
|
@@ -74,6 +79,10 @@ default `"warn"`), `fetch`, `waitUntil`, `onError`.
|
|
|
74
79
|
- `log(message, { level, eventName, attributes }?)` — log record correlated to the active trace.
|
|
75
80
|
- `getTraceparent()` — W3C traceparent of the active span (pass to other services; accept it via
|
|
76
81
|
`startSpan(name, { parent })`).
|
|
82
|
+
- `activeContext()` / `withContext(context, fn)` — read or temporarily activate an OpenTelemetry
|
|
83
|
+
context.
|
|
84
|
+
- `extractW3cContext(carrier, options?)` / `injectW3cContext(context, carrier, options?)` — extract
|
|
85
|
+
or inject W3C trace context. Baggage is omitted unless `includeBaggage` is true.
|
|
77
86
|
- `flush()` / `shutdown()` — export pending data; required before serverless freeze/exit.
|
|
78
87
|
|
|
79
88
|
Span types: `span` (default) | `generation` | `tool` | `agent` | `embedding` — mapped to
|
package/dist/index.d.mts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { AttributeValue, Context, Span, SpanContext } from "@opentelemetry/api";
|
|
1
|
+
import { AttributeValue, Context, Link, Span, SpanContext, SpanKind } from "@opentelemetry/api";
|
|
2
2
|
import { LogRecordExporter } from "@opentelemetry/sdk-logs";
|
|
3
|
-
import { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
4
|
-
import { BatchOptions, BatchOptions as BatchOptions$1, ExportMode, ExportMode as ExportMode$1, LogLevel, PropagatedAttributes, SdkLogLevel, SdkLogLevel as SdkLogLevel$1, propagateAttributes } from "@telemetry-dev/otel";
|
|
3
|
+
import { ReadableSpan, Sampler, SpanExporter } from "@opentelemetry/sdk-trace-base";
|
|
4
|
+
import { BatchOptions, BatchOptions as BatchOptions$1, ExportMode, ExportMode as ExportMode$1, LogLevel, PropagatedAttributes, SCOPE_NAME, SdkLogLevel, SdkLogLevel as SdkLogLevel$1, SessionRootOf, TelemetrySpanProcessor, TelemetrySpanProcessorOptions, activeContext, propagateAttributes, withContext } from "@telemetry-dev/otel";
|
|
5
5
|
import { PushMetricExporter } from "@opentelemetry/sdk-metrics";
|
|
6
6
|
|
|
7
7
|
//#region src/config.d.ts
|
|
8
8
|
/** Redaction hook applied to captured content (input/output/log message) before JSON stringification. */
|
|
9
|
-
type MaskFn = (value:
|
|
9
|
+
type MaskFn<T = unknown> = (value: T, ctx: {
|
|
10
10
|
key: string;
|
|
11
|
-
}) =>
|
|
11
|
+
}) => T;
|
|
12
12
|
interface TelemetryOptions {
|
|
13
13
|
/** telemetry.dev ingest key (`td_live_…`). Falls back to `TELEMETRY_DEV_API_KEY`. When absent the SDK is a no-op. */
|
|
14
14
|
apiKey?: string;
|
|
@@ -24,6 +24,8 @@ interface TelemetryOptions {
|
|
|
24
24
|
registerGlobal?: boolean;
|
|
25
25
|
/** "batched" (default) buffers spans; "immediate" exports per span (serverless). */
|
|
26
26
|
exportMode?: ExportMode$1;
|
|
27
|
+
/** OTel sampling policy. Defaults to OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG. */
|
|
28
|
+
sampler?: Sampler;
|
|
27
29
|
/** Capture span inputs (args, prompts) by default. Default true. */
|
|
28
30
|
captureInput?: boolean;
|
|
29
31
|
/** Capture span outputs (results, completions) by default. Default true. */
|
|
@@ -35,6 +37,12 @@ interface TelemetryOptions {
|
|
|
35
37
|
batch?: BatchOptions$1;
|
|
36
38
|
/** Export filter. Default when registerGlobal: only spans from this SDK's instrumentation scope. */
|
|
37
39
|
spanFilter?: (span: ReadableSpan) => boolean;
|
|
40
|
+
/**
|
|
41
|
+
* With registerGlobal: spans from other tracers that this returns a session id for are
|
|
42
|
+
* parented under the session trace even when a parent span is active. Use it for frameworks
|
|
43
|
+
* that start each turn inside their own engine span.
|
|
44
|
+
*/
|
|
45
|
+
sessionRootOf?: SessionRootOf;
|
|
38
46
|
/** Extra resource attributes merged onto service.name / deployment.environment.name. */
|
|
39
47
|
resourceAttributes?: Record<string, AttributeValue>;
|
|
40
48
|
/** SDK self-diagnostics console level. Default "warn"; "silent" disables. */
|
|
@@ -44,7 +52,9 @@ interface TelemetryOptions {
|
|
|
44
52
|
/** Serverless extender (e.g. Cloudflare `ctx.waitUntil`). When provided, `flush()` does not await exports. */
|
|
45
53
|
waitUntil?: (p: Promise<unknown>) => void;
|
|
46
54
|
/** Receives any error raised while emitting telemetry; the SDK never throws into user code. */
|
|
47
|
-
onError?: (
|
|
55
|
+
onError?: (cause: Error) => void;
|
|
56
|
+
/** Header value for a custom telemetry.dev integration. */
|
|
57
|
+
sdkName?: string;
|
|
48
58
|
}
|
|
49
59
|
//#endregion
|
|
50
60
|
//#region src/attrs.d.ts
|
|
@@ -60,7 +70,7 @@ interface TokenUsage {
|
|
|
60
70
|
interface GenerationFields {
|
|
61
71
|
model?: string;
|
|
62
72
|
provider?: string;
|
|
63
|
-
systemInstructions?:
|
|
73
|
+
systemInstructions?: Parameters<MaskFn>[0];
|
|
64
74
|
responseModel?: string;
|
|
65
75
|
responseId?: string;
|
|
66
76
|
usage?: TokenUsage;
|
|
@@ -90,12 +100,12 @@ interface AgentFields {
|
|
|
90
100
|
interface SpanFields extends GenerationFields, ToolFields, AgentFields {
|
|
91
101
|
/** Rename the span (update only). */
|
|
92
102
|
name?: string;
|
|
93
|
-
input?:
|
|
94
|
-
output?:
|
|
103
|
+
input?: Parameters<MaskFn>[0];
|
|
104
|
+
output?: Parameters<MaskFn>[0];
|
|
95
105
|
/** Marks the span failed: status ERROR + error.type + an "exception" event. */
|
|
96
106
|
error?: unknown;
|
|
97
107
|
/** td.metadata.<key> on this span only; use propagateAttributes for scope-wide metadata. */
|
|
98
|
-
metadata?: Record<string,
|
|
108
|
+
metadata?: Record<string, Parameters<MaskFn>[0]>;
|
|
99
109
|
/** Raw attribute escape hatch, merged last. */
|
|
100
110
|
attributes?: Record<string, AttributeValue>;
|
|
101
111
|
}
|
|
@@ -122,7 +132,7 @@ declare function init(options?: TelemetryOptions, overrides?: ClientOverrides):
|
|
|
122
132
|
interface LogOptions {
|
|
123
133
|
/** Default "info". */
|
|
124
134
|
level?: LogLevel;
|
|
125
|
-
attributes?: Record<string,
|
|
135
|
+
attributes?: Record<string, Parameters<MaskFn>[0]>;
|
|
126
136
|
eventName?: string;
|
|
127
137
|
timestamp?: Date | number;
|
|
128
138
|
}
|
|
@@ -137,8 +147,12 @@ type ParentRef = string | SpanHandle | SpanContext | Context;
|
|
|
137
147
|
interface StartSpanOptions extends SpanFields {
|
|
138
148
|
/** Default "span" → operation "function". */
|
|
139
149
|
type?: SpanType;
|
|
150
|
+
/** OpenTelemetry span kind; defaults to INTERNAL. */
|
|
151
|
+
kind?: SpanKind;
|
|
140
152
|
/** Parent override; defaults to the active context. */
|
|
141
153
|
parent?: ParentRef;
|
|
154
|
+
/** Links to associate with the span. */
|
|
155
|
+
links?: Link[];
|
|
142
156
|
startTime?: Date | number;
|
|
143
157
|
/** Per-call override of the global captureInput. */
|
|
144
158
|
captureInput?: boolean;
|
|
@@ -166,6 +180,14 @@ declare function startActiveSpan<T>(name: string, fn: (span: SpanHandle) => T):
|
|
|
166
180
|
declare function startActiveSpan<T>(name: string, options: StartSpanOptions, fn: (span: SpanHandle) => T): T;
|
|
167
181
|
/** Update the innermost active span; no-op when none is recording. */
|
|
168
182
|
declare function updateActiveSpan(fields: SpanFields): void;
|
|
183
|
+
/** Extract W3C trace context; baggage is omitted unless includeBaggage is true. */
|
|
184
|
+
declare function extractW3cContext(carrier: Record<string, unknown>, options?: {
|
|
185
|
+
includeBaggage?: boolean;
|
|
186
|
+
}): Context;
|
|
187
|
+
/** Inject W3C trace context; baggage is omitted unless includeBaggage is true. */
|
|
188
|
+
declare function injectW3cContext(context: Context, carrier: Record<string, unknown>, options?: {
|
|
189
|
+
includeBaggage?: boolean;
|
|
190
|
+
}): void;
|
|
169
191
|
/** Serialize the active span context as a W3C traceparent; null when there is none. */
|
|
170
192
|
declare function getTraceparent(): string | null;
|
|
171
193
|
//#endregion
|
|
@@ -179,6 +201,6 @@ interface ObserveOptions extends StartSpanOptions {
|
|
|
179
201
|
* output, thrown/rejected errors captured and rethrown. Activates context so nested SDK calls
|
|
180
202
|
* parent to it. Resolves the client at call time, so wrapping at module load before init() works.
|
|
181
203
|
*/
|
|
182
|
-
declare function observe<TArgs extends unknown[], TReturn>(fn: (...args: TArgs) => TReturn, options?: ObserveOptions): (...args: TArgs) => TReturn;
|
|
204
|
+
declare function observe<TThis, TArgs extends unknown[], TReturn>(fn: (this: TThis, ...args: TArgs) => TReturn, options?: ObserveOptions): (...args: TArgs) => TReturn;
|
|
183
205
|
//#endregion
|
|
184
|
-
export { type AgentFields, type BatchOptions, type ClientOverrides, type ExportMode, type GenerationFields, type LogLevel, type LogOptions, type MaskFn, type ObserveOptions, type ParentRef, type PropagatedAttributes, type SdkLogLevel, type SpanFields, type SpanHandle, type SpanType, type StartSpanOptions, type TelemetryClient, type TelemetryOptions, type TokenUsage, type ToolFields, flush, getTraceparent, init, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan };
|
|
206
|
+
export { type AgentFields, type BatchOptions, 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, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { INVALID_SPAN_CONTEXT, ROOT_CONTEXT, SpanKind, SpanStatusCode, context, defaultTextMapSetter, propagation, trace } from "@opentelemetry/api";
|
|
2
|
-
import { W3CTraceContextPropagator, parseTraceParent } from "@opentelemetry/core";
|
|
1
|
+
import { INVALID_SPAN_CONTEXT, ROOT_CONTEXT, SpanKind, SpanStatusCode, context, defaultTextMapGetter, defaultTextMapSetter, propagation, trace } from "@opentelemetry/api";
|
|
2
|
+
import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator, parseTraceParent } from "@opentelemetry/core";
|
|
3
3
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
4
4
|
import { BatchLogRecordProcessor, LoggerProvider, SimpleLogRecordProcessor } from "@opentelemetry/sdk-logs";
|
|
5
5
|
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
|
|
6
|
-
import { AlsContextManager, BATCHED_METRIC_INTERVAL_MS, DEFAULT_BASE_URL, DEFAULT_BATCH, DORMANT_INTERVAL_MS, PROPAGATED_KEY, SCOPE_NAME, SCOPE_VERSION, StampingSpanProcessor, activeContext, als, createLogExporter, createMetricExporter, createMetricsPipeline, createTraceExporter, diag, jsonAttr, omitUndefined, otlpHeaders, propagateAttributes, propagatedFromContext, reportError, resolveEnv, setLogLevel, withContext } from "@telemetry-dev/otel";
|
|
6
|
+
import { AlsContextManager, BATCHED_METRIC_INTERVAL_MS, DEFAULT_BASE_URL, DEFAULT_BATCH, DORMANT_INTERVAL_MS, 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
8
|
function resolveConfig(options = {}) {
|
|
9
9
|
const env = resolveEnv();
|
|
@@ -16,6 +16,7 @@ function resolveConfig(options = {}) {
|
|
|
16
16
|
enabled: options.enabled ?? true,
|
|
17
17
|
registerGlobal: options.registerGlobal ?? false,
|
|
18
18
|
exportMode: options.exportMode ?? "batched",
|
|
19
|
+
sampler: options.sampler,
|
|
19
20
|
captureInput: options.captureInput ?? true,
|
|
20
21
|
captureOutput: options.captureOutput ?? true,
|
|
21
22
|
mask: options.mask,
|
|
@@ -25,11 +26,13 @@ function resolveConfig(options = {}) {
|
|
|
25
26
|
...options.batch
|
|
26
27
|
},
|
|
27
28
|
spanFilter: options.spanFilter,
|
|
29
|
+
sessionRootOf: options.sessionRootOf,
|
|
28
30
|
resourceAttributes: options.resourceAttributes,
|
|
29
31
|
logLevel: options.logLevel ?? "warn",
|
|
30
32
|
fetchImpl: options.fetch ?? globalThis.fetch,
|
|
31
33
|
waitUntil: options.waitUntil,
|
|
32
|
-
onError: options.onError
|
|
34
|
+
onError: options.onError,
|
|
35
|
+
sdkName: options.sdkName ?? "@telemetry-dev/sdk"
|
|
33
36
|
};
|
|
34
37
|
}
|
|
35
38
|
//#endregion
|
|
@@ -53,7 +56,7 @@ function init(options, overrides) {
|
|
|
53
56
|
try {
|
|
54
57
|
return initInner(options, overrides);
|
|
55
58
|
} catch (error) {
|
|
56
|
-
reportError(options?.onError, error);
|
|
59
|
+
reportError(options?.onError, error instanceof Error ? error : new Error(String(error)));
|
|
57
60
|
activeClient = NOOP_CLIENT;
|
|
58
61
|
return NOOP_CLIENT;
|
|
59
62
|
}
|
|
@@ -79,7 +82,7 @@ function initInner(options, overrides) {
|
|
|
79
82
|
fetchImpl: config.fetchImpl,
|
|
80
83
|
onError: config.onError
|
|
81
84
|
};
|
|
82
|
-
const headers = config.apiKey ? otlpHeaders(config.apiKey) : void 0;
|
|
85
|
+
const headers = config.apiKey ? otlpHeaders(config.apiKey, config.sdkName) : void 0;
|
|
83
86
|
const spanExporter = overrides?.spanExporter ?? createTraceExporter({
|
|
84
87
|
url: `${config.baseUrl}/v1/traces`,
|
|
85
88
|
headers
|
|
@@ -93,25 +96,27 @@ function initInner(options, overrides) {
|
|
|
93
96
|
exporter: metricExporter,
|
|
94
97
|
exportIntervalMillis: config.exportMode === "batched" ? BATCHED_METRIC_INTERVAL_MS : DORMANT_INTERVAL_MS
|
|
95
98
|
}) : void 0;
|
|
96
|
-
const spanFilter = config.spanFilter ?? (config.registerGlobal ? (span) => span.instrumentationScope.name === SCOPE_NAME : void 0);
|
|
99
|
+
const spanFilter = config.spanFilter ?? (config.registerGlobal ? (span) => span.instrumentationScope.name === SCOPE_NAME$1 : void 0);
|
|
100
|
+
const processor = new StampingSpanProcessor({
|
|
101
|
+
exporter: spanExporter,
|
|
102
|
+
exportMode: config.exportMode,
|
|
103
|
+
batch: config.batch,
|
|
104
|
+
spanFilter,
|
|
105
|
+
recordMetrics: metrics ? (span) => metrics.record(span) : void 0,
|
|
106
|
+
onError: config.onError
|
|
107
|
+
});
|
|
97
108
|
const provider = new BasicTracerProvider({
|
|
98
109
|
resource,
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
exportMode: config.exportMode,
|
|
102
|
-
batch: config.batch,
|
|
103
|
-
spanFilter,
|
|
104
|
-
recordMetrics: metrics ? (span) => metrics.record(span) : void 0,
|
|
105
|
-
onError: config.onError
|
|
106
|
-
})],
|
|
110
|
+
sampler: sessionSampler(config.sampler),
|
|
111
|
+
spanProcessors: [processor],
|
|
107
112
|
spanLimits: { attributeValueLengthLimit: config.maxAttributeLength }
|
|
108
113
|
});
|
|
109
|
-
const tracer = provider.getTracer(SCOPE_NAME, SCOPE_VERSION);
|
|
114
|
+
const tracer = provider.getTracer(SCOPE_NAME$1, SCOPE_VERSION);
|
|
110
115
|
let registeredTrace = false;
|
|
111
116
|
let registeredContext = false;
|
|
112
117
|
let registeredPropagation = false;
|
|
113
118
|
if (config.registerGlobal) {
|
|
114
|
-
registeredTrace = trace.setGlobalTracerProvider(provider);
|
|
119
|
+
registeredTrace = trace.setGlobalTracerProvider(config.sessionRootOf ? sessionRootTracerProvider(provider, config.apiKey, config.sessionRootOf, config.onError) : provider);
|
|
115
120
|
if (als) registeredContext = context.setGlobalContextManager(new AlsContextManager(als));
|
|
116
121
|
registeredPropagation = propagation.setGlobalPropagator(new W3CTraceContextPropagator());
|
|
117
122
|
if (!registeredTrace) diag.warn("a global TracerProvider is already registered; attach the TelemetrySpanProcessor from '@telemetry-dev/otel' to your own provider instead");
|
|
@@ -131,7 +136,7 @@ function initInner(options, overrides) {
|
|
|
131
136
|
processors: [logProcessor]
|
|
132
137
|
});
|
|
133
138
|
logs = {
|
|
134
|
-
logger: loggerProvider.getLogger(SCOPE_NAME, SCOPE_VERSION),
|
|
139
|
+
logger: loggerProvider.getLogger(SCOPE_NAME$1, SCOPE_VERSION),
|
|
135
140
|
forceFlush: () => loggerProvider.forceFlush(),
|
|
136
141
|
shutdown: () => loggerProvider.shutdown()
|
|
137
142
|
};
|
|
@@ -188,7 +193,7 @@ function prepareCaptureValue(key, value, cfg) {
|
|
|
188
193
|
if (cfg.mask) try {
|
|
189
194
|
masked = cfg.mask(value, { key });
|
|
190
195
|
} catch (error) {
|
|
191
|
-
reportError(cfg.onError, error);
|
|
196
|
+
reportError(cfg.onError, error instanceof Error ? error : new Error(String(error)));
|
|
192
197
|
return;
|
|
193
198
|
}
|
|
194
199
|
const serialized = jsonAttr(masked);
|
|
@@ -232,7 +237,7 @@ function log(message, options) {
|
|
|
232
237
|
onError: cfg.onError
|
|
233
238
|
};
|
|
234
239
|
const severity = SEVERITIES[options?.level ?? "info"];
|
|
235
|
-
const ctx = activeContext();
|
|
240
|
+
const ctx = activeContext$1();
|
|
236
241
|
const attributes = {};
|
|
237
242
|
for (const [key, value] of Object.entries(options?.attributes ?? {})) {
|
|
238
243
|
if (value === void 0) continue;
|
|
@@ -245,7 +250,7 @@ function log(message, options) {
|
|
|
245
250
|
}
|
|
246
251
|
const propagated = propagatedFromContext(ctx);
|
|
247
252
|
if (propagated) {
|
|
248
|
-
for (const [key, value] of Object.entries(propagated)) if (value !== void 0) attributes[key] =
|
|
253
|
+
for (const [key, value] of Object.entries(propagated)) if (value !== void 0) attributes[key] = value.constructor === String ? truncate(String(value), cfg.maxAttributeLength) : value;
|
|
249
254
|
}
|
|
250
255
|
logger.emit({
|
|
251
256
|
body: prepareCaptureValue("log.message", message, captureCfg) ?? "",
|
|
@@ -257,7 +262,7 @@ function log(message, options) {
|
|
|
257
262
|
attributes
|
|
258
263
|
});
|
|
259
264
|
} catch (error) {
|
|
260
|
-
reportError(core.config.onError, error);
|
|
265
|
+
reportError(core.config.onError, error instanceof Error ? error : new Error(String(error)));
|
|
261
266
|
}
|
|
262
267
|
}
|
|
263
268
|
//#endregion
|
|
@@ -379,13 +384,14 @@ function resolveParent(parent) {
|
|
|
379
384
|
isRemote: true
|
|
380
385
|
});
|
|
381
386
|
}
|
|
382
|
-
|
|
383
|
-
if (
|
|
384
|
-
return
|
|
387
|
+
const ref = parent;
|
|
388
|
+
if (isSpanHandle(ref)) return withAmbientPropagated(ref.context);
|
|
389
|
+
if (isContext(ref)) return withAmbientPropagated(ref);
|
|
390
|
+
return trace.setSpanContext(base, ref);
|
|
385
391
|
}
|
|
386
392
|
function applyError(span, error) {
|
|
387
393
|
const err = error instanceof Error ? error : void 0;
|
|
388
|
-
const errorType = err?.name ?? "Error";
|
|
394
|
+
const errorType = err === void 0 ? "Error" : err.name !== "Error" && err.name.length > 0 ? err.name : (err.constructor?.name ?? "Error") || "Error";
|
|
389
395
|
const message = err?.message ?? String(error);
|
|
390
396
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
391
397
|
span.setAttribute("error.type", errorType);
|
|
@@ -418,14 +424,23 @@ function metaFor(core, options) {
|
|
|
418
424
|
}
|
|
419
425
|
function createSpanHandle(core, name, options = {}) {
|
|
420
426
|
const meta = metaFor(core, options);
|
|
421
|
-
const
|
|
427
|
+
const resolved = resolveParent(options.parent);
|
|
428
|
+
const parentCtx = withSessionParent(resolved, sessionIdOf(resolved, options.attributes), core.config.apiKey);
|
|
429
|
+
const attrs = fieldsToAttributes(options, meta.type, meta.capture);
|
|
422
430
|
const span = core.tracer.startSpan(name, {
|
|
423
|
-
kind: SpanKind.INTERNAL,
|
|
431
|
+
kind: options.kind ?? SpanKind.INTERNAL,
|
|
424
432
|
startTime: options.startTime,
|
|
425
|
-
|
|
433
|
+
links: options.links,
|
|
434
|
+
attributes: {
|
|
435
|
+
"gen_ai.operation.name": SPAN_TYPE_OPERATIONS[meta.type],
|
|
436
|
+
...propagatedFromContext(parentCtx),
|
|
437
|
+
...attrs
|
|
438
|
+
}
|
|
426
439
|
}, parentCtx);
|
|
427
440
|
SPAN_META.set(span, meta);
|
|
428
|
-
|
|
441
|
+
if (Object.keys(attrs).length > 0) span.setAttributes(attrs);
|
|
442
|
+
if (options.name) span.updateName(options.name);
|
|
443
|
+
if (options.error !== void 0) applyError(span, options.error);
|
|
429
444
|
const spanContext = span.spanContext();
|
|
430
445
|
const flags = (spanContext.traceFlags & 255).toString(16).padStart(2, "0");
|
|
431
446
|
const handle = {
|
|
@@ -441,7 +456,7 @@ function createSpanHandle(core, name, options = {}) {
|
|
|
441
456
|
try {
|
|
442
457
|
applyFields(span, meta, fields);
|
|
443
458
|
} catch (error) {
|
|
444
|
-
reportError(meta.onError, error);
|
|
459
|
+
reportError(meta.onError, error instanceof Error ? error : new Error(String(error)));
|
|
445
460
|
}
|
|
446
461
|
return handle;
|
|
447
462
|
},
|
|
@@ -449,7 +464,7 @@ function createSpanHandle(core, name, options = {}) {
|
|
|
449
464
|
try {
|
|
450
465
|
if (fields) applyFields(span, meta, fields);
|
|
451
466
|
} catch (error) {
|
|
452
|
-
reportError(meta.onError, error);
|
|
467
|
+
reportError(meta.onError, error instanceof Error ? error : new Error(String(error)));
|
|
453
468
|
}
|
|
454
469
|
span.end(fields?.endTime);
|
|
455
470
|
}
|
|
@@ -463,7 +478,7 @@ function startSpan(name, options) {
|
|
|
463
478
|
try {
|
|
464
479
|
return createSpanHandle(core, name, options);
|
|
465
480
|
} catch (error) {
|
|
466
|
-
reportError(core.config.onError, error);
|
|
481
|
+
reportError(core.config.onError, error instanceof Error ? error : new Error(String(error)));
|
|
467
482
|
return NOOP_SPAN_HANDLE;
|
|
468
483
|
}
|
|
469
484
|
}
|
|
@@ -485,9 +500,10 @@ function runWithHandle(handle, fn) {
|
|
|
485
500
|
}
|
|
486
501
|
}
|
|
487
502
|
function startActiveSpan(name, optionsOrFn, maybeFn) {
|
|
488
|
-
const
|
|
489
|
-
const
|
|
490
|
-
|
|
503
|
+
const isFn = typeof optionsOrFn === "function";
|
|
504
|
+
const fn = isFn ? optionsOrFn : maybeFn;
|
|
505
|
+
const handle = startSpan(name, isFn ? void 0 : optionsOrFn);
|
|
506
|
+
if (handle === NOOP_SPAN_HANDLE) return fn(handle);
|
|
491
507
|
return runWithHandle(handle, fn);
|
|
492
508
|
}
|
|
493
509
|
/** Update the innermost active span; no-op when none is recording. */
|
|
@@ -499,14 +515,28 @@ function updateActiveSpan(fields) {
|
|
|
499
515
|
if (!span?.isRecording()) return;
|
|
500
516
|
applyFields(span, SPAN_META.get(span) ?? metaFor(core), fields);
|
|
501
517
|
} catch (error) {
|
|
502
|
-
reportError(core.config.onError, error);
|
|
518
|
+
reportError(core.config.onError, error instanceof Error ? error : new Error(String(error)));
|
|
503
519
|
}
|
|
504
520
|
}
|
|
505
|
-
const
|
|
521
|
+
const TRACE_CONTEXT_PROPAGATOR = new W3CTraceContextPropagator();
|
|
522
|
+
const W3C_PROPAGATOR = new CompositePropagator({ propagators: [TRACE_CONTEXT_PROPAGATOR, new W3CBaggagePropagator()] });
|
|
523
|
+
/** Extract W3C trace context; baggage is omitted unless includeBaggage is true. */
|
|
524
|
+
function extractW3cContext(carrier, options = {}) {
|
|
525
|
+
return (options.includeBaggage === true ? W3C_PROPAGATOR : TRACE_CONTEXT_PROPAGATOR).extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
|
|
526
|
+
}
|
|
527
|
+
/** Inject W3C trace context; baggage is omitted unless includeBaggage is true. */
|
|
528
|
+
function injectW3cContext(context, carrier, options = {}) {
|
|
529
|
+
const propagator = options.includeBaggage === true ? W3C_PROPAGATOR : TRACE_CONTEXT_PROPAGATOR;
|
|
530
|
+
for (const key of Object.keys(carrier)) {
|
|
531
|
+
const normalized = key.toLowerCase();
|
|
532
|
+
if (normalized === "traceparent" || normalized === "tracestate" || normalized === "baggage") delete carrier[key];
|
|
533
|
+
}
|
|
534
|
+
propagator.inject(context, carrier, defaultTextMapSetter);
|
|
535
|
+
}
|
|
506
536
|
/** Serialize the active span context as a W3C traceparent; null when there is none. */
|
|
507
537
|
function getTraceparent() {
|
|
508
|
-
const carrier = {};
|
|
509
|
-
|
|
538
|
+
const carrier = { traceparent: void 0 };
|
|
539
|
+
TRACE_CONTEXT_PROPAGATOR.inject(activeContext(), carrier, defaultTextMapSetter);
|
|
510
540
|
return carrier.traceparent ?? null;
|
|
511
541
|
}
|
|
512
542
|
//#endregion
|
|
@@ -529,22 +559,22 @@ function observe(fn, options) {
|
|
|
529
559
|
input
|
|
530
560
|
});
|
|
531
561
|
} catch (error) {
|
|
532
|
-
reportError(core.config.onError, error);
|
|
562
|
+
reportError(core.config.onError, error instanceof Error ? error : new Error(String(error)));
|
|
533
563
|
return fn.apply(this, args);
|
|
534
564
|
}
|
|
535
565
|
try {
|
|
536
|
-
const result = withContext(handle.context, () => fn.apply(this, args));
|
|
566
|
+
const result = withContext$1(handle.context, () => fn.apply(this, args));
|
|
537
567
|
if (isThenable(result)) return result.then((value) => {
|
|
538
568
|
handle.end({ output: value });
|
|
539
569
|
return value;
|
|
540
570
|
}, (error) => {
|
|
541
|
-
handle.end({ error });
|
|
571
|
+
handle.end({ error: error instanceof Error ? error : new Error(String(error)) });
|
|
542
572
|
throw error;
|
|
543
573
|
});
|
|
544
574
|
handle.end({ output: result });
|
|
545
575
|
return result;
|
|
546
576
|
} catch (error) {
|
|
547
|
-
handle.end({ error });
|
|
577
|
+
handle.end({ error: error instanceof Error ? error : new Error(String(error)) });
|
|
548
578
|
throw error;
|
|
549
579
|
}
|
|
550
580
|
};
|
|
@@ -555,4 +585,4 @@ function observe(fn, options) {
|
|
|
555
585
|
return wrapped;
|
|
556
586
|
}
|
|
557
587
|
//#endregion
|
|
558
|
-
export { flush, getTraceparent, init, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan };
|
|
588
|
+
export { SCOPE_NAME, TelemetrySpanProcessor, activeContext, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telemetry-dev/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Pure TypeScript SDK for telemetry.dev: LLM traces, logs, and GenAI metrics on OpenTelemetry, for Node and edge runtimes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"genai",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@opentelemetry/sdk-logs": "^0.218.0",
|
|
43
43
|
"@opentelemetry/sdk-metrics": "^2.7.1",
|
|
44
44
|
"@opentelemetry/sdk-trace-base": "^2.7.1",
|
|
45
|
-
"@telemetry-dev/otel": "^0.1.
|
|
45
|
+
"@telemetry-dev/otel": "^0.1.2"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@opentelemetry/api": "^1.9.1",
|
package/src/attrs.ts
CHANGED
|
@@ -9,13 +9,13 @@ export type SpanType = "span" | "generation" | "tool" | "agent" | "embedding";
|
|
|
9
9
|
// Operation names are taken verbatim by the ingest's deriveOperation. Plain spans always get an
|
|
10
10
|
// explicit "function": without one, the propagated gen_ai.conversation.id would trip the ingest's
|
|
11
11
|
// hasGenAi heuristic and misclassify them as "llm".
|
|
12
|
-
export const SPAN_TYPE_OPERATIONS
|
|
12
|
+
export const SPAN_TYPE_OPERATIONS = {
|
|
13
13
|
span: "function",
|
|
14
14
|
generation: "chat",
|
|
15
15
|
tool: "execute_tool",
|
|
16
16
|
agent: "invoke_agent",
|
|
17
17
|
embedding: "embeddings",
|
|
18
|
-
}
|
|
18
|
+
} satisfies Record<SpanType, string>;
|
|
19
19
|
|
|
20
20
|
export function inputKeyFor(type: SpanType): string {
|
|
21
21
|
return type === "tool" ? "gen_ai.tool.call.arguments" : "gen_ai.input.messages";
|
|
@@ -37,7 +37,7 @@ export interface TokenUsage {
|
|
|
37
37
|
export interface GenerationFields {
|
|
38
38
|
model?: string;
|
|
39
39
|
provider?: string;
|
|
40
|
-
systemInstructions?:
|
|
40
|
+
systemInstructions?: Parameters<import("./config.ts").MaskFn>[0];
|
|
41
41
|
responseModel?: string;
|
|
42
42
|
responseId?: string;
|
|
43
43
|
usage?: TokenUsage;
|
|
@@ -70,12 +70,12 @@ export interface AgentFields {
|
|
|
70
70
|
export interface SpanFields extends GenerationFields, ToolFields, AgentFields {
|
|
71
71
|
/** Rename the span (update only). */
|
|
72
72
|
name?: string;
|
|
73
|
-
input?:
|
|
74
|
-
output?:
|
|
73
|
+
input?: Parameters<import("./config.ts").MaskFn>[0];
|
|
74
|
+
output?: Parameters<import("./config.ts").MaskFn>[0];
|
|
75
75
|
/** Marks the span failed: status ERROR + error.type + an "exception" event. */
|
|
76
76
|
error?: unknown;
|
|
77
77
|
/** td.metadata.<key> on this span only; use propagateAttributes for scope-wide metadata. */
|
|
78
|
-
metadata?: Record<string,
|
|
78
|
+
metadata?: Record<string, Parameters<import("./config.ts").MaskFn>[0]>;
|
|
79
79
|
/** Raw attribute escape hatch, merged last. */
|
|
80
80
|
attributes?: Record<string, AttributeValue>;
|
|
81
81
|
}
|
package/src/capture.ts
CHANGED
|
@@ -8,7 +8,7 @@ export const TRUNCATION_MARKER = "...[truncated]";
|
|
|
8
8
|
export interface CaptureConfig {
|
|
9
9
|
mask?: MaskFn;
|
|
10
10
|
maxAttributeLength: number;
|
|
11
|
-
onError?: (
|
|
11
|
+
onError?: (cause: Error) => void;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export function truncate(value: string, maxLength: number): string {
|
|
@@ -21,7 +21,7 @@ export function truncate(value: string, maxLength: number): string {
|
|
|
21
21
|
/** The single content funnel: mask → JSON stringify → truncate. */
|
|
22
22
|
export function prepareCaptureValue(
|
|
23
23
|
key: string,
|
|
24
|
-
value:
|
|
24
|
+
value: Parameters<MaskFn>[0],
|
|
25
25
|
cfg: CaptureConfig,
|
|
26
26
|
): string | undefined {
|
|
27
27
|
let masked = value;
|
|
@@ -29,7 +29,7 @@ export function prepareCaptureValue(
|
|
|
29
29
|
try {
|
|
30
30
|
masked = cfg.mask(value, { key });
|
|
31
31
|
} catch (error) {
|
|
32
|
-
reportError(cfg.onError, error);
|
|
32
|
+
reportError(cfg.onError, error instanceof Error ? error : new Error(String(error)));
|
|
33
33
|
return undefined;
|
|
34
34
|
}
|
|
35
35
|
}
|
package/src/client.ts
CHANGED
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
reportError,
|
|
30
30
|
SCOPE_NAME,
|
|
31
31
|
SCOPE_VERSION,
|
|
32
|
+
sessionRootTracerProvider,
|
|
33
|
+
sessionSampler,
|
|
32
34
|
setLogLevel,
|
|
33
35
|
StampingSpanProcessor,
|
|
34
36
|
type Transport,
|
|
@@ -85,7 +87,7 @@ export function init(options?: TelemetryOptions, overrides?: ClientOverrides): T
|
|
|
85
87
|
try {
|
|
86
88
|
return initInner(options, overrides);
|
|
87
89
|
} catch (error) {
|
|
88
|
-
reportError(options?.onError, error);
|
|
90
|
+
reportError(options?.onError, error instanceof Error ? error : new Error(String(error)));
|
|
89
91
|
activeClient = NOOP_CLIENT;
|
|
90
92
|
return NOOP_CLIENT;
|
|
91
93
|
}
|
|
@@ -98,7 +100,7 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
|
|
|
98
100
|
if (activeClient && activeClient !== NOOP_CLIENT) {
|
|
99
101
|
diag.warn("init() called again; replacing the previous client");
|
|
100
102
|
// shutdown() synchronously releases global registrations before this init claims them.
|
|
101
|
-
void activeClient.shutdown().catch((error:
|
|
103
|
+
void activeClient.shutdown().catch((error: Error) => reportError(config.onError, error));
|
|
102
104
|
}
|
|
103
105
|
|
|
104
106
|
const enabled = config.enabled && Boolean(config.apiKey ?? overrides?.spanExporter);
|
|
@@ -116,7 +118,7 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
|
|
|
116
118
|
...config.resourceAttributes,
|
|
117
119
|
});
|
|
118
120
|
const transport: Transport = { fetchImpl: config.fetchImpl, onError: config.onError };
|
|
119
|
-
const headers = config.apiKey ? otlpHeaders(config.apiKey) : undefined;
|
|
121
|
+
const headers = config.apiKey ? otlpHeaders(config.apiKey, config.sdkName) : undefined;
|
|
120
122
|
|
|
121
123
|
const spanExporter =
|
|
122
124
|
overrides?.spanExporter ??
|
|
@@ -153,6 +155,7 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
|
|
|
153
155
|
|
|
154
156
|
const provider = new BasicTracerProvider({
|
|
155
157
|
resource,
|
|
158
|
+
sampler: sessionSampler(config.sampler),
|
|
156
159
|
spanProcessors: [processor],
|
|
157
160
|
spanLimits: { attributeValueLengthLimit: config.maxAttributeLength },
|
|
158
161
|
});
|
|
@@ -162,7 +165,11 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
|
|
|
162
165
|
let registeredContext = false;
|
|
163
166
|
let registeredPropagation = false;
|
|
164
167
|
if (config.registerGlobal) {
|
|
165
|
-
registeredTrace = trace.setGlobalTracerProvider(
|
|
168
|
+
registeredTrace = trace.setGlobalTracerProvider(
|
|
169
|
+
config.sessionRootOf
|
|
170
|
+
? sessionRootTracerProvider(provider, config.apiKey, config.sessionRootOf, config.onError)
|
|
171
|
+
: provider,
|
|
172
|
+
);
|
|
166
173
|
if (als) {
|
|
167
174
|
registeredContext = apiContext.setGlobalContextManager(new AlsContextManager(als));
|
|
168
175
|
}
|
|
@@ -204,12 +211,12 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
|
|
|
204
211
|
|
|
205
212
|
let torn = false;
|
|
206
213
|
const collect = (op: "forceFlush" | "shutdown"): Promise<void> => {
|
|
207
|
-
const parts: Promise<
|
|
214
|
+
const parts: Promise<void>[] = [provider[op]()];
|
|
208
215
|
if (logs) parts.push(logs[op]());
|
|
209
216
|
if (metrics) parts.push(metrics[op]());
|
|
210
217
|
return Promise.all(parts)
|
|
211
218
|
.then(() => undefined)
|
|
212
|
-
.catch((error:
|
|
219
|
+
.catch((error: Error) => reportError(config.onError, error));
|
|
213
220
|
};
|
|
214
221
|
const handoff = (p: Promise<void>): Promise<void> => {
|
|
215
222
|
if (config.waitUntil) {
|
package/src/config.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AttributeValue } from "@opentelemetry/api";
|
|
2
|
-
import type { ReadableSpan } from "@opentelemetry/sdk-trace-base";
|
|
2
|
+
import type { ReadableSpan, Sampler } from "@opentelemetry/sdk-trace-base";
|
|
3
3
|
import {
|
|
4
4
|
type BatchOptions,
|
|
5
5
|
DEFAULT_BASE_URL,
|
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
type ExportMode,
|
|
8
8
|
resolveEnv,
|
|
9
9
|
type SdkLogLevel,
|
|
10
|
+
type SessionRootOf,
|
|
10
11
|
} from "@telemetry-dev/otel";
|
|
11
12
|
|
|
12
13
|
export type { BatchOptions, ExportMode, LogLevel, SdkLogLevel } from "@telemetry-dev/otel";
|
|
13
14
|
|
|
14
15
|
/** Redaction hook applied to captured content (input/output/log message) before JSON stringification. */
|
|
15
|
-
export type MaskFn = (value:
|
|
16
|
+
export type MaskFn<T = unknown> = (value: T, ctx: { key: string }) => T;
|
|
16
17
|
|
|
17
18
|
export interface TelemetryOptions {
|
|
18
19
|
/** telemetry.dev ingest key (`td_live_…`). Falls back to `TELEMETRY_DEV_API_KEY`. When absent the SDK is a no-op. */
|
|
@@ -29,6 +30,8 @@ export interface TelemetryOptions {
|
|
|
29
30
|
registerGlobal?: boolean;
|
|
30
31
|
/** "batched" (default) buffers spans; "immediate" exports per span (serverless). */
|
|
31
32
|
exportMode?: ExportMode;
|
|
33
|
+
/** OTel sampling policy. Defaults to OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG. */
|
|
34
|
+
sampler?: Sampler;
|
|
32
35
|
/** Capture span inputs (args, prompts) by default. Default true. */
|
|
33
36
|
captureInput?: boolean;
|
|
34
37
|
/** Capture span outputs (results, completions) by default. Default true. */
|
|
@@ -40,6 +43,12 @@ export interface TelemetryOptions {
|
|
|
40
43
|
batch?: BatchOptions;
|
|
41
44
|
/** Export filter. Default when registerGlobal: only spans from this SDK's instrumentation scope. */
|
|
42
45
|
spanFilter?: (span: ReadableSpan) => boolean;
|
|
46
|
+
/**
|
|
47
|
+
* With registerGlobal: spans from other tracers that this returns a session id for are
|
|
48
|
+
* parented under the session trace even when a parent span is active. Use it for frameworks
|
|
49
|
+
* that start each turn inside their own engine span.
|
|
50
|
+
*/
|
|
51
|
+
sessionRootOf?: SessionRootOf;
|
|
43
52
|
/** Extra resource attributes merged onto service.name / deployment.environment.name. */
|
|
44
53
|
resourceAttributes?: Record<string, AttributeValue>;
|
|
45
54
|
/** SDK self-diagnostics console level. Default "warn"; "silent" disables. */
|
|
@@ -49,7 +58,9 @@ export interface TelemetryOptions {
|
|
|
49
58
|
/** Serverless extender (e.g. Cloudflare `ctx.waitUntil`). When provided, `flush()` does not await exports. */
|
|
50
59
|
waitUntil?: (p: Promise<unknown>) => void;
|
|
51
60
|
/** Receives any error raised while emitting telemetry; the SDK never throws into user code. */
|
|
52
|
-
onError?: (
|
|
61
|
+
onError?: (cause: Error) => void;
|
|
62
|
+
/** Header value for a custom telemetry.dev integration. */
|
|
63
|
+
sdkName?: string;
|
|
53
64
|
}
|
|
54
65
|
|
|
55
66
|
export interface ResolvedConfig {
|
|
@@ -60,17 +71,20 @@ export interface ResolvedConfig {
|
|
|
60
71
|
enabled: boolean;
|
|
61
72
|
registerGlobal: boolean;
|
|
62
73
|
exportMode: ExportMode;
|
|
74
|
+
sampler?: Sampler;
|
|
63
75
|
captureInput: boolean;
|
|
64
76
|
captureOutput: boolean;
|
|
65
77
|
mask?: MaskFn;
|
|
66
78
|
maxAttributeLength: number;
|
|
67
79
|
batch: Required<BatchOptions>;
|
|
68
80
|
spanFilter?: (span: ReadableSpan) => boolean;
|
|
81
|
+
sessionRootOf?: SessionRootOf;
|
|
69
82
|
resourceAttributes?: Record<string, AttributeValue>;
|
|
70
83
|
logLevel: SdkLogLevel;
|
|
71
84
|
fetchImpl: typeof fetch;
|
|
72
85
|
waitUntil?: (p: Promise<unknown>) => void;
|
|
73
|
-
onError?: (
|
|
86
|
+
onError?: (cause: Error) => void;
|
|
87
|
+
sdkName: string;
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
export const DEFAULT_MAX_ATTRIBUTE_LENGTH = 65536;
|
|
@@ -89,16 +103,19 @@ export function resolveConfig(options: TelemetryOptions = {}): ResolvedConfig {
|
|
|
89
103
|
enabled: options.enabled ?? true,
|
|
90
104
|
registerGlobal: options.registerGlobal ?? false,
|
|
91
105
|
exportMode: options.exportMode ?? "batched",
|
|
106
|
+
sampler: options.sampler,
|
|
92
107
|
captureInput: options.captureInput ?? true,
|
|
93
108
|
captureOutput: options.captureOutput ?? true,
|
|
94
109
|
mask: options.mask,
|
|
95
110
|
maxAttributeLength: options.maxAttributeLength ?? DEFAULT_MAX_ATTRIBUTE_LENGTH,
|
|
96
111
|
batch: { ...DEFAULT_BATCH, ...options.batch },
|
|
97
112
|
spanFilter: options.spanFilter,
|
|
113
|
+
sessionRootOf: options.sessionRootOf,
|
|
98
114
|
resourceAttributes: options.resourceAttributes,
|
|
99
115
|
logLevel: options.logLevel ?? "warn",
|
|
100
116
|
fetchImpl: options.fetch ?? globalThis.fetch,
|
|
101
117
|
waitUntil: options.waitUntil,
|
|
102
118
|
onError: options.onError,
|
|
119
|
+
sdkName: options.sdkName ?? "@telemetry-dev/sdk",
|
|
103
120
|
};
|
|
104
121
|
}
|
package/src/index.ts
CHANGED
|
@@ -15,15 +15,25 @@ export type {
|
|
|
15
15
|
SdkLogLevel,
|
|
16
16
|
TelemetryOptions,
|
|
17
17
|
} from "./config.ts";
|
|
18
|
-
export {
|
|
18
|
+
export {
|
|
19
|
+
propagateAttributes,
|
|
20
|
+
type PropagatedAttributes,
|
|
21
|
+
SCOPE_NAME,
|
|
22
|
+
TelemetrySpanProcessor,
|
|
23
|
+
type TelemetrySpanProcessorOptions,
|
|
24
|
+
} from "@telemetry-dev/otel";
|
|
19
25
|
export { log, type LogOptions } from "./logs.ts";
|
|
20
26
|
export { observe, type ObserveOptions } from "./observe.ts";
|
|
21
27
|
export {
|
|
28
|
+
activeContext,
|
|
29
|
+
extractW3cContext,
|
|
22
30
|
getTraceparent,
|
|
31
|
+
injectW3cContext,
|
|
23
32
|
type ParentRef,
|
|
24
33
|
type SpanHandle,
|
|
25
34
|
startActiveSpan,
|
|
26
35
|
startSpan,
|
|
27
36
|
type StartSpanOptions,
|
|
28
37
|
updateActiveSpan,
|
|
38
|
+
withContext,
|
|
29
39
|
} from "./span.ts";
|
package/src/logs.ts
CHANGED
|
@@ -8,17 +8,17 @@ import { currentClient } from "./client.ts";
|
|
|
8
8
|
import type { LogLevel } from "./config.ts";
|
|
9
9
|
|
|
10
10
|
// Severity numbers match the ingest's severityFromNumber buckets exactly.
|
|
11
|
-
const SEVERITIES
|
|
11
|
+
const SEVERITIES = {
|
|
12
12
|
debug: { number: SeverityNumber.DEBUG, text: "DEBUG" },
|
|
13
13
|
info: { number: SeverityNumber.INFO, text: "INFO" },
|
|
14
14
|
warn: { number: SeverityNumber.WARN, text: "WARN" },
|
|
15
15
|
error: { number: SeverityNumber.ERROR, text: "ERROR" },
|
|
16
|
-
};
|
|
16
|
+
} satisfies Record<LogLevel, { number: SeverityNumber; text: string }>;
|
|
17
17
|
|
|
18
18
|
export interface LogOptions {
|
|
19
19
|
/** Default "info". */
|
|
20
20
|
level?: LogLevel;
|
|
21
|
-
attributes?: Record<string,
|
|
21
|
+
attributes?: Record<string, Parameters<import("./config.ts").MaskFn>[0]>;
|
|
22
22
|
eventName?: string;
|
|
23
23
|
timestamp?: Date | number;
|
|
24
24
|
}
|
|
@@ -57,7 +57,7 @@ export function log(message: string, options?: LogOptions): void {
|
|
|
57
57
|
for (const [key, value] of Object.entries(propagated)) {
|
|
58
58
|
if (value !== undefined) {
|
|
59
59
|
attributes[key] =
|
|
60
|
-
|
|
60
|
+
value.constructor === String ? truncate(String(value), cfg.maxAttributeLength) : value;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}
|
|
@@ -72,6 +72,6 @@ export function log(message: string, options?: LogOptions): void {
|
|
|
72
72
|
attributes,
|
|
73
73
|
});
|
|
74
74
|
} catch (error) {
|
|
75
|
-
reportError(core.config.onError, error);
|
|
75
|
+
reportError(core.config.onError, error instanceof Error ? error : new Error(String(error)));
|
|
76
76
|
}
|
|
77
77
|
}
|
package/src/observe.ts
CHANGED
|
@@ -13,11 +13,11 @@ export interface ObserveOptions extends StartSpanOptions {
|
|
|
13
13
|
* output, thrown/rejected errors captured and rethrown. Activates context so nested SDK calls
|
|
14
14
|
* parent to it. Resolves the client at call time, so wrapping at module load before init() works.
|
|
15
15
|
*/
|
|
16
|
-
export function observe<TArgs extends unknown[], TReturn>(
|
|
17
|
-
fn: (...args: TArgs) => TReturn,
|
|
16
|
+
export function observe<TThis, TArgs extends unknown[], TReturn>(
|
|
17
|
+
fn: (this: TThis, ...args: TArgs) => TReturn,
|
|
18
18
|
options?: ObserveOptions,
|
|
19
19
|
): (...args: TArgs) => TReturn {
|
|
20
|
-
const wrapped = function (this:
|
|
20
|
+
const wrapped = function (this: TThis, ...args: TArgs): TReturn {
|
|
21
21
|
const core = currentClient().core;
|
|
22
22
|
if (!core) return fn.apply(this, args);
|
|
23
23
|
|
|
@@ -34,20 +34,20 @@ export function observe<TArgs extends unknown[], TReturn>(
|
|
|
34
34
|
: args;
|
|
35
35
|
handle = createSpanHandle(core, name, { ...options, input });
|
|
36
36
|
} catch (error) {
|
|
37
|
-
reportError(core.config.onError, error);
|
|
37
|
+
reportError(core.config.onError, error instanceof Error ? error : new Error(String(error)));
|
|
38
38
|
return fn.apply(this, args);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
try {
|
|
42
42
|
const result = withContext(handle.context, () => fn.apply(this, args));
|
|
43
43
|
if (isThenable(result)) {
|
|
44
|
-
return
|
|
44
|
+
return result.then(
|
|
45
45
|
(value) => {
|
|
46
46
|
handle.end({ output: value });
|
|
47
47
|
return value;
|
|
48
48
|
},
|
|
49
|
-
(error:
|
|
50
|
-
handle.end({ error });
|
|
49
|
+
(error: Error) => {
|
|
50
|
+
handle.end({ error: error instanceof Error ? error : new Error(String(error)) });
|
|
51
51
|
throw error;
|
|
52
52
|
},
|
|
53
53
|
) as TReturn;
|
|
@@ -55,7 +55,7 @@ export function observe<TArgs extends unknown[], TReturn>(
|
|
|
55
55
|
handle.end({ output: result });
|
|
56
56
|
return result;
|
|
57
57
|
} catch (error) {
|
|
58
|
-
handle.end({ error });
|
|
58
|
+
handle.end({ error: error instanceof Error ? error : new Error(String(error)) });
|
|
59
59
|
throw error;
|
|
60
60
|
}
|
|
61
61
|
};
|
package/src/span.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Context,
|
|
3
|
+
defaultTextMapGetter,
|
|
3
4
|
defaultTextMapSetter,
|
|
5
|
+
type Link,
|
|
6
|
+
ROOT_CONTEXT,
|
|
4
7
|
type Span,
|
|
5
8
|
type SpanContext,
|
|
6
9
|
SpanKind,
|
|
7
10
|
SpanStatusCode,
|
|
8
11
|
trace,
|
|
9
12
|
} from "@opentelemetry/api";
|
|
10
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
CompositePropagator,
|
|
15
|
+
parseTraceParent,
|
|
16
|
+
W3CBaggagePropagator,
|
|
17
|
+
W3CTraceContextPropagator,
|
|
18
|
+
} from "@opentelemetry/core";
|
|
11
19
|
|
|
12
20
|
import {
|
|
13
21
|
activeContext,
|
|
@@ -15,7 +23,9 @@ import {
|
|
|
15
23
|
PROPAGATED_KEY,
|
|
16
24
|
propagatedFromContext,
|
|
17
25
|
reportError,
|
|
26
|
+
sessionIdOf,
|
|
18
27
|
withContext,
|
|
28
|
+
withSessionParent,
|
|
19
29
|
} from "@telemetry-dev/otel";
|
|
20
30
|
|
|
21
31
|
import {
|
|
@@ -33,8 +43,12 @@ export type ParentRef = string /* W3C traceparent */ | SpanHandle | SpanContext
|
|
|
33
43
|
export interface StartSpanOptions extends SpanFields {
|
|
34
44
|
/** Default "span" → operation "function". */
|
|
35
45
|
type?: SpanType;
|
|
46
|
+
/** OpenTelemetry span kind; defaults to INTERNAL. */
|
|
47
|
+
kind?: SpanKind;
|
|
36
48
|
/** Parent override; defaults to the active context. */
|
|
37
49
|
parent?: ParentRef;
|
|
50
|
+
/** Links to associate with the span. */
|
|
51
|
+
links?: Link[];
|
|
38
52
|
startTime?: Date | number;
|
|
39
53
|
/** Per-call override of the global captureInput. */
|
|
40
54
|
captureInput?: boolean;
|
|
@@ -59,7 +73,7 @@ export interface SpanHandle {
|
|
|
59
73
|
interface SpanMeta {
|
|
60
74
|
type: SpanType;
|
|
61
75
|
capture: FieldCaptureConfig;
|
|
62
|
-
onError?: (
|
|
76
|
+
onError?: (cause: Error) => void;
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
// Remembers each span's type + capture flags so update()/end()/updateActiveSpan map
|
|
@@ -68,7 +82,7 @@ const SPAN_META = new WeakMap<Span, SpanMeta>();
|
|
|
68
82
|
|
|
69
83
|
const SEVERITY_ERROR = 17;
|
|
70
84
|
|
|
71
|
-
export function isThenable(value:
|
|
85
|
+
export function isThenable<T>(value: T): value is T & PromiseLike<Awaited<T>> {
|
|
72
86
|
return (
|
|
73
87
|
value != null &&
|
|
74
88
|
(typeof value === "object" || typeof value === "function") &&
|
|
@@ -77,11 +91,11 @@ export function isThenable(value: unknown): value is PromiseLike<unknown> {
|
|
|
77
91
|
);
|
|
78
92
|
}
|
|
79
93
|
|
|
80
|
-
function isContext(value:
|
|
94
|
+
function isContext(value: Exclude<ParentRef, string>): value is Context {
|
|
81
95
|
return "getValue" in value && typeof value.getValue === "function";
|
|
82
96
|
}
|
|
83
97
|
|
|
84
|
-
function isSpanHandle(value:
|
|
98
|
+
function isSpanHandle(value: Exclude<ParentRef, string>): value is SpanHandle {
|
|
85
99
|
return "span" in value && "context" in value && "update" in value;
|
|
86
100
|
}
|
|
87
101
|
|
|
@@ -101,14 +115,22 @@ function resolveParent(parent: ParentRef | undefined): Context {
|
|
|
101
115
|
if (!parsed) return base;
|
|
102
116
|
return trace.setSpanContext(base, { ...parsed, isRemote: true });
|
|
103
117
|
}
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
return
|
|
118
|
+
const ref = parent as Exclude<ParentRef, string>;
|
|
119
|
+
if (isSpanHandle(ref)) return withAmbientPropagated(ref.context);
|
|
120
|
+
if (isContext(ref)) return withAmbientPropagated(ref);
|
|
121
|
+
return trace.setSpanContext(base, ref);
|
|
107
122
|
}
|
|
108
123
|
|
|
109
124
|
function applyError(span: Span, error: unknown): void {
|
|
110
125
|
const err = error instanceof Error ? error : undefined;
|
|
111
|
-
|
|
126
|
+
// Prefer the specific subclass name (Python parity: type(error).__name__) — many SDK
|
|
127
|
+
// error classes (e.g. openai's BadRequestError) never override the inherited "Error" name.
|
|
128
|
+
const errorType =
|
|
129
|
+
err === undefined
|
|
130
|
+
? "Error"
|
|
131
|
+
: err.name !== "Error" && err.name.length > 0
|
|
132
|
+
? err.name
|
|
133
|
+
: (err.constructor?.name ?? "Error") || "Error";
|
|
112
134
|
const message = err?.message ?? String(error);
|
|
113
135
|
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
114
136
|
span.setAttribute("error.type", errorType);
|
|
@@ -151,18 +173,32 @@ export function createSpanHandle(
|
|
|
151
173
|
options: StartSpanOptions = {},
|
|
152
174
|
): SpanHandle {
|
|
153
175
|
const meta = metaFor(core, options);
|
|
154
|
-
const
|
|
176
|
+
const resolved = resolveParent(options.parent);
|
|
177
|
+
const parentCtx = withSessionParent(
|
|
178
|
+
resolved,
|
|
179
|
+
sessionIdOf(resolved, options.attributes),
|
|
180
|
+
core.config.apiKey,
|
|
181
|
+
);
|
|
182
|
+
const attrs = fieldsToAttributes(options, meta.type, meta.capture);
|
|
155
183
|
const span = core.tracer.startSpan(
|
|
156
184
|
name,
|
|
157
185
|
{
|
|
158
|
-
kind: SpanKind.INTERNAL,
|
|
186
|
+
kind: options.kind ?? SpanKind.INTERNAL,
|
|
159
187
|
startTime: options.startTime,
|
|
160
|
-
|
|
188
|
+
links: options.links,
|
|
189
|
+
attributes: {
|
|
190
|
+
"gen_ai.operation.name": SPAN_TYPE_OPERATIONS[meta.type],
|
|
191
|
+
...propagatedFromContext(parentCtx),
|
|
192
|
+
...attrs,
|
|
193
|
+
},
|
|
161
194
|
},
|
|
162
195
|
parentCtx,
|
|
163
196
|
);
|
|
164
197
|
SPAN_META.set(span, meta);
|
|
165
|
-
|
|
198
|
+
// The processor stamps propagation on start; explicit fields still win.
|
|
199
|
+
if (Object.keys(attrs).length > 0) span.setAttributes(attrs);
|
|
200
|
+
if (options.name) span.updateName(options.name);
|
|
201
|
+
if (options.error !== undefined) applyError(span, options.error);
|
|
166
202
|
|
|
167
203
|
const spanContext = span.spanContext();
|
|
168
204
|
const flags = (spanContext.traceFlags & 0xff).toString(16).padStart(2, "0");
|
|
@@ -179,7 +215,7 @@ export function createSpanHandle(
|
|
|
179
215
|
try {
|
|
180
216
|
applyFields(span, meta, fields);
|
|
181
217
|
} catch (error) {
|
|
182
|
-
reportError(meta.onError, error);
|
|
218
|
+
reportError(meta.onError, error instanceof Error ? error : new Error(String(error)));
|
|
183
219
|
}
|
|
184
220
|
return handle;
|
|
185
221
|
},
|
|
@@ -187,7 +223,7 @@ export function createSpanHandle(
|
|
|
187
223
|
try {
|
|
188
224
|
if (fields) applyFields(span, meta, fields);
|
|
189
225
|
} catch (error) {
|
|
190
|
-
reportError(meta.onError, error);
|
|
226
|
+
reportError(meta.onError, error instanceof Error ? error : new Error(String(error)));
|
|
191
227
|
}
|
|
192
228
|
span.end(fields?.endTime);
|
|
193
229
|
},
|
|
@@ -202,7 +238,7 @@ export function startSpan(name: string, options?: StartSpanOptions): SpanHandle
|
|
|
202
238
|
try {
|
|
203
239
|
return createSpanHandle(core, name, options);
|
|
204
240
|
} catch (error) {
|
|
205
|
-
reportError(core.config.onError, error);
|
|
241
|
+
reportError(core.config.onError, error instanceof Error ? error : new Error(String(error)));
|
|
206
242
|
return NOOP_SPAN_HANDLE;
|
|
207
243
|
}
|
|
208
244
|
}
|
|
@@ -211,7 +247,7 @@ export function runWithHandle<T>(handle: SpanHandle, fn: (span: SpanHandle) => T
|
|
|
211
247
|
try {
|
|
212
248
|
const result = withContext(handle.context, () => fn(handle));
|
|
213
249
|
if (isThenable(result)) {
|
|
214
|
-
return
|
|
250
|
+
return result.then(
|
|
215
251
|
(value) => {
|
|
216
252
|
handle.end();
|
|
217
253
|
return value;
|
|
@@ -241,10 +277,11 @@ export function startActiveSpan<T>(
|
|
|
241
277
|
optionsOrFn: StartSpanOptions | ((span: SpanHandle) => T),
|
|
242
278
|
maybeFn?: (span: SpanHandle) => T,
|
|
243
279
|
): T {
|
|
244
|
-
const
|
|
245
|
-
const
|
|
280
|
+
const isFn = typeof optionsOrFn === "function";
|
|
281
|
+
const fn = isFn ? optionsOrFn : maybeFn!;
|
|
282
|
+
const options = isFn ? undefined : optionsOrFn;
|
|
246
283
|
const handle = startSpan(name, options);
|
|
247
|
-
if (
|
|
284
|
+
if (handle === NOOP_SPAN_HANDLE) return fn(handle);
|
|
248
285
|
return runWithHandle(handle, fn);
|
|
249
286
|
}
|
|
250
287
|
|
|
@@ -258,15 +295,45 @@ export function updateActiveSpan(fields: SpanFields): void {
|
|
|
258
295
|
const meta = SPAN_META.get(span) ?? metaFor(core);
|
|
259
296
|
applyFields(span, meta, fields);
|
|
260
297
|
} catch (error) {
|
|
261
|
-
reportError(core.config.onError, error);
|
|
298
|
+
reportError(core.config.onError, error instanceof Error ? error : new Error(String(error)));
|
|
262
299
|
}
|
|
263
300
|
}
|
|
264
301
|
|
|
265
|
-
const
|
|
302
|
+
const TRACE_CONTEXT_PROPAGATOR = new W3CTraceContextPropagator();
|
|
303
|
+
const W3C_PROPAGATOR = new CompositePropagator({
|
|
304
|
+
propagators: [TRACE_CONTEXT_PROPAGATOR, new W3CBaggagePropagator()],
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
export { activeContext, withContext };
|
|
308
|
+
|
|
309
|
+
/** Extract W3C trace context; baggage is omitted unless includeBaggage is true. */
|
|
310
|
+
export function extractW3cContext(
|
|
311
|
+
carrier: Record<string, unknown>,
|
|
312
|
+
options: { includeBaggage?: boolean } = {},
|
|
313
|
+
): Context {
|
|
314
|
+
const propagator = options.includeBaggage === true ? W3C_PROPAGATOR : TRACE_CONTEXT_PROPAGATOR;
|
|
315
|
+
return propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** Inject W3C trace context; baggage is omitted unless includeBaggage is true. */
|
|
319
|
+
export function injectW3cContext(
|
|
320
|
+
context: Context,
|
|
321
|
+
carrier: Record<string, unknown>,
|
|
322
|
+
options: { includeBaggage?: boolean } = {},
|
|
323
|
+
): void {
|
|
324
|
+
const propagator = options.includeBaggage === true ? W3C_PROPAGATOR : TRACE_CONTEXT_PROPAGATOR;
|
|
325
|
+
for (const key of Object.keys(carrier)) {
|
|
326
|
+
const normalized = key.toLowerCase();
|
|
327
|
+
if (normalized === "traceparent" || normalized === "tracestate" || normalized === "baggage") {
|
|
328
|
+
delete carrier[key];
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
propagator.inject(context, carrier, defaultTextMapSetter);
|
|
332
|
+
}
|
|
266
333
|
|
|
267
334
|
/** Serialize the active span context as a W3C traceparent; null when there is none. */
|
|
268
335
|
export function getTraceparent(): string | null {
|
|
269
|
-
const carrier
|
|
270
|
-
|
|
336
|
+
const carrier = { traceparent: undefined };
|
|
337
|
+
TRACE_CONTEXT_PROPAGATOR.inject(activeContext(), carrier, defaultTextMapSetter);
|
|
271
338
|
return carrier.traceparent ?? null;
|
|
272
339
|
}
|