@autter/runtime-node 1.7.0 → 1.9.0

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
@@ -8,6 +8,7 @@ import {
8
8
  compileIgnore,
9
9
  configureRuntimeLogger,
10
10
  consoleSink,
11
+ createErrorFilter,
11
12
  createRequestState,
12
13
  createRuntimeLogger,
13
14
  currentOperation,
@@ -20,10 +21,12 @@ import {
20
21
  fingerprintEventAttributes,
21
22
  fingerprintValues,
22
23
  flushRuntimeLogs,
24
+ isCaptured,
23
25
  isError,
24
26
  isRuntimeErrorLike,
25
27
  local,
26
28
  makeRedactor,
29
+ markCaptured,
27
30
  normalizeRoutePath,
28
31
  noteOperationError,
29
32
  notifyException,
@@ -41,14 +44,17 @@ import {
41
44
  runtimeLogStats,
42
45
  runtimeLogger,
43
46
  sanitizeBreadcrumbs,
47
+ setHandledCapture,
44
48
  shutdownRuntimeLogger,
45
49
  splitFingerprint,
50
+ startLoggedErrorCapture,
51
+ stopLoggedErrorCapture,
46
52
  toClientError,
47
53
  withRuntimeOperation
48
- } from "./chunk-KA4AOZX2.js";
54
+ } from "./chunk-HHKQ3UCH.js";
49
55
  import {
50
56
  runDoctor
51
- } from "./chunk-QUC6OUE7.js";
57
+ } from "./chunk-JSGGUWRD.js";
52
58
  import {
53
59
  COMPAT_MANIFEST,
54
60
  CountingExporter,
@@ -70,7 +76,7 @@ import {
70
76
  sharedSlot,
71
77
  telemetryStats,
72
78
  unregisterFlushTargets
73
- } from "./chunk-LG52CW3V.js";
79
+ } from "./chunk-ZKM76SDP.js";
74
80
 
75
81
  // src/relay.ts
