@autter/runtime-node 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -1
- package/dist/{chunk-GI47WGRR.cjs → chunk-ARN26OK6.cjs} +32 -1
- package/dist/{chunk-3TJHUCZ6.js → chunk-FBKYOHU2.js} +290 -28
- package/dist/{chunk-L7HWKMCZ.js → chunk-OY2HTUWY.js} +32 -1
- package/dist/{chunk-L6SRL65J.cjs → chunk-UGNPCOIW.cjs} +11 -11
- package/dist/{chunk-P4VRTFAV.cjs → chunk-UKYTXZOQ.cjs} +302 -40
- package/dist/{chunk-ZS4WQJQR.js → chunk-Y4T7SQMO.js} +1 -1
- package/dist/cli.cjs +29 -9
- package/dist/cli.js +22 -2
- package/dist/index.cjs +474 -187
- package/dist/index.d.cts +148 -7
- package/dist/index.d.ts +148 -7
- package/dist/index.js +335 -48
- package/dist/{sinks-RFV76lF1.d.cts → sinks-CPGDyfU8.d.cts} +29 -1
- package/dist/{sinks-RFV76lF1.d.ts → sinks-CPGDyfU8.d.ts} +29 -1
- package/dist/testing.cjs +8 -8
- package/dist/testing.d.cts +2 -2
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +2 -2
- package/package.json +3 -1
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import {
|
|
|
2
2
|
CODE_PATTERN,
|
|
3
3
|
DEFAULT_REQUEST_ID_HEADER,
|
|
4
4
|
RuntimeError,
|
|
5
|
+
addBreadcrumb,
|
|
6
|
+
breadcrumbEventAttributes,
|
|
5
7
|
captureExceptionAsLog,
|
|
6
8
|
compileIgnore,
|
|
7
9
|
configureRuntimeLogger,
|
|
@@ -15,7 +17,10 @@ import {
|
|
|
15
17
|
errorCodeOf,
|
|
16
18
|
errorInternal,
|
|
17
19
|
fileSink,
|
|
20
|
+
fingerprintEventAttributes,
|
|
21
|
+
fingerprintValues,
|
|
18
22
|
flushRuntimeLogs,
|
|
23
|
+
isError,
|
|
19
24
|
isRuntimeErrorLike,
|
|
20
25
|
local,
|
|
21
26
|
makeRedactor,
|
|
@@ -35,13 +40,15 @@ import {
|
|
|
35
40
|
runtimeContext,
|
|
36
41
|
runtimeLogStats,
|
|
37
42
|
runtimeLogger,
|
|
43
|
+
sanitizeBreadcrumbs,
|
|
38
44
|
shutdownRuntimeLogger,
|
|
45
|
+
splitFingerprint,
|
|
39
46
|
toClientError,
|
|
40
47
|
withRuntimeOperation
|
|
41
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-FBKYOHU2.js";
|
|
42
49
|
import {
|
|
43
50
|
runDoctor
|
|
44
|
-
} from "./chunk-
|
|
51
|
+
} from "./chunk-Y4T7SQMO.js";
|
|
45
52
|
import {
|
|
46
53
|
COMPAT_MANIFEST,
|
|
47
54
|
CountingExporter,
|
|
@@ -62,7 +69,7 @@ import {
|
|
|
62
69
|
setDebugMode,
|
|
63
70
|
telemetryStats,
|
|
64
71
|
unregisterFlushTargets
|
|
65
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-OY2HTUWY.js";
|
|
66
73
|
|
|
67
74
|
// src/relay.ts
|
|
68
75
|
var IpWindow = class {
|
|
@@ -102,6 +109,7 @@ var EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
|
102
109
|
"timing"
|
|
103
110
|
]);
|
|
104
111
|
var SEVERITIES = /* @__PURE__ */ new Set(["fatal", "error", "warning", "info"]);
|
|
112
|
+
var TRACE_ID = /^(?!0{32})[0-9a-f]{32}$/;
|
|
105
113
|
var CONTEXT_MAX_DEPTH = 6;
|
|
106
114
|
var CONTEXT_MAX_NODES = 256;
|
|
107
115
|
var CONTEXT_MAX_STRING = 4e3;
|
|
@@ -214,6 +222,27 @@ function relayContext(raw, redactor) {
|
|
|
214
222
|
const bounded = boundContext(raw);
|
|
215
223
|
return Array.isArray(bounded) ? {} : { context: redactor(bounded) };
|
|
216
224
|
}
|
|
225
|
+
function relayBreadcrumbs(raw, redactor) {
|
|
226
|
+
const breadcrumbs = sanitizeBreadcrumbs(raw, redactor);
|
|
227
|
+
return breadcrumbs ? { breadcrumbs } : {};
|
|
228
|
+
}
|
|
229
|
+
var DEBUG_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
230
|
+
function relayDebugIds(raw, redactor) {
|
|
231
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return {};
|
|
232
|
+
const debugIds = {};
|
|
233
|
+
let count = 0;
|
|
234
|
+
for (const [url, id] of Object.entries(raw)) {
|
|
235
|
+
if (count >= 50) break;
|
|
236
|
+
if (typeof id !== "string" || !DEBUG_ID_RE.test(id) || url.length > 1e3) continue;
|
|
237
|
+
debugIds[redactor.text(url.split(/[?#]/)[0])] = id.toLowerCase();
|
|
238
|
+
count++;
|
|
239
|
+
}
|
|
240
|
+
return count ? { debugIds } : {};
|
|
241
|
+
}
|
|
242
|
+
function relayFingerprint(raw, redactor) {
|
|
243
|
+
const values = fingerprintValues(raw, redactor.text);
|
|
244
|
+
return values ? { fingerprint: Array.isArray(raw) ? values : values[0] } : {};
|
|
245
|
+
}
|
|
217
246
|
var defaultRelayRedactor = makeRedactor(true);
|
|
218
247
|
function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
|
|
219
248
|
if (typeof raw !== "object" || raw === null) return null;
|
|
@@ -241,7 +270,11 @@ function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
|
|
|
241
270
|
...typeof e.line === "number" ? { line: e.line } : {},
|
|
242
271
|
...typeof e.column === "number" ? { column: e.column } : {},
|
|
243
272
|
...typeof e.route === "string" ? { route: e.route.split("?")[0].slice(0, 1e3) } : {},
|
|
244
|
-
...typeof e.context === "object" && e.context !== null ? relayContext(e.context, redactor) : {}
|
|
273
|
+
...typeof e.context === "object" && e.context !== null ? relayContext(e.context, redactor) : {},
|
|
274
|
+
...relayBreadcrumbs(e.breadcrumbs, redactor),
|
|
275
|
+
...relayDebugIds(e.debugIds, redactor),
|
|
276
|
+
...relayFingerprint(e.fingerprint, redactor),
|
|
277
|
+
...typeof e.traceId === "string" && TRACE_ID.test(e.traceId) ? { traceId: e.traceId } : {}
|
|
245
278
|
});
|
|
246
279
|
}
|
|
247
280
|
return {
|
|
@@ -275,6 +308,9 @@ function forward(payload, opts) {
|
|
|
275
308
|
);
|
|
276
309
|
observeIngesterResponse({ status: response.status, headers: response.headers });
|
|
277
310
|
for (const feature of featuresForBrowserEvents(types)) noteCompatFeature(feature);
|
|
311
|
+
if ((payload.events ?? []).some((e) => e.traceId)) {
|
|
312
|
+
noteCompatFeature("browser_trace_propagation");
|
|
313
|
+
}
|
|
278
314
|
noteRelayRejection(types, response.status, response.headers);
|
|
279
315
|
}).catch((err) => {
|
|
280
316
|
(opts.onError ?? ((e) => console.warn("autter relay forward failed:", e)))(
|
|
@@ -413,7 +449,7 @@ import {
|
|
|
413
449
|
SpanStatusCode,
|
|
414
450
|
TraceFlags
|
|
415
451
|
} from "@opentelemetry/api";
|
|
416
|
-
import { getRPCMetadata, RPCType } from "@opentelemetry/core";
|
|
452
|
+
import { getRPCMetadata as getRPCMetadata2, RPCType as RPCType2 } from "@opentelemetry/core";
|
|
417
453
|
import {
|
|
418
454
|
AggregationTemporalityPreference,
|
|
419
455
|
OTLPMetricExporter
|
|
@@ -602,6 +638,7 @@ function startMemoryMetrics() {
|
|
|
602
638
|
// src/requests.ts
|
|
603
639
|
import { AsyncResource } from "async_hooks";
|
|
604
640
|
import { context, trace } from "@opentelemetry/api";
|
|
641
|
+
import { getRPCMetadata, RPCType } from "@opentelemetry/core";
|
|
605
642
|
var REQUEST_STATE = /* @__PURE__ */ Symbol.for("autter.runtime.request");
|
|
606
643
|
var BOUND = /* @__PURE__ */ Symbol.for("autter.runtime.bound");
|
|
607
644
|
var CAPTURED = /* @__PURE__ */ new WeakSet();
|
|
@@ -610,6 +647,11 @@ var capturer = () => {
|
|
|
610
647
|
function setRequestExceptionCapturer(fn) {
|
|
611
648
|
capturer = fn;
|
|
612
649
|
}
|
|
650
|
+
var annotator = () => {
|
|
651
|
+
};
|
|
652
|
+
function setRequestSpanAnnotator(fn) {
|
|
653
|
+
annotator = fn;
|
|
654
|
+
}
|
|
613
655
|
function markCaptured(error) {
|
|
614
656
|
if (error && typeof error === "object") CAPTURED.add(error);
|
|
615
657
|
}
|
|
@@ -624,17 +666,48 @@ function expressRouteOf(req) {
|
|
|
624
666
|
function defaultShouldCapture(error) {
|
|
625
667
|
return error instanceof RuntimeError || errorCodeOf(error) !== void 0 || responseStatusOf(error) >= 500;
|
|
626
668
|
}
|
|
627
|
-
function
|
|
628
|
-
const
|
|
669
|
+
function onRequestSpan(fn) {
|
|
670
|
+
const active2 = context.active();
|
|
671
|
+
const metadata = getRPCMetadata(active2);
|
|
672
|
+
const span = metadata?.type === RPCType.HTTP ? metadata.span : void 0;
|
|
673
|
+
if (!span || !span.isRecording() || trace.getSpan(active2) === span) {
|
|
674
|
+
fn();
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
context.with(trace.setSpan(active2, span), fn);
|
|
678
|
+
}
|
|
679
|
+
function captureAtBoundary(error, state, rule = void 0, attributes) {
|
|
680
|
+
let wanted;
|
|
681
|
+
try {
|
|
682
|
+
wanted = typeof rule === "function" ? rule(error) : rule === void 0 ? defaultShouldCapture(error) : rule;
|
|
683
|
+
} catch {
|
|
684
|
+
wanted = false;
|
|
685
|
+
}
|
|
629
686
|
if (!wanted) return;
|
|
630
687
|
if (error && typeof error === "object" && CAPTURED.has(error)) return;
|
|
631
688
|
markCaptured(error);
|
|
632
689
|
try {
|
|
633
|
-
|
|
634
|
-
|
|
690
|
+
const run = () => onRequestSpan(() => capturer(error, attributes));
|
|
691
|
+
if (state && !state.sealed) local.run(state, run);
|
|
692
|
+
else run();
|
|
635
693
|
} catch {
|
|
636
694
|
}
|
|
637
695
|
}
|
|
696
|
+
function requestStateOf(req) {
|
|
697
|
+
if (!req || typeof req !== "object") return void 0;
|
|
698
|
+
return req[REQUEST_STATE];
|
|
699
|
+
}
|
|
700
|
+
function captureRequestError(error, attributes) {
|
|
701
|
+
if (error && typeof error === "object" && CAPTURED.has(error)) {
|
|
702
|
+
if (!attributes) return;
|
|
703
|
+
try {
|
|
704
|
+
onRequestSpan(() => annotator(attributes));
|
|
705
|
+
} catch {
|
|
706
|
+
}
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
captureAtBoundary(error, currentOperation(), true, attributes);
|
|
710
|
+
}
|
|
638
711
|
function bindEmitter(emitter, state) {
|
|
639
712
|
const target = emitter;
|
|
640
713
|
if (!target || typeof target !== "object" || target[BOUND]) return;
|
|
@@ -976,6 +1049,132 @@ function startHookRequest(res) {
|
|
|
976
1049
|
local.enterWith(state);
|
|
977
1050
|
}
|
|
978
1051
|
|
|
1052
|
+
// src/unhandled-rejections.ts
|
|
1053
|
+
var MODES = /* @__PURE__ */ new Set([
|
|
1054
|
+
"throw",
|
|
1055
|
+
"strict",
|
|
1056
|
+
"warn",
|
|
1057
|
+
"warn-with-error-code",
|
|
1058
|
+
"none"
|
|
1059
|
+
]);
|
|
1060
|
+
var LISTENER_MARK = /* @__PURE__ */ Symbol.for("autter.runtime.unhandledRejection.listener");
|
|
1061
|
+
var HANDLED_KEY = /* @__PURE__ */ Symbol.for("autter.runtime.unhandledRejection.handled");
|
|
1062
|
+
function handledPromises() {
|
|
1063
|
+
const registry = globalThis;
|
|
1064
|
+
let set = registry[HANDLED_KEY];
|
|
1065
|
+
if (!set) {
|
|
1066
|
+
set = /* @__PURE__ */ new WeakSet();
|
|
1067
|
+
registry[HANDLED_KEY] = set;
|
|
1068
|
+
}
|
|
1069
|
+
return set;
|
|
1070
|
+
}
|
|
1071
|
+
var recorded = /* @__PURE__ */ new WeakSet();
|
|
1072
|
+
function markRecorded(reason) {
|
|
1073
|
+
if (!reason || typeof reason !== "object") return;
|
|
1074
|
+
recorded.add(reason);
|
|
1075
|
+
setImmediate(() => recorded.delete(reason)).unref?.();
|
|
1076
|
+
}
|
|
1077
|
+
function recordedAsRejection(error) {
|
|
1078
|
+
return !!error && typeof error === "object" && recorded.has(error);
|
|
1079
|
+
}
|
|
1080
|
+
function splitOptions(value) {
|
|
1081
|
+
return value ? value.split(/\s+/).filter(Boolean) : [];
|
|
1082
|
+
}
|
|
1083
|
+
function unhandledRejectionsMode(execArgv = process.execArgv, nodeOptions = process.env.NODE_OPTIONS) {
|
|
1084
|
+
const args = [...splitOptions(nodeOptions), ...execArgv];
|
|
1085
|
+
let mode = "throw";
|
|
1086
|
+
for (let i = 0; i < args.length; i++) {
|
|
1087
|
+
const arg = args[i] ?? "";
|
|
1088
|
+
if (!arg.startsWith("--")) continue;
|
|
1089
|
+
const eq = arg.indexOf("=");
|
|
1090
|
+
const name = (eq < 0 ? arg : arg.slice(0, eq)).replace(/_/g, "-");
|
|
1091
|
+
if (name !== "--unhandled-rejections") continue;
|
|
1092
|
+
const value = eq < 0 ? args[++i] : arg.slice(eq + 1);
|
|
1093
|
+
if (value && MODES.has(value))
|
|
1094
|
+
mode = value;
|
|
1095
|
+
}
|
|
1096
|
+
return mode;
|
|
1097
|
+
}
|
|
1098
|
+
function isErrorLike(value) {
|
|
1099
|
+
try {
|
|
1100
|
+
return typeof value === "object" && value !== null && Object.prototype.hasOwnProperty.call(value, "stack");
|
|
1101
|
+
} catch {
|
|
1102
|
+
return false;
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function describeReason(reason) {
|
|
1106
|
+
if (typeof reason !== "object" && typeof reason !== "function" || reason === null) {
|
|
1107
|
+
try {
|
|
1108
|
+
return String(reason);
|
|
1109
|
+
} catch {
|
|
1110
|
+
return typeof reason;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
try {
|
|
1114
|
+
const proto = Object.getPrototypeOf(reason);
|
|
1115
|
+
const ctor = proto?.constructor;
|
|
1116
|
+
const name = typeof ctor === "function" ? ctor.name : "";
|
|
1117
|
+
return name ? `#<${name}>` : "[object Object]";
|
|
1118
|
+
} catch {
|
|
1119
|
+
return "[object Object]";
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function toUncaught(reason, from) {
|
|
1123
|
+
if (isErrorLike(reason)) return reason;
|
|
1124
|
+
const error = new Error(
|
|
1125
|
+
`This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). The promise rejected with the reason "${describeReason(reason)}".`
|
|
1126
|
+
);
|
|
1127
|
+
error.name = "UnhandledPromiseRejection";
|
|
1128
|
+
error.code = "ERR_UNHANDLED_REJECTION";
|
|
1129
|
+
Error.captureStackTrace?.(error, from);
|
|
1130
|
+
return error;
|
|
1131
|
+
}
|
|
1132
|
+
var warningId = 0;
|
|
1133
|
+
function emitNodeWarning(reason) {
|
|
1134
|
+
try {
|
|
1135
|
+
const text = isErrorLike(reason) ? String(reason.stack) : describeReason(reason);
|
|
1136
|
+
process.emitWarning(text, "UnhandledPromiseRejectionWarning");
|
|
1137
|
+
process.emitWarning(
|
|
1138
|
+
`Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag \`--unhandled-rejections=strict\` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: ${++warningId})`,
|
|
1139
|
+
"UnhandledPromiseRejectionWarning"
|
|
1140
|
+
);
|
|
1141
|
+
} catch {
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
function installUnhandledRejectionCapture(capture, mode = unhandledRejectionsMode()) {
|
|
1145
|
+
if (mode === "strict") return () => {
|
|
1146
|
+
};
|
|
1147
|
+
const listener = (reason, promise) => {
|
|
1148
|
+
try {
|
|
1149
|
+
if (promise && typeof promise === "object") {
|
|
1150
|
+
const handled = handledPromises();
|
|
1151
|
+
if (handled.has(promise)) return;
|
|
1152
|
+
handled.add(promise);
|
|
1153
|
+
}
|
|
1154
|
+
const alone = process.listeners("unhandledRejection").every((fn) => fn[LISTENER_MARK] === true);
|
|
1155
|
+
if (alone && mode === "throw") {
|
|
1156
|
+
const error = toUncaught(reason, listener);
|
|
1157
|
+
process.nextTick(() => {
|
|
1158
|
+
throw error;
|
|
1159
|
+
});
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (alone && mode === "warn-with-error-code") {
|
|
1163
|
+
emitNodeWarning(reason);
|
|
1164
|
+
process.exitCode = 1;
|
|
1165
|
+
}
|
|
1166
|
+
markRecorded(reason);
|
|
1167
|
+
capture(reason);
|
|
1168
|
+
} catch {
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
listener[LISTENER_MARK] = true;
|
|
1172
|
+
process.on("unhandledRejection", listener);
|
|
1173
|
+
return () => {
|
|
1174
|
+
process.off("unhandledRejection", listener);
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
|
|
979
1178
|
// src/server.ts
|
|
980
1179
|
var LLM_MARKER_ATTRIBUTES = [
|
|
981
1180
|
"gen_ai.operation.name",
|
|
@@ -1225,7 +1424,7 @@ async function runLlmSpan(tracer, info, fn) {
|
|
|
1225
1424
|
return result;
|
|
1226
1425
|
} catch (err) {
|
|
1227
1426
|
recordError(span, err);
|
|
1228
|
-
if (err
|
|
1427
|
+
if (isError(err)) span.setAttribute("error.type", err.name);
|
|
1229
1428
|
throw err;
|
|
1230
1429
|
} finally {
|
|
1231
1430
|
span.end();
|
|
@@ -1247,7 +1446,7 @@ function recordLlmCall(tracer, call) {
|
|
|
1247
1446
|
);
|
|
1248
1447
|
if (call.error !== void 0 && call.error !== null) {
|
|
1249
1448
|
recordError(span, call.error);
|
|
1250
|
-
if (call.error
|
|
1449
|
+
if (isError(call.error)) {
|
|
1251
1450
|
span.setAttribute("error.type", call.error.name);
|
|
1252
1451
|
}
|
|
1253
1452
|
} else {
|
|
@@ -1275,13 +1474,14 @@ async function runWithSpan(tracer, name, fn, attributes, links) {
|
|
|
1275
1474
|
}
|
|
1276
1475
|
}
|
|
1277
1476
|
function recordError(span, err) {
|
|
1278
|
-
const
|
|
1279
|
-
const message = activeRedactor.text(
|
|
1280
|
-
if (
|
|
1477
|
+
const isErr = isError(err);
|
|
1478
|
+
const message = activeRedactor.text(isErr ? err.message : String(err));
|
|
1479
|
+
if (isErr) {
|
|
1281
1480
|
span.addEvent("exception", {
|
|
1282
1481
|
"exception.type": err.name,
|
|
1283
1482
|
"exception.message": message,
|
|
1284
|
-
...err.stack ? { "exception.stacktrace": activeRedactor.text(err.stack) } : {}
|
|
1483
|
+
...err.stack ? { "exception.stacktrace": activeRedactor.text(err.stack) } : {},
|
|
1484
|
+
...breadcrumbEventAttributes(activeRedactor.text, err)
|
|
1285
1485
|
});
|
|
1286
1486
|
}
|
|
1287
1487
|
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
@@ -1291,8 +1491,8 @@ function redactWithActive(attributes) {
|
|
|
1291
1491
|
}
|
|
1292
1492
|
function captureExpressRoute(_span, response) {
|
|
1293
1493
|
if (!(response instanceof ServerResponse)) return;
|
|
1294
|
-
const rpcMetadata =
|
|
1295
|
-
if (rpcMetadata?.type !==
|
|
1494
|
+
const rpcMetadata = getRPCMetadata2(context2.active());
|
|
1495
|
+
if (rpcMetadata?.type !== RPCType2.HTTP) return;
|
|
1296
1496
|
const setRoute = () => {
|
|
1297
1497
|
if (rpcMetadata.route) return;
|
|
1298
1498
|
const route = expressRouteOf(response.req);
|
|
@@ -1352,8 +1552,15 @@ function initAutterServer(options) {
|
|
|
1352
1552
|
// would double-count every sampled request).
|
|
1353
1553
|
"autter.metrics_wired": true
|
|
1354
1554
|
});
|
|
1555
|
+
const rootSampler = new TraceIdRatioBasedSampler(options.traceSampleRate ?? 0.01);
|
|
1355
1556
|
const headSampler = new ParentBasedSampler({
|
|
1356
|
-
root:
|
|
1557
|
+
root: rootSampler,
|
|
1558
|
+
// Browsers (runtime-browser's traceparent) send unsampled parents: they
|
|
1559
|
+
// make no sampling decision. Apply this service's own rate to such
|
|
1560
|
+
// requests instead of never head-sampling them. The ratio sampler is
|
|
1561
|
+
// deterministic by trace id, so services sharing a rate still agree
|
|
1562
|
+
// with an upstream service that chose not to sample.
|
|
1563
|
+
remoteParentNotSampled: rootSampler
|
|
1357
1564
|
});
|
|
1358
1565
|
const retainOnError = options.retainTracesOnError !== false;
|
|
1359
1566
|
const slowThresholdMs = Number.isFinite(options.retainTracesAboveMs) ? Math.max(0, options.retainTracesAboveMs) : 0;
|
|
@@ -1420,18 +1627,22 @@ function initAutterServer(options) {
|
|
|
1420
1627
|
function captureException2(error, attributes) {
|
|
1421
1628
|
errorTraceBuffer?.retainActiveTrace();
|
|
1422
1629
|
telemetryStats.markCaptured();
|
|
1423
|
-
const
|
|
1424
|
-
const message = activeRedactor.text(
|
|
1425
|
-
const stackText =
|
|
1426
|
-
const
|
|
1630
|
+
const isErr = isError(error);
|
|
1631
|
+
const message = activeRedactor.text(isErr ? error.message : String(error));
|
|
1632
|
+
const stackText = isErr && error.stack ? activeRedactor.text(error.stack) : "";
|
|
1633
|
+
const split = splitFingerprint(attributes);
|
|
1634
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, activeRedactor.text);
|
|
1635
|
+
if (fingerprint["autter.fingerprint"]) noteCompatFeature("fingerprint_override");
|
|
1636
|
+
const redacted = activeRedactor(split.attributes);
|
|
1427
1637
|
const activeSpan = trace2.getActiveSpan();
|
|
1428
1638
|
const reuseActive = activeSpan?.isRecording() === true;
|
|
1429
|
-
const span = reuseActive ? activeSpan : errorTracer.startSpan(
|
|
1639
|
+
const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
|
|
1430
1640
|
const structured = structuredErrorAttributes(error);
|
|
1431
1641
|
const causes = errorAttributes(error);
|
|
1432
|
-
const declared =
|
|
1433
|
-
Object.entries(causes).filter(([key]) => key !== "autter.error.internal")
|
|
1434
|
-
|
|
1642
|
+
const declared = {
|
|
1643
|
+
...Object.fromEntries(Object.entries(causes).filter(([key]) => key !== "autter.error.internal")),
|
|
1644
|
+
...fingerprint
|
|
1645
|
+
};
|
|
1435
1646
|
const requestId = currentOperation()?.requestId;
|
|
1436
1647
|
span.setAttributes({
|
|
1437
1648
|
"autter.severity": "error",
|
|
@@ -1441,23 +1652,25 @@ function initAutterServer(options) {
|
|
|
1441
1652
|
});
|
|
1442
1653
|
markCaptured(error);
|
|
1443
1654
|
noteOperationError(error);
|
|
1444
|
-
notifyException(error, redacted);
|
|
1445
|
-
if (
|
|
1655
|
+
notifyException(error, { ...redacted, ...fingerprint });
|
|
1656
|
+
if (isErr && stackText) {
|
|
1446
1657
|
span.addEvent("exception", {
|
|
1447
1658
|
"exception.type": error.name,
|
|
1448
1659
|
"exception.message": message,
|
|
1449
1660
|
"exception.stacktrace": stackText,
|
|
1450
1661
|
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {},
|
|
1451
|
-
...declared
|
|
1662
|
+
...declared,
|
|
1663
|
+
...breadcrumbEventAttributes(activeRedactor.text, error)
|
|
1452
1664
|
});
|
|
1453
1665
|
} else {
|
|
1454
1666
|
const stack = new Error().stack?.split("\n").filter((line, i) => i === 0 || !line.includes("captureException")).join("\n");
|
|
1455
1667
|
span.addEvent("exception", {
|
|
1456
|
-
"exception.type":
|
|
1668
|
+
"exception.type": isErr ? error.name : "Error",
|
|
1457
1669
|
"exception.message": message,
|
|
1458
1670
|
...stack ? { "exception.stacktrace": activeRedactor.text(stack) } : {},
|
|
1459
1671
|
...redacted["autter.handled"] === true ? { "autter.handled": true, "autter.sampled": redacted["autter.sampled"] === true } : {},
|
|
1460
|
-
...declared
|
|
1672
|
+
...declared,
|
|
1673
|
+
...breadcrumbEventAttributes(activeRedactor.text, error)
|
|
1461
1674
|
});
|
|
1462
1675
|
}
|
|
1463
1676
|
span.setStatus({ code: SpanStatusCode.ERROR, message });
|
|
@@ -1477,7 +1690,8 @@ function initAutterServer(options) {
|
|
|
1477
1690
|
"exception.type": "Message",
|
|
1478
1691
|
"exception.message": safeMessage,
|
|
1479
1692
|
...stack ? { "exception.stacktrace": activeRedactor.text(stack) } : {},
|
|
1480
|
-
"autter.severity": severity
|
|
1693
|
+
"autter.severity": severity,
|
|
1694
|
+
...breadcrumbEventAttributes(activeRedactor.text)
|
|
1481
1695
|
});
|
|
1482
1696
|
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1483
1697
|
span.end();
|
|
@@ -1513,12 +1727,21 @@ function initAutterServer(options) {
|
|
|
1513
1727
|
reportOutcome: reportOutcome2
|
|
1514
1728
|
});
|
|
1515
1729
|
hookRequests = options.logging?.requests === true;
|
|
1730
|
+
let onCrash = null;
|
|
1731
|
+
let uninstallRejections = null;
|
|
1516
1732
|
if (options.captureGlobalErrors !== false) {
|
|
1517
|
-
|
|
1733
|
+
onCrash = (err) => {
|
|
1734
|
+
if (recordedAsRejection(err)) return;
|
|
1518
1735
|
captureException2(err, { "autter.unhandled": true });
|
|
1519
1736
|
if (options.autoFlush === false) void Promise.resolve(flushTarget.forceFlush()).catch(() => {
|
|
1520
1737
|
});
|
|
1521
|
-
}
|
|
1738
|
+
};
|
|
1739
|
+
process.on("uncaughtExceptionMonitor", onCrash);
|
|
1740
|
+
if (options.captureUnhandledRejections !== false) {
|
|
1741
|
+
uninstallRejections = installUnhandledRejectionCapture(
|
|
1742
|
+
(reason) => captureException2(reason, { "autter.unhandled_rejection": true })
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1522
1745
|
}
|
|
1523
1746
|
const flushTarget = {
|
|
1524
1747
|
forceFlush: async () => {
|
|
@@ -1552,6 +1775,8 @@ function initAutterServer(options) {
|
|
|
1552
1775
|
shutdown: async () => {
|
|
1553
1776
|
const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
|
|
1554
1777
|
hookRequests = false;
|
|
1778
|
+
if (onCrash) process.off("uncaughtExceptionMonitor", onCrash);
|
|
1779
|
+
uninstallRejections?.();
|
|
1555
1780
|
stopMemoryMetrics?.();
|
|
1556
1781
|
active = null;
|
|
1557
1782
|
activeAlwaysOnProvider = null;
|
|
@@ -1578,9 +1803,11 @@ function captureException(error, attributes) {
|
|
|
1578
1803
|
}
|
|
1579
1804
|
markCaptured(error);
|
|
1580
1805
|
noteOperationError(error);
|
|
1581
|
-
|
|
1582
|
-
const
|
|
1583
|
-
|
|
1806
|
+
const split = splitFingerprint(attributes);
|
|
1807
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, activeRedactor.text);
|
|
1808
|
+
notifyException(error, { ...split.attributes, ...fingerprint });
|
|
1809
|
+
const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error", {
|
|
1810
|
+
attributes: { ...activeRedactor(split.attributes), ...fingerprint }
|
|
1584
1811
|
});
|
|
1585
1812
|
recordError(span, error);
|
|
1586
1813
|
span.end();
|
|
@@ -1592,7 +1819,11 @@ function setLoggerOnlyCapture(fn) {
|
|
|
1592
1819
|
function serverActive() {
|
|
1593
1820
|
return active !== null;
|
|
1594
1821
|
}
|
|
1595
|
-
setRequestExceptionCapturer((error) => captureException(error));
|
|
1822
|
+
setRequestExceptionCapturer((error, attributes) => captureException(error, attributes));
|
|
1823
|
+
setRequestSpanAnnotator((attributes) => {
|
|
1824
|
+
const span = trace2.getActiveSpan();
|
|
1825
|
+
if (span?.isRecording()) span.setAttributes(activeRedactor(splitFingerprint(attributes).attributes));
|
|
1826
|
+
});
|
|
1596
1827
|
function withProcessSpan(name, fn, attributes) {
|
|
1597
1828
|
if (active) return active.withProcessSpan(name, fn, attributes);
|
|
1598
1829
|
return runWithSpan(
|
|
@@ -1625,7 +1856,8 @@ function captureMessage(message, severity = "warning", attributes) {
|
|
|
1625
1856
|
span.addEvent("exception", {
|
|
1626
1857
|
"exception.type": "Message",
|
|
1627
1858
|
"exception.message": safeMessage,
|
|
1628
|
-
"autter.severity": severity
|
|
1859
|
+
"autter.severity": severity,
|
|
1860
|
+
...breadcrumbEventAttributes(activeRedactor.text)
|
|
1629
1861
|
});
|
|
1630
1862
|
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1631
1863
|
span.end();
|
|
@@ -1955,6 +2187,42 @@ ${frames.join("\n")}`;
|
|
|
1955
2187
|
} };
|
|
1956
2188
|
}
|
|
1957
2189
|
|
|
2190
|
+
// src/framework-errors.ts
|
|
2191
|
+
function recordBoundaryError(error, req, rule) {
|
|
2192
|
+
try {
|
|
2193
|
+
const state = requestStateOf(req);
|
|
2194
|
+
if (state) noteOperationError(error, false, state);
|
|
2195
|
+
captureAtBoundary(error, state, rule);
|
|
2196
|
+
} catch {
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
function autterErrorHandler(options = {}) {
|
|
2200
|
+
return function autterErrorHandlerMiddleware(error, req, _res, next) {
|
|
2201
|
+
recordBoundaryError(error, req, options.capture);
|
|
2202
|
+
next(error);
|
|
2203
|
+
};
|
|
2204
|
+
}
|
|
2205
|
+
function autterFastifyErrors(fastify, options, done) {
|
|
2206
|
+
const rule = options?.capture;
|
|
2207
|
+
fastify.addHook("onError", ((request, _reply, error, next) => {
|
|
2208
|
+
recordBoundaryError(error, request?.raw, rule);
|
|
2209
|
+
next();
|
|
2210
|
+
}));
|
|
2211
|
+
done?.();
|
|
2212
|
+
}
|
|
2213
|
+
autterFastifyErrors[/* @__PURE__ */ Symbol.for("skip-override")] = true;
|
|
2214
|
+
autterFastifyErrors[/* @__PURE__ */ Symbol.for("fastify.display-name")] = "autter-runtime-errors";
|
|
2215
|
+
function autterKoa(options = {}) {
|
|
2216
|
+
return async function autterKoaMiddleware(ctx, next) {
|
|
2217
|
+
try {
|
|
2218
|
+
await next();
|
|
2219
|
+
} catch (error) {
|
|
2220
|
+
recordBoundaryError(error, ctx?.req, options.capture);
|
|
2221
|
+
throw error;
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
}
|
|
2225
|
+
|
|
1958
2226
|
// src/logging-only.ts
|
|
1959
2227
|
import {
|
|
1960
2228
|
SpanStatusCode as SpanStatusCode3,
|
|
@@ -1982,14 +2250,16 @@ function initAutterLogging(options) {
|
|
|
1982
2250
|
debugLog(`initialising logger-only service=${options.service} endpoint=${endpoint}`);
|
|
1983
2251
|
function captureException2(error, attributes) {
|
|
1984
2252
|
markCaptured(error);
|
|
1985
|
-
const
|
|
2253
|
+
const split = splitFingerprint(attributes);
|
|
2254
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
|
|
2255
|
+
const redacted = redact(split.attributes);
|
|
1986
2256
|
const span = trace3.getActiveSpan();
|
|
1987
2257
|
if (mode === "auto" && span?.isRecording()) {
|
|
1988
2258
|
noteOperationError(error);
|
|
1989
|
-
notifyException(error, redacted);
|
|
2259
|
+
notifyException(error, { ...redacted, ...fingerprint });
|
|
1990
2260
|
span.setAttributes({ "autter.severity": "error", ...redacted, ...structuredErrorAttributes(error) });
|
|
1991
|
-
const declared = errorAttributes(error);
|
|
1992
|
-
if (error
|
|
2261
|
+
const declared = { ...errorAttributes(error), ...fingerprint };
|
|
2262
|
+
if (isError(error)) {
|
|
1993
2263
|
span.addEvent("exception", {
|
|
1994
2264
|
"exception.type": error.name,
|
|
1995
2265
|
"exception.message": error.message,
|
|
@@ -2005,12 +2275,12 @@ function initAutterLogging(options) {
|
|
|
2005
2275
|
}
|
|
2006
2276
|
span.setStatus({
|
|
2007
2277
|
code: SpanStatusCode3.ERROR,
|
|
2008
|
-
message: error
|
|
2278
|
+
message: isError(error) ? error.message : String(error)
|
|
2009
2279
|
});
|
|
2010
2280
|
return;
|
|
2011
2281
|
}
|
|
2012
|
-
notifyException(error, redacted);
|
|
2013
|
-
captureExceptionAsLog(error, redacted);
|
|
2282
|
+
notifyException(error, { ...redacted, ...fingerprint });
|
|
2283
|
+
captureExceptionAsLog(error, { ...redacted, ...fingerprint });
|
|
2014
2284
|
}
|
|
2015
2285
|
const captured = /* @__PURE__ */ new WeakSet();
|
|
2016
2286
|
configureRuntimeLogger({
|
|
@@ -2037,9 +2307,18 @@ function initAutterLogging(options) {
|
|
|
2037
2307
|
}
|
|
2038
2308
|
});
|
|
2039
2309
|
setLoggerOnlyCapture(captureException2);
|
|
2040
|
-
const onCrash = (error) =>
|
|
2041
|
-
|
|
2310
|
+
const onCrash = (error) => {
|
|
2311
|
+
if (recordedAsRejection(error)) return;
|
|
2312
|
+
captureException2(error, { "autter.unhandled": true });
|
|
2313
|
+
};
|
|
2314
|
+
let uninstallRejections = null;
|
|
2315
|
+
if (options.captureGlobalErrors !== false) {
|
|
2042
2316
|
process.on("uncaughtExceptionMonitor", onCrash);
|
|
2317
|
+
if (options.captureUnhandledRejections !== false)
|
|
2318
|
+
uninstallRejections = installUnhandledRejectionCapture(
|
|
2319
|
+
(reason) => captureException2(reason, { "autter.unhandled_rejection": true })
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2043
2322
|
registerFlushTarget("logging", { forceFlush: () => flushRuntimeLogs() });
|
|
2044
2323
|
let autoFlushHandle = options.autoFlush === false ? null : installAutterAutoFlush();
|
|
2045
2324
|
const handle = {
|
|
@@ -2047,6 +2326,8 @@ function initAutterLogging(options) {
|
|
|
2047
2326
|
flush: flushRuntimeLogs,
|
|
2048
2327
|
shutdown: async () => {
|
|
2049
2328
|
process.off("uncaughtExceptionMonitor", onCrash);
|
|
2329
|
+
uninstallRejections?.();
|
|
2330
|
+
uninstallRejections = null;
|
|
2050
2331
|
setLoggerOnlyCapture(null);
|
|
2051
2332
|
autoFlushHandle?.dispose();
|
|
2052
2333
|
autoFlushHandle = null;
|
|
@@ -2152,11 +2433,16 @@ export {
|
|
|
2152
2433
|
INGESTER_VERSION_HEADER,
|
|
2153
2434
|
RuntimeError,
|
|
2154
2435
|
SDK_IDENTITY,
|
|
2436
|
+
addBreadcrumb,
|
|
2437
|
+
autterErrorHandler,
|
|
2155
2438
|
autterErrorResponse,
|
|
2156
2439
|
autterFastify,
|
|
2440
|
+
autterFastifyErrors,
|
|
2441
|
+
autterKoa,
|
|
2157
2442
|
autterRequests,
|
|
2158
2443
|
captureException,
|
|
2159
2444
|
captureMessage,
|
|
2445
|
+
captureRequestError,
|
|
2160
2446
|
consoleSink,
|
|
2161
2447
|
createBrowserRelayFetchHandler,
|
|
2162
2448
|
createBrowserRelayHandler,
|
|
@@ -2191,6 +2477,7 @@ export {
|
|
|
2191
2477
|
startCaughtExceptionSampler,
|
|
2192
2478
|
toClientError,
|
|
2193
2479
|
trackLlmCall,
|
|
2480
|
+
unhandledRejectionsMode,
|
|
2194
2481
|
withLlmCall,
|
|
2195
2482
|
withProcessSpan,
|
|
2196
2483
|
withRuntimeOperation,
|