@autter/runtime-node 1.8.0 → 1.9.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
@@ -21,10 +21,12 @@ import {
21
21
  fingerprintEventAttributes,
22
22
  fingerprintValues,
23
23
  flushRuntimeLogs,
24
+ isCaptured,
24
25
  isError,
25
26
  isRuntimeErrorLike,
26
27
  local,
27
28
  makeRedactor,
29
+ markCaptured,
28
30
  normalizeRoutePath,
29
31
  noteOperationError,
30
32
  notifyException,
@@ -42,14 +44,17 @@ import {
42
44
  runtimeLogStats,
43
45
  runtimeLogger,
44
46
  sanitizeBreadcrumbs,
47
+ setHandledCapture,
45
48
  shutdownRuntimeLogger,
46
49
  splitFingerprint,
50
+ startLoggedErrorCapture,
51
+ stopLoggedErrorCapture,
47
52
  toClientError,
48
53
  withRuntimeOperation
49
- } from "./chunk-QKY2OQSO.js";
54
+ } from "./chunk-D3PJ7TQ4.js";
50
55
  import {
51
56
  runDoctor
52
- } from "./chunk-UC3QH4PC.js";
57
+ } from "./chunk-FYMEBULO.js";
53
58
  import {
54
59
  COMPAT_MANIFEST,
55
60
  CountingExporter,
@@ -71,7 +76,7 @@ import {
71
76
  sharedSlot,
72
77
  telemetryStats,
73
78
  unregisterFlushTargets
74
- } from "./chunk-B5ZMOCEM.js";
79
+ } from "./chunk-VIP2EL47.js";
75
80
 
76
81
  // src/relay.ts
77
82
  var IpWindow = class {
@@ -96,6 +101,112 @@ function firstForwardedFor(value) {
96
101
  const raw = Array.isArray(value) ? value[0] : value;
97
102
  return raw ? (raw.split(",")[0] ?? "").trim() : "";
98
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
+ }
99
210
  var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
100
211
  var DEFAULT_MAX_BODY = 64 * 1024;
101
212
  var MAX_EVENTS = 50;
@@ -290,20 +401,38 @@ function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
290
401
  events
291
402
  };
292
403
  }
