@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/README.md
CHANGED
|
@@ -48,6 +48,38 @@ expectOperation(runtime, "POST /checkout").toHaveOutcome("degraded").toHaveError
|
|
|
48
48
|
|
|
49
49
|
Full guide: [Requests, coded errors and background work](../../docs/REQUESTS-AND-ERRORS.md).
|
|
50
50
|
|
|
51
|
+
# Breadcrumbs (1.6.0)
|
|
52
|
+
|
|
53
|
+
The steps before an error, scoped to the current request:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { addBreadcrumb, runtimeLogger } from "@autter/runtime-node";
|
|
57
|
+
|
|
58
|
+
app.post("/checkout", async (req, res) => {
|
|
59
|
+
addBreadcrumb({ message: "Loaded cart", data: { items: 3 } }); // type "custom"
|
|
60
|
+
runtimeLogger.info("Charging card", { provider: "stripe" }); // recorded as a "log" breadcrumb
|
|
61
|
+
await charge(); // a throw here carries both
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- Scope: inside `autterRequests()` / `withRuntimeRequest()` /
|
|
66
|
+
`withRuntimeOperation()` the trail belongs to the request and is shared with
|
|
67
|
+
its child operations and forks (AsyncLocalStorage). Inside a traced request
|
|
68
|
+
without the middleware it belongs to the trace; outside both, to the process
|
|
69
|
+
(so a crash outside any request carries what happened before it).
|
|
70
|
+
- The last 30 are kept. `captureException`, `captureMessage` and errors thrown
|
|
71
|
+
out of operations attach them to the exception span event as
|
|
72
|
+
`autter.breadcrumbs` (a JSON string, at most 16 KB, or less when
|
|
73
|
+
`OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT` is lower).
|
|
74
|
+
- Every `runtimeLogger` / `runtimeContext.debug|info|warn|error` entry is
|
|
75
|
+
recorded as a `log` breadcrumb with its level and up to 10 primitive
|
|
76
|
+
attributes.
|
|
77
|
+
- Messages (≤ 500 characters) and data (≤ 10 keys of strings, numbers,
|
|
78
|
+
booleans or null) are scrubbed with your `redact` settings.
|
|
79
|
+
- The browser relay forwards browser breadcrumbs, re-validated and re-scrubbed.
|
|
80
|
+
- Needs ingester 1.6.0. Logger-only mode (`initAutterLogging`) and
|
|
81
|
+
`@autter/runtime-edge` don't attach them yet.
|
|
82
|
+
|
|
51
83
|
# @autter/runtime-node
|
|
52
84
|
|
|
53
85
|
The server tracker exports per-instance RSS, heap, memory limit, and GC
|
|
@@ -113,6 +145,12 @@ const autter = initAutterServer({
|
|
|
113
145
|
// handled errors — always recorded, never sampled out:
|
|
114
146
|
autter.captureException(err, { "order.id": "…" });
|
|
115
147
|
|
|
148
|
+
// fingerprint override — every error carrying the same fingerprint is one
|
|
149
|
+
// issue, whatever its message or stack (a string or up to 10 strings, each
|
|
150
|
+
// ≤200 chars; sent as `autter.fingerprint` on the exception event; needs
|
|
151
|
+
// ingester 1.6.0+, older ingesters ignore it):
|
|
152
|
+
autter.captureException(err, { fingerprint: ["checkout", "card-timeout"] });
|
|
153
|
+
|
|
116
154
|
// warnings/info without an exception — same grouping+aggregation as
|
|
117
155
|
// errors, just a lower severity ("fatal" | "error" | "warning" | "info"):
|
|
118
156
|
autter.captureMessage("Legacy /orders lookup used", "warning");
|
|
@@ -142,7 +180,7 @@ Defaults (cheap by construction):
|
|
|
142
180
|
| Traces containing an error | 100% (tail retention, `retainTracesOnError`) |
|
|
143
181
|
| `withProcessSpan` spans | 100% (same always-on tracer) |
|
|
144
182
|
| LLM/GenAI call spans | 100% (`llmTracing`, on by default) |
|
|
145
|
-
| Healthy traces | 1% head sampling (`traceSampleRate`) |
|
|
183
|
+
| Healthy traces | 1% head sampling (`traceSampleRate`; also for requests whose incoming `traceparent` is unsampled, such as browser fetches from `@autter/runtime-browser`) |
|
|
146
184
|
| Request metrics | exported every 60 s |
|
|
147
185
|
| Logs | not collected |
|
|
148
186
|
| PII in custom attributes | redacted before export (`redactAttributes`) |
|
|
@@ -167,6 +205,63 @@ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
|
|
167
205
|
initAutterServer({ ..., instrumentations: [new ExpressInstrumentation()] });
|
|
168
206
|
```
|
|
169
207
|
|
|
208
|
+
### Server errors keep their stacks
|
|
209
|
+
|
|
210
|
+
When a handler throws, the framework answers 500 — and on its own that 500
|
|
211
|
+
is all the tracker sees: a stackless `HttpServerError` grouped by route. Add
|
|
212
|
+
your framework's hook and the thrown error is recorded, with its stack, on
|
|
213
|
+
the request span; the framework then carries on exactly as before.
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { autterErrorHandler, autterFastifyErrors, autterKoa, captureRequestError } from "@autter/runtime-node";
|
|
217
|
+
|
|
218
|
+
// Express / Connect: after your routes, before your own error handlers.
|
|
219
|
+
// Records the error, then calls next(err) unchanged.
|
|
220
|
+
app.use(autterErrorHandler());
|
|
221
|
+
|
|
222
|
+
// Fastify 4/5: an onError hook (Fastify's error handler still answers).
|
|
223
|
+
// autterFastify (request summaries) already includes it.
|
|
224
|
+
app.register(autterFastifyErrors); // or autterFastifyErrors(app)
|
|
225
|
+
|
|
226
|
+
// Koa 2/3: mount first; records and rethrows, so app.on("error") still fires.
|
|
227
|
+
app.use(autterKoa());
|
|
228
|
+
|
|
229
|
+
// Anything else (NestJS exception filters, hapi onPreResponse, …):
|
|
230
|
+
captureRequestError(err, { "app.handler": "checkout" });
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
All three report errors with status ≥ 500 (or none), RuntimeErrors and coded
|
|
234
|
+
errors — `ctx.throw(404)` and validation failures stay out of your issues.
|
|
235
|
+
Pass `{ capture: true }` to report every error, or a predicate. An error is
|
|
236
|
+
reported once, even if your handler already called `captureException`, and it
|
|
237
|
+
lands on the request's HTTP server span even when an instrumentation's child
|
|
238
|
+
span is active (`captureRequestError` still adds its attributes to that span
|
|
239
|
+
when the error was already reported). Errors from another JavaScript realm
|
|
240
|
+
(a `vm` context, Next's edge sandbox under `next start`) keep their own type,
|
|
241
|
+
message and stack. `autterErrorResponse()` reports the same way and also
|
|
242
|
+
answers the request.
|
|
243
|
+
|
|
244
|
+
### Unhandled promise rejections
|
|
245
|
+
|
|
246
|
+
Recorded as errors (severity `error`, `autter.unhandled_rejection: true`, not
|
|
247
|
+
fatal) — without changing whether your process crashes. Any
|
|
248
|
+
`unhandledRejection` listener switches off Node's default crash, so the SDK
|
|
249
|
+
does what Node would have done when its listener is the only one:
|
|
250
|
+
|
|
251
|
+
| `--unhandled-rejections` | Your own listener? | What happens | Recorded as |
|
|
252
|
+
| --- | --- | --- | --- |
|
|
253
|
+
| `throw` (default) | no | rethrown on the next tick: exit code 1, as before | fatal crash (`autter.unhandled`) |
|
|
254
|
+
| `throw` (default) | yes | your listener decides | error |
|
|
255
|
+
| `strict` | either | Node crashes first, as before | fatal crash |
|
|
256
|
+
| `warn`, `none` | either | no crash, as before | error |
|
|
257
|
+
| `warn-with-error-code` | no | Node's warning and exit code 1, as before | error |
|
|
258
|
+
|
|
259
|
+
The one visible difference: after the rethrow, `uncaughtException` listeners
|
|
260
|
+
receive origin `"uncaughtException"` rather than `"unhandledRejection"`.
|
|
261
|
+
`captureUnhandledRejections: false` (or `captureGlobalErrors: false`) restores
|
|
262
|
+
the 1.5.0 behaviour, where a rejection is seen only when it crashes the
|
|
263
|
+
process.
|
|
264
|
+
|
|
170
265
|
### Version compatibility: checked for you
|
|
171
266
|
|
|
172
267
|
Some features need a minimum ingester version: operation logging needs
|
|
@@ -90,6 +90,35 @@ var compat_manifest_default = {
|
|
|
90
90
|
ingester: "1.5.0",
|
|
91
91
|
migrations: ["0013-occurrence-codes"],
|
|
92
92
|
sdks: { "@autter/runtime-node": "1.5.0", "@autter/runtime-next": "1.5.0", "@autter/runtime-browser": "1.4.0", "@autter/runtime-edge": "1.0.0" }
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "breadcrumbs",
|
|
96
|
+
title: "Breadcrumbs: the steps before an error",
|
|
97
|
+
ingester: "1.6.0",
|
|
98
|
+
migrations: [],
|
|
99
|
+
sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-node": "1.6.0", "@autter/runtime-next": "1.6.0" }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "source_map_debug_ids",
|
|
103
|
+
title: "Source map debug ids",
|
|
104
|
+
ingester: "1.6.0",
|
|
105
|
+
migrations: ["0017-source-maps-debug-id"],
|
|
106
|
+
route: "/v1/sourcemaps/check",
|
|
107
|
+
sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-next": "1.6.0" }
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "fingerprint_override",
|
|
111
|
+
title: "Fingerprint override",
|
|
112
|
+
ingester: "1.6.0",
|
|
113
|
+
migrations: [],
|
|
114
|
+
sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-node": "1.6.0", "@autter/runtime-next": "1.6.0", "@autter/runtime-edge": "1.1.0" }
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "browser_trace_propagation",
|
|
118
|
+
title: "Browser trace propagation (browser errors linked to server traces)",
|
|
119
|
+
ingester: "1.6.0",
|
|
120
|
+
migrations: [],
|
|
121
|
+
sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-next": "1.6.0", "@autter/runtime-node": "1.6.0" }
|
|
93
122
|
}
|
|
94
123
|
]
|
|
95
124
|
};
|
|
@@ -435,7 +464,7 @@ var _https = require('https');
|
|
|
435
464
|
// package.json
|
|
436
465
|
var package_default = {
|
|
437
466
|
name: "@autter/runtime-node",
|
|
438
|
-
version: "1.
|
|
467
|
+
version: "1.6.0",
|
|
439
468
|
description: "Autter Runtime for Node.js: request wide events, coded errors, operation logging, same-origin browser relay and a curated OpenTelemetry server tracker",
|
|
440
469
|
license: "MIT",
|
|
441
470
|
type: "module",
|
|
@@ -486,6 +515,8 @@ var package_default = {
|
|
|
486
515
|
"@autter/runtime-core": "0.0.0",
|
|
487
516
|
"@types/node": "^22.10.0",
|
|
488
517
|
express: "^4.21.2",
|
|
518
|
+
fastify: "^5.12.5",
|
|
519
|
+
koa: "^3.2.1",
|
|
489
520
|
tsup: "^8.3.5",
|
|
490
521
|
typescript: "^5.7.0"
|
|
491
522
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
noteCompatFeature,
|
|
3
3
|
observeIngesterResponse
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-OY2HTUWY.js";
|
|
5
5
|
|
|
6
6
|
// ../runtime-core/dist/index.js
|
|
7
7
|
var SENSITIVE_KEY_PATTERNS = [
|
|
@@ -327,8 +327,8 @@ function priority(key) {
|
|
|
327
327
|
if (/^(?:exception|autter\.error)\./.test(key)) return 1;
|
|
328
328
|
return 0;
|
|
329
329
|
}
|
|
330
|
-
function boundContext(attributes = {},
|
|
331
|
-
const redacted =
|
|
330
|
+
function boundContext(attributes = {}, redact3) {
|
|
331
|
+
const redacted = redact3 ? redact3(attributes) : redactAttributes(attributes);
|
|
332
332
|
let budget = 512;
|
|
333
333
|
let characters = 16384;
|
|
334
334
|
let truncated = false;
|
|
@@ -365,14 +365,14 @@ function boundContext(attributes = {}, redact2) {
|
|
|
365
365
|
if (truncated) result["autter.context.truncated"] = true;
|
|
366
366
|
return result;
|
|
367
367
|
}
|
|
368
|
-
function userContext(attributes = {},
|
|
368
|
+
function userContext(attributes = {}, redact3) {
|
|
369
369
|
return Object.fromEntries(
|
|
370
|
-
Object.entries(boundContext(attributes,
|
|
370
|
+
Object.entries(boundContext(attributes, redact3)).filter(
|
|
371
371
|
([key]) => !RESERVED_KEY.test(key)
|
|
372
372
|
)
|
|
373
373
|
);
|
|
374
374
|
}
|
|
375
|
-
function mergeContext(current, next = {},
|
|
375
|
+
function mergeContext(current, next = {}, redact3) {
|
|
376
376
|
const merge2 = (left, right, depth) => {
|
|
377
377
|
const result = { ...left };
|
|
378
378
|
for (const [key, value] of Object.entries(right)) {
|
|
@@ -381,7 +381,7 @@ function mergeContext(current, next = {}, redact2) {
|
|
|
381
381
|
}
|
|
382
382
|
return result;
|
|
383
383
|
};
|
|
384
|
-
return userContext(merge2(current, userContext(next,
|
|
384
|
+
return userContext(merge2(current, userContext(next, redact3), 0), redact3);
|
|
385
385
|
}
|
|
386
386
|
function toSpanAttributes(context) {
|
|
387
387
|
return Object.fromEntries(
|
|
@@ -473,6 +473,17 @@ function defineRuntimeErrors(namespace, catalog) {
|
|
|
473
473
|
}
|
|
474
474
|
return out;
|
|
475
475
|
}
|
|
476
|
+
function isError(value) {
|
|
477
|
+
if (!value || typeof value !== "object") return false;
|
|
478
|
+
try {
|
|
479
|
+
if (value instanceof Error) return true;
|
|
480
|
+
const native = Error.isError;
|
|
481
|
+
if (typeof native === "function") return native(value) === true;
|
|
482
|
+
return Object.prototype.toString.call(value) === "[object Error]";
|
|
483
|
+
} catch {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
476
487
|
function isRuntimeErrorLike(err) {
|
|
477
488
|
if (err instanceof RuntimeError) return true;
|
|
478
489
|
if (!err || typeof err !== "object") return false;
|
|
@@ -523,15 +534,15 @@ function errorAttributes(err, options = {}) {
|
|
|
523
534
|
for (let n = 1; n <= CAUSE_DEPTH && cause !== void 0 && cause !== null; n++) {
|
|
524
535
|
if (seen.has(cause)) break;
|
|
525
536
|
seen.add(cause);
|
|
526
|
-
const
|
|
527
|
-
const type =
|
|
528
|
-
const message =
|
|
537
|
+
const isError2 = typeof cause === "object";
|
|
538
|
+
const type = isError2 ? String(field(cause, "name") ?? cause.constructor?.name ?? "Error") : typeof cause;
|
|
539
|
+
const message = isError2 ? field(cause, "message") : cause;
|
|
529
540
|
out[`exception.cause.${n}.type`] = type.slice(0, 200);
|
|
530
541
|
out[`exception.cause.${n}.message`] = String(message ?? "").slice(0, 1e3);
|
|
531
542
|
const causeCode = field(cause, "code");
|
|
532
543
|
if (typeof causeCode === "string" && causeCode)
|
|
533
544
|
out[`exception.cause.${n}.code`] = causeCode.slice(0, CODE_MAX_LENGTH);
|
|
534
|
-
cause =
|
|
545
|
+
cause = isError2 ? field(cause, "cause") : void 0;
|
|
535
546
|
}
|
|
536
547
|
}
|
|
537
548
|
return out;
|
|
@@ -577,19 +588,19 @@ function createInlineLogState(startedAt) {
|
|
|
577
588
|
level: "info"
|
|
578
589
|
};
|
|
579
590
|
}
|
|
580
|
-
function appendInlineLog(state, level, message, attrs,
|
|
591
|
+
function appendInlineLog(state, level, message, attrs, redact3, now = Date.now()) {
|
|
581
592
|
state.level = maxLevel(state.level, level);
|
|
582
593
|
if (state.logs.length >= INLINE_LOG_LIMIT) {
|
|
583
594
|
state.logsTruncated = true;
|
|
584
595
|
return;
|
|
585
596
|
}
|
|
586
|
-
const text = String(boundContext({ message },
|
|
597
|
+
const text = String(boundContext({ message }, redact3).message ?? "").slice(
|
|
587
598
|
0,
|
|
588
599
|
INLINE_MESSAGE_MAX
|
|
589
600
|
);
|
|
590
601
|
let small;
|
|
591
602
|
if (attrs && Object.keys(attrs).length) {
|
|
592
|
-
small = boundContext(attrs,
|
|
603
|
+
small = boundContext(attrs, redact3);
|
|
593
604
|
const encoded = JSON.stringify(small);
|
|
594
605
|
if (encoded.length > 1e3) small = { "autter.context.truncated": true };
|
|
595
606
|
}
|
|
@@ -806,6 +817,172 @@ function parseUserAgent(ua) {
|
|
|
806
817
|
const device = !value ? "unknown" : bot ? "bot" : /iPad|Tablet/.test(value) || /Android/.test(value) && !/Mobile/.test(value) ? "tablet" : /Mobi|iPhone|iPod|Android/.test(value) ? "mobile" : "desktop";
|
|
807
818
|
return { browser, os, device, bot };
|
|
808
819
|
}
|
|
820
|
+
var BREADCRUMBS_ATTRIBUTE = "autter.breadcrumbs";
|
|
821
|
+
var MAX_BREADCRUMBS = 30;
|
|
822
|
+
var MAX_MESSAGE = 500;
|
|
823
|
+
var MAX_DATA_KEYS = 10;
|
|
824
|
+
var MAX_DATA_STRING = 200;
|
|
825
|
+
var BREADCRUMBS_ATTRIBUTE_MAX_CHARS = 16 * 1024;
|
|
826
|
+
var MASK = "[redacted]";
|
|
827
|
+
var TYPES = /* @__PURE__ */ new Set(["console", "request", "navigation", "click", "log", "custom"]);
|
|
828
|
+
var LEVELS = {
|
|
829
|
+
debug: "debug",
|
|
830
|
+
trace: "debug",
|
|
831
|
+
info: "info",
|
|
832
|
+
log: "info",
|
|
833
|
+
warning: "warning",
|
|
834
|
+
warn: "warning",
|
|
835
|
+
error: "error",
|
|
836
|
+
fatal: "error"
|
|
837
|
+
};
|
|
838
|
+
var UNSAFE_KEY = /^(?:__proto__|constructor|prototype)$/;
|
|
839
|
+
function scrubber(redact3) {
|
|
840
|
+
return (value, max) => {
|
|
841
|
+
const scrubbed = redact3({ message: value.slice(0, max + 512) }).message;
|
|
842
|
+
return stripUrlQueries(typeof scrubbed === "string" ? scrubbed : "").slice(0, max);
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
function cleanData(raw, redact3) {
|
|
846
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
847
|
+
const picked = {};
|
|
848
|
+
let count = 0;
|
|
849
|
+
for (const key of Object.keys(raw).slice(0, 64)) {
|
|
850
|
+
if (count >= MAX_DATA_KEYS) break;
|
|
851
|
+
if (!key || UNSAFE_KEY.test(key)) continue;
|
|
852
|
+
const value = raw[key];
|
|
853
|
+
const name = key.slice(0, 100);
|
|
854
|
+
if (typeof value === "string") {
|
|
855
|
+
const text = /(?:url|path|route|target)$/i.test(name) ? value.split(/[?#]/)[0] : value;
|
|
856
|
+
picked[name] = text.slice(0, MAX_DATA_STRING + 512);
|
|
857
|
+
} else if (typeof value === "number") {
|
|
858
|
+
if (!Number.isFinite(value)) continue;
|
|
859
|
+
picked[name] = value;
|
|
860
|
+
} else if (typeof value === "boolean" || value === null) {
|
|
861
|
+
picked[name] = value;
|
|
862
|
+
} else {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
count++;
|
|
866
|
+
}
|
|
867
|
+
if (count === 0) return void 0;
|
|
868
|
+
const redacted = redact3(picked);
|
|
869
|
+
const out = {};
|
|
870
|
+
for (const key of Object.keys(picked)) {
|
|
871
|
+
const value = key in redacted ? redacted[key] : picked[key];
|
|
872
|
+
out[key] = typeof value === "string" ? stripUrlQueries(value).slice(0, MAX_DATA_STRING) : value;
|
|
873
|
+
}
|
|
874
|
+
return out;
|
|
875
|
+
}
|
|
876
|
+
function timestamp(value) {
|
|
877
|
+
if (value === void 0) return (/* @__PURE__ */ new Date()).toISOString();
|
|
878
|
+
if (typeof value !== "string" && typeof value !== "number" && !(value instanceof Date)) return null;
|
|
879
|
+
const at = new Date(value);
|
|
880
|
+
return Number.isNaN(at.getTime()) ? null : at.toISOString();
|
|
881
|
+
}
|
|
882
|
+
function normalizeBreadcrumb(input, redact3, options = {}) {
|
|
883
|
+
try {
|
|
884
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) return null;
|
|
885
|
+
const raw = input;
|
|
886
|
+
if (options.requireAt && raw.at === void 0) return null;
|
|
887
|
+
const at = timestamp(raw.at);
|
|
888
|
+
if (!at) return null;
|
|
889
|
+
const rawMessage = raw.message;
|
|
890
|
+
const message = typeof rawMessage === "string" ? rawMessage : typeof rawMessage === "number" || typeof rawMessage === "boolean" ? String(rawMessage) : "";
|
|
891
|
+
const data = cleanData(raw.data, redact3);
|
|
892
|
+
if (!message && !data) return null;
|
|
893
|
+
const type = typeof raw.type === "string" && TYPES.has(raw.type) ? raw.type : "custom";
|
|
894
|
+
const level = typeof raw.level === "string" ? LEVELS[raw.level.toLowerCase()] : void 0;
|
|
895
|
+
return {
|
|
896
|
+
type,
|
|
897
|
+
at,
|
|
898
|
+
...level ? { level } : {},
|
|
899
|
+
message: scrubber(redact3)(message, MAX_MESSAGE),
|
|
900
|
+
...data ? { data } : {}
|
|
901
|
+
};
|
|
902
|
+
} catch {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function sanitizeBreadcrumbs(raw, redact3) {
|
|
907
|
+
const out = [];
|
|
908
|
+
try {
|
|
909
|
+
if (!Array.isArray(raw)) return void 0;
|
|
910
|
+
const first = Math.max(0, raw.length - 200);
|
|
911
|
+
for (let i = raw.length - 1; i >= first && out.length < MAX_BREADCRUMBS; i--) {
|
|
912
|
+
const crumb = normalizeBreadcrumb(raw[i], redact3, { requireAt: true });
|
|
913
|
+
if (crumb) out.push(crumb);
|
|
914
|
+
}
|
|
915
|
+
} catch {
|
|
916
|
+
}
|
|
917
|
+
return out.length ? out.reverse() : void 0;
|
|
918
|
+
}
|
|
919
|
+
var BreadcrumbTrail = class {
|
|
920
|
+
constructor(max = MAX_BREADCRUMBS) {
|
|
921
|
+
this.max = max;
|
|
922
|
+
}
|
|
923
|
+
max;
|
|
924
|
+
items = [];
|
|
925
|
+
add(crumb) {
|
|
926
|
+
this.items.push(crumb);
|
|
927
|
+
if (this.items.length > this.max) this.items.splice(0, this.items.length - this.max);
|
|
928
|
+
}
|
|
929
|
+
/** Copies, oldest first. */
|
|
930
|
+
list() {
|
|
931
|
+
return this.items.map((crumb) => ({ ...crumb, ...crumb.data ? { data: { ...crumb.data } } : {} }));
|
|
932
|
+
}
|
|
933
|
+
get size() {
|
|
934
|
+
return this.items.length;
|
|
935
|
+
}
|
|
936
|
+
clear() {
|
|
937
|
+
this.items = [];
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
function breadcrumbsAttribute(list, scrub, maxChars = BREADCRUMBS_ATTRIBUTE_MAX_CHARS) {
|
|
941
|
+
const parts = [];
|
|
942
|
+
let chars = 2;
|
|
943
|
+
for (let i = list.length - 1; i >= 0 && parts.length < MAX_BREADCRUMBS; i--) {
|
|
944
|
+
const crumb = list[i];
|
|
945
|
+
let json = JSON.stringify(crumb);
|
|
946
|
+
if (scrub(json) !== json) {
|
|
947
|
+
json = JSON.stringify({ type: crumb.type, at: crumb.at, ...crumb.level ? { level: crumb.level } : {}, message: MASK });
|
|
948
|
+
}
|
|
949
|
+
if (chars + json.length + 1 > maxChars) break;
|
|
950
|
+
chars += json.length + 1;
|
|
951
|
+
parts.push(json);
|
|
952
|
+
}
|
|
953
|
+
if (parts.length === 0) return void 0;
|
|
954
|
+
const out = `[${parts.reverse().join(",")}]`;
|
|
955
|
+
return scrub(out) === out ? out : void 0;
|
|
956
|
+
}
|
|
957
|
+
var FINGERPRINT_ATTRIBUTE = "autter.fingerprint";
|
|
958
|
+
var FINGERPRINT_MAX_VALUES = 10;
|
|
959
|
+
var FINGERPRINT_MAX_LENGTH = 200;
|
|
960
|
+
function fingerprintValues(value, scrub = (text) => text) {
|
|
961
|
+
const list = Array.isArray(value) ? value : [value];
|
|
962
|
+
const values = list.filter((item) => typeof item === "string" && item !== "").slice(0, FINGERPRINT_MAX_VALUES).map((item) => scrub(item.slice(0, FINGERPRINT_MAX_LENGTH + 512)).slice(0, FINGERPRINT_MAX_LENGTH)).filter((item) => item !== "");
|
|
963
|
+
return values.length > 0 ? values : void 0;
|
|
964
|
+
}
|
|
965
|
+
function fingerprintEventAttributes(value, scrub) {
|
|
966
|
+
const values = fingerprintValues(value, scrub);
|
|
967
|
+
return values ? { [FINGERPRINT_ATTRIBUTE]: JSON.stringify(values) } : {};
|
|
968
|
+
}
|
|
969
|
+
function splitFingerprint(attributes) {
|
|
970
|
+
if (!attributes || typeof attributes !== "object" || !("fingerprint" in attributes)) {
|
|
971
|
+
return { attributes: attributes ?? void 0, fingerprint: void 0 };
|
|
972
|
+
}
|
|
973
|
+
const { fingerprint, ...rest } = attributes;
|
|
974
|
+
return { attributes: rest, fingerprint };
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// src/breadcrumbs.ts
|
|
978
|
+
import { trace as trace2 } from "@opentelemetry/api";
|
|
979
|
+
|
|
980
|
+
// src/logger.ts
|
|
981
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
982
|
+
import { randomUUID } from "crypto";
|
|
983
|
+
import {
|
|
984
|
+
trace
|
|
985
|
+
} from "@opentelemetry/api";
|
|
809
986
|
|
|
810
987
|
// src/sinks.ts
|
|
811
988
|
import {
|
|
@@ -1185,11 +1362,6 @@ function memorySink() {
|
|
|
1185
1362
|
}
|
|
1186
1363
|
|
|
1187
1364
|
// src/logger.ts
|
|
1188
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
1189
|
-
import { randomUUID } from "crypto";
|
|
1190
|
-
import {
|
|
1191
|
-
trace
|
|
1192
|
-
} from "@opentelemetry/api";
|
|
1193
1365
|
var local = new AsyncLocalStorage();
|
|
1194
1366
|
var config = null;
|
|
1195
1367
|
var sinks = [];
|
|
@@ -1237,6 +1409,7 @@ function emit(level, message, attributes, extra = {}, options = {}) {
|
|
|
1237
1409
|
const state = options.state !== void 0 ? options.state ?? void 0 : local.getStore();
|
|
1238
1410
|
if (state?.sealed) return;
|
|
1239
1411
|
const summary = extra["autter.event.type"] === "operation";
|
|
1412
|
+
if (!summary) recordLogBreadcrumb(level, message, attributes, state);
|
|
1240
1413
|
if (!summary && LEVEL_SEVERITY[level] < LEVEL_SEVERITY[config?.options?.minLevel ?? "debug"])
|
|
1241
1414
|
return;
|
|
1242
1415
|
if (!summary && state && config?.options?.inline !== false) {
|
|
@@ -1303,6 +1476,9 @@ function configureRuntimeLogger(next) {
|
|
|
1303
1476
|
}
|
|
1304
1477
|
}
|
|
1305
1478
|
}
|
|
1479
|
+
function loggerConfigured() {
|
|
1480
|
+
return config;
|
|
1481
|
+
}
|
|
1306
1482
|
function addRuntimeSink(sink) {
|
|
1307
1483
|
extraSinks.add(sink);
|
|
1308
1484
|
return () => extraSinks.delete(sink);
|
|
@@ -1313,8 +1489,8 @@ function notifyException(error, attributes = {}) {
|
|
|
1313
1489
|
const record = {
|
|
1314
1490
|
time: Date.now(),
|
|
1315
1491
|
error,
|
|
1316
|
-
message: error
|
|
1317
|
-
type: error
|
|
1492
|
+
message: isError(error) ? error.message : String(error),
|
|
1493
|
+
type: isError(error) ? error.name : "Error",
|
|
1318
1494
|
attributes: { ...attributes, ...errorAttributes(error) },
|
|
1319
1495
|
...state ? { operationId: state.id } : {},
|
|
1320
1496
|
...state?.requestId ? { requestId: state.requestId } : {}
|
|
@@ -1367,11 +1543,11 @@ function createRuntimeLogger(attributes = {}) {
|
|
|
1367
1543
|
warn: (message, extra) => emit("warning", message, merge(attrs, extra)),
|
|
1368
1544
|
error: (error, extra) => emit(
|
|
1369
1545
|
"error",
|
|
1370
|
-
error
|
|
1546
|
+
isError(error) ? error.message : String(error),
|
|
1371
1547
|
{
|
|
1372
1548
|
...attrs,
|
|
1373
1549
|
...extra,
|
|
1374
|
-
...error
|
|
1550
|
+
...isError(error) ? {
|
|
1375
1551
|
"exception.type": error.name,
|
|
1376
1552
|
"exception.stacktrace": error.stack ?? ""
|
|
1377
1553
|
} : {}
|
|
@@ -1382,16 +1558,16 @@ function createRuntimeLogger(attributes = {}) {
|
|
|
1382
1558
|
}
|
|
1383
1559
|
var runtimeLogger = createRuntimeLogger();
|
|
1384
1560
|
function captureExceptionAsLog(error, attributes = {}) {
|
|
1385
|
-
const
|
|
1386
|
-
const message =
|
|
1387
|
-
const stack =
|
|
1561
|
+
const isErr = isError(error);
|
|
1562
|
+
const message = isErr ? error.message : String(error);
|
|
1563
|
+
const stack = isErr && error.stack ? error.stack : new Error().stack?.split("\n").filter((line, i) => i === 0 || !/captureException/.test(line)).join("\n") ?? "";
|
|
1388
1564
|
noteOperationError(error);
|
|
1389
1565
|
emit(
|
|
1390
1566
|
"error",
|
|
1391
1567
|
message,
|
|
1392
1568
|
{
|
|
1393
1569
|
...attributes,
|
|
1394
|
-
"exception.type":
|
|
1570
|
+
"exception.type": isErr ? error.name : "Error",
|
|
1395
1571
|
"exception.message": message,
|
|
1396
1572
|
"exception.stacktrace": stack
|
|
1397
1573
|
},
|
|
@@ -1486,7 +1662,7 @@ function emitSummary(state, options = {}) {
|
|
|
1486
1662
|
state.message ?? `${state.name}: ${state.outcome}`,
|
|
1487
1663
|
{
|
|
1488
1664
|
...operationAttributes(state),
|
|
1489
|
-
...err
|
|
1665
|
+
...isError(err) ? {
|
|
1490
1666
|
"exception.type": err.name,
|
|
1491
1667
|
"exception.stacktrace": err.stack ?? ""
|
|
1492
1668
|
} : {}
|
|
@@ -1521,6 +1697,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
|
|
|
1521
1697
|
...parent?.requestId ? { requestId: parent.requestId } : {}
|
|
1522
1698
|
}
|
|
1523
1699
|
);
|
|
1700
|
+
if (linked && parent) state.breadcrumbs = parent.breadcrumbs ??= new BreadcrumbTrail();
|
|
1524
1701
|
const operation = operationHandle(state);
|
|
1525
1702
|
const run = () => local.run(state, async () => {
|
|
1526
1703
|
let thrown;
|
|
@@ -1530,6 +1707,7 @@ function startOperation(name, fn, attributes, options, internal = {}) {
|
|
|
1530
1707
|
state.outcome = "failed";
|
|
1531
1708
|
thrown = error;
|
|
1532
1709
|
noteOperationError(error, true, state);
|
|
1710
|
+
rememberThrownBreadcrumbs(error, state);
|
|
1533
1711
|
config?.captureThrown?.(error);
|
|
1534
1712
|
throw error;
|
|
1535
1713
|
} finally {
|
|
@@ -1634,6 +1812,83 @@ var runtimeContext = {
|
|
|
1634
1812
|
}
|
|
1635
1813
|
};
|
|
1636
1814
|
|
|
1815
|
+
// src/breadcrumbs.ts
|
|
1816
|
+
var INVALID_TRACE_ID = "00000000000000000000000000000000";
|
|
1817
|
+
var MAX_TRACE_TRAILS = 200;
|
|
1818
|
+
var byTrace = /* @__PURE__ */ new Map();
|
|
1819
|
+
var processTrail = new BreadcrumbTrail();
|
|
1820
|
+
var thrownTrails = /* @__PURE__ */ new WeakMap();
|
|
1821
|
+
function redact2(attributes) {
|
|
1822
|
+
return loggerConfigured()?.redact(attributes) ?? redactAttributes(attributes);
|
|
1823
|
+
}
|
|
1824
|
+
function trailFor(create, state = local.getStore()) {
|
|
1825
|
+
if (state) {
|
|
1826
|
+
if (!state.breadcrumbs && create) state.breadcrumbs = new BreadcrumbTrail();
|
|
1827
|
+
return state.breadcrumbs;
|
|
1828
|
+
}
|
|
1829
|
+
const traceId = trace2.getActiveSpan()?.spanContext().traceId;
|
|
1830
|
+
if (traceId && traceId !== INVALID_TRACE_ID) {
|
|
1831
|
+
let trail = byTrace.get(traceId);
|
|
1832
|
+
if (!trail && create) {
|
|
1833
|
+
trail = new BreadcrumbTrail();
|
|
1834
|
+
byTrace.set(traceId, trail);
|
|
1835
|
+
if (byTrace.size > MAX_TRACE_TRAILS) {
|
|
1836
|
+
const oldest = byTrace.keys().next().value;
|
|
1837
|
+
if (oldest !== void 0) byTrace.delete(oldest);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
return trail;
|
|
1841
|
+
}
|
|
1842
|
+
return processTrail;
|
|
1843
|
+
}
|
|
1844
|
+
function addBreadcrumb(input) {
|
|
1845
|
+
try {
|
|
1846
|
+
const crumb = normalizeBreadcrumb(input, redact2);
|
|
1847
|
+
if (!crumb) return;
|
|
1848
|
+
trailFor(true)?.add(crumb);
|
|
1849
|
+
noteCompatFeature("breadcrumbs");
|
|
1850
|
+
} catch {
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
function recordLogBreadcrumb(level, message, attributes, state) {
|
|
1854
|
+
try {
|
|
1855
|
+
const data = {};
|
|
1856
|
+
let count = 0;
|
|
1857
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
1858
|
+
if (count >= 10) break;
|
|
1859
|
+
if (/^(?:exception|autter)\./.test(key)) continue;
|
|
1860
|
+
if (value === null || ["string", "number", "boolean"].includes(typeof value)) {
|
|
1861
|
+
data[key] = value;
|
|
1862
|
+
count++;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
const crumb = normalizeBreadcrumb({ type: "log", level, message, data }, redact2);
|
|
1866
|
+
if (crumb) trailFor(true, state)?.add(crumb);
|
|
1867
|
+
} catch {
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
function attributeBudget() {
|
|
1871
|
+
const limit = Number(
|
|
1872
|
+
process.env.OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT ?? process.env.OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT
|
|
1873
|
+
);
|
|
1874
|
+
return Number.isFinite(limit) && limit > 0 ? Math.min(BREADCRUMBS_ATTRIBUTE_MAX_CHARS, limit) : BREADCRUMBS_ATTRIBUTE_MAX_CHARS;
|
|
1875
|
+
}
|
|
1876
|
+
function rememberThrownBreadcrumbs(error, state) {
|
|
1877
|
+
if (state.breadcrumbs && error !== null && (typeof error === "object" || typeof error === "function"))
|
|
1878
|
+
thrownTrails.set(error, state.breadcrumbs);
|
|
1879
|
+
}
|
|
1880
|
+
function breadcrumbEventAttributes(scrub, error) {
|
|
1881
|
+
try {
|
|
1882
|
+
const thrown = error !== null && typeof error === "object" ? thrownTrails.get(error) : void 0;
|
|
1883
|
+
const trail = thrown ?? trailFor(false);
|
|
1884
|
+
if (!trail?.size) return {};
|
|
1885
|
+
const json = breadcrumbsAttribute(trail.list(), scrub, attributeBudget());
|
|
1886
|
+
return json ? { [BREADCRUMBS_ATTRIBUTE]: json } : {};
|
|
1887
|
+
} catch {
|
|
1888
|
+
return {};
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1637
1892
|
export {
|
|
1638
1893
|
redactAttributes,
|
|
1639
1894
|
redactText,
|
|
@@ -1641,6 +1896,7 @@ export {
|
|
|
1641
1896
|
CODE_PATTERN,
|
|
1642
1897
|
RuntimeError,
|
|
1643
1898
|
defineRuntimeErrors,
|
|
1899
|
+
isError,
|
|
1644
1900
|
isRuntimeErrorLike,
|
|
1645
1901
|
errorCodeOf,
|
|
1646
1902
|
errorAttributes,
|
|
@@ -1654,6 +1910,12 @@ export {
|
|
|
1654
1910
|
compileIgnore,
|
|
1655
1911
|
requestOutcome,
|
|
1656
1912
|
parseUserAgent,
|
|
1913
|
+
sanitizeBreadcrumbs,
|
|
1914
|
+
fingerprintValues,
|
|
1915
|
+
fingerprintEventAttributes,
|
|
1916
|
+
splitFingerprint,
|
|
1917
|
+
addBreadcrumb,
|
|
1918
|
+
breadcrumbEventAttributes,
|
|
1657
1919
|
otlpSink,
|
|
1658
1920
|
consoleSink,
|
|
1659
1921
|
fileSink,
|