@fixback/sdk-core 0.2.0 → 0.3.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 +28 -9
- package/{src/annotation.ts → dist/annotation.d.ts} +36 -47
- package/dist/anonymous-id.d.ts +23 -0
- package/dist/anonymous-id.js +39 -0
- package/dist/anonymous-id.js.map +1 -0
- package/dist/auto-capture.d.ts +129 -0
- package/dist/auto-capture.js +210 -0
- package/dist/auto-capture.js.map +1 -0
- package/{src/backoff.ts → dist/backoff.d.ts} +17 -52
- package/dist/boot.d.ts +148 -0
- package/dist/boot.js +113 -0
- package/dist/boot.js.map +1 -0
- package/dist/breadcrumb.d.ts +133 -0
- package/dist/connect.d.ts +110 -0
- package/dist/connect.js +147 -0
- package/dist/connect.js.map +1 -0
- package/dist/env.d.ts +37 -0
- package/dist/env.js +83 -0
- package/dist/env.js.map +1 -0
- package/dist/fingerprint.d.ts +39 -0
- package/dist/fingerprint.js +10 -15
- package/dist/fingerprint.js.map +1 -1
- package/dist/http.d.ts +51 -0
- package/dist/http.js +42 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +115 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1057 -0
- package/dist/index.mjs.map +7 -0
- package/dist/options.d.ts +82 -0
- package/dist/options.js +22 -0
- package/dist/options.js.map +1 -0
- package/dist/release.d.ts +19 -0
- package/dist/release.js +36 -0
- package/dist/release.js.map +1 -0
- package/dist/scrub.d.ts +62 -0
- package/dist/stack.d.ts +51 -0
- package/dist/stack.js +97 -0
- package/dist/stack.js.map +1 -0
- package/dist/trace/buffer.d.ts +121 -0
- package/dist/trace/buffer.js +230 -0
- package/dist/trace/buffer.js.map +1 -0
- package/dist/trace/console-args.d.ts +60 -0
- package/dist/trace/console-args.js +189 -0
- package/dist/trace/console-args.js.map +1 -0
- package/dist/trace/console.d.ts +42 -0
- package/dist/trace/console.js +71 -0
- package/dist/trace/console.js.map +1 -0
- package/dist/trace/crumbs.d.ts +88 -0
- package/dist/trace/crumbs.js +164 -0
- package/dist/trace/crumbs.js.map +1 -0
- package/dist/trace/source.d.ts +30 -0
- package/dist/trace/source.js +59 -0
- package/dist/trace/source.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +17 -0
- package/dist/version.js.map +1 -0
- package/dist/wire.d.ts +101 -0
- package/package.json +12 -8
- package/src/backoff.test.ts +0 -94
- package/src/breadcrumb.ts +0 -169
- package/src/fingerprint.test.ts +0 -96
- package/src/fingerprint.ts +0 -112
- package/src/index.ts +0 -63
- package/src/scrub.test.ts +0 -215
- package/src/scrub.ts +0 -226
- package/src/wire.ts +0 -116
package/dist/stack.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The **one stack parser** every Fixback SDK shares (ADR-0028): a single frame
|
|
4
|
+
* tokenizer ({@link parseStackLine}) and the structured-frame extraction the wire
|
|
5
|
+
* carries ({@link extractStructuredFrames}).
|
|
6
|
+
*
|
|
7
|
+
* Three consumers read a stack, and each used to carry its own copy of the same
|
|
8
|
+
* regexes: the fingerprint's compact top-frames signature (`fingerprint.ts`), the
|
|
9
|
+
* console crumb's `file:line` source (`trace/source.ts`), and the structured
|
|
10
|
+
* frames server-side symbolication matches against uploaded sourcemaps
|
|
11
|
+
* (`extractStructuredFrames`, #117 / ADR-0024). They now all tokenize a frame the
|
|
12
|
+
* same way, so a stack that parses on one surface parses identically on every
|
|
13
|
+
* other — the parity guarantee the shared fingerprint depends on.
|
|
14
|
+
*
|
|
15
|
+
* Both engine dialects are handled: V8/Hermes (`at fn (loc)` / `at loc`) and
|
|
16
|
+
* JSC/SpiderMonkey (`fn@loc` / `@loc`).
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.parseStackLine = parseStackLine;
|
|
20
|
+
exports.parseFrameLocation = parseFrameLocation;
|
|
21
|
+
exports.extractStructuredFrames = extractStructuredFrames;
|
|
22
|
+
const scrub_1 = require("./scrub");
|
|
23
|
+
/** The most structured frames shipped per report (mirrors the server's cap). */
|
|
24
|
+
const STRUCTURED_FRAME_LIMIT = 30;
|
|
25
|
+
/**
|
|
26
|
+
* Tokenize one **trimmed** stack line into its function name and raw location, or
|
|
27
|
+
* `null` when the line is not a frame at all (the leading `Error: message`, a
|
|
28
|
+
* blank line). The single place the two engine dialects are recognised — every
|
|
29
|
+
* other stack reader in the core builds on this.
|
|
30
|
+
*/
|
|
31
|
+
function parseStackLine(line) {
|
|
32
|
+
// V8 / Hermes: "at fn (loc)"
|
|
33
|
+
const v8Named = line.match(/^at\s+(.+?)\s+\((.+)\)$/);
|
|
34
|
+
if (v8Named)
|
|
35
|
+
return { fn: v8Named[1] ?? null, location: v8Named[2] ?? "" };
|
|
36
|
+
// V8 / Hermes: "at loc"
|
|
37
|
+
const v8Bare = line.match(/^at\s+(.+)$/);
|
|
38
|
+
if (v8Bare)
|
|
39
|
+
return { fn: null, location: v8Bare[1] ?? "" };
|
|
40
|
+
// JSC / SpiderMonkey: "fn@loc" | "@loc"
|
|
41
|
+
const at = line.indexOf("@");
|
|
42
|
+
if (at >= 0) {
|
|
43
|
+
return { fn: at > 0 ? line.slice(0, at) : null, location: line.slice(at + 1) };
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Split a `file:line:col` location into its parts; `null` when it carries no line
|
|
49
|
+
* number or names no locatable file. The file may itself contain colons
|
|
50
|
+
* (`https://…`, `node:internal/…`), so the numeric groups are taken from the right.
|
|
51
|
+
* Unlocatable frames (`native`, `<anonymous>`, eval) are rejected here.
|
|
52
|
+
*/
|
|
53
|
+
function parseFrameLocation(location) {
|
|
54
|
+
const match = location.match(/^(.*?):(\d+)(?::(\d+))?$/);
|
|
55
|
+
if (!match)
|
|
56
|
+
return null;
|
|
57
|
+
const file = match[1] ?? "";
|
|
58
|
+
if (file.length === 0 || file === "native" || file.includes("<anonymous>")) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const line = Number(match[2]);
|
|
62
|
+
if (!Number.isFinite(line))
|
|
63
|
+
return null;
|
|
64
|
+
const column = match[3] !== undefined ? Number(match[3]) : null;
|
|
65
|
+
return { file, line, column };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Extract **structured** frames from a stack for the wire (#117, ADR-0024), top of
|
|
69
|
+
* stack first — unlike the fingerprint's compact signature (which drops the origin
|
|
70
|
+
* so a per-deploy asset hash cannot split a bug), these keep the full script path,
|
|
71
|
+
* because server-side symbolication matches it against uploaded sourcemap paths.
|
|
72
|
+
* Each URL is scrubbed (query dropped, path PII redacted) before it can leave the
|
|
73
|
+
* process; unlocatable frames are skipped; the count is capped.
|
|
74
|
+
*/
|
|
75
|
+
function extractStructuredFrames(stack, limit = STRUCTURED_FRAME_LIMIT) {
|
|
76
|
+
if (typeof stack !== "string" || stack.length === 0)
|
|
77
|
+
return [];
|
|
78
|
+
const frames = [];
|
|
79
|
+
for (const raw of stack.split("\n")) {
|
|
80
|
+
if (frames.length >= limit)
|
|
81
|
+
break;
|
|
82
|
+
const tokens = parseStackLine(raw.trim());
|
|
83
|
+
if (!tokens || !tokens.location)
|
|
84
|
+
continue;
|
|
85
|
+
const parsed = parseFrameLocation(tokens.location);
|
|
86
|
+
if (!parsed)
|
|
87
|
+
continue;
|
|
88
|
+
frames.push({
|
|
89
|
+
file: (0, scrub_1.scrubUrl)(parsed.file),
|
|
90
|
+
line: parsed.line,
|
|
91
|
+
column: parsed.column,
|
|
92
|
+
function: tokens.fn && tokens.fn.length > 0 ? tokens.fn : null,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return frames;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=stack.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stack.js","sourceRoot":"","sources":["../src/stack.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The trace **ring buffer** (spec #122 §A/§B, decision D6) — the shared,
|
|
3
|
+
* runtime-agnostic store every Fixback capture SDK records crumbs into (ADR-0028).
|
|
4
|
+
*
|
|
5
|
+
* It holds **three independent FIFO rings** — `network`, `console`,
|
|
6
|
+
* `breadcrumbs` — each trimmed to its own budget so a chatty stream never evicts
|
|
7
|
+
* another's lead-up, plus a shared age cap. On `add`, a crumb passes through
|
|
8
|
+
* `beforeBreadcrumb`, is stamped with a stable `id` and a high-res `mono`
|
|
9
|
+
* timestamp (and an epoch `timestamp` if it has none), routed to its stream, then
|
|
10
|
+
* that stream is pruned by age and trimmed to its budget. `snapshot` prunes by age
|
|
11
|
+
* again, then merges the three streams into one `mono`-ordered array for transport
|
|
12
|
+
* — so the wire keeps its single `trace` field.
|
|
13
|
+
*
|
|
14
|
+
* Nothing here touches a DOM or a Node built-in: the instrumentation that *produces*
|
|
15
|
+
* crumbs lives in the consuming SDK, and the clocks are injectable so the buffer is
|
|
16
|
+
* unit-tested deterministically.
|
|
17
|
+
*/
|
|
18
|
+
import type { Breadcrumb, BreadcrumbLevel } from "../breadcrumb";
|
|
19
|
+
/**
|
|
20
|
+
* The three independent trace streams (spec #122 §A, decision D6). Each is its own
|
|
21
|
+
* FIFO ring with its own size budget, so a chatty stream can't evict another's
|
|
22
|
+
* lead-up. Every crumb category maps to exactly one stream via {@link streamOf}.
|
|
23
|
+
*/
|
|
24
|
+
export type TraceStream = "network" | "console" | "breadcrumbs";
|
|
25
|
+
/**
|
|
26
|
+
* Per-stream ring budgets — starting points from the grill (D6): network is the
|
|
27
|
+
* chattiest, breadcrumbs the sparsest. Per-project tunable, never frozen.
|
|
28
|
+
*/
|
|
29
|
+
export declare const DEFAULT_STREAM_BUDGETS: Readonly<Record<TraceStream, number>>;
|
|
30
|
+
/**
|
|
31
|
+
* The shared age cap (spec #122 §A): entries older than this are pruned from every
|
|
32
|
+
* stream, whichever trims first. ~3 minutes and **on** by default, so an idle tab
|
|
33
|
+
* (or a backgrounded app) never ships stale context.
|
|
34
|
+
*/
|
|
35
|
+
export declare const DEFAULT_MAX_AGE_MS: number;
|
|
36
|
+
/**
|
|
37
|
+
* Console levels captured by default (spec #122 §C, decision D7): **all** of them, so
|
|
38
|
+
* the Console tab is a real console and not just an error log. Eviction priority (not
|
|
39
|
+
* capture) is what keeps a chatty app's `log`/`info`/`debug` from burying the signal —
|
|
40
|
+
* see {@link PINNED_CONSOLE_LEVELS}.
|
|
41
|
+
*/
|
|
42
|
+
export declare const DEFAULT_CONSOLE_LEVELS: readonly BreadcrumbLevel[];
|
|
43
|
+
/**
|
|
44
|
+
* The console levels **pinned** against eviction (spec #122 §C, decision D7). When the
|
|
45
|
+
* console stream overflows its budget, `warn`/`error`/`assert` are retained ahead of
|
|
46
|
+
* `log`/`info`/`debug`, so an error is never evicted by a burst of chatter — the
|
|
47
|
+
* low-priority levels fill the remainder and are dropped first.
|
|
48
|
+
*/
|
|
49
|
+
export declare const PINNED_CONSOLE_LEVELS: readonly BreadcrumbLevel[];
|
|
50
|
+
/** Whether a console level is pinned against eviction (see {@link PINNED_CONSOLE_LEVELS}). */
|
|
51
|
+
export declare function isPinnedConsoleLevel(level: BreadcrumbLevel | undefined): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* The console levels for which `source` (`file:line`) is captured (ticket #159).
|
|
54
|
+
*
|
|
55
|
+
* Capturing a call site constructs a `new Error()` to read its stack on **every** console
|
|
56
|
+
* call — a measurable hot-page cost when a chatty app logs in a tight loop. The
|
|
57
|
+
* spec-sanctioned mitigation (decision D7) narrows source capture to the **levels that
|
|
58
|
+
* matter for debugging** — the same `warn`/`error`/`assert` that are pinned against
|
|
59
|
+
* eviction — so `log`/`info`/`debug` pay no per-call stack cost and simply omit `source`.
|
|
60
|
+
* Per-project tunability of this set is future work; today it deliberately mirrors
|
|
61
|
+
* {@link PINNED_CONSOLE_LEVELS} so "what survives eviction" and "what carries a call
|
|
62
|
+
* site" stay one idea.
|
|
63
|
+
*/
|
|
64
|
+
export declare const SOURCE_CAPTURE_LEVELS: readonly BreadcrumbLevel[];
|
|
65
|
+
/** Whether a console level captures `source` `file:line` (see {@link SOURCE_CAPTURE_LEVELS}). */
|
|
66
|
+
export declare function capturesSource(level: BreadcrumbLevel): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Which stream a crumb's category belongs to (spec #122 §A). Network APIs
|
|
69
|
+
* (`fetch` / `xhr` / `beacon`) form the network stream, `console` its own, and
|
|
70
|
+
* everything else (navigation, masked UI events, the failing error) breadcrumbs.
|
|
71
|
+
*/
|
|
72
|
+
export declare function streamOf(category: string): TraceStream;
|
|
73
|
+
/** Filters or edits each crumb before it enters the buffer; `null` drops it. */
|
|
74
|
+
export type BeforeBreadcrumb = (crumb: Breadcrumb) => Breadcrumb | null;
|
|
75
|
+
/** Configuration for {@link createBreadcrumbBuffer}. All values are optional. */
|
|
76
|
+
export interface BreadcrumbBufferConfig {
|
|
77
|
+
/**
|
|
78
|
+
* Per-stream size budgets (oldest drop, independently per stream). Any stream
|
|
79
|
+
* omitted falls back to {@link DEFAULT_STREAM_BUDGETS}.
|
|
80
|
+
*/
|
|
81
|
+
readonly budgets?: Partial<Record<TraceStream, number>>;
|
|
82
|
+
/**
|
|
83
|
+
* Shared age cap in ms: entries older than this are dropped from every stream.
|
|
84
|
+
* Defaults to {@link DEFAULT_MAX_AGE_MS} (on); pass `0` (or a non-positive value)
|
|
85
|
+
* to disable age pruning.
|
|
86
|
+
*/
|
|
87
|
+
readonly maxAgeMs?: number;
|
|
88
|
+
/** A per-crumb filter (mute a category, edit, or drop by returning `null`). */
|
|
89
|
+
readonly beforeBreadcrumb?: BeforeBreadcrumb | null;
|
|
90
|
+
/** Epoch clock source, injectable for tests. Defaults to `Date.now`. */
|
|
91
|
+
readonly now?: () => number;
|
|
92
|
+
/** High-res monotonic clock, injectable for tests. Defaults to {@link nowMono}. */
|
|
93
|
+
readonly mono?: () => number;
|
|
94
|
+
/** Stable id generator, injectable for tests. Defaults to a per-buffer sequence. */
|
|
95
|
+
readonly nextId?: () => string;
|
|
96
|
+
}
|
|
97
|
+
/** A live trace buffer. */
|
|
98
|
+
export interface BreadcrumbBuffer {
|
|
99
|
+
/** Record a crumb (subject to `beforeBreadcrumb`, id/mono stamping, size, and age trimming). */
|
|
100
|
+
add(crumb: Breadcrumb): void;
|
|
101
|
+
/** The current crumbs, merged across streams and ordered in time — a fresh array. */
|
|
102
|
+
snapshot(): Breadcrumb[];
|
|
103
|
+
/** Drop every crumb. */
|
|
104
|
+
clear(): void;
|
|
105
|
+
}
|
|
106
|
+
/** Coerce a configured budget to a usable positive integer, else the default. */
|
|
107
|
+
export declare function normalizeBudget(value: number | undefined, fallback: number): number;
|
|
108
|
+
/** A per-buffer id factory: a short random salt + a base-36 sequence, stable once assigned. */
|
|
109
|
+
export declare function createIdFactory(): () => string;
|
|
110
|
+
/**
|
|
111
|
+
* Trim the **console** stream to its budget with eviction priority (spec #122 §C,
|
|
112
|
+
* decision D7): `warn`/`error`/`assert` are pinned, so the oldest `log`/`info`/`debug`
|
|
113
|
+
* entries are dropped first; a pinned entry is evicted only when dropping every
|
|
114
|
+
* low-priority one still leaves the stream over budget (oldest pinned first). Survivors
|
|
115
|
+
* keep their insertion order. A plain FIFO `slice(-budget)` handles every other stream.
|
|
116
|
+
*/
|
|
117
|
+
export declare function trimConsoleStream<T extends {
|
|
118
|
+
readonly crumb: Breadcrumb;
|
|
119
|
+
}>(held: T[], budget: number): T[];
|
|
120
|
+
/** Create the per-stream trace buffer. See the module doc. */
|
|
121
|
+
export declare function createBreadcrumbBuffer(config?: BreadcrumbBufferConfig): BreadcrumbBuffer;
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The trace **ring buffer** (spec #122 §A/§B, decision D6) — the shared,
|
|
4
|
+
* runtime-agnostic store every Fixback capture SDK records crumbs into (ADR-0028).
|
|
5
|
+
*
|
|
6
|
+
* It holds **three independent FIFO rings** — `network`, `console`,
|
|
7
|
+
* `breadcrumbs` — each trimmed to its own budget so a chatty stream never evicts
|
|
8
|
+
* another's lead-up, plus a shared age cap. On `add`, a crumb passes through
|
|
9
|
+
* `beforeBreadcrumb`, is stamped with a stable `id` and a high-res `mono`
|
|
10
|
+
* timestamp (and an epoch `timestamp` if it has none), routed to its stream, then
|
|
11
|
+
* that stream is pruned by age and trimmed to its budget. `snapshot` prunes by age
|
|
12
|
+
* again, then merges the three streams into one `mono`-ordered array for transport
|
|
13
|
+
* — so the wire keeps its single `trace` field.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here touches a DOM or a Node built-in: the instrumentation that *produces*
|
|
16
|
+
* crumbs lives in the consuming SDK, and the clocks are injectable so the buffer is
|
|
17
|
+
* unit-tested deterministically.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.SOURCE_CAPTURE_LEVELS = exports.PINNED_CONSOLE_LEVELS = exports.DEFAULT_CONSOLE_LEVELS = exports.DEFAULT_MAX_AGE_MS = exports.DEFAULT_STREAM_BUDGETS = void 0;
|
|
21
|
+
exports.isPinnedConsoleLevel = isPinnedConsoleLevel;
|
|
22
|
+
exports.capturesSource = capturesSource;
|
|
23
|
+
exports.streamOf = streamOf;
|
|
24
|
+
exports.normalizeBudget = normalizeBudget;
|
|
25
|
+
exports.createIdFactory = createIdFactory;
|
|
26
|
+
exports.trimConsoleStream = trimConsoleStream;
|
|
27
|
+
exports.createBreadcrumbBuffer = createBreadcrumbBuffer;
|
|
28
|
+
const env_1 = require("../env");
|
|
29
|
+
/**
|
|
30
|
+
* Per-stream ring budgets — starting points from the grill (D6): network is the
|
|
31
|
+
* chattiest, breadcrumbs the sparsest. Per-project tunable, never frozen.
|
|
32
|
+
*/
|
|
33
|
+
exports.DEFAULT_STREAM_BUDGETS = {
|
|
34
|
+
network: 100,
|
|
35
|
+
console: 80,
|
|
36
|
+
breadcrumbs: 40,
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The shared age cap (spec #122 §A): entries older than this are pruned from every
|
|
40
|
+
* stream, whichever trims first. ~3 minutes and **on** by default, so an idle tab
|
|
41
|
+
* (or a backgrounded app) never ships stale context.
|
|
42
|
+
*/
|
|
43
|
+
exports.DEFAULT_MAX_AGE_MS = 3 * 60 * 1000;
|
|
44
|
+
/**
|
|
45
|
+
* Console levels captured by default (spec #122 §C, decision D7): **all** of them, so
|
|
46
|
+
* the Console tab is a real console and not just an error log. Eviction priority (not
|
|
47
|
+
* capture) is what keeps a chatty app's `log`/`info`/`debug` from burying the signal —
|
|
48
|
+
* see {@link PINNED_CONSOLE_LEVELS}.
|
|
49
|
+
*/
|
|
50
|
+
exports.DEFAULT_CONSOLE_LEVELS = [
|
|
51
|
+
"log",
|
|
52
|
+
"info",
|
|
53
|
+
"warn",
|
|
54
|
+
"error",
|
|
55
|
+
"assert",
|
|
56
|
+
"debug",
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* The console levels **pinned** against eviction (spec #122 §C, decision D7). When the
|
|
60
|
+
* console stream overflows its budget, `warn`/`error`/`assert` are retained ahead of
|
|
61
|
+
* `log`/`info`/`debug`, so an error is never evicted by a burst of chatter — the
|
|
62
|
+
* low-priority levels fill the remainder and are dropped first.
|
|
63
|
+
*/
|
|
64
|
+
exports.PINNED_CONSOLE_LEVELS = [
|
|
65
|
+
"warn",
|
|
66
|
+
"error",
|
|
67
|
+
"assert",
|
|
68
|
+
];
|
|
69
|
+
/** Whether a console level is pinned against eviction (see {@link PINNED_CONSOLE_LEVELS}). */
|
|
70
|
+
function isPinnedConsoleLevel(level) {
|
|
71
|
+
return level !== undefined && exports.PINNED_CONSOLE_LEVELS.includes(level);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The console levels for which `source` (`file:line`) is captured (ticket #159).
|
|
75
|
+
*
|
|
76
|
+
* Capturing a call site constructs a `new Error()` to read its stack on **every** console
|
|
77
|
+
* call — a measurable hot-page cost when a chatty app logs in a tight loop. The
|
|
78
|
+
* spec-sanctioned mitigation (decision D7) narrows source capture to the **levels that
|
|
79
|
+
* matter for debugging** — the same `warn`/`error`/`assert` that are pinned against
|
|
80
|
+
* eviction — so `log`/`info`/`debug` pay no per-call stack cost and simply omit `source`.
|
|
81
|
+
* Per-project tunability of this set is future work; today it deliberately mirrors
|
|
82
|
+
* {@link PINNED_CONSOLE_LEVELS} so "what survives eviction" and "what carries a call
|
|
83
|
+
* site" stay one idea.
|
|
84
|
+
*/
|
|
85
|
+
exports.SOURCE_CAPTURE_LEVELS = exports.PINNED_CONSOLE_LEVELS;
|
|
86
|
+
/** Whether a console level captures `source` `file:line` (see {@link SOURCE_CAPTURE_LEVELS}). */
|
|
87
|
+
function capturesSource(level) {
|
|
88
|
+
return exports.SOURCE_CAPTURE_LEVELS.includes(level);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Which stream a crumb's category belongs to (spec #122 §A). Network APIs
|
|
92
|
+
* (`fetch` / `xhr` / `beacon`) form the network stream, `console` its own, and
|
|
93
|
+
* everything else (navigation, masked UI events, the failing error) breadcrumbs.
|
|
94
|
+
*/
|
|
95
|
+
function streamOf(category) {
|
|
96
|
+
if (category === "console")
|
|
97
|
+
return "console";
|
|
98
|
+
if (category === "fetch" || category === "xhr" || category === "beacon") {
|
|
99
|
+
return "network";
|
|
100
|
+
}
|
|
101
|
+
return "breadcrumbs";
|
|
102
|
+
}
|
|
103
|
+
/** Coerce a configured budget to a usable positive integer, else the default. */
|
|
104
|
+
function normalizeBudget(value, fallback) {
|
|
105
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 1) {
|
|
106
|
+
return fallback;
|
|
107
|
+
}
|
|
108
|
+
return Math.floor(value);
|
|
109
|
+
}
|
|
110
|
+
/** A per-buffer id factory: a short random salt + a base-36 sequence, stable once assigned. */
|
|
111
|
+
function createIdFactory() {
|
|
112
|
+
const salt = Math.random().toString(36).slice(2, 8);
|
|
113
|
+
let seq = 0;
|
|
114
|
+
return () => `${salt}${(seq++).toString(36)}`;
|
|
115
|
+
}
|
|
116
|
+
/** The streams, in a fixed order for a deterministic merge. */
|
|
117
|
+
const STREAMS = ["network", "console", "breadcrumbs"];
|
|
118
|
+
/**
|
|
119
|
+
* Trim the **console** stream to its budget with eviction priority (spec #122 §C,
|
|
120
|
+
* decision D7): `warn`/`error`/`assert` are pinned, so the oldest `log`/`info`/`debug`
|
|
121
|
+
* entries are dropped first; a pinned entry is evicted only when dropping every
|
|
122
|
+
* low-priority one still leaves the stream over budget (oldest pinned first). Survivors
|
|
123
|
+
* keep their insertion order. A plain FIFO `slice(-budget)` handles every other stream.
|
|
124
|
+
*/
|
|
125
|
+
function trimConsoleStream(held, budget) {
|
|
126
|
+
const over = held.length - budget;
|
|
127
|
+
if (over <= 0)
|
|
128
|
+
return held;
|
|
129
|
+
const drop = new Set();
|
|
130
|
+
// First: oldest low-priority (not pinned) entries, until enough room is freed.
|
|
131
|
+
for (let i = 0; i < held.length && drop.size < over; i += 1) {
|
|
132
|
+
if (!isPinnedConsoleLevel(held[i].crumb.level))
|
|
133
|
+
drop.add(i);
|
|
134
|
+
}
|
|
135
|
+
// Then, only if the low-priority ones weren't enough: oldest pinned entries.
|
|
136
|
+
for (let i = 0; i < held.length && drop.size < over; i += 1) {
|
|
137
|
+
if (!drop.has(i))
|
|
138
|
+
drop.add(i);
|
|
139
|
+
}
|
|
140
|
+
return held.filter((_, i) => !drop.has(i));
|
|
141
|
+
}
|
|
142
|
+
/** Create the per-stream trace buffer. See the module doc. */
|
|
143
|
+
function createBreadcrumbBuffer(config = {}) {
|
|
144
|
+
const budgets = {
|
|
145
|
+
network: normalizeBudget(config.budgets?.network, exports.DEFAULT_STREAM_BUDGETS.network),
|
|
146
|
+
console: normalizeBudget(config.budgets?.console, exports.DEFAULT_STREAM_BUDGETS.console),
|
|
147
|
+
breadcrumbs: normalizeBudget(config.budgets?.breadcrumbs, exports.DEFAULT_STREAM_BUDGETS.breadcrumbs),
|
|
148
|
+
};
|
|
149
|
+
const maxAgeMs = config.maxAgeMs === undefined ? exports.DEFAULT_MAX_AGE_MS : config.maxAgeMs;
|
|
150
|
+
const beforeBreadcrumb = config.beforeBreadcrumb;
|
|
151
|
+
const now = config.now ?? Date.now;
|
|
152
|
+
const mono = config.mono ?? env_1.nowMono;
|
|
153
|
+
const nextId = config.nextId ?? createIdFactory();
|
|
154
|
+
const streams = {
|
|
155
|
+
network: [],
|
|
156
|
+
console: [],
|
|
157
|
+
breadcrumbs: [],
|
|
158
|
+
};
|
|
159
|
+
let seq = 0;
|
|
160
|
+
function pruneByAge() {
|
|
161
|
+
if (typeof maxAgeMs !== "number" || maxAgeMs <= 0)
|
|
162
|
+
return;
|
|
163
|
+
const cutoff = now() - maxAgeMs;
|
|
164
|
+
for (const stream of STREAMS) {
|
|
165
|
+
const held = streams[stream];
|
|
166
|
+
if (held.length > 0) {
|
|
167
|
+
streams[stream] = held.filter((h) => h.crumb.timestamp >= cutoff);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
add(crumb) {
|
|
173
|
+
let entry = crumb;
|
|
174
|
+
if (beforeBreadcrumb) {
|
|
175
|
+
try {
|
|
176
|
+
entry = beforeBreadcrumb(crumb);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// A throwing filter must never break capture; keep the (already
|
|
180
|
+
// masked) crumb rather than silently erasing the trace.
|
|
181
|
+
entry = crumb;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (!entry)
|
|
185
|
+
return;
|
|
186
|
+
// Stamp a stable id + high-res mono (and an epoch timestamp if absent) so
|
|
187
|
+
// every entry orders and cross-links exactly, whatever stream it lands in.
|
|
188
|
+
const stamped = {
|
|
189
|
+
...entry,
|
|
190
|
+
id: entry.id ?? nextId(),
|
|
191
|
+
timestamp: typeof entry.timestamp === "number" ? entry.timestamp : now(),
|
|
192
|
+
mono: typeof entry.mono === "number" ? entry.mono : mono(),
|
|
193
|
+
};
|
|
194
|
+
const stream = streamOf(stamped.category);
|
|
195
|
+
streams[stream].push({ crumb: stamped, seq: seq++ });
|
|
196
|
+
// Age-prune every stream first (it may reassign the array), then trim this
|
|
197
|
+
// stream to its own budget — a flood here never evicts another stream.
|
|
198
|
+
pruneByAge();
|
|
199
|
+
const held = streams[stream];
|
|
200
|
+
const budget = budgets[stream];
|
|
201
|
+
if (held.length > budget) {
|
|
202
|
+
streams[stream] =
|
|
203
|
+
stream === "console"
|
|
204
|
+
? trimConsoleStream(held, budget)
|
|
205
|
+
: held.slice(-budget);
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
snapshot() {
|
|
209
|
+
pruneByAge();
|
|
210
|
+
const merged = [];
|
|
211
|
+
for (const stream of STREAMS)
|
|
212
|
+
merged.push(...streams[stream]);
|
|
213
|
+
// Order in time by the high-res mono clock (every entry has one), with the
|
|
214
|
+
// insertion sequence as a stable tiebreak so equal clocks never scramble.
|
|
215
|
+
merged.sort((a, b) => {
|
|
216
|
+
const am = typeof a.crumb.mono === "number" ? a.crumb.mono : a.crumb.timestamp;
|
|
217
|
+
const bm = typeof b.crumb.mono === "number" ? b.crumb.mono : b.crumb.timestamp;
|
|
218
|
+
return am - bm || a.seq - b.seq;
|
|
219
|
+
});
|
|
220
|
+
return merged.map((h) => h.crumb);
|
|
221
|
+
},
|
|
222
|
+
clear() {
|
|
223
|
+
streams.network = [];
|
|
224
|
+
streams.console = [];
|
|
225
|
+
streams.breadcrumbs = [];
|
|
226
|
+
seq = 0;
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=buffer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buffer.js","sourceRoot":"","sources":["../../src/trace/buffer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured **console arguments** (spec #122 §C, decision D7) — the shared,
|
|
3
|
+
* runtime-agnostic assembly that turns a `console.*` call's arguments into
|
|
4
|
+
* type-tagged, JSON-safe, size-capped {@link ConsoleArg} values, plus the one-line
|
|
5
|
+
* preview the crumb's `message` carries.
|
|
6
|
+
*
|
|
7
|
+
* Preserving arguments type-tagged rather than flattening them to a string is what
|
|
8
|
+
* lets the Console tab render an object or an Error expandably instead of
|
|
9
|
+
* `[object Object]`. Every cap is applied **at assembly**, so a single console
|
|
10
|
+
* crumb can never bloat a report and an exotic or circular value can never reach
|
|
11
|
+
* the wire unserialized.
|
|
12
|
+
*/
|
|
13
|
+
import type { ConsoleArg } from "../breadcrumb";
|
|
14
|
+
/** Longest crumb message kept; a huge log line is truncated, never dropped. */
|
|
15
|
+
export declare const MAX_MESSAGE_LENGTH = 300;
|
|
16
|
+
/**
|
|
17
|
+
* Structured console argument caps (spec #122 §C/§F), all applied **at assembly** so a
|
|
18
|
+
* single console crumb can never bloat a report:
|
|
19
|
+
* - {@link MAX_ARG_DEPTH} bounds how deep a `json` argument is cloned (deeper nodes
|
|
20
|
+
* collapse to an `[Object]`/`[Array]` marker);
|
|
21
|
+
* - {@link MAX_ARG_ITEMS} bounds how many keys/elements are kept at each level;
|
|
22
|
+
* - {@link MAX_ARG_STRING_LENGTH} truncates a single over-long string value;
|
|
23
|
+
* - {@link MAX_CONSOLE_ARGS_BYTES} bounds the serialized size of the whole args array
|
|
24
|
+
* (trailing args are dropped to fit), kept well under ingest's per-entry byte cap.
|
|
25
|
+
*/
|
|
26
|
+
export declare const MAX_ARG_DEPTH = 4;
|
|
27
|
+
export declare const MAX_ARG_ITEMS = 100;
|
|
28
|
+
export declare const MAX_ARG_STRING_LENGTH = 1024;
|
|
29
|
+
export declare const MAX_CONSOLE_ARGS_BYTES = 4096;
|
|
30
|
+
/** Truncate a string to `max` with an ellipsis marker; keeps the value, never drops it. */
|
|
31
|
+
export declare function capLength(value: string, max: number): string;
|
|
32
|
+
/** Render one console argument to the flat text of the crumb's preview line. */
|
|
33
|
+
export declare function stringifyArg(arg: unknown): string;
|
|
34
|
+
/** The one-line preview a console crumb's `message` carries, length-capped. */
|
|
35
|
+
export declare function joinArgs(args: readonly unknown[]): string;
|
|
36
|
+
/** An Error rendered to a structured, size-capped `{ name, message, stack? }`. */
|
|
37
|
+
export declare function describeErrorValue(error: Error): {
|
|
38
|
+
name: string;
|
|
39
|
+
message: string;
|
|
40
|
+
stack?: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Build a JSON-safe, depth-/breadth-/string-capped clone of a value for a `json`
|
|
44
|
+
* console argument. Beyond {@link MAX_ARG_DEPTH} the node collapses to a marker;
|
|
45
|
+
* at each level at most {@link MAX_ARG_ITEMS} keys/elements are kept; strings are
|
|
46
|
+
* truncated to {@link MAX_ARG_STRING_LENGTH}; circular references become
|
|
47
|
+
* `"[Circular]"`; and exotic values (bigint / symbol / function / undefined) are
|
|
48
|
+
* rendered to safe text — so the result is always serializable and bounded.
|
|
49
|
+
*/
|
|
50
|
+
export declare function safeCloneValue(value: unknown, depth: number, seen: Set<object>): unknown;
|
|
51
|
+
/** Classify one console argument into a type-tagged {@link ConsoleArg} (spec #122 §C). */
|
|
52
|
+
export declare function toConsoleArg(value: unknown): ConsoleArg;
|
|
53
|
+
/**
|
|
54
|
+
* Cap an entry's structured args to {@link MAX_CONSOLE_ARGS_BYTES} (spec #122 §F):
|
|
55
|
+
* drop trailing args until the array fits, keeping the earliest (usually the format
|
|
56
|
+
* string / main message); if even a single arg is over budget, keep one honest
|
|
57
|
+
* placeholder rather than an unbounded value. Per-arg depth/breadth/string caps bound
|
|
58
|
+
* most cases already — this is the whole-entry backstop.
|
|
59
|
+
*/
|
|
60
|
+
export declare function capConsoleArgs(args: ConsoleArg[]): ConsoleArg[];
|