404
+ var FORWARD_ATTEMPTS = 3;
405
+ var FORWARD_MAX_WAIT_MS = 1e4;
293
406
  function forward(payload, opts) {
294
407
  const endpoint = (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
295
408
  const url = `${endpoint}/v1/browser`;
296
409
  const compat = opts.compatCheck !== false;
297
410
  if (compat) ensureCompatConfigured(endpoint);
298
- void fetch(url, {
299
- method: "POST",
300
- headers: {
301
- "content-type": "application/json",
302
- authorization: `Bearer ${opts.apiKey}`
303
- },
304
- body: JSON.stringify(payload),
305
- signal: AbortSignal.timeout(1e4)
306
- }).then((response) => {
411
+ const body = JSON.stringify(payload);
412
+ const attempt = async (n) => {
413
+ let response;
414
+ try {
415
+ response = await fetch(url, {
416
+ method: "POST",
417
+ headers: {
418
+ "content-type": "application/json",
419
+ authorization: `Bearer ${opts.apiKey}`
420
+ },
421
+ body,
422
+ signal: AbortSignal.timeout(1e4)
423
+ });
424
+ } catch (err) {
425
+ if (n >= FORWARD_ATTEMPTS) throw err;
426
+ await sleep(1e3 * 2 ** (n - 1));
427
+ return attempt(n + 1);
428
+ }
429
+ if ((response.status === 429 || response.status >= 500) && n < FORWARD_ATTEMPTS) {
430
+ const seconds = Number(response.headers.get("retry-after"));
431
+ await response.body?.cancel().catch(() => {
432
+ });
433
+ await sleep(seconds > 0 ? Math.min(seconds * 1e3, FORWARD_MAX_WAIT_MS) : 1e3 * 2 ** (n - 1));
434
+ return attempt(n + 1);
435
+ }
307
436
  if (!compat) return;
308
437
  const types = new Set(
309
438
  (payload.events ?? []).map((e) => String(e.type))
@@ -314,12 +443,16 @@ function forward(payload, opts) {
314
443
  noteCompatFeature("browser_trace_propagation");
315
444
  }
316
445
  noteRelayRejection(types, response.status, response.headers);
317
- }).catch((err) => {
446
+ };
447
+ void attempt(1).catch((err) => {
318
448
  (opts.onError ?? ((e) => console.warn("autter relay forward failed:", e)))(
319
449
  err
320
450
  );
321
451
  });
322
452
  }
453
+ function sleep(ms) {
454
+ return new Promise((resolve) => setTimeout(resolve, ms));
455
+ }
323
456
  function createBrowserRelayFetchHandler(opts) {
324
457
  const maxBody = opts.maxBodyBytes ?? DEFAULT_MAX_BODY;
325
458
  const redactor = opts.redact === void 0 || opts.redact === true ? defaultRelayRedactor : makeRedactor(opts.redact);
@@ -329,7 +462,7 @@ function createBrowserRelayFetchHandler(opts) {
329
462
  return new Response(null, { status: 405 });
330
463
  }
331
464
  if (limiter) {
332
- const ip = firstForwardedFor(request.headers.get("x-forwarded-for")) || "unknown";
465
+ const ip = fetchClientKey(request, opts);
333
466
  if (!limiter.allow(ip)) {
334
467
  return new Response(JSON.stringify({ error: "rate limit exceeded" }), {
335
468
  status: 429
@@ -395,7 +528,7 @@ function createBrowserRelayHandler(opts) {
395
528
  return;
396
529
  }
397
530
  if (limiter) {
398
- const ip = firstForwardedFor(req.headers["x-forwarded-for"]) || req.socket?.remoteAddress || "unknown";
531
+ const ip = nodeClientKey(req, opts);
399
532
  if (!limiter.allow(ip)) {
400
533
  respond(res, 429, { error: "rate limit exceeded" });
401
534
  return;
@@ -746,7 +879,6 @@ import { context, trace } from "@opentelemetry/api";
746
879
  import { getRPCMetadata, RPCType } from "@opentelemetry/core";
747
880
  var REQUEST_STATE = /* @__PURE__ */ Symbol.for("autter.runtime.request");
748
881
  var BOUND = /* @__PURE__ */ Symbol.for("autter.runtime.bound");
749
- var CAPTURED = sharedSlot("requests", () => ({ captured: /* @__PURE__ */ new WeakSet() })).captured;
750
882
  var capturer = () => {
751
883
  };
752
884
  function setRequestExceptionCapturer(fn) {
@@ -757,8 +889,14 @@ var annotator = () => {
757
889
  function setRequestSpanAnnotator(fn) {
758
890
  annotator = fn;
759
891
  }
760
- function markCaptured(error) {
761
- if (error && typeof error === "object") CAPTURED.add(error);
892
+ function decide(rule, error, fallback) {
893
+ if (rule === void 0) return fallback;
894
+ if (typeof rule !== "function") return rule;
895
+ try {
896
+ return rule(error) === true;
897
+ } catch {
898
+ return false;
899
+ }
762
900
  }
763
901
  function expressRouteOf(req) {
764
902
  const props = req;
@@ -789,7 +927,7 @@ function captureAtBoundary(error, state, rule = void 0, attributes) {
789
927
  wanted = false;
790
928
  }
791
929
  if (!wanted) return;
792
- if (error && typeof error === "object" && CAPTURED.has(error)) return;
930
+ if (isCaptured(error)) return;
793
931
  markCaptured(error);
794
932
  try {
795
933
  const run = () => onRequestSpan(() => capturer(error, attributes));
@@ -803,7 +941,7 @@ function requestStateOf(req) {
803
941
  return req[REQUEST_STATE];
804
942
  }
805
943
  function captureRequestError(error, attributes) {
806
- if (error && typeof error === "object" && CAPTURED.has(error)) {
944
+ if (isCaptured(error)) {
807
945
  if (!attributes) return;
808
946
  try {
809
947
  onRequestSpan(() => annotator(attributes));
@@ -895,7 +1033,8 @@ function finishRequest(state, input) {
895
1033
  ...state.error !== void 0 ? { error: state.error } : {},
896
1034
  ...state.errorThrown ? { thrown: true } : {},
897
1035
  status: input.status,
898
- aborted: input.aborted
1036
+ aborted: input.aborted,
1037
+ ...state.degraded ? { degraded: true } : {}
899
1038
  });
900
1039
  const span = trace.getActiveSpan();
901
1040
  if (span?.isRecording()) span.setAttribute("autter.operation.outcome", state.outcome);
@@ -1120,9 +1259,15 @@ function withRuntimeRequest(handler, options = {}) {
1120
1259
  try {
1121
1260
  return await handler(request, ...rest);
1122
1261
  } catch (error) {
1123
- noteOperationError(error, true, state);
1124
- captureAtBoundary(error, state, true);
1125
- throw error;
1262
+ if (decide(options.capture, error, true)) {
1263
+ noteOperationError(error, true, state);
1264
+ captureAtBoundary(error, state, true);
1265
+ }
1266
+ if (!decide(options.errorResponse, error, false)) throw error;
1267
+ return new Response(JSON.stringify(toClientError(error, requestId)), {
1268
+ status: responseStatusOf(error),
1269
+ headers: { "content-type": "application/json; charset=utf-8" }
1270
+ });
1126
1271
  }
1127
1272
  });
1128
1273
  status = response.status;
@@ -1320,6 +1465,8 @@ var S = sharedSlot("server", () => ({
1320
1465
  alwaysOnProvider: null,
1321
1466
  redactor: makeRedactor(true),
1322
1467
  loggerOnlyCapture: null,
1468
+ handled: null,
1469
+ loggerOnlyHandled: null,
1323
1470
  hookRequests: false
1324
1471
  }));
1325
1472
  var LLM_MARKER_ATTRIBUTES = [
@@ -1896,6 +2043,37 @@ function initAutterServer(options) {
1896
2043
  span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
1897
2044
  if (!reuseActive) span.end();
1898
2045
  }
2046
+ function captureHandled(error, attributes, severity) {
2047
+ const isErr = isError(error);
2048
+ if (!errorFilter({
2049
+ type: isErr ? String(error.name ?? "") : "Error",
2050
+ message: isErr ? String(error.message ?? "") : String(error),
2051
+ severity
2052
+ })) return;
2053
+ if (severity === "error") errorTraceBuffer?.retainActiveTrace();
2054
+ telemetryStats.markCaptured();
2055
+ const message = redactor.text(isErr ? error.message : String(error));
2056
+ 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");
2057
+ const split = splitFingerprint(attributes);
2058
+ const fingerprint = fingerprintEventAttributes(split.fingerprint, redactor.text);
2059
+ const redacted = redactor(split.attributes);
2060
+ const activeSpan = trace2.getActiveSpan();
2061
+ const reuseActive = activeSpan?.isRecording() === true;
2062
+ const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
2063
+ markCaptured(error);
2064
+ notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
2065
+ span.addEvent("exception", {
2066
+ ...redacted,
2067
+ "exception.type": isErr ? error.name : "Error",
2068
+ "exception.message": message,
2069
+ ...stack ? { "exception.stacktrace": redactor.text(stack) } : {},
2070
+ "autter.severity": severity,
2071
+ ...errorAttributes(error),
2072
+ ...fingerprint,
2073
+ ...breadcrumbEventAttributes(redactor.text, error)
2074
+ });
2075
+ if (!reuseActive) span.end();
2076
+ }
1899
2077
  configureRuntimeLogger({
1900
2078
  endpoint,
1901
2079
  apiKey: options.apiKey,
@@ -1909,6 +2087,8 @@ function initAutterServer(options) {
1909
2087
  reportOutcome: reportOutcome2
1910
2088
  });
1911
2089
  S.hookRequests = options.logging?.requests === true;
2090
+ S.handled = captureHandled;
2091
+ startLoggedErrorCapture(options.captureLoggedErrors);
1912
2092
  let onCrash = null;
1913
2093
  let uninstallRejections = null;
1914
2094
  if (options.captureGlobalErrors !== false) {
@@ -1955,6 +2135,7 @@ function initAutterServer(options) {
1955
2135
  withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
1956
2136
  trackLlmCall: (call) => recordLlmCall(llmTracer, call),
1957
2137
  shutdown: async () => {
2138
+ if (S.active === server) stopLoggedErrorCapture();
1958
2139
  const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
1959
2140
  if (onCrash) process.off("uncaughtExceptionMonitor", onCrash);
1960
2141
  uninstallRejections?.();
@@ -1962,6 +2143,7 @@ function initAutterServer(options) {
1962
2143
  if (S.active === server) {
1963
2144
  S.active = null;
1964
2145
  S.alwaysOnProvider = null;
2146
+ S.handled = null;
1965
2147
  S.hookRequests = false;
1966
2148
  S.redactor = makeRedactor(true);
1967
2149
  }
@@ -1997,13 +2179,39 @@ function captureException(error, attributes) {
1997
2179
  recordError(span, error);
1998
2180
  span.end();
1999
2181
  }
2000
- function setLoggerOnlyCapture(fn) {
2182
+ function setLoggerOnlyCapture(fn, handled = null) {
2001
2183
  S.loggerOnlyCapture = fn;
2184
+ S.loggerOnlyHandled = handled;
2185
+ }
2186
+ function captureHandledError(error, attributes, severity) {
2187
+ if (S.active) {
2188
+ S.handled?.(error, attributes, severity);
2189
+ return;
2190
+ }
2191
+ if (S.loggerOnlyHandled) {
2192
+ S.loggerOnlyHandled(error, attributes, severity);
2193
+ return;
2194
+ }
2195
+ markCaptured(error);
2196
+ const split = splitFingerprint(attributes);
2197
+ notifyException(error, { ...split.attributes, "autter.severity": severity });
2198
+ const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error");
2199
+ span.addEvent("exception", {
2200
+ ...S.redactor(split.attributes),
2201
+ "exception.type": isError(error) ? error.name : "Error",
2202
+ "exception.message": S.redactor.text(isError(error) ? error.message : String(error)),
2203
+ ...isError(error) && error.stack ? { "exception.stacktrace": S.redactor.text(error.stack) } : {},
2204
+ "autter.severity": severity,
2205
+ ...errorAttributes(error),
2206
+ ...fingerprintEventAttributes(split.fingerprint, S.redactor.text)
2207
+ });
2208
+ span.end();
2002
2209
  }
2003
2210
  function serverActive() {
2004
2211
  return S.active !== null;
2005
2212
  }
2006
2213
  setRequestExceptionCapturer((error, attributes) => captureException(error, attributes));
2214
+ setHandledCapture(captureHandledError);
2007
2215
  setRequestSpanAnnotator((attributes) => {
2008
2216
  const span = trace2.getActiveSpan();
2009
2217
  if (span?.isRecording()) span.setAttributes(S.redactor(splitFingerprint(attributes).attributes));
@@ -2474,6 +2682,33 @@ function initAutterLogging(options) {
2474
2682
  notifyException(error, { ...redacted, ...fingerprint });
2475
2683
  captureExceptionAsLog(error, { ...redacted, ...fingerprint });
2476
2684
  }
2685
+ function captureHandled(error, attributes, severity) {
2686
+ markCaptured(error);
2687
+ const isErrValue = isError(error);
2688
+ if (!errorFilter({
2689
+ type: isErrValue ? String(error.name ?? "") : "Error",
2690
+ message: isErrValue ? String(error.message ?? "") : String(error),
2691
+ severity
2692
+ })) return;
2693
+ const split = splitFingerprint(attributes);
2694
+ const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
2695
+ const redacted = redact(split.attributes);
2696
+ notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
2697
+ const span = trace3.getActiveSpan();
2698
+ if (mode === "auto" && span?.isRecording()) {
2699
+ span.addEvent("exception", {
2700
+ ...redacted,
2701
+ "exception.type": isErrValue ? error.name : "Error",
2702
+ "exception.message": redact.text(isErrValue ? error.message : String(error)),
2703
+ ...isErrValue && error.stack ? { "exception.stacktrace": redact.text(error.stack) } : {},
2704
+ "autter.severity": severity,
2705
+ ...errorAttributes(error),
2706
+ ...fingerprint
2707
+ });
2708
+ return;
2709
+ }
2710
+ captureExceptionAsLog(error, { ...redacted, ...fingerprint }, severity);
2711
+ }
2477
2712
  const captured = /* @__PURE__ */ new WeakSet();
2478
2713
  configureRuntimeLogger({
2479
2714
  endpoint,
@@ -2498,7 +2733,8 @@ function initAutterLogging(options) {
2498
2733
  captureException2(error);
2499
2734
  }
2500
2735
  });
2501
- setLoggerOnlyCapture(captureException2);
2736
+ setLoggerOnlyCapture(captureException2, captureHandled);
2737
+ startLoggedErrorCapture(options.captureLoggedErrors);
2502
2738
  const onCrash = (error) => {
2503
2739
  if (recordedAsRejection(error)) return;
2504
2740
  captureException2(error, { "autter.unhandled": true });
@@ -2517,6 +2753,7 @@ function initAutterLogging(options) {
2517
2753
  captureException: captureException2,
2518
2754
  flush: flushRuntimeLogs,
2519
2755
  shutdown: async () => {
2756
+ if (L.active === handle) stopLoggedErrorCapture();
2520
2757
  process.off("uncaughtExceptionMonitor", onCrash);
2521
2758
  uninstallRejections?.();
2522
2759
  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,33 @@ 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
+
253
289
  /**
254
290
  * Client-side volume control shared by the server SDKs (runtime-node,
255
291
  * runtime-edge; 1.8.0 / 1.2.0, EI-38): `ignoreErrors` and `sampleRate`.
@@ -319,7 +355,9 @@ interface OtlpSinkOptions {
319
355
  * OTLP/HTTP JSON export to `${endpoint}/v1/logs`. Bounded exactly like
320
356
  * 1.4.0: 1000 records / 4 MiB buffered, 256 KiB per record, batches of 50
321
357
  * (≤ 512 KiB), 3 attempts within a 10 s deadline, exponential back-off
322
- * on failure, undelivered records reported at shutdown.
358
+ * on failure, undelivered records reported at shutdown. A 429 or 5xx is
359
+ * retried after its Retry-After (else 500 ms, 1 s); any other 4xx is not
360
+ * retried within the flush.
323
361
  */
324
362
  declare function otlpSink(options?: OtlpSinkOptions): RuntimeSink;
325
363
  interface ConsoleSinkOptions {
@@ -358,4 +396,4 @@ interface MemorySink extends RuntimeSink {
358
396
  /** Keeps every record in memory — for tests (`@autter/runtime-node/testing`). */
359
397
  declare function memorySink(): MemorySink;
360
398
 
361
- 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, type IgnoreErrorPattern as I, redactText as J, toClientError as K, 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 };
399
+ 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,33 @@ 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
+
253
289
  /**
254
290
  * Client-side volume control shared by the server SDKs (runtime-node,
255
291
  * runtime-edge; 1.8.0 / 1.2.0, EI-38): `ignoreErrors` and `sampleRate`.
@@ -319,7 +355,9 @@ interface OtlpSinkOptions {
319
355
  * OTLP/HTTP JSON export to `${endpoint}/v1/logs`. Bounded exactly like
320
356
  * 1.4.0: 1000 records / 4 MiB buffered, 256 KiB per record, batches of 50
321
357
  * (≤ 512 KiB), 3 attempts within a 10 s deadline, exponential back-off
322
- * on failure, undelivered records reported at shutdown.
358
+ * on failure, undelivered records reported at shutdown. A 429 or 5xx is
359
+ * retried after its Retry-After (else 500 ms, 1 s); any other 4xx is not
360
+ * retried within the flush.
323
361
  */
324
362
  declare function otlpSink(options?: OtlpSinkOptions): RuntimeSink;
325
363
  interface ConsoleSinkOptions {
@@ -358,4 +396,4 @@ interface MemorySink extends RuntimeSink {
358
396
  /** Keeps every record in memory — for tests (`@autter/runtime-node/testing`). */
359
397
  declare function memorySink(): MemorySink;
360
398
 
361
- 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, type IgnoreErrorPattern as I, redactText as J, toClientError as K, 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 };
399
+ 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 };
package/dist/testing.cjs CHANGED
@@ -2,22 +2,22 @@
2
2
 
3
3
 
4
4
 
5
- var _chunk37W7YYXOcjs = require('./chunk-37W7YYXO.cjs');
6
- require('./chunk-MOZOUEMA.cjs');
5
+ var _chunkFV64SIRQcjs = require('./chunk-FV64SIRQ.cjs');
6
+ require('./chunk-3P573RHA.cjs');
7
7
 
8
8
  // src/testing.ts
9
9
  function captureRuntime() {
10
- const sink = _chunk37W7YYXOcjs.memorySink.call(void 0, );
11
- const remove = _chunk37W7YYXOcjs.addRuntimeSink.call(void 0, sink);
10
+ const sink = _chunkFV64SIRQcjs.memorySink.call(void 0, );
11
+ const remove = _chunkFV64SIRQcjs.addRuntimeSink.call(void 0, sink);
12
12
  return {
13
13
  get events() {
14
14
  return [...sink.events];
15
15
  },
16
16
  get operations() {
17
- return sink.events.filter(_chunk37W7YYXOcjs.isSummary);
17
+ return sink.events.filter(_chunkFV64SIRQcjs.isSummary);
18
18
  },
19
19
  get logs() {
20
- return sink.events.filter((event) => !_chunk37W7YYXOcjs.isSummary.call(void 0, event));
20
+ return sink.events.filter((event) => !_chunkFV64SIRQcjs.isSummary.call(void 0, event));
21
21
  },
22
22
  get exceptions() {
23
23
  return [...sink.exceptions];
@@ -98,7 +98,7 @@ var OperationExpectation = class {
98
98
  }
99
99
  };
100
100
  function expectOperation(source, name) {
101
- const events = Array.isArray(source) ? source.filter(_chunk37W7YYXOcjs.isSummary) : source.operations;
101
+ const events = Array.isArray(source) ? source.filter(_chunkFV64SIRQcjs.isSummary) : source.operations;
102
102
  const matches = events.filter((event2) => {
103
103
  const actual = String(_nullishCoalesce(event2.attributes["autter.operation.name"], () => ( "")));
104
104
  return typeof name === "string" ? actual === name : name.test(actual);
@@ -118,4 +118,4 @@ function expectOperation(source, name) {
118
118
 
119
119
 
120
120
 
121
- exports.captureRuntime = captureRuntime; exports.expectOperation = expectOperation; exports.memorySink = _chunk37W7YYXOcjs.memorySink;
121
+ exports.captureRuntime = captureRuntime; exports.expectOperation = expectOperation; exports.memorySink = _chunkFV64SIRQcjs.memorySink;
@@ -1,5 +1,5 @@
1
- import { R as RuntimeEvent, a as RuntimeOutcome, C as CapturedException } from './sinks-CIdlmYwy.cjs';
2
- export { M as MemorySink, m as memorySink } from './sinks-CIdlmYwy.cjs';
1
+ import { R as RuntimeEvent, a as RuntimeOutcome, C as CapturedException } from './sinks-BfUoE96B.cjs';
2
+ export { M as MemorySink, m as memorySink } from './sinks-BfUoE96B.cjs';
3
3
 
4
4
  /**
5
5
  * Test helpers — `@autter/runtime-node/testing`. No ingester, no network: