@lunora/observability 1.0.0-alpha.8 → 1.0.0-alpha.81
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 +2 -4
- package/dist/index.d.mts +239 -49
- package/dist/index.d.ts +239 -49
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-CeplWoRm.mjs +22 -0
- package/dist/packem_shared/DEFAULT_EXPLAIN_ISSUE_MODEL-BGGX6k5D.mjs +3 -0
- package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-ChlIe0vF.mjs +56 -0
- package/dist/packem_shared/ISSUE_SEVERITIES-gLC4VEPQ.mjs +15 -0
- package/dist/packem_shared/LogBuffer-BmD4CfV0.mjs +1 -0
- package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-DSOQ6rno.mjs +1 -0
- package/dist/packem_shared/MetricBuffer-DnOJJVHH.mjs +1 -0
- package/dist/packem_shared/REQUEST_LOG_TABLE--5PBTOVZ.mjs +1 -0
- package/dist/packem_shared/SpanBuffer-DnYOc8uk.mjs +1 -0
- package/dist/packem_shared/createDatabaseTally-n3nCJ5OW.mjs +1 -0
- package/dist/packem_shared/createMetrics-C1-k5PCU.mjs +1 -0
- package/dist/packem_shared/findDanglingReferences-BNgg0jD_.mjs +1 -0
- package/dist/packem_shared/metric-buffer-Bx0XbypU.mjs +1 -0
- package/dist/packem_shared/prune-marker-B4An9Nuj.mjs +1 -0
- package/dist/packem_shared/readMetricHistory-TKfnwFNv.mjs +31 -0
- package/dist/packem_shared/readQueryInsights-B98JliCi.mjs +32 -0
- package/dist/packem_shared/request-log-C2scoFaB.mjs +24 -0
- package/dist/packem_shared/resolveTraceAnchor-COLuCGmj.mjs +1 -0
- package/dist/packem_shared/run-sql-0aPgJkIw.mjs +1 -0
- package/dist/packem_shared/trace-context-DgWo6N3y.mjs +1 -0
- package/package.json +5 -5
- package/dist/packem_shared/AUTH_METRICS_BUCKETS_TABLE-C2V-DiGu.mjs +0 -22
- package/dist/packem_shared/DEFAULT_EXPLAIN_ISSUE_MODEL-xF11R4vX.mjs +0 -3
- package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-BB8kcFBe.mjs +0 -56
- package/dist/packem_shared/ISSUE_SEVERITIES-Js6lGUG5.mjs +0 -15
- package/dist/packem_shared/LogBuffer-bIvCelI-.mjs +0 -1
- package/dist/packem_shared/MIN_ADMIN_TOKEN_LENGTH-T-zuwRVR.mjs +0 -1
- package/dist/packem_shared/MetricBuffer-CYrLuEsz.mjs +0 -1
- package/dist/packem_shared/REQUEST_LOG_TABLE-BvpprPd_.mjs +0 -1
- package/dist/packem_shared/SpanBuffer-BR6Ff0M-.mjs +0 -1
- package/dist/packem_shared/createDatabaseTally-DBs7wDeF.mjs +0 -1
- package/dist/packem_shared/createMetrics-BXNmqy0o.mjs +0 -1
- package/dist/packem_shared/findDanglingReferences-D-x9LvY0.mjs +0 -1
- package/dist/packem_shared/metric-buffer-CdgXal7w.mjs +0 -1
- package/dist/packem_shared/readMetricHistory-CVcUgQEL.mjs +0 -31
- package/dist/packem_shared/readQueryInsights-DN07YcQy.mjs +0 -32
- package/dist/packem_shared/request-log-BwFic6VV.mjs +0 -21
- package/dist/packem_shared/resolveTraceAnchor-IAN8riI0.mjs +0 -1
- package/dist/packem_shared/trace-context-DrdF960P.mjs +0 -1
package/README.md
CHANGED
|
@@ -19,15 +19,13 @@ None of it is Cloudflare-specific. It reads and writes through the SQL handle th
|
|
|
19
19
|
|
|
20
20
|
That mattered in practice: anything wanting request logs or metrics had to depend on the Cloudflare Durable Object package to get them. The dependency now runs `@lunora/do` → `@lunora/observability` → `@lunora/shard-engine`, so a second host consumes this directly rather than inheriting a provider-bound edge.
|
|
21
21
|
|
|
22
|
-
`@lunora/do` re-exports every symbol it previously exposed from these modules, so existing imports and the codegen-emitted surface are unchanged.
|
|
23
|
-
|
|
24
22
|
## Host-specific values are injected, not baked in
|
|
25
23
|
|
|
26
24
|
Three details are Cloudflare's rather than this package's, so the host passes them:
|
|
27
25
|
|
|
28
26
|
- **AI model** — `explainIssue(binding, args, { defaultModel })`. `DEFAULT_EXPLAIN_ISSUE_MODEL` is a Workers AI id, exported so the Cloudflare host has a name to pass rather than a string literal.
|
|
29
|
-
- **Query batch size** —
|
|
30
|
-
- **Span projections** — `HostSpanLike`, `HostTracingLike
|
|
27
|
+
- **Query batch size** — hashes are read in batches of `DEFAULT_HASH_QUERY_BATCH` (100), the Durable Object SQLite bound-parameter cap. `readIssueStates` takes it as a `{ hashQueryBatch }` option, but neither the function nor the constant is on the package barrel and the in-package reader passes no options, so the batch is fixed at 100 until a host with a different cap needs it threaded through.
|
|
28
|
+
- **Span projections** — the exported types `HostSpanLike`, `HostTracingLike` and `HostTracingResolver`. Named for the role, not the provider; Cloudflare's `enterSpan` callback argument is one shape that satisfies them. The host injects its tracer through the `TracerDeps` option fields `fuseHostSpans?: boolean` and `resolveHostTracing?: HostTracingResolver` — those are option names, not exported values, and the resolver itself lives in the host (`@lunora/do`'s `shard-do.ts` guards a dynamic `import("cloudflare:workers")`).
|
|
31
29
|
|
|
32
30
|
## Importing it
|
|
33
31
|
|
package/dist/index.d.mts
CHANGED
|
@@ -64,9 +64,9 @@ declare const ensureAuthMetricsTables: (sql: SqlExec) => void;
|
|
|
64
64
|
* `failures` advances only when `outcome === "fail"`. `since_ms` is set once on
|
|
65
65
|
* the first attempt and never moved (so it stays a true first-seen marker).
|
|
66
66
|
*
|
|
67
|
-
* Exactly two `INSERT … ON CONFLICT … DO UPDATE` statements
|
|
68
|
-
*
|
|
69
|
-
* response path without blocking it.
|
|
67
|
+
* Exactly two `INSERT … ON CONFLICT … DO UPDATE` statements, all keyed by
|
|
68
|
+
* primary key, plus a bounded `DELETE` that runs once per bucket window —
|
|
69
|
+
* cheap enough to fire off the auth response path without blocking it.
|
|
70
70
|
*/
|
|
71
71
|
declare const recordAuthEvent: (sql: SqlExec, input: RecordAuthEventInput) => void;
|
|
72
72
|
/**
|
|
@@ -320,10 +320,50 @@ interface SpanHandle {
|
|
|
320
320
|
* `@opentelemetry/api` bridge parenting a third-party library's spans — to
|
|
321
321
|
* reach around the API for it.
|
|
322
322
|
*/
|
|
323
|
-
spanContext: () =>
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
323
|
+
spanContext: () => SpanContextIds;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* The W3C identity of one span, plus the trace's settled sampling verdict.
|
|
327
|
+
*
|
|
328
|
+
* `sampled` is the propagated head decision (absent means "no verdict reached
|
|
329
|
+
* this tier", which every consumer reads as keep). It rides alongside the ids
|
|
330
|
+
* because everything that needs the ids to announce this span downstream — a
|
|
331
|
+
* hand-built `traceparent`, the `@opentelemetry/api` bridge's `SpanContext` —
|
|
332
|
+
* needs the flag in the same breath, and announcing `sampled` on a trace that
|
|
333
|
+
* was sampled OUT is what leaves a collector holding the middle of a trace.
|
|
334
|
+
*/
|
|
335
|
+
interface SpanContextIds {
|
|
336
|
+
/** The trace's settled W3C `sampled` verdict; absent when none was propagated. */
|
|
337
|
+
sampled?: boolean;
|
|
338
|
+
/** This span's id (16-hex). */
|
|
339
|
+
spanId: string;
|
|
340
|
+
/** The trace this span belongs to (32-hex). */
|
|
341
|
+
traceId: string;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Caller-supplied ids for one `ctx.trace` span, passed as the tracer's fourth
|
|
345
|
+
* argument.
|
|
346
|
+
*
|
|
347
|
+
* For adapters that must hand a span's identity to somebody else BEFORE the span
|
|
348
|
+
* body runs — the `@opentelemetry/api` bridge returns a `SpanContext`
|
|
349
|
+
* synchronously from `startSpan`, and a library builds a `traceparent` from it —
|
|
350
|
+
* so the id the adapter published is the id that reaches the collector rather
|
|
351
|
+
* than a phantom. `parentSpanId` overrides the enclosing span for the same
|
|
352
|
+
* reason: an adapter that tracks its own parent/child structure (OTel's
|
|
353
|
+
* `Context`) can express it without an ambient span stack.
|
|
354
|
+
*
|
|
355
|
+
* Not part of the ordinary `ctx.trace(name, fn, attributes)` call — a handler
|
|
356
|
+
* never mints its own ids.
|
|
357
|
+
*
|
|
358
|
+
* Both ids are required: an adapter that has published one has published the
|
|
359
|
+
* other, and `identity` is itself optional — omitting it, not passing a partial
|
|
360
|
+
* object, is how a caller says "no adapter involved".
|
|
361
|
+
*/
|
|
362
|
+
interface SpanIdentity {
|
|
363
|
+
/** Parent to this span id instead of the enclosing `ctx.trace` / dispatch span. */
|
|
364
|
+
parentSpanId: string;
|
|
365
|
+
/** Record the span under this id (16-hex) instead of a freshly minted one. */
|
|
366
|
+
spanId: string;
|
|
327
367
|
}
|
|
328
368
|
/** Options accepted by `ctx.trace(name, fn, options)` beyond the plain attribute bag. */
|
|
329
369
|
interface SpanOptions {
|
|
@@ -423,7 +463,7 @@ interface SpanEvent {
|
|
|
423
463
|
* which it can attach attributes only known *after* it resolves (post-hoc). It is
|
|
424
464
|
* a trailing parameter, so a `(trace) => …` body that ignores it still conforms.
|
|
425
465
|
*/
|
|
426
|
-
type ContextTracer = <T>(name: string, function_: (trace: ContextTracer, span: SpanHandle) => Promise<T> | T, options?: LogFields | SpanOptions) => Promise<T>;
|
|
466
|
+
type ContextTracer = <T>(name: string, function_: (trace: ContextTracer, span: SpanHandle) => Promise<T> | T, options?: LogFields | SpanOptions, identity?: SpanIdentity) => Promise<T>;
|
|
427
467
|
/** Structural shape of the `ctx.metrics` recorder (see the server `LunoraMetrics`). */
|
|
428
468
|
interface ContextMetrics {
|
|
429
469
|
count: (name: string, value?: number, attributes?: LogFields) => void;
|
|
@@ -560,10 +600,7 @@ interface SpanCollector {
|
|
|
560
600
|
* class `isInternalCode` redaction exists for, so it rides the same dev-only
|
|
561
601
|
* escape hatch rather than shipping to a third-party collector by default.
|
|
562
602
|
*/
|
|
563
|
-
declare const createSpanCollector: (ids:
|
|
564
|
-
spanId: string;
|
|
565
|
-
traceId: string;
|
|
566
|
-
}, captureRaw?: boolean) => SpanCollector;
|
|
603
|
+
declare const createSpanCollector: (ids: SpanContextIds, captureRaw?: boolean) => SpanCollector;
|
|
567
604
|
/**
|
|
568
605
|
* Build the `ctx.trace` span factory for one dispatched function.
|
|
569
606
|
*
|
|
@@ -605,7 +642,7 @@ declare const createSpanCollector: (ids: {
|
|
|
605
642
|
*
|
|
606
643
|
* **DO async-context caveat (EXPERIMENTAL, partially workerd-validated).**
|
|
607
644
|
* `tracing.enterSpan` is now confirmed to EXIST and RUN inside a real Durable
|
|
608
|
-
* Object under `@cloudflare/vitest-
|
|
645
|
+
* Object under `@cloudflare/vitest-plugin` (see
|
|
609
646
|
* `__tests__/workerd/context-telemetry-cf-bridge.workerd.test.ts`): it resolves
|
|
610
647
|
* from `cloudflare:workers`, its callback executes and returns the body value
|
|
611
648
|
* without throwing, `span.isTraced` is a real boolean, and — the key additive
|
|
@@ -625,6 +662,14 @@ declare const createTracer: (deps: TracerDeps) => ContextTracer;
|
|
|
625
662
|
interface TracedFetchDeps {
|
|
626
663
|
/** The trace the CLIENT spans belong to. */
|
|
627
664
|
anchor: TraceAnchor;
|
|
665
|
+
/**
|
|
666
|
+
* Whether to record a failed fetch's error message verbatim rather than
|
|
667
|
+
* redacted. Same dev-only escape hatch as {@link TracerDeps.captureRaw}, and
|
|
668
|
+
* the same reason: a `fetch` TypeError embeds the request URL, so a key in a
|
|
669
|
+
* query string would otherwise reach the collector in the clear on the very
|
|
670
|
+
* span whose `url.full` is scrubbed by `redactUrl`.
|
|
671
|
+
*/
|
|
672
|
+
captureRaw?: boolean;
|
|
628
673
|
/** Function path the spans are attributed to. */
|
|
629
674
|
functionPath: string;
|
|
630
675
|
/**
|
|
@@ -660,6 +705,15 @@ type ContextFetch = (input: Request | string | URL, init?: RequestInit) => Promi
|
|
|
660
705
|
* Kind is `client` rather than `internal` — that is what lets a collector draw
|
|
661
706
|
* the edge to the downstream service in a service map.
|
|
662
707
|
*
|
|
708
|
+
* **Parenting is the DISPATCH, not the enclosing `ctx.trace`.** `ctx.fetch` is
|
|
709
|
+
* built once per dispatch, and the enclosing span is threaded explicitly rather
|
|
710
|
+
* than kept on an ambient stack (no `AsyncLocalStorage` in the Durable Object
|
|
711
|
+
* profile — the same constraint `ctx.trace` and the `@opentelemetry/api` bridge
|
|
712
|
+
* document), so a fetch inside `ctx.trace("stripe", …)` renders as that span's
|
|
713
|
+
* SIBLING under the dispatch, not its child. The duration and the ordering are
|
|
714
|
+
* right and the span is never lost; only the nesting is flat. Wrap the call in
|
|
715
|
+
* the span whose bar you want it under and read the two side by side.
|
|
716
|
+
*
|
|
663
717
|
* Failures are recorded and re-thrown untouched, and a non-2xx response is
|
|
664
718
|
* recorded as an ERROR span (it is a failed call from the caller's point of
|
|
665
719
|
* view) while still being returned normally — instrumentation, never flow
|
|
@@ -911,7 +965,7 @@ interface RecordFunctionMetricInput {
|
|
|
911
965
|
* dispatch used no declared index, keeping the hot path unchanged.
|
|
912
966
|
*/
|
|
913
967
|
indexHits?: ReadonlyArray<IndexHit>;
|
|
914
|
-
/** The
|
|
968
|
+
/** The `<file>:<function>` identifier. */
|
|
915
969
|
path: string;
|
|
916
970
|
/**
|
|
917
971
|
* Distinct tables this dispatch full-scanned (read with no index / point
|
|
@@ -1040,7 +1094,7 @@ interface AiRunBinding {
|
|
|
1040
1094
|
}
|
|
1041
1095
|
/** Parsed `__lunora_admin__:explainIssue` payload: the folded Issue's identifying facts, plus an optional model override. */
|
|
1042
1096
|
interface ExplainIssueArgs {
|
|
1043
|
-
/** The Issue's culprit (
|
|
1097
|
+
/** The Issue's culprit (`<file>:<function>` or `container:<name>`), for grounding context. */
|
|
1044
1098
|
culprit?: string;
|
|
1045
1099
|
/** Per-request model-id override; falls back to the caller's `defaultModel`, then {@link DEFAULT_EXPLAIN_ISSUE_MODEL}. */
|
|
1046
1100
|
model?: string;
|
|
@@ -1189,13 +1243,15 @@ type LogLevel = ContextLogLevel;
|
|
|
1189
1243
|
*/
|
|
1190
1244
|
interface LogEntry {
|
|
1191
1245
|
exitCode?: number;
|
|
1192
|
-
/** Structured fields from a `ctx.log
|
|
1246
|
+
/** Structured fields from a `ctx.log.<level>(message, fields)` / `ctx.log.with(fields)` call, when present. */
|
|
1193
1247
|
fields?: Record<string, unknown>;
|
|
1194
1248
|
functionPath?: string;
|
|
1195
1249
|
instance?: string;
|
|
1196
1250
|
level: LogLevel;
|
|
1197
1251
|
message: string;
|
|
1198
1252
|
timestamp: number;
|
|
1253
|
+
/** Trace the line was emitted under; absent outside a dispatch (container lifecycle, hibernation-path errors). What the Studio joins a log line to its waterfall on. */
|
|
1254
|
+
traceId?: string;
|
|
1199
1255
|
}
|
|
1200
1256
|
/**
|
|
1201
1257
|
* A bounded, in-memory ring buffer of recent {@link LogEntry} records.
|
|
@@ -1216,10 +1272,21 @@ declare class LogBuffer {
|
|
|
1216
1272
|
/** Backing store, kept in insertion order (oldest first). */
|
|
1217
1273
|
private readonly buffer;
|
|
1218
1274
|
private readonly capacity;
|
|
1275
|
+
/** How many entries the ring has evicted since it was last cleared. */
|
|
1276
|
+
private droppedCount;
|
|
1219
1277
|
constructor(capacity?: number);
|
|
1278
|
+
/**
|
|
1279
|
+
* Entries evicted for capacity since the last {@link LogBuffer.clear}.
|
|
1280
|
+
*
|
|
1281
|
+
* Without it a full ring is indistinguishable from a quiet instance that
|
|
1282
|
+
* happened to log exactly `capacity` lines: the reader sees 500 entries
|
|
1283
|
+
* either way and cannot tell whether 500 lines happened or 50,000 did. The
|
|
1284
|
+
* count is what turns "the newest 500" into an honest statement.
|
|
1285
|
+
*/
|
|
1286
|
+
get dropped(): number;
|
|
1220
1287
|
/** Number of entries currently buffered. */
|
|
1221
1288
|
get size(): number;
|
|
1222
|
-
/** Drop every buffered entry. */
|
|
1289
|
+
/** Drop every buffered entry, including the eviction count. */
|
|
1223
1290
|
clear(): void;
|
|
1224
1291
|
/**
|
|
1225
1292
|
* Snapshot of the buffered entries, **newest first** so the panel renders
|
|
@@ -1342,7 +1409,14 @@ interface MetricHistoryResult {
|
|
|
1342
1409
|
* omitted field keeps the historical behaviour.
|
|
1343
1410
|
*/
|
|
1344
1411
|
interface MetricHistoryOptions {
|
|
1345
|
-
/**
|
|
1412
|
+
/**
|
|
1413
|
+
* Distinct series tracked before a brand-new one is REFUSED admission
|
|
1414
|
+
* (default {@link METRIC_HISTORY_MAX_SERIES}). Nothing is evicted: an
|
|
1415
|
+
* already-tracked series keeps accumulating past the cap, and a flood of
|
|
1416
|
+
* one-off series cannot displace the app's real ones — see
|
|
1417
|
+
* {@link admitNewSeries}. `readMetricHistory`'s `capped` flag is the
|
|
1418
|
+
* read-side signal that admission is being refused.
|
|
1419
|
+
*/
|
|
1346
1420
|
maxSeries?: number;
|
|
1347
1421
|
/** Minute-buckets kept per series before older rows are trimmed (default {@link METRIC_HISTORY_BUCKET_RETENTION}). */
|
|
1348
1422
|
retentionBuckets?: number;
|
|
@@ -1393,9 +1467,9 @@ interface QueryStatEntry {
|
|
|
1393
1467
|
execCount: number;
|
|
1394
1468
|
/** Normalised SQL text (literals stripped, truncated). */
|
|
1395
1469
|
normalizedSql: string;
|
|
1396
|
-
/** Total rows
|
|
1470
|
+
/** Total rows SCANNED across all executions (the billed number, not the result-set size). */
|
|
1397
1471
|
rowsRead: number;
|
|
1398
|
-
/** Total rows
|
|
1472
|
+
/** Total rows WRITTEN across all executions. */
|
|
1399
1473
|
rowsWritten: number;
|
|
1400
1474
|
/** Total wall-clock milliseconds across all executions. */
|
|
1401
1475
|
totalDurationMs: number;
|
|
@@ -1476,11 +1550,13 @@ type RequestOutcome = "error" | "ok";
|
|
|
1476
1550
|
interface RequestLogEntry {
|
|
1477
1551
|
/** Whether the result was served from the reactive cache; `undefined` when the cache is disabled or the path isn't cached (a write/action). */
|
|
1478
1552
|
cacheHit?: boolean;
|
|
1553
|
+
/** Worker deployment id from the `CF_VERSION_METADATA` binding; `undefined` when the binding isn't declared. Group on this to answer "did this start with a deploy?". */
|
|
1554
|
+
deploymentId?: string;
|
|
1479
1555
|
/** Handler wall-clock duration in milliseconds (before the subscription write-flush, matching the per-function metrics). */
|
|
1480
1556
|
durationMs: number;
|
|
1481
1557
|
/** Error message when `outcome === "error"`, redacted like args/identity; absent on success. */
|
|
1482
1558
|
errorMessage?: string;
|
|
1483
|
-
/** The
|
|
1559
|
+
/** The `<file>:<function>` identifier dispatched, e.g. `messages:list`. */
|
|
1484
1560
|
functionPath: string;
|
|
1485
1561
|
/** Identity-claim envelope forwarded by the runtime, JSON-decoded; leaf values are redacted (the claims are PII), so only the shape survives. Absent for anonymous requests. Correlate on `userId` instead. */
|
|
1486
1562
|
identity?: Record<string, unknown>;
|
|
@@ -1498,14 +1574,19 @@ interface RequestLogEntry {
|
|
|
1498
1574
|
tablesRead: string[];
|
|
1499
1575
|
/** Tables the handler wrote (from the change tracker); empty for a read-only dispatch. */
|
|
1500
1576
|
tablesWritten: string[];
|
|
1577
|
+
/** W3C trace id (32-hex) of the dispatch; `undefined` on a row appended before this column existed. See the module docstring on the retention asymmetry. */
|
|
1578
|
+
traceId?: string;
|
|
1501
1579
|
/** Wall-clock millis when the dispatch completed. */
|
|
1502
1580
|
ts: number;
|
|
1503
1581
|
/** Acting userId forwarded by the runtime, or `undefined` when anonymous. */
|
|
1504
1582
|
userId?: string;
|
|
1583
|
+
/** Worker version tag from the `CF_VERSION_METADATA` binding; `undefined` when the binding isn't declared or the deploy carried no tag. */
|
|
1584
|
+
versionTag?: string;
|
|
1505
1585
|
}
|
|
1506
1586
|
/** Fields accepted when appending one request-log entry; `seq` is assigned by the table. */
|
|
1507
1587
|
interface AppendRequestLogEntry {
|
|
1508
1588
|
cacheHit?: boolean;
|
|
1589
|
+
deploymentId?: string;
|
|
1509
1590
|
durationMs: number;
|
|
1510
1591
|
errorMessage?: string;
|
|
1511
1592
|
functionPath: string;
|
|
@@ -1516,8 +1597,10 @@ interface AppendRequestLogEntry {
|
|
|
1516
1597
|
subscriptionsReRun?: number;
|
|
1517
1598
|
tablesRead?: string[];
|
|
1518
1599
|
tablesWritten?: string[];
|
|
1600
|
+
traceId?: string;
|
|
1519
1601
|
ts: number;
|
|
1520
1602
|
userId?: string;
|
|
1603
|
+
versionTag?: string;
|
|
1521
1604
|
}
|
|
1522
1605
|
/** Knobs the dispatch site threads into a request-log write. */
|
|
1523
1606
|
interface RequestLogWriteOptions {
|
|
@@ -1528,7 +1611,7 @@ interface RequestLogWriteOptions {
|
|
|
1528
1611
|
}
|
|
1529
1612
|
/** Filters for {@link readRequestLog}, all AND-combined; every value is a bound SQL parameter, so nothing here injects SQL. */
|
|
1530
1613
|
interface ReadRequestLogOptions {
|
|
1531
|
-
/** Functions whose path begins with this prefix (a
|
|
1614
|
+
/** Functions whose path begins with this prefix (a `<file>:` or `<file>:<fn>` correlation). */
|
|
1532
1615
|
functionPathPrefix?: string;
|
|
1533
1616
|
/** Upper bound on returned rows, clamped to [1, 10000]. */
|
|
1534
1617
|
limit?: number;
|
|
@@ -1536,14 +1619,19 @@ interface ReadRequestLogOptions {
|
|
|
1536
1619
|
outcome?: RequestOutcome;
|
|
1537
1620
|
/** Exact shard-key match. */
|
|
1538
1621
|
shardKey?: string;
|
|
1539
|
-
/**
|
|
1622
|
+
/**
|
|
1623
|
+
* Only entries strictly after this cursor. Setting it switches the read to
|
|
1624
|
+
* ASCENDING order — see {@link readRequestLog} — because that is the only
|
|
1625
|
+
* ordering under which advancing the cursor to the last returned `seq`
|
|
1626
|
+
* actually pages forward without a hole.
|
|
1627
|
+
*/
|
|
1540
1628
|
sinceSeq?: number;
|
|
1541
1629
|
/** Keep only entries whose read OR written table set contains this table. */
|
|
1542
1630
|
tableTouched?: string;
|
|
1543
1631
|
/** Exact acting-userId match. */
|
|
1544
1632
|
userId?: string;
|
|
1545
1633
|
}
|
|
1546
|
-
/** Payload of a `__lunora_admin__:getRequestLog` call: the recorded entries, newest first. */
|
|
1634
|
+
/** Payload of a `__lunora_admin__:getRequestLog` call: the recorded entries, newest first — or oldest first when the request paged forward with `sinceSeq` (see {@link readRequestLog}). */
|
|
1547
1635
|
interface RequestLogResult {
|
|
1548
1636
|
entries: RequestLogEntry[];
|
|
1549
1637
|
}
|
|
@@ -1558,7 +1646,7 @@ interface ErrorIssue {
|
|
|
1558
1646
|
assignee?: string;
|
|
1559
1647
|
/** Number of `error` rows folded into this Issue within the scanned window. */
|
|
1560
1648
|
count: number;
|
|
1561
|
-
/** The
|
|
1649
|
+
/** The `<file>:<function>` (or `container:<name>`) the errors came from. */
|
|
1562
1650
|
culprit: string;
|
|
1563
1651
|
/** Wall-clock millis of the oldest folded row. */
|
|
1564
1652
|
firstSeen: number;
|
|
@@ -1597,7 +1685,7 @@ interface IssuesResult {
|
|
|
1597
1685
|
}
|
|
1598
1686
|
/** Filters for {@link readErrorIssues}; forwarded to {@link readRequestLog} with `outcome` forced to `error`. */
|
|
1599
1687
|
interface ReadIssuesOptions {
|
|
1600
|
-
/** Functions whose path begins with this prefix (a
|
|
1688
|
+
/** Functions whose path begins with this prefix (a `<file>:` or `<file>:<fn>` correlation). */
|
|
1601
1689
|
functionPathPrefix?: string;
|
|
1602
1690
|
/** Upper bound on error rows scanned before grouping, clamped to [1, 10000]. */
|
|
1603
1691
|
limit?: number;
|
|
@@ -1622,7 +1710,7 @@ interface ReadIssuesOptions {
|
|
|
1622
1710
|
* STRING — which is what `errorMessage`/log `fields`-as-rendered-text are —
|
|
1623
1711
|
* only pattern-shaped matches apply: emails, long digit runs / structured
|
|
1624
1712
|
* numeric IDs (credit-card, phone, SSN, AWS-access-key-style), and an explicit
|
|
1625
|
-
* `Bearer
|
|
1713
|
+
* `Bearer <token>` / `token=…`-shaped substring. A free-text `password=hunter2`
|
|
1626
1714
|
* or a bare provider API key embedded in prose (e.g. `sk-live-…`) is NOT
|
|
1627
1715
|
* caught on a plain string — there is no key to match against, and neither is
|
|
1628
1716
|
* a recognized value pattern. So this is a PII-pattern net for rendered text,
|
|
@@ -1651,12 +1739,17 @@ declare const redactArgs: (value: unknown, captureRaw?: boolean) => unknown;
|
|
|
1651
1739
|
*
|
|
1652
1740
|
* `error_fingerprint` is the {@link fingerprintError} grouping hash captured
|
|
1653
1741
|
* from the RAW `error_message` at write time, before {@link appendRequestLogEntry}
|
|
1654
|
-
* redacts it — see that function's docstring.
|
|
1655
|
-
*
|
|
1742
|
+
* redacts it — see that function's docstring. `trace_id` is the dispatch's W3C
|
|
1743
|
+
* trace id, the correlation key to the span ring and to whatever collector
|
|
1744
|
+
* `otlpSink` ships to.
|
|
1745
|
+
*
|
|
1746
|
+
* Both are also added via a guarded `ALTER TABLE`, mirroring
|
|
1656
1747
|
* `function-metrics.ts`'s `ensureFunctionMetricsTables`, so a shard whose
|
|
1657
|
-
* `__lunora_reqlog__` predates
|
|
1748
|
+
* `__lunora_reqlog__` predates a column gains it on the next call without a
|
|
1658
1749
|
* migration. SQLite has no `ADD COLUMN IF NOT EXISTS`, so the duplicate-column
|
|
1659
|
-
* error from a re-run (or the freshly-created schema above) is swallowed
|
|
1750
|
+
* error from a re-run (or from the freshly-created schema above) is swallowed
|
|
1751
|
+
* per column — the loop is what keeps one column's duplicate from skipping the
|
|
1752
|
+
* next column's add.
|
|
1660
1753
|
*/
|
|
1661
1754
|
declare const ensureRequestLogTable: (sql: SqlExec) => void;
|
|
1662
1755
|
/**
|
|
@@ -1672,8 +1765,8 @@ declare const ensureRequestLogTable: (sql: SqlExec) => void;
|
|
|
1672
1765
|
* it's redacted below, and the resulting hash is persisted in
|
|
1673
1766
|
* `error_fingerprint`. `readErrorIssues` groups off that stored hash instead of
|
|
1674
1767
|
* recomputing `fingerprintError` from the (redacted) `error_message` column, so
|
|
1675
|
-
* masking a PII-bearing value — e.g. two different
|
|
1676
|
-
* redact to two different tag lengths (
|
|
1768
|
+
* masking a PII-bearing value — e.g. two different `<n>`-bucketed IDs that
|
|
1769
|
+
* redact to two different tag lengths (`<DL>` vs `<BANKACC>`) — can't split an
|
|
1677
1770
|
* existing Issue or change its identity.
|
|
1678
1771
|
*/
|
|
1679
1772
|
declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry, options?: RequestLogWriteOptions) => void;
|
|
@@ -1702,7 +1795,7 @@ declare const emitRequestLogEvent: (entry: AppendRequestLogEntry, options?: Requ
|
|
|
1702
1795
|
*/
|
|
1703
1796
|
type LogEventInput = LogEvent;
|
|
1704
1797
|
/**
|
|
1705
|
-
* Split a `ctx.log
|
|
1798
|
+
* Split a `ctx.log.<level>(...)` call's raw arguments into a display `message`
|
|
1706
1799
|
* and optional structured `fields`. The structured form — a message string plus
|
|
1707
1800
|
* a plain-object fields bag — is matched only for exactly `(string, object)`;
|
|
1708
1801
|
* every other shape is console-style and rendered whole (so existing
|
|
@@ -1747,12 +1840,21 @@ declare const parseLogArgs: (args: unknown[], boundFields?: LogFields) => {
|
|
|
1747
1840
|
*/
|
|
1748
1841
|
declare const emitLogEvent: (input: LogEventInput, options?: RequestLogWriteOptions) => void;
|
|
1749
1842
|
/**
|
|
1750
|
-
* Read request-log entries
|
|
1751
|
-
*
|
|
1752
|
-
*
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1843
|
+
* Read request-log entries, AND-combining the supplied filters (function-path
|
|
1844
|
+
* prefix, exact userId/shardKey/outcome, and a table-touched match against the
|
|
1845
|
+
* read OR written table sets), up to `limit` (clamped to [1, 10000]). Each value
|
|
1846
|
+
* is a bound parameter, so no filter can inject SQL. Creates the table first so
|
|
1847
|
+
* reads on a never-logged shard return `[]` instead of throwing. Mirrors
|
|
1848
|
+
* `readAuditLog`/`readCdcChanges`.
|
|
1849
|
+
*
|
|
1850
|
+
* **Ordering follows `sinceSeq`.** Without a cursor this is a "show me the tail"
|
|
1851
|
+
* read and returns NEWEST FIRST, which is what the studio's Logs tab renders.
|
|
1852
|
+
* With `sinceSeq` it is forward paging and returns OLDEST FIRST, starting at the
|
|
1853
|
+
* cursor: descending there silently loses rows, because `ORDER BY seq DESC LIMIT
|
|
1854
|
+
* n` answers "the newest n after the cursor", so a consumer that advances to the
|
|
1855
|
+
* largest returned `seq` skips everything between `sinceSeq` and that page
|
|
1856
|
+
* whenever more than `limit` rows accumulated between polls — the more traffic
|
|
1857
|
+
* the shard takes, the more it drops.
|
|
1756
1858
|
*/
|
|
1757
1859
|
declare const readRequestLog: (sql: SqlExec, options?: ReadRequestLogOptions) => RequestLogEntry[];
|
|
1758
1860
|
declare const readErrorIssues: (sql: SqlExec, options?: ReadIssuesOptions) => ErrorIssue[];
|
|
@@ -1770,7 +1872,7 @@ type SecurityFindingLevel = "error" | "info" | "warning";
|
|
|
1770
1872
|
*
|
|
1771
1873
|
* `admin-token-weak`: `LUNORA_ADMIN_TOKEN` is set but short enough to be brute-forceable. (An *unset* token disables admin introspection entirely, so this audit — itself admin-gated — only ever runs with a token present.)
|
|
1772
1874
|
*
|
|
1773
|
-
* `ws-gate-open`:
|
|
1875
|
+
* `ws-gate-open`: `LUNORA_WS_BEARER` is unset, so the WebSocket upgrade gate defaults open and anyone who can reach the worker can open a socket and run ordinary USER subscriptions (whatever `ctx.auth` / RLS then allows them to read). Admin subscriptions are NOT part of this: they require the socket's `admin` stamp, which the upgrade sets only from `LUNORA_ADMIN_TOKEN` or a minted admin sub-token, so an unset `LUNORA_WS_BEARER` never exposes Logs/Metrics/introspection. Which is why this is a posture finding about the app's own live-query surface, not an admin hole — set the var when subscribers are expected to present a shared credential.
|
|
1774
1876
|
*
|
|
1775
1877
|
* `dev-args-unredacted`: the worker reports a development environment, so the durable request log captures raw, un-redacted args and identity (PII). A production deploy mislabeled as dev would persist sensitive payloads.
|
|
1776
1878
|
*
|
|
@@ -1842,6 +1944,15 @@ interface TraceSpan {
|
|
|
1842
1944
|
message: string;
|
|
1843
1945
|
type: string;
|
|
1844
1946
|
};
|
|
1947
|
+
/**
|
|
1948
|
+
* Timestamped occurrences inside the span — `span.addEvent(...)` and
|
|
1949
|
+
* `span.recordException(...)`. Carried through so a handled retry or a
|
|
1950
|
+
* swallowed exception is visible on the span it happened in, which is the
|
|
1951
|
+
* only place it is interpretable. Absent when the body recorded none.
|
|
1952
|
+
*/
|
|
1953
|
+
events?: SpanEventPoint[];
|
|
1954
|
+
/** OTel `SpanKind`; absent means `"internal"`. */
|
|
1955
|
+
kind?: OtlpSpanKind;
|
|
1845
1956
|
name: string;
|
|
1846
1957
|
/** Start of this span relative to the trace's start, in ms. */
|
|
1847
1958
|
offsetMs: number;
|
|
@@ -1860,12 +1971,25 @@ interface TraceSummary {
|
|
|
1860
1971
|
rootName: string;
|
|
1861
1972
|
shardKey?: string;
|
|
1862
1973
|
/**
|
|
1863
|
-
* Spans
|
|
1864
|
-
*
|
|
1865
|
-
*
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1868
|
-
*
|
|
1974
|
+
* Spans in **pre-order**, ready to render as waterfall rows: every span is
|
|
1975
|
+
* immediately followed by its own subtree, siblings in start order. So
|
|
1976
|
+
* indenting each row by its `depth` yields the real nesting — the row above
|
|
1977
|
+
* a `depth`-`n+1` row is genuinely its parent.
|
|
1978
|
+
*
|
|
1979
|
+
* This is a tree walk, not a sort, because no ordering on `(offsetMs,
|
|
1980
|
+
* depth)` can produce it. Given a parent, its child `a`, `a`'s child `a1`
|
|
1981
|
+
* and `a`'s sibling `b` all at offset 0, that comparator yields
|
|
1982
|
+
* `parent, a, b, a1` — which renders `a1` indented beneath `b`, under a
|
|
1983
|
+
* parent it does not belong to.
|
|
1984
|
+
*
|
|
1985
|
+
* **Why they tie is not millisecond resolution.** On the Workers runtime
|
|
1986
|
+
* `Date.now()` is pinned to the time of the last I/O — a Spectre mitigation,
|
|
1987
|
+
* not a bug — so it does not advance at all across pure computation. Every
|
|
1988
|
+
* duration in this package is `Date.now() - startTs`, which means a span
|
|
1989
|
+
* wrapping CPU-only work reports `0`, and a parent whose child performed no
|
|
1990
|
+
* I/O shares its exact start and end. See `docs/concepts/observability`
|
|
1991
|
+
* ("Span durations on Workers"); the practical consequence is that a `0 ms`
|
|
1992
|
+
* span means "no I/O happened here", not "this was fast".
|
|
1869
1993
|
*/
|
|
1870
1994
|
spans: TraceSpan[];
|
|
1871
1995
|
startTs: number;
|
|
@@ -1880,10 +2004,19 @@ interface TraceSummary {
|
|
|
1880
2004
|
declare class SpanBuffer {
|
|
1881
2005
|
private readonly buffer;
|
|
1882
2006
|
private readonly capacity;
|
|
2007
|
+
/** How many spans the ring has evicted since it was last cleared. */
|
|
2008
|
+
private droppedCount;
|
|
1883
2009
|
constructor(capacity?: number);
|
|
2010
|
+
/**
|
|
2011
|
+
* Spans evicted for capacity since the last {@link SpanBuffer.clear}. The
|
|
2012
|
+
* ring silently drops its oldest span once full, which makes a busy instance
|
|
2013
|
+
* look identical to one that recorded exactly `capacity` spans; this count
|
|
2014
|
+
* is the difference between the two.
|
|
2015
|
+
*/
|
|
2016
|
+
get dropped(): number;
|
|
1884
2017
|
/** Number of spans currently buffered. */
|
|
1885
2018
|
get size(): number;
|
|
1886
|
-
/** Drop every buffered span. */
|
|
2019
|
+
/** Drop every buffered span, including the eviction count. */
|
|
1887
2020
|
clear(): void;
|
|
1888
2021
|
/** Snapshot of the buffered spans in insertion order. Fresh array per call. */
|
|
1889
2022
|
entries(): SpanEvent[];
|
|
@@ -1965,4 +2098,61 @@ declare const resolveTraceAnchor: (traceparent: string | undefined) => {
|
|
|
1965
2098
|
sampled: boolean;
|
|
1966
2099
|
traceId: string;
|
|
1967
2100
|
};
|
|
1968
|
-
export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AiRunBinding, type AppendRequestLogEntry,
|
|
2101
|
+
export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AiRunBinding, type AppendRequestLogEntry,
|
|
2102
|
+
/**
|
|
2103
|
+
* `@lunora/observability` — host-neutral telemetry storage and read models.
|
|
2104
|
+
*
|
|
2105
|
+
* Logs, traces, metrics, issue grouping and the security-audit view: the data
|
|
2106
|
+
* behind the Studio's observability pages. None of it is Cloudflare-specific —
|
|
2107
|
+
* it reads and writes through the SQL handle the engine hands it, and it lived
|
|
2108
|
+
* inside `@lunora/do` only because that is where the Durable Object it
|
|
2109
|
+
* instruments happens to live.
|
|
2110
|
+
*
|
|
2111
|
+
* The dependency runs `@lunora/do` → here → `@lunora/shard-engine`, never the
|
|
2112
|
+
* reverse, which is what makes this reusable by a second host rather than
|
|
2113
|
+
* merely relocated.
|
|
2114
|
+
*
|
|
2115
|
+
* This barrel is deliberately curated rather than `export *`. A star export
|
|
2116
|
+
* would promote every module internal to permanent public API — the exact
|
|
2117
|
+
* mistake `@lunora/do`'s own barrel documents having made once with the search
|
|
2118
|
+
* core. What is exported here is what a consumer actually needs.
|
|
2119
|
+
*/
|
|
2120
|
+
type AuthMetrics,
|
|
2121
|
+
/**
|
|
2122
|
+
* `@lunora/observability` — host-neutral telemetry storage and read models.
|
|
2123
|
+
*
|
|
2124
|
+
* Logs, traces, metrics, issue grouping and the security-audit view: the data
|
|
2125
|
+
* behind the Studio's observability pages. None of it is Cloudflare-specific —
|
|
2126
|
+
* it reads and writes through the SQL handle the engine hands it, and it lived
|
|
2127
|
+
* inside `@lunora/do` only because that is where the Durable Object it
|
|
2128
|
+
* instruments happens to live.
|
|
2129
|
+
*
|
|
2130
|
+
* The dependency runs `@lunora/do` → here → `@lunora/shard-engine`, never the
|
|
2131
|
+
* reverse, which is what makes this reusable by a second host rather than
|
|
2132
|
+
* merely relocated.
|
|
2133
|
+
*
|
|
2134
|
+
* This barrel is deliberately curated rather than `export *`. A star export
|
|
2135
|
+
* would promote every module internal to permanent public API — the exact
|
|
2136
|
+
* mistake `@lunora/do`'s own barrel documents having made once with the search
|
|
2137
|
+
* core. What is exported here is what a consumer actually needs.
|
|
2138
|
+
*/
|
|
2139
|
+
type AuthMetricsBucket, type ContextFetch, type ContextLogLevel, type ContextMetrics, type ContextTracer, DEFAULT_EXPLAIN_ISSUE_MODEL, type DanglingReference, type DanglingReferenceResult, type DatabaseInstrumentation, type DatabaseTally, type DatabaseTelemetryDeps, type ErrorIssue, type ExplainIssueArgs, type ExplainIssueDegradedReason, type ExplainIssueGrounding, type ExplainIssueResult, FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_MAX_PATHS, FUNCTION_METRICS_READ_LIMIT, FUNCTION_METRICS_SCANS_TABLE, FUNCTION_METRICS_TABLE, type FoldedTraces, type FunctionMetricBucket, type FunctionMetricBucketsResult, type FunctionMetricIndexHit, type HostSpanLike, type HostTracingLike, type HostTracingResolver, ISSUE_SEVERITIES, ISSUE_STATE_TABLE, ISSUE_STATUSES, type IndexHit, type IssueSeverity, type IssueState, type IssueStatePatch, type IssueStatus, type IssuesResult, LogBuffer, type LogEntry, type LogEventInput, type LogLevel, MIN_ADMIN_TOKEN_LENGTH, MIN_AUTH_SECRET_LENGTH, MetricBuffer, type MetricEvent, type MetricHistoryOptions, type MetricHistoryPoint, type MetricHistoryResult, type MetricHistorySeries, type MetricKind, type MetricSeries, type MetricsDeps, type QueryInsightBucket, type QueryInsightEntry, type QueryInsightsResult, type QueryStatEntry, REQUEST_LOG_TABLE, type ReadIssuesOptions, type ReadRequestLogOptions,
|
|
2140
|
+
/**
|
|
2141
|
+
* `@lunora/observability` — host-neutral telemetry storage and read models.
|
|
2142
|
+
*
|
|
2143
|
+
* Logs, traces, metrics, issue grouping and the security-audit view: the data
|
|
2144
|
+
* behind the Studio's observability pages. None of it is Cloudflare-specific —
|
|
2145
|
+
* it reads and writes through the SQL handle the engine hands it, and it lived
|
|
2146
|
+
* inside `@lunora/do` only because that is where the Durable Object it
|
|
2147
|
+
* instruments happens to live.
|
|
2148
|
+
*
|
|
2149
|
+
* The dependency runs `@lunora/do` → here → `@lunora/shard-engine`, never the
|
|
2150
|
+
* reverse, which is what makes this reusable by a second host rather than
|
|
2151
|
+
* merely relocated.
|
|
2152
|
+
*
|
|
2153
|
+
* This barrel is deliberately curated rather than `export *`. A star export
|
|
2154
|
+
* would promote every module internal to permanent public API — the exact
|
|
2155
|
+
* mistake `@lunora/do`'s own barrel documents having made once with the search
|
|
2156
|
+
* core. What is exported here is what a consumer actually needs.
|
|
2157
|
+
*/
|
|
2158
|
+
type RecordAuthEventInput, type RecordFunctionMetricInput, type RequestLogEntry, type RequestLogResult, type RequestLogWriteOptions, type RequestOutcome, type SecurityAuditResult, type SecurityFinding, type SecurityFindingKind, type SecurityFindingLevel, SpanBuffer, type SpanCollection, type SpanCollector, type SpanEvent, type SpanEventPoint, type SpanHandle, type OtlpSpanKind as SpanKind, type SpanLink, type SpanOptions, type TraceAnchor, type TraceSpan, type TraceSummary, type TracedFetchDeps, type TracerDeps, appendRequestLogEntry, buildSecurityAudit, createDatabaseTally, createMetrics, createSpanCollector, createTracedFetch, createTracer, dispatchRootSpan, emitLogEvent, emitRequestLogEvent, ensureAuthMetricsTables, ensureFunctionMetricsTables, ensureRequestLogTable, explainIssue, findDanglingReferences, foldTraces, formatTally, instrumentDatabase, mergeScanAttribution, parseExplainIssueArgs, parseLogArgs, readAuthMetrics, readErrorIssues, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetricScans, readFunctionMetrics, readFunctionMetricsTotals, readMetricHistory, readQueryInsights, readQueryMetrics, readRequestLog, recordAuthEvent, recordFunctionMetric, recordMetricHistory, recordQueryMetric, redactArgs, resolveTraceAnchor, upsertIssueState };
|