@telemetry-dev/otel 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 +37 -1
- package/dist/index.d.mts +54 -13
- package/dist/index.mjs +467 -95
- package/package.json +1 -1
- package/src/attrs.ts +9 -1
- package/src/config.ts +2 -3
- package/src/context.ts +2 -2
- package/src/debug.ts +7 -4
- package/src/index.ts +9 -0
- package/src/metrics.ts +5 -5
- package/src/otel.ts +9 -3
- package/src/processor.ts +1 -1
- package/src/session.ts +260 -0
- package/src/transport.ts +260 -90
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ROOT_CONTEXT, context, createContextKey } from "@opentelemetry/api";
|
|
1
|
+
import { ROOT_CONTEXT, TraceFlags, context, createContextKey, diag as diag$1, isSpanContextValid, trace } from "@opentelemetry/api";
|
|
2
2
|
import { AggregationTemporality, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
|
|
3
|
-
import { ExportResultCode } from "@opentelemetry/core";
|
|
3
|
+
import { ExportResultCode, getNumberFromEnv, getStringFromEnv } from "@opentelemetry/core";
|
|
4
4
|
import { resourceFromAttributes } from "@opentelemetry/resources";
|
|
5
|
-
import { BatchSpanProcessor, SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
|
|
5
|
+
import { AlwaysOffSampler, AlwaysOnSampler, BatchSpanProcessor, ParentBasedSampler, SimpleSpanProcessor, TraceIdRatioBasedSampler } from "@opentelemetry/sdk-trace-base";
|
|
6
6
|
import { ProtobufLogsSerializer, ProtobufMetricsSerializer, ProtobufTraceSerializer } from "@opentelemetry/otlp-transformer";
|
|
7
7
|
//#region src/attrs.ts
|
|
8
8
|
const SCOPE_NAME = "@telemetry-dev/sdk";
|
|
@@ -34,7 +34,7 @@ const DEFAULT_BATCH = {
|
|
|
34
34
|
exportTimeoutMillis: 3e4
|
|
35
35
|
};
|
|
36
36
|
function resolveEnv() {
|
|
37
|
-
if (
|
|
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,
|
|
64
|
+
function reportError(onError, cause) {
|
|
65
65
|
try {
|
|
66
|
-
onError?.(
|
|
66
|
+
onError?.(cause instanceof Error ? cause : new Error(String(cause)));
|
|
67
67
|
} catch {}
|
|
68
|
-
diag.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
|
|
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 (
|
|
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 (
|
|
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 (
|
|
249
|
+
if (outputTokens?.constructor === Number) tokenHistogram.record(outputTokens, {
|
|
250
250
|
...attrs,
|
|
251
251
|
"gen_ai.token.type": "output"
|
|
252
252
|
});
|
|
@@ -301,6 +301,8 @@ var StampingSpanProcessor = class {
|
|
|
301
301
|
//#endregion
|
|
302
302
|
//#region src/transport.ts
|
|
303
303
|
const RETRY_DELAYS_MS = [100, 500];
|
|
304
|
+
const MAX_RETRY_AFTER_MS = 2e3;
|
|
305
|
+
const MAX_TIMER_DELAY_MS = 2147483647;
|
|
304
306
|
const RETRYABLE_STATUSES = new Set([
|
|
305
307
|
429,
|
|
306
308
|
502,
|
|
@@ -308,21 +310,105 @@ const RETRYABLE_STATUSES = new Set([
|
|
|
308
310
|
504
|
|
309
311
|
]);
|
|
310
312
|
const isRetryableStatus = (status) => RETRYABLE_STATUSES.has(status);
|
|
311
|
-
const
|
|
313
|
+
const HTTP_MONTHS = [
|
|
314
|
+
"Jan",
|
|
315
|
+
"Feb",
|
|
316
|
+
"Mar",
|
|
317
|
+
"Apr",
|
|
318
|
+
"May",
|
|
319
|
+
"Jun",
|
|
320
|
+
"Jul",
|
|
321
|
+
"Aug",
|
|
322
|
+
"Sep",
|
|
323
|
+
"Oct",
|
|
324
|
+
"Nov",
|
|
325
|
+
"Dec"
|
|
326
|
+
];
|
|
327
|
+
const HTTP_WEEKDAYS = [
|
|
328
|
+
"Sun",
|
|
329
|
+
"Mon",
|
|
330
|
+
"Tue",
|
|
331
|
+
"Wed",
|
|
332
|
+
"Thu",
|
|
333
|
+
"Fri",
|
|
334
|
+
"Sat"
|
|
335
|
+
];
|
|
336
|
+
const HTTP_WEEKDAYS_LONG = [
|
|
337
|
+
"Sunday",
|
|
338
|
+
"Monday",
|
|
339
|
+
"Tuesday",
|
|
340
|
+
"Wednesday",
|
|
341
|
+
"Thursday",
|
|
342
|
+
"Friday",
|
|
343
|
+
"Saturday"
|
|
344
|
+
];
|
|
345
|
+
const httpDateTimestamp = (weekday, dayText, monthText, yearText, hourText, minuteText, secondText, weekdays) => {
|
|
346
|
+
const day = Number(dayText);
|
|
347
|
+
const month = HTTP_MONTHS.indexOf(monthText);
|
|
348
|
+
const year = Number(yearText);
|
|
349
|
+
const hour = Number(hourText);
|
|
350
|
+
const minute = Number(minuteText);
|
|
351
|
+
const second = Number(secondText);
|
|
352
|
+
if (hour > 23 || minute > 59 || second > 59) return void 0;
|
|
353
|
+
const date = /* @__PURE__ */ new Date(0);
|
|
354
|
+
date.setUTCFullYear(year, month, day);
|
|
355
|
+
date.setUTCHours(hour, minute, second, 0);
|
|
356
|
+
if (date.getUTCFullYear() !== year || date.getUTCMonth() !== month || date.getUTCDate() !== day || weekdays[date.getUTCDay()] !== weekday) return;
|
|
357
|
+
return date.getTime();
|
|
358
|
+
};
|
|
359
|
+
const parseHttpDate = (value) => {
|
|
360
|
+
const imf = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (\d{2}) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (\d{4}) (\d{2}):(\d{2}):(\d{2}) GMT$/.exec(value);
|
|
361
|
+
if (imf) return httpDateTimestamp(...imf.slice(1), HTTP_WEEKDAYS);
|
|
362
|
+
const rfc850 = /^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday), (\d{2})-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d{2}) (\d{2}):(\d{2}):(\d{2}) GMT$/.exec(value);
|
|
363
|
+
if (rfc850) {
|
|
364
|
+
const fields = rfc850.slice(1);
|
|
365
|
+
const currentYear = (/* @__PURE__ */ new Date()).getUTCFullYear();
|
|
366
|
+
let year = Math.floor(currentYear / 100) * 100 + Number(fields[3]);
|
|
367
|
+
if (year > currentYear + 50) year -= 100;
|
|
368
|
+
fields[3] = String(year);
|
|
369
|
+
return httpDateTimestamp(...fields, HTTP_WEEKDAYS_LONG);
|
|
370
|
+
}
|
|
371
|
+
const asctime = /^(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) ( [1-9]|[12]\d|3[01]) (\d{2}):(\d{2}):(\d{2}) (\d{4})$/.exec(value);
|
|
372
|
+
if (!asctime) return void 0;
|
|
373
|
+
const [, weekday, month, day, hour, minute, second, year] = asctime;
|
|
374
|
+
return httpDateTimestamp(weekday, day.trim(), month, year, hour, minute, second, HTTP_WEEKDAYS);
|
|
375
|
+
};
|
|
376
|
+
const retryAfterMs = (res) => {
|
|
377
|
+
const header = res.headers.get("retry-after")?.trim();
|
|
378
|
+
if (header === void 0) return void 0;
|
|
379
|
+
if (/^\d+$/.test(header)) return Number(header) * 1e3;
|
|
380
|
+
const at = parseHttpDate(header);
|
|
381
|
+
return at === void 0 ? void 0 : Math.max(0, at - Date.now());
|
|
382
|
+
};
|
|
383
|
+
const delay = (ms, signal) => new Promise((resolve, reject) => {
|
|
384
|
+
const onAbort = () => {
|
|
385
|
+
clearTimeout(timer);
|
|
386
|
+
reject(signal?.reason ?? /* @__PURE__ */ new Error("telemetry.dev export aborted"));
|
|
387
|
+
};
|
|
388
|
+
const timer = setTimeout(() => {
|
|
389
|
+
signal?.removeEventListener("abort", onAbort);
|
|
390
|
+
resolve();
|
|
391
|
+
}, ms);
|
|
392
|
+
if (signal?.aborted) onAbort();
|
|
393
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
394
|
+
});
|
|
312
395
|
const cancelBody = async (res) => {
|
|
313
396
|
try {
|
|
314
397
|
await res.body?.cancel();
|
|
315
398
|
} catch {}
|
|
316
399
|
};
|
|
317
|
-
const postOtlp = async ({ fetchImpl, url, headers, body }) => {
|
|
400
|
+
const postOtlp = async ({ fetchImpl, url, headers, body, signal }) => {
|
|
318
401
|
for (let attempt = 0;; attempt += 1) {
|
|
402
|
+
let retryAfter;
|
|
319
403
|
try {
|
|
320
404
|
const res = await fetchImpl(url, {
|
|
321
405
|
method: "POST",
|
|
322
406
|
headers,
|
|
323
|
-
body
|
|
407
|
+
body,
|
|
408
|
+
signal
|
|
324
409
|
});
|
|
325
|
-
|
|
410
|
+
retryAfter = retryAfterMs(res);
|
|
411
|
+
if (res.ok || !isRetryableStatus(res.status) || attempt === RETRY_DELAYS_MS.length || retryAfter !== void 0 && retryAfter > MAX_RETRY_AFTER_MS) {
|
|
326
412
|
await cancelBody(res);
|
|
327
413
|
return res;
|
|
328
414
|
}
|
|
@@ -330,12 +416,12 @@ const postOtlp = async ({ fetchImpl, url, headers, body }) => {
|
|
|
330
416
|
} catch (error) {
|
|
331
417
|
if (attempt === RETRY_DELAYS_MS.length) throw error;
|
|
332
418
|
}
|
|
333
|
-
await delay(RETRY_DELAYS_MS[attempt]);
|
|
419
|
+
await delay(retryAfter ?? RETRY_DELAYS_MS[attempt], signal);
|
|
334
420
|
}
|
|
335
421
|
};
|
|
336
422
|
const GZIP_THRESHOLD_BYTES = 1024;
|
|
337
423
|
async function maybeGzip(body) {
|
|
338
|
-
if (body.byteLength <= GZIP_THRESHOLD_BYTES ||
|
|
424
|
+
if (body.byteLength <= GZIP_THRESHOLD_BYTES || globalThis.CompressionStream === void 0) return { body };
|
|
339
425
|
try {
|
|
340
426
|
const stream = new Blob([body]).stream().pipeThrough(new CompressionStream("gzip"));
|
|
341
427
|
return {
|
|
@@ -346,119 +432,140 @@ async function maybeGzip(body) {
|
|
|
346
432
|
return { body };
|
|
347
433
|
}
|
|
348
434
|
}
|
|
435
|
+
async function postSerialized(body, target, transport, label, post = postOtlp, signal) {
|
|
436
|
+
const { body: finalBody, contentEncoding } = await maybeGzip(body);
|
|
437
|
+
const headers = contentEncoding ? {
|
|
438
|
+
...target.headers,
|
|
439
|
+
"content-encoding": contentEncoding
|
|
440
|
+
} : target.headers;
|
|
441
|
+
const res = await post({
|
|
442
|
+
fetchImpl: transport.fetchImpl,
|
|
443
|
+
url: target.url,
|
|
444
|
+
headers,
|
|
445
|
+
body: finalBody,
|
|
446
|
+
signal
|
|
447
|
+
});
|
|
448
|
+
if (!res.ok) {
|
|
449
|
+
const retryAfter = retryAfterMs(res);
|
|
450
|
+
const retryAfterHeader = res.headers.get("retry-after")?.trim();
|
|
451
|
+
const retryDetail = retryAfter !== void 0 && retryAfter > MAX_RETRY_AFTER_MS && retryAfterHeader !== void 0 ? `; retry-after ${retryAfterHeader} exceeds ${MAX_RETRY_AFTER_MS / 1e3}s retry cap` : "";
|
|
452
|
+
throw new Error(`telemetry.dev ${label} ingest failed: ${res.status}${retryDetail}`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
const createExportLifecycle = (transport) => {
|
|
456
|
+
const inFlight = /* @__PURE__ */ new Set();
|
|
457
|
+
let shutDown = false;
|
|
458
|
+
const configuredTimeoutMillis = transport.exportTimeoutMillis ?? DEFAULT_BATCH.exportTimeoutMillis;
|
|
459
|
+
const timeoutMillis = Number.isFinite(configuredTimeoutMillis) && configuredTimeoutMillis >= 0 && configuredTimeoutMillis <= MAX_TIMER_DELAY_MS ? configuredTimeoutMillis : DEFAULT_BATCH.exportTimeoutMillis;
|
|
460
|
+
const callback = (resultCallback, result) => {
|
|
461
|
+
try {
|
|
462
|
+
resultCallback(result);
|
|
463
|
+
} catch (error) {
|
|
464
|
+
reportError(transport.onError, error);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
const exportBatch = (send, resultCallback) => {
|
|
468
|
+
if (shutDown) {
|
|
469
|
+
const error = /* @__PURE__ */ new Error("telemetry.dev exporter is shut down");
|
|
470
|
+
callback(resultCallback, {
|
|
471
|
+
code: ExportResultCode.FAILED,
|
|
472
|
+
error
|
|
473
|
+
});
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const controller = new AbortController();
|
|
477
|
+
let timer;
|
|
478
|
+
const timeout = new Promise((_, reject) => {
|
|
479
|
+
timer = setTimeout(() => {
|
|
480
|
+
controller.abort();
|
|
481
|
+
reject(/* @__PURE__ */ new Error(`telemetry.dev export timed out after ${timeoutMillis}ms`));
|
|
482
|
+
}, timeoutMillis);
|
|
483
|
+
});
|
|
484
|
+
let tracked;
|
|
485
|
+
tracked = Promise.race([Promise.resolve().then(() => send(controller.signal)), timeout]).then(() => callback(resultCallback, { code: ExportResultCode.SUCCESS }), (cause) => {
|
|
486
|
+
const error = cause instanceof Error ? cause : new Error(String(cause));
|
|
487
|
+
reportError(transport.onError, error);
|
|
488
|
+
callback(resultCallback, {
|
|
489
|
+
code: ExportResultCode.FAILED,
|
|
490
|
+
error
|
|
491
|
+
});
|
|
492
|
+
}).finally(() => {
|
|
493
|
+
clearTimeout(timer);
|
|
494
|
+
inFlight.delete(tracked);
|
|
495
|
+
});
|
|
496
|
+
inFlight.add(tracked);
|
|
497
|
+
};
|
|
498
|
+
const forceFlush = () => Promise.all(inFlight).then(() => void 0);
|
|
499
|
+
const shutdown = () => {
|
|
500
|
+
shutDown = true;
|
|
501
|
+
return forceFlush();
|
|
502
|
+
};
|
|
503
|
+
return {
|
|
504
|
+
exportBatch,
|
|
505
|
+
forceFlush,
|
|
506
|
+
shutdown
|
|
507
|
+
};
|
|
508
|
+
};
|
|
349
509
|
const MAX_BODY_BYTES = 35e5;
|
|
350
510
|
function createOtlpBatchSender(serializer, target, transport, label) {
|
|
351
|
-
const send = async (items) => {
|
|
511
|
+
const send = async (items, signal) => {
|
|
352
512
|
const body = serializer.serializeRequest(items);
|
|
353
513
|
if (!body || body.byteLength === 0) return;
|
|
354
514
|
if (body.byteLength > MAX_BODY_BYTES) {
|
|
355
515
|
if (items.length > 1) {
|
|
356
516
|
const mid = Math.ceil(items.length / 2);
|
|
357
|
-
await send(items.slice(0, mid));
|
|
358
|
-
await send(items.slice(mid));
|
|
517
|
+
await send(items.slice(0, mid), signal);
|
|
518
|
+
await send(items.slice(mid), signal);
|
|
359
519
|
return;
|
|
360
520
|
}
|
|
361
521
|
reportError(transport.onError, /* @__PURE__ */ new Error(`telemetry.dev: ${label} record exceeds max export size, dropped`));
|
|
362
522
|
return;
|
|
363
523
|
}
|
|
364
|
-
|
|
365
|
-
const headers = contentEncoding ? {
|
|
366
|
-
...target.headers,
|
|
367
|
-
"content-encoding": contentEncoding
|
|
368
|
-
} : target.headers;
|
|
369
|
-
const res = await postOtlp({
|
|
370
|
-
fetchImpl: transport.fetchImpl,
|
|
371
|
-
url: target.url,
|
|
372
|
-
headers,
|
|
373
|
-
body: finalBody
|
|
374
|
-
});
|
|
375
|
-
if (!res.ok) throw new Error(`telemetry.dev ${label} ingest failed: ${res.status}`);
|
|
524
|
+
await postSerialized(body, target, transport, label, postOtlp, signal);
|
|
376
525
|
};
|
|
377
526
|
return send;
|
|
378
527
|
}
|
|
379
528
|
function createTraceExporter(target, transport) {
|
|
380
529
|
const send = createOtlpBatchSender(ProtobufTraceSerializer, target, transport, "trace");
|
|
530
|
+
const lifecycle = createExportLifecycle(transport);
|
|
381
531
|
return {
|
|
382
532
|
export(spans, resultCallback) {
|
|
383
|
-
|
|
384
|
-
reportError(transport.onError, error);
|
|
385
|
-
resultCallback({
|
|
386
|
-
code: ExportResultCode.FAILED,
|
|
387
|
-
error: error instanceof Error ? error : void 0
|
|
388
|
-
});
|
|
389
|
-
});
|
|
533
|
+
lifecycle.exportBatch((signal) => send(spans, signal), resultCallback);
|
|
390
534
|
},
|
|
391
|
-
forceFlush:
|
|
392
|
-
shutdown:
|
|
535
|
+
forceFlush: lifecycle.forceFlush,
|
|
536
|
+
shutdown: lifecycle.shutdown
|
|
393
537
|
};
|
|
394
538
|
}
|
|
395
539
|
function createLogExporter(target, transport) {
|
|
396
540
|
const send = createOtlpBatchSender(ProtobufLogsSerializer, target, transport, "log");
|
|
541
|
+
const lifecycle = createExportLifecycle(transport);
|
|
397
542
|
return {
|
|
398
543
|
export(logs, resultCallback) {
|
|
399
|
-
|
|
400
|
-
reportError(transport.onError, error);
|
|
401
|
-
resultCallback({
|
|
402
|
-
code: ExportResultCode.FAILED,
|
|
403
|
-
error: error instanceof Error ? error : void 0
|
|
404
|
-
});
|
|
405
|
-
});
|
|
544
|
+
lifecycle.exportBatch((signal) => send(logs, signal), resultCallback);
|
|
406
545
|
},
|
|
407
|
-
forceFlush:
|
|
408
|
-
shutdown:
|
|
546
|
+
forceFlush: lifecycle.forceFlush,
|
|
547
|
+
shutdown: lifecycle.shutdown
|
|
409
548
|
};
|
|
410
549
|
}
|
|
411
550
|
function createMetricExporter(target, transport) {
|
|
412
|
-
const
|
|
551
|
+
const lifecycle = createExportLifecycle(transport);
|
|
413
552
|
return {
|
|
414
553
|
export(resourceMetrics, resultCallback) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
const body = ProtobufMetricsSerializer.serializeRequest(resourceMetrics);
|
|
420
|
-
if (!body || body.byteLength === 0) {
|
|
421
|
-
resultCallback({ code: 0 });
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
maybeGzip(body).then(({ body: finalBody, contentEncoding }) => {
|
|
425
|
-
const headers = contentEncoding ? {
|
|
426
|
-
...target.headers,
|
|
427
|
-
"content-encoding": contentEncoding
|
|
428
|
-
} : target.headers;
|
|
429
|
-
return fetchImpl(target.url, {
|
|
430
|
-
method: "POST",
|
|
431
|
-
headers,
|
|
432
|
-
body: finalBody
|
|
433
|
-
});
|
|
434
|
-
}).then((res) => {
|
|
435
|
-
if (!res.ok) {
|
|
436
|
-
const error = /* @__PURE__ */ new Error(`telemetry.dev metric ingest failed: ${res.status}`);
|
|
437
|
-
reportError(onError, error);
|
|
438
|
-
resultCallback({
|
|
439
|
-
code: 1,
|
|
440
|
-
error
|
|
441
|
-
});
|
|
442
|
-
return;
|
|
443
|
-
}
|
|
444
|
-
resultCallback({ code: 0 });
|
|
445
|
-
}).catch((error) => {
|
|
446
|
-
reportError(onError, error);
|
|
447
|
-
resultCallback({
|
|
448
|
-
code: 1,
|
|
449
|
-
error: error instanceof Error ? error : void 0
|
|
450
|
-
});
|
|
451
|
-
});
|
|
554
|
+
lifecycle.exportBatch(async (signal) => {
|
|
555
|
+
const body = resourceMetrics.scopeMetrics.some((scope) => scope.metrics.some((metric) => metric.dataPoints.length > 0)) ? ProtobufMetricsSerializer.serializeRequest(resourceMetrics) : void 0;
|
|
556
|
+
if (body !== void 0 && body.byteLength > 0) await postSerialized(body, target, transport, "metric", postOtlp, signal);
|
|
557
|
+
}, resultCallback);
|
|
452
558
|
},
|
|
453
559
|
selectAggregationTemporality: () => AggregationTemporality.DELTA,
|
|
454
|
-
forceFlush:
|
|
455
|
-
shutdown:
|
|
560
|
+
forceFlush: lifecycle.forceFlush,
|
|
561
|
+
shutdown: lifecycle.shutdown
|
|
456
562
|
};
|
|
457
563
|
}
|
|
458
|
-
function otlpHeaders(apiKey) {
|
|
564
|
+
function otlpHeaders(apiKey, sdkName = "@telemetry-dev/otel") {
|
|
459
565
|
return {
|
|
460
566
|
"content-type": "application/x-protobuf",
|
|
461
|
-
authorization: `Bearer ${apiKey}
|
|
567
|
+
authorization: `Bearer ${apiKey}`,
|
|
568
|
+
"x-telemetry-dev-sdk": sdkName
|
|
462
569
|
};
|
|
463
570
|
}
|
|
464
571
|
//#endregion
|
|
@@ -477,7 +584,8 @@ var TelemetrySpanProcessor = class {
|
|
|
477
584
|
const baseUrl = (options.baseUrl ?? env.TELEMETRY_DEV_BASE_URL ?? "https://ingest.telemetry.dev").replace(/\/+$/, "");
|
|
478
585
|
const transport = {
|
|
479
586
|
fetchImpl: options.fetch ?? globalThis.fetch,
|
|
480
|
-
onError: options.onError
|
|
587
|
+
onError: options.onError,
|
|
588
|
+
exportTimeoutMillis: options.batch?.exportTimeoutMillis
|
|
481
589
|
};
|
|
482
590
|
const exporter = options.spanExporter ?? (apiKey ? createTraceExporter({
|
|
483
591
|
url: `${baseUrl}/v1/traces`,
|
|
@@ -546,8 +654,272 @@ function createTelemetrySpanExporter(options = {}) {
|
|
|
546
654
|
headers: otlpHeaders(apiKey)
|
|
547
655
|
}, {
|
|
548
656
|
fetchImpl: options.fetch ?? globalThis.fetch,
|
|
549
|
-
onError: options.onError
|
|
657
|
+
onError: options.onError,
|
|
658
|
+
exportTimeoutMillis: options.exportTimeoutMillis
|
|
550
659
|
});
|
|
551
660
|
}
|
|
552
661
|
//#endregion
|
|
553
|
-
|
|
662
|
+
//#region src/session.ts
|
|
663
|
+
const SESSION_PARENTS = /* @__PURE__ */ new WeakMap();
|
|
664
|
+
/**
|
|
665
|
+
* Install on the provider used with withSessionParent or sessionRootTracerProvider.
|
|
666
|
+
* Pass the provider's configured sampler explicitly; OTel cannot read it back from a provider.
|
|
667
|
+
* Without an argument, uses OTEL_TRACES_SAMPLER / OTEL_TRACES_SAMPLER_ARG defaults.
|
|
668
|
+
*/
|
|
669
|
+
function sessionSampler(inner = samplerFromEnv()) {
|
|
670
|
+
return {
|
|
671
|
+
shouldSample(ctx, traceId, name, kind, attributes, links) {
|
|
672
|
+
const parent = trace.getSpan(ctx);
|
|
673
|
+
const original = parent && SESSION_PARENTS.get(parent);
|
|
674
|
+
if (original) {
|
|
675
|
+
const span = trace.getSpan(original);
|
|
676
|
+
ctx = span ? trace.setSpan(ctx, span) : trace.deleteSpan(ctx);
|
|
677
|
+
}
|
|
678
|
+
return inner.shouldSample(ctx, traceId, name, kind, attributes, links);
|
|
679
|
+
},
|
|
680
|
+
toString: () => `SessionSampler{${inner.toString()}}`
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
function samplerFromEnv() {
|
|
684
|
+
const name = getStringFromEnv("OTEL_TRACES_SAMPLER") ?? "parentbased_always_on";
|
|
685
|
+
let root;
|
|
686
|
+
switch (name) {
|
|
687
|
+
case "always_off":
|
|
688
|
+
case "parentbased_always_off":
|
|
689
|
+
root = new AlwaysOffSampler();
|
|
690
|
+
break;
|
|
691
|
+
case "traceidratio":
|
|
692
|
+
case "parentbased_traceidratio": {
|
|
693
|
+
const ratio = getNumberFromEnv("OTEL_TRACES_SAMPLER_ARG");
|
|
694
|
+
const valid = ratio !== void 0 && Number.isFinite(ratio) && ratio >= 0 && ratio <= 1;
|
|
695
|
+
if (!valid) diag$1.error("Invalid OTEL_TRACES_SAMPLER_ARG; using 1.");
|
|
696
|
+
root = new TraceIdRatioBasedSampler(valid ? ratio : 1);
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
case "always_on":
|
|
700
|
+
case "parentbased_always_on":
|
|
701
|
+
root = new AlwaysOnSampler();
|
|
702
|
+
break;
|
|
703
|
+
default:
|
|
704
|
+
diag$1.error(`Invalid OTEL_TRACES_SAMPLER "${name}"; using parentbased_always_on.`);
|
|
705
|
+
return new ParentBasedSampler({ root: new AlwaysOnSampler() });
|
|
706
|
+
}
|
|
707
|
+
return name.startsWith("parentbased_") ? new ParentBasedSampler({ root }) : root;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Deterministic remote parent for a session: every root span of the session joins one trace
|
|
711
|
+
* (`traceId = SHA-256(apiKey ‖ 0x00 ‖ sessionId)[0:16]`, parent `spanId = digest[16:24]`).
|
|
712
|
+
* The API key is part of the input so two projects with the same session id never share a trace
|
|
713
|
+
* id. No span is ever emitted for the parent. Its flags are not a sampling decision:
|
|
714
|
+
* use withSessionParent and install sessionSampler on the provider. Python mirrors these IDs.
|
|
715
|
+
*/
|
|
716
|
+
function sessionSpanContext(apiKey, sessionId) {
|
|
717
|
+
const digest = sha256(new TextEncoder().encode(`${apiKey ?? ""}\0${sessionId}`));
|
|
718
|
+
return {
|
|
719
|
+
traceId: hex(digest.subarray(0, 16)),
|
|
720
|
+
spanId: hex(digest.subarray(16, 24)),
|
|
721
|
+
traceFlags: TraceFlags.NONE,
|
|
722
|
+
isRemote: true
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Wraps a provider so that spans `sessionRootOf` recognizes are parented under the session
|
|
727
|
+
* parent even when a parent is active. Frameworks that run each turn inside their own
|
|
728
|
+
* engine span (eve's "workflow" scope) otherwise give every turn its own trace.
|
|
729
|
+
* The inner provider MUST use sessionSampler(configuredSampler) to retain sampling policy.
|
|
730
|
+
*/
|
|
731
|
+
function sessionRootTracerProvider(inner, apiKey, sessionRootOf, onError) {
|
|
732
|
+
const reparent = (name, options, ctx) => {
|
|
733
|
+
if (!apiKey) return ctx;
|
|
734
|
+
try {
|
|
735
|
+
const sessionId = sessionRootOf(name, options?.attributes ?? {});
|
|
736
|
+
return sessionId ? setSessionParent(options?.root ? trace.deleteSpan(ctx) : ctx, sessionId, apiKey) : ctx;
|
|
737
|
+
} catch (error) {
|
|
738
|
+
reportError(onError, error);
|
|
739
|
+
return ctx;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
return { getTracer(name, version, options) {
|
|
743
|
+
const tracer = inner.getTracer(name, version, options);
|
|
744
|
+
return {
|
|
745
|
+
startSpan: (spanName, spanOptions, ctx = context.active()) => {
|
|
746
|
+
const parent = reparent(spanName, spanOptions, ctx);
|
|
747
|
+
return tracer.startSpan(spanName, parent !== ctx && spanOptions?.root ? {
|
|
748
|
+
...spanOptions,
|
|
749
|
+
root: false
|
|
750
|
+
} : spanOptions, parent);
|
|
751
|
+
},
|
|
752
|
+
startActiveSpan(spanName, ...args) {
|
|
753
|
+
const fn = args.at(-1);
|
|
754
|
+
const spanOptions = args.length > 1 ? args[0] : void 0;
|
|
755
|
+
const ctx = args.length > 2 ? args[1] : context.active();
|
|
756
|
+
const parent = reparent(spanName, spanOptions, ctx);
|
|
757
|
+
return tracer.startActiveSpan(spanName, parent !== ctx && spanOptions?.root ? {
|
|
758
|
+
...spanOptions,
|
|
759
|
+
root: false
|
|
760
|
+
} : spanOptions ?? {}, parent, fn);
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
} };
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Parent a would-be root under the session; nested spans keep their real parent.
|
|
767
|
+
* Requires sessionSampler(configuredSampler) on the provider that starts the span.
|
|
768
|
+
*/
|
|
769
|
+
function withSessionParent(ctx, sessionId, apiKey) {
|
|
770
|
+
if (!apiKey || !sessionId) return ctx;
|
|
771
|
+
const current = trace.getSpanContext(ctx);
|
|
772
|
+
if (current && isSpanContextValid(current)) return ctx;
|
|
773
|
+
return setSessionParent(ctx, sessionId, apiKey);
|
|
774
|
+
}
|
|
775
|
+
function setSessionParent(ctx, sessionId, apiKey) {
|
|
776
|
+
const parent = trace.wrapSpanContext({
|
|
777
|
+
...sessionSpanContext(apiKey, sessionId),
|
|
778
|
+
traceState: trace.getSpanContext(ctx)?.traceState
|
|
779
|
+
});
|
|
780
|
+
SESSION_PARENTS.set(parent, ctx);
|
|
781
|
+
return trace.setSpan(ctx, parent);
|
|
782
|
+
}
|
|
783
|
+
function sessionIdOf(ctx, attributes) {
|
|
784
|
+
const explicit = attributes?.["gen_ai.conversation.id"];
|
|
785
|
+
if (typeof explicit === "string") return explicit;
|
|
786
|
+
const propagated = propagatedFromContext(ctx)?.["gen_ai.conversation.id"];
|
|
787
|
+
return typeof propagated === "string" ? propagated : void 0;
|
|
788
|
+
}
|
|
789
|
+
function hex(bytes) {
|
|
790
|
+
let s = "";
|
|
791
|
+
for (const b of bytes) s += b.toString(16).padStart(2, "0");
|
|
792
|
+
return s;
|
|
793
|
+
}
|
|
794
|
+
const K = new Uint32Array([
|
|
795
|
+
1116352408,
|
|
796
|
+
1899447441,
|
|
797
|
+
3049323471,
|
|
798
|
+
3921009573,
|
|
799
|
+
961987163,
|
|
800
|
+
1508970993,
|
|
801
|
+
2453635748,
|
|
802
|
+
2870763221,
|
|
803
|
+
3624381080,
|
|
804
|
+
310598401,
|
|
805
|
+
607225278,
|
|
806
|
+
1426881987,
|
|
807
|
+
1925078388,
|
|
808
|
+
2162078206,
|
|
809
|
+
2614888103,
|
|
810
|
+
3248222580,
|
|
811
|
+
3835390401,
|
|
812
|
+
4022224774,
|
|
813
|
+
264347078,
|
|
814
|
+
604807628,
|
|
815
|
+
770255983,
|
|
816
|
+
1249150122,
|
|
817
|
+
1555081692,
|
|
818
|
+
1996064986,
|
|
819
|
+
2554220882,
|
|
820
|
+
2821834349,
|
|
821
|
+
2952996808,
|
|
822
|
+
3210313671,
|
|
823
|
+
3336571891,
|
|
824
|
+
3584528711,
|
|
825
|
+
113926993,
|
|
826
|
+
338241895,
|
|
827
|
+
666307205,
|
|
828
|
+
773529912,
|
|
829
|
+
1294757372,
|
|
830
|
+
1396182291,
|
|
831
|
+
1695183700,
|
|
832
|
+
1986661051,
|
|
833
|
+
2177026350,
|
|
834
|
+
2456956037,
|
|
835
|
+
2730485921,
|
|
836
|
+
2820302411,
|
|
837
|
+
3259730800,
|
|
838
|
+
3345764771,
|
|
839
|
+
3516065817,
|
|
840
|
+
3600352804,
|
|
841
|
+
4094571909,
|
|
842
|
+
275423344,
|
|
843
|
+
430227734,
|
|
844
|
+
506948616,
|
|
845
|
+
659060556,
|
|
846
|
+
883997877,
|
|
847
|
+
958139571,
|
|
848
|
+
1322822218,
|
|
849
|
+
1537002063,
|
|
850
|
+
1747873779,
|
|
851
|
+
1955562222,
|
|
852
|
+
2024104815,
|
|
853
|
+
2227730452,
|
|
854
|
+
2361852424,
|
|
855
|
+
2428436474,
|
|
856
|
+
2756734187,
|
|
857
|
+
3204031479,
|
|
858
|
+
3329325298
|
|
859
|
+
]);
|
|
860
|
+
const rotr = (x, n) => x >>> n | x << 32 - n;
|
|
861
|
+
/** FIPS 180-4 SHA-256 in plain JS: the package must stay synchronous and runtime-neutral. */
|
|
862
|
+
function sha256(bytes) {
|
|
863
|
+
const h = new Uint32Array([
|
|
864
|
+
1779033703,
|
|
865
|
+
3144134277,
|
|
866
|
+
1013904242,
|
|
867
|
+
2773480762,
|
|
868
|
+
1359893119,
|
|
869
|
+
2600822924,
|
|
870
|
+
528734635,
|
|
871
|
+
1541459225
|
|
872
|
+
]);
|
|
873
|
+
const padded = new Uint8Array(Math.ceil((bytes.length + 9) / 64) * 64);
|
|
874
|
+
padded.set(bytes);
|
|
875
|
+
padded[bytes.length] = 128;
|
|
876
|
+
const view = new DataView(padded.buffer);
|
|
877
|
+
const bitLen = bytes.length * 8;
|
|
878
|
+
view.setUint32(padded.length - 8, Math.floor(bitLen / 4294967296));
|
|
879
|
+
view.setUint32(padded.length - 4, bitLen >>> 0);
|
|
880
|
+
const w = new Uint32Array(64);
|
|
881
|
+
for (let off = 0; off < padded.length; off += 64) {
|
|
882
|
+
for (let i = 0; i < 16; i++) w[i] = view.getUint32(off + i * 4);
|
|
883
|
+
for (let i = 16; i < 64; i++) {
|
|
884
|
+
const x = w[i - 15];
|
|
885
|
+
const y = w[i - 2];
|
|
886
|
+
const s0 = rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3;
|
|
887
|
+
const s1 = rotr(y, 17) ^ rotr(y, 19) ^ y >>> 10;
|
|
888
|
+
w[i] = w[i - 16] + s0 + w[i - 7] + s1 >>> 0;
|
|
889
|
+
}
|
|
890
|
+
let a = h[0];
|
|
891
|
+
let b = h[1];
|
|
892
|
+
let c = h[2];
|
|
893
|
+
let d = h[3];
|
|
894
|
+
let e = h[4];
|
|
895
|
+
let f = h[5];
|
|
896
|
+
let g = h[6];
|
|
897
|
+
let hh = h[7];
|
|
898
|
+
for (let i = 0; i < 64; i++) {
|
|
899
|
+
const t1 = hh + (rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25)) + (e & f ^ ~e & g) + K[i] + w[i] >>> 0;
|
|
900
|
+
const t2 = (rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22)) + (a & b ^ a & c ^ b & c) >>> 0;
|
|
901
|
+
hh = g;
|
|
902
|
+
g = f;
|
|
903
|
+
f = e;
|
|
904
|
+
e = d + t1 >>> 0;
|
|
905
|
+
d = c;
|
|
906
|
+
c = b;
|
|
907
|
+
b = a;
|
|
908
|
+
a = t1 + t2 >>> 0;
|
|
909
|
+
}
|
|
910
|
+
h[0] = h[0] + a >>> 0;
|
|
911
|
+
h[1] = h[1] + b >>> 0;
|
|
912
|
+
h[2] = h[2] + c >>> 0;
|
|
913
|
+
h[3] = h[3] + d >>> 0;
|
|
914
|
+
h[4] = h[4] + e >>> 0;
|
|
915
|
+
h[5] = h[5] + f >>> 0;
|
|
916
|
+
h[6] = h[6] + g >>> 0;
|
|
917
|
+
h[7] = h[7] + hh >>> 0;
|
|
918
|
+
}
|
|
919
|
+
const out = new Uint8Array(32);
|
|
920
|
+
const outView = new DataView(out.buffer);
|
|
921
|
+
for (let i = 0; i < 8; i++) outView.setUint32(i * 4, h[i]);
|
|
922
|
+
return out;
|
|
923
|
+
}
|
|
924
|
+
//#endregion
|
|
925
|
+
export { AlsContextManager, BATCHED_METRIC_INTERVAL_MS, DEFAULT_BASE_URL, DEFAULT_BATCH, DORMANT_INTERVAL_MS, DURATION_BUCKETS, PROPAGATED_KEY, SCOPE_NAME, SCOPE_VERSION, StampingSpanProcessor, TOKEN_BUCKETS, TelemetrySpanProcessor, activeContext, als, buildPropagatedAttributes, createLogExporter, createMetricExporter, createMetricsPipeline, createTelemetrySpanExporter, createTraceExporter, diag, jsonAttr, maybeGzip, omitUndefined, otlpHeaders, postOtlp, propagateAttributes, propagatedFromContext, reportError, resolveEnv, sessionIdOf, sessionRootTracerProvider, sessionSampler, sessionSpanContext, setLogLevel, sha256, withContext, withSessionParent };
|