@autter/runtime-node 1.5.0 → 1.6.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.
@@ -1,7 +1,8 @@
1
1
  import {
2
2
  noteCompatFeature,
3
- observeIngesterResponse
4
- } from "./chunk-L7HWKMCZ.js";
3
+ observeIngesterResponse,
4
+ sharedSlot
5
+ } from "./chunk-HRDELHZP.js";
5
6
 
6
7
  // ../runtime-core/dist/index.js
7
8
  var SENSITIVE_KEY_PATTERNS = [
@@ -327,8 +328,8 @@ function priority(key) {
327
328
  if (/^(?:exception|autter\.error)\./.test(key)) return 1;
328
329
  return 0;
329
330
  }
330
- function boundContext(attributes = {}, redact2) {
331
- const redacted = redact2 ? redact2(attributes) : redactAttributes(attributes);
331
+ function boundContext(attributes = {}, redact3) {
332
+ const redacted = redact3 ? redact3(attributes) : redactAttributes(attributes);
332
333
  let budget = 512;
333
334
  let characters = 16384;
334
335
  let truncated = false;
@@ -365,14 +366,14 @@ function boundContext(attributes = {}, redact2) {
365
366
  if (truncated) result["autter.context.truncated"] = true;
366
367
  return result;
367
368
  }
368
- function userContext(attributes = {}, redact2) {
369
+ function userContext(attributes = {}, redact3) {
369
370
  return Object.fromEntries(
370
- Object.entries(boundContext(attributes, redact2)).filter(
371
+ Object.entries(boundContext(attributes, redact3)).filter(
371
372
  ([key]) => !RESERVED_KEY.test(key)
372
373
  )
373
374
  );
374
375
  }
375
- function mergeContext(current, next = {}, redact2) {
376
+ function mergeContext(current, next = {}, redact3) {
376
377
  const merge2 = (left, right, depth) => {
377
378
  const result = { ...left };
378
379
  for (const [key, value] of Object.entries(right)) {
@@ -381,7 +382,7 @@ function mergeContext(current, next = {}, redact2) {
381
382
  }
382
383
  return result;
383
384
  };
384
- return userContext(merge2(current, userContext(next, redact2), 0), redact2);
385
+ return userContext(merge2(current, userContext(next, redact3), 0), redact3);
385
386
  }
386
387
  function toSpanAttributes(context) {
387
388
  return Object.fromEntries(
@@ -473,6 +474,17 @@ function defineRuntimeErrors(namespace, catalog) {
473
474
  }
474
475
  return out;
475
476
  }
477
+ function isError(value) {
478
+ if (!value || typeof value !== "object") return false;
479
+ try {
480
+ if (value instanceof Error) return true;
481
+ const native = Error.isError;
482
+ if (typeof native === "function") return native(value) === true;
483
+ return Object.prototype.toString.call(value) === "[object Error]";
484
+ } catch {
485
+ return false;
486
+ }
487
+ }
476
488
  function isRuntimeErrorLike(err) {
477
489
  if (err instanceof RuntimeError) return true;
478
490
  if (!err || typeof err !== "object") return false;
@@ -523,15 +535,15 @@ function errorAttributes(err, options = {}) {
523
535
  for (let n = 1; n <= CAUSE_DEPTH && cause !== void 0 && cause !== null; n++) {
524
536
  if (seen.has(cause)) break;
525
537
  seen.add(cause);
526
- const isError = typeof cause === "object";
527
- const type = isError ? String(field(cause, "name") ?? cause.constructor?.name ?? "Error") : typeof cause;
528
- const message = isError ? field(cause, "message") : cause;
538
+ const isError2 = typeof cause === "object";
539
+ const type = isError2 ? String(field(cause, "name") ?? cause.constructor?.name ?? "Error") : typeof cause;
540
+ const message = isError2 ? field(cause, "message") : cause;
529
541
  out[`exception.cause.${n}.type`] = type.slice(0, 200);
530
542
  out[`exception.cause.${n}.message`] = String(message ?? "").slice(0, 1e3);
531
543
  const causeCode = field(cause, "code");
532
544
  if (typeof causeCode === "string" && causeCode)
533
545
  out[`exception.cause.${n}.code`] = causeCode.slice(0, CODE_MAX_LENGTH);
534
- cause = isError ? field(cause, "cause") : void 0;
546
+ cause = isError2 ? field(cause, "cause") : void 0;
535
547
  }
536
548
  }
537
549
  return out;
@@ -577,19 +589,19 @@ function createInlineLogState(startedAt) {
577
589
  level: "info"
578
590
  };
579
591
  }
580
- function appendInlineLog(state, level, message, attrs, redact2, now = Date.now()) {
592
+ function appendInlineLog(state, level, message, attrs, redact3, now = Date.now()) {
581
593
  state.level = maxLevel(state.level, level);
582
594
  if (state.logs.length >= INLINE_LOG_LIMIT) {
583
595
  state.logsTruncated = true;
584
596
  return;
585
597
  }
586
- const text = String(boundContext({ message }, redact2).message ?? "").slice(
598
+ const text = String(boundContext({ message }, redact3).message ?? "").slice(
587
599
  0,
588
600
  INLINE_MESSAGE_MAX
589
601
  );
590
602
  let small;
591
603
  if (attrs && Object.keys(attrs).length) {
592
- small = boundContext(attrs, redact2);
604
+ small = boundContext(attrs, redact3);
593
605
  const encoded = JSON.stringify(small);
594
606
  if (encoded.length > 1e3) small = { "autter.context.truncated": true };
595
607
  }
@@ -806,6 +818,172 @@ function parseUserAgent(ua) {
806
818
  const device = !value ? "unknown" : bot ? "bot" : /iPad|Tablet/.test(value) || /Android/.test(value) && !/Mobile/.test(value) ? "tablet" : /Mobi|iPhone|iPod|Android/.test(value) ? "mobile" : "desktop";
807
819
  return { browser, os, device, bot };
808
820
  }
821
+ var BREADCRUMBS_ATTRIBUTE = "autter.breadcrumbs";
822
+ var MAX_BREADCRUMBS = 30;
823
+ var MAX_MESSAGE = 500;
824
+ var MAX_DATA_KEYS = 10;
825
+ var MAX_DATA_STRING = 200;
826
+ var BREADCRUMBS_ATTRIBUTE_MAX_CHARS = 16 * 1024;
827
+ var MASK = "[redacted]";
828
+ var TYPES = /* @__PURE__ */ new Set(["console", "request", "navigation", "click", "log", "custom"]);
829
+ var LEVELS = {
830
+ debug: "debug",
831
+ trace: "debug",
832
+ info: "info",
833
+ log: "info",
834
+ warning: "warning",
835
+ warn: "warning",
836
+ error: "error",
837
+ fatal: "error"
838
+ };
839
+ var UNSAFE_KEY = /^(?:__proto__|constructor|prototype)$/;
840
+ function scrubber(redact3) {
841
+ return (value, max) => {
842
+ const scrubbed = redact3({ message: value.slice(0, max + 512) }).message;
843
+ return stripUrlQueries(typeof scrubbed === "string" ? scrubbed : "").slice(0, max);
844
+ };
845
+ }
846
+ function cleanData(raw, redact3) {
847
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
848
+ const picked = {};
849
+ let count = 0;
850
+ for (const key of Object.keys(raw).slice(0, 64)) {
851
+ if (count >= MAX_DATA_KEYS) break;
852
+ if (!key || UNSAFE_KEY.test(key)) continue;
853
+ const value = raw[key];
854
+ const name = key.slice(0, 100);
855
+ if (typeof value === "string") {
856
+ const text = /(?:url|path|route|target)$/i.test(name) ? value.split(/[?#]/)[0] : value;
857
+ picked[name] = text.slice(0, MAX_DATA_STRING + 512);
858
+ } else if (typeof value === "number") {
859
+ if (!Number.isFinite(value)) continue;
860
+ picked[name] = value;
861
+ } else if (typeof value === "boolean" || value === null) {
862
+ picked[name] = value;
863
+ } else {
864
+ continue;
865
+ }
866
+ count++;
867
+ }
868
+ if (count === 0) return void 0;
869
+ const redacted = redact3(picked);
870
+ const out = {};
871
+ for (const key of Object.keys(picked)) {
872
+ const value = key in redacted ? redacted[key] : picked[key];
873
+ out[key] = typeof value === "string" ? stripUrlQueries(value).slice(0, MAX_DATA_STRING) : value;
874
+ }
875
+ return out;
876
+ }
877
+ function timestamp(value) {
878
+ if (value === void 0) return (/* @__PURE__ */ new Date()).toISOString();
879
+ if (typeof value !== "string" && typeof value !== "number" && !(value instanceof Date)) return null;
880
+ const at = new Date(value);
881
+ return Number.isNaN(at.getTime()) ? null : at.toISOString();
882
+ }
883
+ function normalizeBreadcrumb(input, redact3, options = {}) {
884
+ try {
885
+ if (!input || typeof input !== "object" || Array.isArray(input)) return null;
886
+ const raw = input;
887
+ if (options.requireAt && raw.at === void 0) return null;
888
+ const at = timestamp(raw.at);
889
+ if (!at) return null;
890
+ const rawMessage = raw.message;
891
+ const message = typeof rawMessage === "string" ? rawMessage : typeof rawMessage === "number" || typeof rawMessage === "boolean" ? String(rawMessage) : "";
892
+ const data = cleanData(raw.data, redact3);
893
+ if (!message && !data) return null;
894
+ const type = typeof raw.type === "string" && TYPES.has(raw.type) ? raw.type : "custom";
895
+ const level = typeof raw.level === "string" ? LEVELS[raw.level.toLowerCase()] : void 0;
896
+ return {
897
+ type,
898
+ at,
899
+ ...level ? { level } : {},
900
+ message: scrubber(redact3)(message, MAX_MESSAGE),
901
+ ...data ? { data } : {}
902
+ };
903
+ } catch {
904
+ return null;
905
+ }
906
+ }
907
+ function sanitizeBreadcrumbs(raw, redact3) {
908
+ const out = [];
909
+ try {
910
+ if (!Array.isArray(raw)) return void 0;
911
+ const first = Math.max(0, raw.length - 200);
912
+ for (let i = raw.length - 1; i >= first && out.length < MAX_BREADCRUMBS; i--) {
913
+ const crumb = normalizeBreadcrumb(raw[i], redact3, { requireAt: true });
914
+ if (crumb) out.push(crumb);
915
+ }
916
+ } catch {
917
+ }
918
+ return out.length ? out.reverse() : void 0;
919
+ }
920
+ var BreadcrumbTrail = class {
921
+ constructor(max = MAX_BREADCRUMBS) {
922
+ this.max = max;
923
+ }
924
+ max;
925
+ items = [];
926
+ add(crumb) {
927
+ this.items.push(crumb);
928
+ if (this.items.length > this.max) this.items.splice(0, this.items.length - this.max);
929
+ }
930
+ /** Copies, oldest first. */
931
+ list() {
932
+ return this.items.map((crumb) => ({ ...crumb, ...crumb.data ? { data: { ...crumb.data } } : {} }));
933
+ }
934
+ get size() {
935
+ return this.items.length;
936
+ }
937
+ clear() {
938
+ this.items = [];
939
+ }
940
+ };
941
+ function breadcrumbsAttribute(list, scrub, maxChars = BREADCRUMBS_ATTRIBUTE_MAX_CHARS) {
942
+ const parts = [];
943
+ let chars = 2;
944
+ for (let i = list.length - 1; i >= 0 && parts.length < MAX_BREADCRUMBS; i--) {
945
+ const crumb = list[i];
946
+ let json = JSON.stringify(crumb);
947
+ if (scrub(json) !== json) {
948
+ json = JSON.stringify({ type: crumb.type, at: crumb.at, ...crumb.level ? { level: crumb.level } : {}, message: MASK });
949
+ }
950
+ if (chars + json.length + 1 > maxChars) break;
951
+ chars += json.length + 1;
952
+ parts.push(json);
953
+ }
954
+ if (parts.length === 0) return void 0;
955
+ const out = `[${parts.reverse().join(",")}]`;
956
+ return scrub(out) === out ? out : void 0;
957
+ }
958
+ var FINGERPRINT_ATTRIBUTE = "autter.fingerprint";
959
+ var FINGERPRINT_MAX_VALUES = 10;
960
+ var FINGERPRINT_MAX_LENGTH = 200;
961
+ function fingerprintValues(value, scrub = (text) => text) {
962
+ const list = Array.isArray(value) ? value : [value];
963
+ const values = list.filter((item) => typeof item === "string" && item !== "").slice(0, FINGERPRINT_MAX_VALUES).map((item) => scrub(item.slice(0, FINGERPRINT_MAX_LENGTH + 512)).slice(0, FINGERPRINT_MAX_LENGTH)).filter((item) => item !== "");
964
+ return values.length > 0 ? values : void 0;
965
+ }
966
+ function fingerprintEventAttributes(value, scrub) {
967
+ const values = fingerprintValues(value, scrub);
968
+ return values ? { [FINGERPRINT_ATTRIBUTE]: JSON.stringify(values) } : {};
969
+ }
970
+ function splitFingerprint(attributes) {
971
+ if (!attributes || typeof attributes !== "object" || !("fingerprint" in attributes)) {
972
+ return { attributes: attributes ?? void 0, fingerprint: void 0 };
973
+ }
974
+ const { fingerprint, ...rest } = attributes;
975
+ return { attributes: rest, fingerprint };
976
+ }
977
+
978
+ // src/breadcrumbs.ts
979
+ import { trace as trace2 } from "@opentelemetry/api";
980
+
981
+ // src/logger.ts
982
+ import { AsyncLocalStorage } from "async_hooks";
983
+ import { randomUUID } from "crypto";
984
+ import {
985
+ trace
986
+ } from "@opentelemetry/api";
809
987
 
810
988
  // src/sinks.ts
811
989
  import {
@@ -816,7 +994,7 @@ import {
816
994
  unlinkSync
817
995
  } from "fs";
818
996
  import { join, resolve } from "path";
819
- var sinkStats = { dropped: 0 };
997
+ var sinkStats = sharedSlot("sinks", () => ({ dropped: 0 }));
820
998
  function otlpSink(options = {}) {
821
999
  let context = null;
822
1000
  let queue = [];
@@ -1185,23 +1363,22 @@ function memorySink() {
1185
1363
  }
1186
1364
 
1187
1365
  // src/logger.ts
1188
- import { AsyncLocalStorage } from "async_hooks";
1189
- import { randomUUID } from "crypto";
1190
- import {
1191
- trace
1192
- } from "@opentelemetry/api";
1193
- var local = new AsyncLocalStorage();
1194
- var config = null;
1195
- var sinks = [];
1196
- var extraSinks = /* @__PURE__ */ new Set();
1366
+ var shared = sharedSlot("logger", () => ({
1367
+ local: new AsyncLocalStorage(),
1368
+ config: null,
1369
+ sinks: [],
1370
+ extraSinks: /* @__PURE__ */ new Set()
1371
+ }));
1372
+ var local = shared.local;
1373
+ var extraSinks = shared.extraSinks;
1197
1374
  var fallbackConsole = null;
1198
1375
  function redact(attributes) {
1199
- return config?.redact(attributes) ?? redactAttributes(attributes);
1376
+ return shared.config?.redact(attributes) ?? redactAttributes(attributes);
1200
1377
  }
1201
1378
  var safe = (attributes = {}) => boundContext(attributes, redact);
1202
1379
  var merge = (current, next) => mergeContext(current, next, redact);
1203
1380
  function activeSinks() {
1204
- if (config) return extraSinks.size ? [...sinks, ...extraSinks] : sinks;
1381
+ if (shared.config) return extraSinks.size ? [...shared.sinks, ...extraSinks] : shared.sinks;
1205
1382
  if (extraSinks.size) return [...extraSinks];
1206
1383
  fallbackConsole ??= consoleSink();
1207
1384
  return [fallbackConsole];
@@ -1237,9 +1414,10 @@ function emit(level, message, attributes, extra = {}, options = {}) {
1237
1414
  const state = options.state !== void 0 ? options.state ?? void 0 : local.getStore();
1238
1415
  if (state?.sealed) return;
1239
1416
  const summary = extra["autter.event.type"] === "operation";
1240
- if (!summary && LEVEL_SEVERITY[level] < LEVEL_SEVERITY[config?.options?.minLevel ?? "debug"])
1417
+ if (!summary) recordLogBreadcrumb(level, message, attributes, state);
1418
+ if (!summary && LEVEL_SEVERITY[level] < LEVEL_SEVERITY[shared.config?.options?.minLevel ?? "debug"])
1241
1419
  return;
1242
- if (!summary && state && config?.options?.inline !== false) {
1420
+ if (!summary && state && shared.config?.options?.inline !== false) {
1243
1421
  const { "exception.stacktrace": _stack, ...inlineAttrs } = userContext(
1244
1422
  attributes,
1245
1423
  redact
@@ -1259,7 +1437,7 @@ function emit(level, message, attributes, extra = {}, options = {}) {
1259
1437
  ...operationAttributes(state),
1260
1438
  ...user
1261
1439
  };
1262
- const enrichers = config?.options?.enrich;
1440
+ const enrichers = shared.config?.options?.enrich;
1263
1441
  if (summary && state && enrichers?.length) {
1264
1442
  const event2 = { level, message, attributes: raw };
1265
1443
  for (const enrich of enrichers) {
@@ -1287,9 +1465,9 @@ function emit(level, message, attributes, extra = {}, options = {}) {
1287
1465
  }
1288
1466
  }
1289
1467
  function configureRuntimeLogger(next) {
1290
- config = next;
1291
- sinks = defaultSinks(next);
1292
- for (const sink of sinks) {
1468
+ shared.config = next;
1469
+ shared.sinks = defaultSinks(next);
1470
+ for (const sink of shared.sinks) {
1293
1471
  try {
1294
1472
  sink.start?.({
1295
1473
  endpoint: next.endpoint,
@@ -1303,23 +1481,26 @@ function configureRuntimeLogger(next) {
1303
1481
  }
1304
1482
  }
1305
1483
  }
1484
+ function loggerConfigured() {
1485
+ return shared.config;
1486
+ }
1306
1487
  function addRuntimeSink(sink) {
1307
1488
  extraSinks.add(sink);
1308
1489
  return () => extraSinks.delete(sink);
1309
1490
  }
1310
1491
  function notifyException(error, attributes = {}) {
1311
- if (!extraSinks.size && !sinks.some((sink) => sink.exception)) return;
1492
+ if (!extraSinks.size && !shared.sinks.some((sink) => sink.exception)) return;
1312
1493
  const state = local.getStore();
1313
1494
  const record = {
1314
1495
  time: Date.now(),
1315
1496
  error,
1316
- message: error instanceof Error ? error.message : String(error),
1317
- type: error instanceof Error ? error.name : "Error",
1497
+ message: isError(error) ? error.message : String(error),
1498
+ type: isError(error) ? error.name : "Error",
1318
1499
  attributes: { ...attributes, ...errorAttributes(error) },
1319
1500
  ...state ? { operationId: state.id } : {},
1320
1501
  ...state?.requestId ? { requestId: state.requestId } : {}
1321
1502
  };
1322
- for (const sink of [...sinks, ...extraSinks]) {
1503
+ for (const sink of [...shared.sinks, ...extraSinks]) {
1323
1504
  try {
1324
1505
  sink.exception?.(record);
1325
1506
  } catch {
@@ -1328,25 +1509,25 @@ function notifyException(error, attributes = {}) {
1328
1509
  }
1329
1510
  function runtimeLogStats() {
1330
1511
  return {
1331
- buffered: sinks.reduce((sum, sink) => sum + (sink.buffered?.() ?? 0), 0),
1512
+ buffered: shared.sinks.reduce((sum, sink) => sum + (sink.buffered?.() ?? 0), 0),
1332
1513
  dropped: sinkStats.dropped
1333
1514
  };
1334
1515
  }
1335
1516
  async function flushRuntimeLogs() {
1336
- if (!config) return;
1517
+ if (!shared.config) return;
1337
1518
  const results = await Promise.allSettled(
1338
- sinks.map((sink) => Promise.resolve().then(() => sink.flush?.()))
1519
+ shared.sinks.map((sink) => Promise.resolve().then(() => sink.flush?.()))
1339
1520
  );
1340
1521
  const failed = results.find((result) => result.status === "rejected");
1341
1522
  if (failed) throw failed.reason;
1342
1523
  }
1343
1524
  async function shutdownRuntimeLogger() {
1344
- const active = sinks;
1525
+ const active = shared.sinks;
1345
1526
  const results = await Promise.allSettled(
1346
1527
  active.map((sink) => Promise.resolve().then(() => sink.shutdown?.()))
1347
1528
  );
1348
- config = null;
1349
- sinks = [];
1529
+ shared.config = null;
1530
+ shared.sinks = [];
1350
1531
  const failed = results.find((result) => result.status === "rejected");
1351
1532
  if (failed) throw failed.reason;
1352
1533
  }
@@ -1367,11 +1548,11 @@ function createRuntimeLogger(attributes = {}) {
1367
1548
  warn: (message, extra) => emit("warning", message, merge(attrs, extra)),
1368
1549
  error: (error, extra) => emit(
1369
1550
  "error",
1370
- error instanceof Error ? error.message : String(error),
1551
+ isError(error) ? error.message : String(error),
1371
1552
  {
1372
1553
  ...attrs,
1373
1554
  ...extra,
1374
- ...error instanceof Error ? {
1555
+ ...isError(error) ? {
1375
1556
  "exception.type": error.name,
1376
1557
  "exception.stacktrace": error.stack ?? ""
1377
1558
  } : {}
@@ -1382,16 +1563,16 @@ function createRuntimeLogger(attributes = {}) {
1382
1563
  }
1383
1564
  var runtimeLogger = createRuntimeLogger();
1384
1565
  function captureExceptionAsLog(error, attributes = {}) {
1385
- const isError = error instanceof Error;
1386
- const message = isError ? error.message : String(error);
1387
- const stack = isError && error.stack ? error.stack : new Error().stack?.split("\n").filter((line, i) => i === 0 || !/captureException/.test(line)).join("\n") ?? "";
1566
+ const isErr = isError(error);
1567
+ const message = isErr ? error.message : String(error);
1568
+ const stack = isErr && error.stack ? error.stack : new Error().stack?.split("\n").filter((line, i) => i === 0 || !/captureException/.test(line)).join("\n") ?? "";
1388
1569
  noteOperationError(error);
1389
1570
  emit(
1390
1571
  "error",
1391
1572
  message,
1392
1573
  {
1393
1574
  ...attributes,
1394
- "exception.type": isError ? error.name : "Error",
1575
+ "exception.type": isErr ? error.name : "Error",
1395
1576
  "exception.message": message,
1396
1577
  "exception.stacktrace": stack
1397
1578
  },
@@ -1486,7 +1667,7 @@ function emitSummary(state, options = {}) {
1486
1667
  state.message ?? `${state.name}: ${state.outcome}`,
1487
1668
  {
1488
1669
  ...operationAttributes(state),
1489
- ...err instanceof Error ? {
1670
+ ...isError(err) ? {
1490
1671
  "exception.type": err.name,
1491
1672
  "exception.stacktrace": err.stack ?? ""
1492
1673
  } : {}
@@ -1498,7 +1679,7 @@ function emitSummary(state, options = {}) {
1498
1679
  }
1499
1680
  function reportExplicitFailure(state) {
1500
1681
  if (state.outcome === "failed" && state.explicitOutcome && !state.errorThrown)
1501
- config?.reportOutcome(state.name, state.message ?? "Operation failed", {
1682
+ shared.config?.reportOutcome(state.name, state.message ?? "Operation failed", {
1502
1683
  ...traceAttributes(state),
1503
1684
  "autter.outcome.stack": state.reportingStack ?? ""
1504
1685
  });
@@ -1521,6 +1702,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
1521
1702
  ...parent?.requestId ? { requestId: parent.requestId } : {}
1522
1703
  }
1523
1704
  );
1705
+ if (linked && parent) state.breadcrumbs = parent.breadcrumbs ??= new BreadcrumbTrail();
1524
1706
  const operation = operationHandle(state);
1525
1707
  const run = () => local.run(state, async () => {
1526
1708
  let thrown;
@@ -1530,7 +1712,8 @@ function startOperation(name, fn, attributes, options, internal = {}) {
1530
1712
  state.outcome = "failed";
1531
1713
  thrown = error;
1532
1714
  noteOperationError(error, true, state);
1533
- config?.captureThrown?.(error);
1715
+ rememberThrownBreadcrumbs(error, state);
1716
+ shared.config?.captureThrown?.(error);
1534
1717
  throw error;
1535
1718
  } finally {
1536
1719
  trace.getActiveSpan()?.setAttributes({
@@ -1539,7 +1722,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
1539
1722
  "autter.operation.kind": state.kind
1540
1723
  });
1541
1724
  if (state.outcome === "failed" && !thrown)
1542
- config?.reportOutcome(
1725
+ shared.config?.reportOutcome(
1543
1726
  state.name,
1544
1727
  state.message ?? "Operation failed",
1545
1728
  {
@@ -1560,7 +1743,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
1560
1743
  }
1561
1744
  }
1562
1745
  ] : [];
1563
- const promise = config?.run ? config.run(state.name, run, traceAttributes(state), links) : run();
1746
+ const promise = shared.config?.run ? shared.config.run(state.name, run, traceAttributes(state), links) : run();
1564
1747
  if (!options.waitUntil) return promise;
1565
1748
  const waitUntil = options.waitUntil;
1566
1749
  return promise.finally(() => {
@@ -1634,6 +1817,88 @@ var runtimeContext = {
1634
1817
  }
1635
1818
  };
1636
1819
 
1820
+ // src/breadcrumbs.ts
1821
+ var INVALID_TRACE_ID = "00000000000000000000000000000000";
1822
+ var MAX_TRACE_TRAILS = 200;
1823
+ var trails = sharedSlot("breadcrumbs", () => ({
1824
+ byTrace: /* @__PURE__ */ new Map(),
1825
+ process: new BreadcrumbTrail(),
1826
+ thrown: /* @__PURE__ */ new WeakMap()
1827
+ }));
1828
+ var byTrace = trails.byTrace;
1829
+ var processTrail = trails.process;
1830
+ var thrownTrails = trails.thrown;
1831
+ function redact2(attributes) {
1832
+ return loggerConfigured()?.redact(attributes) ?? redactAttributes(attributes);
1833
+ }
1834
+ function trailFor(create, state = local.getStore()) {
1835
+ if (state) {
1836
+ if (!state.breadcrumbs && create) state.breadcrumbs = new BreadcrumbTrail();
1837
+ return state.breadcrumbs;
1838
+ }
1839
+ const traceId = trace2.getActiveSpan()?.spanContext().traceId;
1840
+ if (traceId && traceId !== INVALID_TRACE_ID) {
1841
+ let trail = byTrace.get(traceId);
1842
+ if (!trail && create) {
1843
+ trail = new BreadcrumbTrail();
1844
+ byTrace.set(traceId, trail);
1845
+ if (byTrace.size > MAX_TRACE_TRAILS) {
1846
+ const oldest = byTrace.keys().next().value;
1847
+ if (oldest !== void 0) byTrace.delete(oldest);
1848
+ }
1849
+ }
1850
+ return trail;
1851
+ }
1852
+ return processTrail;
1853
+ }
1854
+ function addBreadcrumb(input) {
1855
+ try {
1856
+ const crumb = normalizeBreadcrumb(input, redact2);
1857
+ if (!crumb) return;
1858
+ trailFor(true)?.add(crumb);
1859
+ noteCompatFeature("breadcrumbs");
1860
+ } catch {
1861
+ }
1862
+ }
1863
+ function recordLogBreadcrumb(level, message, attributes, state) {
1864
+ try {
1865
+ const data = {};
1866
+ let count = 0;
1867
+ for (const [key, value] of Object.entries(attributes)) {
1868
+ if (count >= 10) break;
1869
+ if (/^(?:exception|autter)\./.test(key)) continue;
1870
+ if (value === null || ["string", "number", "boolean"].includes(typeof value)) {
1871
+ data[key] = value;
1872
+ count++;
1873
+ }
1874
+ }
1875
+ const crumb = normalizeBreadcrumb({ type: "log", level, message, data }, redact2);
1876
+ if (crumb) trailFor(true, state)?.add(crumb);
1877
+ } catch {
1878
+ }
1879
+ }
1880
+ function attributeBudget() {
1881
+ const limit = Number(
1882
+ process.env.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT ?? process.env.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT
1883
+ );
1884
+ return Number.isFinite(limit) && limit > 0 ? Math.min(BREADCRUMBS_ATTRIBUTE_MAX_CHARS, limit) : BREADCRUMBS_ATTRIBUTE_MAX_CHARS;
1885
+ }
1886
+ function rememberThrownBreadcrumbs(error, state) {
1887
+ if (state.breadcrumbs && error !== null && (typeof error === "object" || typeof error === "function"))
1888
+ thrownTrails.set(error, state.breadcrumbs);
1889
+ }
1890
+ function breadcrumbEventAttributes(scrub, error) {
1891
+ try {
1892
+ const thrown = error !== null && typeof error === "object" ? thrownTrails.get(error) : void 0;
1893
+ const trail = thrown ?? trailFor(false);
1894
+ if (!trail?.size) return {};
1895
+ const json = breadcrumbsAttribute(trail.list(), scrub, attributeBudget());
1896
+ return json ? { [BREADCRUMBS_ATTRIBUTE]: json } : {};
1897
+ } catch {
1898
+ return {};
1899
+ }
1900
+ }
1901
+
1637
1902
  export {
1638
1903
  redactAttributes,
1639
1904
  redactText,
@@ -1641,6 +1906,7 @@ export {
1641
1906
  CODE_PATTERN,
1642
1907
  RuntimeError,
1643
1908
  defineRuntimeErrors,
1909
+ isError,
1644
1910
  isRuntimeErrorLike,
1645
1911
  errorCodeOf,
1646
1912
  errorAttributes,
@@ -1654,6 +1920,12 @@ export {
1654
1920
  compileIgnore,
1655
1921
  requestOutcome,
1656
1922
  parseUserAgent,
1923
+ sanitizeBreadcrumbs,
1924
+ fingerprintValues,
1925
+ fingerprintEventAttributes,
1926
+ splitFingerprint,
1927
+ addBreadcrumb,
1928
+ breadcrumbEventAttributes,
1657
1929
  otlpSink,
1658
1930
  consoleSink,
1659
1931
  fileSink,
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
 
8
- var _chunkGI47WGRRcjs = require('./chunk-GI47WGRR.cjs');
8
+ var _chunk4MVWIRBIcjs = require('./chunk-4MVWIRBI.cjs');
9
9
 
10
10
  // src/doctor.ts
11
11
  var _fs = require('fs');
@@ -30,19 +30,19 @@ function installedSdks(cwd) {
30
30
  }
31
31
  if (_path.dirname.call(void 0, dir) === dir) break;
32
32
  }
33
- if (!found.has(_chunkGI47WGRRcjs.SDK_IDENTITY.name)) found.set(_chunkGI47WGRRcjs.SDK_IDENTITY.name, _chunkGI47WGRRcjs.SDK_IDENTITY.version);
33
+ if (!found.has(_chunk4MVWIRBIcjs.SDK_IDENTITY.name)) found.set(_chunk4MVWIRBIcjs.SDK_IDENTITY.name, _chunk4MVWIRBIcjs.SDK_IDENTITY.version);
34
34
  return [...found].map(([name, version]) => ({ name, version }));
35
35
  }
36
36
  function featuresInUse(sdks) {
37
- return _chunkGI47WGRRcjs.COMPAT_MANIFEST.features.filter(
37
+ return _chunk4MVWIRBIcjs.COMPAT_MANIFEST.features.filter(
38
38
  (feature) => sdks.some((sdk) => {
39
39
  const min = feature.sdks[sdk.name];
40
- return min !== void 0 && _chunkGI47WGRRcjs.versionAtLeast.call(void 0, sdk.version, min) === true;
40
+ return min !== void 0 && _chunk4MVWIRBIcjs.versionAtLeast.call(void 0, sdk.version, min) === true;
41
41
  })
42
42
  ).map((feature) => feature.id);
43
43
  }
44
44
  async function checkKey(endpoint, key, timeoutMs) {
45
- const res = await _chunkGI47WGRRcjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/traces`, {
45
+ const res = await _chunk4MVWIRBIcjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/traces`, {
46
46
  body: JSON.stringify({ resourceSpans: [] }),
47
47
  headers: { authorization: `Bearer ${key}` },
48
48
  unref: false,
@@ -62,10 +62,10 @@ async function runDoctor(options = {}) {
62
62
  const features = _optionalChain([options, 'access', _ => _.features, 'optionalAccess', _2 => _2.length]) ? options.features : featuresInUse(sdks);
63
63
  const routes = [
64
64
  ...new Set(
65
- features.map((id) => _optionalChain([_chunkGI47WGRRcjs.COMPAT_MANIFEST, 'access', _3 => _3.features, 'access', _4 => _4.find, 'call', _5 => _5((f) => f.id === id), 'optionalAccess', _6 => _6.route])).filter((route) => Boolean(route))
65
+ features.map((id) => _optionalChain([_chunk4MVWIRBIcjs.COMPAT_MANIFEST, 'access', _3 => _3.features, 'access', _4 => _4.find, 'call', _5 => _5((f) => f.id === id), 'optionalAccess', _6 => _6.route])).filter((route) => Boolean(route))
66
66
  )
67
67
  ];
68
- const { info, error } = await _chunkGI47WGRRcjs.fetchIngesterCompat.call(void 0, endpoint, { probeRoutes: routes, unref: false, timeoutMs });
68
+ const { info, error } = await _chunk4MVWIRBIcjs.fetchIngesterCompat.call(void 0, endpoint, { probeRoutes: routes, unref: false, timeoutMs });
69
69
  const issues = [];
70
70
  const seen = /* @__PURE__ */ new Set();
71
71
  const add = (issue) => {
@@ -75,15 +75,15 @@ async function runDoctor(options = {}) {
75
75
  issues.push(issue);
76
76
  }
77
77
  };
78
- _chunkGI47WGRRcjs.evaluateCompat.call(void 0, { features, ingester: info }).forEach(add);
78
+ _chunk4MVWIRBIcjs.evaluateCompat.call(void 0, { features, ingester: info }).forEach(add);
79
79
  for (const sdk of sdks) {
80
80
  if (_optionalChain([options, 'access', _7 => _7.features, 'optionalAccess', _8 => _8.length])) {
81
- _chunkGI47WGRRcjs.evaluateCompat.call(void 0, { features: options.features, ingester: null, sdk }).forEach(add);
81
+ _chunk4MVWIRBIcjs.evaluateCompat.call(void 0, { features: options.features, ingester: null, sdk }).forEach(add);
82
82
  }
83
83
  }
84
84
  const key = options.key ? await checkKey(endpoint, options.key, timeoutMs) : void 0;
85
85
  const featureRows = features.flatMap((id) => {
86
- const feature = _chunkGI47WGRRcjs.COMPAT_MANIFEST.features.find((f) => f.id === id);
86
+ const feature = _chunk4MVWIRBIcjs.COMPAT_MANIFEST.features.find((f) => f.id === id);
87
87
  if (!feature) return [];
88
88
  const bad = issues.some((issue) => issue.feature === id);
89
89
  const known = _optionalChain([info, 'optionalAccess', _9 => _9.version]) != null || feature.route !== void 0 && _optionalChain([info, 'optionalAccess', _10 => _10.routes, 'optionalAccess', _11 => _11[feature.route]]) !== void 0;
@@ -153,7 +153,7 @@ Checks that your Autter SDKs, ingester and ClickHouse schema versions match.
153
153
  Options:
154
154
  --endpoint <url> Ingester base URL (default: $AUTTER_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT, or ${DEFAULT_ENDPOINT})
155
155
  --key <key> Also verify an ingest key (default: $AUTTER_RUNTIME_KEY)
156
- --features <a,b> Only check these features (${_chunkGI47WGRRcjs.COMPAT_MANIFEST.features.map((f) => f.id).join(", ")})
156
+ --features <a,b> Only check these features (${_chunk4MVWIRBIcjs.COMPAT_MANIFEST.features.map((f) => f.id).join(", ")})
157
157
  --json Machine-readable output
158
158
  --timeout <ms> Per-request timeout (default 5000)
159
159