@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.
@@ -2,7 +2,7 @@ import {
2
2
  noteCompatFeature,
3
3
  observeIngesterResponse,
4
4
  sharedSlot
5
- } from "./chunk-B5ZMOCEM.js";
5
+ } from "./chunk-VIP2EL47.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,132 @@ 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
+ };
977
1170
  function testPattern(pattern, type, message) {
978
1171
  if (typeof pattern === "string") {
979
1172
  return pattern !== "" && (message.includes(pattern) || type.includes(pattern));
@@ -1022,6 +1215,267 @@ import {
1022
1215
  trace
1023
1216
  } from "@opentelemetry/api";
1024
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
+
1025
1479
  // src/sinks.ts
1026
1480
  import {
1027
1481
  appendFileSync,
@@ -1030,7 +1484,7 @@ import {
1030
1484
  statSync,
1031
1485
  unlinkSync
1032
1486
  } from "fs";
1033
- import { join, resolve } from "path";
1487
+ import { join as join2, resolve } from "path";
1034
1488
  var sinkStats = sharedSlot("sinks", () => ({ dropped: 0 }));
1035
1489
  function otlpSink(options = {}) {
1036
1490
  let context = null;
@@ -1042,6 +1496,7 @@ function otlpSink(options = {}) {
1042
1496
  let flushing = null;
1043
1497
  let stopping = false;
1044
1498
  let failureStreak = 0;
1499
+ let nextFlushAfter = 0;
1045
1500
  const scheduleFlush = () => {
1046
1501
  if (!timer && context && !stopping && queue.length) {
1047
1502
  timer = setTimeout(
@@ -1050,7 +1505,7 @@ function otlpSink(options = {}) {
1050
1505
  void flush().catch(() => {
1051
1506
  });
1052
1507
  },
1053
- Math.min(3e4, 2e3 * 2 ** Math.min(failureStreak, 4))
1508
+ Math.max(nextFlushAfter, Math.min(3e4, 2e3 * 2 ** Math.min(failureStreak, 4)))
1054
1509
  );
1055
1510
  timer.unref();
1056
1511
  }
@@ -1093,6 +1548,7 @@ function otlpSink(options = {}) {
1093
1548
  );
1094
1549
  let failure;
1095
1550
  for (let attempt = 0; attempt < 3; attempt++) {
1551
+ let retryAfter = null;
1096
1552
  try {
1097
1553
  if (Date.now() >= deadline)
1098
1554
  throw new Error("Runtime log flush exceeded 10 seconds");
@@ -1108,13 +1564,22 @@ function otlpSink(options = {}) {
1108
1564
  )
1109
1565
  });
1110
1566
  observeIngesterResponse({ status: response.status, headers: response.headers, route: "/v1/logs" });
1111
- if (!response.ok)
1112
- throw new Error(`Runtime log export failed (${response.status})`);
1113
- failure = void 0;
1114
- break;
1567
+ if (!response.ok) {
1568
+ failure = new Error(`Runtime log export failed (${response.status})`);
1569
+ if (response.status !== 429 && response.status < 500) break;
1570
+ retryAfter = response.headers.get("retry-after");
1571
+ } else {
1572
+ failure = void 0;
1573
+ break;
1574
+ }
1115
1575
  } catch (error) {
1116
1576
  failure = error;
1117
1577
  }
1578
+ const seconds = Number(retryAfter);
1579
+ const wait = seconds > 0 ? Math.min(seconds * 1e3, 3e5) : 500 * 2 ** attempt;
1580
+ nextFlushAfter = Math.max(nextFlushAfter, wait);
1581
+ if (attempt >= 2 || Date.now() + wait >= deadline) break;
1582
+ await new Promise((resolve2) => setTimeout(resolve2, wait));
1118
1583
  }
1119
1584
  inFlight = 0;
1120
1585
  if (failure) {
@@ -1126,6 +1591,7 @@ function otlpSink(options = {}) {
1126
1591
  }
1127
1592
  inFlightBytes = 0;
1128
1593
  failureStreak = 0;
1594
+ nextFlushAfter = 0;
1129
1595
  }
1130
1596
  })().finally(() => {
1131
1597
  flushing = null;
@@ -1284,14 +1750,14 @@ function formatPretty(event, color = false) {
1284
1750
  return lines.join("\n");
1285
1751
  }
1286
1752
  function consoleSink(options = {}) {
1287
- const format = options.format ?? (process.env.NODE_ENV === "production" ? "json" : "pretty");
1753
+ const format2 = options.format ?? (process.env.NODE_ENV === "production" ? "json" : "pretty");
1288
1754
  const color = options.color ?? (process.stdout?.isTTY === true && !process.env.NO_COLOR);
1289
1755
  const write = options.write ?? ((line) => console.log(line));
1290
1756
  return {
1291
1757
  name: "console",
1292
1758
  write(event) {
1293
1759
  write(
1294
- format === "json" ? JSON.stringify({
1760
+ format2 === "json" ? JSON.stringify({
1295
1761
  ...event.attributes,
1296
1762
  level: event.level,
1297
1763
  message: event.message
@@ -1321,7 +1787,7 @@ function fileSink(options = {}) {
1321
1787
  const existing = files().filter((file) => file.name !== current);
1322
1788
  const excess = existing.length + 1 - maxFiles;
1323
1789
  for (const file of existing.slice(0, Math.max(0, excess)))
1324
- unlinkSync(join(dir, file.name));
1790
+ unlinkSync(join2(dir, file.name));
1325
1791
  };
1326
1792
  const open = (nextDay) => {
1327
1793
  mkdirSync(dir, { recursive: true });
@@ -1329,7 +1795,7 @@ function fileSink(options = {}) {
1329
1795
  const sameDay = files().filter((file) => file.day === day);
1330
1796
  index = sameDay.length ? sameDay[sameDay.length - 1].index : 0;
1331
1797
  try {
1332
- currentBytes = statSync(join(dir, fileName(day, index))).size;
1798
+ currentBytes = statSync(join2(dir, fileName(day, index))).size;
1333
1799
  } catch {
1334
1800
  currentBytes = 0;
1335
1801
  }
@@ -1371,7 +1837,7 @@ function fileSink(options = {}) {
1371
1837
  currentBytes = 0;
1372
1838
  prune();
1373
1839
  }
1374
- appendFileSync(join(dir, fileName(day, index)), line);
1840
+ appendFileSync(join2(dir, fileName(day, index)), line);
1375
1841
  currentBytes += bytes;
1376
1842
  } catch (error) {
1377
1843
  disable(error);
@@ -1467,13 +1933,13 @@ function emit(level, message, attributes, extra = {}, options = {}) {
1467
1933
  const exceptions = Object.fromEntries(
1468
1934
  Object.entries(user).filter(([key]) => key.startsWith("exception."))
1469
1935
  );
1470
- const raw = {
1936
+ const raw = stackAttributesLast({
1471
1937
  "autter.event.id": randomUUID(),
1472
1938
  ...extra,
1473
1939
  ...exceptions,
1474
1940
  ...operationAttributes(state),
1475
1941
  ...user
1476
- };
1942
+ });
1477
1943
  const enrichers = shared.config?.options?.enrich;
1478
1944
  if (summary && state && enrichers?.length) {
1479
1945
  const event2 = { level, message, attributes: raw };
@@ -1583,29 +2049,32 @@ function createRuntimeLogger(attributes = {}) {
1583
2049
  debug: (message, extra) => emit("debug", message, merge(attrs, extra)),
1584
2050
  info: (message, extra) => emit("info", message, merge(attrs, extra)),
1585
2051
  warn: (message, extra) => emit("warning", message, merge(attrs, extra)),
1586
- error: (error, extra) => emit(
1587
- "error",
1588
- isError(error) ? error.message : String(error),
1589
- {
1590
- ...attrs,
1591
- ...extra,
1592
- ...isError(error) ? {
1593
- "exception.type": error.name,
1594
- "exception.stacktrace": error.stack ?? ""
1595
- } : {}
1596
- },
1597
- errorAttributes(error)
1598
- )
2052
+ error: (error, extra) => {
2053
+ emit(
2054
+ "error",
2055
+ isError(error) ? error.message : String(error),
2056
+ {
2057
+ ...attrs,
2058
+ ...extra,
2059
+ ...isError(error) ? {
2060
+ "exception.type": error.name,
2061
+ "exception.stacktrace": error.stack ?? ""
2062
+ } : {}
2063
+ },
2064
+ errorAttributes(error)
2065
+ );
2066
+ if (isError(error)) reportLoggedError(error, "runtimeLogger", void 0, toSpanAttributes(merge(attrs, extra)));
2067
+ }
1599
2068
  };
1600
2069
  }
1601
2070
  var runtimeLogger = createRuntimeLogger();
1602
- function captureExceptionAsLog(error, attributes = {}) {
2071
+ function captureExceptionAsLog(error, attributes = {}, level = "error") {
1603
2072
  const isErr = isError(error);
1604
2073
  const message = isErr ? error.message : String(error);
1605
2074
  const stack = isErr && error.stack ? error.stack : new Error().stack?.split("\n").filter((line, i) => i === 0 || !/captureException/.test(line)).join("\n") ?? "";
1606
- noteOperationError(error);
2075
+ if (level === "error") noteOperationError(error);
1607
2076
  emit(
1608
- "error",
2077
+ level,
1609
2078
  message,
1610
2079
  {
1611
2080
  ...attributes,
@@ -1690,13 +2159,15 @@ function summaryExtra(state, level, http = {}) {
1690
2159
  "autter.operation.level": maxLevel(state.inline.level, level),
1691
2160
  ...state.ai ? { "autter.operation.ai": state.ai } : {},
1692
2161
  ...http,
1693
- ...err !== void 0 ? errorAttributes(err) : {},
2162
+ // A summary is no occurrence: no cause stacks or debug ids.
2163
+ ...err !== void 0 ? errorAttributes(err, { stacks: false }) : {},
1694
2164
  ...state.inline.logsTruncated ? { "autter.operation.logs_truncated": true } : {},
1695
2165
  ...state.inline.logs.length ? { "autter.operation.logs": state.inline.logs } : {}
1696
2166
  };
1697
2167
  }
1698
2168
  function emitSummary(state, options = {}) {
1699
2169
  if (state.sealed) return;
2170
+ settleDegraded(state);
1700
2171
  const err = state.error;
1701
2172
  const level = state.outcome === "failed" ? "error" : "info";
1702
2173
  emit(
@@ -1714,6 +2185,10 @@ function emitSummary(state, options = {}) {
1714
2185
  );
1715
2186
  state.sealed = true;
1716
2187
  }
2188
+ function settleDegraded(state) {
2189
+ if (state.degraded && state.outcome === "succeeded" && !state.explicitOutcome)
2190
+ state.outcome = "degraded";
2191
+ }
1717
2192
  function reportExplicitFailure(state) {
1718
2193
  if (state.outcome === "failed" && state.explicitOutcome && !state.errorThrown)
1719
2194
  shared.config?.reportOutcome(state.name, state.message ?? "Operation failed", {
@@ -1753,6 +2228,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
1753
2228
  shared.config?.captureThrown?.(error);
1754
2229
  throw error;
1755
2230
  } finally {
2231
+ settleDegraded(state);
1756
2232
  trace.getActiveSpan()?.setAttributes({
1757
2233
  ...traceAttributes(state),
1758
2234
  "autter.operation.outcome": state.outcome,
@@ -1824,6 +2300,30 @@ var runtimeContext = {
1824
2300
  noteOperationError(error);
1825
2301
  runtimeLogger.error(error, attributes);
1826
2302
  },
2303
+ /**
2304
+ * The app caught `error` and chose to continue on a fallback (RF-02):
2305
+ *
2306
+ * } catch (err) {
2307
+ * runtimeContext.degraded(err, "pricing service down, using cached prices");
2308
+ * }
2309
+ *
2310
+ * Recorded as an occurrence at `warning` severity, marked
2311
+ * `autter.handling = "deliberate"` with the reason (grouped by its code
2312
+ * when the error has one), never an incident. The current request or
2313
+ * operation ends "degraded" unless it failed or set its outcome.
2314
+ */
2315
+ degraded(error, reason, attributes) {
2316
+ try {
2317
+ const state = currentOperation();
2318
+ if (state) {
2319
+ state.degraded = true;
2320
+ noteOperationError(error, false, state);
2321
+ appendInlineLog(state.inline, "warning", `degraded: ${String(reason)}`, void 0, redact);
2322
+ }
2323
+ reportDegraded(error, reason, toSpanAttributes(userContext(attributes, redact)));
2324
+ } catch {
2325
+ }
2326
+ },
1827
2327
  /** Current operation id. */
1828
2328
  get id() {
1829
2329
  return local.getStore()?.id;
@@ -1853,6 +2353,15 @@ var runtimeContext = {
1853
2353
  });
1854
2354
  }
1855
2355
  };
2356
+ setHandledTail(
2357
+ (type, dropped) => emit(
2358
+ "warning",
2359
+ `${dropped} caught ${type} error(s) left out by the burst limit`,
2360
+ { "autter.caught.dropped": dropped, "autter.caught.type": type },
2361
+ {},
2362
+ { state: null }
2363
+ )
2364
+ );
1856
2365
 
1857
2366
  // src/breadcrumbs.ts
1858
2367
  var INVALID_TRACE_ID = "00000000000000000000000000000000";
@@ -1915,10 +2424,10 @@ function recordLogBreadcrumb(level, message, attributes, state) {
1915
2424
  }
1916
2425
  }
1917
2426
  function attributeBudget() {
1918
- const limit = Number(
2427
+ const limit2 = Number(
1919
2428
  process.env.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT ?? process.env.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT
1920
2429
  );
1921
- return Number.isFinite(limit) && limit > 0 ? Math.min(BREADCRUMBS_ATTRIBUTE_MAX_CHARS, limit) : BREADCRUMBS_ATTRIBUTE_MAX_CHARS;
2430
+ return Number.isFinite(limit2) && limit2 > 0 ? Math.min(BREADCRUMBS_ATTRIBUTE_MAX_CHARS, limit2) : BREADCRUMBS_ATTRIBUTE_MAX_CHARS;
1922
2431
  }
1923
2432
  function rememberThrownBreadcrumbs(error, state) {
1924
2433
  if (state.breadcrumbs && error !== null && (typeof error === "object" || typeof error === "function"))
@@ -1964,6 +2473,11 @@ export {
1964
2473
  createErrorFilter,
1965
2474
  addBreadcrumb,
1966
2475
  breadcrumbEventAttributes,
2476
+ markCaptured,
2477
+ isCaptured,
2478
+ setHandledCapture,
2479
+ startLoggedErrorCapture,
2480
+ stopLoggedErrorCapture,
1967
2481
  otlpSink,
1968
2482
  consoleSink,
1969
2483
  fileSink,