@telemetry-dev/sdk 0.1.1 → 0.1.3

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 CHANGED
@@ -60,6 +60,17 @@ 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
+ Root spans remain independent by default. `init({ sessionMode: "process" })` groups otherwise
64
+ uncorrelated spans and logs using a fresh opaque UUID for each enabled SDK initialization.
65
+ Explicit and propagated session IDs take precedence, and valid parent trace context is preserved.
66
+ The generated session ends when the client is shut down or replaced; a later `init()` generates a
67
+ new ID. It is not persisted across processes.
68
+
69
+ `sampler` accepts an OpenTelemetry `Sampler` and overrides `OTEL_TRACES_SAMPLER` / `OTEL_TRACES_SAMPLER_ARG`.
70
+ The default is parent-based always-on. Session roots keep the configured root policy.
71
+ Nested spans and explicitly parented spans keep the real parent's policy.
72
+ The SDK installs the [session sampling wrapper](../otel/README.md#correlation-attributes) on its provider. This includes the global provider that Eve uses.
73
+
63
74
  ## API
64
75
 
65
76
  - `init(options?)` — create the client (isolated OTel TracerProvider by default).
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
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, activeContext, propagateAttributes, withContext } 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
@@ -20,10 +20,14 @@ interface TelemetryOptions {
20
20
  serviceName?: string;
21
21
  /** Kill switch: `false` makes the SDK a complete no-op (tests). Default true. */
22
22
  enabled?: boolean;
23
+ /** Group otherwise uncorrelated spans and logs for this SDK initialization. Default "explicit". */
24
+ sessionMode?: "explicit" | "process";
23
25
  /** Register this SDK's TracerProvider/context manager/propagator globally. Default false (isolated). */
24
26
  registerGlobal?: boolean;
25
27
  /** "batched" (default) buffers spans; "immediate" exports per span (serverless). */
26
28
  exportMode?: ExportMode$1;
29
+ /** OTel sampling policy. Defaults to OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG. */
30
+ sampler?: Sampler;
27
31
  /** Capture span inputs (args, prompts) by default. Default true. */
28
32
  captureInput?: boolean;
29
33
  /** Capture span outputs (results, completions) by default. Default true. */
@@ -35,6 +39,12 @@ interface TelemetryOptions {
35
39
  batch?: BatchOptions$1;
36
40
  /** Export filter. Default when registerGlobal: only spans from this SDK's instrumentation scope. */
37
41
  spanFilter?: (span: ReadableSpan) => boolean;
42
+ /**
43
+ * With registerGlobal: spans from other tracers that this returns a session id for are
44
+ * parented under the session trace even when a parent span is active. Use it for frameworks
45
+ * that start each turn inside their own engine span.
46
+ */
47
+ sessionRootOf?: SessionRootOf;
38
48
  /** Extra resource attributes merged onto service.name / deployment.environment.name. */
39
49
  resourceAttributes?: Record<string, AttributeValue>;
40
50
  /** SDK self-diagnostics console level. Default "warn"; "silent" disables. */
@@ -195,4 +205,4 @@ interface ObserveOptions extends StartSpanOptions {
195
205
  */
196
206
  declare function observe<TThis, TArgs extends unknown[], TReturn>(fn: (this: TThis, ...args: TArgs) => TReturn, options?: ObserveOptions): (...args: TArgs) => TReturn;
197
207
  //#endregion
198
- 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, activeContext, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
208
+ 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
@@ -3,10 +3,11 @@ import { CompositePropagator, W3CBaggagePropagator, W3CTraceContextPropagator, p
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, activeContext as activeContext$1, als, createLogExporter, createMetricExporter, createMetricsPipeline, createTraceExporter, diag, jsonAttr, omitUndefined, otlpHeaders, propagateAttributes, propagatedFromContext, reportError, resolveEnv, setLogLevel, withContext, withContext as withContext$1 } 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();
10
+ if (options.sessionMode !== void 0 && options.sessionMode !== "explicit" && options.sessionMode !== "process") throw new TypeError(`Invalid sessionMode: ${String(options.sessionMode)}`);
10
11
  const baseUrl = (options.baseUrl ?? env.TELEMETRY_DEV_BASE_URL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
11
12
  return {
12
13
  apiKey: options.apiKey ?? env.TELEMETRY_DEV_API_KEY,
@@ -14,8 +15,10 @@ function resolveConfig(options = {}) {
14
15
  environment: options.environment ?? env.TELEMETRY_DEV_ENVIRONMENT ?? "production",
15
16
  serviceName: options.serviceName ?? env.OTEL_SERVICE_NAME ?? "unknown_service",
16
17
  enabled: options.enabled ?? true,
18
+ sessionMode: options.sessionMode ?? "explicit",
17
19
  registerGlobal: options.registerGlobal ?? false,
18
20
  exportMode: options.exportMode ?? "batched",
21
+ sampler: options.sampler,
19
22
  captureInput: options.captureInput ?? true,
20
23
  captureOutput: options.captureOutput ?? true,
21
24
  mask: options.mask,
@@ -25,6 +28,7 @@ function resolveConfig(options = {}) {
25
28
  ...options.batch
26
29
  },
27
30
  spanFilter: options.spanFilter,
31
+ sessionRootOf: options.sessionRootOf,
28
32
  resourceAttributes: options.resourceAttributes,
29
33
  logLevel: options.logLevel ?? "warn",
30
34
  fetchImpl: options.fetch ?? globalThis.fetch,
@@ -71,6 +75,7 @@ function initInner(options, overrides) {
71
75
  activeClient = NOOP_CLIENT;
72
76
  return NOOP_CLIENT;
73
77
  }
78
+ if (config.sessionMode === "process") config.processSessionId = globalThis.crypto.randomUUID();
74
79
  const resource = resourceFromAttributes({
75
80
  "service.name": config.serviceName,
76
81
  "deployment.environment.name": config.environment,
@@ -94,25 +99,27 @@ function initInner(options, overrides) {
94
99
  exporter: metricExporter,
95
100
  exportIntervalMillis: config.exportMode === "batched" ? BATCHED_METRIC_INTERVAL_MS : DORMANT_INTERVAL_MS
96
101
  }) : void 0;
97
- const spanFilter = config.spanFilter ?? (config.registerGlobal ? (span) => span.instrumentationScope.name === SCOPE_NAME : void 0);
102
+ const spanFilter = config.spanFilter ?? (config.registerGlobal ? (span) => span.instrumentationScope.name === SCOPE_NAME$1 : void 0);
103
+ const processor = new StampingSpanProcessor({
104
+ exporter: spanExporter,
105
+ exportMode: config.exportMode,
106
+ batch: config.batch,
107
+ spanFilter,
108
+ recordMetrics: metrics ? (span) => metrics.record(span) : void 0,
109
+ onError: config.onError
110
+ });
98
111
  const provider = new BasicTracerProvider({
99
112
  resource,
100
- spanProcessors: [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
- })],
113
+ sampler: sessionSampler(config.sampler),
114
+ spanProcessors: [processor],
108
115
  spanLimits: { attributeValueLengthLimit: config.maxAttributeLength }
109
116
  });
110
- const tracer = provider.getTracer(SCOPE_NAME, SCOPE_VERSION);
117
+ const tracer = provider.getTracer(SCOPE_NAME$1, SCOPE_VERSION);
111
118
  let registeredTrace = false;
112
119
  let registeredContext = false;
113
120
  let registeredPropagation = false;
114
121
  if (config.registerGlobal) {
115
- registeredTrace = trace.setGlobalTracerProvider(provider);
122
+ registeredTrace = trace.setGlobalTracerProvider(config.sessionRootOf ? sessionRootTracerProvider(provider, config.apiKey, config.sessionRootOf, config.onError) : provider);
116
123
  if (als) registeredContext = context.setGlobalContextManager(new AlsContextManager(als));
117
124
  registeredPropagation = propagation.setGlobalPropagator(new W3CTraceContextPropagator());
118
125
  if (!registeredTrace) diag.warn("a global TracerProvider is already registered; attach the TelemetrySpanProcessor from '@telemetry-dev/otel' to your own provider instead");
@@ -132,7 +139,7 @@ function initInner(options, overrides) {
132
139
  processors: [logProcessor]
133
140
  });
134
141
  logs = {
135
- logger: loggerProvider.getLogger(SCOPE_NAME, SCOPE_VERSION),
142
+ logger: loggerProvider.getLogger(SCOPE_NAME$1, SCOPE_VERSION),
136
143
  forceFlush: () => loggerProvider.forceFlush(),
137
144
  shutdown: () => loggerProvider.shutdown()
138
145
  };
@@ -233,7 +240,14 @@ function log(message, options) {
233
240
  onError: cfg.onError
234
241
  };
235
242
  const severity = SEVERITIES[options?.level ?? "info"];
236
- const ctx = activeContext$1();
243
+ const active = activeContext$1();
244
+ const explicitSession = options?.attributes?.["gen_ai.conversation.id"];
245
+ const propagatedSession = propagatedFromContext(active)?.["gen_ai.conversation.id"];
246
+ const sessionId = explicitSession !== void 0 ? void 0 : typeof propagatedSession === "string" ? propagatedSession : cfg.processSessionId;
247
+ const ctx = sessionId === void 0 ? active : active.setValue(PROPAGATED_KEY, {
248
+ ...propagatedFromContext(active),
249
+ "gen_ai.conversation.id": sessionId
250
+ });
237
251
  const attributes = {};
238
252
  for (const [key, value] of Object.entries(options?.attributes ?? {})) {
239
253
  if (value === void 0) continue;
@@ -420,15 +434,27 @@ function metaFor(core, options) {
420
434
  }
421
435
  function createSpanHandle(core, name, options = {}) {
422
436
  const meta = metaFor(core, options);
423
- const parentCtx = resolveParent(options.parent);
437
+ const resolved = resolveParent(options.parent);
438
+ const sessionId = sessionIdOf(resolved, options.attributes) ?? core.config.processSessionId;
439
+ const parentCtx = withSessionParent(sessionId === void 0 ? resolved : resolved.setValue(PROPAGATED_KEY, {
440
+ ...propagatedFromContext(resolved),
441
+ "gen_ai.conversation.id": sessionId
442
+ }), sessionId, core.config.apiKey);
443
+ const attrs = fieldsToAttributes(options, meta.type, meta.capture);
424
444
  const span = core.tracer.startSpan(name, {
425
445
  kind: options.kind ?? SpanKind.INTERNAL,
426
446
  startTime: options.startTime,
427
447
  links: options.links,
428
- attributes: { "gen_ai.operation.name": SPAN_TYPE_OPERATIONS[meta.type] }
448
+ attributes: {
449
+ "gen_ai.operation.name": SPAN_TYPE_OPERATIONS[meta.type],
450
+ ...propagatedFromContext(parentCtx),
451
+ ...attrs
452
+ }
429
453
  }, parentCtx);
430
454
  SPAN_META.set(span, meta);
431
- applyFields(span, meta, options);
455
+ if (Object.keys(attrs).length > 0) span.setAttributes(attrs);
456
+ if (options.name) span.updateName(options.name);
457
+ if (options.error !== void 0) applyError(span, options.error);
432
458
  const spanContext = span.spanContext();
433
459
  const flags = (spanContext.traceFlags & 255).toString(16).padStart(2, "0");
434
460
  const handle = {
@@ -491,7 +517,7 @@ function startActiveSpan(name, optionsOrFn, maybeFn) {
491
517
  const isFn = typeof optionsOrFn === "function";
492
518
  const fn = isFn ? optionsOrFn : maybeFn;
493
519
  const handle = startSpan(name, isFn ? void 0 : optionsOrFn);
494
- if (!handle.isRecording) return fn(handle);
520
+ if (handle === NOOP_SPAN_HANDLE) return fn(handle);
495
521
  return runWithHandle(handle, fn);
496
522
  }
497
523
  /** Update the innermost active span; no-op when none is recording. */
@@ -573,4 +599,4 @@ function observe(fn, options) {
573
599
  return wrapped;
574
600
  }
575
601
  //#endregion
576
- export { activeContext, extractW3cContext, flush, getTraceparent, init, injectW3cContext, log, observe, propagateAttributes, shutdown, startActiveSpan, startSpan, updateActiveSpan, withContext };
602
+ 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.1",
3
+ "version": "0.1.3",
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",
@@ -16,7 +16,7 @@
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "git+https://github.com/telemetry-dev/telemetry.dev.git",
19
+ "url": "git+https://github.com/telemetry-dev/sdks.git",
20
20
  "directory": "packages/sdk"
21
21
  },
22
22
  "files": [
@@ -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.1"
45
+ "@telemetry-dev/otel": "^0.1.3"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@opentelemetry/api": "^1.9.1",
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,
@@ -110,6 +112,8 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
110
112
  return NOOP_CLIENT;
111
113
  }
112
114
 
115
+ if (config.sessionMode === "process") config.processSessionId = globalThis.crypto.randomUUID();
116
+
113
117
  const resource = resourceFromAttributes({
114
118
  "service.name": config.serviceName,
115
119
  "deployment.environment.name": config.environment,
@@ -153,6 +157,7 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
153
157
 
154
158
  const provider = new BasicTracerProvider({
155
159
  resource,
160
+ sampler: sessionSampler(config.sampler),
156
161
  spanProcessors: [processor],
157
162
  spanLimits: { attributeValueLengthLimit: config.maxAttributeLength },
158
163
  });
@@ -162,7 +167,11 @@ function initInner(options?: TelemetryOptions, overrides?: ClientOverrides): Tel
162
167
  let registeredContext = false;
163
168
  let registeredPropagation = false;
164
169
  if (config.registerGlobal) {
165
- registeredTrace = trace.setGlobalTracerProvider(provider);
170
+ registeredTrace = trace.setGlobalTracerProvider(
171
+ config.sessionRootOf
172
+ ? sessionRootTracerProvider(provider, config.apiKey, config.sessionRootOf, config.onError)
173
+ : provider,
174
+ );
166
175
  if (als) {
167
176
  registeredContext = apiContext.setGlobalContextManager(new AlsContextManager(als));
168
177
  }
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,6 +7,7 @@ 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";
@@ -25,10 +26,14 @@ export interface TelemetryOptions {
25
26
  serviceName?: string;
26
27
  /** Kill switch: `false` makes the SDK a complete no-op (tests). Default true. */
27
28
  enabled?: boolean;
29
+ /** Group otherwise uncorrelated spans and logs for this SDK initialization. Default "explicit". */
30
+ sessionMode?: "explicit" | "process";
28
31
  /** Register this SDK's TracerProvider/context manager/propagator globally. Default false (isolated). */
29
32
  registerGlobal?: boolean;
30
33
  /** "batched" (default) buffers spans; "immediate" exports per span (serverless). */
31
34
  exportMode?: ExportMode;
35
+ /** OTel sampling policy. Defaults to OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG. */
36
+ sampler?: Sampler;
32
37
  /** Capture span inputs (args, prompts) by default. Default true. */
33
38
  captureInput?: boolean;
34
39
  /** Capture span outputs (results, completions) by default. Default true. */
@@ -40,6 +45,12 @@ export interface TelemetryOptions {
40
45
  batch?: BatchOptions;
41
46
  /** Export filter. Default when registerGlobal: only spans from this SDK's instrumentation scope. */
42
47
  spanFilter?: (span: ReadableSpan) => boolean;
48
+ /**
49
+ * With registerGlobal: spans from other tracers that this returns a session id for are
50
+ * parented under the session trace even when a parent span is active. Use it for frameworks
51
+ * that start each turn inside their own engine span.
52
+ */
53
+ sessionRootOf?: SessionRootOf;
43
54
  /** Extra resource attributes merged onto service.name / deployment.environment.name. */
44
55
  resourceAttributes?: Record<string, AttributeValue>;
45
56
  /** SDK self-diagnostics console level. Default "warn"; "silent" disables. */
@@ -60,14 +71,18 @@ export interface ResolvedConfig {
60
71
  environment: string;
61
72
  serviceName: string;
62
73
  enabled: boolean;
74
+ sessionMode: "explicit" | "process";
75
+ processSessionId?: string;
63
76
  registerGlobal: boolean;
64
77
  exportMode: ExportMode;
78
+ sampler?: Sampler;
65
79
  captureInput: boolean;
66
80
  captureOutput: boolean;
67
81
  mask?: MaskFn;
68
82
  maxAttributeLength: number;
69
83
  batch: Required<BatchOptions>;
70
84
  spanFilter?: (span: ReadableSpan) => boolean;
85
+ sessionRootOf?: SessionRootOf;
71
86
  resourceAttributes?: Record<string, AttributeValue>;
72
87
  logLevel: SdkLogLevel;
73
88
  fetchImpl: typeof fetch;
@@ -80,6 +95,13 @@ export const DEFAULT_MAX_ATTRIBUTE_LENGTH = 65536;
80
95
 
81
96
  export function resolveConfig(options: TelemetryOptions = {}): ResolvedConfig {
82
97
  const env = resolveEnv();
98
+ if (
99
+ options.sessionMode !== undefined &&
100
+ options.sessionMode !== "explicit" &&
101
+ options.sessionMode !== "process"
102
+ ) {
103
+ throw new TypeError(`Invalid sessionMode: ${String(options.sessionMode)}`);
104
+ }
83
105
  const baseUrl = (options.baseUrl ?? env.TELEMETRY_DEV_BASE_URL ?? DEFAULT_BASE_URL).replace(
84
106
  /\/+$/,
85
107
  "",
@@ -90,14 +112,17 @@ export function resolveConfig(options: TelemetryOptions = {}): ResolvedConfig {
90
112
  environment: options.environment ?? env.TELEMETRY_DEV_ENVIRONMENT ?? "production",
91
113
  serviceName: options.serviceName ?? env.OTEL_SERVICE_NAME ?? "unknown_service",
92
114
  enabled: options.enabled ?? true,
115
+ sessionMode: options.sessionMode ?? "explicit",
93
116
  registerGlobal: options.registerGlobal ?? false,
94
117
  exportMode: options.exportMode ?? "batched",
118
+ sampler: options.sampler,
95
119
  captureInput: options.captureInput ?? true,
96
120
  captureOutput: options.captureOutput ?? true,
97
121
  mask: options.mask,
98
122
  maxAttributeLength: options.maxAttributeLength ?? DEFAULT_MAX_ATTRIBUTE_LENGTH,
99
123
  batch: { ...DEFAULT_BATCH, ...options.batch },
100
124
  spanFilter: options.spanFilter,
125
+ sessionRootOf: options.sessionRootOf,
101
126
  resourceAttributes: options.resourceAttributes,
102
127
  logLevel: options.logLevel ?? "warn",
103
128
  fetchImpl: options.fetch ?? globalThis.fetch,
package/src/index.ts CHANGED
@@ -15,7 +15,13 @@ export type {
15
15
  SdkLogLevel,
16
16
  TelemetryOptions,
17
17
  } from "./config.ts";
18
- export { propagateAttributes, type PropagatedAttributes } from "@telemetry-dev/otel";
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 {
package/src/logs.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  import type { AttributeValue } from "@opentelemetry/api";
2
2
  import { SeverityNumber } from "@opentelemetry/api-logs";
3
3
 
4
- import { activeContext, propagatedFromContext, reportError } from "@telemetry-dev/otel";
4
+ import {
5
+ activeContext,
6
+ PROPAGATED_KEY,
7
+ propagatedFromContext,
8
+ reportError,
9
+ } from "@telemetry-dev/otel";
5
10
 
6
11
  import { prepareCaptureValue, truncate } from "./capture.ts";
7
12
  import { currentClient } from "./client.ts";
@@ -40,7 +45,22 @@ export function log(message: string, options?: LogOptions): void {
40
45
  onError: cfg.onError,
41
46
  };
42
47
  const severity = SEVERITIES[options?.level ?? "info"];
43
- const ctx = activeContext();
48
+ const active = activeContext();
49
+ const explicitSession = options?.attributes?.["gen_ai.conversation.id"];
50
+ const propagatedSession = propagatedFromContext(active)?.["gen_ai.conversation.id"];
51
+ const sessionId =
52
+ explicitSession !== undefined
53
+ ? undefined
54
+ : typeof propagatedSession === "string"
55
+ ? propagatedSession
56
+ : cfg.processSessionId;
57
+ const ctx =
58
+ sessionId === undefined
59
+ ? active
60
+ : active.setValue(PROPAGATED_KEY, {
61
+ ...propagatedFromContext(active),
62
+ "gen_ai.conversation.id": sessionId,
63
+ });
44
64
 
45
65
  const attributes: Record<string, AttributeValue> = {};
46
66
  for (const [key, value] of Object.entries(options?.attributes ?? {})) {
package/src/span.ts CHANGED
@@ -23,7 +23,9 @@ import {
23
23
  PROPAGATED_KEY,
24
24
  propagatedFromContext,
25
25
  reportError,
26
+ sessionIdOf,
26
27
  withContext,
28
+ withSessionParent,
27
29
  } from "@telemetry-dev/otel";
28
30
 
29
31
  import {
@@ -171,19 +173,36 @@ export function createSpanHandle(
171
173
  options: StartSpanOptions = {},
172
174
  ): SpanHandle {
173
175
  const meta = metaFor(core, options);
174
- const parentCtx = resolveParent(options.parent);
176
+ const resolved = resolveParent(options.parent);
177
+ const sessionId = sessionIdOf(resolved, options.attributes) ?? core.config.processSessionId;
178
+ const sessionContext =
179
+ sessionId === undefined
180
+ ? resolved
181
+ : resolved.setValue(PROPAGATED_KEY, {
182
+ ...propagatedFromContext(resolved),
183
+ "gen_ai.conversation.id": sessionId,
184
+ });
185
+ const parentCtx = withSessionParent(sessionContext, sessionId, core.config.apiKey);
186
+ const attrs = fieldsToAttributes(options, meta.type, meta.capture);
175
187
  const span = core.tracer.startSpan(
176
188
  name,
177
189
  {
178
190
  kind: options.kind ?? SpanKind.INTERNAL,
179
191
  startTime: options.startTime,
180
192
  links: options.links,
181
- attributes: { "gen_ai.operation.name": SPAN_TYPE_OPERATIONS[meta.type] },
193
+ attributes: {
194
+ "gen_ai.operation.name": SPAN_TYPE_OPERATIONS[meta.type],
195
+ ...propagatedFromContext(parentCtx),
196
+ ...attrs,
197
+ },
182
198
  },
183
199
  parentCtx,
184
200
  );
185
201
  SPAN_META.set(span, meta);
186
- applyFields(span, meta, options);
202
+ // The processor stamps propagation on start; explicit fields still win.
203
+ if (Object.keys(attrs).length > 0) span.setAttributes(attrs);
204
+ if (options.name) span.updateName(options.name);
205
+ if (options.error !== undefined) applyError(span, options.error);
187
206
 
188
207
  const spanContext = span.spanContext();
189
208
  const flags = (spanContext.traceFlags & 0xff).toString(16).padStart(2, "0");
@@ -266,7 +285,7 @@ export function startActiveSpan<T>(
266
285
  const fn = isFn ? optionsOrFn : maybeFn!;
267
286
  const options = isFn ? undefined : optionsOrFn;
268
287
  const handle = startSpan(name, options);
269
- if (!handle.isRecording) return fn(handle);
288
+ if (handle === NOOP_SPAN_HANDLE) return fn(handle);
270
289
  return runWithHandle(handle, fn);
271
290
  }
272
291