@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 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), `onError`.
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 _chunkGYTQLITWcjs = require('./chunk-GYTQLITW.cjs');
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(_chunkGYTQLITWcjs.SDK_IDENTITY.name)) found.set(_chunkGYTQLITWcjs.SDK_IDENTITY.name, _chunkGYTQLITWcjs.SDK_IDENTITY.version);
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 _chunkGYTQLITWcjs.COMPAT_MANIFEST.features.filter(
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 && _chunkGYTQLITWcjs.versionAtLeast.call(void 0, sdk.version, min) === true;
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 _chunkGYTQLITWcjs.compatRequest.call(void 0, "POST", `${endpoint}/v1/traces`, {
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([_chunkGYTQLITWcjs.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))
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 _chunkGYTQLITWcjs.fetchIngesterCompat.call(void 0, endpoint, { probeRoutes: routes, unref: false, timeoutMs });
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
- _chunkGYTQLITWcjs.evaluateCompat.call(void 0, { features, ingester: info }).forEach(add);
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
- _chunkGYTQLITWcjs.evaluateCompat.call(void 0, { features: options.features, ingester: null, sdk }).forEach(add);
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 = _chunkGYTQLITWcjs.COMPAT_MANIFEST.features.find((f) => f.id === id);
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 (${_chunkGYTQLITWcjs.COMPAT_MANIFEST.features.map((f) => f.id).join(", ")})
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.7.0",
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"