@autter/runtime-node 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -1
- package/dist/{chunk-HKVFURYT.cjs → chunk-AN45KLWD.cjs} +11 -11
- package/dist/{chunk-GYTQLITW.cjs → chunk-BHYHORUD.cjs} +58 -3
- package/dist/{chunk-KA4AOZX2.js → chunk-HHKQ3UCH.js} +571 -31
- package/dist/{chunk-QUC6OUE7.js → chunk-JSGGUWRD.js} +1 -1
- package/dist/{chunk-RV4MJNM2.cjs → chunk-YWTIKZSI.cjs} +631 -91
- package/dist/{chunk-LG52CW3V.js → chunk-ZKM76SDP.js} +58 -3
- package/dist/cli.cjs +8 -8
- package/dist/cli.js +2 -2
- package/dist/index.cjs +447 -210
- package/dist/index.d.cts +98 -3
- package/dist/index.d.ts +98 -3
- package/dist/index.js +253 -16
- package/dist/{sinks-CPGDyfU8.d.ts → sinks-CLtQFPnn.d.cts} +59 -3
- package/dist/{sinks-CPGDyfU8.d.cts → sinks-CLtQFPnn.d.ts} +59 -3
- 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
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
compileIgnore,
|
|
9
9
|
configureRuntimeLogger,
|
|
10
10
|
consoleSink,
|
|
11
|
+
createErrorFilter,
|
|
11
12
|
createRequestState,
|
|
12
13
|
createRuntimeLogger,
|
|
13
14
|
currentOperation,
|
|
@@ -20,10 +21,12 @@ import {
|
|
|
20
21
|
fingerprintEventAttributes,
|
|
21
22
|
fingerprintValues,
|
|
22
23
|
flushRuntimeLogs,
|
|
24
|
+
isCaptured,
|
|
23
25
|
isError,
|
|
24
26
|
isRuntimeErrorLike,
|
|
25
27
|
local,
|
|
26
28
|
makeRedactor,
|
|
29
|
+
markCaptured,
|
|
27
30
|
normalizeRoutePath,
|
|
28
31
|
noteOperationError,
|
|
29
32
|
notifyException,
|
|
@@ -41,14 +44,17 @@ import {
|
|
|
41
44
|
runtimeLogStats,
|
|
42
45
|
runtimeLogger,
|
|
43
46
|
sanitizeBreadcrumbs,
|
|
47
|
+
setHandledCapture,
|
|
44
48
|
shutdownRuntimeLogger,
|
|
45
49
|
splitFingerprint,
|
|
50
|
+
startLoggedErrorCapture,
|
|
51
|
+
stopLoggedErrorCapture,
|
|
46
52
|
toClientError,
|
|
47
53
|
withRuntimeOperation
|
|
48
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-HHKQ3UCH.js";
|
|
49
55
|
import {
|
|
50
56
|
runDoctor
|
|
51
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-JSGGUWRD.js";
|
|
52
58
|
import {
|
|
53
59
|
COMPAT_MANIFEST,
|
|
54
60
|
CountingExporter,
|
|
@@ -70,7 +76,7 @@ import {
|
|
|
70
76
|
sharedSlot,
|
|
71
77
|
telemetryStats,
|
|
72
78
|
unregisterFlushTargets
|
|
73
|
-
} from "./chunk-
|
|
79
|
+
} from "./chunk-ZKM76SDP.js";
|
|
74
80
|
|
|
75
81
|
// src/relay.ts
|
|
76
82
|
var IpWindow = class {
|
|
@@ -95,6 +101,112 @@ function firstForwardedFor(value) {
|
|
|
95
101
|
const raw = Array.isArray(value) ? value[0] : value;
|
|
96
102
|
return raw ? (raw.split(",")[0] ?? "").trim() : "";
|
|
97
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
|
+
}
|
|
98
210
|
var DEFAULT_ENDPOINT = "https://otlp.autter.dev";
|
|
99
211
|
var DEFAULT_MAX_BODY = 64 * 1024;
|
|
100
212
|
var MAX_EVENTS = 50;
|
|
@@ -328,7 +440,7 @@ function createBrowserRelayFetchHandler(opts) {
|
|
|
328
440
|
return new Response(null, { status: 405 });
|
|
329
441
|
}
|
|
330
442
|
if (limiter) {
|
|
331
|
-
const ip =
|
|
443
|
+
const ip = fetchClientKey(request, opts);
|
|
332
444
|
if (!limiter.allow(ip)) {
|
|
333
445
|
return new Response(JSON.stringify({ error: "rate limit exceeded" }), {
|
|
334
446
|
status: 429
|
|
@@ -394,7 +506,7 @@ function createBrowserRelayHandler(opts) {
|
|
|
394
506
|
return;
|
|
395
507
|
}
|
|
396
508
|
if (limiter) {
|
|
397
|
-
const ip =
|
|
509
|
+
const ip = nodeClientKey(req, opts);
|
|
398
510
|
if (!limiter.allow(ip)) {
|
|
399
511
|
respond(res, 429, { error: "rate limit exceeded" });
|
|
400
512
|
return;
|
|
@@ -745,7 +857,6 @@ import { context, trace } from "@opentelemetry/api";
|
|
|
745
857
|
import { getRPCMetadata, RPCType } from "@opentelemetry/core";
|
|
746
858
|
var REQUEST_STATE = /* @__PURE__ */ Symbol.for("autter.runtime.request");
|
|
747
859
|
var BOUND = /* @__PURE__ */ Symbol.for("autter.runtime.bound");
|
|
748
|
-
var CAPTURED = sharedSlot("requests", () => ({ captured: /* @__PURE__ */ new WeakSet() })).captured;
|
|
749
860
|
var capturer = () => {
|
|
750
861
|
};
|
|
751
862
|
function setRequestExceptionCapturer(fn) {
|
|
@@ -756,8 +867,14 @@ var annotator = () => {
|
|
|
756
867
|
function setRequestSpanAnnotator(fn) {
|
|
757
868
|
annotator = fn;
|
|
758
869
|
}
|
|
759
|
-
function
|
|
760
|
-
if (
|
|
870
|
+
function decide(rule, error, fallback) {
|
|
871
|
+
if (rule === void 0) return fallback;
|
|
872
|
+
if (typeof rule !== "function") return rule;
|
|
873
|
+
try {
|
|
874
|
+
return rule(error) === true;
|
|
875
|
+
} catch {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
761
878
|
}
|
|
762
879
|
function expressRouteOf(req) {
|
|
763
880
|
const props = req;
|
|
@@ -788,7 +905,7 @@ function captureAtBoundary(error, state, rule = void 0, attributes) {
|
|
|
788
905
|
wanted = false;
|
|
789
906
|
}
|
|
790
907
|
if (!wanted) return;
|
|
791
|
-
if (
|
|
908
|
+
if (isCaptured(error)) return;
|
|
792
909
|
markCaptured(error);
|
|
793
910
|
try {
|
|
794
911
|
const run = () => onRequestSpan(() => capturer(error, attributes));
|
|
@@ -802,7 +919,7 @@ function requestStateOf(req) {
|
|
|
802
919
|
return req[REQUEST_STATE];
|
|
803
920
|
}
|
|
804
921
|
function captureRequestError(error, attributes) {
|
|
805
|
-
if (
|
|
922
|
+
if (isCaptured(error)) {
|
|
806
923
|
if (!attributes) return;
|
|
807
924
|
try {
|
|
808
925
|
onRequestSpan(() => annotator(attributes));
|
|
@@ -894,7 +1011,8 @@ function finishRequest(state, input) {
|
|
|
894
1011
|
...state.error !== void 0 ? { error: state.error } : {},
|
|
895
1012
|
...state.errorThrown ? { thrown: true } : {},
|
|
896
1013
|
status: input.status,
|
|
897
|
-
aborted: input.aborted
|
|
1014
|
+
aborted: input.aborted,
|
|
1015
|
+
...state.degraded ? { degraded: true } : {}
|
|
898
1016
|
});
|
|
899
1017
|
const span = trace.getActiveSpan();
|
|
900
1018
|
if (span?.isRecording()) span.setAttribute("autter.operation.outcome", state.outcome);
|
|
@@ -1119,9 +1237,15 @@ function withRuntimeRequest(handler, options = {}) {
|
|
|
1119
1237
|
try {
|
|
1120
1238
|
return await handler(request, ...rest);
|
|
1121
1239
|
} catch (error) {
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1240
|
+
if (decide(options.capture, error, true)) {
|
|
1241
|
+
noteOperationError(error, true, state);
|
|
1242
|
+
captureAtBoundary(error, state, true);
|
|
1243
|
+
}
|
|
1244
|
+
if (!decide(options.errorResponse, error, false)) throw error;
|
|
1245
|
+
return new Response(JSON.stringify(toClientError(error, requestId)), {
|
|
1246
|
+
status: responseStatusOf(error),
|
|
1247
|
+
headers: { "content-type": "application/json; charset=utf-8" }
|
|
1248
|
+
});
|
|
1125
1249
|
}
|
|
1126
1250
|
});
|
|
1127
1251
|
status = response.status;
|
|
@@ -1319,6 +1443,8 @@ var S = sharedSlot("server", () => ({
|
|
|
1319
1443
|
alwaysOnProvider: null,
|
|
1320
1444
|
redactor: makeRedactor(true),
|
|
1321
1445
|
loggerOnlyCapture: null,
|
|
1446
|
+
handled: null,
|
|
1447
|
+
loggerOnlyHandled: null,
|
|
1322
1448
|
hookRequests: false
|
|
1323
1449
|
}));
|
|
1324
1450
|
var LLM_MARKER_ATTRIBUTES = [
|
|
@@ -1718,6 +1844,7 @@ function initAutterServer(options) {
|
|
|
1718
1844
|
// its span-fed per-route counts.
|
|
1719
1845
|
"autter.metrics_routes": true
|
|
1720
1846
|
});
|
|
1847
|
+
const errorFilter = createErrorFilter({ ignoreErrors: options.ignoreErrors, sampleRate: options.sampleRate });
|
|
1721
1848
|
const rootSampler = new TraceIdRatioBasedSampler(options.traceSampleRate ?? 0.01);
|
|
1722
1849
|
const headSampler = new ParentBasedSampler({
|
|
1723
1850
|
root: rootSampler,
|
|
@@ -1793,6 +1920,16 @@ function initAutterServer(options) {
|
|
|
1793
1920
|
const processTracer = alwaysOnProvider.getTracer("autter-processes");
|
|
1794
1921
|
const llmTracer = alwaysOnProvider.getTracer("autter-llm");
|
|
1795
1922
|
function captureException2(error, attributes) {
|
|
1923
|
+
const isErrValue = isError(error);
|
|
1924
|
+
const reported = errorFilter({
|
|
1925
|
+
type: isErrValue ? String(error.name ?? "") : "Error",
|
|
1926
|
+
message: isErrValue ? String(error.message ?? "") : String(error),
|
|
1927
|
+
crash: attributes?.["autter.unhandled"] === true || attributes?.["autter.unhandled_rejection"] === true
|
|
1928
|
+
});
|
|
1929
|
+
if (!reported) {
|
|
1930
|
+
markCaptured(error);
|
|
1931
|
+
return;
|
|
1932
|
+
}
|
|
1796
1933
|
errorTraceBuffer?.retainActiveTrace();
|
|
1797
1934
|
telemetryStats.markCaptured();
|
|
1798
1935
|
const isErr = isError(error);
|
|
@@ -1845,6 +1982,7 @@ function initAutterServer(options) {
|
|
|
1845
1982
|
if (!reuseActive) span.end();
|
|
1846
1983
|
}
|
|
1847
1984
|
function captureMessage2(message, severity = "warning", attributes) {
|
|
1985
|
+
if (!errorFilter({ type: "Message", message: String(message), severity })) return;
|
|
1848
1986
|
if (severity === "error" || severity === "fatal") {
|
|
1849
1987
|
errorTraceBuffer?.retainActiveTrace();
|
|
1850
1988
|
}
|
|
@@ -1865,6 +2003,7 @@ function initAutterServer(options) {
|
|
|
1865
2003
|
span.end();
|
|
1866
2004
|
}
|
|
1867
2005
|
function reportOutcome2(name, message, attributes) {
|
|
2006
|
+
if (!errorFilter({ type: "OutcomeFailure", message: `${name}: ${message}` })) return;
|
|
1868
2007
|
errorTraceBuffer?.retainActiveTrace();
|
|
1869
2008
|
telemetryStats.markCaptured();
|
|
1870
2009
|
const activeSpan = trace2.getActiveSpan();
|
|
@@ -1882,6 +2021,37 @@ function initAutterServer(options) {
|
|
|
1882
2021
|
span.setStatus({ code: SpanStatusCode.ERROR, message: safeMessage });
|
|
1883
2022
|
if (!reuseActive) span.end();
|
|
1884
2023
|
}
|
|
2024
|
+
function captureHandled(error, attributes, severity) {
|
|
2025
|
+
const isErr = isError(error);
|
|
2026
|
+
if (!errorFilter({
|
|
2027
|
+
type: isErr ? String(error.name ?? "") : "Error",
|
|
2028
|
+
message: isErr ? String(error.message ?? "") : String(error),
|
|
2029
|
+
severity
|
|
2030
|
+
})) return;
|
|
2031
|
+
if (severity === "error") errorTraceBuffer?.retainActiveTrace();
|
|
2032
|
+
telemetryStats.markCaptured();
|
|
2033
|
+
const message = redactor.text(isErr ? error.message : String(error));
|
|
2034
|
+
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");
|
|
2035
|
+
const split = splitFingerprint(attributes);
|
|
2036
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, redactor.text);
|
|
2037
|
+
const redacted = redactor(split.attributes);
|
|
2038
|
+
const activeSpan = trace2.getActiveSpan();
|
|
2039
|
+
const reuseActive = activeSpan?.isRecording() === true;
|
|
2040
|
+
const span = reuseActive ? activeSpan : errorTracer.startSpan(isErr ? error.name : "Error");
|
|
2041
|
+
markCaptured(error);
|
|
2042
|
+
notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
|
|
2043
|
+
span.addEvent("exception", {
|
|
2044
|
+
...redacted,
|
|
2045
|
+
"exception.type": isErr ? error.name : "Error",
|
|
2046
|
+
"exception.message": message,
|
|
2047
|
+
...stack ? { "exception.stacktrace": redactor.text(stack) } : {},
|
|
2048
|
+
"autter.severity": severity,
|
|
2049
|
+
...errorAttributes(error),
|
|
2050
|
+
...fingerprint,
|
|
2051
|
+
...breadcrumbEventAttributes(redactor.text, error)
|
|
2052
|
+
});
|
|
2053
|
+
if (!reuseActive) span.end();
|
|
2054
|
+
}
|
|
1885
2055
|
configureRuntimeLogger({
|
|
1886
2056
|
endpoint,
|
|
1887
2057
|
apiKey: options.apiKey,
|
|
@@ -1895,6 +2065,8 @@ function initAutterServer(options) {
|
|
|
1895
2065
|
reportOutcome: reportOutcome2
|
|
1896
2066
|
});
|
|
1897
2067
|
S.hookRequests = options.logging?.requests === true;
|
|
2068
|
+
S.handled = captureHandled;
|
|
2069
|
+
startLoggedErrorCapture(options.captureLoggedErrors);
|
|
1898
2070
|
let onCrash = null;
|
|
1899
2071
|
let uninstallRejections = null;
|
|
1900
2072
|
if (options.captureGlobalErrors !== false) {
|
|
@@ -1941,6 +2113,7 @@ function initAutterServer(options) {
|
|
|
1941
2113
|
withLlmCall: (info, fn) => runLlmSpan(llmTracer, info, fn),
|
|
1942
2114
|
trackLlmCall: (call) => recordLlmCall(llmTracer, call),
|
|
1943
2115
|
shutdown: async () => {
|
|
2116
|
+
if (S.active === server) stopLoggedErrorCapture();
|
|
1944
2117
|
const logShutdown = await Promise.allSettled([shutdownRuntimeLogger()]);
|
|
1945
2118
|
if (onCrash) process.off("uncaughtExceptionMonitor", onCrash);
|
|
1946
2119
|
uninstallRejections?.();
|
|
@@ -1948,6 +2121,7 @@ function initAutterServer(options) {
|
|
|
1948
2121
|
if (S.active === server) {
|
|
1949
2122
|
S.active = null;
|
|
1950
2123
|
S.alwaysOnProvider = null;
|
|
2124
|
+
S.handled = null;
|
|
1951
2125
|
S.hookRequests = false;
|
|
1952
2126
|
S.redactor = makeRedactor(true);
|
|
1953
2127
|
}
|
|
@@ -1983,13 +2157,39 @@ function captureException(error, attributes) {
|
|
|
1983
2157
|
recordError(span, error);
|
|
1984
2158
|
span.end();
|
|
1985
2159
|
}
|
|
1986
|
-
function setLoggerOnlyCapture(fn) {
|
|
2160
|
+
function setLoggerOnlyCapture(fn, handled = null) {
|
|
1987
2161
|
S.loggerOnlyCapture = fn;
|
|
2162
|
+
S.loggerOnlyHandled = handled;
|
|
2163
|
+
}
|
|
2164
|
+
function captureHandledError(error, attributes, severity) {
|
|
2165
|
+
if (S.active) {
|
|
2166
|
+
S.handled?.(error, attributes, severity);
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
if (S.loggerOnlyHandled) {
|
|
2170
|
+
S.loggerOnlyHandled(error, attributes, severity);
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
markCaptured(error);
|
|
2174
|
+
const split = splitFingerprint(attributes);
|
|
2175
|
+
notifyException(error, { ...split.attributes, "autter.severity": severity });
|
|
2176
|
+
const span = trace2.getTracer("autter-errors").startSpan(isError(error) ? error.name : "Error");
|
|
2177
|
+
span.addEvent("exception", {
|
|
2178
|
+
...S.redactor(split.attributes),
|
|
2179
|
+
"exception.type": isError(error) ? error.name : "Error",
|
|
2180
|
+
"exception.message": S.redactor.text(isError(error) ? error.message : String(error)),
|
|
2181
|
+
...isError(error) && error.stack ? { "exception.stacktrace": S.redactor.text(error.stack) } : {},
|
|
2182
|
+
"autter.severity": severity,
|
|
2183
|
+
...errorAttributes(error),
|
|
2184
|
+
...fingerprintEventAttributes(split.fingerprint, S.redactor.text)
|
|
2185
|
+
});
|
|
2186
|
+
span.end();
|
|
1988
2187
|
}
|
|
1989
2188
|
function serverActive() {
|
|
1990
2189
|
return S.active !== null;
|
|
1991
2190
|
}
|
|
1992
2191
|
setRequestExceptionCapturer((error, attributes) => captureException(error, attributes));
|
|
2192
|
+
setHandledCapture(captureHandledError);
|
|
1993
2193
|
setRequestSpanAnnotator((attributes) => {
|
|
1994
2194
|
const span = trace2.getActiveSpan();
|
|
1995
2195
|
if (span?.isRecording()) span.setAttributes(S.redactor(splitFingerprint(attributes).attributes));
|
|
@@ -2418,8 +2618,16 @@ function initAutterLogging(options) {
|
|
|
2418
2618
|
const redact = makeRedactor(options.redactAttributes ?? true);
|
|
2419
2619
|
const mode = options.exceptions ?? "auto";
|
|
2420
2620
|
debugLog(`initialising logger-only service=${options.service} endpoint=${endpoint}`);
|
|
2621
|
+
const errorFilter = createErrorFilter({ ignoreErrors: options.ignoreErrors, sampleRate: options.sampleRate });
|
|
2421
2622
|
function captureException2(error, attributes) {
|
|
2422
2623
|
markCaptured(error);
|
|
2624
|
+
const isErrValue = isError(error);
|
|
2625
|
+
const reported = errorFilter({
|
|
2626
|
+
type: isErrValue ? String(error.name ?? "") : "Error",
|
|
2627
|
+
message: isErrValue ? String(error.message ?? "") : String(error),
|
|
2628
|
+
crash: attributes?.["autter.unhandled"] === true || attributes?.["autter.unhandled_rejection"] === true
|
|
2629
|
+
});
|
|
2630
|
+
if (!reported) return;
|
|
2423
2631
|
const split = splitFingerprint(attributes);
|
|
2424
2632
|
const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
|
|
2425
2633
|
const redacted = redact(split.attributes);
|
|
@@ -2452,6 +2660,33 @@ function initAutterLogging(options) {
|
|
|
2452
2660
|
notifyException(error, { ...redacted, ...fingerprint });
|
|
2453
2661
|
captureExceptionAsLog(error, { ...redacted, ...fingerprint });
|
|
2454
2662
|
}
|
|
2663
|
+
function captureHandled(error, attributes, severity) {
|
|
2664
|
+
markCaptured(error);
|
|
2665
|
+
const isErrValue = isError(error);
|
|
2666
|
+
if (!errorFilter({
|
|
2667
|
+
type: isErrValue ? String(error.name ?? "") : "Error",
|
|
2668
|
+
message: isErrValue ? String(error.message ?? "") : String(error),
|
|
2669
|
+
severity
|
|
2670
|
+
})) return;
|
|
2671
|
+
const split = splitFingerprint(attributes);
|
|
2672
|
+
const fingerprint = fingerprintEventAttributes(split.fingerprint, redact.text);
|
|
2673
|
+
const redacted = redact(split.attributes);
|
|
2674
|
+
notifyException(error, { ...redacted, ...fingerprint, "autter.severity": severity });
|
|
2675
|
+
const span = trace3.getActiveSpan();
|
|
2676
|
+
if (mode === "auto" && span?.isRecording()) {
|
|
2677
|
+
span.addEvent("exception", {
|
|
2678
|
+
...redacted,
|
|
2679
|
+
"exception.type": isErrValue ? error.name : "Error",
|
|
2680
|
+
"exception.message": redact.text(isErrValue ? error.message : String(error)),
|
|
2681
|
+
...isErrValue && error.stack ? { "exception.stacktrace": redact.text(error.stack) } : {},
|
|
2682
|
+
"autter.severity": severity,
|
|
2683
|
+
...errorAttributes(error),
|
|
2684
|
+
...fingerprint
|
|
2685
|
+
});
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
captureExceptionAsLog(error, { ...redacted, ...fingerprint }, severity);
|
|
2689
|
+
}
|
|
2455
2690
|
const captured = /* @__PURE__ */ new WeakSet();
|
|
2456
2691
|
configureRuntimeLogger({
|
|
2457
2692
|
endpoint,
|
|
@@ -2476,7 +2711,8 @@ function initAutterLogging(options) {
|
|
|
2476
2711
|
captureException2(error);
|
|
2477
2712
|
}
|
|
2478
2713
|
});
|
|
2479
|
-
setLoggerOnlyCapture(captureException2);
|
|
2714
|
+
setLoggerOnlyCapture(captureException2, captureHandled);
|
|
2715
|
+
startLoggedErrorCapture(options.captureLoggedErrors);
|
|
2480
2716
|
const onCrash = (error) => {
|
|
2481
2717
|
if (recordedAsRejection(error)) return;
|
|
2482
2718
|
captureException2(error, { "autter.unhandled": true });
|
|
@@ -2495,6 +2731,7 @@ function initAutterLogging(options) {
|
|
|
2495
2731
|
captureException: captureException2,
|
|
2496
2732
|
flush: flushRuntimeLogs,
|
|
2497
2733
|
shutdown: async () => {
|
|
2734
|
+
if (L.active === handle) stopLoggedErrorCapture();
|
|
2498
2735
|
process.off("uncaughtExceptionMonitor", onCrash);
|
|
2499
2736
|
uninstallRejections?.();
|
|
2500
2737
|
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,53 @@ 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
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Client-side volume control shared by the server SDKs (runtime-node,
|
|
291
|
+
* runtime-edge; 1.8.0 / 1.2.0, EI-38): `ignoreErrors` and `sampleRate`.
|
|
292
|
+
* The browser has its own copy in `@autter/runtime-browser/filters`.
|
|
293
|
+
*
|
|
294
|
+
* - `ignoreErrors`: strings or RegExps. A string matches when the error's
|
|
295
|
+
* type or message contains it (case-sensitive, like the message itself);
|
|
296
|
+
* a RegExp is tested against the type, the message and
|
|
297
|
+
* `"<type>: <message>"`. A match is never reported, whatever its severity.
|
|
298
|
+
* - `sampleRate`: 0..1, the share of errors reported. Errors only: warnings
|
|
299
|
+
* and info messages are never sampled, and neither is a fatal error, a
|
|
300
|
+
* crash (uncaught exception) or an unhandled rejection, which may be one.
|
|
301
|
+
* Out of range or not a number: everything is reported.
|
|
302
|
+
*
|
|
303
|
+
* What is filtered here never leaves the process and is counted nowhere.
|
|
304
|
+
* To leave errors out of every SDK at once, and see how many were left out,
|
|
305
|
+
* use suppression rules in Autter (applied by the ingester, 1.9.0).
|
|
306
|
+
*/
|
|
307
|
+
type IgnoreErrorPattern = string | RegExp;
|
|
308
|
+
|
|
253
309
|
/**
|
|
254
310
|
* Where runtime records go. The logger pipeline is
|
|
255
311
|
* `enrich → redact/bound → sinks[]`; each sink receives the same immutable
|
|
@@ -338,4 +394,4 @@ interface MemorySink extends RuntimeSink {
|
|
|
338
394
|
/** Keeps every record in memory — for tests (`@autter/runtime-node/testing`). */
|
|
339
395
|
declare function memorySink(): MemorySink;
|
|
340
396
|
|
|
341
|
-
export {
|
|
397
|
+
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,53 @@ 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
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Client-side volume control shared by the server SDKs (runtime-node,
|
|
291
|
+
* runtime-edge; 1.8.0 / 1.2.0, EI-38): `ignoreErrors` and `sampleRate`.
|
|
292
|
+
* The browser has its own copy in `@autter/runtime-browser/filters`.
|
|
293
|
+
*
|
|
294
|
+
* - `ignoreErrors`: strings or RegExps. A string matches when the error's
|
|
295
|
+
* type or message contains it (case-sensitive, like the message itself);
|
|
296
|
+
* a RegExp is tested against the type, the message and
|
|
297
|
+
* `"<type>: <message>"`. A match is never reported, whatever its severity.
|
|
298
|
+
* - `sampleRate`: 0..1, the share of errors reported. Errors only: warnings
|
|
299
|
+
* and info messages are never sampled, and neither is a fatal error, a
|
|
300
|
+
* crash (uncaught exception) or an unhandled rejection, which may be one.
|
|
301
|
+
* Out of range or not a number: everything is reported.
|
|
302
|
+
*
|
|
303
|
+
* What is filtered here never leaves the process and is counted nowhere.
|
|
304
|
+
* To leave errors out of every SDK at once, and see how many were left out,
|
|
305
|
+
* use suppression rules in Autter (applied by the ingester, 1.9.0).
|
|
306
|
+
*/
|
|
307
|
+
type IgnoreErrorPattern = string | RegExp;
|
|
308
|
+
|
|
253
309
|
/**
|
|
254
310
|
* Where runtime records go. The logger pipeline is
|
|
255
311
|
* `enrich → redact/bound → sinks[]`; each sink receives the same immutable
|
|
@@ -338,4 +394,4 @@ interface MemorySink extends RuntimeSink {
|
|
|
338
394
|
/** Keeps every record in memory — for tests (`@autter/runtime-node/testing`). */
|
|
339
395
|
declare function memorySink(): MemorySink;
|
|
340
396
|
|
|
341
|
-
export {
|
|
397
|
+
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 };
|