@telemetry-dev/otel 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -9,7 +9,7 @@ import { LogRecordExporter } from "@opentelemetry/sdk-logs";
9
9
  declare const SCOPE_NAME = "@telemetry-dev/sdk";
10
10
  declare const SCOPE_VERSION = "0.0.0";
11
11
  declare function omitUndefined(attributes: Attributes): Attributes;
12
- declare function jsonAttr(value: unknown): string | undefined;
12
+ declare function jsonAttr<T>(value: T): string | undefined;
13
13
  //#endregion
14
14
  //#region src/config.d.ts
15
15
  type LogLevel = "debug" | "info" | "warn" | "error";
@@ -27,7 +27,7 @@ interface BatchOptions {
27
27
  }
28
28
  declare const DEFAULT_BASE_URL = "https://ingest.telemetry.dev";
29
29
  declare const DEFAULT_BATCH: Required<BatchOptions>;
30
- declare function resolveEnv(): Record<string, string | undefined>;
30
+ declare function resolveEnv(): NodeJS.ProcessEnv;
31
31
  //#endregion
32
32
  //#region src/context.d.ts
33
33
  declare const als: AsyncLocalStorage<Context> | undefined;
@@ -39,13 +39,13 @@ declare function activeContext(): Context;
39
39
  /** Run `fn` with `ctx` active in both our ALS and the global OTel context manager. */
40
40
  declare function withContext<T>(ctx: Context, fn: () => T): T;
41
41
  declare const PROPAGATED_KEY: symbol;
