@autter/runtime-node 1.4.0 → 1.5.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 CHANGED
@@ -5,6 +5,49 @@ 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
+
8
51
  # @autter/runtime-node
9
52
 
10
53
  The server tracker exports per-instance RSS, heap, memory limit, and GC
@@ -124,6 +167,35 @@ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
124
167
  initAutterServer({ ..., instrumentations: [new ExpressInstrumentation()] });
125
168
  ```
126
169
 
170
+ ### Version compatibility: checked for you
171
+
172
+ Some features need a minimum ingester version: operation logging needs
173
+ 1.4.0, memory metrics 1.3.3, endpoint latency 1.3.1. After
174
+ `initAutterServer`, the SDK checks the ingester once in the background
175
+ (`GET /v1/compat`, unref'd, 3 s timeout). For each feature in use that the
176
+ ingester can't store, it prints one warning:
177
+
178
+ ```text
179
+ [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
180
+ ```
181
+
182
+ The check never throws, never delays startup or exit, and is silent when
183
+ versions match or are unknown. `debug: true` logs the result. Turn it off
184
+ with `compatCheck: false` or `AUTTER_COMPAT_CHECK=0`. The browser relay does
185
+ the same for browser features such as CSP violation capture. The SDK reports
186
+ itself through the OTLP resource attributes `telemetry.distro.name` and
187
+ `telemetry.distro.version`, so the Autter dashboard shows the SDK version
188
+ each service runs.
189
+
190
+ For a full report (for example in CI or after a deploy):
191
+
192
+ ```bash
193
+ npx @autter/runtime-node doctor --endpoint https://ingest.example.com [--key "$AUTTER_RUNTIME_KEY"] [--json]
194
+ ```
195
+
196
+ Exit codes: 0 compatible, 1 mismatch or rejected key, 2 ingester unreachable.
197
+ See [docs/COMPATIBILITY.md](../../docs/COMPATIBILITY.md).
198
+
127
199
  ### Lifecycle: never lose telemetry to a forgotten shutdown()
128
200
 
129
201
  Telemetry is batched (errors every ~2 s, healthy traces every ~5 s, metrics
@@ -155,23 +227,39 @@ default 1% sampling the span contribution is negligible; if you set
155
227
  Tail-retained error traces don't distort this: their spans carry
156
228
  `autter.tail_retained` and the ingester keeps them out of span-fed rollups.
157
229
 
158
- ### Privacy: custom attributes are redacted before they leave the process
159
-
160
- The browser relay whitelist-sanitises everything a client posts — but the
161
- server tracker accepts free-form attributes from *your* code, where a stray
162
- `captureException(err, { "user.email": … })` would otherwise go out
163
- verbatim. Custom attributes are therefore scrubbed at capture time by
164
- default:
165
-
166
- - values that look like emails, JWTs, `sk-…`/`ghp_…`/AWS/Slack tokens,
167
- `Bearer …` headers, or `scheme://user:pass@host` URLs are masked;
230
+ ### Privacy: secrets and PII are scrubbed before they leave the process
231
+
232
+ Error context is where secrets leak: `connect ECONNREFUSED
233
+ postgres://admin:hunter2@db…` in an exception message, a provider error
234
+ echoing an API key, a `?token=` in a request URL, a stray
235
+ `captureException(err, { "user.email": … })`. Everything the SDK exports is
236
+ therefore scrubbed by default — custom attributes, exception messages and
237
+ stack traces, span status messages, LLM call attributes and errors,
238
+ structured logs, and the browser events the relay forwards. A final pass at
239
+ export time scrubs every span regardless of who created it (HTTP
240
+ instrumentation URLs, third-party instrumentations, `recordException` in
241
+ your own code).
242
+
243
+ - secrets inside any string are masked in place, keeping the rest of the
244
+ message/stack readable: JWTs, `Bearer`/`Basic` credentials,
245
+ `Authorization:`/`Cookie:`/`Set-Cookie:` header text,
246
+ `scheme://user:pass@host` connection strings (postgres, mysql,
247
+ mongodb+srv, redis, …), vendor keys (`sk-…`, `sk_live_…`, `ghp_…`,
248
+ `github_pat_…`, `xox?-…`, `AIza…`, `AKIA…`, …), PEM private keys,
249
+ `password=`/`?token=`/`?api_key=`-style assignments, emails, and
250
+ Luhn-valid card numbers;
168
251
  - attributes whose **key** looks sensitive (`password`, `token`, `secret`,
169
- `api_key`, `authorization`, `cookie`, `ssn`, `card_number`, …) are masked
170
- wholesale;
171
- - non-sensitive keys and primitives pass through untouched, so grouping and
172
- dashboards keep working.
252
+ `api_key`, `authorization`, `cookie`, `session`, `ssn`, `card_number`, …)
253
+ are masked wholesale, at any nesting depth;
254
+ - non-sensitive keys, numbers (including LLM token counts) and booleans pass
255
+ through untouched, so grouping, dashboards and cost tracking keep working.
256
+
257
+ The same patterns run in the browser SDK, the relay, the Python adapter,
258
+ and again in the ingester before storage (shared test vectors in
259
+ `test-vectors/redaction.json`).
173
260
 
174
- Disable or extend it per service:
261
+ Extend it (applies to messages, stacks and URLs too) or disable it per
262
+ service:
175
263
 
176
264
  ```ts
177
265
  initAutterServer({
@@ -193,7 +281,9 @@ const safe = makeSafeCapture();
193
281
  safe.captureException(err, { "user.email": email }); // masked before export
194
282
  ```
195
283
 
196
- The raw primitive is exported too (`redactAttributes(attrs, options)`).
284
+ The raw primitives are exported too (`redactAttributes(attrs, options)`,
285
+ `redactText(text, options)`). The browser relay takes the same options as
286
+ `createBrowserRelayHandler({ ..., redact })`.
197
287
  This closes the server-side gap to match the browser relay's payload
198
288
  whitelist; it is best-effort scrubbing of obvious PII shapes, not a DLP
199
289
  engine — keep secrets out of attributes in the first place.