@autter/runtime-node 1.4.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 +202 -17
- package/dist/chunk-ARN26OK6.cjs +810 -0
- package/dist/chunk-FBKYOHU2.js +1942 -0
- package/dist/chunk-OY2HTUWY.js +810 -0
- package/dist/chunk-UGNPCOIW.cjs +202 -0
- package/dist/chunk-UKYTXZOQ.cjs +1942 -0
- package/dist/chunk-Y4T7SQMO.js +202 -0
- package/dist/cli.cjs +60 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +60 -0
- package/dist/index.cjs +1558 -1140
- package/dist/index.d.cts +563 -44
- package/dist/index.d.ts +563 -44
- package/dist/index.js +1372 -919
- package/dist/sinks-CPGDyfU8.d.cts +341 -0
- package/dist/sinks-CPGDyfU8.d.ts +341 -0
- package/dist/testing.cjs +121 -0
- package/dist/testing.d.cts +57 -0
- package/dist/testing.d.ts +57 -0
- package/dist/testing.js +121 -0
- package/package.json +18 -5
package/README.md
CHANGED
|
@@ -5,6 +5,81 @@ application context to logs and completed operations. See the full
|
|
|
5
5
|
[operation logging guide](../../docs/OPERATION-LOGGING.md) for initialization,
|
|
6
6
|
steps, outcomes, redaction, export lifecycle, and release requirements.
|
|
7
7
|
|
|
8
|
+
# Requests and coded errors (1.5.0)
|
|
9
|
+
|
|
10
|
+
Requires ingester 1.5.0. One summary per request (always kept), request ids
|
|
11
|
+
echoed to clients, one issue per error code:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
autterRequests, autterErrorResponse, defineRuntimeErrors, runtimeContext,
|
|
16
|
+
} from "@autter/runtime-node";
|
|
17
|
+
|
|
18
|
+
export const billingErrors = defineRuntimeErrors("billing", {
|
|
19
|
+
declined: { status: 402, message: "Payment declined", expected: true,
|
|
20
|
+
why: "The card issuer rejected the charge", fix: "Ask for another card" },
|
|
21
|
+
limit: ({ plan }: { plan: string }) => ({ status: 429, message: `Plan ${plan} limit reached` }),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
app.use(autterRequests({ ignore: ["/healthz", "/metrics"] }));
|
|
25
|
+
app.post("/checkout", async (req, res) => {
|
|
26
|
+
runtimeContext.set({ cart: { items: req.body.items.length } });
|
|
27
|
+
runtimeContext.info("Applied coupon", { coupon: "SPRING" }); // folded into the summary
|
|
28
|
+
if (overLimit) throw billingErrors.limit({ plan: "free" }); // Express 5 (next(err) on 4)
|
|
29
|
+
res.json({ ok: true, requestId: runtimeContext.requestId });
|
|
30
|
+
});
|
|
31
|
+
app.use(autterErrorResponse()); // { error: { message, code, why?, fix?, link?, requestId } }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Also: `autterFastify`, `withRuntimeRequest` (fetch-style handlers),
|
|
35
|
+
`runtimeContext.fork` / `runInBackground` / `carrier()` for background work and
|
|
36
|
+
queues, the per-operation AI usage rollup, `waitUntil`, `initAutterLogging`
|
|
37
|
+
(no NodeSDK — for apps with their own OTel), enrichers (`enrichUserAgent`,
|
|
38
|
+
`enrichRequestSize`, `enrichEdgeGeo`, `enrichDeployment`), sinks (`otlpSink`,
|
|
39
|
+
`consoleSink`, `fileSink` → `.autter/runtime/*.jsonl` in development) and test
|
|
40
|
+
helpers:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { captureRuntime, expectOperation } from "@autter/runtime-node/testing";
|
|
44
|
+
const runtime = captureRuntime();
|
|
45
|
+
// … exercise the app …
|
|
46
|
+
expectOperation(runtime, "POST /checkout").toHaveOutcome("degraded").toHaveErrorCode("billing.declined");
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Full guide: [Requests, coded errors and background work](../../docs/REQUESTS-AND-ERRORS.md).
|
|
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
|
+
|
|
8
83
|
# @autter/runtime-node
|
|
9
84
|
|
|
10
85
|
The server tracker exports per-instance RSS, heap, memory limit, and GC
|
|
@@ -70,6 +145,12 @@ const autter = initAutterServer({
|
|
|
70
145
|
// handled errors — always recorded, never sampled out:
|
|
71
146
|
autter.captureException(err, { "order.id": "…" });
|
|
72
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
|
+
|
|
73
154
|
// warnings/info without an exception — same grouping+aggregation as
|
|
74
155
|
// errors, just a lower severity ("fatal" | "error" | "warning" | "info"):
|
|
75
156
|
autter.captureMessage("Legacy /orders lookup used", "warning");
|
|
@@ -99,7 +180,7 @@ Defaults (cheap by construction):
|
|
|
99
180
|
| Traces containing an error | 100% (tail retention, `retainTracesOnError`) |
|
|
100
181
|
| `withProcessSpan` spans | 100% (same always-on tracer) |
|
|
101
182
|
| LLM/GenAI call spans | 100% (`llmTracing`, on by default) |
|
|
102
|
-
| 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`) |
|
|
103
184
|
| Request metrics | exported every 60 s |
|
|
104
185
|
| Logs | not collected |
|
|
105
186
|
| PII in custom attributes | redacted before export (`redactAttributes`) |
|
|
@@ -124,6 +205,92 @@ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
|
|
124
205
|
initAutterServer({ ..., instrumentations: [new ExpressInstrumentation()] });
|
|
125
206
|
```
|
|
126
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
|
+
|
|
265
|
+
### Version compatibility: checked for you
|
|
266
|
+
|
|
267
|
+
Some features need a minimum ingester version: operation logging needs
|
|
268
|
+
1.4.0, memory metrics 1.3.3, endpoint latency 1.3.1. After
|
|
269
|
+
`initAutterServer`, the SDK checks the ingester once in the background
|
|
270
|
+
(`GET /v1/compat`, unref'd, 3 s timeout). For each feature in use that the
|
|
271
|
+
ingester can't store, it prints one warning:
|
|
272
|
+
|
|
273
|
+
```text
|
|
274
|
+
[autter-runtime] Operation logging needs ingester >= 1.4.0; yours is 1.3.4. Upgrade the ingester: docker pull ghcr.io/autter-dev/otlp-ingester:latest and restart it (ClickHouse migrations run at boot). See …/docs/COMPATIBILITY.md
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
The check never throws, never delays startup or exit, and is silent when
|
|
278
|
+
versions match or are unknown. `debug: true` logs the result. Turn it off
|
|
279
|
+
with `compatCheck: false` or `AUTTER_COMPAT_CHECK=0`. The browser relay does
|
|
280
|
+
the same for browser features such as CSP violation capture. The SDK reports
|
|
281
|
+
itself through the OTLP resource attributes `telemetry.distro.name` and
|
|
282
|
+
`telemetry.distro.version`, so the Autter dashboard shows the SDK version
|
|
283
|
+
each service runs.
|
|
284
|
+
|
|
285
|
+
For a full report (for example in CI or after a deploy):
|
|
286
|
+
|
|
287
|
+
```bash
|
|
288
|
+
npx @autter/runtime-node doctor --endpoint https://ingest.example.com [--key "$AUTTER_RUNTIME_KEY"] [--json]
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Exit codes: 0 compatible, 1 mismatch or rejected key, 2 ingester unreachable.
|
|
292
|
+
See [docs/COMPATIBILITY.md](../../docs/COMPATIBILITY.md).
|
|
293
|
+
|
|
127
294
|
### Lifecycle: never lose telemetry to a forgotten shutdown()
|
|
128
295
|
|
|
129
296
|
Telemetry is batched (errors every ~2 s, healthy traces every ~5 s, metrics
|
|
@@ -155,23 +322,39 @@ default 1% sampling the span contribution is negligible; if you set
|
|
|
155
322
|
Tail-retained error traces don't distort this: their spans carry
|
|
156
323
|
`autter.tail_retained` and the ingester keeps them out of span-fed rollups.
|
|
157
324
|
|
|
158
|
-
### Privacy:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
default
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
325
|
+
### Privacy: secrets and PII are scrubbed before they leave the process
|
|
326
|
+
|
|
327
|
+
Error context is where secrets leak: `connect ECONNREFUSED
|
|
328
|
+
postgres://admin:hunter2@db…` in an exception message, a provider error
|
|
329
|
+
echoing an API key, a `?token=` in a request URL, a stray
|
|
330
|
+
`captureException(err, { "user.email": … })`. Everything the SDK exports is
|
|
331
|
+
therefore scrubbed by default — custom attributes, exception messages and
|
|
332
|
+
stack traces, span status messages, LLM call attributes and errors,
|
|
333
|
+
structured logs, and the browser events the relay forwards. A final pass at
|
|
334
|
+
export time scrubs every span regardless of who created it (HTTP
|
|
335
|
+
instrumentation URLs, third-party instrumentations, `recordException` in
|
|
336
|
+
your own code).
|
|
337
|
+
|
|
338
|
+
- secrets inside any string are masked in place, keeping the rest of the
|
|
339
|
+
message/stack readable: JWTs, `Bearer`/`Basic` credentials,
|
|
340
|
+
`Authorization:`/`Cookie:`/`Set-Cookie:` header text,
|
|
341
|
+
`scheme://user:pass@host` connection strings (postgres, mysql,
|
|
342
|
+
mongodb+srv, redis, …), vendor keys (`sk-…`, `sk_live_…`, `ghp_…`,
|
|
343
|
+
`github_pat_…`, `xox?-…`, `AIza…`, `AKIA…`, …), PEM private keys,
|
|
344
|
+
`password=`/`?token=`/`?api_key=`-style assignments, emails, and
|
|
345
|
+
Luhn-valid card numbers;
|
|
168
346
|
- attributes whose **key** looks sensitive (`password`, `token`, `secret`,
|
|
169
|
-
`api_key`, `authorization`, `cookie`, `ssn`, `card_number`, …)
|
|
170
|
-
wholesale;
|
|
171
|
-
- non-sensitive keys
|
|
172
|
-
dashboards keep working.
|
|
347
|
+
`api_key`, `authorization`, `cookie`, `session`, `ssn`, `card_number`, …)
|
|
348
|
+
are masked wholesale, at any nesting depth;
|
|
349
|
+
- non-sensitive keys, numbers (including LLM token counts) and booleans pass
|
|
350
|
+
through untouched, so grouping, dashboards and cost tracking keep working.
|
|
351
|
+
|
|
352
|
+
The same patterns run in the browser SDK, the relay, the Python adapter,
|
|
353
|
+
and again in the ingester before storage (shared test vectors in
|
|
354
|
+
`test-vectors/redaction.json`).
|
|
173
355
|
|
|
174
|
-
|
|
356
|
+
Extend it (applies to messages, stacks and URLs too) or disable it per
|
|
357
|
+
service:
|
|
175
358
|
|
|
176
359
|
```ts
|
|
177
360
|
initAutterServer({
|
|
@@ -193,7 +376,9 @@ const safe = makeSafeCapture();
|
|
|
193
376
|
safe.captureException(err, { "user.email": email }); // masked before export
|
|
194
377
|
```
|
|
195
378
|
|
|
196
|
-
The raw
|
|
379
|
+
The raw primitives are exported too (`redactAttributes(attrs, options)`,
|
|
380
|
+
`redactText(text, options)`). The browser relay takes the same options as
|
|
381
|
+
`createBrowserRelayHandler({ ..., redact })`.
|
|
197
382
|
This closes the server-side gap to match the browser relay's payload
|
|
198
383
|
whitelist; it is best-effort scrubbing of obvious PII shapes, not a DLP
|
|
199
384
|
engine — keep secrets out of attributes in the first place.
|