@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.
package/dist/index.js CHANGED
@@ -2,6 +2,8 @@ import {
2
2
  CODE_PATTERN,
3
3
  DEFAULT_REQUEST_ID_HEADER,
4
4
  RuntimeError,
5
+ addBreadcrumb,
6
+ breadcrumbEventAttributes,
5
7
  captureExceptionAsLog,
6
8
  compileIgnore,
7
9
  configureRuntimeLogger,
@@ -15,7 +17,10 @@ import {
15
17
  errorCodeOf,
16
18
  errorInternal,
17
19
  fileSink,
20
+ fingerprintEventAttributes,
21
+ fingerprintValues,
18
22
  flushRuntimeLogs,
23
+ isError,
19
24
  isRuntimeErrorLike,
20
25
  local,
21
26
  makeRedactor,
@@ -35,13 +40,15 @@ import {
35
40
  runtimeContext,
36
41
  runtimeLogStats,
37
42
  runtimeLogger,
43
+ sanitizeBreadcrumbs,
38
44
  shutdownRuntimeLogger,
45
+ splitFingerprint,
39
46
  toClientError,
40
47
  withRuntimeOperation
41
- } from "./chunk-3TJHUCZ6.js";
48
+ } from "./chunk-5KISVAVB.js";
42
49
  import {
43
50
  runDoctor
44
- } from "./chunk-ZS4WQJQR.js";
51
+ } from "./chunk-L5ENELB2.js";
45
52
  import {
46
53
  COMPAT_MANIFEST,
47
54
  CountingExporter,
@@ -60,9 +67,10 @@ import {
60
67
  observeIngesterResponse,
61
68
  registerFlushTarget,
62
69
  setDebugMode,
70
+ sharedSlot,
63
71
  telemetryStats,
64
72
  unregisterFlushTargets
65
- } from "./chunk-L7HWKMCZ.js";
73
+ } from "./chunk-HRDELHZP.js";
66
74
 
67
75
  // src/relay.ts