42
- interface PropagatedAttributes {
42
+ interface PropagatedAttributes<MetadataValue = unknown> {
43
43
  /** Stamped as user.id on every span and log record in scope. */
44
44
  userId?: string;
45
45
  /** Stamped as gen_ai.conversation.id on every span and log record in scope. */
46
46
  sessionId?: string;
47
47
  /** Stamped as td.metadata.<key> on every span and log record in scope. */
48
- metadata?: Record<string, unknown>;
48
+ metadata?: Record<string, MetadataValue>;
49
49
  }
50
50
  declare function buildPropagatedAttributes(attrs: PropagatedAttributes): Attributes;
51
51
  declare function propagatedFromContext(ctx: Context): Attributes | undefined;
@@ -74,7 +74,7 @@ declare const diag: {
74
74
  error: (...args: unknown[]) => void;
75
75
  };
76
76
  /** Fail-open guard: SDK internals report through onError + diagnostics, never into user code. */
77
- declare function reportError(onError: ((error: unknown) => void) | undefined, error: unknown): void;
77
+ declare function reportError(onError: ((error: Error) => void) | undefined, cause: unknown): void;
78
78
  //#endregion
79
79
  //#region src/metrics.d.ts
80
80
  declare const DURATION_BUCKETS: number[];
@@ -114,7 +114,7 @@ interface TelemetrySpanProcessorOptions {
114
114
  /** deployment.environment.name on the metrics resource. Falls back to `TELEMETRY_DEV_ENVIRONMENT`. */
115
115
  environment?: string;
116
116
  fetch?: typeof fetch;
117
- onError?: (error: unknown) => void;
117
+ onError?: (error: Error) => void;
118
118
  /** Advanced/test seam: replaces the OTLP fetch exporter. */
119
119
  spanExporter?: SpanExporter;
120
120
  }
@@ -137,7 +137,7 @@ declare function createTelemetrySpanExporter(options?: {
137
137
  apiKey?: string;
138
138
  baseUrl?: string;
139
139
  fetch?: typeof fetch;
140
- onError?: (error: unknown) => void;
140
+ onError?: (error: Error) => void;
141
141
  }): SpanExporter;
142
142
  //#endregion
143
143
  //#region src/processor.d.ts
@@ -147,7 +147,7 @@ interface StampingProcessorOptions {
147
147
  batch: Required<BatchOptions>;
148
148
  spanFilter?: (span: ReadableSpan) => boolean;
149
149
  recordMetrics?: (span: ReadableSpan) => void;
150
- onError?: (error: unknown) => void;
150
+ onError?: (error: Error) => void;
151
151
  }
152
152
  /**
153
153
  * The vendor span processor: stamps propagated correlation attributes onto every span at start,
@@ -166,7 +166,7 @@ declare class StampingSpanProcessor implements SpanProcessor {
166
166
  //#region src/transport.d.ts
167
167
  interface Transport {
168
168
  fetchImpl: typeof fetch;
169
- onError?: (error: unknown) => void;
169
+ onError?: (error: Error) => void;
170
170
  }
171
171
  interface OtlpTarget {
172
172
  url: string;
@@ -190,6 +190,10 @@ declare function maybeGzip(body: Uint8Array): Promise<{
190
190
  declare function createTraceExporter(target: OtlpTarget, transport: Transport): SpanExporter;
191
191
  declare function createLogExporter(target: OtlpTarget, transport: Transport): LogRecordExporter;
192
192
  declare function createMetricExporter(target: OtlpTarget, transport: Transport): PushMetricExporter;
193
- declare function otlpHeaders(apiKey: string): Record<string, string>;
193
+ declare function otlpHeaders(apiKey: string, sdkName?: string): {
194
+ "content-type": string;
195
+ authorization: string;
196
+ "x-telemetry-dev-sdk": string;
197
+ };
194
198
  //#endregion
195
199
  export { AlsContextManager, BATCHED_METRIC_INTERVAL_MS, type BatchOptions, DEFAULT_BASE_URL, DEFAULT_BATCH, DORMANT_INTERVAL_MS, DURATION_BUCKETS, type ExportMode, type LogLevel, type MetricsPipeline, type OtlpTarget, PROPAGATED_KEY, type PropagatedAttributes, SCOPE_NAME, SCOPE_VERSION, type SdkLogLevel, type StampingProcessorOptions, StampingSpanProcessor, TOKEN_BUCKETS, TelemetrySpanProcessor, type TelemetrySpanProcessorOptions, type Transport, activeContext, als, buildPropagatedAttributes, createLogExporter, createMetricExporter, createMetricsPipeline, createTelemetrySpanExporter, createTraceExporter, diag, jsonAttr, maybeGzip, omitUndefined, otlpHeaders, postOtlp, propagateAttributes, propagatedFromContext, reportError, resolveEnv, setLogLevel, withContext };
package/dist/index.mjs CHANGED
@@ -34,7 +34,7 @@ const DEFAULT_BATCH = {
34
34
  exportTimeoutMillis: 3e4
35
35
  };
36
36
  function resolveEnv() {
37
- if (typeof process !== "undefined" && process.env) return process.env;
37
+ if (globalThis.process !== void 0 && process.env) return process.env;
38
38
  return {};
39
39
  }
40
40
  //#endregion
@@ -61,11 +61,11 @@ const diag = {
61
61
  error: (...args) => emit("error", args)
62
62
  };
63
63
  /** Fail-open guard: SDK internals report through onError + diagnostics, never into user code. */
64
- function reportError(onError, error) {
64
+ function reportError(onError, cause) {
65
65
  try {
66
- onError?.(error);
66
+ onError?.(cause instanceof Error ? cause : new Error(String(cause)));
67
67
  } catch {}
68
- diag.error(error);
68
+ diag.error(cause);
69
69
  }
70
70
  //#endregion
71
71
  //#region src/context.ts
@@ -204,7 +204,7 @@ const TOKEN_OPERATIONS = new Set([
204
204
  const DORMANT_INTERVAL_MS = 2 ** 31 - 1;
205
205
  const BATCHED_METRIC_INTERVAL_MS = 6e4;
206
206
  function stringAttr(value) {
207
- return typeof value === "string" ? value : void 0;
207
+ return value?.constructor === String ? `${value}` : void 0;
208
208
  }
209
209
  function createMetricsPipeline({ resource, exporter, exportIntervalMillis }) {
210
210
  const reader = new PeriodicExportingMetricReader({
@@ -226,7 +226,7 @@ function createMetricsPipeline({ resource, exporter, exportIntervalMillis }) {
226
226
  });
227
227
  const record = (span) => {
228
228
  const operation = span.attributes["gen_ai.operation.name"];
229
- if (typeof operation !== "string" || !DURATION_OPERATIONS.has(operation)) return;
229
+ if (operation?.constructor !== String || !DURATION_OPERATIONS.has(`${operation}`)) return;
230
230
  const attrs = omitUndefined({
231
231
  "gen_ai.operation.name": operation,
232
232
  "gen_ai.provider.name": stringAttr(span.attributes["gen_ai.provider.name"]),
@@ -241,12 +241,12 @@ function createMetricsPipeline({ resource, exporter, exportIntervalMillis }) {
241
241
  } : attrs);
242
242
  if (!TOKEN_OPERATIONS.has(operation)) return;
243
243
  const inputTokens = span.attributes["gen_ai.usage.input_tokens"];
244
- if (typeof inputTokens === "number") tokenHistogram.record(inputTokens, {
244
+ if (inputTokens?.constructor === Number) tokenHistogram.record(inputTokens, {
245
245
  ...attrs,
246
246
  "gen_ai.token.type": "input"
247
247
  });
248
248
  const outputTokens = span.attributes["gen_ai.usage.output_tokens"];
249
- if (typeof outputTokens === "number") tokenHistogram.record(outputTokens, {
249
+ if (outputTokens?.constructor === Number) tokenHistogram.record(outputTokens, {
250
250
  ...attrs,
251
251
  "gen_ai.token.type": "output"
252
252
  });
@@ -335,7 +335,7 @@ const postOtlp = async ({ fetchImpl, url, headers, body }) => {
335
335
  };
336
336
  const GZIP_THRESHOLD_BYTES = 1024;
337
337
  async function maybeGzip(body) {
338
- if (body.byteLength <= GZIP_THRESHOLD_BYTES || typeof CompressionStream === "undefined") return { body };
338
+ if (body.byteLength <= GZIP_THRESHOLD_BYTES || globalThis.CompressionStream === void 0) return { body };
339
339
  try {
340
340
  const stream = new Blob([body]).stream().pipeThrough(new CompressionStream("gzip"));
341
341
  return {
@@ -455,10 +455,11 @@ function createMetricExporter(target, transport) {
455
455
  shutdown: () => Promise.resolve()
456
456
  };
457
457
  }
458
- function otlpHeaders(apiKey) {
458
+ function otlpHeaders(apiKey, sdkName = "@telemetry-dev/otel") {
459
459
  return {
460
460
  "content-type": "application/x-protobuf",
461
- authorization: `Bearer ${apiKey}`
461
+ authorization: `Bearer ${apiKey}`,
462
+ "x-telemetry-dev-sdk": sdkName
462
463
  };
463
464
  }
464
465
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@telemetry-dev/otel",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Bring-your-own OpenTelemetry base layer for telemetry.dev: span processor and OTLP/protobuf trace exporter.",
5
5
  "keywords": [
6
6
  "genai",
package/src/attrs.ts CHANGED
@@ -19,7 +19,15 @@ export function omitUndefined(attributes: Attributes): Attributes {
19
19
 
20
20
  // Stringify structured content (messages / tool args) for the gen_ai.* string attributes the
21
21
  // ingest parses back into JSON. Returns undefined so omitUndefined drops absent content.
22
- export function jsonAttr(value: unknown): string | undefined {
22
+ export type JsonValue =
23
+ | string
24
+ | number
25
+ | boolean
26
+ | null
27
+ | JsonValue[]
28
+ | { [key: string]: JsonValue };
29
+
30
+ export function jsonAttr<T>(value: T): string | undefined {
23
31
  if (value === undefined) return undefined;
24
32
  if (typeof value === "string") return value;
25
33
  try {
package/src/config.ts CHANGED
@@ -22,8 +22,7 @@ export const DEFAULT_BATCH: Required<BatchOptions> = {
22
22
  exportTimeoutMillis: 30000,
23
23
  };
24
24
 
25
- export function resolveEnv(): Record<string, string | undefined> {
26
- if (typeof process !== "undefined" && process.env) return process.env;
27
- const emptyEnv: Record<string, string | undefined> = {};
28
- return emptyEnv;
25
+ export function resolveEnv() {
26
+ if (globalThis.process !== undefined && process.env) return process.env;
27
+ return {};
29
28
  }
package/src/context.ts CHANGED
@@ -53,13 +53,13 @@ export function withContext<T>(ctx: Context, fn: () => T): T {
53
53
 
54
54
  export const PROPAGATED_KEY = createContextKey("telemetry.dev propagated attributes");
55
55
 
56
- export interface PropagatedAttributes {
56
+ export interface PropagatedAttributes<MetadataValue = unknown> {
57
57
  /** Stamped as user.id on every span and log record in scope. */
58
58
  userId?: string;
59
59
  /** Stamped as gen_ai.conversation.id on every span and log record in scope. */
60
60
  sessionId?: string;
61
61
  /** Stamped as td.metadata.<key> on every span and log record in scope. */
62
- metadata?: Record<string, unknown>;
62
+ metadata?: Record<string, MetadataValue>;
63
63
  }
64
64
 
65
65
  const RESERVED_METADATA_KEYS = new Set(["userId", "sessionId", "user_id", "session_id"]);
package/src/debug.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  import type { LogLevel, SdkLogLevel } from "./config.ts";
2
2
 
3
- const ORDER: Record<SdkLogLevel, number> = { debug: 0, info: 1, warn: 2, error: 3, silent: 4 };
3
+ const ORDER = { debug: 0, info: 1, warn: 2, error: 3, silent: 4 } satisfies Record<
4
+ SdkLogLevel,
5
+ number
6
+ >;
4
7
 
5
8
  let currentLevel: SdkLogLevel = "warn";
6
9
 
@@ -22,11 +25,11 @@ export const diag = {
22
25
  };
23
26
 
24
27
  /** Fail-open guard: SDK internals report through onError + diagnostics, never into user code. */
25
- export function reportError(onError: ((error: unknown) => void) | undefined, error: unknown): void {
28
+ export function reportError(onError: ((error: Error) => void) | undefined, cause: unknown): void {
26
29
  try {
27
- onError?.(error);
30
+ onError?.(cause instanceof Error ? cause : new Error(String(cause)));
28
31
  } catch {
29
32
  // onError itself must never propagate
30
33
  }
31
- diag.error(error);
34
+ diag.error(cause);
32
35
  }
package/src/metrics.ts CHANGED
@@ -31,8 +31,8 @@ export interface MetricsPipeline {
31
31
  shutdown(): Promise<void>;
32
32
  }
33
33
 
34
- function stringAttr(value: unknown): string | undefined {
35
- return typeof value === "string" ? value : undefined;
34
+ function stringAttr(value: Attributes[string]): string | undefined {
35
+ return value?.constructor === String ? `${value}` : undefined;
36
36
  }
37
37
 
38
38
  export function createMetricsPipeline({
@@ -59,7 +59,7 @@ export function createMetricsPipeline({
59
59
 
60
60
  const record = (span: ReadableSpan): void => {
61
61
  const operation = span.attributes["gen_ai.operation.name"];
62
- if (typeof operation !== "string" || !DURATION_OPERATIONS.has(operation)) return;
62
+ if (operation?.constructor !== String || !DURATION_OPERATIONS.has(`${operation}`)) return;
63
63
  const attrs: Attributes = omitUndefined({
64
64
  "gen_ai.operation.name": operation,
65
65
  "gen_ai.provider.name": stringAttr(span.attributes["gen_ai.provider.name"]),
@@ -74,11 +74,11 @@ export function createMetricsPipeline({
74
74
  );
75
75
  if (!TOKEN_OPERATIONS.has(operation)) return;
76
76
  const inputTokens = span.attributes["gen_ai.usage.input_tokens"];
77
- if (typeof inputTokens === "number") {
77
+ if (inputTokens?.constructor === Number) {
78
78
  tokenHistogram.record(inputTokens, { ...attrs, "gen_ai.token.type": "input" });
79
79
  }
80
80
  const outputTokens = span.attributes["gen_ai.usage.output_tokens"];
81
- if (typeof outputTokens === "number") {
81
+ if (outputTokens?.constructor === Number) {
82
82
  tokenHistogram.record(outputTokens, { ...attrs, "gen_ai.token.type": "output" });
83
83
  }
84
84
  };
package/src/otel.ts CHANGED
@@ -47,7 +47,7 @@ export interface TelemetrySpanProcessorOptions {
47
47
  /** deployment.environment.name on the metrics resource. Falls back to `TELEMETRY_DEV_ENVIRONMENT`. */
48
48
  environment?: string;
49
49
  fetch?: typeof fetch;
50
- onError?: (error: unknown) => void;
50
+ onError?: (error: Error) => void;
51
51
  /** Advanced/test seam: replaces the OTLP fetch exporter. */
52
52
  spanExporter?: SpanExporter;
53
53
  }
@@ -142,7 +142,7 @@ export function createTelemetrySpanExporter(
142
142
  apiKey?: string;
143
143
  baseUrl?: string;
144
144
  fetch?: typeof fetch;
145
- onError?: (error: unknown) => void;
145
+ onError?: (error: Error) => void;
146
146
  } = {},
147
147
  ): SpanExporter {
148
148
  const env = resolveEnv();
package/src/processor.ts CHANGED
@@ -18,7 +18,7 @@ export interface StampingProcessorOptions {
18
18
  batch: Required<BatchOptions>;
19
19
  spanFilter?: (span: ReadableSpan) => boolean;
20
20
  recordMetrics?: (span: ReadableSpan) => void;
21
- onError?: (error: unknown) => void;
21
+ onError?: (error: Error) => void;
22
22
  }
23
23
 
24
24
  /**
package/src/transport.ts CHANGED
@@ -16,7 +16,7 @@ import { reportError } from "./debug.ts";
16
16
 
17
17
  export interface Transport {
18
18
  fetchImpl: typeof fetch;
19
- onError?: (error: unknown) => void;
19
+ onError?: (error: Error) => void;
20
20
  }
21
21
 
22
22
  export interface OtlpTarget {
@@ -74,7 +74,7 @@ const GZIP_THRESHOLD_BYTES = 1024;
74
74
  export async function maybeGzip(
75
75
  body: Uint8Array,
76
76
  ): Promise<{ body: Uint8Array; contentEncoding?: "gzip" }> {
77
- if (body.byteLength <= GZIP_THRESHOLD_BYTES || typeof CompressionStream === "undefined") {
77
+ if (body.byteLength <= GZIP_THRESHOLD_BYTES || globalThis.CompressionStream === undefined) {
78
78
  return { body };
79
79
  }
80
80
  try {
@@ -147,7 +147,7 @@ export function createTraceExporter(target: OtlpTarget, transport: Transport): S
147
147
  export(spans, resultCallback) {
148
148
  send(spans).then(
149
149
  () => resultCallback({ code: ExportResultCode.SUCCESS }),
150
- (error: unknown) => {
150
+ (error: Error) => {
151
151
  reportError(transport.onError, error);
152
152
  resultCallback({
153
153
  code: ExportResultCode.FAILED,
@@ -172,7 +172,7 @@ export function createLogExporter(target: OtlpTarget, transport: Transport): Log
172
172
  export(logs, resultCallback) {
173
173
  send(logs).then(
174
174
  () => resultCallback({ code: ExportResultCode.SUCCESS }),
175
- (error: unknown) => {
175
+ (error: Error) => {
176
176
  reportError(transport.onError, error);
177
177
  resultCallback({
178
178
  code: ExportResultCode.FAILED,
@@ -225,7 +225,7 @@ export function createMetricExporter(target: OtlpTarget, transport: Transport):
225
225
  }
226
226
  resultCallback({ code: 0 });
227
227
  })
228
- .catch((error: unknown) => {
228
+ .catch((error: Error) => {
229
229
  reportError(onError, error);
230
230
  resultCallback({ code: 1, error: error instanceof Error ? error : undefined });
231
231
  });
@@ -236,9 +236,10 @@ export function createMetricExporter(target: OtlpTarget, transport: Transport):
236
236
  };
237
237
  }
238
238
 
239
- export function otlpHeaders(apiKey: string): Record<string, string> {
239
+ export function otlpHeaders(apiKey: string, sdkName = "@telemetry-dev/otel") {
240
240
  return {
241
241
  "content-type": "application/x-protobuf",
242
242
  authorization: `Bearer ${apiKey}`,
243
+ "x-telemetry-dev-sdk": sdkName,
243
244
  };
244
245
  }