@autter/runtime-node 1.5.0 → 1.6.1
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 +139 -1
- package/dist/{chunk-GI47WGRR.cjs → chunk-4MVWIRBI.cjs} +120 -39
- package/dist/{chunk-3TJHUCZ6.js → chunk-5KISVAVB.js} +326 -54
- package/dist/{chunk-L6SRL65J.cjs → chunk-7OTD2YPT.cjs} +11 -11
- package/dist/{chunk-L7HWKMCZ.js → chunk-HRDELHZP.js} +112 -31
- package/dist/{chunk-ZS4WQJQR.js → chunk-L5ENELB2.js} +1 -1
- package/dist/{chunk-P4VRTFAV.cjs → chunk-UXTIMS3G.cjs} +353 -81
- package/dist/cli.cjs +29 -9
- package/dist/cli.js +22 -2
- package/dist/index.cjs +668 -272
- package/dist/index.d.cts +160 -7
- package/dist/index.d.ts +160 -7
- package/dist/index.js +529 -133
- package/dist/{sinks-RFV76lF1.d.cts → sinks-CPGDyfU8.d.cts} +29 -1
- package/dist/{sinks-RFV76lF1.d.ts → sinks-CPGDyfU8.d.ts} +29 -1
- 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 +3 -1
package/README.md
CHANGED
|
@@ -48,6 +48,38 @@ expectOperation(runtime, "POST /checkout").toHaveOutcome("degraded").toHaveError
|
|
|
48
48
|
|
|
49
49
|
Full guide: [Requests, coded errors and background work](../../docs/REQUESTS-AND-ERRORS.md).
|
|
50
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
|
+
|
|
51
83
|
# @autter/runtime-node
|
|
52
84
|
|
|
53
85
|
The server tracker exports per-instance RSS, heap, memory limit, and GC
|
|
@@ -113,6 +145,12 @@ const autter = initAutterServer({
|
|
|
113
145
|
// handled errors — always recorded, never sampled out:
|
|
114
146
|
autter.captureException(err, { "order.id": "…" });
|
|
115
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
|
+
|
|
116
154
|
// warnings/info without an exception — same grouping+aggregation as
|
|
117
155
|
// errors, just a lower severity ("fatal" | "error" | "warning" | "info"):
|
|
118
156
|
autter.captureMessage("Legacy /orders lookup used", "warning");
|
|
@@ -142,10 +180,11 @@ Defaults (cheap by construction):
|
|
|
142
180
|
| Traces containing an error | 100% (tail retention, `retainTracesOnError`) |
|
|
143
181
|
| `withProcessSpan` spans | 100% (same always-on tracer) |
|
|
144
182
|
| LLM/GenAI call spans | 100% (`llmTracing`, on by default) |
|
|
145
|
-
| 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`) |
|
|
146
184
|
| Request metrics | exported every 60 s |
|
|
147
185
|
| Logs | not collected |
|
|
148
186
|
| PII in custom attributes | redacted before export (`redactAttributes`) |
|
|
187
|
+
| Query strings in span URLs | parameter names only (`keepQueryParams`) |
|
|
149
188
|
| Forgotten `shutdown()` | exporters still flushed on exit (`autoFlush`) |
|
|
150
189
|
|
|
151
190
|
**Error-linked trace retention.** Errors export at 100% while traces are
|
|
@@ -167,6 +206,63 @@ import { ExpressInstrumentation } from "@opentelemetry/instrumentation-express";
|
|
|
167
206
|
initAutterServer({ ..., instrumentations: [new ExpressInstrumentation()] });
|
|
168
207
|
```
|
|
169
208
|
|
|
209
|
+
### Server errors keep their stacks
|
|
210
|
+
|
|
211
|
+
When a handler throws, the framework answers 500 — and on its own that 500
|
|
212
|
+
is all the tracker sees: a stackless `HttpServerError` grouped by route. Add
|
|
213
|
+
your framework's hook and the thrown error is recorded, with its stack, on
|
|
214
|
+
the request span; the framework then carries on exactly as before.
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
import { autterErrorHandler, autterFastifyErrors, autterKoa, captureRequestError } from "@autter/runtime-node";
|
|
218
|
+
|
|
219
|
+
// Express / Connect: after your routes, before your own error handlers.
|
|
220
|
+
// Records the error, then calls next(err) unchanged.
|
|
221
|
+
app.use(autterErrorHandler());
|
|
222
|
+
|
|
223
|
+
// Fastify 4/5: an onError hook (Fastify's error handler still answers).
|
|
224
|
+
// autterFastify (request summaries) already includes it.
|
|
225
|
+
app.register(autterFastifyErrors); // or autterFastifyErrors(app)
|
|
226
|
+
|
|
227
|
+
// Koa 2/3: mount first; records and rethrows, so app.on("error") still fires.
|
|
228
|
+
app.use(autterKoa());
|
|
229
|
+
|
|
230
|
+
// Anything else (NestJS exception filters, hapi onPreResponse, …):
|
|
231
|
+
captureRequestError(err, { "app.handler": "checkout" });
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
All three report errors with status ≥ 500 (or none), RuntimeErrors and coded
|
|
235
|
+
errors — `ctx.throw(404)` and validation failures stay out of your issues.
|
|
236
|
+
Pass `{ capture: true }` to report every error, or a predicate. An error is
|
|
237
|
+
reported once, even if your handler already called `captureException`, and it
|
|
238
|
+
lands on the request's HTTP server span even when an instrumentation's child
|
|
239
|
+
span is active (`captureRequestError` still adds its attributes to that span
|
|
240
|
+
when the error was already reported). Errors from another JavaScript realm
|
|
241
|
+
(a `vm` context, Next's edge sandbox under `next start`) keep their own type,
|
|
242
|
+
message and stack. `autterErrorResponse()` reports the same way and also
|
|
243
|
+
answers the request.
|
|
244
|
+
|
|
245
|
+
### Unhandled promise rejections
|
|
246
|
+
|
|
247
|
+
Recorded as errors (severity `error`, `autter.unhandled_rejection: true`, not
|
|
248
|
+
fatal) — without changing whether your process crashes. Any
|
|
249
|
+
`unhandledRejection` listener switches off Node's default crash, so the SDK
|
|
250
|
+
does what Node would have done when its listener is the only one:
|
|
251
|
+
|
|
252
|
+
| `--unhandled-rejections` | Your own listener? | What happens | Recorded as |
|
|
253
|
+
| --- | --- | --- | --- |
|
|
254
|
+
| `throw` (default) | no | rethrown on the next tick: exit code 1, as before | fatal crash (`autter.unhandled`) |
|
|
255
|
+
| `throw` (default) | yes | your listener decides | error |
|
|
256
|
+
| `strict` | either | Node crashes first, as before | fatal crash |
|
|
257
|
+
| `warn`, `none` | either | no crash, as before | error |
|
|
258
|
+
| `warn-with-error-code` | no | Node's warning and exit code 1, as before | error |
|
|
259
|
+
|
|
260
|
+
The one visible difference: after the rethrow, `uncaughtException` listeners
|
|
261
|
+
receive origin `"uncaughtException"` rather than `"unhandledRejection"`.
|
|
262
|
+
`captureUnhandledRejections: false` (or `captureGlobalErrors: false`) restores
|
|
263
|
+
the 1.5.0 behaviour, where a rejection is seen only when it crashes the
|
|
264
|
+
process.
|
|
265
|
+
|
|
170
266
|
### Version compatibility: checked for you
|
|
171
267
|
|
|
172
268
|
Some features need a minimum ingester version: operation logging needs
|
|
@@ -288,6 +384,48 @@ This closes the server-side gap to match the browser relay's payload
|
|
|
288
384
|
whitelist; it is best-effort scrubbing of obvious PII shapes, not a DLP
|
|
289
385
|
engine — keep secrets out of attributes in the first place.
|
|
290
386
|
|
|
387
|
+
**Query strings (1.6.1+).** Query strings carry what a path never should:
|
|
388
|
+
emails, one-time codes, API keys, session ids, search terms. So no span
|
|
389
|
+
leaves the process with query values, whichever instrumentation made it
|
|
390
|
+
(HTTP server and client spans, undici/fetch, Express, Fastify, Next.js's own
|
|
391
|
+
spans):
|
|
392
|
+
|
|
393
|
+
- `http.target`, `http.url`, `url.full` and `url.original` are cut at the `?`
|
|
394
|
+
(and `#`): `/search?email=jane@example.com&page=2` becomes `/search`;
|
|
395
|
+
- `url.query` keeps only the parameter names, `email=[redacted]&page=[redacted]`.
|
|
396
|
+
It is added from the URL when the instrumentation did not set it, so you
|
|
397
|
+
still see which parameters a request used. A parameter without `=`
|
|
398
|
+
(`/verify?<token>`) may itself be the secret and is masked whole;
|
|
399
|
+
- span names lose their query too (Next.js names a request span
|
|
400
|
+
`GET /search?q=…` until it knows the route).
|
|
401
|
+
|
|
402
|
+
List the parameters whose values are safe to keep, such as pagination or
|
|
403
|
+
sorting (exact names, after percent-decoding; kept values are still scrubbed
|
|
404
|
+
by `redactAttributes`):
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
initAutterServer({ ..., keepQueryParams: ["page", "sort"] });
|
|
408
|
+
// url.query: "email=[redacted]&page=2"
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
This runs at export, like the scrubbing above, and also with
|
|
412
|
+
`redactAttributes: false`. Route templates (`http.route`) and request
|
|
413
|
+
summaries never contained a query.
|
|
414
|
+
|
|
415
|
+
### One SDK per process, however many copies are loaded
|
|
416
|
+
|
|
417
|
+
Bundlers can load this package more than once: Next.js bundles one copy
|
|
418
|
+
into `instrumentation.ts` and another into each route (unless it is in
|
|
419
|
+
`serverExternalPackages`), and a process can load both the ESM and the CJS
|
|
420
|
+
build. Since 1.6.1 every copy shares one process-wide state
|
|
421
|
+
(`globalThis[Symbol.for("@autter/runtime-node")]`): the copy that ran
|
|
422
|
+
`initAutterServer` (or `initAutterLogging`) is the SDK for all of them, so
|
|
423
|
+
`withRuntimeRequest`, `captureException`, `runtimeLogger` and the rest export
|
|
424
|
+
through it from any copy, and an error one copy reported is never reported
|
|
425
|
+
again by another. Before any copy starts, and after `shutdown()`, every copy
|
|
426
|
+
behaves as before: records go to the console. The shared object is
|
|
427
|
+
versioned; a copy that finds a layout it does not know keeps its own state.
|
|
428
|
+
|
|
291
429
|
## 3. LLM tracing
|
|
292
430
|
|
|
293
431
|
For the field contract, manual reporting patterns, and provider-agnostic
|
|
@@ -90,6 +90,35 @@ var compat_manifest_default = {
|
|
|
90
90
|
ingester: "1.5.0",
|
|
91
91
|
migrations: ["0013-occurrence-codes"],
|
|
92
92
|
sdks: { "@autter/runtime-node": "1.5.0", "@autter/runtime-next": "1.5.0", "@autter/runtime-browser": "1.4.0", "@autter/runtime-edge": "1.0.0" }
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "breadcrumbs",
|
|
96
|
+
title: "Breadcrumbs: the steps before an error",
|
|
97
|
+
ingester: "1.6.0",
|
|
98
|
+
migrations: [],
|
|
99
|
+
sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-node": "1.6.0", "@autter/runtime-next": "1.6.0" }
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "source_map_debug_ids",
|
|
103
|
+
title: "Source map debug ids",
|
|
104
|
+
ingester: "1.6.0",
|
|
105
|
+
migrations: ["0017-source-maps-debug-id"],
|
|
106
|
+
route: "/v1/sourcemaps/check",
|
|
107
|
+
sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-next": "1.6.0" }
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: "fingerprint_override",
|
|
111
|
+
title: "Fingerprint override",
|
|
112
|
+
ingester: "1.6.0",
|
|
113
|
+
migrations: [],
|
|
114
|
+
sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-node": "1.6.0", "@autter/runtime-next": "1.6.0", "@autter/runtime-edge": "1.1.0" }
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: "browser_trace_propagation",
|
|
118
|
+
title: "Browser trace propagation (browser errors linked to server traces)",
|
|
119
|
+
ingester: "1.6.0",
|
|
120
|
+
migrations: [],
|
|
121
|
+
sdks: { "@autter/runtime-browser": "1.6.0", "@autter/runtime-next": "1.6.0", "@autter/runtime-node": "1.6.0" }
|
|
93
122
|
}
|
|
94
123
|
]
|
|
95
124
|
};
|
|
@@ -435,7 +464,7 @@ var _https = require('https');
|
|
|
435
464
|
// package.json
|
|
436
465
|
var package_default = {
|
|
437
466
|
name: "@autter/runtime-node",
|
|
438
|
-
version: "1.
|
|
467
|
+
version: "1.6.1",
|
|
439
468
|
description: "Autter Runtime for Node.js: request wide events, coded errors, operation logging, same-origin browser relay and a curated OpenTelemetry server tracker",
|
|
440
469
|
license: "MIT",
|
|
441
470
|
type: "module",
|
|
@@ -486,6 +515,8 @@ var package_default = {
|
|
|
486
515
|
"@autter/runtime-core": "0.0.0",
|
|
487
516
|
"@types/node": "^22.10.0",
|
|
488
517
|
express: "^4.21.2",
|
|
518
|
+
fastify: "^5.12.5",
|
|
519
|
+
koa: "^3.2.1",
|
|
489
520
|
tsup: "^8.3.5",
|
|
490
521
|
typescript: "^5.7.0"
|
|
491
522
|
},
|
|
@@ -508,6 +539,55 @@ var package_default = {
|
|
|
508
539
|
]
|
|
509
540
|
};
|
|
510
541
|
|
|
542
|
+
// src/shared.ts
|
|
543
|
+
var SHARED_STATE_KEY = /* @__PURE__ */ Symbol.for("@autter/runtime-node");
|
|
544
|
+
var SHARED_STATE_LAYOUT = 1;
|
|
545
|
+
function isObject(value) {
|
|
546
|
+
return typeof value === "object" && value !== null;
|
|
547
|
+
}
|
|
548
|
+
var VERSION = package_default.version;
|
|
549
|
+
function resolveShared() {
|
|
550
|
+
const fresh = { v: SHARED_STATE_LAYOUT, sdk: VERSION };
|
|
551
|
+
const host = globalThis;
|
|
552
|
+
let existing;
|
|
553
|
+
try {
|
|
554
|
+
existing = host[SHARED_STATE_KEY];
|
|
555
|
+
if (existing === void 0) {
|
|
556
|
+
Object.defineProperty(host, SHARED_STATE_KEY, {
|
|
557
|
+
value: fresh,
|
|
558
|
+
configurable: true,
|
|
559
|
+
enumerable: false,
|
|
560
|
+
writable: false
|
|
561
|
+
});
|
|
562
|
+
return fresh;
|
|
563
|
+
}
|
|
564
|
+
} catch (e) {
|
|
565
|
+
return fresh;
|
|
566
|
+
}
|
|
567
|
+
if (isObject(existing) && existing.v === SHARED_STATE_LAYOUT) return existing;
|
|
568
|
+
debugLog(
|
|
569
|
+
`another copy of @autter/runtime-node uses shared state layout ${String(isObject(existing) ? existing.v : typeof existing)}; this copy (${VERSION}, layout ${SHARED_STATE_LAYOUT}) keeps its own state`
|
|
570
|
+
);
|
|
571
|
+
return fresh;
|
|
572
|
+
}
|
|
573
|
+
var shared = resolveShared();
|
|
574
|
+
function sharedSlot(name, init) {
|
|
575
|
+
const fresh = init();
|
|
576
|
+
try {
|
|
577
|
+
const existing = shared[name];
|
|
578
|
+
if (!isObject(existing)) {
|
|
579
|
+
shared[name] = fresh;
|
|
580
|
+
return fresh;
|
|
581
|
+
}
|
|
582
|
+
for (const [key, value] of Object.entries(fresh)) {
|
|
583
|
+
if (existing[key] === void 0) existing[key] = value;
|
|
584
|
+
}
|
|
585
|
+
return existing;
|
|
586
|
+
} catch (e2) {
|
|
587
|
+
return fresh;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
511
591
|
// src/compat.ts
|
|
512
592
|
var SDK_IDENTITY = {
|
|
513
593
|
name: "@autter/runtime-node",
|
|
@@ -556,7 +636,7 @@ function compatRequest(method, url, options = {}) {
|
|
|
556
636
|
if (options.unref !== false) req.on("socket", (socket) => socket.unref());
|
|
557
637
|
req.on("error", () => done(null));
|
|
558
638
|
req.end(options.body);
|
|
559
|
-
} catch (
|
|
639
|
+
} catch (e3) {
|
|
560
640
|
done(null);
|
|
561
641
|
}
|
|
562
642
|
});
|
|
@@ -569,7 +649,7 @@ async function fetchIngesterCompat(endpoint, options = {}) {
|
|
|
569
649
|
let report = null;
|
|
570
650
|
try {
|
|
571
651
|
report = JSON.parse(res.body);
|
|
572
|
-
} catch (
|
|
652
|
+
} catch (e4) {
|
|
573
653
|
}
|
|
574
654
|
const info = _nullishCoalesce(ingesterInfoFromReport(report), () => ( headerInfo(res.headers)));
|
|
575
655
|
return info ? { info, report } : { info: null, report, error: "unrecognised /v1/compat response" };
|
|
@@ -600,7 +680,7 @@ function envDisabled() {
|
|
|
600
680
|
return value === "0" || value === "false" || value === "off";
|
|
601
681
|
}
|
|
602
682
|
var defaultWarn = (message) => console.warn(`[autter-runtime] ${message}`);
|
|
603
|
-
var
|
|
683
|
+
var compat = sharedSlot("compat", () => ({ state: freshState(null, false) }));
|
|
604
684
|
function freshState(endpoint, enabled, sdk = SDK_IDENTITY, warn = defaultWarn) {
|
|
605
685
|
return {
|
|
606
686
|
endpoint,
|
|
@@ -615,7 +695,7 @@ function freshState(endpoint, enabled, sdk = SDK_IDENTITY, warn = defaultWarn) {
|
|
|
615
695
|
};
|
|
616
696
|
}
|
|
617
697
|
function configureCompatCheck(options) {
|
|
618
|
-
state = freshState(
|
|
698
|
+
compat.state = freshState(
|
|
619
699
|
options.endpoint.replace(/\/$/, ""),
|
|
620
700
|
options.enabled !== false && !envDisabled(),
|
|
621
701
|
_nullishCoalesce(options.sdk, () => ( SDK_IDENTITY)),
|
|
@@ -623,23 +703,23 @@ function configureCompatCheck(options) {
|
|
|
623
703
|
);
|
|
624
704
|
}
|
|
625
705
|
function ensureCompatConfigured(endpoint) {
|
|
626
|
-
if (state.endpoint === null && !envDisabled()) {
|
|
627
|
-
state = freshState(endpoint.replace(/\/$/, ""), true);
|
|
706
|
+
if (compat.state.endpoint === null && !envDisabled()) {
|
|
707
|
+
compat.state = freshState(endpoint.replace(/\/$/, ""), true);
|
|
628
708
|
}
|
|
629
709
|
}
|
|
630
710
|
function noteCompatFeature(id) {
|
|
631
711
|
try {
|
|
632
|
-
if (!state.enabled || state.features.has(id) || !featureById(id)) return;
|
|
633
|
-
state.features.add(id);
|
|
634
|
-
if (state.info) {
|
|
712
|
+
if (!compat.state.enabled || compat.state.features.has(id) || !featureById(id)) return;
|
|
713
|
+
compat.state.features.add(id);
|
|
714
|
+
if (compat.state.info) {
|
|
635
715
|
evaluateAndWarn();
|
|
636
716
|
return;
|
|
637
717
|
}
|
|
638
|
-
if (state.started) return;
|
|
639
|
-
state.started = true;
|
|
640
|
-
const current = state;
|
|
718
|
+
if (compat.state.started) return;
|
|
719
|
+
compat.state.started = true;
|
|
720
|
+
const current = compat.state;
|
|
641
721
|
setTimeout(() => void runCheck(current), 0).unref();
|
|
642
|
-
} catch (
|
|
722
|
+
} catch (e5) {
|
|
643
723
|
}
|
|
644
724
|
}
|
|
645
725
|
async function runCheck(current) {
|
|
@@ -648,7 +728,7 @@ async function runCheck(current) {
|
|
|
648
728
|
const { info, error } = await fetchIngesterCompat(current.endpoint, {
|
|
649
729
|
probeRoutes: routesFor(current.features)
|
|
650
730
|
});
|
|
651
|
-
if (state !== current) return;
|
|
731
|
+
if (compat.state !== current) return;
|
|
652
732
|
if (!info) {
|
|
653
733
|
debugLog(`compat check skipped: ${_nullishCoalesce(error, () => ( "ingester version unknown"))}`);
|
|
654
734
|
return;
|
|
@@ -672,8 +752,8 @@ function routesFor(features) {
|
|
|
672
752
|
return routes;
|
|
673
753
|
}
|
|
674
754
|
function mergeInfo(next) {
|
|
675
|
-
const prev = state.info;
|
|
676
|
-
state.info = {
|
|
755
|
+
const prev = compat.state.info;
|
|
756
|
+
compat.state.info = {
|
|
677
757
|
version: _nullishCoalesce(_nullishCoalesce(next.version, () => ( _optionalChain([prev, 'optionalAccess', _21 => _21.version]))), () => ( null)),
|
|
678
758
|
...next.legacy || _optionalChain([prev, 'optionalAccess', _22 => _22.legacy]) ? { legacy: true } : {},
|
|
679
759
|
..._nullishCoalesce(next.schema, () => ( _optionalChain([prev, 'optionalAccess', _23 => _23.schema]))) ? { schema: _nullishCoalesce(next.schema, () => ( _optionalChain([prev, 'optionalAccess', _24 => _24.schema]))) } : {},
|
|
@@ -681,17 +761,17 @@ function mergeInfo(next) {
|
|
|
681
761
|
};
|
|
682
762
|
}
|
|
683
763
|
function evaluateAndWarn() {
|
|
684
|
-
const info = state.info;
|
|
764
|
+
const info = compat.state.info;
|
|
685
765
|
if (!info) return;
|
|
686
|
-
if (info.version === null && state.endpoint) {
|
|
687
|
-
const unprobed = routesFor(state.features).filter(
|
|
688
|
-
(route) => _optionalChain([info, 'access', _26 => _26.routes, 'optionalAccess', _27 => _27[route]]) === void 0 && !state.pendingProbes.has(route)
|
|
766
|
+
if (info.version === null && compat.state.endpoint) {
|
|
767
|
+
const unprobed = routesFor(compat.state.features).filter(
|
|
768
|
+
(route) => _optionalChain([info, 'access', _26 => _26.routes, 'optionalAccess', _27 => _27[route]]) === void 0 && !compat.state.pendingProbes.has(route)
|
|
689
769
|
);
|
|
690
770
|
if (unprobed.length) {
|
|
691
|
-
const current = state;
|
|
771
|
+
const current = compat.state;
|
|
692
772
|
for (const route of unprobed) current.pendingProbes.add(route);
|
|
693
773
|
void Promise.all(unprobed.map((route) => probeRoute(current.endpoint, route, {}).then((exists) => [route, exists]))).then((results) => {
|
|
694
|
-
if (state !== current) return;
|
|
774
|
+
if (compat.state !== current) return;
|
|
695
775
|
const routes = {};
|
|
696
776
|
for (const [route, exists] of results) if (exists !== null) routes[route] = exists;
|
|
697
777
|
mergeInfo({ version: null, legacy: true, routes });
|
|
@@ -700,56 +780,56 @@ function evaluateAndWarn() {
|
|
|
700
780
|
});
|
|
701
781
|
}
|
|
702
782
|
}
|
|
703
|
-
for (const issue of evaluateCompat({ features: state.features, ingester: info })) {
|
|
783
|
+
for (const issue of evaluateCompat({ features: compat.state.features, ingester: info })) {
|
|
704
784
|
warnOnce(issue);
|
|
705
785
|
}
|
|
706
786
|
}
|
|
707
787
|
function warnOnce(issue) {
|
|
708
788
|
const key = `${issue.feature}:${issue.kind}`;
|
|
709
|
-
if (state.warned.has(key)) return;
|
|
710
|
-
state.warned.add(key);
|
|
789
|
+
if (compat.state.warned.has(key)) return;
|
|
790
|
+
compat.state.warned.add(key);
|
|
711
791
|
try {
|
|
712
|
-
state.warn(issue.message);
|
|
713
|
-
} catch (
|
|
792
|
+
compat.state.warn(issue.message);
|
|
793
|
+
} catch (e6) {
|
|
714
794
|
}
|
|
715
795
|
}
|
|
716
796
|
function observeIngesterResponse(response) {
|
|
717
797
|
try {
|
|
718
|
-
if (!state.enabled) return;
|
|
798
|
+
if (!compat.state.enabled) return;
|
|
719
799
|
const fromHeader = headerInfo(response.headers);
|
|
720
800
|
if (fromHeader) {
|
|
721
|
-
if (_optionalChain([
|
|
801
|
+
if (_optionalChain([compat, 'access', _28 => _28.state, 'access', _29 => _29.info, 'optionalAccess', _30 => _30.version]) !== fromHeader.version) {
|
|
722
802
|
mergeInfo(fromHeader);
|
|
723
803
|
evaluateAndWarn();
|
|
724
804
|
}
|
|
725
805
|
return;
|
|
726
806
|
}
|
|
727
|
-
if (response.status === 404 && response.route && !_optionalChain([state, 'access',
|
|
807
|
+
if (response.status === 404 && response.route && !_optionalChain([compat, 'access', _31 => _31.state, 'access', _32 => _32.info, 'optionalAccess', _33 => _33.version])) {
|
|
728
808
|
mergeInfo({ version: null, legacy: true, routes: { [response.route]: false } });
|
|
729
809
|
evaluateAndWarn();
|
|
730
810
|
}
|
|
731
|
-
} catch (
|
|
811
|
+
} catch (e7) {
|
|
732
812
|
}
|
|
733
813
|
}
|
|
734
814
|
function noteRelayRejection(eventTypes, status, headers) {
|
|
735
815
|
try {
|
|
736
|
-
if (!state.enabled || status !== 400 || headerInfo(headers)) return [];
|
|
816
|
+
if (!compat.state.enabled || status !== 400 || headerInfo(headers)) return [];
|
|
737
817
|
const named = [];
|
|
738
818
|
for (const id of featuresForBrowserEvents(eventTypes)) {
|
|
739
819
|
const feature = featureById(id);
|
|
740
820
|
const key = `${id}:relay_rejected`;
|
|
741
|
-
if (state.warned.has(key)) continue;
|
|
742
|
-
state.warned.add(key);
|
|
821
|
+
if (compat.state.warned.has(key)) continue;
|
|
822
|
+
compat.state.warned.add(key);
|
|
743
823
|
named.push(id);
|
|
744
824
|
try {
|
|
745
|
-
state.warn(
|
|
825
|
+
compat.state.warn(
|
|
746
826
|
`The ingester rejected a browser batch containing ${feature.title.toLowerCase()} events. ${feature.title} needs ingester >= ${feature.ingester}; yours is 1.4.0 or older (it predates version reporting), so those batches are dropped. ${ingesterUpgradeHint(COMPAT_MANIFEST)}`
|
|
747
827
|
);
|
|
748
|
-
} catch (
|
|
828
|
+
} catch (e8) {
|
|
749
829
|
}
|
|
750
830
|
}
|
|
751
831
|
return named;
|
|
752
|
-
} catch (
|
|
832
|
+
} catch (e9) {
|
|
753
833
|
return [];
|
|
754
834
|
}
|
|
755
835
|
}
|
|
@@ -776,4 +856,5 @@ function noteRelayRejection(eventTypes, status, headers) {
|
|
|
776
856
|
|
|
777
857
|
|
|
778
858
|
|
|
779
|
-
|
|
859
|
+
|
|
860
|
+
exports.COMPAT_MANIFEST = COMPAT_MANIFEST; exports.INGESTER_VERSION_HEADER = INGESTER_VERSION_HEADER; exports.versionAtLeast = versionAtLeast; exports.featuresForBrowserEvents = featuresForBrowserEvents; exports.evaluateCompat = evaluateCompat; exports.setDebugMode = setDebugMode; exports.isDebugEnabled = isDebugEnabled; exports.debugLog = debugLog; exports.telemetryStats = telemetryStats; exports.CountingExporter = CountingExporter; exports.registerFlushTarget = registerFlushTarget; exports.unregisterFlushTargets = unregisterFlushTargets; exports.installAutterAutoFlush = installAutterAutoFlush; exports.sharedSlot = sharedSlot; exports.SDK_IDENTITY = SDK_IDENTITY; exports.compatRequest = compatRequest; exports.fetchIngesterCompat = fetchIngesterCompat; exports.configureCompatCheck = configureCompatCheck; exports.ensureCompatConfigured = ensureCompatConfigured; exports.noteCompatFeature = noteCompatFeature; exports.observeIngesterResponse = observeIngesterResponse; exports.noteRelayRejection = noteRelayRejection;
|