@autter/runtime-node 1.0.0 → 1.2.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 +188 -1
- package/dist/index.cjs +1126 -28
- package/dist/index.d.cts +283 -2
- package/dist/index.d.ts +283 -2
- package/dist/index.js +1133 -30
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -61,6 +61,14 @@ autter.captureException(err, { "order.id": "…" });
|
|
|
61
61
|
// errors, just a lower severity ("fatal" | "error" | "warning" | "info"):
|
|
62
62
|
autter.captureMessage("Legacy /orders lookup used", "warning");
|
|
63
63
|
|
|
64
|
+
// named process spans — background jobs, queue consumers, cron ticks,
|
|
65
|
+
// DB-heavy calls. Always recorded (never head-sampled), so Autter's
|
|
66
|
+
// slow-process monitor sees accurate run counts and durations, and can
|
|
67
|
+
// flag the process when it is slow and repeating a lot:
|
|
68
|
+
await autter.withProcessSpan("invoice.rebuild", async () => {
|
|
69
|
+
await rebuildInvoices();
|
|
70
|
+
});
|
|
71
|
+
|
|
64
72
|
// graceful shutdown flushes exporters:
|
|
65
73
|
await autter.shutdown();
|
|
66
74
|
```
|
|
@@ -75,9 +83,24 @@ Defaults (cheap by construction):
|
|
|
75
83
|
| Signal | Default |
|
|
76
84
|
| --- | --- |
|
|
77
85
|
| Captured/unhandled exceptions | 100% (dedicated always-on tracer) |
|
|
78
|
-
| Traces |
|
|
86
|
+
| Traces containing an error | 100% (tail retention, `retainTracesOnError`) |
|
|
87
|
+
| `withProcessSpan` spans | 100% (same always-on tracer) |
|
|
88
|
+
| LLM/GenAI call spans | 100% (`llmTracing`, on by default) |
|
|
89
|
+
| Healthy traces | 1% head sampling (`traceSampleRate`) |
|
|
79
90
|
| Request metrics | exported every 60 s |
|
|
80
91
|
| Logs | not collected |
|
|
92
|
+
| PII in custom attributes | redacted before export (`redactAttributes`) |
|
|
93
|
+
| Forgotten `shutdown()` | exporters still flushed on exit (`autoFlush`) |
|
|
94
|
+
|
|
95
|
+
**Error-linked trace retention.** Errors export at 100% while traces are
|
|
96
|
+
head-sampled — on its own that strands a retained error without the trace
|
|
97
|
+
that explains it. So unsampled spans are kept briefly in an in-process
|
|
98
|
+
buffer, and the moment a trace shows an error — a 5xx response, a recorded
|
|
99
|
+
exception, `captureException`, or an error-severity `captureMessage` — the
|
|
100
|
+
whole trace is exported, sampling lottery notwithstanding. The buffer is
|
|
101
|
+
bounded (256 spans per trace, 5 000 spans total, dropped as soon as the
|
|
102
|
+
request ends healthy, 30 s TTL), degrades to plain head sampling on
|
|
103
|
+
overflow, and never blocks. Disable with `retainTracesOnError: false`.
|
|
81
104
|
|
|
82
105
|
Crashes are observed via `process.uncaughtExceptionMonitor`, which does
|
|
83
106
|
**not** change your process's exit behaviour; the final flush is
|
|
@@ -88,7 +111,171 @@ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
|
|
88
111
|
initAutterServer({ ..., instrumentations: [new ExpressInstrumentation()] });
|
|
89
112
|
```
|
|
90
113
|
|
|
114
|
+
### Lifecycle: never lose telemetry to a forgotten shutdown()
|
|
115
|
+
|
|
116
|
+
Telemetry is batched (errors every ~2 s, healthy traces every ~5 s, metrics
|
|
117
|
+
every 60 s) — so exiting without flushing loses whatever is still buffered.
|
|
118
|
+
`initAutterServer` therefore installs an exit flush **by default**: on
|
|
119
|
+
`beforeExit`, SIGINT, and SIGTERM it force-flushes every exporter, then lets
|
|
120
|
+
your process die as it would have (conventional 130/143 codes). If your own
|
|
121
|
+
code also handles those signals, Autter only flushes alongside it and never
|
|
122
|
+
touches your exit path. Opt out with `autoFlush: false`.
|
|
123
|
+
|
|
124
|
+
Prefer explicit control? Do the same yourself and get a handle back:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { installAutterAutoFlush } from "@autter/runtime-node";
|
|
128
|
+
|
|
129
|
+
const handle = installAutterAutoFlush(); // uses the active server's exporters
|
|
130
|
+
// …later: handle.flush("deploy-drain") or handle.dispose()
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
If the process still exits with captures that were never confirmed
|
|
134
|
+
exported (e.g. a flush timed out), you get a one-line stderr warning — not
|
|
135
|
+
silent loss. While wiring things up, set `debug: true` (or `AUTTER_DEBUG=1`)
|
|
136
|
+
to see `[autter] exported N span(s)` lines on stderr as batches leave.
|
|
137
|
+
|
|
91
138
|
Note on usage rollups: requests are counted from the `http.server.duration`
|
|
92
139
|
metric (100% accurate) and additionally from sampled server spans. At the
|
|
93
140
|
default 1% sampling the span contribution is negligible; if you set
|
|
94
141
|
`traceSampleRate: 1` in development, expect request counts roughly doubled.
|
|
142
|
+
Tail-retained error traces don't distort this: their spans carry
|
|
143
|
+
`autter.tail_retained` and the ingester keeps them out of span-fed rollups.
|
|
144
|
+
|
|
145
|
+
### Privacy: custom attributes are redacted before they leave the process
|
|
146
|
+
|
|
147
|
+
The browser relay whitelist-sanitises everything a client posts — but the
|
|
148
|
+
server tracker accepts free-form attributes from *your* code, where a stray
|
|
149
|
+
`captureException(err, { "user.email": … })` would otherwise go out
|
|
150
|
+
verbatim. Custom attributes are therefore scrubbed at capture time by
|
|
151
|
+
default:
|
|
152
|
+
|
|
153
|
+
- values that look like emails, JWTs, `sk-…`/`ghp_…`/AWS/Slack tokens,
|
|
154
|
+
`Bearer …` headers, or `scheme://user:pass@host` URLs are masked;
|
|
155
|
+
- attributes whose **key** looks sensitive (`password`, `token`, `secret`,
|
|
156
|
+
`api_key`, `authorization`, `cookie`, `ssn`, `card_number`, …) are masked
|
|
157
|
+
wholesale;
|
|
158
|
+
- non-sensitive keys and primitives pass through untouched, so grouping and
|
|
159
|
+
dashboards keep working.
|
|
160
|
+
|
|
161
|
+
Disable or extend it per service:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
initAutterServer({
|
|
165
|
+
...,
|
|
166
|
+
// redactAttributes: false, // opt out entirely
|
|
167
|
+
redactAttributes: {
|
|
168
|
+
additionalKeyPatterns: ["employee_id"],
|
|
169
|
+
additionalValuePatterns: [/^ACC-\d+$/],
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Libraries that must never forward PII regardless of host configuration can
|
|
175
|
+
wrap once:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { makeSafeCapture } from "@autter/runtime-node";
|
|
179
|
+
const safe = makeSafeCapture();
|
|
180
|
+
safe.captureException(err, { "user.email": email }); // masked before export
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
The raw primitive is exported too (`redactAttributes(attrs, options)`).
|
|
184
|
+
This closes the server-side gap to match the browser relay's payload
|
|
185
|
+
whitelist; it is best-effort scrubbing of obvious PII shapes, not a DLP
|
|
186
|
+
engine — keep secrets out of attributes in the first place.
|
|
187
|
+
|
|
188
|
+
## 3. LLM tracing
|
|
189
|
+
|
|
190
|
+
`initAutterServer` initialises LLM tracing automatically: any GenAI span —
|
|
191
|
+
`gen_ai.*` semconv attributes or the Vercel AI SDK's `ai.*` spans — bypasses
|
|
192
|
+
head sampling, so **every** model call is recorded with model, tokens,
|
|
193
|
+
latency, and a USD cost. Opt out with `llmTracing: false`.
|
|
194
|
+
|
|
195
|
+
**Easiest: wrap the client once** — works with the OpenAI, Anthropic, and
|
|
196
|
+
Google GenAI SDKs (or anything with the same call shapes), streaming
|
|
197
|
+
included; every call through it is traced with no per-call code:
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
import { instrumentLlmClient } from "@autter/runtime-node";
|
|
201
|
+
|
|
202
|
+
const openai = instrumentLlmClient(new OpenAI());
|
|
203
|
+
// use it exactly as before — chat, embeddings, streams are all recorded
|
|
204
|
+
const out = await openai.chat.completions.create({ model: "gpt-5-mini", ... });
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Provider is detected from the client (override with
|
|
208
|
+
`{ provider, userId, attributes }` as the second argument). For streamed
|
|
209
|
+
responses the span closes when the stream is consumed; OpenAI streams only
|
|
210
|
+
report token usage when you pass
|
|
211
|
+
`stream_options: { include_usage: true }`.
|
|
212
|
+
|
|
213
|
+
**Vercel AI SDK** — just turn on its telemetry, nothing else:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
const { text } = await generateText({
|
|
217
|
+
model: openai("gpt-5-mini"),
|
|
218
|
+
prompt,
|
|
219
|
+
experimental_telemetry: { isEnabled: true, metadata: { userId: user.id } },
|
|
220
|
+
});
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**Manual control** (raw fetch, unusual clients) — wrap the call:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { withLlmCall } from "@autter/runtime-node";
|
|
227
|
+
|
|
228
|
+
const res = await withLlmCall(
|
|
229
|
+
{ provider: "openai", model: "gpt-5-mini", userId: user.id },
|
|
230
|
+
async (llm) => {
|
|
231
|
+
const out = await openai.chat.completions.create({ ... });
|
|
232
|
+
llm.setUsage({
|
|
233
|
+
inputTokens: out.usage?.prompt_tokens,
|
|
234
|
+
outputTokens: out.usage?.completion_tokens,
|
|
235
|
+
});
|
|
236
|
+
return out;
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Errors thrown inside are rethrown after marking the span failed — failing
|
|
242
|
+
model calls surface both as error issues and as `status: "error"` LLM calls.
|
|
243
|
+
Costs are estimated ingest-side from a built-in price table; report exact
|
|
244
|
+
figures with `llm.setCost(usd)` (the `autter.llm.cost_usd` attribute).
|
|
245
|
+
|
|
246
|
+
Where wrapping is awkward (queues, callbacks, batch results), report after
|
|
247
|
+
the fact with
|
|
248
|
+
`trackLlmCall({ provider, model, inputTokens, outputTokens, durationMs })`.
|
|
249
|
+
|
|
250
|
+
To verify the pipe end-to-end without calling a real model:
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
import { emitLlmSelftestTrace } from "@autter/runtime-node";
|
|
254
|
+
|
|
255
|
+
const { traceId } = await emitLlmSelftestTrace();
|
|
256
|
+
// one fake "autter-selftest" call is flushed to the ingester; look it up by
|
|
257
|
+
// traceId in the dashboard's LLM tab (or runtime_llm_calls when self-hosting)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## 4. Process spans (jobs, consumers, crons)
|
|
261
|
+
|
|
262
|
+
Non-HTTP work is only visible to the slow-process monitor where a span
|
|
263
|
+
exists — and regular traces are 1% sampled. `withProcessSpan` records a
|
|
264
|
+
span **always**:
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
import { withProcessSpan } from "@autter/runtime-node";
|
|
268
|
+
|
|
269
|
+
await withProcessSpan("invoice.rebuild", async () => {
|
|
270
|
+
await rebuildInvoices();
|
|
271
|
+
});
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Use stable, low-cardinality names; put ids in attributes
|
|
275
|
+
(`withProcessSpan("email.digest", fn, { "user.id": id })`).
|
|
276
|
+
|
|
277
|
+
Note on the slow-process monitor: Autter flags HTTP routes from the
|
|
278
|
+
unsampled request metrics, so route detection works out of the box. Non-HTTP
|
|
279
|
+
work is only visible where a span exists — relying on 1%-sampled regular
|
|
280
|
+
traces there would undercount ~100×, which is why these spans skip head
|
|
281
|
+
sampling.
|