@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/README.md
CHANGED
|
@@ -127,7 +127,39 @@ export const POST = createBrowserRelayFetchHandler({
|
|
|
127
127
|
```
|
|
128
128
|
|
|
129
129
|
Options: `endpoint` (default `https://otlp.autter.dev`), `maxBodyBytes`
|
|
130
|
-
(default 64 KB), `
|
|
130
|
+
(default 64 KB), `perIpRateLimit` (default 120 requests a minute per client;
|
|
131
|
+
`false` when a WAF already limits the route), `trustProxy`, `redact`,
|
|
132
|
+
`compatCheck`, `onError`.
|
|
133
|
+
|
|
134
|
+
**Rate-limit key and `trustProxy`.** The relay route is public, so it limits
|
|
135
|
+
each visitor. Any caller can write anything into `X-Forwarded-For`, so by
|
|
136
|
+
default (`trustProxy: "auto"`) the relay never takes that header's first entry.
|
|
137
|
+
Proxies append the address they saw to the right of the header, or overwrite
|
|
138
|
+
it, so the relay reads it right to left and takes the first public address,
|
|
139
|
+
skipping hops on your own network. That works behind load balancers, CDNs and
|
|
140
|
+
hosting platforms with no setup:
|
|
141
|
+
|
|
142
|
+
| Where the relay runs | What `"auto"` keys |
|
|
143
|
+
| --- | --- |
|
|
144
|
+
| Node, reached directly | The connection's own address; the header is ignored |
|
|
145
|
+
| Node behind a load balancer or ingress on your network | The address that proxy appended |
|
|
146
|
+
| Next.js route handlers, Vercel, Netlify, Deno Deploy and other fetch runtimes | The rightmost public entry, which the platform's edge wrote |
|
|
147
|
+
| Cloudflare Workers | `CF-Connecting-IP` |
|
|
148
|
+
|
|
149
|
+
Other settings:
|
|
150
|
+
|
|
151
|
+
- `trustProxy: 1` (or any count) when exactly that many proxies you control
|
|
152
|
+
sit in front, such as a Node origin behind Cloudflare with no load balancer.
|
|
153
|
+
- `trustProxy: true` when every proxy in front strips client values: the
|
|
154
|
+
leftmost entry.
|
|
155
|
+
- `trustProxy: false` never reads the header: the socket address in Node, one
|
|
156
|
+
shared bucket in fetch runtimes.
|
|
157
|
+
- `clientIp: (request) => string` to supply the address yourself, for example
|
|
158
|
+
Deno's `info.remoteAddr` or Bun's `server.requestIP`.
|
|
159
|
+
|
|
160
|
+
Strings from environment variables work too (`"auto"`, `"false"`, `"2"`).
|
|
161
|
+
A fetch handler exposed straight to the internet with no proxy in front can
|
|
162
|
+
still be fooled by a forged header; set `trustProxy: false` or `clientIp` there.
|
|
131
163
|
|
|
132
164
|
## 2. Server tracker
|
|
133
165
|
|
|
@@ -242,6 +274,76 @@ when the error was already reported). Errors from another JavaScript realm
|
|
|
242
274
|
message and stack. `autterErrorResponse()` reports the same way and also
|
|
243
275
|
answers the request.
|
|
244
276
|
|
|
277
|
+
### Sample and ignore errors (1.8.0)
|
|
278
|
+
|
|
279
|
+
```ts
|
|
280
|
+
initAutterServer({
|
|
281
|
+
...,
|
|
282
|
+
sampleRate: 0.5, // half of captured errors
|
|
283
|
+
ignoreErrors: ["ECONNRESET", /^AbortError$/, /^billing\.declined/],
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- `sampleRate` (0..1, default 1): the share of captured errors reported:
|
|
288
|
+
`captureException`, error-level `captureMessage` and `reportOutcome`.
|
|
289
|
+
Never a fatal message, a crash (uncaught exception) or an unhandled
|
|
290
|
+
rejection (which may be one); warnings and info are never sampled. Not
|
|
291
|
+
`traceSampleRate`, which samples healthy traces.
|
|
292
|
+
- `ignoreErrors`: never reported. A string matches when the error's type
|
|
293
|
+
(`error.name`) or message contains it (case-sensitive); a RegExp is tested
|
|
294
|
+
against the type, the message and `"<type>: <message>"`. Every capture
|
|
295
|
+
path, crashes included.
|
|
296
|
+
- A value filtered out is marked captured, so a later hook (error
|
|
297
|
+
middleware, `onRequestError`) does not report it either. A request that
|
|
298
|
+
failed still exports its failed span, which the ingester turns into a
|
|
299
|
+
stackless `HttpServerError` for a 5xx; leave those out with a
|
|
300
|
+
suppression rule.
|
|
301
|
+
- `initAutterLogging` takes the same two options; `registerAutter`
|
|
302
|
+
(`@autter/runtime-next`) passes them through.
|
|
303
|
+
|
|
304
|
+
What the SDK filters never leaves the process and is counted nowhere. For
|
|
305
|
+
volume control that is counted, applies to every SDK and service at once and
|
|
306
|
+
is changed without a deploy, use **suppression rules** in Autter's Runtime
|
|
307
|
+
settings (drop or sample by error type, message, service, environment,
|
|
308
|
+
route, release or source; ingester 1.9.0). The ingester also collapses a
|
|
309
|
+
looping error into one sample a minute with an exact count.
|
|
310
|
+
|
|
311
|
+
### Caught errors and deliberate fallbacks
|
|
312
|
+
|
|
313
|
+
Errors you catch and log count on their own — no `captureException` needed.
|
|
314
|
+
An `Error` passed to `console.error`, pino, winston, bunyan or
|
|
315
|
+
`runtimeLogger.error` becomes an occurrence at `error` severity, with the log
|
|
316
|
+
line as context and the error's stack, marked
|
|
317
|
+
`autter.handling: "caught"`. A log line with no error object is untouched, and
|
|
318
|
+
an error you also pass to `captureException` in the same tick is reported
|
|
319
|
+
once. A caught error never fails the request it happened in, and handled
|
|
320
|
+
occurrences stay out of your error rate — they are issues to see, not
|
|
321
|
+
failures. A burst limit per error kind keeps ten at once, then one every ten
|
|
322
|
+
seconds; the ones left out are counted.
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
initAutterServer({ ... }); // on by default
|
|
326
|
+
initAutterServer({ ..., captureLoggedErrors: false }); // off
|
|
327
|
+
initAutterServer({ ..., captureLoggedErrors: { console: false } }); // keep loggers, not console
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
When you mean to ignore an error — a fallback you chose — say so in one line:
|
|
331
|
+
|
|
332
|
+
```ts
|
|
333
|
+
try {
|
|
334
|
+
prices = await pricingService.get();
|
|
335
|
+
} catch (err) {
|
|
336
|
+
runtimeContext.degraded(err, "pricing service down, using cached prices");
|
|
337
|
+
prices = cached;
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`runtimeContext.degraded(err, reason)` records the error at `warning`
|
|
342
|
+
severity, marked `autter.handling: "deliberate"` with the reason (grouped by
|
|
343
|
+
its code when the error is coded), and never pages anyone. The current
|
|
344
|
+
request or operation ends `degraded` unless it already failed or set its own
|
|
345
|
+
outcome — so a crash, a caught error and a deliberate fallback are told apart.
|
|
346
|
+
|
|
245
347
|
### Unhandled promise rejections
|
|
246
348
|
|
|
247
349
|
Recorded as errors (severity `error`, `autter.unhandled_rejection: true`, not
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunkBHYHORUDcjs = require('./chunk-BHYHORUD.cjs');
|
|
9
9
|
|
|
10
10
|
// src/doctor.ts
|
|
11
11
|
var _fs = require('fs');
|
|
@@ -30,19 +30,19 @@ function installedSdks(cwd) {
|
|
|
30
30
|
}
|
|
31
31
|
if (_path.dirname.call(void 0, dir) === dir) break;
|
|
32
32
|
}
|
|
33
|
-
if (!found.has(
|
|
33
|
+
if (!found.has(_chunkBHYHORUDcjs.SDK_IDENTITY.name)) found.set(_chunkBHYHORUDcjs.SDK_IDENTITY.name, _chunkBHYHORUDcjs.SDK_IDENTITY.version);
|
|
34
34
|
return [...found].map(([name, version]) => ({ name, version }));
|
|
35
35
|
}
|
|
36
36
|
function featuresInUse(sdks) {
|
|
37
|
-
return
|
|
37
|
+
return _chunkBHYHORUDcjs.COMPAT_MANIFEST.features.filter(
|
|
38
38
|
(feature) => sdks.some((sdk) => {
|
|
39
39
|
const min = feature.sdks[sdk.name];
|
|
40
|
-
return min !== void 0 &&
|
|
40
|
+
return min !== void 0 && _chunkBHYHORUDcjs.versionAtLeast.call(void 0, sdk.version, min) === true;
|
|
41
41
|
})
|
|
42
42
|
).map((feature) => feature.id);
|
|
43
43
|
}
|
|
44
44
|
async function checkKey(endpoint, key, timeoutMs) {
|
|
45
|
-
const res = await
|
|
45
|
+
const res = await _chunkBHYHORUDcjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/traces`, {
|
|
46
46
|
body: JSON.stringify({ resourceSpans: [] }),
|
|
47
47
|
headers: { authorization: `Bearer ${key}` },
|
|
48
48
|
unref: false,
|
|
@@ -62,10 +62,10 @@ async function runDoctor(options = {}) {
|
|
|
62
62
|
const features = _optionalChain([options, 'access', _ => _.features, 'optionalAccess', _2 => _2.length]) ? options.features : featuresInUse(sdks);
|
|
63
63
|
const routes = [
|
|
64
64
|
...new Set(
|
|
65
|
-
features.map((id) => _optionalChain([
|
|
65
|
+
features.map((id) => _optionalChain([_chunkBHYHORUDcjs.COMPAT_MANIFEST, 'access', _3 => _3.features, 'access', _4 => _4.find, 'call', _5 => _5((f) => f.id === id), 'optionalAccess', _6 => _6.route])).filter((route) => Boolean(route))
|
|
66
66
|
)
|
|
67
67
|
];
|
|
68
|
-
const { info, error } = await
|
|
68
|
+
const { info, error } = await _chunkBHYHORUDcjs.fetchIngesterCompat.call(void 0, endpoint, { probeRoutes: routes, unref: false, timeoutMs });
|
|
69
69
|
const issues = [];
|
|
70
70
|
const seen = /* @__PURE__ */ new Set();
|
|
71
71
|
const add = (issue) => {
|
|
@@ -75,15 +75,15 @@ async function runDoctor(options = {}) {
|
|
|
75
75
|
issues.push(issue);
|
|
76
76
|
}
|
|
77
77
|
};
|
|
78
|
-
|
|
78
|
+
_chunkBHYHORUDcjs.evaluateCompat.call(void 0, { features, ingester: info }).forEach(add);
|
|
79
79
|
for (const sdk of sdks) {
|
|
80
80
|
if (_optionalChain([options, 'access', _7 => _7.features, 'optionalAccess', _8 => _8.length])) {
|
|
81
|
-
|
|
81
|
+
_chunkBHYHORUDcjs.evaluateCompat.call(void 0, { features: options.features, ingester: null, sdk }).forEach(add);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
const key = options.key ? await checkKey(endpoint, options.key, timeoutMs) : void 0;
|
|
85
85
|
const featureRows = features.flatMap((id) => {
|
|
86
|
-
const feature =
|
|
86
|
+
const feature = _chunkBHYHORUDcjs.COMPAT_MANIFEST.features.find((f) => f.id === id);
|
|
87
87
|
if (!feature) return [];
|
|
88
88
|
const bad = issues.some((issue) => issue.feature === id);
|
|
89
89
|
const known = _optionalChain([info, 'optionalAccess', _9 => _9.version]) != null || feature.route !== void 0 && _optionalChain([info, 'optionalAccess', _10 => _10.routes, 'optionalAccess', _11 => _11[feature.route]]) !== void 0;
|
|
@@ -153,7 +153,7 @@ Checks that your Autter SDKs, ingester and ClickHouse schema versions match.
|
|
|
153
153
|
Options:
|
|
154
154
|
--endpoint <url> Ingester base URL (default: $AUTTER_ENDPOINT, $OTEL_EXPORTER_OTLP_ENDPOINT, or ${DEFAULT_ENDPOINT})
|
|
155
155
|
--key <key> Also verify an ingest key (default: $AUTTER_RUNTIME_KEY)
|
|
156
|
-
--features <a,b> Only check these features (${
|
|
156
|
+
--features <a,b> Only check these features (${_chunkBHYHORUDcjs.COMPAT_MANIFEST.features.map((f) => f.id).join(", ")})
|
|
157
157
|
--json Machine-readable output
|
|
158
158
|
--timeout <ms> Per-request timeout (default 5000)
|
|
159
159
|
|
|
@@ -156,6 +156,57 @@ var compat_manifest_default = {
|
|
|
156
156
|
ingester: "1.8.0",
|
|
157
157
|
migrations: ["0020-occurrence-frame-resolution"],
|
|
158
158
|
sdks: {}
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "ingest_suppression",
|
|
162
|
+
title: "Suppression rules: drop or sample matching errors at ingest",
|
|
163
|
+
ingester: "1.9.0",
|
|
164
|
+
migrations: [],
|
|
165
|
+
sdks: {}
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: "loop_collapse",
|
|
169
|
+
title: "Loop collapse: a looping error stored as one sample a minute with an exact count",
|
|
170
|
+
ingester: "1.9.0",
|
|
171
|
+
migrations: ["0021-occurrence-collapsed-count"],
|
|
172
|
+
sdks: {}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "edge_request_counts",
|
|
176
|
+
title: "Edge requests counted from their request summaries",
|
|
177
|
+
ingester: "1.10.0",
|
|
178
|
+
migrations: [],
|
|
179
|
+
route: "/v1/logs",
|
|
180
|
+
sdks: { "@autter/runtime-edge": "1.3.0" }
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
id: "caught_errors",
|
|
184
|
+
title: "Caught errors and deliberate fallbacks recorded as handled occurrences",
|
|
185
|
+
ingester: "1.11.0",
|
|
186
|
+
migrations: [],
|
|
187
|
+
sdks: { "@autter/runtime-node": "1.9.0", "@autter/runtime-edge": "1.4.0", "@autter/runtime-next": "1.9.0", "@autter/runtime-browser": "1.8.0" }
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "route_percentiles",
|
|
191
|
+
title: "Per-route p95 and p99 latency in the 1-minute rollups",
|
|
192
|
+
ingester: "1.11.0",
|
|
193
|
+
migrations: ["0022-route-latency-1m"],
|
|
194
|
+
sdks: {}
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
id: "server_source_maps",
|
|
198
|
+
title: "Server bundles symbolicated, with source context lines",
|
|
199
|
+
ingester: "1.11.0",
|
|
200
|
+
migrations: [],
|
|
201
|
+
route: "/v1/sourcemaps",
|
|
202
|
+
sdks: { "@autter/runtime-cli": "1.3.0", "@autter/runtime-node": "1.9.0", "@autter/runtime-edge": "1.4.0", "@autter/runtime-next": "1.9.0" }
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
id: "cause_fingerprint",
|
|
206
|
+
title: "Wrapped errors grouped with their root cause (cause-v1)",
|
|
207
|
+
ingester: "1.11.0",
|
|
208
|
+
migrations: [],
|
|
209
|
+
sdks: { "@autter/runtime-node": "1.9.0", "@autter/runtime-edge": "1.4.0", "@autter/runtime-next": "1.9.0" }
|
|
159
210
|
}
|
|
160
211
|
]
|
|
161
212
|
};
|
|
@@ -501,7 +552,7 @@ var _https = require('https');
|
|
|
501
552
|
// package.json
|
|
502
553
|
var package_default = {
|
|
503
554
|
name: "@autter/runtime-node",
|
|
504
|
-
version: "1.
|
|
555
|
+
version: "1.9.0",
|
|
505
556
|
description: "Autter Runtime for Node.js: request wide events, coded errors, operation logging, same-origin browser relay and a curated OpenTelemetry server tracker",
|
|
506
557
|
license: "MIT",
|
|
507
558
|
type: "module",
|
|
@@ -546,16 +597,20 @@ var package_default = {
|
|
|
546
597
|
"@opentelemetry/sdk-metrics": "^1.30.1",
|
|
547
598
|
"@opentelemetry/sdk-node": "^0.57.1",
|
|
548
599
|
"@opentelemetry/sdk-trace-base": "^1.30.1",
|
|
549
|
-
"@opentelemetry/semantic-conventions": "^1.28.0"
|
|
600
|
+
"@opentelemetry/semantic-conventions": "^1.28.0",
|
|
601
|
+
"require-in-the-middle": "^7.5.2"
|
|
550
602
|
},
|
|
551
603
|
devDependencies: {
|
|
552
604
|
"@autter/runtime-core": "0.0.0",
|
|
553
605
|
"@types/node": "^22.10.0",
|
|
606
|
+
bunyan: "^1.8.15",
|
|
554
607
|
express: "^4.21.2",
|
|
555
608
|
fastify: "^5.12.5",
|
|
556
609
|
koa: "^3.2.1",
|
|
610
|
+
pino: "^10.4.0",
|
|
557
611
|
tsup: "^8.3.5",
|
|
558
|
-
typescript: "^5.7.0"
|
|
612
|
+
typescript: "^5.7.0",
|
|
613
|
+
winston: "^3.19.0"
|
|
559
614
|
},
|
|
560
615
|
engines: {
|
|
561
616
|
node: ">=20"
|