76
82
  var IpWindow = class {
@@ -95,6 +101,112 @@ function firstForwardedFor(value) {
95
101
  const raw = Array.isArray(value) ? value[0] : value;
96
102
  return raw ? (raw.split(",")[0] ?? "").trim() : "";
97
103
  }
104
+ var TRUST_AUTO = { mode: "auto" };
105
+ var warnedTrustProxy = false;
106
+ function resolveTrustProxy(value) {
107
+ if (value === void 0 || value === null || value === "auto") return TRUST_AUTO;
108
+ if (value === true) return { mode: "all" };
109
+ if (value === false || value === 0) return { mode: "never" };
110
+ if (typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 100) {
111
+ return { mode: "hops", hops: value };
112
+ }
113
+ if (typeof value === "string") {
114
+ const v = value.trim().toLowerCase();
115
+ if (v === "" || v === "auto") return TRUST_AUTO;
116
+ if (v === "false" || v === "0" || v === "no" || v === "off") return { mode: "never" };
117
+ if (v === "true" || v === "yes" || v === "on") return { mode: "all" };
118
+ if (/^[1-9][0-9]?$/.test(v)) return { mode: "hops", hops: Number(v) };
119
+ }
120
+ if (!warnedTrustProxy) {
121
+ warnedTrustProxy = true;
122
+ console.warn(
123
+ `[autter-runtime] relay: trustProxy ${JSON.stringify(value)} is not recognised; using "auto".`
124
+ );
125
+ }
126
+ return TRUST_AUTO;
127
+ }
128
+ var IPV4_RE = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
129
+ function normalizeAddress(entry) {
130
+ if (!entry) return null;
131
+ let a = entry.trim();
132
+ if (!a || a.length > 64) return null;
133
+ const bracketed = /^\[([0-9A-Fa-f:.]+)\](?::\d{1,5})?$/.exec(a);
134
+ if (bracketed) a = bracketed[1] ?? "";
135
+ else if (/^[\d.]+:\d{1,5}$/.test(a)) a = a.slice(0, a.lastIndexOf(":"));
136
+ a = a.toLowerCase();
137
+ if (a.startsWith("::ffff:") && IPV4_RE.test(a.slice(7))) a = a.slice(7);
138
+ if (IPV4_RE.test(a)) return a;
139
+ if (/^[0-9a-f:]+(:(\d{1,3}\.){3}\d{1,3})?$/.test(a) && a.includes(":") && a.split(":").length <= 9) {
140
+ return a;
141
+ }
142
+ return null;
143
+ }
144
+ function isPrivateAddress(a) {
145
+ if (IPV4_RE.test(a)) {
146
+ const [x, y] = a.split(".").map(Number);
147
+ return x === 10 || x === 127 || x === 0 || x === 169 && y === 254 || x === 172 && y >= 16 && y <= 31 || x === 192 && y === 168 || x === 100 && y >= 64 && y <= 127;
148
+ }
149
+ return a === "::1" || a === "::" || /^f[cd]/.test(a) || /^fe[89ab]/.test(a);
150
+ }
151
+ function forwardedEntries(value) {
152
+ const raw = Array.isArray(value) ? value.join(",") : value;
153
+ if (!raw) return [];
154
+ const out = [];
155
+ for (const part of raw.split(",")) {
156
+ const a = normalizeAddress(part);
157
+ if (a) out.push(a);
158
+ }
159
+ return out;
160
+ }
161
+ function rightmostPublic(entries) {
162
+ for (let i = entries.length - 1; i >= 0; i--) {
163
+ const a = entries[i];
164
+ if (a && !isPrivateAddress(a)) return a;
165
+ }
166
+ return null;
167
+ }
168
+ function fromCustom(opts, request) {
169
+ if (!opts.clientIp) return null;
170
+ try {
171
+ const v = opts.clientIp(request);
172
+ return typeof v === "string" && v.trim() ? v.trim().slice(0, 64) : null;
173
+ } catch {
174
+ return null;
175
+ }
176
+ }
177
+ function fetchClientKey(request, opts) {
178
+ const custom = fromCustom(opts, request);
179
+ if (custom) return custom;
180
+ const trust = resolveTrustProxy(opts.trustProxy);
181
+ if (trust.mode === "never") return "shared";
182
+ const header = request.headers.get("x-forwarded-for");
183
+ if (trust.mode === "all") return firstForwardedFor(header) || "shared";
184
+ const entries = forwardedEntries(header);
185
+ if (trust.mode === "hops") {
186
+ return entries[Math.max(0, entries.length - trust.hops)] ?? "shared";
187
+ }
188
+ const cf = request.cf;
189
+ if (cf !== null && typeof cf === "object") {
190
+ const viaCf = normalizeAddress(request.headers.get("cf-connecting-ip"));
191
+ if (viaCf) return viaCf;
192
+ }
193
+ return rightmostPublic(entries) ?? entries[entries.length - 1] ?? "shared";
194
+ }
195
+ function nodeClientKey(req, opts) {
196
+ const custom = fromCustom(opts, req);
197
+ if (custom) return custom;
198
+ const trust = resolveTrustProxy(opts.trustProxy);
199
+ const peer = normalizeAddress(req.socket?.remoteAddress);
200
+ if (trust.mode === "never") return peer ?? "unknown";
201
+ const header = req.headers["x-forwarded-for"];
202
+ if (trust.mode === "all") return firstForwardedFor(header) || peer || "unknown";
203
+ const entries = forwardedEntries(header);
204
+ if (trust.mode === "hops") {
205
+ return entries[Math.max(0, entries.length - trust.hops)] ?? peer ?? "unknown";
206
+ }
207
+ if (peer && !isPrivateAddress(peer)) return peer;
208
+ return rightmostPublic(entries) ?? peer ?? entries[entries.length - 1] ?? "unknown";
209
+ }
98
210
  var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
99
211
  var DEFAULT_MAX_BODY = 64 * 1024;
100
212
  var MAX_EVENTS = 50;
@@ -328,7 +440,7 @@ function createBrowserRelayFetchHandler(opts) {
328
440
  return new Response(null, { status: 405 });
329
441
  }
330
442
  if (limiter) {
331
- const ip = firstForwardedFor(request.headers.get("x-forwarded-for")) || "unknown";
443
+ const ip = fetchClientKey(request, opts);
332
444
  if (!limiter.allow(ip)) {
333
445
  return new Response(JSON.stringify({ error: "rate limit exceeded" }), {
334
446
  status: 429
@@ -394,7 +506,7 @@ function createBrowserRelayHandler(opts) {
394
506
  return;
395
507
  }
396
508
  if (limiter) {
397
- const ip = firstForwardedFor(req.headers["x-forwarded-for"]) || req.socket?.remoteAddress || "unknown";
509
+ const ip = nodeClientKey(req, opts);
398
510
  if (!limiter.allow(ip)) {
399
511
  respond(res, 429, { error: "rate limit exceeded" });
400
512
  return;
@@ -745,7 +857,6 @@ import { context, trace } from "@opentelemetry/api";
745
857
  import { getRPCMetadata, RPCType } from "@opentelemetry/core";
746
858
  var REQUEST_STATE = /* @__PURE__ */ Symbol.for("autter.runtime.request");
747
859
  var BOUND = /* @__PURE__ */ Symbol.for("autter.runtime.bound");
748
- var CAPTURED = sharedSlot("requests", () => ({ captured: /* @__PURE__ */ new WeakSet() })).captured;
749
860
  var capturer = () => {
750
861
  };
751
862
  function setRequestExceptionCapturer(fn) {
@@ -756,8 +867,14 @@ var annotator = () => {
756
867
  function setRequestSpanAnnotator(fn) {
757
868
  annotator = fn;
758
869
  }
759
- function markCaptured(error) {
760
- if (error && typeof error === "object") CAPTURED.add(error);
870
+ function decide(rule, error, fallback) {
871
+ if (rule === void 0) return fallback;
872
+ if (typeof rule !== "function") return rule;
873
+ try {
874
+ return rule(error) === true;
875
+ } catch {
876
+ return false;
877
+ }
761
878
  }
762
879
  function expressRouteOf(req) {
763
880
  const props = req;
@@ -788,7 +905,7 @@ function captureAtBoundary(error, state, rule = void 0, attributes) {
788
905
  wanted = false;
789
906
  }
790
907
  if (!wanted) return;
791
- if (error && typeof error === "object" && CAPTURED.has(error)) return;
908
+ if (isCaptured(error)) return;
792
909
  markCaptured(error);
793
910
  try {
794
911
  const run = () => onRequestSpan(() => capturer(error, attributes));
@@ -802,7 +919,7 @@ function requestStateOf(req) {
802
919
  return req[REQUEST_STATE];
803
920
  }
804
921
  function captureRequestError(error, attributes) {
805
- if (error && typeof error === "object" && CAPTURED.has(error)) {
922
+ if (isCaptured(error)) {
806
923
  if (!attributes) return;
807
924
  try {
808
925
  onRequestSpan(() => annotator(attributes));
@@ -894,7 +1011,8 @@ function finishRequest(state, input) {
894
1011
  ...state.error !== void 0 ? { error: state.error } : {},
895
1012
  ...state.errorThrown ? { thrown: true } : {},
896
1013
  status: input.status,
897
- aborted: input.aborted
1014
+ aborted: input.aborted,
1015
+ ...state.degraded ? { degraded: true } : {}
898
1016
  });
899
1017
  const span = trace.getActiveSpan();
900
1018
  if (span?.isRecording()) span.setAttribute("autter.operation.outcome", state.outcome);
@@ -1119,9 +1237,15 @@ function withRuntimeRequest(handler, options = {}) {
1119
1237
  try {
1120
1238
  return await handler(request, ...rest);
1121
1239
  } catch (error) {
1122
- noteOperationError(error, true, state);
1123
- captureAtBoundary(error, state, true);
1124
- throw error;
1240
+ if (decide(options.capture, error, true)) {
1241
+ noteOperationError(error, true, state);
1242
+ captureAtBoundary(error, state, true);
1243
+ }
1244
+ if (!decide(options.errorResponse, error, false)) throw error;
1245
+ return new Response(JSON.stringify(toClientError(error, requestId)), {
1246
+ status: responseStatusOf(error),
1247
+ headers: { "content-type": "application/json; charset=utf-8" }
1248
+ });
1125
1249
  }
1126
1250
  });
1127
1251
  status = response.status;
@@ -1319,6 +1443,8 @@ var S = sharedSlot("server", () => ({
1319
1443
  alwaysOnProvider: null,
1320
1444
  redactor: makeRedactor(true),
1321
1445
  loggerOnlyCapture: null,
1446
+ handled: null,
1447
+ loggerOnlyHandled: null,
1322
1448
  hookRequests: false
1323
1449
  }));
1324
1450
  var LLM_MARKER_ATTRIBUTES = [
@@ -1718,6 +1844,7 @@ function initAutterServer(options) {
1718
1844
  // its span-fed per-route counts.
1719
1845
  "autter.metrics_routes": true
1720
1846
  });
1847
+ const errorFilter = createErrorFilter({ ignoreErrors: options.ignoreErrors, sampleRate: options.sampleRate });
1721
1848
  const rootSampler = new TraceIdRatioBasedSampler(options.traceSampleRate ?? 0.01);
1722
1849
  const headSampler = new ParentBasedSampler({
1723
1850
  root: rootSampler,
@@ -1793,6 +1920,16 @@ function initAutterServer(options) {
1793
1920
  const processTracer = alwaysOnProvider.getTracer("autter-processes");
1794
1921
  const llmTracer = alwaysOnProvider.getTracer("autter-llm");
1795
1922
  function captureException2(error, attributes) {
1923
+ const isErrValue = isError(error);
1924
+ const reported = errorFilter({
1925
+ type: isErrValue ? String(error.name ?? "") : "Error",
1926
+ message: isErrValue ? String(error.message ?? "") : String(error),
1927
+ crash: attributes?.["autter.unhandled"] === true || attributes?.["autter.unhandled_rejection"] === true
1928
+ });
1929
+ if (!reported) {
1930
+ markCaptured(error);
1931
+ return;
1932
+ }
1796
1933
  errorTraceBuffer?.retainActiveTrace();
1797
1934
  telemetryStats.markCaptured();
1798
1935
  const isErr = isError(error);
@@ -1845,6 +1982,7 @@ function initAutterServer(options) {
1845
1982
  if (!reuseActive) span.end();
1846
1983
  }
1847
1984
  function captureMessage2(message, severity = "warning", attributes) {
1985
+ if (!errorFilter({ type: "Message", message: String(message), severity })) return;
1848
1986
  if (severity === "error" || severity === "fatal") {
1849
1987
  errorTraceBuffer?.retainActiveTrace();
1850
1988
  }
@@ -1865,6 +2003,7 @@ function initAutterServer(options) {
1865
2003
  span.end();
1866
2004
  }
1867
2005
  function reportOutcome2(name, message, attributes) {
2006
+ if (!errorFilter({ type: "OutcomeFailure", message: `${name}: ${message}` })) return;
1868
2007
  errorTraceBuffer?.retainActiveTrace();
1869
2008
  telemetryStats.markCaptured();
1870
2009
  const activeSpan = trace2.getActiveSpan();
@@ -1882,6 +2021,37 @@ function initAutterServer(options) {
1882
2021
  span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
1883
2022
  if (!reuseActive) span.end();
1884
2023
  }
2024
+ function captureHandled(error, attributes, severity) {
2025
+ const isErr = isError(error);
2026
+ if (!errorFilter({
2027
+ type: isErr ? String(error.name ?? "") : "Error",
2028
+ message: isErr ? String(error.message ?? "") : String(error),
2029
+ severity
2030
+ })) return;
2031
+ if (severity === "error") errorTraceBuffer?.retainActiveTrace();
2032
+ telemetryStats.markCaptured();
2033
+ const message = redactor.text(isErr ? error.message : String(error));
2034
+ const stack = isErr && error.stack ? error.stack : new Error().stack?.split("\n").filter((line, i) => i === 0 || !/captureHandled|report(?:Degraded|LoggedError)/.test(line)).join("\n");
2035
+ const split = splitFingerprint(attributes);
2036
+ const fingerprint = fingerprintEventAttributes(split.fingerprint, redactor.text);
2037
+ const redacted = redactor(split.attributes);
2038
+ const activeSpan = trace2.getActiveSpan();
2039
+ const reuseActive = activeSpan?.isRecording() === true;
2040
+ const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
2041
+ markCaptured(error);
2042
+ notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
2043
+ span.addEvent("exception", {
2044
+ ...redacted,
2045
+ "exception.type": isErr ? error.name : "Error",
2046
+ "exception.message": message,
2047
+ ...stack ? { "exception.stacktrace": redactor.text(stack) } : {},
2048
+ "autter.severity": severity,
2049
+ ...errorAttributes(error),
2050
+ ...fingerprint,
2051
+ ...breadcrumbEventAttributes(redactor.text, error)
2052
+ });
2053
+ if (!reuseActive) span.end();
2054
+ }
1885
2055
  configureRuntimeLogger({
1886
2056
  endpoint,
1887
2057
  apiKey: options.apiKey,
@@ -1895,6 +2065,8 @@ function initAutterServer(options) {
1895
2065
  reportOutcome: reportOutcome2
1896
2066
  });
1897
2067
  S.hookRequests = options.logging?.requests === true;
2068
+ S.handled = captureHandled;
2069
+ startLoggedErrorCapture(options.captureLoggedErrors);
1898
2070
  let onCrash = null;
1899
2071
  let uninstallRejections = null;
1900
2072
  if (options.captureGlobalErrors !== false) {
@@ -1941,6 +2113,7 @@ function initAutterServer(options) {
1941
2113
  withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
1942
2114
  trackLlmCall: (call) => recordLlmCall(llmTracer, call),
1943
2115
  shutdown: async () => {
2116
+ if (S.active === server) stopLoggedErrorCapture();
1944
2117
  const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
1945
2118
  if (onCrash) process.off("uncaughtExceptionMonitor", onCrash);
1946
2119
  uninstallRejections?.();
@@ -1948,6 +2121,7 @@ function initAutterServer(options) {
1948
2121
  if (S.active === server) {
1949
2122
  S.active = null;
1950
2123
  S.alwaysOnProvider = null;
2124
+ S.handled = null;
1951
2125
  S.hookRequests = false;
1952
2126
  S.redactor = makeRedactor(true);
1953
2127
  }
@@ -1983,13 +2157,39 @@ function captureException(error, attributes) {
1983
2157
  recordError(span, error);
1984
2158
  span.end();
1985
2159
  }
1986
- function setLoggerOnlyCapture(fn) {
2160
+ function setLoggerOnlyCapture(fn, handled = null) {
1987
2161
  S.loggerOnlyCapture = fn;
2162
+ S.loggerOnlyHandled = handled;
2163
+ }
2164
+ function captureHandledError(error, attributes, severity) {
2165
+ if (S.active) {
2166
+ S.handled?.(error, attributes, severity);
2167
+ return;
2168
+ }
2169
+ if (S.loggerOnlyHandled) {
2170
+ S.loggerOnlyHandled(error, attributes, severity);
2171
+ return;
2172
+ }
2173
+ markCaptured(error);
2174
+ const split = splitFingerprint(attributes);
2175
+ notifyException(error, { ...split.attributes, "autter.severity": severity });
2176
+ const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error");
2177
+ span.addEvent("exception", {
2178
+ ...S.redactor(split.attributes),
2179
+ "exception.type": isError(error) ? error.name : "Error",
2180
+ "exception.message": S.redactor.text(isError(error) ? error.message : String(error)),
2181
+ ...isError(error) && error.stack ? { "exception.stacktrace": S.redactor.text(error.stack) } : {},
2182
+ "autter.severity": severity,
2183
+ ...errorAttributes(error),
2184
+ ...fingerprintEventAttributes(split.fingerprint, S.redactor.text)
2185
+ });
2186
+ span.end();
1988
2187
  }
1989
2188
  function serverActive() {
1990
2189
  return S.active !== null;
1991
2190
  }
1992
2191
  setRequestExceptionCapturer((error, attributes) => captureException(error, attributes));
2192
+ setHandledCapture(captureHandledError);
1993
2193
  setRequestSpanAnnotator((attributes) => {
1994
2194
  const span = trace2.getActiveSpan();
1995
2195
  if (span?.isRecording()) span.setAttributes(S.redactor(splitFingerprint(attributes).attributes));
@@ -2418,8 +2618,16 @@ function initAutterLogging(options) {
2418
2618
  const redact = makeRedactor(options.redactAttributes ?? true);
2419
2619
  const mode = options.exceptions ?? "auto";
2420
2620
  debugLog(`initialising logger-only service=${options.service} endpoint=${endpoint}`);
2621
+ const errorFilter = createErrorFilter({ ignoreErrors: options.ignoreErrors, sampleRate: options.sampleRate });
2421
2622
  function captureException2(error, attributes) {
2422
2623
  markCaptured(error);
2624
+ const isErrValue = isError(error);
2625
+ const reported = errorFilter({
2626
+ type: isErrValue ? String(error.name ?? "") : "Error",
2627
+ message: isErrValue ? String(error.message ?? "") : String(error),
2628
+ crash: attributes?.["autter.unhandled"] === true || attributes?.["autter.unhandled_rejection"] === true
2629
+ });
2630
+ if (!reported) return;
2423
2631
  const split = splitFingerprint(attributes);
2424
2632
  const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
2425
2633
  const redacted = redact(split.attributes);
@@ -2452,6 +2660,33 @@ function initAutterLogging(options) {
2452
2660
  notifyException(error, { ...redacted, ...fingerprint });
2453
2661
  captureExceptionAsLog(error, { ...redacted, ...fingerprint });
2454
2662
  }
2663
+ function captureHandled(error, attributes, severity) {
2664
+ markCaptured(error);
2665
+ const isErrValue = isError(error);
2666
+ if (!errorFilter({
2667
+ type: isErrValue ? String(error.name ?? "") : "Error",
2668
+ message: isErrValue ? String(error.message ?? "") : String(error),
2669
+ severity
2670
+ })) return;
2671
+ const split = splitFingerprint(attributes);
2672
+ const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
2673
+ const redacted = redact(split.attributes);
2674
+ notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
2675
+ const span = trace3.getActiveSpan();
2676
+ if (mode === "auto" && span?.isRecording()) {
2677
+ span.addEvent("exception", {
2678
+ ...redacted,
2679
+ "exception.type": isErrValue ? error.name : "Error",
2680
+ "exception.message": redact.text(isErrValue ? error.message : String(error)),
2681
+ ...isErrValue && error.stack ? { "exception.stacktrace": redact.text(error.stack) } : {},
2682
+ "autter.severity": severity,
2683
+ ...errorAttributes(error),
2684
+ ...fingerprint
2685
+ });
2686
+ return;
2687
+ }
2688
+ captureExceptionAsLog(error, { ...redacted, ...fingerprint }, severity);
2689
+ }
2455
2690
  const captured = /* @__PURE__ */ new WeakSet();
2456
2691
  configureRuntimeLogger({
2457
2692
  endpoint,
@@ -2476,7 +2711,8 @@ function initAutterLogging(options) {
2476
2711
  captureException2(error);
2477
2712
  }
2478
2713
  });
2479
- setLoggerOnlyCapture(captureException2);
2714
+ setLoggerOnlyCapture(captureException2, captureHandled);
2715
+ startLoggedErrorCapture(options.captureLoggedErrors);
2480
2716
  const onCrash = (error) => {
2481
2717
  if (recordedAsRejection(error)) return;
2482
2718
  captureException2(error, { "autter.unhandled": true });
@@ -2495,6 +2731,7 @@ function initAutterLogging(options) {
2495
2731
  captureException: captureException2,
2496
2732
  flush: flushRuntimeLogs,
2497
2733
  shutdown: async () => {
2734
+ if (L.active === handle) stopLoggedErrorCapture();
2498
2735
  process.off("uncaughtExceptionMonitor", onCrash);
2499
2736
  uninstallRejections?.();
2500
2737
  uninstallRejections = null;
@@ -166,10 +166,19 @@ declare function isRuntimeErrorLike(err: unknown): err is RuntimeErrorLike;
166
166
  interface ErrorAttributeOptions {
167
167
  /** Also emit `exception.cause.N.*` for the cause chain (default true). */
168
168
  causes?: boolean;
169
+ /**
170
+ * With `causes`, also emit what only an occurrence record needs (default
171
+ * true): each cause's stack (`exception.cause.N.stacktrace`, RF-06: a
172
+ * wrapped error groups with its root cause) and the debug ids of the
173
+ * bundles in the stacks (`autter.debug_ids`, RF-05: server source maps).
174
+ * Request and operation summaries pass false.
175
+ */
176
+ stacks?: boolean;
169
177
  }
170
178
  /**
171
- * Wire attributes for an error: `autter.error.code|why|fix|link|status|expected`
172
- * and `exception.cause.N.type|message|code` (N = 1..5). Never includes
179
+ * Wire attributes for an error: `autter.error.code|why|fix|link|status|expected`,
180
+ * `exception.cause.N.type|message|code|stacktrace` (N = 1..5) and
181
+ * `autter.debug_ids` (see ErrorAttributeOptions.stacks). Never includes
173
182
  * `internal` — see `errorInternal`.
174
183
  */
175
184
  declare function errorAttributes(err: unknown, options?: ErrorAttributeOptions): Record<string, string | number | boolean>;
@@ -250,6 +259,53 @@ interface BreadcrumbInput {
250
259
  at?: string | number | Date;
251
260
  }
252
261
 
262
+ /**
263
+ * Handled errors (RF-01, RF-02): the building blocks every server SDK shares
264
+ * for errors the app caught instead of letting them crash.
265
+ *
266
+ * - Caught errors: an `Error` the app passes to `console.error`, a logger
267
+ * (pino, winston, bunyan) or `runtimeLogger.error` is reported as an
268
+ * occurrence at `error` severity, marked `autter.handled = true`,
269
+ * `autter.handling = "caught"`, with the log line as context
270
+ * (`autter.log.source`, `autter.log.message`).
271
+ * - Deliberate fallbacks: `runtimeContext.degraded(err, reason)` reports the
272
+ * error at `warning` severity (never an incident), marked
273
+ * `autter.handling = "deliberate"` with `autter.degraded.reason`.
274
+ *
275
+ * A burst limit keeps a looping error from flooding: per kind (error type
276
+ * and throw site) ten at once, then one every ten seconds. What it leaves
277
+ * out is counted, and the count rides on the next record of that kind as
278
+ * `autter.caught.dropped`, which the ingester adds to `runtime_ingest_drops`
279
+ * (reason `caught_limited`).
280
+ */
281
+ type LoggedErrorSource = "console" | "pino" | "winston" | "bunyan" | "runtimeLogger";
282
+ /**
283
+ * `captureLoggedErrors`: `true` (default) reports errors logged through
284
+ * every source the SDK supports, `false` none; an object turns single
285
+ * sources off, e.g. `{ console: false }`.
286
+ */
287
+ type CaptureLoggedErrorsOption = boolean | Partial<Record<LoggedErrorSource, boolean>>;
288
+
289
+ /**
290
+ * Client-side volume control shared by the server SDKs (runtime-node,
291
+ * runtime-edge; 1.8.0 / 1.2.0, EI-38): `ignoreErrors` and `sampleRate`.
292
+ * The browser has its own copy in `@autter/runtime-browser/filters`.
293
+ *
294
+ * - `ignoreErrors`: strings or RegExps. A string matches when the error's
295
+ * type or message contains it (case-sensitive, like the message itself);
296
+ * a RegExp is tested against the type, the message and
297
+ * `"<type>: <message>"`. A match is never reported, whatever its severity.
298
+ * - `sampleRate`: 0..1, the share of errors reported. Errors only: warnings
299
+ * and info messages are never sampled, and neither is a fatal error, a
300
+ * crash (uncaught exception) or an unhandled rejection, which may be one.
301
+ * Out of range or not a number: everything is reported.
302
+ *
303
+ * What is filtered here never leaves the process and is counted nowhere.
304
+ * To leave errors out of every SDK at once, and see how many were left out,
305
+ * use suppression rules in Autter (applied by the ingester, 1.9.0).
306
+ */
307
+ type IgnoreErrorPattern = string | RegExp;
308
+
253
309
  /**
254
310
  * Where runtime records go. The logger pipeline is
255
311
  * `enrich → redact/bound → sinks[]`; each sink receives the same immutable
@@ -338,4 +394,4 @@ interface MemorySink extends RuntimeSink {
338
394
  /** Keeps every record in memory — for tests (`@autter/runtime-node/testing`). */
339
395
  declare function memorySink(): MemorySink;
340
396
 
341
- export { errorAttributes as A, type BreadcrumbInput as B, type CapturedException as C, fileSink as D, isRuntimeErrorLike as E, type FileSinkOptions as F, otlpSink as G, redactAttributes as H, redactText as I, toClientError as J, type MemorySink as M, type OtlpSinkOptions as O, type RuntimeEvent as R, type RuntimeOutcome as a, type RedactOptions as b, type Redactor$1 as c, type RuntimeLogLevel as d, type RuntimeOperationKind as e, type RuntimeSink as f, type RuntimeLogContext as g, type RuntimeCarrier as h, type Breadcrumb as i, type BreadcrumbLevel as j, type BreadcrumbType as k, CODE_PATTERN as l, memorySink as m, type ClientErrorBody as n, type ConsoleSinkOptions as o, RuntimeError as p, type RuntimeErrorCatalog as q, type RuntimeErrorDefinition as r, type RuntimeErrorExtras as s, type RuntimeErrorFactory as t, type RuntimeErrorLike as u, type RuntimeErrorOptions as v, type RuntimeLogContextValue as w, type RuntimeSinkContext as x, consoleSink as y, defineRuntimeErrors as z };
397
+ export { defineRuntimeErrors as A, type BreadcrumbInput as B, type CapturedException as C, errorAttributes as D, fileSink as E, type FileSinkOptions as F, isRuntimeErrorLike as G, otlpSink as H, type IgnoreErrorPattern as I, redactAttributes as J, redactText as K, type LoggedErrorSource as L, type MemorySink as M, toClientError as N, type OtlpSinkOptions as O, type RuntimeEvent as R, type RuntimeOutcome as a, type RedactOptions as b, type Redactor$1 as c, type RuntimeLogLevel as d, type RuntimeOperationKind as e, type RuntimeSink as f, type RuntimeLogContext as g, type RuntimeCarrier as h, type CaptureLoggedErrorsOption as i, type Breadcrumb as j, type BreadcrumbLevel as k, type BreadcrumbType as l, memorySink as m, CODE_PATTERN as n, type ClientErrorBody as o, type ConsoleSinkOptions as p, RuntimeError as q, type RuntimeErrorCatalog as r, type RuntimeErrorDefinition as s, type RuntimeErrorExtras as t, type RuntimeErrorFactory as u, type RuntimeErrorLike as v, type RuntimeErrorOptions as w, type RuntimeLogContextValue as x, type RuntimeSinkContext as y, consoleSink as z };
@@ -166,10 +166,19 @@ declare function isRuntimeErrorLike(err: unknown): err is RuntimeErrorLike;
166
166
  interface ErrorAttributeOptions {
167
167
  /** Also emit `exception.cause.N.*` for the cause chain (default true). */
168
168
  causes?: boolean;
169
+ /**
170
+ * With `causes`, also emit what only an occurrence record needs (default
171
+ * true): each cause's stack (`exception.cause.N.stacktrace`, RF-06: a
172
+ * wrapped error groups with its root cause) and the debug ids of the
173
+ * bundles in the stacks (`autter.debug_ids`, RF-05: server source maps).
174
+ * Request and operation summaries pass false.
175
+ */
176
+ stacks?: boolean;
169
177
  }
170
178
  /**
171
- * Wire attributes for an error: `autter.error.code|why|fix|link|status|expected`
172
- * and `exception.cause.N.type|message|code` (N = 1..5). Never includes
179
+ * Wire attributes for an error: `autter.error.code|why|fix|link|status|expected`,
180
+ * `exception.cause.N.type|message|code|stacktrace` (N = 1..5) and
181
+ * `autter.debug_ids` (see ErrorAttributeOptions.stacks). Never includes
173
182
  * `internal` — see `errorInternal`.
174
183
  */
175
184
  declare function errorAttributes(err: unknown, options?: ErrorAttributeOptions): Record<string, string | number | boolean>;
@@ -250,6 +259,53 @@ interface BreadcrumbInput {
250
259
  at?: string | number | Date;
251
260
  }
252
261
 
262
+ /**
263
+ * Handled errors (RF-01, RF-02): the building blocks every server SDK shares
264
+ * for errors the app caught instead of letting them crash.
265
+ *
266
+ * - Caught errors: an `Error` the app passes to `console.error`, a logger
267
+ * (pino, winston, bunyan) or `runtimeLogger.error` is reported as an
268
+ * occurrence at `error` severity, marked `autter.handled = true`,
269
+ * `autter.handling = "caught"`, with the log line as context
270
+ * (`autter.log.source`, `autter.log.message`).
271
+ * - Deliberate fallbacks: `runtimeContext.degraded(err, reason)` reports the
272
+ * error at `warning` severity (never an incident), marked
273
+ * `autter.handling = "deliberate"` with `autter.degraded.reason`.
274
+ *
275
+ * A burst limit keeps a looping error from flooding: per kind (error type
276
+ * and throw site) ten at once, then one every ten seconds. What it leaves
277
+ * out is counted, and the count rides on the next record of that kind as
278
+ * `autter.caught.dropped`, which the ingester adds to `runtime_ingest_drops`
279
+ * (reason `caught_limited`).
280
+ */
281
+ type LoggedErrorSource = "console" | "pino" | "winston" | "bunyan" | "runtimeLogger";
282
+ /**
283
+ * `captureLoggedErrors`: `true` (default) reports errors logged through
284
+ * every source the SDK supports, `false` none; an object turns single
285
+ * sources off, e.g. `{ console: false }`.
286
+ */
287
+ type CaptureLoggedErrorsOption = boolean | Partial<Record<LoggedErrorSource, boolean>>;
288
+
289
+ /**
290
+ * Client-side volume control shared by the server SDKs (runtime-node,
291
+ * runtime-edge; 1.8.0 / 1.2.0, EI-38): `ignoreErrors` and `sampleRate`.
292
+ * The browser has its own copy in `@autter/runtime-browser/filters`.
293
+ *
294
+ * - `ignoreErrors`: strings or RegExps. A string matches when the error's
295
+ * type or message contains it (case-sensitive, like the message itself);
296
+ * a RegExp is tested against the type, the message and
297
+ * `"<type>: <message>"`. A match is never reported, whatever its severity.
298
+ * - `sampleRate`: 0..1, the share of errors reported. Errors only: warnings
299
+ * and info messages are never sampled, and neither is a fatal error, a
300
+ * crash (uncaught exception) or an unhandled rejection, which may be one.
301
+ * Out of range or not a number: everything is reported.
302
+ *
303
+ * What is filtered here never leaves the process and is counted nowhere.
304
+ * To leave errors out of every SDK at once, and see how many were left out,
305
+ * use suppression rules in Autter (applied by the ingester, 1.9.0).
306
+ */
307
+ type IgnoreErrorPattern = string | RegExp;
308
+
253
309
  /**
254
310
  * Where runtime records go. The logger pipeline is
255
311
  * `enrich → redact/bound → sinks[]`; each sink receives the same immutable
@@ -338,4 +394,4 @@ interface MemorySink extends RuntimeSink {
338
394
  /** Keeps every record in memory — for tests (`@autter/runtime-node/testing`). */
339
395
  declare function memorySink(): MemorySink;
340
396
 
341
- export { errorAttributes as A, type BreadcrumbInput as B, type CapturedException as C, fileSink as D, isRuntimeErrorLike as E, type FileSinkOptions as F, otlpSink as G, redactAttributes as H, redactText as I, toClientError as J, type MemorySink as M, type OtlpSinkOptions as O, type RuntimeEvent as R, type RuntimeOutcome as a, type RedactOptions as b, type Redactor$1 as c, type RuntimeLogLevel as d, type RuntimeOperationKind as e, type RuntimeSink as f, type RuntimeLogContext as g, type RuntimeCarrier as h, type Breadcrumb as i, type BreadcrumbLevel as j, type BreadcrumbType as k, CODE_PATTERN as l, memorySink as m, type ClientErrorBody as n, type ConsoleSinkOptions as o, RuntimeError as p, type RuntimeErrorCatalog as q, type RuntimeErrorDefinition as r, type RuntimeErrorExtras as s, type RuntimeErrorFactory as t, type RuntimeErrorLike as u, type RuntimeErrorOptions as v, type RuntimeLogContextValue as w, type RuntimeSinkContext as x, consoleSink as y, defineRuntimeErrors as z };
397
+ export { defineRuntimeErrors as A, type BreadcrumbInput as B, type CapturedException as C, errorAttributes as D, fileSink as E, type FileSinkOptions as F, isRuntimeErrorLike as G, otlpSink as H, type IgnoreErrorPattern as I, redactAttributes as J, redactText as K, type LoggedErrorSource as L, type MemorySink as M, toClientError as N, type OtlpSinkOptions as O, type RuntimeEvent as R, type RuntimeOutcome as a, type RedactOptions as b, type Redactor$1 as c, type RuntimeLogLevel as d, type RuntimeOperationKind as e, type RuntimeSink as f, type RuntimeLogContext as g, type RuntimeCarrier as h, type CaptureLoggedErrorsOption as i, type Breadcrumb as j, type BreadcrumbLevel as k, type BreadcrumbType as l, memorySink as m, CODE_PATTERN as n, type ClientErrorBody as o, type ConsoleSinkOptions as p, RuntimeError as q, type RuntimeErrorCatalog as r, type RuntimeErrorDefinition as s, type RuntimeErrorExtras as t, type RuntimeErrorFactory as u, type RuntimeErrorLike as v, type RuntimeErrorOptions as w, type RuntimeLogContextValue as x, type RuntimeSinkContext as y, consoleSink as z };