@autter/runtime-node 1.8.0 → 1.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -1
- package/dist/{chunk-MOZOUEMA.cjs → chunk-3P573RHA.cjs} +44 -3
- package/dist/{chunk-QKY2OQSO.js → chunk-D3PJ7TQ4.js} +550 -36
- package/dist/{chunk-37W7YYXO.cjs → chunk-FV64SIRQ.cjs} +613 -99
- package/dist/{chunk-UC3QH4PC.js → chunk-FYMEBULO.js} +4 -3
- package/dist/{chunk-7H3FCV4P.cjs → chunk-LYXQC4FI.cjs} +14 -13
- package/dist/{chunk-B5ZMOCEM.js → chunk-VIP2EL47.js} +44 -3
- package/dist/cli.cjs +8 -8
- package/dist/cli.js +2 -2
- package/dist/index.cjs +489 -252
- package/dist/index.d.cts +76 -4
- package/dist/index.d.ts +76 -4
- package/dist/index.js +263 -26
- package/dist/{sinks-CIdlmYwy.d.cts → sinks-BfUoE96B.d.cts} +42 -4
- package/dist/{sinks-CIdlmYwy.d.ts → sinks-BfUoE96B.d.ts} +42 -4
- 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 +7 -3
package/dist/index.js
CHANGED
|
@@ -21,10 +21,12 @@ import {
|
|
|
21
21
|
fingerprintEventAttributes,
|
|
22
22
|
fingerprintValues,
|
|
23
23
|
flushRuntimeLogs,
|
|
24
|
+
isCaptured,
|
|
24
25
|
isError,
|
|
25
26
|
isRuntimeErrorLike,
|
|
26
27
|
local,
|
|
27
28
|
makeRedactor,
|
|
29
|
+
markCaptured,
|
|
28
30
|
normalizeRoutePath,
|
|
29
31
|
noteOperationError,
|
|
30
32
|
notifyException,
|
|
@@ -42,14 +44,17 @@ import {
|
|
|
42
44
|
runtimeLogStats,
|
|
43
45
|
runtimeLogger,
|
|
44
46
|
sanitizeBreadcrumbs,
|
|
47
|
+
setHandledCapture,
|
|
45
48
|
shutdownRuntimeLogger,
|
|
46
49
|
splitFingerprint,
|
|
50
|
+
startLoggedErrorCapture,
|
|
51
|
+
stopLoggedErrorCapture,
|
|
47
52
|
toClientError,
|
|
48
53
|
withRuntimeOperation
|
|
49
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-D3PJ7TQ4.js";
|
|
50
55
|
import {
|
|
51
56
|
runDoctor
|
|
52
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-FYMEBULO.js";
|
|
53
58
|
import {
|
|
54
59
|
COMPAT_MANIFEST,
|
|
55
60
|
CountingExporter,
|
|
@@ -71,7 +76,7 @@ import {
|
|
|
71
76
|
sharedSlot,
|
|
72
77
|
telemetryStats,
|
|
73
78
|
unregisterFlushTargets
|
|
74
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-VIP2EL47.js";
|
|
75
80
|
|
|
76
81
|
// src/relay.ts
|
|
77
82
|
var IpWindow = class {
|
|
@@ -96,6 +101,112 @@ function firstForwardedFor(value) {
|
|
|
96
101
|
const raw = Array.isArray(value) ? value[0] : value;
|
|
97
102
|
return raw ? (raw.split(",")[0] ?? "").trim() : "";
|
|
98
103
|
}
|
|
104
|
+
var TRUST_AUTO = { mode: "auto" };
|
|
105
|
+
var warnedTrustProxy = false;
|
|
106
|
+
function resolveTrustProxy(value) {
|
|
107
|
+
if (value === void 0 || value === null || value === "auto") return TRUST_AUTO;
|
|
108
|
+
if (value === true) return { mode: "all" };
|
|
109
|
+
if (value === false || value === 0) return { mode: "never" };
|
|
110
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 1 && value <= 100) {
|
|
111
|
+
return { mode: "hops", hops: value };
|
|
112
|
+
}
|
|
113
|
+
if (typeof value === "string") {
|
|
114
|
+
const v = value.trim().toLowerCase();
|
|
115
|
+
if (v === "" || v === "auto") return TRUST_AUTO;
|
|
116
|
+
if (v === "false" || v === "0" || v === "no" || v === "off") return { mode: "never" };
|
|
117
|
+
if (v === "true" || v === "yes" || v === "on") return { mode: "all" };
|
|
118
|
+
if (/^[1-9][0-9]?$/.test(v)) return { mode: "hops", hops: Number(v) };
|
|
119
|
+
}
|
|
120
|
+
if (!warnedTrustProxy) {
|
|
121
|
+
warnedTrustProxy = true;
|
|
122
|
+
console.warn(
|
|
123
|
+
`[autter-runtime] relay: trustProxy ${JSON.stringify(value)} is not recognised; using "auto".`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
return TRUST_AUTO;
|
|
127
|
+
}
|
|
128
|
+
var IPV4_RE = /^(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
|
|
129
|
+
function normalizeAddress(entry) {
|
|
130
|
+
if (!entry) return null;
|
|
131
|
+
let a = entry.trim();
|
|
132
|
+
if (!a || a.length > 64) return null;
|
|
133
|
+
const bracketed = /^\[([0-9A-Fa-f:.]+)\](?::\d{1,5})?$/.exec(a);
|
|
134
|
+
if (bracketed) a = bracketed[1] ?? "";
|
|
135
|
+
else if (/^[\d.]+:\d{1,5}$/.test(a)) a = a.slice(0, a.lastIndexOf(":"));
|
|
136
|
+
a = a.toLowerCase();
|
|
137
|
+
if (a.startsWith("::ffff:") && IPV4_RE.test(a.slice(7))) a = a.slice(7);
|
|
138
|
+
if (IPV4_RE.test(a)) return a;
|
|
139
|
+
if (/^[0-9a-f:]+(:(\d{1,3}\.){3}\d{1,3})?$/.test(a) && a.includes(":") && a.split(":").length <= 9) {
|
|
140
|
+
return a;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
function isPrivateAddress(a) {
|
|
145
|
+
if (IPV4_RE.test(a)) {
|
|
146
|
+
const [x, y] = a.split(".").map(Number);
|
|
147
|
+
return x === 10 || x === 127 || x === 0 || x === 169 && y === 254 || x === 172 && y >= 16 && y <= 31 || x === 192 && y === 168 || x === 100 && y >= 64 && y <= 127;
|
|
148
|
+
}
|
|
149
|
+
return a === "::1" || a === "::" || /^f[cd]/.test(a) || /^fe[89ab]/.test(a);
|
|
150
|
+
}
|
|
151
|
+
function forwardedEntries(value) {
|
|
152
|
+
const raw = Array.isArray(value) ? value.join(",") : value;
|
|
153
|
+
if (!raw) return [];
|
|
154
|
+
const out = [];
|
|
155
|
+
for (const part of raw.split(",")) {
|
|
156
|
+
const a = normalizeAddress(part);
|
|
157
|
+
if (a) out.push(a);
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
function rightmostPublic(entries) {
|
|
162
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
163
|
+
const a = entries[i];
|
|
164
|
+
if (a && !isPrivateAddress(a)) return a;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
function fromCustom(opts, request) {
|
|
169
|
+
if (!opts.clientIp) return null;
|
|
170
|
+
try {
|
|
171
|
+
const v = opts.clientIp(request);
|
|
172
|
+
return typeof v === "string" && v.trim() ? v.trim().slice(0, 64) : null;
|
|
173
|
+
} catch {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function fetchClientKey(request, opts) {
|
|
178
|
+
const custom = fromCustom(opts, request);
|
|
179
|
+
if (custom) return custom;
|
|
180
|
+
const trust = resolveTrustProxy(opts.trustProxy);
|
|
181
|
+
if (trust.mode === "never") return "shared";
|
|
182
|
+
const header = request.headers.get("x-forwarded-for");
|
|
183
|
+
if (trust.mode === "all") return firstForwardedFor(header) || "shared";
|
|
184
|
+
const entries = forwardedEntries(header);
|
|
185
|
+
if (trust.mode === "hops") {
|
|
186
|
+
return entries[Math.max(0, entries.length - trust.hops)] ?? "shared";
|
|
187
|
+
}
|
|
188
|
+
const cf = request.cf;
|
|
189
|
+
if (cf !== null && typeof cf === "object") {
|
|
190
|
+
const viaCf = normalizeAddress(request.headers.get("cf-connecting-ip"));
|
|
191
|
+
if (viaCf) return viaCf;
|
|
192
|
+
}
|
|
193
|
+
return rightmostPublic(entries) ?? entries[entries.length - 1] ?? "shared";
|
|
194
|
+
}
|
|
195
|
+
function nodeClientKey(req, opts) {
|
|
196
|
+
const custom = fromCustom(opts, req);
|
|
197
|
+
if (custom) return custom;
|
|
198
|
+
const trust = resolveTrustProxy(opts.trustProxy);
|
|
199
|
+
const peer = normalizeAddress(req.socket?.remoteAddress);
|
|
200
|
+
if (trust.mode === "never") return peer ?? "unknown";
|
|
201
|
+
const header = req.headers["x-forwarded-for"];
|
|
202
|
+
if (trust.mode === "all") return firstForwardedFor(header) || peer || "unknown";
|
|
203
|
+
const entries = forwardedEntries(header);
|
|
204
|
+
if (trust.mode === "hops") {
|
|
205
|
+
return entries[Math.max(0, entries.length - trust.hops)] ?? peer ?? "unknown";
|
|
206
|
+
}
|
|
207
|
+
if (peer && !isPrivateAddress(peer)) return peer;
|
|
208
|
+
return rightmostPublic(entries) ?? peer ?? entries[entries.length - 1] ?? "unknown";
|
|
209
|
+
}
|
|
99
210
|
var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
|
|
100
211
|
var DEFAULT_MAX_BODY = 64 * 1024;
|
|
101
212
|
var MAX_EVENTS = 50;
|
|
@@ -290,20 +401,38 @@ function sanitizeBrowserPayload(raw, redactor = defaultRelayRedactor) {
|
|
|
290
401
|
events
|
|
291
402
|
};
|
|
292
403
|
}
|
|
404
|
+
var FORWARD_ATTEMPTS = 3;
|
|
405
|
+
var FORWARD_MAX_WAIT_MS = 1e4;
|
|
293
406
|
function forward(payload, opts) {
|
|
294
407
|
const endpoint = (opts.endpoint ?? DEFAULT_ENDPOINT).replace(/\/$/, "");
|
|
295
408
|
const url = `${endpoint}/v1/browser`;
|
|
296
409
|
const compat = opts.compatCheck !== false;
|
|
297
410
|
if (compat) ensureCompatConfigured(endpoint);
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
411
|
+
const body = JSON.stringify(payload);
|
|
412
|
+
const attempt = async (n) => {
|
|
413
|
+
let response;
|
|
414
|
+
try {
|
|
415
|
+
response = await fetch(url, {
|
|
416
|
+
method: "POST",
|
|
417
|
+
headers: {
|
|
418
|
+
"content-type": "application/json",
|
|
419
|
+
authorization: `Bearer ${opts.apiKey}`
|
|
420
|
+
},
|
|
421
|
+
body,
|
|
422
|
+
signal: AbortSignal.timeout(1e4)
|
|
423
|
+
});
|
|
424
|
+
} catch (err) {
|
|
425
|
+
if (n >= FORWARD_ATTEMPTS) throw err;
|
|
426
|
+
await sleep(1e3 * 2 ** (n - 1));
|
|
427
|
+
return attempt(n + 1);
|
|
428
|
+
}
|
|
429
|
+
if ((response.status === 429 || response.status >= 500) && n < FORWARD_ATTEMPTS) {
|
|
430
|
+
const seconds = Number(response.headers.get("retry-after"));
|
|
431
|
+
await response.body?.cancel().catch(() => {
|
|
432
|
+
});
|
|
433
|
+
await sleep(seconds > 0 ? Math.min(seconds * 1e3, FORWARD_MAX_WAIT_MS) : 1e3 * 2 ** (n - 1));
|
|
434
|
+
return attempt(n + 1);
|
|
435
|
+
}
|
|
307
436
|
if (!compat) return;
|
|
308
437
|
const types = new Set(
|
|
309
438
|
(payload.events ?? []).map((e) => String(e.type))
|
|
@@ -314,12 +443,16 @@ function forward(payload, opts) {
|
|
|
314
443
|
noteCompatFeature("browser_trace_propagation");
|
|
315
444
|
}
|
|
316
445
|
noteRelayRejection(types, response.status, response.headers);
|
|
317
|
-
}
|
|
446
|
+
};
|
|
447
|
+
void attempt(1).catch((err) => {
|
|
318
448
|
(opts.onError ?? ((e) => console.warn("autter relay forward failed:", e)))(
|
|
319
449
|
err
|
|
320
450
|
);
|
|
321
451
|
});
|
|
322
452
|
}
|
|
453
|
+
function sleep(ms) {
|
|
454
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
455
|
+
}
|
|
323
456
|
function createBrowserRelayFetchHandler(opts) {
|
|
324
457
|
const maxBody = opts.maxBodyBytes ?? DEFAULT_MAX_BODY;
|
|
325
458
|
const redactor = opts.redact === void 0 || opts.redact === true ? defaultRelayRedactor : makeRedactor(opts.redact);
|
|
@@ -329,7 +462,7 @@ function createBrowserRelayFetchHandler(opts) {
|
|
|
329
462
|
return new Response(null, { status: 405 });
|
|
330
463
|
}
|
|
331
464
|
if (limiter) {
|
|
332
|
-
const ip =
|
|
465
|
+
const ip = fetchClientKey(request, opts);
|
|
333
466
|
if (!limiter.allow(ip)) {
|
|
334
467
|
return new Response(JSON.stringify({ error: "rate limit exceeded" }), {
|
|
335
468
|
status: 429
|
|
@@ -395,7 +528,7 @@ function createBrowserRelayHandler(opts) {
|
|
|
395
528
|
return;
|
|
396
529
|
}
|
|
397
530
|
if (limiter) {
|
|
398
|
-
const ip =
|
|
531
|
+
const ip = nodeClientKey(req, opts);
|
|
399
532
|
if (!limiter.allow(ip)) {
|
|
400
533
|
respond(res, 429, { error: "rate limit exceeded" });
|
|
401
534
|
return;
|
|
@@ -746,7 +879,6 @@ import { context, trace } from "@opentelemetry/api";
|
|
|
746
879
|
import { getRPCMetadata, RPCType } from "@opentelemetry/core";
|
|
747
880
|
var REQUEST_STATE = /* @__PURE__ */ Symbol.for("autter.runtime.request");
|
|
748
881
|
var BOUND = /* @__PURE__ */ Symbol.for("autter.runtime.bound");
|
|
749
|
-
var CAPTURED = sharedSlot("requests", () => ({ captured: /* @__PURE__ */ new WeakSet() })).captured;
|
|
750
882
|
var capturer = () => {
|
|
751
883
|
};
|
|
752
884
|
function setRequestExceptionCapturer(fn) {
|
|
@@ -757,8 +889,14 @@ var annotator = () => {
|
|
|
757
889
|
function setRequestSpanAnnotator(fn) {
|
|
758
890
|
annotator = fn;
|
|
759
891
|
}
|
|
760
|
-
function
|
|
761
|
-
if (
|
|
892
|
+
function decide(rule, error, fallback) {
|
|
893
|
+
if (rule === void 0) return fallback;
|
|
894
|
+
if (typeof rule !== "function") return rule;
|
|
895
|
+
try {
|
|
896
|
+
return rule(error) === true;
|
|
897
|
+
} catch {
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
762
900
|
}
|
|
763
901
|
function expressRouteOf(req) {
|
|
764
902
|
const props = req;
|
|
@@ -789,7 +927,7 @@ function captureAtBoundary(error, state, rule = void 0, attributes) {
|
|
|
789
927
|
wanted = false;
|
|
790
928
|
}
|
|
791
929
|
if (!wanted) return;
|
|
792
|
-
if (
|
|
930
|
+
if (isCaptured(error)) return;
|
|
793
931
|
markCaptured(error);
|
|
794
932
|
try {
|
|
795
933
|
const run = () => onRequestSpan(() => capturer(error, attributes));
|
|
@@ -803,7 +941,7 @@ function requestStateOf(req) {
|
|
|
803
941
|
return req[REQUEST_STATE];
|
|
804
942
|
}
|
|
805
943
|
function captureRequestError(error, attributes) {
|
|
806
|
-
if (
|
|
944
|
+
if (isCaptured(error)) {
|
|
807
945
|
if (!attributes) return;
|
|
808
946
|
try {
|
|
809
947
|
onRequestSpan(() => annotator(attributes));
|
|
@@ -895,7 +1033,8 @@ function finishRequest(state, input) {
|
|
|
895
1033
|
...state.error !== void 0 ? { error: state.error } : {},
|
|
896
1034
|
...state.errorThrown ? { thrown: true } : {},
|
|
897
1035
|
status: input.status,
|
|
898
|
-
aborted: input.aborted
|
|
1036
|
+
aborted: input.aborted,
|
|
1037
|
+
...state.degraded ? { degraded: true } : {}
|
|
899
1038
|
});
|
|
900
1039
|
const span = trace.getActiveSpan();
|
|
901
1040
|
if (span?.isRecording()) span.setAttribute("autter.operation.outcome", state.outcome);
|
|
@@ -1120,9 +1259,15 @@ function withRuntimeRequest(handler, options = {}) {
|
|
|
1120
1259
|
try {
|
|
1121
1260
|
return await handler(request, ...rest);
|
|
1122
1261
|
} catch (error) {
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1262
|
+
if (decide(options.capture, error, true)) {
|
|
1263
|
+
noteOperationError(error, true, state);
|
|
1264
|
+
captureAtBoundary(error, state, true);
|
|
1265
|
+
}
|
|
1266
|
+
if (!decide(options.errorResponse, error, false)) throw error;
|
|
1267
|
+
return new Response(JSON.stringify(toClientError(error, requestId)), {
|
|
1268
|
+
status: responseStatusOf(error),
|
|
1269
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
1270
|
+
});
|
|
1126
1271
|
}
|
|
1127
1272
|
});
|
|
1128
1273
|
status = response.status;
|
|
@@ -1320,6 +1465,8 @@ var S = sharedSlot("server", () => ({
|
|
|
1320
1465
|
alwaysOnProvider: null,
|
|
1321
1466
|
redactor: makeRedactor(true),
|
|
1322
1467
|
loggerOnlyCapture: null,
|
|
1468
|
+
handled: null,
|
|
1469
|
+
loggerOnlyHandled: null,
|
|
1323
1470
|
hookRequests: false
|
|
1324
1471
|
}));
|
|
1325
1472
|
var LLM_MARKER_ATTRIBUTES = [
|
|
@@ -1896,6 +2043,37 @@ function initAutterServer(options) {
|
|
|
1896
2043
|
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1897
2044
|
if (!reuseActive) span.end();
|
|
1898
2045
|
}
|
|
2046
|
+
function captureHandled(error, attributes, severity) {
|
|
2047
|
+
const isErr = isError(error);
|
|
2048
|
+
if (!errorFilter({
|
|
2049
|
+
type: isErr ? String(error.name ?? "") : "Error",
|
|
2050
|
+
message: isErr ? String(error.message ?? "") : String(error),
|
|
2051
|
+
severity
|
|
2052
|
+
})) return;
|
|
2053
|
+
if (severity === "error") errorTraceBuffer?.retainActiveTrace();
|
|
2054
|
+
telemetryStats.markCaptured();
|
|
2055
|
+
const message = redactor.text(isErr ? error.message : String(error));
|
|
2056
|
+
const stack = isErr && error.stack ? error.stack : new Error().stack?.split("\n").filter((line, i) => i === 0 || !/captureHandled|report(?:Degraded|LoggedError)/.test(line)).join("\n");
|
|
2057
|
+
const split = splitFingerprint(attributes);
|
|
2058
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, redactor.text);
|
|
2059
|
+
const redacted = redactor(split.attributes);
|
|
2060
|
+
const activeSpan = trace2.getActiveSpan();
|
|
2061
|
+
const reuseActive = activeSpan?.isRecording() === true;
|
|
2062
|
+
const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
|
|
2063
|
+
markCaptured(error);
|
|
2064
|
+
notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
|
|
2065
|
+
span.addEvent("exception", {
|
|
2066
|
+
...redacted,
|
|
2067
|
+
"exception.type": isErr ? error.name : "Error",
|
|
2068
|
+
"exception.message": message,
|
|
2069
|
+
...stack ? { "exception.stacktrace": redactor.text(stack) } : {},
|
|
2070
|
+
"autter.severity": severity,
|
|
2071
|
+
...errorAttributes(error),
|
|
2072
|
+
...fingerprint,
|
|
2073
|
+
...breadcrumbEventAttributes(redactor.text, error)
|
|
2074
|
+
});
|
|
2075
|
+
if (!reuseActive) span.end();
|
|
2076
|
+
}
|
|
1899
2077
|
configureRuntimeLogger({
|
|
1900
2078
|
endpoint,
|
|
1901
2079
|
apiKey: options.apiKey,
|
|
@@ -1909,6 +2087,8 @@ function initAutterServer(options) {
|
|
|
1909
2087
|
reportOutcome: reportOutcome2
|
|
1910
2088
|
});
|
|
1911
2089
|
S.hookRequests = options.logging?.requests === true;
|
|
2090
|
+
S.handled = captureHandled;
|
|
2091
|
+
startLoggedErrorCapture(options.captureLoggedErrors);
|
|
1912
2092
|
let onCrash = null;
|
|
1913
2093
|
let uninstallRejections = null;
|
|
1914
2094
|
if (options.captureGlobalErrors !== false) {
|
|
@@ -1955,6 +2135,7 @@ function initAutterServer(options) {
|
|
|
1955
2135
|
withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
|
|
1956
2136
|
trackLlmCall: (call) => recordLlmCall(llmTracer, call),
|
|
1957
2137
|
shutdown: async () => {
|
|
2138
|
+
if (S.active === server) stopLoggedErrorCapture();
|
|
1958
2139
|
const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
|
|
1959
2140
|
if (onCrash) process.off("uncaughtExceptionMonitor", onCrash);
|
|
1960
2141
|
uninstallRejections?.();
|
|
@@ -1962,6 +2143,7 @@ function initAutterServer(options) {
|
|
|
1962
2143
|
if (S.active === server) {
|
|
1963
2144
|
S.active = null;
|
|
1964
2145
|
S.alwaysOnProvider = null;
|
|
2146
|
+
S.handled = null;
|
|
1965
2147
|
S.hookRequests = false;
|
|
1966
2148
|
S.redactor = makeRedactor(true);
|
|
1967
2149
|
}
|
|
@@ -1997,13 +2179,39 @@ function captureException(error, attributes) {
|
|
|
1997
2179
|
recordError(span, error);
|
|
1998
2180
|
span.end();
|
|
1999
2181
|
}
|
|
2000
|
-
function setLoggerOnlyCapture(fn) {
|
|
2182
|
+
function setLoggerOnlyCapture(fn, handled = null) {
|
|
2001
2183
|
S.loggerOnlyCapture = fn;
|
|
2184
|
+
S.loggerOnlyHandled = handled;
|
|
2185
|
+
}
|
|
2186
|
+
function captureHandledError(error, attributes, severity) {
|
|
2187
|
+
if (S.active) {
|
|
2188
|
+
S.handled?.(error, attributes, severity);
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
if (S.loggerOnlyHandled) {
|
|
2192
|
+
S.loggerOnlyHandled(error, attributes, severity);
|
|
2193
|
+
return;
|
|
2194
|
+
}
|
|
2195
|
+
markCaptured(error);
|
|
2196
|
+
const split = splitFingerprint(attributes);
|
|
2197
|
+
notifyException(error, { ...split.attributes, "autter.severity": severity });
|
|
2198
|
+
const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error");
|
|
2199
|
+
span.addEvent("exception", {
|
|
2200
|
+
...S.redactor(split.attributes),
|
|
2201
|
+
"exception.type": isError(error) ? error.name : "Error",
|
|
2202
|
+
"exception.message": S.redactor.text(isError(error) ? error.message : String(error)),
|
|
2203
|
+
...isError(error) && error.stack ? { "exception.stacktrace": S.redactor.text(error.stack) } : {},
|
|
2204
|
+
"autter.severity": severity,
|
|
2205
|
+
...errorAttributes(error),
|
|
2206
|
+
...fingerprintEventAttributes(split.fingerprint, S.redactor.text)
|
|
2207
|
+
});
|
|
2208
|
+
span.end();
|
|
2002
2209
|
}
|
|
2003
2210
|
function serverActive() {
|
|
2004
2211
|
return S.active !== null;
|
|
2005
2212
|
}
|
|
2006
2213
|
setRequestExceptionCapturer((error, attributes) => captureException(error, attributes));
|
|
2214
|
+
setHandledCapture(captureHandledError);
|
|
2007
2215
|
setRequestSpanAnnotator((attributes) => {
|
|
2008
2216
|
const span = trace2.getActiveSpan();
|
|
2009
2217
|
if (span?.isRecording()) span.setAttributes(S.redactor(splitFingerprint(attributes).attributes));
|
|
@@ -2474,6 +2682,33 @@ function initAutterLogging(options) {
|
|
|
2474
2682
|
notifyException(error, { ...redacted, ...fingerprint });
|
|
2475
2683
|
captureExceptionAsLog(error, { ...redacted, ...fingerprint });
|
|
2476
2684
|
}
|
|
2685
|
+
function captureHandled(error, attributes, severity) {
|
|
2686
|
+
markCaptured(error);
|
|
2687
|
+
const isErrValue = isError(error);
|
|
2688
|
+
if (!errorFilter({
|
|
2689
|
+
type: isErrValue ? String(error.name ?? "") : "Error",
|
|
2690
|
+
message: isErrValue ? String(error.message ?? "") : String(error),
|
|
2691
|
+
severity
|
|
2692
|
+
})) return;
|
|
2693
|
+
const split = splitFingerprint(attributes);
|
|
2694
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
|
|
2695
|
+
const redacted = redact(split.attributes);
|
|
2696
|
+
notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
|
|
2697
|
+
const span = trace3.getActiveSpan();
|
|
2698
|
+
if (mode === "auto" && span?.isRecording()) {
|
|
2699
|
+
span.addEvent("exception", {
|
|
2700
|
+
...redacted,
|
|
2701
|
+
"exception.type": isErrValue ? error.name : "Error",
|
|
2702
|
+
"exception.message": redact.text(isErrValue ? error.message : String(error)),
|
|
2703
|
+
...isErrValue && error.stack ? { "exception.stacktrace": redact.text(error.stack) } : {},
|
|
2704
|
+
"autter.severity": severity,
|
|
2705
|
+
...errorAttributes(error),
|
|
2706
|
+
...fingerprint
|
|
2707
|
+
});
|
|
2708
|
+
return;
|
|
2709
|
+
}
|
|
2710
|
+
captureExceptionAsLog(error, { ...redacted, ...fingerprint }, severity);
|
|
2711
|
+
}
|
|
2477
2712
|
const captured = /* @__PURE__ */ new WeakSet();
|
|
2478
2713
|
configureRuntimeLogger({
|
|
2479
2714
|
endpoint,
|
|
@@ -2498,7 +2733,8 @@ function initAutterLogging(options) {
|
|
|
2498
2733
|
captureException2(error);
|
|
2499
2734
|
}
|
|
2500
2735
|
});
|
|
2501
|
-
setLoggerOnlyCapture(captureException2);
|
|
2736
|
+
setLoggerOnlyCapture(captureException2, captureHandled);
|
|
2737
|
+
startLoggedErrorCapture(options.captureLoggedErrors);
|
|
2502
2738
|
const onCrash = (error) => {
|
|
2503
2739
|
if (recordedAsRejection(error)) return;
|
|
2504
2740
|
captureException2(error, { "autter.unhandled": true });
|
|
@@ -2517,6 +2753,7 @@ function initAutterLogging(options) {
|
|
|
2517
2753
|
captureException: captureException2,
|
|
2518
2754
|
flush: flushRuntimeLogs,
|
|
2519
2755
|
shutdown: async () => {
|
|
2756
|
+
if (L.active === handle) stopLoggedErrorCapture();
|
|
2520
2757
|
process.off("uncaughtExceptionMonitor", onCrash);
|
|
2521
2758
|
uninstallRejections?.();
|
|
2522
2759
|
uninstallRejections = null;
|
|
@@ -166,10 +166,19 @@ declare function isRuntimeErrorLike(err: unknown): err is RuntimeErrorLike;
|
|
|
166
166
|
interface ErrorAttributeOptions {
|
|
167
167
|
/** Also emit `exception.cause.N.*` for the cause chain (default true). */
|
|
168
168
|
causes?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* With `causes`, also emit what only an occurrence record needs (default
|
|
171
|
+
* true): each cause's stack (`exception.cause.N.stacktrace`, RF-06: a
|
|
172
|
+
* wrapped error groups with its root cause) and the debug ids of the
|
|
173
|
+
* bundles in the stacks (`autter.debug_ids`, RF-05: server source maps).
|
|
174
|
+
* Request and operation summaries pass false.
|
|
175
|
+
*/
|
|
176
|
+
stacks?: boolean;
|
|
169
177
|
}
|
|
170
178
|
/**
|
|
171
|
-
* Wire attributes for an error: `autter.error.code|why|fix|link|status|expected
|
|
172
|
-
*
|
|
179
|
+
* Wire attributes for an error: `autter.error.code|why|fix|link|status|expected`,
|
|
180
|
+
* `exception.cause.N.type|message|code|stacktrace` (N = 1..5) and
|
|
181
|
+
* `autter.debug_ids` (see ErrorAttributeOptions.stacks). Never includes
|
|
173
182
|
* `internal` — see `errorInternal`.
|
|
174
183
|
*/
|
|
175
184
|
declare function errorAttributes(err: unknown, options?: ErrorAttributeOptions): Record<string, string | number | boolean>;
|
|
@@ -250,6 +259,33 @@ interface BreadcrumbInput {
|
|
|
250
259
|
at?: string | number | Date;
|
|
251
260
|
}
|
|
252
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Handled errors (RF-01, RF-02): the building blocks every server SDK shares
|
|
264
|
+
* for errors the app caught instead of letting them crash.
|
|
265
|
+
*
|
|
266
|
+
* - Caught errors: an `Error` the app passes to `console.error`, a logger
|
|
267
|
+
* (pino, winston, bunyan) or `runtimeLogger.error` is reported as an
|
|
268
|
+
* occurrence at `error` severity, marked `autter.handled = true`,
|
|
269
|
+
* `autter.handling = "caught"`, with the log line as context
|
|
270
|
+
* (`autter.log.source`, `autter.log.message`).
|
|
271
|
+
* - Deliberate fallbacks: `runtimeContext.degraded(err, reason)` reports the
|
|
272
|
+
* error at `warning` severity (never an incident), marked
|
|
273
|
+
* `autter.handling = "deliberate"` with `autter.degraded.reason`.
|
|
274
|
+
*
|
|
275
|
+
* A burst limit keeps a looping error from flooding: per kind (error type
|
|
276
|
+
* and throw site) ten at once, then one every ten seconds. What it leaves
|
|
277
|
+
* out is counted, and the count rides on the next record of that kind as
|
|
278
|
+
* `autter.caught.dropped`, which the ingester adds to `runtime_ingest_drops`
|
|
279
|
+
* (reason `caught_limited`).
|
|
280
|
+
*/
|
|
281
|
+
type LoggedErrorSource = "console" | "pino" | "winston" | "bunyan" | "runtimeLogger";
|
|
282
|
+
/**
|
|
283
|
+
* `captureLoggedErrors`: `true` (default) reports errors logged through
|
|
284
|
+
* every source the SDK supports, `false` none; an object turns single
|
|
285
|
+
* sources off, e.g. `{ console: false }`.
|
|
286
|
+
*/
|
|
287
|
+
type CaptureLoggedErrorsOption = boolean | Partial<Record<LoggedErrorSource, boolean>>;
|
|
288
|
+
|
|
253
289
|
/**
|
|
254
290
|
* Client-side volume control shared by the server SDKs (runtime-node,
|
|
255
291
|
* runtime-edge; 1.8.0 / 1.2.0, EI-38): `ignoreErrors` and `sampleRate`.
|
|
@@ -319,7 +355,9 @@ interface OtlpSinkOptions {
|
|
|
319
355
|
* OTLP/HTTP JSON export to `${endpoint}/v1/logs`. Bounded exactly like
|
|
320
356
|
* 1.4.0: 1000 records / 4 MiB buffered, 256 KiB per record, batches of 50
|
|
321
357
|
* (≤ 512 KiB), 3 attempts within a 10 s deadline, exponential back-off
|
|
322
|
-
* on failure, undelivered records reported at shutdown.
|
|
358
|
+
* on failure, undelivered records reported at shutdown. A 429 or 5xx is
|
|
359
|
+
* retried after its Retry-After (else 500 ms, 1 s); any other 4xx is not
|
|
360
|
+
* retried within the flush.
|
|
323
361
|
*/
|
|
324
362
|
declare function otlpSink(options?: OtlpSinkOptions): RuntimeSink;
|
|
325
363
|
interface ConsoleSinkOptions {
|
|
@@ -358,4 +396,4 @@ interface MemorySink extends RuntimeSink {
|
|
|
358
396
|
/** Keeps every record in memory — for tests (`@autter/runtime-node/testing`). */
|
|
359
397
|
declare function memorySink(): MemorySink;
|
|
360
398
|
|
|
361
|
-
export {
|
|
399
|
+
export { defineRuntimeErrors as A, type BreadcrumbInput as B, type CapturedException as C, errorAttributes as D, fileSink as E, type FileSinkOptions as F, isRuntimeErrorLike as G, otlpSink as H, type IgnoreErrorPattern as I, redactAttributes as J, redactText as K, type LoggedErrorSource as L, type MemorySink as M, toClientError as N, type OtlpSinkOptions as O, type RuntimeEvent as R, type RuntimeOutcome as a, type RedactOptions as b, type Redactor$1 as c, type RuntimeLogLevel as d, type RuntimeOperationKind as e, type RuntimeSink as f, type RuntimeLogContext as g, type RuntimeCarrier as h, type CaptureLoggedErrorsOption as i, type Breadcrumb as j, type BreadcrumbLevel as k, type BreadcrumbType as l, memorySink as m, CODE_PATTERN as n, type ClientErrorBody as o, type ConsoleSinkOptions as p, RuntimeError as q, type RuntimeErrorCatalog as r, type RuntimeErrorDefinition as s, type RuntimeErrorExtras as t, type RuntimeErrorFactory as u, type RuntimeErrorLike as v, type RuntimeErrorOptions as w, type RuntimeLogContextValue as x, type RuntimeSinkContext as y, consoleSink as z };
|
|
@@ -166,10 +166,19 @@ declare function isRuntimeErrorLike(err: unknown): err is RuntimeErrorLike;
|
|
|
166
166
|
interface ErrorAttributeOptions {
|
|
167
167
|
/** Also emit `exception.cause.N.*` for the cause chain (default true). */
|
|
168
168
|
causes?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* With `causes`, also emit what only an occurrence record needs (default
|
|
171
|
+
* true): each cause's stack (`exception.cause.N.stacktrace`, RF-06: a
|
|
172
|
+
* wrapped error groups with its root cause) and the debug ids of the
|
|
173
|
+
* bundles in the stacks (`autter.debug_ids`, RF-05: server source maps).
|
|
174
|
+
* Request and operation summaries pass false.
|
|
175
|
+
*/
|
|
176
|
+
stacks?: boolean;
|
|
169
177
|
}
|
|
170
178
|
/**
|
|
171
|
-
* Wire attributes for an error: `autter.error.code|why|fix|link|status|expected
|
|
172
|
-
*
|
|
179
|
+
* Wire attributes for an error: `autter.error.code|why|fix|link|status|expected`,
|
|
180
|
+
* `exception.cause.N.type|message|code|stacktrace` (N = 1..5) and
|
|
181
|
+
* `autter.debug_ids` (see ErrorAttributeOptions.stacks). Never includes
|
|
173
182
|
* `internal` — see `errorInternal`.
|
|
174
183
|
*/
|
|
175
184
|
declare function errorAttributes(err: unknown, options?: ErrorAttributeOptions): Record<string, string | number | boolean>;
|
|
@@ -250,6 +259,33 @@ interface BreadcrumbInput {
|
|
|
250
259
|
at?: string | number | Date;
|
|
251
260
|
}
|
|
252
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Handled errors (RF-01, RF-02): the building blocks every server SDK shares
|
|
264
|
+
* for errors the app caught instead of letting them crash.
|
|
265
|
+
*
|
|
266
|
+
* - Caught errors: an `Error` the app passes to `console.error`, a logger
|
|
267
|
+
* (pino, winston, bunyan) or `runtimeLogger.error` is reported as an
|
|
268
|
+
* occurrence at `error` severity, marked `autter.handled = true`,
|
|
269
|
+
* `autter.handling = "caught"`, with the log line as context
|
|
270
|
+
* (`autter.log.source`, `autter.log.message`).
|
|
271
|
+
* - Deliberate fallbacks: `runtimeContext.degraded(err, reason)` reports the
|
|
272
|
+
* error at `warning` severity (never an incident), marked
|
|
273
|
+
* `autter.handling = "deliberate"` with `autter.degraded.reason`.
|
|
274
|
+
*
|
|
275
|
+
* A burst limit keeps a looping error from flooding: per kind (error type
|
|
276
|
+
* and throw site) ten at once, then one every ten seconds. What it leaves
|
|
277
|
+
* out is counted, and the count rides on the next record of that kind as
|
|
278
|
+
* `autter.caught.dropped`, which the ingester adds to `runtime_ingest_drops`
|
|
279
|
+
* (reason `caught_limited`).
|
|
280
|
+
*/
|
|
281
|
+
type LoggedErrorSource = "console" | "pino" | "winston" | "bunyan" | "runtimeLogger";
|
|
282
|
+
/**
|
|
283
|
+
* `captureLoggedErrors`: `true` (default) reports errors logged through
|
|
284
|
+
* every source the SDK supports, `false` none; an object turns single
|
|
285
|
+
* sources off, e.g. `{ console: false }`.
|
|
286
|
+
*/
|
|
287
|
+
type CaptureLoggedErrorsOption = boolean | Partial<Record<LoggedErrorSource, boolean>>;
|
|
288
|
+
|
|
253
289
|
/**
|
|
254
290
|
* Client-side volume control shared by the server SDKs (runtime-node,
|
|
255
291
|
* runtime-edge; 1.8.0 / 1.2.0, EI-38): `ignoreErrors` and `sampleRate`.
|
|
@@ -319,7 +355,9 @@ interface OtlpSinkOptions {
|
|
|
319
355
|
* OTLP/HTTP JSON export to `${endpoint}/v1/logs`. Bounded exactly like
|
|
320
356
|
* 1.4.0: 1000 records / 4 MiB buffered, 256 KiB per record, batches of 50
|
|
321
357
|
* (≤ 512 KiB), 3 attempts within a 10 s deadline, exponential back-off
|
|
322
|
-
* on failure, undelivered records reported at shutdown.
|
|
358
|
+
* on failure, undelivered records reported at shutdown. A 429 or 5xx is
|
|
359
|
+
* retried after its Retry-After (else 500 ms, 1 s); any other 4xx is not
|
|
360
|
+
* retried within the flush.
|
|
323
361
|
*/
|
|
324
362
|
declare function otlpSink(options?: OtlpSinkOptions): RuntimeSink;
|
|
325
363
|
interface ConsoleSinkOptions {
|
|
@@ -358,4 +396,4 @@ interface MemorySink extends RuntimeSink {
|
|
|
358
396
|
/** Keeps every record in memory — for tests (`@autter/runtime-node/testing`). */
|
|
359
397
|
declare function memorySink(): MemorySink;
|
|
360
398
|
|
|
361
|
-
export {
|
|
399
|
+
export { defineRuntimeErrors as A, type BreadcrumbInput as B, type CapturedException as C, errorAttributes as D, fileSink as E, type FileSinkOptions as F, isRuntimeErrorLike as G, otlpSink as H, type IgnoreErrorPattern as I, redactAttributes as J, redactText as K, type LoggedErrorSource as L, type MemorySink as M, toClientError as N, type OtlpSinkOptions as O, type RuntimeEvent as R, type RuntimeOutcome as a, type RedactOptions as b, type Redactor$1 as c, type RuntimeLogLevel as d, type RuntimeOperationKind as e, type RuntimeSink as f, type RuntimeLogContext as g, type RuntimeCarrier as h, type CaptureLoggedErrorsOption as i, type Breadcrumb as j, type BreadcrumbLevel as k, type BreadcrumbType as l, memorySink as m, CODE_PATTERN as n, type ClientErrorBody as o, type ConsoleSinkOptions as p, RuntimeError as q, type RuntimeErrorCatalog as r, type RuntimeErrorDefinition as s, type RuntimeErrorExtras as t, type RuntimeErrorFactory as u, type RuntimeErrorLike as v, type RuntimeErrorOptions as w, type RuntimeLogContextValue as x, type RuntimeSinkContext as y, consoleSink as z };
|
package/dist/testing.cjs
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
6
|
-
require('./chunk-
|
|
5
|
+
var _chunkFV64SIRQcjs = require('./chunk-FV64SIRQ.cjs');
|
|
6
|
+
require('./chunk-3P573RHA.cjs');
|
|
7
7
|
|
|
8
8
|
// src/testing.ts
|
|
9
9
|
function captureRuntime() {
|
|
10
|
-
const sink =
|
|
11
|
-
const remove =
|
|
10
|
+
const sink = _chunkFV64SIRQcjs.memorySink.call(void 0, );
|
|
11
|
+
const remove = _chunkFV64SIRQcjs.addRuntimeSink.call(void 0, sink);
|
|
12
12
|
return {
|
|
13
13
|
get events() {
|
|
14
14
|
return [...sink.events];
|
|
15
15
|
},
|
|
16
16
|
get operations() {
|
|
17
|
-
return sink.events.filter(
|
|
17
|
+
return sink.events.filter(_chunkFV64SIRQcjs.isSummary);
|
|
18
18
|
},
|
|
19
19
|
get logs() {
|
|
20
|
-
return sink.events.filter((event) => !
|
|
20
|
+
return sink.events.filter((event) => !_chunkFV64SIRQcjs.isSummary.call(void 0, event));
|
|
21
21
|
},
|
|
22
22
|
get exceptions() {
|
|
23
23
|
return [...sink.exceptions];
|
|
@@ -98,7 +98,7 @@ var OperationExpectation = class {
|
|
|
98
98
|
}
|
|
99
99
|
};
|
|
100
100
|
function expectOperation(source, name) {
|
|
101
|
-
const events = Array.isArray(source) ? source.filter(
|
|
101
|
+
const events = Array.isArray(source) ? source.filter(_chunkFV64SIRQcjs.isSummary) : source.operations;
|
|
102
102
|
const matches = events.filter((event2) => {
|
|
103
103
|
const actual = String(_nullishCoalesce(event2.attributes["autter.operation.name"], () => ( "")));
|
|
104
104
|
return typeof name === "string" ? actual === name : name.test(actual);
|
|
@@ -118,4 +118,4 @@ function expectOperation(source, name) {
|
|
|
118
118
|
|
|
119
119
|
|
|
120
120
|
|
|
121
|
-
exports.captureRuntime = captureRuntime; exports.expectOperation = expectOperation; exports.memorySink =
|
|
121
|
+
exports.captureRuntime = captureRuntime; exports.expectOperation = expectOperation; exports.memorySink = _chunkFV64SIRQcjs.memorySink;
|
package/dist/testing.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { R as RuntimeEvent, a as RuntimeOutcome, C as CapturedException } from './sinks-
|
|
2
|
-
export { M as MemorySink, m as memorySink } from './sinks-
|
|
1
|
+
import { R as RuntimeEvent, a as RuntimeOutcome, C as CapturedException } from './sinks-BfUoE96B.cjs';
|
|
2
|
+
export { M as MemorySink, m as memorySink } from './sinks-BfUoE96B.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Test helpers — `@autter/runtime-node/testing`. No ingester, no network:
|