68
76
  var IpWindow = class {
@@ -102,6 +110,7 @@ var EVENT_TYPES = /* @__PURE__ */ new Set([
102
110
  "timing"
103
111
  ]);
104
112
  var SEVERITIES = /* @__PURE__ */ new Set(["fatal", "error", "warning", "info"]);
113
+ var TRACE_ID = /^(?!0{32})[0-9a-f]{32}$/;
105
114
  var CONTEXT_MAX_DEPTH = 6;
106
115
  var CONTEXT_MAX_NODES = 256;
107
116
  var CONTEXT_MAX_STRING = 4e3;
@@ -214,6 +223,27 @@ function relayContext(raw, redactor) {
214
223
  const bounded = boundContext(raw);
215
224
  return Array.isArray(bounded) ? {} : { context: redactor(bounded) };
216
225
  }
226
+ function relayBreadcrumbs(raw, redactor) {
227
+ const breadcrumbs = sanitizeBreadcrumbs(raw, redactor);
228
+ return breadcrumbs ? { breadcrumbs } : {};
229
+ }
230
+ var DEBUG_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
231
+ function relayDebugIds(raw, redactor) {
232
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
233
+ const debugIds = {};
234
+ let count = 0;
235
+ for (const [url, id] of Object.entries(raw)) {
236
+ if (count >= 50) break;
237
+ if (typeof id !== "string" || !DEBUG_ID_RE.test(id) || url.length > 1e3) continue;
238
+ debugIds[redactor.text(url.split(/[?#]/)[0])] = id.toLowerCase();
239
+ count++;
240
+ }
241
+ return count ? { debugIds } : {};
242
+ }
243
+ function relayFingerprint(raw, redactor) {
244
+ const values = fingerprintValues(raw, redactor.text);
245
+ return values ? { fingerprint: Array.isArray(raw) ? values : values[0] } : {};
246
+ }
217
247
  var defaultRelayRedactor = makeRedactor(true);
218
248
  function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
219
249
  if (typeof raw !== "object" || raw === null) return null;
@@ -241,7 +271,11 @@ function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
241
271
  ...typeof e.line === "number" ? { line: e.line } : {},
242
272
  ...typeof e.column === "number" ? { column: e.column } : {},
243
273
  ...typeof e.route === "string" ? { route: e.route.split("?")[0].slice(0, 1e3) } : {},
244
- ...typeof e.context === "object" && e.context !== null ? relayContext(e.context, redactor) : {}
274
+ ...typeof e.context === "object" && e.context !== null ? relayContext(e.context, redactor) : {},
275
+ ...relayBreadcrumbs(e.breadcrumbs, redactor),
276
+ ...relayDebugIds(e.debugIds, redactor),
277
+ ...relayFingerprint(e.fingerprint, redactor),
278
+ ...typeof e.traceId === "string" && TRACE_ID.test(e.traceId) ? { traceId: e.traceId } : {}
245
279
  });
246
280
  }
247
281
  return {
@@ -275,6 +309,9 @@ function forward(payload, opts) {
275
309
  );
276
310
  observeIngesterResponse({ status: response.status, headers: response.headers });
277
311
  for (const feature of featuresForBrowserEvents(types)) noteCompatFeature(feature);
312
+ if ((payload.events ?? []).some((e) => e.traceId)) {
313
+ noteCompatFeature("browser_trace_propagation");
314
+ }
278
315
  noteRelayRejection(types, response.status, response.headers);
279
316
  }).catch((err) => {
280
317
  (opts.onError ?? ((e) => console.warn("autter relay forward failed:", e)))(
@@ -413,7 +450,7 @@ import {
413
450
  SpanStatusCode,
414
451
  TraceFlags
415
452
  } from "@opentelemetry/api";
416
- import { getRPCMetadata, RPCType } from "@opentelemetry/core";
453
+ import { getRPCMetadata as getRPCMetadata2, RPCType as RPCType2 } from "@opentelemetry/core";
417
454
  import {
418
455
  AggregationTemporalityPreference,
419
456
  OTLPMetricExporter
@@ -442,6 +479,89 @@ import { Resource } from "@opentelemetry/resources";
442
479
  import {
443
480
  AggregationTemporality
444
481
  } from "@opentelemetry/sdk-metrics";
482
+
483
+ // src/url-query.ts
484
+ var URL_KEYS = ["http.target", "http.url", "url.full", "url.original", "url.path"];
485
+ var QUERY_KEY = "url.query";
486
+ var NAME_KEYS = ["next.span_name"];
487
+ var MASK = "[redacted]";
488
+ var SCHEME = /^[a-z][a-z0-9+.-]*:\/\//i;
489
+ function splitUrl(value) {
490
+ const from = SCHEME.test(value) ? value.indexOf("://") + 3 : 0;
491
+ const question = value.indexOf("?", from);
492
+ const hash = value.indexOf("#", from);
493
+ const at = question < 0 ? hash : hash < 0 ? question : Math.min(question, hash);
494
+ if (at < 0) return null;
495
+ const query = at === question ? value.slice(at + 1, hash > at ? hash : void 0) : null;
496
+ return { base: value.slice(0, at), query };
497
+ }
498
+ function decodeName(raw) {
499
+ try {
500
+ return decodeURIComponent(raw.replace(/\+/g, " "));
501
+ } catch {
502
+ return raw;
503
+ }
504
+ }
505
+ function makeUrlQueryPolicy(keepQueryParams = []) {
506
+ const keep = new Set(keepQueryParams.filter((name) => typeof name === "string"));
507
+ const maskQuery = (query) => {
508
+ const out = [];
509
+ for (const part of query.split("&")) {
510
+ if (!part) continue;
511
+ const eq = part.indexOf("=");
512
+ const name = eq < 0 ? part : part.slice(0, eq);
513
+ if (keep.size && keep.has(decodeName(name))) out.push(part);
514
+ else if (eq < 0) out.push(MASK);
515
+ else out.push(eq === part.length - 1 ? part : `${name}=${MASK}`);
516
+ }
517
+ return out.join("&");
518
+ };
519
+ const stripName = (name) => {
520
+ if (!name.includes("?") && !name.includes("#")) return name;
521
+ return name.split(" ").map((token) => {
522
+ if (!token.startsWith("/") && !SCHEME.test(token)) return token;
523
+ return splitUrl(token)?.base ?? token;
524
+ }).join(" ");
525
+ };
526
+ return {
527
+ attributes(attributes) {
528
+ let out = null;
529
+ const set = (key, value) => {
530
+ out ??= { ...attributes };
531
+ out[key] = value;
532
+ };
533
+ let dropped = null;
534
+ for (const key of URL_KEYS) {
535
+ const value = attributes[key];
536
+ if (typeof value !== "string") continue;
537
+ const split = splitUrl(value);
538
+ if (!split) continue;
539
+ set(key, split.base);
540
+ if (split.query) dropped ??= split.query;
541
+ }
542
+ for (const key of NAME_KEYS) {
543
+ const value = attributes[key];
544
+ if (typeof value !== "string") continue;
545
+ const stripped = stripName(value);
546
+ if (stripped !== value) set(key, stripped);
547
+ }
548
+ const current = attributes[QUERY_KEY];
549
+ if (typeof current === "string") {
550
+ const masked = maskQuery(current.startsWith("?") ? current.slice(1) : current);
551
+ if (masked !== current) set(QUERY_KEY, masked);
552
+ } else if (current !== void 0) {
553
+ set(QUERY_KEY, MASK);
554
+ } else if (dropped !== null) {
555
+ const masked = maskQuery(dropped);
556
+ if (masked) set(QUERY_KEY, masked);
557
+ }
558
+ return out ?? attributes;
559
+ },
560
+ name: stripName
561
+ };
562
+ }
563
+
564
+ // src/redact-exporter.ts
445
565
  var scrubbedResources = /* @__PURE__ */ new WeakMap();
446
566
  function redactResource(resource, redactor) {
447
567
  const cached = scrubbedResources.get(resource);
@@ -450,17 +570,41 @@ function redactResource(resource, redactor) {
450
570
  scrubbedResources.set(resource, scrubbed);
451
571
  return scrubbed;
452
572
  }
453
- function redactSpan(span, redactor) {
454
- const status = span.status.message ? { ...span.status, message: redactor.text(span.status.message) } : span.status;
573
+ var DEFAULT_QUERY_POLICY = makeUrlQueryPolicy();
574
+ function stripQueries(span, query) {
575
+ const name = query.name(span.name);
576
+ const attributes = query.attributes(span.attributes);
577
+ if (name === span.name && attributes === span.attributes) return span;
578
+ return wrap(span, { name, attributes });
579
+ }
580
+ function wrap(span, fields) {
455
581
  return {
456
- name: redactor.text(span.name),
582
+ name: fields.name,
457
583
  kind: span.kind,
458
584
  spanContext: () => span.spanContext(),
459
585
  parentSpanId: span.parentSpanId,
460
586
  startTime: span.startTime,
461
587
  endTime: span.endTime,
588
+ status: fields.status ?? span.status,
589
+ attributes: fields.attributes,
590
+ links: fields.links ?? span.links,
591
+ events: fields.events ?? span.events,
592
+ duration: span.duration,
593
+ ended: span.ended,
594
+ resource: fields.resource ?? span.resource,
595
+ instrumentationLibrary: span.instrumentationLibrary,
596
+ droppedAttributesCount: span.droppedAttributesCount,
597
+ droppedEventsCount: span.droppedEventsCount,
598
+ droppedLinksCount: span.droppedLinksCount
599
+ };
600
+ }
601
+ function redactSpan(span, redactor, query = DEFAULT_QUERY_POLICY) {
602
+ if (!redactor.enabled) return stripQueries(span, query);
603
+ const status = span.status.message ? { ...span.status, message: redactor.text(span.status.message) } : span.status;
604
+ return wrap(span, {
605
+ name: query.name(redactor.text(span.name)),
462
606
  status,
463
- attributes: redactor.exported(span.attributes),
607
+ attributes: query.attributes(redactor.exported(span.attributes)),
464
608
  links: span.links.map(
465
609
  (link) => link.attributes ? { ...link, attributes: redactor.exported(link.attributes) } : link
466
610
  ),
@@ -469,34 +613,27 @@ function redactSpan(span, redactor) {
469
613
  name: redactor.text(event.name),
470
614
  ...event.attributes ? { attributes: redactor.exported(event.attributes) } : {}
471
615
  })),
472
- duration: span.duration,
473
- ended: span.ended,
474
- resource: redactResource(span.resource, redactor),
475
- instrumentationLibrary: span.instrumentationLibrary,
476
- droppedAttributesCount: span.droppedAttributesCount,
477
- droppedEventsCount: span.droppedEventsCount,
478
- droppedLinksCount: span.droppedLinksCount
479
- };
616
+ resource: redactResource(span.resource, redactor)
617
+ });
480
618
  }
481
619
  var RedactingSpanExporter = class {
482
- constructor(inner, redactor) {
620
+ constructor(inner, redactor, query = DEFAULT_QUERY_POLICY) {
483
621
  this.inner = inner;
484
622
  this.redactor = redactor;
623
+ this.query = query;
485
624
  }
486
625
  inner;
487
626
  redactor;
627
+ query;
488
628
  export(spans, resultCallback) {
489
629
  const redactor = this.redactor();
490
- let scrubbed = spans;
630
+ const scrubbed = [];
491
631
  let failed = 0;
492
- if (redactor.enabled) {
493
- scrubbed = [];
494
- for (const span of spans) {
495
- try {
496
- scrubbed.push(redactSpan(span, redactor));
497
- } catch {
498
- failed++;
499
- }
632
+ for (const span of spans) {
633
+ try {
634
+ scrubbed.push(redactSpan(span, redactor, this.query));
635
+ } catch {
636
+ failed++;
500
637
  }
501
638
  }
502
639
  if (!failed) {
@@ -602,14 +739,20 @@ function startMemoryMetrics() {
602
739
  // src/requests.ts
603
740
  import { AsyncResource } from "async_hooks";
604
741
  import { context, trace } from "@opentelemetry/api";
742
+ import { getRPCMetadata, RPCType } from "@opentelemetry/core";
605
743
  var REQUEST_STATE = /* @__PURE__ */ Symbol.for("autter.runtime.request");
606
744
  var BOUND = /* @__PURE__ */ Symbol.for("autter.runtime.bound");
607
- var CAPTURED = /* @__PURE__ */ new WeakSet();
745
+ var CAPTURED = sharedSlot("requests", () => ({ captured: /* @__PURE__ */ new WeakSet() })).captured;
608
746
  var capturer = () => {
609
747
  };
610
748
  function setRequestExceptionCapturer(fn) {
611
749
  capturer = fn;
612
750
  }
751
+ var annotator = () => {
752
+ };
753
+ function setRequestSpanAnnotator(fn) {
754
+ annotator = fn;
755
+ }
613
756
  function markCaptured(error) {
614
757
  if (error && typeof error === "object") CAPTURED.add(error);
615
758
  }
@@ -624,23 +767,54 @@ function expressRouteOf(req) {
624
767
  function defaultShouldCapture(error) {
625
768
  return error instanceof RuntimeError || errorCodeOf(error) !== void 0 || responseStatusOf(error) >= 500;
626
769
  }
627
- function captureAtBoundary(error, state, rule = void 0) {
628
- const wanted = typeof rule === "function" ? rule(error) : rule === void 0 ? defaultShouldCapture(error) : rule;
770
+ function onRequestSpan(fn) {
771
+ const active = context.active();
772
+ const metadata = getRPCMetadata(active);
773
+ const span = metadata?.type === RPCType.HTTP ? metadata.span : void 0;
774
+ if (!span || !span.isRecording() || trace.getSpan(active) === span) {
775
+ fn();
776
+ return;
777
+ }
778
+ context.with(trace.setSpan(active, span), fn);
779
+ }
780
+ function captureAtBoundary(error, state, rule = void 0, attributes) {
781
+ let wanted;
782
+ try {
783
+ wanted = typeof rule === "function" ? rule(error) : rule === void 0 ? defaultShouldCapture(error) : rule;
784
+ } catch {
785
+ wanted = false;
786
+ }
629
787
  if (!wanted) return;
630
788
  if (error && typeof error === "object" && CAPTURED.has(error)) return;
631
789
  markCaptured(error);
632
790
  try {
633
- if (state && !state.sealed) local.run(state, () => capturer(error));
634
- else capturer(error);
791
+ const run = () => onRequestSpan(() => capturer(error, attributes));
792
+ if (state && !state.sealed) local.run(state, run);
793
+ else run();
635
794
  } catch {
636
795
  }
637
796
  }
797
+ function requestStateOf(req) {
798
+ if (!req || typeof req !== "object") return void 0;
799
+ return req[REQUEST_STATE];
800
+ }
801
+ function captureRequestError(error, attributes) {
802
+ if (error && typeof error === "object" && CAPTURED.has(error)) {
803
+ if (!attributes) return;
804
+ try {
805
+ onRequestSpan(() => annotator(attributes));
806
+ } catch {
807
+ }
808
+ return;
809
+ }
810
+ captureAtBoundary(error, currentOperation(), true, attributes);
811
+ }
638
812
  function bindEmitter(emitter, state) {
639
813
  const target = emitter;
640
814
  if (!target || typeof target !== "object" || target[BOUND]) return;
641
815
  target[BOUND] = true;
642
816
  const wrappers = /* @__PURE__ */ new WeakMap();
643
- const wrap = (listener) => {
817
+ const wrap2 = (listener) => {
644
818
  if (typeof listener !== "function") return listener;
645
819
  let wrapped = wrappers.get(listener);
646
820
  if (!wrapped) {
@@ -664,7 +838,7 @@ function bindEmitter(emitter, state) {
664
838
  const original = target[method];
665
839
  if (typeof original !== "function") continue;
666
840
  target[method] = function(event, listener) {
667
- return original.call(this, event, wrap(listener));
841
+ return original.call(this, event, wrap2(listener));
668
842
  };
669
843
  }
670
844
  for (const method of ["removeListener", "off"]) {
@@ -976,7 +1150,140 @@ function startHookRequest(res) {
976
1150
  local.enterWith(state);
977
1151
  }
978
1152
 
1153
+ // src/unhandled-rejections.ts
1154
+ var MODES = /* @__PURE__ */ new Set([
1155
+ "throw",
1156
+ "strict",
1157
+ "warn",
1158
+ "warn-with-error-code",
1159
+ "none"
1160
+ ]);
1161
+ var LISTENER_MARK = /* @__PURE__ */ Symbol.for("autter.runtime.unhandledRejection.listener");
1162
+ var HANDLED_KEY = /* @__PURE__ */ Symbol.for("autter.runtime.unhandledRejection.handled");
1163
+ function handledPromises() {
1164
+ const registry = globalThis;
1165
+ let set = registry[HANDLED_KEY];
1166
+ if (!set) {
1167
+ set = /* @__PURE__ */ new WeakSet();
1168
+ registry[HANDLED_KEY] = set;
1169
+ }
1170
+ return set;
1171
+ }
1172
+ var recorded = /* @__PURE__ */ new WeakSet();
1173
+ function markRecorded(reason) {
1174
+ if (!reason || typeof reason !== "object") return;
1175
+ recorded.add(reason);
1176
+ setImmediate(() => recorded.delete(reason)).unref?.();
1177
+ }
1178
+ function recordedAsRejection(error) {
1179
+ return !!error && typeof error === "object" && recorded.has(error);
1180
+ }
1181
+ function splitOptions(value) {
1182
+ return value ? value.split(/\s+/).filter(Boolean) : [];
1183
+ }
1184
+ function unhandledRejectionsMode(execArgv = process.execArgv, nodeOptions = process.env.NODE_OPTIONS) {
1185
+ const args = [...splitOptions(nodeOptions), ...execArgv];
1186
+ let mode = "throw";
1187
+ for (let i = 0; i < args.length; i++) {
1188
+ const arg = args[i] ?? "";
1189
+ if (!arg.startsWith("--")) continue;
1190
+ const eq = arg.indexOf("=");
1191
+ const name = (eq < 0 ? arg : arg.slice(0, eq)).replace(/_/g, "-");
1192
+ if (name !== "--unhandled-rejections") continue;
1193
+ const value = eq < 0 ? args[++i] : arg.slice(eq + 1);
1194
+ if (value && MODES.has(value))
1195
+ mode = value;
1196
+ }
1197
+ return mode;
1198
+ }
1199
+ function isErrorLike(value) {
1200
+ try {
1201
+ return typeof value === "object" && value !== null && Object.prototype.hasOwnProperty.call(value, "stack");
1202
+ } catch {
1203
+ return false;
1204
+ }
1205
+ }
1206
+ function describeReason(reason) {
1207
+ if (typeof reason !== "object" && typeof reason !== "function" || reason === null) {
1208
+ try {
1209
+ return String(reason);
1210
+ } catch {
1211
+ return typeof reason;
1212
+ }
1213
+ }
1214
+ try {
1215
+ const proto = Object.getPrototypeOf(reason);
1216
+ const ctor = proto?.constructor;
1217
+ const name = typeof ctor === "function" ? ctor.name : "";
1218
+ return name ? `#<${name}>` : "[object Object]";
1219
+ } catch {
1220
+ return "[object Object]";
1221
+ }
1222
+ }
1223
+ function toUncaught(reason, from) {
1224
+ if (isErrorLike(reason)) return reason;
1225
+ const error = new Error(
1226
+ `This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "${describeReason(reason)}".`
1227
+ );
1228
+ error.name = "UnhandledPromiseRejection";
1229
+ error.code = "ERR_UNHANDLED_REJECTION";
1230
+ Error.captureStackTrace?.(error, from);
1231
+ return error;
1232
+ }
1233
+ var warningId = 0;
1234
+ function emitNodeWarning(reason) {
1235
+ try {
1236
+ const text = isErrorLike(reason) ? String(reason.stack) : describeReason(reason);
1237
+ process.emitWarning(text, "UnhandledPromiseRejectionWarning");
1238
+ process.emitWarning(
1239
+ `Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag \`--unhandled-rejections=strict\` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: ${++warningId})`,
1240
+ "UnhandledPromiseRejectionWarning"
1241
+ );
1242
+ } catch {
1243
+ }
1244
+ }
1245
+ function installUnhandledRejectionCapture(capture, mode = unhandledRejectionsMode()) {
1246
+ if (mode === "strict") return () => {
1247
+ };
1248
+ const listener = (reason, promise) => {
1249
+ try {
1250
+ if (promise && typeof promise === "object") {
1251
+ const handled = handledPromises();
1252
+ if (handled.has(promise)) return;
1253
+ handled.add(promise);
1254
+ }
1255
+ const alone = process.listeners("unhandledRejection").every((fn) => fn[LISTENER_MARK] === true);
1256
+ if (alone && mode === "throw") {
1257
+ const error = toUncaught(reason, listener);
1258
+ process.nextTick(() => {
1259
+ throw error;
1260
+ });
1261
+ return;
1262
+ }
1263
+ if (alone && mode === "warn-with-error-code") {
1264
+ emitNodeWarning(reason);
1265
+ process.exitCode = 1;
1266
+ }
1267
+ markRecorded(reason);
1268
+ capture(reason);
1269
+ } catch {
1270
+ }
1271
+ };
1272
+ listener[LISTENER_MARK] = true;
1273
+ process.on("unhandledRejection", listener);
1274
+ return () => {
1275
+ process.off("unhandledRejection", listener);
1276
+ };
1277
+ }
1278
+
979
1279
  // src/server.ts
1280
+ var S = sharedSlot("server", () => ({
1281
+ active: null,
1282
+ alwaysOnProvider: null,
1283
+ redactor: makeRedactor(true),
1284
+ loggerOnlyCapture: null,
1285
+ hookRequests: false
1286
+ }));
980
1287
  var LLM_MARKER_ATTRIBUTES = [
981
1288
  "gen_ai.operation.name",
982
1289
  "gen_ai.system",
@@ -1104,9 +1411,9 @@ var ErrorTraceRetentionProcessor = class {
1104
1411
  }
1105
1412
  /** Promote the active trace, if any — called on captured errors. */
1106
1413
  retainActiveTrace() {
1107
- const active2 = trace2.getActiveSpan()?.spanContext();
1108
- if (!active2 || (active2.traceFlags & TraceFlags.SAMPLED) !== 0) return;
1109
- const entry = this.entryFor(active2.traceId);
1414
+ const active = trace2.getActiveSpan()?.spanContext();
1415
+ if (!active || (active.traceFlags & TraceFlags.SAMPLED) !== 0) return;
1416
+ const entry = this.entryFor(active.traceId);
1110
1417
  if (entry.retained) return;
1111
1418
  entry.retained = true;
1112
1419
  this.flush(entry);
@@ -1173,7 +1480,7 @@ function llmBaseAttributes(info) {
1173
1480
  ...info.sessionId ? { "autter.session_id": info.sessionId } : {},
1174
1481
  // Caller-supplied extras go through redaction like every other
1175
1482
  // custom attribute family (prompts can contain emails/keys).
1176
- ...info.attributes ? activeRedactor(info.attributes) : {}
1483
+ ...info.attributes ? S.redactor(info.attributes) : {}
1177
1484
  };
1178
1485
  }
1179
1486
  function usageAttributes(usage) {
@@ -1214,7 +1521,7 @@ async function runLlmSpan(tracer, info, fn) {
1214
1521
  },
1215
1522
  setResponseModel,
1216
1523
  setModel: setResponseModel,
1217
- setAttributes: (attributes) => span.setAttributes(activeRedactor(attributes))
1524
+ setAttributes: (attributes) => span.setAttributes(S.redactor(attributes))
1218
1525
  };
1219
1526
  try {
1220
1527
  const result = await context2.with(
@@ -1225,7 +1532,7 @@ async function runLlmSpan(tracer, info, fn) {
1225
1532
  return result;
1226
1533
  } catch (err) {
1227
1534
  recordError(span, err);
1228
- if (err instanceof Error) span.setAttribute("error.type", err.name);
1535
+ if (isError(err)) span.setAttribute("error.type", err.name);
1229
1536
  throw err;
1230
1537
  } finally {
1231
1538
  span.end();
@@ -1247,7 +1554,7 @@ function recordLlmCall(tracer, call) {
1247
1554
  );
1248
1555
  if (call.error !== void 0 && call.error !== null) {
1249
1556
  recordError(span, call.error);
1250
- if (call.error instanceof Error) {
1557
+ if (isError(call.error)) {
1251
1558
  span.setAttribute("error.type", call.error.name);
1252
1559
  }
1253
1560
  } else {
@@ -1275,24 +1582,25 @@ async function runWithSpan(tracer, name, fn, attributes, links) {
1275
1582
  }
1276
1583
  }
1277
1584
  function recordError(span, err) {
1278
- const isError = err instanceof Error;
1279
- const message = activeRedactor.text(isError ? err.message : String(err));
1280
- if (isError) {
1585
+ const isErr = isError(err);
1586
+ const message = S.redactor.text(isErr ? err.message : String(err));
1587
+ if (isErr) {
1281
1588
  span.addEvent("exception", {
1282
1589
  "exception.type": err.name,
1283
1590
  "exception.message": message,
1284
- ...err.stack ? { "exception.stacktrace": activeRedactor.text(err.stack) } : {}
1591
+ ...err.stack ? { "exception.stacktrace": S.redactor.text(err.stack) } : {},
1592
+ ...breadcrumbEventAttributes(S.redactor.text, err)
1285
1593
  });
1286
1594
  }
1287
1595
  span.setStatus({ code: SpanStatusCode.ERROR, message });
1288
1596
  }
1289
1597
  function redactWithActive(attributes) {
1290
- return activeRedactor(attributes);
1598
+ return S.redactor(attributes);
1291
1599
  }
1292
1600
  function captureExpressRoute(_span, response) {
1293
1601
  if (!(response instanceof ServerResponse)) return;
1294
- const rpcMetadata = getRPCMetadata(context2.active());
1295
- if (rpcMetadata?.type !== RPCType.HTTP) return;
1602
+ const rpcMetadata = getRPCMetadata2(context2.active());
1603
+ if (rpcMetadata?.type !== RPCType2.HTTP) return;
1296
1604
  const setRoute = () => {
1297
1605
  if (rpcMetadata.route) return;
1298
1606
  const route = expressRouteOf(response.req);
@@ -1300,42 +1608,41 @@ function captureExpressRoute(_span, response) {
1300
1608
  };
1301
1609
  response.prependListener("finish", setRoute);
1302
1610
  response.prependListener("close", setRoute);
1303
- if (hookRequests) startHookRequest(response);
1611
+ if (S.hookRequests) startHookRequest(response);
1304
1612
  }
1305
- var hookRequests = false;
1306
1613
  function structuredErrorAttributes(error) {
1307
1614
  const attrs = { ...errorAttributes(error, { causes: false }) };
1308
1615
  const internal = errorInternal(error);
1309
1616
  if (internal) {
1310
1617
  try {
1311
1618
  attrs["autter.error.internal"] = JSON.stringify(
1312
- activeRedactor(internal)
1619
+ S.redactor(internal)
1313
1620
  ).slice(0, 4e3);
1314
1621
  } catch {
1315
1622
  }
1316
1623
  }
1317
1624
  return attrs;
1318
1625
  }
1319
- var active = null;
1320
- var activeAlwaysOnProvider = null;
1321
- var activeRedactor = makeRedactor(true);
1322
1626
  function initAutterServer(options) {
1323
- if (active) return active;
1627
+ if (S.active) return S.active;
1324
1628
  const endpoint = (options.endpoint ?? "https://otlp.autter.dev").replace(
1325
1629
  /\/$/,
1326
1630
  ""
1327
1631
  );
1328
1632
  const headers = { authorization: `Bearer ${options.apiKey}` };
1633
+ const redactor = makeRedactor(options.redactAttributes ?? true);
1634
+ const queryPolicy = makeUrlQueryPolicy(options.keepQueryParams);
1329
1635
  const traceExporter = () => new CountingExporter(
1330
1636
  new RedactingSpanExporter(
1331
1637
  new OTLPTraceExporter({ url: `${endpoint}/v1/traces`, headers }),
1332
- () => activeRedactor
1638
+ () => redactor,
1639
+ queryPolicy
1333
1640
  )
1334
1641
  );
1335
1642
  const environment = options.environment ?? process.env.NODE_ENV ?? "production";
1336
1643
  if (isDebugEnabled() || options.debug === true) setDebugMode(true);
1337
1644
  debugLog(`initialising service=${options.service} endpoint=${endpoint}`);
1338
- activeRedactor = makeRedactor(options.redactAttributes ?? true);
1645
+ S.redactor = redactor;
1339
1646
  const distro = options.distro ?? SDK_IDENTITY;
1340
1647
  configureCompatCheck({ endpoint, enabled: options.compatCheck !== false, sdk: distro });
1341
1648
  const resource = new Resource2({
@@ -1352,8 +1659,15 @@ function initAutterServer(options) {
1352
1659
  // would double-count every sampled request).
1353
1660
  "autter.metrics_wired": true
1354
1661
  });
1662
+ const rootSampler = new TraceIdRatioBasedSampler(options.traceSampleRate ?? 0.01);
1355
1663
  const headSampler = new ParentBasedSampler({
1356
- root: new TraceIdRatioBasedSampler(options.traceSampleRate ?? 0.01)
1664
+ root: rootSampler,
1665
+ // Browsers (runtime-browser's traceparent) send unsampled parents: they
1666
+ // make no sampling decision. Apply this service's own rate to such
1667
+ // requests instead of never head-sampling them. The ratio sampler is
1668
+ // deterministic by trace id, so services sharing a rate still agree
1669
+ // with an upstream service that chose not to sample.
1670
+ remoteParentNotSampled: rootSampler
1357
1671
  });
1358
1672
  const retainOnError = options.retainTracesOnError !== false;
1359
1673
  const slowThresholdMs = Number.isFinite(options.retainTracesAboveMs) ? Math.max(0, options.retainTracesAboveMs) : 0;
@@ -1377,7 +1691,7 @@ function initAutterServer(options) {
1377
1691
  // 60 s export.
1378
1692
  temporalityPreference: AggregationTemporalityPreference.DELTA
1379
1693
  }),
1380
- () => activeRedactor
1694
+ () => redactor
1381
1695
  ),
1382
1696
  exportIntervalMillis: options.metricIntervalMs ?? 6e4
1383
1697
  });
@@ -1420,18 +1734,22 @@ function initAutterServer(options) {
1420
1734
  function captureException2(error, attributes) {
1421
1735
  errorTraceBuffer?.retainActiveTrace();
1422
1736
  telemetryStats.markCaptured();
1423
- const isError = error instanceof Error;
1424
- const message = activeRedactor.text(isError ? error.message : String(error));
1425
- const stackText = isError && error.stack ? activeRedactor.text(error.stack) : "";
1426
- const redacted = activeRedactor(attributes);
1737
+ const isErr = isError(error);
1738
+ const message = redactor.text(isErr ? error.message : String(error));
1739
+ const stackText = isErr && error.stack ? redactor.text(error.stack) : "";
1740
+ const split = splitFingerprint(attributes);
1741
+ const fingerprint = fingerprintEventAttributes(split.fingerprint, redactor.text);
1742
+ if (fingerprint["autter.fingerprint"]) noteCompatFeature("fingerprint_override");
1743
+ const redacted = redactor(split.attributes);
1427
1744
  const activeSpan = trace2.getActiveSpan();
1428
1745
  const reuseActive = activeSpan?.isRecording() === true;
1429
- const span = reuseActive ? activeSpan : errorTracer.startSpan(isError ? error.name : "Error");
1746
+ const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
1430
1747
  const structured = structuredErrorAttributes(error);
1431
1748
  const causes = errorAttributes(error);
1432
- const declared = Object.fromEntries(
1433
- Object.entries(causes).filter(([key]) => key !== "autter.error.internal")
1434
- );
1749
+ const declared = {
1750
+ ...Object.fromEntries(Object.entries(causes).filter(([key]) => key !== "autter.error.internal")),
1751
+ ...fingerprint
1752
+ };
1435
1753
  const requestId = currentOperation()?.requestId;
1436
1754
  span.setAttributes({
1437
1755
  "autter.severity": "error",
@@ -1441,23 +1759,25 @@ function initAutterServer(options) {
1441
1759
  });
1442
1760
  markCaptured(error);
1443
1761
  noteOperationError(error);
1444
- notifyException(error, redacted);
1445
- if (isError && stackText) {
1762
+ notifyException(error, { ...redacted, ...fingerprint });
1763
+ if (isErr && stackText) {
1446
1764
  span.addEvent("exception", {
1447
1765
  "exception.type": error.name,
1448
1766
  "exception.message": message,
1449
1767
  "exception.stacktrace": stackText,
1450
1768
  ...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {},
1451
- ...declared
1769
+ ...declared,
1770
+ ...breadcrumbEventAttributes(redactor.text, error)
1452
1771
  });
1453
1772
  } else {
1454
1773
  const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureException")).join("\n");
1455
1774
  span.addEvent("exception", {
1456
- "exception.type": isError ? error.name : "Error",
1775
+ "exception.type": isErr ? error.name : "Error",
1457
1776
  "exception.message": message,
1458
- ...stack ? { "exception.stacktrace": activeRedactor.text(stack) } : {},
1777
+ ...stack ? { "exception.stacktrace": redactor.text(stack) } : {},
1459
1778
  ...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {},
1460
- ...declared
1779
+ ...declared,
1780
+ ...breadcrumbEventAttributes(redactor.text, error)
1461
1781
  });
1462
1782
  }
1463
1783
  span.setStatus({ code: SpanStatusCode.ERROR, message });
@@ -1469,15 +1789,16 @@ function initAutterServer(options) {
1469
1789
  }
1470
1790
  telemetryStats.markCaptured();
1471
1791
  const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureMessage")).join("\n");
1472
- const safeMessage = activeRedactor.text(message);
1792
+ const safeMessage = redactor.text(message);
1473
1793
  const span = errorTracer.startSpan("Message", {
1474
- attributes: { "autter.severity": severity, ...activeRedactor(attributes) }
1794
+ attributes: { "autter.severity": severity, ...redactor(attributes) }
1475
1795
  });
1476
1796
  span.addEvent("exception", {
1477
1797
  "exception.type": "Message",
1478
1798
  "exception.message": safeMessage,
1479
- ...stack ? { "exception.stacktrace": activeRedactor.text(stack) } : {},
1480
- "autter.severity": severity
1799
+ ...stack ? { "exception.stacktrace": redactor.text(stack) } : {},
1800
+ "autter.severity": severity,
1801
+ ...breadcrumbEventAttributes(redactor.text)
1481
1802
  });
1482
1803
  span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
1483
1804
  span.end();
@@ -1488,11 +1809,11 @@ function initAutterServer(options) {
1488
1809
  const activeSpan = trace2.getActiveSpan();
1489
1810
  const reuseActive = activeSpan?.isRecording() === true;
1490
1811
  const span = reuseActive ? activeSpan : errorTracer.startSpan("OutcomeFailure");
1491
- const safeOutcome = activeRedactor({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
1812
+ const safeOutcome = redactor({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
1492
1813
  const safeName = String(safeOutcome["autter.outcome.name"] ?? "operation");
1493
1814
  const safeMessage = String(safeOutcome["autter.outcome.message"] ?? "failed");
1494
1815
  span.addEvent("autter.outcome", {
1495
- ...activeRedactor(attributes),
1816
+ ...redactor(attributes),
1496
1817
  "autter.outcome.status": "error",
1497
1818
  "autter.outcome.name": safeName,
1498
1819
  "autter.outcome.message": safeMessage
@@ -1508,17 +1829,26 @@ function initAutterServer(options) {
1508
1829
  distro,
1509
1830
  release: options.release,
1510
1831
  options: options.logging,
1511
- redact: activeRedactor,
1512
- run: (name, fn, attributes, links) => runWithSpan(processTracer, name, fn, activeRedactor(attributes), links),
1832
+ redact: redactor,
1833
+ run: (name, fn, attributes, links) => runWithSpan(processTracer, name, fn, redactor(attributes), links),
1513
1834
  reportOutcome: reportOutcome2
1514
1835
  });
1515
- hookRequests = options.logging?.requests === true;
1836
+ S.hookRequests = options.logging?.requests === true;
1837
+ let onCrash = null;
1838
+ let uninstallRejections = null;
1516
1839
  if (options.captureGlobalErrors !== false) {
1517
- process.on("uncaughtExceptionMonitor", (err) => {
1840
+ onCrash = (err) => {
1841
+ if (recordedAsRejection(err)) return;
1518
1842
  captureException2(err, { "autter.unhandled": true });
1519
1843
  if (options.autoFlush === false) void Promise.resolve(flushTarget.forceFlush()).catch(() => {
1520
1844
  });
1521
- });
1845
+ };
1846
+ process.on("uncaughtExceptionMonitor", onCrash);
1847
+ if (options.captureUnhandledRejections !== false) {
1848
+ uninstallRejections = installUnhandledRejectionCapture(
1849
+ (reason) => captureException2(reason, { "autter.unhandled_rejection": true })
1850
+ );
1851
+ }
1522
1852
  }
1523
1853
  const flushTarget = {
1524
1854
  forceFlush: async () => {
@@ -1546,15 +1876,20 @@ function initAutterServer(options) {
1546
1876
  captureException: captureException2,
1547
1877
  captureMessage: captureMessage2,
1548
1878
  reportOutcome: reportOutcome2,
1549
- withProcessSpan: (name, fn, attributes) => runWithSpan(processTracer, name, fn, activeRedactor(attributes)),
1879
+ withProcessSpan: (name, fn, attributes) => runWithSpan(processTracer, name, fn, redactor(attributes)),
1550
1880
  withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
1551
1881
  trackLlmCall: (call) => recordLlmCall(llmTracer, call),
1552
1882
  shutdown: async () => {
1553
1883
  const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
1554
- hookRequests = false;
1884
+ if (onCrash) process.off("uncaughtExceptionMonitor", onCrash);
1885
+ uninstallRejections?.();
1555
1886
  stopMemoryMetrics?.();
1556
- active = null;
1557
- activeAlwaysOnProvider = null;
1887
+ if (S.active === server) {
1888
+ S.active = null;
1889
+ S.alwaysOnProvider = null;
1890
+ S.hookRequests = false;
1891
+ S.redactor = makeRedactor(true);
1892
+ }
1558
1893
  autoFlushHandle?.dispose();
1559
1894
  autoFlushHandle = null;
1560
1895
  unregisterFlushTargets();
@@ -1563,75 +1898,81 @@ function initAutterServer(options) {
1563
1898
  if (logShutdown[0]?.status === "rejected") throw logShutdown[0].reason;
1564
1899
  }
1565
1900
  };
1566
- active = server;
1567
- activeAlwaysOnProvider = alwaysOnProvider;
1901
+ S.active = server;
1902
+ S.alwaysOnProvider = alwaysOnProvider;
1568
1903
  return server;
1569
1904
  }
1570
1905
  function captureException(error, attributes) {
1571
- if (active) {
1572
- active.captureException(error, attributes);
1906
+ if (S.active) {
1907
+ S.active.captureException(error, attributes);
1573
1908
  return;
1574
1909
  }
1575
- if (loggerOnlyCapture) {
1576
- loggerOnlyCapture(error, attributes);
1910
+ if (S.loggerOnlyCapture) {
1911
+ S.loggerOnlyCapture(error, attributes);
1577
1912
  return;
1578
1913
  }
1579
1914
  markCaptured(error);
1580
1915
  noteOperationError(error);
1581
- notifyException(error, attributes);
1582
- const span = trace2.getTracer("autter-errors").startSpan(error instanceof Error ? error.name : "Error", {
1583
- attributes: activeRedactor(attributes)
1916
+ const split = splitFingerprint(attributes);
1917
+ const fingerprint = fingerprintEventAttributes(split.fingerprint, S.redactor.text);
1918
+ notifyException(error, { ...split.attributes, ...fingerprint });
1919
+ const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error", {
1920
+ attributes: { ...S.redactor(split.attributes), ...fingerprint }
1584
1921
  });
1585
1922
  recordError(span, error);
1586
1923
  span.end();
1587
1924
  }
1588
- var loggerOnlyCapture = null;
1589
1925
  function setLoggerOnlyCapture(fn) {
1590
- loggerOnlyCapture = fn;
1926
+ S.loggerOnlyCapture = fn;
1591
1927
  }
1592
1928
  function serverActive() {
1593
- return active !== null;
1929
+ return S.active !== null;
1594
1930
  }
1595
- setRequestExceptionCapturer((error) => captureException(error));
1931
+ setRequestExceptionCapturer((error, attributes) => captureException(error, attributes));
1932
+ setRequestSpanAnnotator((attributes) => {
1933
+ const span = trace2.getActiveSpan();
1934
+ if (span?.isRecording()) span.setAttributes(S.redactor(splitFingerprint(attributes).attributes));
1935
+ });
1596
1936
  function withProcessSpan(name, fn, attributes) {
1597
- if (active) return active.withProcessSpan(name, fn, attributes);
1937
+ if (S.active) return S.active.withProcessSpan(name, fn, attributes);
1598
1938
  return runWithSpan(
1599
1939
  trace2.getTracer("autter-processes"),
1600
1940
  name,
1601
1941
  fn,
1602
- activeRedactor(attributes)
1942
+ S.redactor(attributes)
1603
1943
  );
1604
1944
  }
1605
1945
  function withLlmCall(info, fn) {
1606
- if (active) return active.withLlmCall(info, fn);
1946
+ if (S.active) return S.active.withLlmCall(info, fn);
1607
1947
  return runLlmSpan(trace2.getTracer("autter-llm"), info, fn);
1608
1948
  }
1609
1949
  function trackLlmCall(call) {
1610
- if (active) {
1611
- active.trackLlmCall(call);
1950
+ if (S.active) {
1951
+ S.active.trackLlmCall(call);
1612
1952
  return;
1613
1953
  }
1614
1954
  recordLlmCall(trace2.getTracer("autter-llm"), call);
1615
1955
  }
1616
1956
  function captureMessage(message, severity = "warning", attributes) {
1617
- if (active) {
1618
- active.captureMessage(message, severity, attributes);
1957
+ if (S.active) {
1958
+ S.active.captureMessage(message, severity, attributes);
1619
1959
  return;
1620
1960
  }
1621
- const safeMessage = activeRedactor.text(message);
1961
+ const safeMessage = S.redactor.text(message);
1622
1962
  const span = trace2.getTracer("autter-errors").startSpan("Message", {
1623
- attributes: { "autter.severity": severity, ...activeRedactor(attributes) }
1963
+ attributes: { "autter.severity": severity, ...S.redactor(attributes) }
1624
1964
  });
1625
1965
  span.addEvent("exception", {
1626
1966
  "exception.type": "Message",
1627
1967
  "exception.message": safeMessage,
1628
- "autter.severity": severity
1968
+ "autter.severity": severity,
1969
+ ...breadcrumbEventAttributes(S.redactor.text)
1629
1970
  });
1630
1971
  span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
1631
1972
  span.end();
1632
1973
  }
1633
1974
  function reportOutcome(name, message, attributes) {
1634
- if (active) return active.reportOutcome(name, message, attributes);
1975
+ if (S.active) return S.active.reportOutcome(name, message, attributes);
1635
1976
  const span = trace2.getTracer("autter-outcomes").startSpan("OutcomeFailure");
1636
1977
  const safeOutcome = redactAttributes({ "autter.outcome.name": name.slice(0, 200), "autter.outcome.message": message.slice(0, 1e3) });
1637
1978
  const safeName = String(safeOutcome["autter.outcome.name"] ?? "operation");
@@ -1653,13 +1994,13 @@ function makeSafeCapture(options) {
1653
1994
  };
1654
1995
  }
1655
1996
  function alwaysOnTracer(name) {
1656
- return activeAlwaysOnProvider ? activeAlwaysOnProvider.getTracer(name) : trace2.getTracer(name);
1997
+ return S.alwaysOnProvider ? S.alwaysOnProvider.getTracer(name) : trace2.getTracer(name);
1657
1998
  }
1658
1999
  function autterLlmTracer() {
1659
2000
  return alwaysOnTracer("autter-llm");
1660
2001
  }
1661
2002
  async function emitLlmSelftestTrace() {
1662
- const provider = activeAlwaysOnProvider;
2003
+ const provider = S.alwaysOnProvider;
1663
2004
  if (!provider) {
1664
2005
  throw new Error(
1665
2006
  "emitLlmSelftestTrace() requires initAutterServer() to have been called"
@@ -1917,14 +2258,14 @@ function startCaughtExceptionSampler(options = {}) {
1917
2258
  const rate = Math.max(0, Math.min(1, options.sampleRate ?? 0.01));
1918
2259
  const cap = Math.max(1, Math.min(100, options.maxPerMinute ?? 10));
1919
2260
  const session = new inspector.Session();
1920
- let active2 = true;
2261
+ let active = true;
1921
2262
  let minute = 0;
1922
2263
  let sent = 0;
1923
2264
  session.connect();
1924
2265
  session.on("Debugger.paused", (message) => {
1925
2266
  session.post("Debugger.resume", () => {
1926
2267
  });
1927
- if (!active2 || message.params.reason !== "exception") return;
2268
+ if (!active || message.params.reason !== "exception") return;
1928
2269
  const nowMinute = Math.floor(Date.now() / 6e4);
1929
2270
  if (nowMinute !== minute) {
1930
2271
  minute = nowMinute;
@@ -1939,30 +2280,66 @@ function startCaughtExceptionSampler(options = {}) {
1939
2280
  error.stack = `${error.name}: ${error.message}
1940
2281
  ${frames.join("\n")}`;
1941
2282
  queueMicrotask(() => {
1942
- if (active2) captureException(error, { "autter.handled": true, "autter.sampled": true });
2283
+ if (active) captureException(error, { "autter.handled": true, "autter.sampled": true });
1943
2284
  });
1944
2285
  });
1945
2286
  session.post("Debugger.enable", (error) => {
1946
- if (!error && active2) session.post("Debugger.setPauseOnExceptions", { state: "all" }, () => {
2287
+ if (!error && active) session.post("Debugger.setPauseOnExceptions", { state: "all" }, () => {
1947
2288
  });
1948
2289
  });
1949
2290
  return { stop() {
1950
- if (!active2) return;
1951
- active2 = false;
2291
+ if (!active) return;
2292
+ active = false;
1952
2293
  session.post("Debugger.setPauseOnExceptions", { state: "none" }, () => {
1953
2294
  session.post("Debugger.disable", () => session.disconnect());
1954
2295
  });
1955
2296
  } };
1956
2297
  }
1957
2298
 
2299
+ // src/framework-errors.ts
2300
+ function recordBoundaryError(error, req, rule) {
2301
+ try {
2302
+ const state = requestStateOf(req);
2303
+ if (state) noteOperationError(error, false, state);
2304
+ captureAtBoundary(error, state, rule);
2305
+ } catch {
2306
+ }
2307
+ }
2308
+ function autterErrorHandler(options = {}) {
2309
+ return function autterErrorHandlerMiddleware(error, req, _res, next) {
2310
+ recordBoundaryError(error, req, options.capture);
2311
+ next(error);
2312
+ };
2313
+ }
2314
+ function autterFastifyErrors(fastify, options, done) {
2315
+ const rule = options?.capture;
2316
+ fastify.addHook("onError", ((request, _reply, error, next) => {
2317
+ recordBoundaryError(error, request?.raw, rule);
2318
+ next();
2319
+ }));
2320
+ done?.();
2321
+ }
2322
+ autterFastifyErrors[/* @__PURE__ */ Symbol.for("skip-override")] = true;
2323
+ autterFastifyErrors[/* @__PURE__ */ Symbol.for("fastify.display-name")] = "autter-runtime-errors";
2324
+ function autterKoa(options = {}) {
2325
+ return async function autterKoaMiddleware(ctx, next) {
2326
+ try {
2327
+ await next();
2328
+ } catch (error) {
2329
+ recordBoundaryError(error, ctx?.req, options.capture);
2330
+ throw error;
2331
+ }
2332
+ };
2333
+ }
2334
+
1958
2335
  // src/logging-only.ts
1959
2336
  import {
1960
2337
  SpanStatusCode as SpanStatusCode3,
1961
2338
  trace as trace3
1962
2339
  } from "@opentelemetry/api";
1963
- var activeLogging = null;
2340
+ var L = sharedSlot("logging", () => ({ active: null }));
1964
2341
  function initAutterLogging(options) {
1965
- if (activeLogging) return activeLogging;
2342
+ if (L.active) return L.active;
1966
2343
  if (serverActive()) {
1967
2344
  console.warn(
1968
2345
  "[autter-runtime] initAutterLogging() ignored: initAutterServer() is already active and includes logging"
@@ -1982,14 +2359,16 @@ function initAutterLogging(options) {
1982
2359
  debugLog(`initialising logger-only service=${options.service} endpoint=${endpoint}`);
1983
2360
  function captureException2(error, attributes) {
1984
2361
  markCaptured(error);
1985
- const redacted = redact(attributes);
2362
+ const split = splitFingerprint(attributes);
2363
+ const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
2364
+ const redacted = redact(split.attributes);
1986
2365
  const span = trace3.getActiveSpan();
1987
2366
  if (mode === "auto" && span?.isRecording()) {
1988
2367
  noteOperationError(error);
1989
- notifyException(error, redacted);
2368
+ notifyException(error, { ...redacted, ...fingerprint });
1990
2369
  span.setAttributes({ "autter.severity": "error", ...redacted, ...structuredErrorAttributes(error) });
1991
- const declared = errorAttributes(error);
1992
- if (error instanceof Error) {
2370
+ const declared = { ...errorAttributes(error), ...fingerprint };
2371
+ if (isError(error)) {
1993
2372
  span.addEvent("exception", {
1994
2373
  "exception.type": error.name,
1995
2374
  "exception.message": error.message,
@@ -2005,12 +2384,12 @@ function initAutterLogging(options) {
2005
2384
  }
2006
2385
  span.setStatus({
2007
2386
  code: SpanStatusCode3.ERROR,
2008
- message: error instanceof Error ? error.message : String(error)
2387
+ message: isError(error) ? error.message : String(error)
2009
2388
  });
2010
2389
  return;
2011
2390
  }
2012
- notifyException(error, redacted);
2013
- captureExceptionAsLog(error, redacted);
2391
+ notifyException(error, { ...redacted, ...fingerprint });
2392
+ captureExceptionAsLog(error, { ...redacted, ...fingerprint });
2014
2393
  }
2015
2394
  const captured = /* @__PURE__ */ new WeakSet();
2016
2395
  configureRuntimeLogger({
@@ -2037,9 +2416,18 @@ function initAutterLogging(options) {
2037
2416
  }
2038
2417
  });
2039
2418
  setLoggerOnlyCapture(captureException2);
2040
- const onCrash = (error) => captureException2(error, { "autter.unhandled": true });
2041
- if (options.captureGlobalErrors !== false)
2419
+ const onCrash = (error) => {
2420
+ if (recordedAsRejection(error)) return;
2421
+ captureException2(error, { "autter.unhandled": true });
2422
+ };
2423
+ let uninstallRejections = null;
2424
+ if (options.captureGlobalErrors !== false) {
2042
2425
  process.on("uncaughtExceptionMonitor", onCrash);
2426
+ if (options.captureUnhandledRejections !== false)
2427
+ uninstallRejections = installUnhandledRejectionCapture(
2428
+ (reason) => captureException2(reason, { "autter.unhandled_rejection": true })
2429
+ );
2430
+ }
2043
2431
  registerFlushTarget("logging", { forceFlush: () => flushRuntimeLogs() });
2044
2432
  let autoFlushHandle = options.autoFlush === false ? null : installAutterAutoFlush();
2045
2433
  const handle = {
@@ -2047,15 +2435,17 @@ function initAutterLogging(options) {
2047
2435
  flush: flushRuntimeLogs,
2048
2436
  shutdown: async () => {
2049
2437
  process.off("uncaughtExceptionMonitor", onCrash);
2438
+ uninstallRejections?.();
2439
+ uninstallRejections = null;
2050
2440
  setLoggerOnlyCapture(null);
2051
2441
  autoFlushHandle?.dispose();
2052
2442
  autoFlushHandle = null;
2053
2443
  unregisterFlushTargets();
2054
- activeLogging = null;
2444
+ if (L.active === handle) L.active = null;
2055
2445
  await shutdownRuntimeLogger();
2056
2446
  }
2057
2447
  };
2058
- activeLogging = handle;
2448
+ L.active = handle;
2059
2449
  return handle;
2060
2450
  }
2061
2451
 
@@ -2152,11 +2542,16 @@ export {
2152
2542
  INGESTER_VERSION_HEADER,
2153
2543
  RuntimeError,
2154
2544
  SDK_IDENTITY,
2545
+ addBreadcrumb,
2546
+ autterErrorHandler,
2155
2547
  autterErrorResponse,
2156
2548
  autterFastify,
2549
+ autterFastifyErrors,
2550
+ autterKoa,
2157
2551
  autterRequests,
2158
2552
  captureException,
2159
2553
  captureMessage,
2554
+ captureRequestError,
2160
2555
  consoleSink,
2161
2556
  createBrowserRelayFetchHandler,
2162
2557
  createBrowserRelayHandler,
@@ -2191,6 +2586,7 @@ export {
2191
2586
  startCaughtExceptionSampler,
2192
2587
  toClientError,
2193
2588
  trackLlmCall,
2589
+ unhandledRejectionsMode,
2194
2590
  withLlmCall,
2195
2591
  withProcessSpan,
2196
2592
  withRuntimeOperation,