@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.
@@ -2,7 +2,7 @@ import {
2
2
  noteCompatFeature,
3
3
  observeIngesterResponse,
4
4
  sharedSlot
5
- } from "./chunk-LG52CW3V.js";
5
+ } from "./chunk-ZKM76SDP.js";
6
6
 
7
7
  // ../runtime-core/dist/index.js
8
8
  var SENSITIVE_KEY_PATTERNS = [
@@ -132,8 +132,8 @@ function redactArray(value, r, state, depth) {
132
132
  state.ancestors.delete(value);
133
133
  return r.mask;
134
134
  }
135
- const limit = Math.min(length, MAX_COLLECTION_ENTRIES);
136
- for (let i = 0; i < limit; i += 1) {
135
+ const limit2 = Math.min(length, MAX_COLLECTION_ENTRIES);
136
+ for (let i = 0; i < limit2; i += 1) {
137
137
  let item;
138
138
  try {
139
139
  item = value[i];
@@ -143,7 +143,7 @@ function redactArray(value, r, state, depth) {
143
143
  }
144
144
  out.push(redactValue(item, r, state, depth + 1));
145
145
  }
146
- if (length > limit) {
146
+ if (length > limit2) {
147
147
  out.push(r.mask);
148
148
  }
149
149
  state.ancestors.delete(value);
@@ -392,6 +392,53 @@ function toSpanAttributes(context) {
392
392
  ])
393
393
  );
394
394
  }
395
+ var DEBUG_IDS_ATTRIBUTE = "autter.debug_ids";
396
+ var REGISTRY = "_autterDebugIds";
397
+ var pathOfKey = /* @__PURE__ */ new Map();
398
+ function withoutPosition(location) {
399
+ const match = /^(.*?):\d+:\d+$/.exec(location.trim());
400
+ return match?.[1] ? match[1] : null;
401
+ }
402
+ function firstFramePath(stack) {
403
+ for (const raw of stack.split("\n")) {
404
+ const line = raw.trim();
405
+ if (line.startsWith("at ")) {
406
+ const body = line.slice(3).trim();
407
+ const withFn = /^(.*?)\s+\((.*)\)$/.exec(body);
408
+ const path = withoutPosition(withFn ? withFn[2] : body.replace(/^async /, ""));
409
+ if (path) return path;
410
+ continue;
411
+ }
412
+ const at = line.lastIndexOf("@");
413
+ if (at >= 0) {
414
+ const path = withoutPosition(line.slice(at + 1));
415
+ if (path) return path;
416
+ }
417
+ }
418
+ return null;
419
+ }
420
+ function debugIdsForStacks(stacks) {
421
+ try {
422
+ const registry = globalThis[REGISTRY];
423
+ if (!registry || typeof registry !== "object") return void 0;
424
+ const texts = stacks.filter((stack) => typeof stack === "string" && stack !== "");
425
+ if (texts.length === 0) return void 0;
426
+ let found;
427
+ for (const key of Object.keys(registry)) {
428
+ const id = registry[key];
429
+ if (typeof id !== "string" || !id) continue;
430
+ let path = pathOfKey.get(key);
431
+ if (path === void 0) {
432
+ path = firstFramePath(key);
433
+ pathOfKey.set(key, path);
434
+ }
435
+ if (path && texts.some((stack) => stack.includes(path))) (found ??= {})[path] = id;
436
+ }
437
+ return found;
438
+ } catch {
439
+ return void 0;
440
+ }
441
+ }
395
442
  var CODE_PATTERN = /^[a-z][a-z0-9_]*(\.[a-z0-9_]+){0,3}$/;
396
443
  var CODE_MAX_LENGTH = 80;
397
444
  var WHY_MAX = 1e3;
@@ -514,6 +561,13 @@ function errorStatusOf(err) {
514
561
  function isExpectedError(err) {
515
562
  return field(err, "expected") === true;
516
563
  }
564
+ var STACK_ATTRIBUTE_RE = /^exception\.cause\.\d+\.stacktrace$|^autter\.debug_ids$/;
565
+ function stackAttributesLast(attributes) {
566
+ const head = {};
567
+ const tail = {};
568
+ for (const [key, value] of Object.entries(attributes)) (STACK_ATTRIBUTE_RE.test(key) ? tail : head)[key] = value;
569
+ return { ...head, ...tail };
570
+ }
517
571
  function errorAttributes(err, options = {}) {
518
572
  const out = {};
519
573
  if (!err || typeof err !== "object") return out;
@@ -530,6 +584,9 @@ function errorAttributes(err, options = {}) {
530
584
  const expected = field(err, "expected");
531
585
  if (typeof expected === "boolean") out["autter.error.expected"] = expected;
532
586
  if (options.causes !== false) {
587
+ const stacks = options.stacks !== false;
588
+ const ownStack = field(err, "stack");
589
+ const allStacks = typeof ownStack === "string" && ownStack ? [ownStack] : [];
533
590
  let cause = field(err, "cause");
534
591
  const seen = /* @__PURE__ */ new Set([err]);
535
592
  for (let n = 1; n <= CAUSE_DEPTH && cause !== void 0 && cause !== null; n++) {
@@ -543,8 +600,17 @@ function errorAttributes(err, options = {}) {
543
600
  const causeCode = field(cause, "code");
544
601
  if (typeof causeCode === "string" && causeCode)
545
602
  out[`exception.cause.${n}.code`] = causeCode.slice(0, CODE_MAX_LENGTH);
603
+ const causeStack = isError2 ? field(cause, "stack") : void 0;
604
+ if (stacks && typeof causeStack === "string" && causeStack) {
605
+ out[`exception.cause.${n}.stacktrace`] = causeStack;
606
+ allStacks.push(causeStack);
607
+ }
546
608
  cause = isError2 ? field(cause, "cause") : void 0;
547
609
  }
610
+ if (stacks) {
611
+ const ids = debugIdsForStacks(allStacks);
612
+ if (ids) out[DEBUG_IDS_ATTRIBUTE] = JSON.stringify(ids);
613
+ }
548
614
  }
549
615
  return out;
550
616
  }
@@ -758,6 +824,7 @@ function requestOutcome(input) {
758
824
  if (input.error !== void 0 && isExpectedError(input.error)) return "degraded";
759
825
  if (input.thrown || input.status !== void 0 && input.status >= 500)
760
826
  return "failed";
827
+ if (input.degraded) return "degraded";
761
828
  if (input.aborted) return "cancelled";
762
829
  return "succeeded";
763
830
  }
@@ -974,6 +1041,169 @@ function splitFingerprint(attributes) {
974
1041
  const { fingerprint, ...rest } = attributes;
975
1042
  return { attributes: rest, fingerprint };
976
1043
  }
1044
+ var HANDLED_ATTRIBUTE = "autter.handled";
1045
+ var HANDLING_ATTRIBUTE = "autter.handling";
1046
+ var LOG_SOURCE_ATTRIBUTE = "autter.log.source";
1047
+ var LOG_MESSAGE_ATTRIBUTE = "autter.log.message";
1048
+ var DEGRADED_REASON_ATTRIBUTE = "autter.degraded.reason";
1049
+ var CAUGHT_DROPPED_ATTRIBUTE = "autter.caught.dropped";
1050
+ var LOG_MESSAGE_MAX = 1e3;
1051
+ var DEGRADED_REASON_MAX = 500;
1052
+ var CAUGHT_BURST = 10;
1053
+ var CAUGHT_REFILL_MS = 1e4;
1054
+ var SOURCES = ["console", "pino", "winston", "bunyan", "runtimeLogger"];
1055
+ function loggedErrorSources(option) {
1056
+ const out = {};
1057
+ for (const source of SOURCES)
1058
+ out[source] = option === false ? false : option === void 0 || option === true || typeof option !== "object" ? true : option[source] !== false;
1059
+ return out;
1060
+ }
1061
+ function handledErrorKind(error) {
1062
+ let type = "Error";
1063
+ let site = "";
1064
+ try {
1065
+ if (isError(error)) {
1066
+ type = String(error.name || "Error").slice(0, 200);
1067
+ const lines = String(error.stack ?? "").split("\n");
1068
+ site = (lines.find((line) => /^\s*at\s/.test(line)) ?? lines[1] ?? "").trim().slice(0, 300);
1069
+ }
1070
+ } catch {
1071
+ }
1072
+ return { key: `${type}
1073
+ ${site}`, type };
1074
+ }
1075
+ function caughtErrorAttributes(source, message, dropped = 0) {
1076
+ return {
1077
+ [HANDLED_ATTRIBUTE]: true,
1078
+ [HANDLING_ATTRIBUTE]: "caught",
1079
+ [LOG_SOURCE_ATTRIBUTE]: source,
1080
+ ...message ? { [LOG_MESSAGE_ATTRIBUTE]: message.slice(0, LOG_MESSAGE_MAX) } : {},
1081
+ ...dropped > 0 ? { [CAUGHT_DROPPED_ATTRIBUTE]: dropped } : {}
1082
+ };
1083
+ }
1084
+ function degradedAttributes(reason, dropped = 0) {
1085
+ let text = "";
1086
+ try {
1087
+ text = String(reason ?? "");
1088
+ } catch {
1089
+ }
1090
+ return {
1091
+ [HANDLED_ATTRIBUTE]: true,
1092
+ [HANDLING_ATTRIBUTE]: "deliberate",
1093
+ [DEGRADED_REASON_ATTRIBUTE]: text.slice(0, DEGRADED_REASON_MAX) || "degraded",
1094
+ ...dropped > 0 ? { [CAUGHT_DROPPED_ATTRIBUTE]: dropped } : {}
1095
+ };
1096
+ }
1097
+ var BurstLimiter = class {
1098
+ constructor(burst = CAUGHT_BURST, refillMs = CAUGHT_REFILL_MS, maxKeys = 1e3) {
1099
+ this.burst = burst;
1100
+ this.refillMs = refillMs;
1101
+ this.maxKeys = maxKeys;
1102
+ }
1103
+ burst;
1104
+ refillMs;
1105
+ maxKeys;
1106
+ buckets = /* @__PURE__ */ new Map();
1107
+ evicted = [];
1108
+ /**
1109
+ * -1 when the record is left out (and counted). Otherwise it is sent,
1110
+ * and the number of its kind left out since the last one sent is
1111
+ * returned, to report with it.
1112
+ */
1113
+ take(key, type, now = Date.now()) {
1114
+ const bucket = this.buckets.get(key) ?? { tokens: this.burst, at: now, dropped: 0, type };
1115
+ this.buckets.delete(key);
1116
+ this.buckets.set(key, bucket);
1117
+ this.refill(bucket, now);
1118
+ let result;
1119
+ if (bucket.tokens < 1) {
1120
+ bucket.dropped += 1;
1121
+ result = -1;
1122
+ } else {
1123
+ bucket.tokens -= 1;
1124
+ result = bucket.dropped;
1125
+ bucket.dropped = 0;
1126
+ }
1127
+ while (this.buckets.size > this.maxKeys) {
1128
+ const oldest = this.buckets.keys().next().value;
1129
+ const gone = this.buckets.get(oldest);
1130
+ this.buckets.delete(oldest);
1131
+ if (gone.dropped > 0) this.evicted.push({ key: oldest, type: gone.type, dropped: gone.dropped });
1132
+ }
1133
+ return result;
1134
+ }
1135
+ /**
1136
+ * Counts no sent record carried: kinds that have a token again (nothing
1137
+ * of theirs came along to carry the count) or, with `all`, every kind
1138
+ * with a count. The counts returned are reset.
1139
+ */
1140
+ drain(now = Date.now(), all = false) {
1141
+ const out = this.evicted;
1142
+ this.evicted = [];
1143
+ for (const [key, bucket] of this.buckets) {
1144
+ if (bucket.dropped === 0) continue;
1145
+ this.refill(bucket, now);
1146
+ if (!all && bucket.tokens < 1) continue;
1147
+ out.push({ key, type: bucket.type, dropped: bucket.dropped });
1148
+ bucket.dropped = 0;
1149
+ }
1150
+ return out;
1151
+ }
1152
+ /** When the next uncarried count can be drained, or null when none is pending. */
1153
+ nextDrainAt() {
1154
+ if (this.evicted.length) return 0;
1155
+ let next = null;
1156
+ for (const bucket of this.buckets.values()) {
1157
+ if (bucket.dropped === 0) continue;
1158
+ const at = bucket.at + Math.max(0, 1 - bucket.tokens) * this.refillMs;
1159
+ if (next === null || at < next) next = at;
1160
+ }
1161
+ return next;
1162
+ }
1163
+ refill(bucket, now) {
1164
+ if (now > bucket.at) {
1165
+ bucket.tokens = Math.min(this.burst, bucket.tokens + (now - bucket.at) / this.refillMs);
1166
+ bucket.at = now;
1167
+ }
1168
+ }
1169
+ };
1170
+ function testPattern(pattern, type, message) {
1171
+ if (typeof pattern === "string") {
1172
+ return pattern !== "" && (message.includes(pattern) || type.includes(pattern));
1173
+ }
1174
+ if (!(pattern instanceof RegExp)) return false;
1175
+ const re = pattern.global || pattern.sticky ? new RegExp(pattern.source, pattern.flags.replace(/[gy]/g, "")) : pattern;
1176
+ return re.test(message) || type !== "" && (re.test(type) || re.test(`${type}: ${message}`));
1177
+ }
1178
+ function isIgnoredError(patterns, type, message) {
1179
+ if (!patterns?.length) return false;
1180
+ for (const pattern of patterns) {
1181
+ try {
1182
+ if (testPattern(pattern, type, message)) return true;
1183
+ } catch {
1184
+ }
1185
+ }
1186
+ return false;
1187
+ }
1188
+ function effectiveSampleRate(rate) {
1189
+ return typeof rate === "number" && Number.isFinite(rate) && rate >= 0 && rate < 1 ? rate : null;
1190
+ }
1191
+ function createErrorFilter(options = {}, random = Math.random) {
1192
+ const patterns = Array.isArray(options.ignoreErrors) ? options.ignoreErrors.slice() : [];
1193
+ const rate = effectiveSampleRate(options.sampleRate);
1194
+ if (!patterns.length && rate === null) return () => true;
1195
+ return (input) => {
1196
+ try {
1197
+ if (isIgnoredError(patterns, input.type ?? "", input.message ?? "")) return false;
1198
+ if (rate === null || input.crash) return true;
1199
+ const severity = input.severity ?? "error";
1200
+ if (severity !== "error") return true;
1201
+ return random() < rate;
1202
+ } catch {
1203
+ return true;
1204
+ }
1205
+ };
1206
+ }
977
1207
 
978
1208
  // src/breadcrumbs.ts
979
1209
  import { trace as trace2 } from "@opentelemetry/api";
@@ -985,6 +1215,267 @@ import {
985
1215
  trace
986
1216
  } from "@opentelemetry/api";
987
1217
 
1218
+ // src/caught-errors.ts
1219
+ import { createRequire } from "module";
1220
+ import { join } from "path";
1221
+ import { format } from "util";
1222
+ import { Hook } from "require-in-the-middle";
1223
+
1224
+ // src/captured.ts
1225
+ var CAPTURED = sharedSlot("requests", () => ({ captured: /* @__PURE__ */ new WeakSet() })).captured;
1226
+ function markCaptured(error) {
1227
+ if (error && typeof error === "object") CAPTURED.add(error);
1228
+ }
1229
+ function isCaptured(error) {
1230
+ return !!error && typeof error === "object" && CAPTURED.has(error);
1231
+ }
1232
+
1233
+ // src/caught-errors.ts
1234
+ var S = sharedSlot("handled", () => ({
1235
+ sources: null,
1236
+ capture: null,
1237
+ tail: null,
1238
+ limiter: new BurstLimiter(),
1239
+ decided: /* @__PURE__ */ new WeakSet(),
1240
+ timer: null,
1241
+ hooked: false
1242
+ }));
1243
+ var PATCHED = /* @__PURE__ */ Symbol.for("autter.runtime.logged-errors");
1244
+ function setHandledCapture(capture) {
1245
+ S.capture = capture;
1246
+ }
1247
+ function setHandledTail(tail) {
1248
+ S.tail = tail;
1249
+ }
1250
+ function scheduleTail() {
1251
+ if (S.timer) return;
1252
+ const at = S.limiter.nextDrainAt();
1253
+ if (at === null) return;
1254
+ S.timer = setTimeout(() => sendTails(false), Math.max(0, at - Date.now()) + 5);
1255
+ S.timer.unref?.();
1256
+ }
1257
+ function sendTails(all) {
1258
+ if (S.timer) clearTimeout(S.timer);
1259
+ S.timer = null;
1260
+ for (const tail of S.limiter.drain(Date.now(), all)) {
1261
+ try {
1262
+ S.tail?.(tail.type, tail.dropped);
1263
+ } catch {
1264
+ }
1265
+ }
1266
+ if (!all) scheduleTail();
1267
+ }
1268
+ function limit(prefix, error) {
1269
+ const kind = handledErrorKind(error);
1270
+ const dropped = S.limiter.take(`${prefix}
1271
+ ${kind.key}`, kind.type);
1272
+ if (dropped < 0) scheduleTail();
1273
+ return dropped;
1274
+ }
1275
+ function reportLoggedError(error, source, message, attributes) {
1276
+ if (!S.sources?.[source] || !isError(error)) return;
1277
+ if (S.decided.has(error) || isCaptured(error)) return;
1278
+ S.decided.add(error);
1279
+ const run = () => {
1280
+ try {
1281
+ if (isCaptured(error) || !S.capture || !S.sources) return;
1282
+ const dropped = limit("caught", error);
1283
+ if (dropped < 0) return;
1284
+ markCaptured(error);
1285
+ S.capture(error, { ...attributes, ...caughtErrorAttributes(source, message, dropped) }, "error");
1286
+ } catch {
1287
+ }
1288
+ };
1289
+ try {
1290
+ setImmediate(run);
1291
+ } catch {
1292
+ }
1293
+ }
1294
+ function reportDegraded(error, reason, attributes) {
1295
+ if (!S.capture) return;
1296
+ if (error && typeof error === "object") S.decided.add(error);
1297
+ const dropped = limit("deliberate", error);
1298
+ if (dropped < 0) return;
1299
+ markCaptured(error);
1300
+ S.capture(error, { ...attributes, ...degradedAttributes(reason, dropped) }, "warning");
1301
+ }
1302
+ function onConsoleError(args) {
1303
+ if (!S.sources?.console) return;
1304
+ if (typeof args[0] === "string" && args[0].startsWith("[autter")) return;
1305
+ for (const arg of args) {
1306
+ if (!isError(arg)) continue;
1307
+ let message = "";
1308
+ try {
1309
+ message = format(...args.filter((other) => other !== arg));
1310
+ } catch {
1311
+ }
1312
+ reportLoggedError(arg, "console", message);
1313
+ }
1314
+ }
1315
+ function patchConsole() {
1316
+ const target = console;
1317
+ try {
1318
+ if (target[PATCHED]) return;
1319
+ const original = target.error;
1320
+ if (typeof original !== "function") return;
1321
+ target.error = function(...args) {
1322
+ try {
1323
+ onConsoleError(args);
1324
+ } catch {
1325
+ }
1326
+ return original.apply(this, args);
1327
+ };
1328
+ Object.defineProperty(target, PATCHED, { value: true });
1329
+ } catch {
1330
+ }
1331
+ }
1332
+ function errorField(value) {
1333
+ if (!value || typeof value !== "object") return void 0;
1334
+ const record = value;
1335
+ for (const key of ["err", "error"]) if (isError(record[key])) return record[key];
1336
+ let seen = 0;
1337
+ for (const key of Object.keys(record)) {
1338
+ if (++seen > 20) break;
1339
+ if (isError(record[key])) return record[key];
1340
+ }
1341
+ return void 0;
1342
+ }
1343
+ function wrapMethod(target, key, inspect) {
1344
+ const original = target[key];
1345
+ if (typeof original !== "function") return;
1346
+ target[key] = function(...args) {
1347
+ try {
1348
+ inspect(this, args);
1349
+ } catch {
1350
+ }
1351
+ return original.apply(this, args);
1352
+ };
1353
+ }
1354
+ function markPatched(target) {
1355
+ const record = target;
1356
+ if (record[PATCHED]) return false;
1357
+ try {
1358
+ Object.defineProperty(target, PATCHED, { value: true });
1359
+ return true;
1360
+ } catch {
1361
+ return false;
1362
+ }
1363
+ }
1364
+ function patchPino(protoFactory) {
1365
+ if (typeof protoFactory !== "function") return;
1366
+ const proto = Object.getPrototypeOf(protoFactory());
1367
+ if (!proto) return;
1368
+ const write = Object.getOwnPropertySymbols(proto).find((symbol) => symbol.description === "pino.write");
1369
+ if (!write || typeof proto[write] !== "function" || !markPatched(proto)) return;
1370
+ wrapMethod(proto, write, (_self, [obj, msg, level]) => {
1371
+ if (!S.sources?.pino || typeof level !== "number" || level < 50) return;
1372
+ const error = isError(obj) ? obj : errorField(obj);
1373
+ if (error) reportLoggedError(error, "pino", typeof msg === "string" ? msg : void 0);
1374
+ });
1375
+ }
1376
+ var WINSTON_ERROR_LEVELS = /^(?:error|fatal|crit|alert|emerg)$/;
1377
+ function winstonErrorLevel(logger, level) {
1378
+ if (typeof level !== "string") return false;
1379
+ const levels = logger?.levels;
1380
+ if (levels && typeof levels[level] === "number" && typeof levels.error === "number")
1381
+ return levels[level] <= levels.error;
1382
+ return WINSTON_ERROR_LEVELS.test(level);
1383
+ }
1384
+ function patchWinston(Logger) {
1385
+ const proto = Logger?.prototype;
1386
+ if (!proto || typeof proto.log !== "function" || !markPatched(proto)) return;
1387
+ wrapMethod(proto, "log", (self, [level, msg, ...meta]) => {
1388
+ if (!S.sources?.winston || !winstonErrorLevel(self, level)) return;
1389
+ for (const arg of [msg, ...meta]) {
1390
+ const error = isError(arg) ? arg : errorField(arg);
1391
+ if (error) reportLoggedError(error, "winston", typeof msg === "string" ? msg : void 0);
1392
+ }
1393
+ });
1394
+ wrapMethod(proto, "_addDefaultMeta", (self, [info]) => {
1395
+ if (!S.sources?.winston || !info || typeof info !== "object") return;
1396
+ if (!winstonErrorLevel(self, info.level)) return;
1397
+ if (isError(info)) reportLoggedError(info, "winston");
1398
+ else {
1399
+ const error = errorField(info);
1400
+ const message = info.message;
1401
+ if (error) reportLoggedError(error, "winston", typeof message === "string" ? message : void 0);
1402
+ }
1403
+ });
1404
+ }
1405
+ function patchBunyan(Logger) {
1406
+ const proto = Logger?.prototype;
1407
+ if (!proto || typeof proto.error !== "function" || !markPatched(proto)) return;
1408
+ for (const [method, level] of [["error", 50], ["fatal", 60]]) {
1409
+ wrapMethod(proto, method, (self, args) => {
1410
+ if (!S.sources?.bunyan || args.length === 0) return;
1411
+ const min = self?._level;
1412
+ if (typeof min === "number" && min > level) return;
1413
+ const error = isError(args[0]) ? args[0] : errorField(args[0]);
1414
+ if (!error) return;
1415
+ let message = "";
1416
+ try {
1417
+ message = format(...args.slice(1));
1418
+ } catch {
1419
+ }
1420
+ reportLoggedError(error, "bunyan", message);
1421
+ });
1422
+ }
1423
+ }
1424
+ var TARGETS = [
1425
+ { file: "pino/lib/proto.js", patch: patchPino },
1426
+ { file: "winston/lib/winston/logger.js", patch: patchWinston },
1427
+ { file: "bunyan/lib/bunyan.js", patch: patchBunyan }
1428
+ ];
1429
+ function patchModule(file, exports) {
1430
+ const target = TARGETS.find((entry) => entry.file === file);
1431
+ if (!target) return;
1432
+ try {
1433
+ target.patch(exports);
1434
+ } catch {
1435
+ }
1436
+ }
1437
+ function scanModuleCache() {
1438
+ try {
1439
+ const cache = createRequire(join(process.cwd(), "autter-runtime-loggers.cjs")).cache;
1440
+ for (const [key, entry] of Object.entries(cache)) {
1441
+ const path = key.replace(/\\/g, "/");
1442
+ for (const target of TARGETS)
1443
+ if (path.endsWith(`/node_modules/${target.file}`)) patchModule(target.file, entry?.exports);
1444
+ }
1445
+ } catch {
1446
+ }
1447
+ }
1448
+ function hookLaterLoads() {
1449
+ if (S.hooked) return;
1450
+ S.hooked = true;
1451
+ try {
1452
+ new Hook(["pino", "winston", "bunyan"], { internals: true }, (exports, name) => {
1453
+ const file = name.replace(/\\/g, "/");
1454
+ patchModule(file === "bunyan" ? "bunyan/lib/bunyan.js" : file, exports);
1455
+ return exports;
1456
+ });
1457
+ } catch {
1458
+ }
1459
+ for (const delay of [0, 1e3, 1e4]) {
1460
+ const timer = setTimeout(scanModuleCache, delay);
1461
+ timer.unref?.();
1462
+ }
1463
+ }
1464
+ function startLoggedErrorCapture(option) {
1465
+ const sources = loggedErrorSources(option);
1466
+ S.sources = sources;
1467
+ if (!Object.values(sources).some(Boolean)) return;
1468
+ if (sources.console) patchConsole();
1469
+ if (sources.pino || sources.winston || sources.bunyan) {
1470
+ scanModuleCache();
1471
+ hookLaterLoads();
1472
+ }
1473
+ }
1474
+ function stopLoggedErrorCapture() {
1475
+ S.sources = null;
1476
+ sendTails(true);
1477
+ }
1478
+
988
1479
  // src/sinks.ts
989
1480
  import {
990
1481
  appendFileSync,
@@ -993,7 +1484,7 @@ import {
993
1484
  statSync,
994
1485
  unlinkSync
995
1486
  } from "fs";
996
- import { join, resolve } from "path";
1487
+ import { join as join2, resolve } from "path";
997
1488
  var sinkStats = sharedSlot("sinks", () => ({ dropped: 0 }));
998
1489
  function otlpSink(options = {}) {
999
1490
  let context = null;
@@ -1247,14 +1738,14 @@ function formatPretty(event, color = false) {
1247
1738
  return lines.join("\n");
1248
1739
  }
1249
1740
  function consoleSink(options = {}) {
1250
- const format = options.format ?? (process.env.NODE_ENV === "production" ? "json" : "pretty");
1741
+ const format2 = options.format ?? (process.env.NODE_ENV === "production" ? "json" : "pretty");
1251
1742
  const color = options.color ?? (process.stdout?.isTTY === true && !process.env.NO_COLOR);
1252
1743
  const write = options.write ?? ((line) => console.log(line));
1253
1744
  return {
1254
1745
  name: "console",
1255
1746
  write(event) {
1256
1747
  write(
1257
- format === "json" ? JSON.stringify({
1748
+ format2 === "json" ? JSON.stringify({
1258
1749
  ...event.attributes,
1259
1750
  level: event.level,
1260
1751
  message: event.message
@@ -1284,7 +1775,7 @@ function fileSink(options = {}) {
1284
1775
  const existing = files().filter((file) => file.name !== current);
1285
1776
  const excess = existing.length + 1 - maxFiles;
1286
1777
  for (const file of existing.slice(0, Math.max(0, excess)))
1287
- unlinkSync(join(dir, file.name));
1778
+ unlinkSync(join2(dir, file.name));
1288
1779
  };
1289
1780
  const open = (nextDay) => {
1290
1781
  mkdirSync(dir, { recursive: true });
@@ -1292,7 +1783,7 @@ function fileSink(options = {}) {
1292
1783
  const sameDay = files().filter((file) => file.day === day);
1293
1784
  index = sameDay.length ? sameDay[sameDay.length - 1].index : 0;
1294
1785
  try {
1295
- currentBytes = statSync(join(dir, fileName(day, index))).size;
1786
+ currentBytes = statSync(join2(dir, fileName(day, index))).size;
1296
1787
  } catch {
1297
1788
  currentBytes = 0;
1298
1789
  }
@@ -1334,7 +1825,7 @@ function fileSink(options = {}) {
1334
1825
  currentBytes = 0;
1335
1826
  prune();
1336
1827
  }
1337
- appendFileSync(join(dir, fileName(day, index)), line);
1828
+ appendFileSync(join2(dir, fileName(day, index)), line);
1338
1829
  currentBytes += bytes;
1339
1830
  } catch (error) {
1340
1831
  disable(error);
@@ -1430,13 +1921,13 @@ function emit(level, message, attributes, extra = {}, options = {}) {
1430
1921
  const exceptions = Object.fromEntries(
1431
1922
  Object.entries(user).filter(([key]) => key.startsWith("exception."))
1432
1923
  );
1433
- const raw = {
1924
+ const raw = stackAttributesLast({
1434
1925
  "autter.event.id": randomUUID(),
1435
1926
  ...extra,
1436
1927
  ...exceptions,
1437
1928
  ...operationAttributes(state),
1438
1929
  ...user
1439
- };
1930
+ });
1440
1931
  const enrichers = shared.config?.options?.enrich;
1441
1932
  if (summary && state && enrichers?.length) {
1442
1933
  const event2 = { level, message, attributes: raw };
@@ -1546,29 +2037,32 @@ function createRuntimeLogger(attributes = {}) {
1546
2037
  debug: (message, extra) => emit("debug", message, merge(attrs, extra)),
1547
2038
  info: (message, extra) => emit("info", message, merge(attrs, extra)),
1548
2039
  warn: (message, extra) => emit("warning", message, merge(attrs, extra)),
1549
- error: (error, extra) => emit(
1550
- "error",
1551
- isError(error) ? error.message : String(error),
1552
- {
1553
- ...attrs,
1554
- ...extra,
1555
- ...isError(error) ? {
1556
- "exception.type": error.name,
1557
- "exception.stacktrace": error.stack ?? ""
1558
- } : {}
1559
- },
1560
- errorAttributes(error)
1561
- )
2040
+ error: (error, extra) => {
2041
+ emit(
2042
+ "error",
2043
+ isError(error) ? error.message : String(error),
2044
+ {
2045
+ ...attrs,
2046
+ ...extra,
2047
+ ...isError(error) ? {
2048
+ "exception.type": error.name,
2049
+ "exception.stacktrace": error.stack ?? ""
2050
+ } : {}
2051
+ },
2052
+ errorAttributes(error)
2053
+ );
2054
+ if (isError(error)) reportLoggedError(error, "runtimeLogger", void 0, toSpanAttributes(merge(attrs, extra)));
2055
+ }
1562
2056
  };
1563
2057
  }
1564
2058
  var runtimeLogger = createRuntimeLogger();
1565
- function captureExceptionAsLog(error, attributes = {}) {
2059
+ function captureExceptionAsLog(error, attributes = {}, level = "error") {
1566
2060
  const isErr = isError(error);
1567
2061
  const message = isErr ? error.message : String(error);
1568
2062
  const stack = isErr && error.stack ? error.stack : new Error().stack?.split("\n").filter((line, i) => i === 0 || !/captureException/.test(line)).join("\n") ?? "";
1569
- noteOperationError(error);
2063
+ if (level === "error") noteOperationError(error);
1570
2064
  emit(
1571
- "error",
2065
+ level,
1572
2066
  message,
1573
2067
  {
1574
2068
  ...attributes,
@@ -1653,13 +2147,15 @@ function summaryExtra(state, level, http = {}) {
1653
2147
  "autter.operation.level": maxLevel(state.inline.level, level),
1654
2148
  ...state.ai ? { "autter.operation.ai": state.ai } : {},
1655
2149
  ...http,
1656
- ...err !== void 0 ? errorAttributes(err) : {},
2150
+ // A summary is no occurrence: no cause stacks or debug ids.
2151
+ ...err !== void 0 ? errorAttributes(err, { stacks: false }) : {},
1657
2152
  ...state.inline.logsTruncated ? { "autter.operation.logs_truncated": true } : {},
1658
2153
  ...state.inline.logs.length ? { "autter.operation.logs": state.inline.logs } : {}
1659
2154
  };
1660
2155
  }
1661
2156
  function emitSummary(state, options = {}) {
1662
2157
  if (state.sealed) return;
2158
+ settleDegraded(state);
1663
2159
  const err = state.error;
1664
2160
  const level = state.outcome === "failed" ? "error" : "info";
1665
2161
  emit(
@@ -1677,6 +2173,10 @@ function emitSummary(state, options = {}) {
1677
2173
  );
1678
2174
  state.sealed = true;
1679
2175
  }
2176
+ function settleDegraded(state) {
2177
+ if (state.degraded && state.outcome === "succeeded" && !state.explicitOutcome)
2178
+ state.outcome = "degraded";
2179
+ }
1680
2180
  function reportExplicitFailure(state) {
1681
2181
  if (state.outcome === "failed" && state.explicitOutcome && !state.errorThrown)
1682
2182
  shared.config?.reportOutcome(state.name, state.message ?? "Operation failed", {
@@ -1716,6 +2216,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
1716
2216
  shared.config?.captureThrown?.(error);
1717
2217
  throw error;
1718
2218
  } finally {
2219
+ settleDegraded(state);
1719
2220
  trace.getActiveSpan()?.setAttributes({
1720
2221
  ...traceAttributes(state),
1721
2222
  "autter.operation.outcome": state.outcome,
@@ -1787,6 +2288,30 @@ var runtimeContext = {
1787
2288
  noteOperationError(error);
1788
2289
  runtimeLogger.error(error, attributes);
1789
2290
  },
2291
+ /**
2292
+ * The app caught `error` and chose to continue on a fallback (RF-02):
2293
+ *
2294
+ * } catch (err) {
2295
+ * runtimeContext.degraded(err, "pricing service down, using cached prices");
2296
+ * }
2297
+ *
2298
+ * Recorded as an occurrence at `warning` severity, marked
2299
+ * `autter.handling = "deliberate"` with the reason (grouped by its code
2300
+ * when the error has one), never an incident. The current request or
2301
+ * operation ends "degraded" unless it failed or set its outcome.
2302
+ */
2303
+ degraded(error, reason, attributes) {
2304
+ try {
2305
+ const state = currentOperation();
2306
+ if (state) {
2307
+ state.degraded = true;
2308
+ noteOperationError(error, false, state);
2309
+ appendInlineLog(state.inline, "warning", `degraded: ${String(reason)}`, void 0, redact);
2310
+ }
2311
+ reportDegraded(error, reason, toSpanAttributes(userContext(attributes, redact)));
2312
+ } catch {
2313
+ }
2314
+ },
1790
2315
  /** Current operation id. */
1791
2316
  get id() {
1792
2317
  return local.getStore()?.id;
@@ -1816,6 +2341,15 @@ var runtimeContext = {
1816
2341
  });
1817
2342
  }
1818
2343
  };
2344
+ setHandledTail(
2345
+ (type, dropped) => emit(
2346
+ "warning",
2347
+ `${dropped} caught ${type} error(s) left out by the burst limit`,
2348
+ { "autter.caught.dropped": dropped, "autter.caught.type": type },
2349
+ {},
2350
+ { state: null }
2351
+ )
2352
+ );
1819
2353
 
1820
2354
  // src/breadcrumbs.ts
1821
2355
  var INVALID_TRACE_ID = "00000000000000000000000000000000";
@@ -1878,10 +2412,10 @@ function recordLogBreadcrumb(level, message, attributes, state) {
1878
2412
  }
1879
2413
  }
1880
2414
  function attributeBudget() {
1881
- const limit = Number(
2415
+ const limit2 = Number(
1882
2416
  process.env.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT ?? process.env.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT
1883
2417
  );
1884
- return Number.isFinite(limit) && limit > 0 ? Math.min(BREADCRUMBS_ATTRIBUTE_MAX_CHARS, limit) : BREADCRUMBS_ATTRIBUTE_MAX_CHARS;
2418
+ return Number.isFinite(limit2) && limit2 > 0 ? Math.min(BREADCRUMBS_ATTRIBUTE_MAX_CHARS, limit2) : BREADCRUMBS_ATTRIBUTE_MAX_CHARS;
1885
2419
  }
1886
2420
  function rememberThrownBreadcrumbs(error, state) {
1887
2421
  if (state.breadcrumbs && error !== null && (typeof error === "object" || typeof error === "function"))
@@ -1924,8 +2458,14 @@ export {
1924
2458
  fingerprintValues,
1925
2459
  fingerprintEventAttributes,
1926
2460
  splitFingerprint,
2461
+ createErrorFilter,
1927
2462
  addBreadcrumb,
1928
2463
  breadcrumbEventAttributes,
2464
+ markCaptured,
2465
+ isCaptured,
2466
+ setHandledCapture,
2467
+ startLoggedErrorCapture,
2468
+ stopLoggedErrorCapture,
1929
2469
  otlpSink,
1930
2470
  consoleSink,
1931
2471
  fileSink,