@lunora/observability 1.0.0-alpha.4 → 1.0.0-alpha.5
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/dist/index.d.mts +187 -37
- package/dist/index.d.ts +187 -37
- package/dist/index.mjs +1 -1
- package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-BB8kcFBe.mjs +56 -0
- package/dist/packem_shared/REQUEST_LOG_TABLE-BvpprPd_.mjs +1 -0
- package/dist/packem_shared/createDatabaseTally-DBs7wDeF.mjs +1 -0
- package/dist/packem_shared/createMetrics-BXNmqy0o.mjs +1 -0
- package/dist/packem_shared/readMetricHistory-CVcUgQEL.mjs +31 -0
- package/dist/packem_shared/readQueryInsights-DN07YcQy.mjs +32 -0
- package/dist/packem_shared/request-log-BwFic6VV.mjs +21 -0
- package/package.json +3 -3
- package/dist/packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-B1U7xWMo.mjs +0 -56
- package/dist/packem_shared/REQUEST_LOG_TABLE-Q_BYgAzY.mjs +0 -20
- package/dist/packem_shared/createDatabaseTally-BTqRpC0j.mjs +0 -1
- package/dist/packem_shared/createMetrics-fpVig63l.mjs +0 -1
- package/dist/packem_shared/log-fields-d5ouN1Pe.mjs +0 -1
- package/dist/packem_shared/readMetricHistory-DtnhufUq.mjs +0 -31
- package/dist/packem_shared/readQueryInsights-CwSKB0I1.mjs +0 -32
package/dist/index.d.mts
CHANGED
|
@@ -488,6 +488,15 @@ type HostTracingResolver = () => HostTracingLike | Promise<HostTracingLike | und
|
|
|
488
488
|
interface TracerDeps {
|
|
489
489
|
/** The trace this ctx's spans belong to. */
|
|
490
490
|
anchor: TraceAnchor;
|
|
491
|
+
/**
|
|
492
|
+
* Whether to record a span body's error message (and a `recordException`
|
|
493
|
+
* stacktrace) verbatim rather than redacted. Mirrors the request log's
|
|
494
|
+
* `captureRaw`: `true` in dev (`isDevEnvironment`), `false` in production —
|
|
495
|
+
* the span pipeline is the one sink third-party collectors (Datadog/Axiom
|
|
496
|
+
* via `otlpSink`) receive, so it must not ship raw PII/internals by default
|
|
497
|
+
* the way the request log and function-metrics sinks already don't.
|
|
498
|
+
*/
|
|
499
|
+
captureRaw?: boolean;
|
|
491
500
|
/** Function path the spans are attributed to. */
|
|
492
501
|
functionPath: string;
|
|
493
502
|
/**
|
|
@@ -544,11 +553,17 @@ interface SpanCollector {
|
|
|
544
553
|
* attributes become the canonical one-event-per-request summary. Sharing the
|
|
545
554
|
* implementation is what makes those two feel like the same API instead of two
|
|
546
555
|
* that happen to resemble each other.
|
|
556
|
+
*
|
|
557
|
+
* `captureRaw` (default `false`) gates `recordException`'s `exception.message`/
|
|
558
|
+
* `exception.stacktrace` the same way {@link createTracer} gates a span's own
|
|
559
|
+
* error message — a stack is file paths and internals by definition, the exact
|
|
560
|
+
* class `isInternalCode` redaction exists for, so it rides the same dev-only
|
|
561
|
+
* escape hatch rather than shipping to a third-party collector by default.
|
|
547
562
|
*/
|
|
548
563
|
declare const createSpanCollector: (ids: {
|
|
549
564
|
spanId: string;
|
|
550
565
|
traceId: string;
|
|
551
|
-
}) => SpanCollector;
|
|
566
|
+
}, captureRaw?: boolean) => SpanCollector;
|
|
552
567
|
/**
|
|
553
568
|
* Build the `ctx.trace` span factory for one dispatched function.
|
|
554
569
|
*
|
|
@@ -673,6 +688,14 @@ declare const createMetrics: (deps: MetricsDeps) => ContextMetrics;
|
|
|
673
688
|
*/
|
|
674
689
|
declare const dispatchRootSpan: (input: {
|
|
675
690
|
anchor: TraceAnchor;
|
|
691
|
+
/**
|
|
692
|
+
* Whether to record the failure message verbatim rather than redacted —
|
|
693
|
+
* the same dev-only escape hatch as {@link TracerDeps.captureRaw}. This
|
|
694
|
+
* synthetic root span carries the SAME error message the request log and
|
|
695
|
+
* function-metrics sinks already redact by default, so it must not be the
|
|
696
|
+
* one durable copy that ships it raw to a third-party collector.
|
|
697
|
+
*/
|
|
698
|
+
captureRaw?: boolean;
|
|
676
699
|
/**
|
|
677
700
|
* What the handler attached to the dispatch through `ctx.span` — the **wide
|
|
678
701
|
* event**. These are the attributes that would otherwise have been scattered
|
|
@@ -715,6 +738,14 @@ type DatabaseInstrumentation = "off" | "spans" | "summary";
|
|
|
715
738
|
interface DatabaseTelemetryDeps {
|
|
716
739
|
/** The trace produced spans belong to (`"spans"` mode only). */
|
|
717
740
|
anchor: TraceAnchor;
|
|
741
|
+
/**
|
|
742
|
+
* Whether to record a failed call's error message verbatim rather than
|
|
743
|
+
* redacted (`"spans"` mode only) — the same dev-only escape hatch as
|
|
744
|
+
* `TracerDeps.captureRaw`. A constraint-error message quotes the
|
|
745
|
+
* conflicting row, so this CLIENT span gets the same default-redacted
|
|
746
|
+
* posture as the request log and function-metrics sinks.
|
|
747
|
+
*/
|
|
748
|
+
captureRaw?: boolean;
|
|
718
749
|
/** Function path spans and attributes are attributed to. */
|
|
719
750
|
functionPath: string;
|
|
720
751
|
/** Detail level; see {@link DatabaseInstrumentation}. */
|
|
@@ -786,13 +817,23 @@ declare const FUNCTION_METRICS_BUCKET_RETENTION = 1440;
|
|
|
786
817
|
/**
|
|
787
818
|
* Maximum distinct function `path`s tracked in the accumulator table. Mirrors
|
|
788
819
|
* `query-metrics.ts`'s `QUERY_METRICS_MAX_STATEMENTS` cap (and exists for the
|
|
789
|
-
* same reason): the
|
|
790
|
-
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
820
|
+
* same reason): the real bound is the app's own registered-function set plus
|
|
821
|
+
* deploy churn (a rename/removal leaves its old path's row in place, still
|
|
822
|
+
* counted against the cap, until an operator's own retention/cleanup
|
|
823
|
+
* process — there is none built in today) — a few thousand registered
|
|
824
|
+
* functions is already far beyond any real app. `shard-do.ts`'s dispatch
|
|
825
|
+
* handler explicitly does NOT record per-function metrics for an
|
|
826
|
+
* unregistered/`FUNCTION_NOT_FOUND` dispatch (see the guard next to its
|
|
827
|
+
* `FUNCTION_NOT_FOUND` check), so a caller cannot mint arbitrary `path`s here
|
|
828
|
+
* the way a raw caller-supplied SQL shape can in `query-metrics.ts`. Without a
|
|
829
|
+
* cap, deploy churn across the app's lifetime would still grow
|
|
830
|
+
* `__lunora_metrics` (and its bucket/scan satellites) without bound,
|
|
831
|
+
* eventually filling the shard's SQLite store shared with the app's real
|
|
832
|
+
* data. At the cap, a brand-new path is refused — protecting the incumbent
|
|
833
|
+
* leaderboard from a flood of one-off paths is the point, so admission is
|
|
834
|
+
* refused rather than evicting an existing path to make room; already-tracked
|
|
835
|
+
* paths keep accumulating past the cap. `readFunctionMetricsTotals`'s
|
|
836
|
+
* `capped` is the read-side signal for this.
|
|
796
837
|
*/
|
|
797
838
|
declare const FUNCTION_METRICS_MAX_PATHS = 5e3;
|
|
798
839
|
/**
|
|
@@ -814,6 +855,22 @@ interface FunctionMetricBucket {
|
|
|
814
855
|
/** Subset of `calls` that threw. */
|
|
815
856
|
errors: number;
|
|
816
857
|
}
|
|
858
|
+
/** {@link readFunctionMetricBuckets} result: the time-series window plus whether the read limit cut it short. */
|
|
859
|
+
interface FunctionMetricBucketsResult {
|
|
860
|
+
buckets: (FunctionMetricBucket & {
|
|
861
|
+
path: string;
|
|
862
|
+
})[];
|
|
863
|
+
/**
|
|
864
|
+
* True when more rows existed than {@link FUNCTION_METRICS_READ_LIMIT} could
|
|
865
|
+
* return, so `buckets` is a partial (newest) window rather than the app's
|
|
866
|
+
* full retained history. Mirrors `readQueryInsights`'s `capped` and
|
|
867
|
+
* `foldTraces`'s `total`: a silently truncated read looks identical to a
|
|
868
|
+
* complete one to a caller that doesn't check for it — the Metrics chart's
|
|
869
|
+
* window would appear to shrink as the app grows, with a wrong leftmost
|
|
870
|
+
* bar, and nothing would say why.
|
|
871
|
+
*/
|
|
872
|
+
truncated: boolean;
|
|
873
|
+
}
|
|
817
874
|
/** One declared index a dispatch exercised (used to narrow a read). */
|
|
818
875
|
interface IndexHit {
|
|
819
876
|
/** The declared index name. */
|
|
@@ -879,7 +936,9 @@ interface RecordFunctionMetricInput {
|
|
|
879
936
|
* than baked into the `CREATE` so a shard whose `__lunora_metrics` predates the
|
|
880
937
|
* causal-attribution feature gains the column on the next call without a
|
|
881
938
|
* migration. SQLite has no `ADD COLUMN IF NOT EXISTS`, so the duplicate-column
|
|
882
|
-
* error from a re-run is swallowed.
|
|
939
|
+
* error from a re-run is swallowed. Both the `CREATE`s and the back-fill only
|
|
940
|
+
* run once per handle (see {@link ensuredHandles}) — a handle already marked
|
|
941
|
+
* ensured returns immediately.
|
|
883
942
|
*/
|
|
884
943
|
declare const ensureFunctionMetricsTables: (sql: SqlExec) => void;
|
|
885
944
|
/**
|
|
@@ -934,18 +993,21 @@ declare const readFunctionMetrics: (sql: SqlExec) => FunctionCallStat[];
|
|
|
934
993
|
/**
|
|
935
994
|
* Read the coarse time-series buckets for `path` (every path when omitted),
|
|
936
995
|
* oldest-bucket first so a chart can plot them left-to-right. Creates the table
|
|
937
|
-
* first so reads on a never-called shard return `[]`.
|
|
996
|
+
* first so reads on a never-called shard return `{ buckets: [], truncated: false }`.
|
|
938
997
|
*/
|
|
939
|
-
declare const readFunctionMetricBuckets: (sql: SqlExec, path?: string) =>
|
|
940
|
-
path: string;
|
|
941
|
-
})[];
|
|
998
|
+
declare const readFunctionMetricBuckets: (sql: SqlExec, path?: string) => FunctionMetricBucketsResult;
|
|
942
999
|
/**
|
|
943
1000
|
* Aggregate the persisted accumulators into the lifetime totals the metrics
|
|
944
|
-
* health snapshot reports: total calls (`requests`), total `errors
|
|
945
|
-
*
|
|
946
|
-
*
|
|
1001
|
+
* health snapshot reports: total calls (`requests`), total `errors`. Returns
|
|
1002
|
+
* zeroes on a never-called shard.
|
|
1003
|
+
*
|
|
1004
|
+
* `capped` is true when the distinct-path cap ({@link FUNCTION_METRICS_MAX_PATHS})
|
|
1005
|
+
* has been reached — the write-side signal that a brand-new function path is
|
|
1006
|
+
* currently being refused (see `admitPath`), mirroring `readQueryInsights`'s
|
|
1007
|
+
* `capped` for query-metrics and `readMetricHistory`'s for metric-history.
|
|
947
1008
|
*/
|
|
948
1009
|
declare const readFunctionMetricsTotals: (sql: SqlExec) => {
|
|
1010
|
+
capped: boolean;
|
|
949
1011
|
errors: number;
|
|
950
1012
|
requests: number;
|
|
951
1013
|
};
|
|
@@ -1261,6 +1323,17 @@ interface MetricHistorySeries {
|
|
|
1261
1323
|
}
|
|
1262
1324
|
/** {@link readMetricHistory} result: every tracked series with its buckets. */
|
|
1263
1325
|
interface MetricHistoryResult {
|
|
1326
|
+
/**
|
|
1327
|
+
* True when the distinct-series cap has been reached — a **write-side**
|
|
1328
|
+
* signal ("this shard can no longer admit a brand-new series", see
|
|
1329
|
+
* `admitNewSeries`), not a read-side truncation flag. It is computed over
|
|
1330
|
+
* the whole table regardless of `options.sinceMs`/the row-count read
|
|
1331
|
+
* limit, so it can be `true` even when every series `readMetricHistory`
|
|
1332
|
+
* actually returned fits comfortably: the caller should read it as "a
|
|
1333
|
+
* flood of new series would currently be refused", the same thing
|
|
1334
|
+
* `readQueryInsights`'s `capped` already signals for query-metrics.
|
|
1335
|
+
*/
|
|
1336
|
+
capped: boolean;
|
|
1264
1337
|
series: MetricHistorySeries[];
|
|
1265
1338
|
}
|
|
1266
1339
|
/**
|
|
@@ -1269,7 +1342,7 @@ interface MetricHistoryResult {
|
|
|
1269
1342
|
* omitted field keeps the historical behaviour.
|
|
1270
1343
|
*/
|
|
1271
1344
|
interface MetricHistoryOptions {
|
|
1272
|
-
/** Distinct series tracked before
|
|
1345
|
+
/** Distinct series tracked before the least-recently-updated one is evicted to admit a new one (default {@link METRIC_HISTORY_MAX_SERIES}). */
|
|
1273
1346
|
maxSeries?: number;
|
|
1274
1347
|
/** Minute-buckets kept per series before older rows are trimmed (default {@link METRIC_HISTORY_BUCKET_RETENTION}). */
|
|
1275
1348
|
retentionBuckets?: number;
|
|
@@ -1306,9 +1379,12 @@ declare const recordMetricHistory: (sql: SqlExec, event: MetricEvent, exemplarTr
|
|
|
1306
1379
|
* trend line reads oldest→newest.
|
|
1307
1380
|
*
|
|
1308
1381
|
* `options.sinceMs`, when set, returns only buckets at or after this epoch-ms —
|
|
1309
|
-
* the studio's time-window selector.
|
|
1382
|
+
* the studio's time-window selector. `options.maxSeries`, mirroring the write
|
|
1383
|
+
* side's tunable, is only used to compute `capped` — it does not affect which
|
|
1384
|
+
* rows are read.
|
|
1310
1385
|
*/
|
|
1311
1386
|
declare const readMetricHistory: (sql: SqlExec, options?: {
|
|
1387
|
+
maxSeries?: number;
|
|
1312
1388
|
sinceMs?: number;
|
|
1313
1389
|
}) => MetricHistoryResult;
|
|
1314
1390
|
/** One row of the `__lunora_metrics_queries` table, as returned by `readQueryMetrics`. */
|
|
@@ -1369,14 +1445,23 @@ interface QueryInsightsResult {
|
|
|
1369
1445
|
*/
|
|
1370
1446
|
declare const readQueryInsights: (sql: SqlExec, rangeMs: number, now?: number) => QueryInsightsResult;
|
|
1371
1447
|
/**
|
|
1372
|
-
* Record one statement
|
|
1448
|
+
* Record one statement's activity. Creates the table on first call. Silently
|
|
1373
1449
|
* skips recording when the normalised statement is empty (shouldn't happen
|
|
1374
1450
|
* in practice) or when the table is already at the
|
|
1375
1451
|
* {@link QUERY_METRICS_MAX_STATEMENTS} cap and the statement is not yet
|
|
1376
|
-
* tracked.
|
|
1377
|
-
*
|
|
1452
|
+
* tracked. See `admitStatement` for how the cap check avoids an unconditional
|
|
1453
|
+
* `COUNT(*)` on every execution.
|
|
1454
|
+
*
|
|
1455
|
+
* `execCount` (default 1) lets a caller fold several executions of the SAME
|
|
1456
|
+
* statement into one call — `shard-do.ts` does this per dispatch so a
|
|
1457
|
+
* query-in-a-loop handler pays one upsert here instead of one per raw
|
|
1458
|
+
* execution. `durationMs`/`rowsRead`/`rowsWritten` are then the SUM across
|
|
1459
|
+
* `execCount` executions, exactly as `exec_count`/`total_duration_ms` already
|
|
1460
|
+
* accumulate sums across separate calls — folding before calling is
|
|
1461
|
+
* indistinguishable, from this table's point of view, from `execCount`
|
|
1462
|
+
* separate calls with the same totals.
|
|
1378
1463
|
*/
|
|
1379
|
-
declare const recordQueryMetric: (sql: SqlExec, rawSql: string, durationMs: number, rowsRead: number, rowsWritten: number, now?: number) => void;
|
|
1464
|
+
declare const recordQueryMetric: (sql: SqlExec, rawSql: string, durationMs: number, rowsRead: number, rowsWritten: number, now?: number, execCount?: number) => void;
|
|
1380
1465
|
/**
|
|
1381
1466
|
* Read all tracked statement aggregates, ordered by `total_duration_ms DESC`
|
|
1382
1467
|
* (the leaderboard's default). Creates the table first so a read on a
|
|
@@ -1393,7 +1478,7 @@ interface RequestLogEntry {
|
|
|
1393
1478
|
cacheHit?: boolean;
|
|
1394
1479
|
/** Handler wall-clock duration in milliseconds (before the subscription write-flush, matching the per-function metrics). */
|
|
1395
1480
|
durationMs: number;
|
|
1396
|
-
/** Error message when `outcome === "error"
|
|
1481
|
+
/** Error message when `outcome === "error"`, redacted like args/identity; absent on success. */
|
|
1397
1482
|
errorMessage?: string;
|
|
1398
1483
|
/** The `<file>:<function>` identifier dispatched, e.g. `messages:list`. */
|
|
1399
1484
|
functionPath: string;
|
|
@@ -1477,11 +1562,16 @@ interface ErrorIssue {
|
|
|
1477
1562
|
culprit: string;
|
|
1478
1563
|
/** Wall-clock millis of the oldest folded row. */
|
|
1479
1564
|
firstSeen: number;
|
|
1480
|
-
/**
|
|
1565
|
+
/**
|
|
1566
|
+
* Stable 16-char grouping hash over `functionPath :: bucket(message)`,
|
|
1567
|
+
* computed from the RAW (pre-redaction) message at write time and stored on
|
|
1568
|
+
* the row — see {@link appendRequestLogEntry} — so redacting `sampleMessage`
|
|
1569
|
+
* below can't change the grouping.
|
|
1570
|
+
*/
|
|
1481
1571
|
hash: string;
|
|
1482
1572
|
/** Wall-clock millis of the newest folded row. */
|
|
1483
1573
|
lastSeen: number;
|
|
1484
|
-
/** A representative
|
|
1574
|
+
/** A representative error message (redacted, like the durable row) — taken from the most recent folded row. */
|
|
1485
1575
|
sampleMessage: string;
|
|
1486
1576
|
/** Developer-tagged severity from the persisted triage state; absent when untriaged. */
|
|
1487
1577
|
severity?: IssueSeverity;
|
|
@@ -1518,21 +1608,73 @@ interface ReadIssuesOptions {
|
|
|
1518
1608
|
/** Exact acting-userId match. */
|
|
1519
1609
|
userId?: string;
|
|
1520
1610
|
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Redact the secrets / PII out of a value before it reaches the durable log or a
|
|
1613
|
+
* Logpush event, via `@visulima/redact`'s `standardRules`. Unlike a blunt
|
|
1614
|
+
* type-tag stamp this masks sensitive values by PATTERN (not just by key name)
|
|
1615
|
+
* while leaving benign values readable, so the studio's args/identity columns
|
|
1616
|
+
* stay useful. `null` / `undefined` pass through unchanged.
|
|
1617
|
+
*
|
|
1618
|
+
* What `standardRules` actually catches differs by shape, verified against its
|
|
1619
|
+
* real behavior rather than assumed from its name: on a KEYED object (`args`,
|
|
1620
|
+
* `identity`) it also matches by key name, so `{ password: "hunter2" }` and
|
|
1621
|
+
* `{ token: "…" }` ARE masked regardless of the value's shape. On a PLAIN
|
|
1622
|
+
* STRING — which is what `errorMessage`/log `fields`-as-rendered-text are —
|
|
1623
|
+
* only pattern-shaped matches apply: emails, long digit runs / structured
|
|
1624
|
+
* numeric IDs (credit-card, phone, SSN, AWS-access-key-style), and an explicit
|
|
1625
|
+
* `Bearer <token>` / `token=…`-shaped substring. A free-text `password=hunter2`
|
|
1626
|
+
* or a bare provider API key embedded in prose (e.g. `sk-live-…`) is NOT
|
|
1627
|
+
* caught on a plain string — there is no key to match against, and neither is
|
|
1628
|
+
* a recognized value pattern. So this is a PII-pattern net for rendered text,
|
|
1629
|
+
* not a general secrets scrubber; a handler that echoes a raw credential into
|
|
1630
|
+
* an error message or a log string can still leak it through here. Works on a
|
|
1631
|
+
* plain string too (`redact` traverses whatever value it's handed), which is
|
|
1632
|
+
* how {@link appendRequestLogEntry} and {@link emitRequestLogEvent} reuse this
|
|
1633
|
+
* for `errorMessage` — a validation error echoes the offending value, a
|
|
1634
|
+
* constraint error quotes the conflicting row, so the error message is at
|
|
1635
|
+
* least as PII-dense as args and gets the same treatment (with the free-text
|
|
1636
|
+
* caveat above).
|
|
1637
|
+
*
|
|
1638
|
+
* `captureRaw` is the development escape hatch: in a dev environment the dispatch
|
|
1639
|
+
* site (`isDevEnvironment`) passes `true` to skip redaction so a developer can
|
|
1640
|
+
* see real arg/identity/error values; production always redacts. The dev
|
|
1641
|
+
* decision is made at the call site from the deployment env, never inferred
|
|
1642
|
+
* here — so a real deploy that omits the env var stays redacted.
|
|
1643
|
+
*/
|
|
1644
|
+
declare const redactArgs: (value: unknown, captureRaw?: boolean) => unknown;
|
|
1521
1645
|
/**
|
|
1522
1646
|
* Create the `__lunora_reqlog__` table. `seq` is an `AUTOINCREMENT` primary
|
|
1523
1647
|
* key, giving each shard a monotonic cursor the Logs tab pages through; the
|
|
1524
1648
|
* `args`/`identity`/`tables_read`/`tables_written` columns hold JSON and are
|
|
1525
1649
|
* `NULL`/empty when none was recorded. Idempotent, so read and write paths can
|
|
1526
1650
|
* call it defensively.
|
|
1651
|
+
*
|
|
1652
|
+
* `error_fingerprint` is the {@link fingerprintError} grouping hash captured
|
|
1653
|
+
* from the RAW `error_message` at write time, before {@link appendRequestLogEntry}
|
|
1654
|
+
* redacts it — see that function's docstring. It is added via a guarded
|
|
1655
|
+
* `ALTER TABLE` rather than baked into the `CREATE`, mirroring
|
|
1656
|
+
* `function-metrics.ts`'s `ensureFunctionMetricsTables`, so a shard whose
|
|
1657
|
+
* `__lunora_reqlog__` predates this column gains it on the next call without a
|
|
1658
|
+
* 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.
|
|
1527
1660
|
*/
|
|
1528
1661
|
declare const ensureRequestLogTable: (sql: SqlExec) => void;
|
|
1529
1662
|
/**
|
|
1530
1663
|
* Append one dispatch to the request log, then trim the log back to the most
|
|
1531
1664
|
* recent `retention` rows (default {@link REQUEST_LOG_RETENTION}). Creates the
|
|
1532
|
-
* table first so callers needn't. Args/identity are redacted here
|
|
1533
|
-
* never reaches the durable table — callers pass the unredacted
|
|
1534
|
-
* this, unless `captureRaw` (dev only) is set. `retention` is
|
|
1535
|
-
* `LUNORA_REQUEST_LOG_RETENTION` override, threaded in by the
|
|
1665
|
+
* table first so callers needn't. Args/identity/error message are redacted here
|
|
1666
|
+
* so a raw value never reaches the durable table — callers pass the unredacted
|
|
1667
|
+
* entry and rely on this, unless `captureRaw` (dev only) is set. `retention` is
|
|
1668
|
+
* the operator's `LUNORA_REQUEST_LOG_RETENTION` override, threaded in by the
|
|
1669
|
+
* dispatch site.
|
|
1670
|
+
*
|
|
1671
|
+
* The error-grouping fingerprint is computed from `entry.errorMessage` BEFORE
|
|
1672
|
+
* it's redacted below, and the resulting hash is persisted in
|
|
1673
|
+
* `error_fingerprint`. `readErrorIssues` groups off that stored hash instead of
|
|
1674
|
+
* recomputing `fingerprintError` from the (redacted) `error_message` column, so
|
|
1675
|
+
* masking a PII-bearing value — e.g. two different `<n>`-bucketed IDs that
|
|
1676
|
+
* redact to two different tag lengths (`<DL>` vs `<BANKACC>`) — can't split an
|
|
1677
|
+
* existing Issue or change its identity.
|
|
1536
1678
|
*/
|
|
1537
1679
|
declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry, options?: RequestLogWriteOptions) => void;
|
|
1538
1680
|
/**
|
|
@@ -1541,8 +1683,8 @@ declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry
|
|
|
1541
1683
|
* NOT reimplement a transport: it produces a richer, lunora-attributed event and
|
|
1542
1684
|
* lets CF's existing trace-log pipe ship it. The event mirrors the durable
|
|
1543
1685
|
* `__lunora_reqlog__` row (function path, shard, user, outcome, duration, tables
|
|
1544
|
-
* read/written, cache hit), with `args` AND `
|
|
1545
|
-
* durable write so no raw PII/secret reaches the log pipeline.
|
|
1686
|
+
* read/written, cache hit), with `args`, `identity`, AND `error` redacted
|
|
1687
|
+
* exactly like the durable write so no raw PII/secret reaches the log pipeline.
|
|
1546
1688
|
*
|
|
1547
1689
|
* An `error` outcome goes to `console.error` (surfacing at error level in the
|
|
1548
1690
|
* trace so a SIEM can alert on it); everything else to `console.log`. The
|
|
@@ -1591,11 +1733,19 @@ declare const parseLogArgs: (args: unknown[], boundFields?: LogFields) => {
|
|
|
1591
1733
|
*
|
|
1592
1734
|
* Structured `fields` (plus `traceId`/`spanId` for correlation) ARE emitted here
|
|
1593
1735
|
* — they are intentional metadata a log pipeline filters on, unlike raw `args`.
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
1596
|
-
*
|
|
1597
|
-
|
|
1598
|
-
|
|
1736
|
+
* Unlike `args`, `fields` IS redacted before it rides this console line — a
|
|
1737
|
+
* developer can attach anything to a fields bag (`ctx.log.info("charged",
|
|
1738
|
+
* { email, cardLast4 })`), and this is the one line that's told to a SIEM as
|
|
1739
|
+
* trustworthy, exactly like the request-log `args`/`identity`/`error` columns.
|
|
1740
|
+
* `options.captureRaw` (dev only) skips it, mirroring every other redaction
|
|
1741
|
+
* point in this module; the sole current caller (`ShardDO.recordUserLog`)
|
|
1742
|
+
* doesn't yet thread a dev flag through, so `fields` redacts unconditionally
|
|
1743
|
+
* there today — a conservative default, never a correctness gap. A field value
|
|
1744
|
+
* that can't be serialised (a circular object) would make `JSON.stringify`
|
|
1745
|
+
* throw and drop the whole line, so serialisation falls back to a fields-free
|
|
1746
|
+
* line rather than losing the event.
|
|
1747
|
+
*/
|
|
1748
|
+
declare const emitLogEvent: (input: LogEventInput, options?: RequestLogWriteOptions) => void;
|
|
1599
1749
|
/**
|
|
1600
1750
|
* Read request-log entries newest-first, AND-combining the supplied filters
|
|
1601
1751
|
* (function-path prefix, exact userId/shardKey/outcome, and a table-touched
|
|
@@ -1815,4 +1965,4 @@ declare const resolveTraceAnchor: (traceparent: string | undefined) => {
|
|
|
1815
1965
|
sampled: boolean;
|
|
1816
1966
|
traceId: string;
|
|
1817
1967
|
};
|
|
1818
|
-
export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AiRunBinding, type AppendRequestLogEntry, type AuthMetrics, type AuthMetricsBucket, type ContextFetch, type ContextLogLevel, type ContextMetrics, type ContextTracer, DEFAULT_EXPLAIN_ISSUE_MODEL, type DatabaseInstrumentation, type DatabaseTally, 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 FunctionMetricBucket, type FunctionMetricIndexHit, type HostTracingLike, 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 MetricHistoryOptions, type MetricHistoryPoint, type MetricHistorySeries, type MetricSeries, type MetricsDeps, type QueryStatEntry, REQUEST_LOG_TABLE, type RecordAuthEventInput, type RecordFunctionMetricInput, type RequestLogResult, type RequestLogWriteOptions, type SecurityAuditResult, type SecurityFinding, type SecurityFindingKind, type SecurityFindingLevel, SpanBuffer, type SpanCollection, type SpanCollector, type SpanHandle, type TraceAnchor, type TraceSpan, type TraceSummary, 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, resolveTraceAnchor, upsertIssueState };
|
|
1968
|
+
export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AiRunBinding, type AppendRequestLogEntry, type AuthMetrics, 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, 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 };
|
package/dist/index.d.ts
CHANGED
|
@@ -488,6 +488,15 @@ type HostTracingResolver = () => HostTracingLike | Promise<HostTracingLike | und
|
|
|
488
488
|
interface TracerDeps {
|
|
489
489
|
/** The trace this ctx's spans belong to. */
|
|
490
490
|
anchor: TraceAnchor;
|
|
491
|
+
/**
|
|
492
|
+
* Whether to record a span body's error message (and a `recordException`
|
|
493
|
+
* stacktrace) verbatim rather than redacted. Mirrors the request log's
|
|
494
|
+
* `captureRaw`: `true` in dev (`isDevEnvironment`), `false` in production —
|
|
495
|
+
* the span pipeline is the one sink third-party collectors (Datadog/Axiom
|
|
496
|
+
* via `otlpSink`) receive, so it must not ship raw PII/internals by default
|
|
497
|
+
* the way the request log and function-metrics sinks already don't.
|
|
498
|
+
*/
|
|
499
|
+
captureRaw?: boolean;
|
|
491
500
|
/** Function path the spans are attributed to. */
|
|
492
501
|
functionPath: string;
|
|
493
502
|
/**
|
|
@@ -544,11 +553,17 @@ interface SpanCollector {
|
|
|
544
553
|
* attributes become the canonical one-event-per-request summary. Sharing the
|
|
545
554
|
* implementation is what makes those two feel like the same API instead of two
|
|
546
555
|
* that happen to resemble each other.
|
|
556
|
+
*
|
|
557
|
+
* `captureRaw` (default `false`) gates `recordException`'s `exception.message`/
|
|
558
|
+
* `exception.stacktrace` the same way {@link createTracer} gates a span's own
|
|
559
|
+
* error message — a stack is file paths and internals by definition, the exact
|
|
560
|
+
* class `isInternalCode` redaction exists for, so it rides the same dev-only
|
|
561
|
+
* escape hatch rather than shipping to a third-party collector by default.
|
|
547
562
|
*/
|
|
548
563
|
declare const createSpanCollector: (ids: {
|
|
549
564
|
spanId: string;
|
|
550
565
|
traceId: string;
|
|
551
|
-
}) => SpanCollector;
|
|
566
|
+
}, captureRaw?: boolean) => SpanCollector;
|
|
552
567
|
/**
|
|
553
568
|
* Build the `ctx.trace` span factory for one dispatched function.
|
|
554
569
|
*
|
|
@@ -673,6 +688,14 @@ declare const createMetrics: (deps: MetricsDeps) => ContextMetrics;
|
|
|
673
688
|
*/
|
|
674
689
|
declare const dispatchRootSpan: (input: {
|
|
675
690
|
anchor: TraceAnchor;
|
|
691
|
+
/**
|
|
692
|
+
* Whether to record the failure message verbatim rather than redacted —
|
|
693
|
+
* the same dev-only escape hatch as {@link TracerDeps.captureRaw}. This
|
|
694
|
+
* synthetic root span carries the SAME error message the request log and
|
|
695
|
+
* function-metrics sinks already redact by default, so it must not be the
|
|
696
|
+
* one durable copy that ships it raw to a third-party collector.
|
|
697
|
+
*/
|
|
698
|
+
captureRaw?: boolean;
|
|
676
699
|
/**
|
|
677
700
|
* What the handler attached to the dispatch through `ctx.span` — the **wide
|
|
678
701
|
* event**. These are the attributes that would otherwise have been scattered
|
|
@@ -715,6 +738,14 @@ type DatabaseInstrumentation = "off" | "spans" | "summary";
|
|
|
715
738
|
interface DatabaseTelemetryDeps {
|
|
716
739
|
/** The trace produced spans belong to (`"spans"` mode only). */
|
|
717
740
|
anchor: TraceAnchor;
|
|
741
|
+
/**
|
|
742
|
+
* Whether to record a failed call's error message verbatim rather than
|
|
743
|
+
* redacted (`"spans"` mode only) — the same dev-only escape hatch as
|
|
744
|
+
* `TracerDeps.captureRaw`. A constraint-error message quotes the
|
|
745
|
+
* conflicting row, so this CLIENT span gets the same default-redacted
|
|
746
|
+
* posture as the request log and function-metrics sinks.
|
|
747
|
+
*/
|
|
748
|
+
captureRaw?: boolean;
|
|
718
749
|
/** Function path spans and attributes are attributed to. */
|
|
719
750
|
functionPath: string;
|
|
720
751
|
/** Detail level; see {@link DatabaseInstrumentation}. */
|
|
@@ -786,13 +817,23 @@ declare const FUNCTION_METRICS_BUCKET_RETENTION = 1440;
|
|
|
786
817
|
/**
|
|
787
818
|
* Maximum distinct function `path`s tracked in the accumulator table. Mirrors
|
|
788
819
|
* `query-metrics.ts`'s `QUERY_METRICS_MAX_STATEMENTS` cap (and exists for the
|
|
789
|
-
* same reason): the
|
|
790
|
-
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
820
|
+
* same reason): the real bound is the app's own registered-function set plus
|
|
821
|
+
* deploy churn (a rename/removal leaves its old path's row in place, still
|
|
822
|
+
* counted against the cap, until an operator's own retention/cleanup
|
|
823
|
+
* process — there is none built in today) — a few thousand registered
|
|
824
|
+
* functions is already far beyond any real app. `shard-do.ts`'s dispatch
|
|
825
|
+
* handler explicitly does NOT record per-function metrics for an
|
|
826
|
+
* unregistered/`FUNCTION_NOT_FOUND` dispatch (see the guard next to its
|
|
827
|
+
* `FUNCTION_NOT_FOUND` check), so a caller cannot mint arbitrary `path`s here
|
|
828
|
+
* the way a raw caller-supplied SQL shape can in `query-metrics.ts`. Without a
|
|
829
|
+
* cap, deploy churn across the app's lifetime would still grow
|
|
830
|
+
* `__lunora_metrics` (and its bucket/scan satellites) without bound,
|
|
831
|
+
* eventually filling the shard's SQLite store shared with the app's real
|
|
832
|
+
* data. At the cap, a brand-new path is refused — protecting the incumbent
|
|
833
|
+
* leaderboard from a flood of one-off paths is the point, so admission is
|
|
834
|
+
* refused rather than evicting an existing path to make room; already-tracked
|
|
835
|
+
* paths keep accumulating past the cap. `readFunctionMetricsTotals`'s
|
|
836
|
+
* `capped` is the read-side signal for this.
|
|
796
837
|
*/
|
|
797
838
|
declare const FUNCTION_METRICS_MAX_PATHS = 5e3;
|
|
798
839
|
/**
|
|
@@ -814,6 +855,22 @@ interface FunctionMetricBucket {
|
|
|
814
855
|
/** Subset of `calls` that threw. */
|
|
815
856
|
errors: number;
|
|
816
857
|
}
|
|
858
|
+
/** {@link readFunctionMetricBuckets} result: the time-series window plus whether the read limit cut it short. */
|
|
859
|
+
interface FunctionMetricBucketsResult {
|
|
860
|
+
buckets: (FunctionMetricBucket & {
|
|
861
|
+
path: string;
|
|
862
|
+
})[];
|
|
863
|
+
/**
|
|
864
|
+
* True when more rows existed than {@link FUNCTION_METRICS_READ_LIMIT} could
|
|
865
|
+
* return, so `buckets` is a partial (newest) window rather than the app's
|
|
866
|
+
* full retained history. Mirrors `readQueryInsights`'s `capped` and
|
|
867
|
+
* `foldTraces`'s `total`: a silently truncated read looks identical to a
|
|
868
|
+
* complete one to a caller that doesn't check for it — the Metrics chart's
|
|
869
|
+
* window would appear to shrink as the app grows, with a wrong leftmost
|
|
870
|
+
* bar, and nothing would say why.
|
|
871
|
+
*/
|
|
872
|
+
truncated: boolean;
|
|
873
|
+
}
|
|
817
874
|
/** One declared index a dispatch exercised (used to narrow a read). */
|
|
818
875
|
interface IndexHit {
|
|
819
876
|
/** The declared index name. */
|
|
@@ -879,7 +936,9 @@ interface RecordFunctionMetricInput {
|
|
|
879
936
|
* than baked into the `CREATE` so a shard whose `__lunora_metrics` predates the
|
|
880
937
|
* causal-attribution feature gains the column on the next call without a
|
|
881
938
|
* migration. SQLite has no `ADD COLUMN IF NOT EXISTS`, so the duplicate-column
|
|
882
|
-
* error from a re-run is swallowed.
|
|
939
|
+
* error from a re-run is swallowed. Both the `CREATE`s and the back-fill only
|
|
940
|
+
* run once per handle (see {@link ensuredHandles}) — a handle already marked
|
|
941
|
+
* ensured returns immediately.
|
|
883
942
|
*/
|
|
884
943
|
declare const ensureFunctionMetricsTables: (sql: SqlExec) => void;
|
|
885
944
|
/**
|
|
@@ -934,18 +993,21 @@ declare const readFunctionMetrics: (sql: SqlExec) => FunctionCallStat[];
|
|
|
934
993
|
/**
|
|
935
994
|
* Read the coarse time-series buckets for `path` (every path when omitted),
|
|
936
995
|
* oldest-bucket first so a chart can plot them left-to-right. Creates the table
|
|
937
|
-
* first so reads on a never-called shard return `[]`.
|
|
996
|
+
* first so reads on a never-called shard return `{ buckets: [], truncated: false }`.
|
|
938
997
|
*/
|
|
939
|
-
declare const readFunctionMetricBuckets: (sql: SqlExec, path?: string) =>
|
|
940
|
-
path: string;
|
|
941
|
-
})[];
|
|
998
|
+
declare const readFunctionMetricBuckets: (sql: SqlExec, path?: string) => FunctionMetricBucketsResult;
|
|
942
999
|
/**
|
|
943
1000
|
* Aggregate the persisted accumulators into the lifetime totals the metrics
|
|
944
|
-
* health snapshot reports: total calls (`requests`), total `errors
|
|
945
|
-
*
|
|
946
|
-
*
|
|
1001
|
+
* health snapshot reports: total calls (`requests`), total `errors`. Returns
|
|
1002
|
+
* zeroes on a never-called shard.
|
|
1003
|
+
*
|
|
1004
|
+
* `capped` is true when the distinct-path cap ({@link FUNCTION_METRICS_MAX_PATHS})
|
|
1005
|
+
* has been reached — the write-side signal that a brand-new function path is
|
|
1006
|
+
* currently being refused (see `admitPath`), mirroring `readQueryInsights`'s
|
|
1007
|
+
* `capped` for query-metrics and `readMetricHistory`'s for metric-history.
|
|
947
1008
|
*/
|
|
948
1009
|
declare const readFunctionMetricsTotals: (sql: SqlExec) => {
|
|
1010
|
+
capped: boolean;
|
|
949
1011
|
errors: number;
|
|
950
1012
|
requests: number;
|
|
951
1013
|
};
|
|
@@ -1261,6 +1323,17 @@ interface MetricHistorySeries {
|
|
|
1261
1323
|
}
|
|
1262
1324
|
/** {@link readMetricHistory} result: every tracked series with its buckets. */
|
|
1263
1325
|
interface MetricHistoryResult {
|
|
1326
|
+
/**
|
|
1327
|
+
* True when the distinct-series cap has been reached — a **write-side**
|
|
1328
|
+
* signal ("this shard can no longer admit a brand-new series", see
|
|
1329
|
+
* `admitNewSeries`), not a read-side truncation flag. It is computed over
|
|
1330
|
+
* the whole table regardless of `options.sinceMs`/the row-count read
|
|
1331
|
+
* limit, so it can be `true` even when every series `readMetricHistory`
|
|
1332
|
+
* actually returned fits comfortably: the caller should read it as "a
|
|
1333
|
+
* flood of new series would currently be refused", the same thing
|
|
1334
|
+
* `readQueryInsights`'s `capped` already signals for query-metrics.
|
|
1335
|
+
*/
|
|
1336
|
+
capped: boolean;
|
|
1264
1337
|
series: MetricHistorySeries[];
|
|
1265
1338
|
}
|
|
1266
1339
|
/**
|
|
@@ -1269,7 +1342,7 @@ interface MetricHistoryResult {
|
|
|
1269
1342
|
* omitted field keeps the historical behaviour.
|
|
1270
1343
|
*/
|
|
1271
1344
|
interface MetricHistoryOptions {
|
|
1272
|
-
/** Distinct series tracked before
|
|
1345
|
+
/** Distinct series tracked before the least-recently-updated one is evicted to admit a new one (default {@link METRIC_HISTORY_MAX_SERIES}). */
|
|
1273
1346
|
maxSeries?: number;
|
|
1274
1347
|
/** Minute-buckets kept per series before older rows are trimmed (default {@link METRIC_HISTORY_BUCKET_RETENTION}). */
|
|
1275
1348
|
retentionBuckets?: number;
|
|
@@ -1306,9 +1379,12 @@ declare const recordMetricHistory: (sql: SqlExec, event: MetricEvent, exemplarTr
|
|
|
1306
1379
|
* trend line reads oldest→newest.
|
|
1307
1380
|
*
|
|
1308
1381
|
* `options.sinceMs`, when set, returns only buckets at or after this epoch-ms —
|
|
1309
|
-
* the studio's time-window selector.
|
|
1382
|
+
* the studio's time-window selector. `options.maxSeries`, mirroring the write
|
|
1383
|
+
* side's tunable, is only used to compute `capped` — it does not affect which
|
|
1384
|
+
* rows are read.
|
|
1310
1385
|
*/
|
|
1311
1386
|
declare const readMetricHistory: (sql: SqlExec, options?: {
|
|
1387
|
+
maxSeries?: number;
|
|
1312
1388
|
sinceMs?: number;
|
|
1313
1389
|
}) => MetricHistoryResult;
|
|
1314
1390
|
/** One row of the `__lunora_metrics_queries` table, as returned by `readQueryMetrics`. */
|
|
@@ -1369,14 +1445,23 @@ interface QueryInsightsResult {
|
|
|
1369
1445
|
*/
|
|
1370
1446
|
declare const readQueryInsights: (sql: SqlExec, rangeMs: number, now?: number) => QueryInsightsResult;
|
|
1371
1447
|
/**
|
|
1372
|
-
* Record one statement
|
|
1448
|
+
* Record one statement's activity. Creates the table on first call. Silently
|
|
1373
1449
|
* skips recording when the normalised statement is empty (shouldn't happen
|
|
1374
1450
|
* in practice) or when the table is already at the
|
|
1375
1451
|
* {@link QUERY_METRICS_MAX_STATEMENTS} cap and the statement is not yet
|
|
1376
|
-
* tracked.
|
|
1377
|
-
*
|
|
1452
|
+
* tracked. See `admitStatement` for how the cap check avoids an unconditional
|
|
1453
|
+
* `COUNT(*)` on every execution.
|
|
1454
|
+
*
|
|
1455
|
+
* `execCount` (default 1) lets a caller fold several executions of the SAME
|
|
1456
|
+
* statement into one call — `shard-do.ts` does this per dispatch so a
|
|
1457
|
+
* query-in-a-loop handler pays one upsert here instead of one per raw
|
|
1458
|
+
* execution. `durationMs`/`rowsRead`/`rowsWritten` are then the SUM across
|
|
1459
|
+
* `execCount` executions, exactly as `exec_count`/`total_duration_ms` already
|
|
1460
|
+
* accumulate sums across separate calls — folding before calling is
|
|
1461
|
+
* indistinguishable, from this table's point of view, from `execCount`
|
|
1462
|
+
* separate calls with the same totals.
|
|
1378
1463
|
*/
|
|
1379
|
-
declare const recordQueryMetric: (sql: SqlExec, rawSql: string, durationMs: number, rowsRead: number, rowsWritten: number, now?: number) => void;
|
|
1464
|
+
declare const recordQueryMetric: (sql: SqlExec, rawSql: string, durationMs: number, rowsRead: number, rowsWritten: number, now?: number, execCount?: number) => void;
|
|
1380
1465
|
/**
|
|
1381
1466
|
* Read all tracked statement aggregates, ordered by `total_duration_ms DESC`
|
|
1382
1467
|
* (the leaderboard's default). Creates the table first so a read on a
|
|
@@ -1393,7 +1478,7 @@ interface RequestLogEntry {
|
|
|
1393
1478
|
cacheHit?: boolean;
|
|
1394
1479
|
/** Handler wall-clock duration in milliseconds (before the subscription write-flush, matching the per-function metrics). */
|
|
1395
1480
|
durationMs: number;
|
|
1396
|
-
/** Error message when `outcome === "error"
|
|
1481
|
+
/** Error message when `outcome === "error"`, redacted like args/identity; absent on success. */
|
|
1397
1482
|
errorMessage?: string;
|
|
1398
1483
|
/** The `<file>:<function>` identifier dispatched, e.g. `messages:list`. */
|
|
1399
1484
|
functionPath: string;
|
|
@@ -1477,11 +1562,16 @@ interface ErrorIssue {
|
|
|
1477
1562
|
culprit: string;
|
|
1478
1563
|
/** Wall-clock millis of the oldest folded row. */
|
|
1479
1564
|
firstSeen: number;
|
|
1480
|
-
/**
|
|
1565
|
+
/**
|
|
1566
|
+
* Stable 16-char grouping hash over `functionPath :: bucket(message)`,
|
|
1567
|
+
* computed from the RAW (pre-redaction) message at write time and stored on
|
|
1568
|
+
* the row — see {@link appendRequestLogEntry} — so redacting `sampleMessage`
|
|
1569
|
+
* below can't change the grouping.
|
|
1570
|
+
*/
|
|
1481
1571
|
hash: string;
|
|
1482
1572
|
/** Wall-clock millis of the newest folded row. */
|
|
1483
1573
|
lastSeen: number;
|
|
1484
|
-
/** A representative
|
|
1574
|
+
/** A representative error message (redacted, like the durable row) — taken from the most recent folded row. */
|
|
1485
1575
|
sampleMessage: string;
|
|
1486
1576
|
/** Developer-tagged severity from the persisted triage state; absent when untriaged. */
|
|
1487
1577
|
severity?: IssueSeverity;
|
|
@@ -1518,21 +1608,73 @@ interface ReadIssuesOptions {
|
|
|
1518
1608
|
/** Exact acting-userId match. */
|
|
1519
1609
|
userId?: string;
|
|
1520
1610
|
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Redact the secrets / PII out of a value before it reaches the durable log or a
|
|
1613
|
+
* Logpush event, via `@visulima/redact`'s `standardRules`. Unlike a blunt
|
|
1614
|
+
* type-tag stamp this masks sensitive values by PATTERN (not just by key name)
|
|
1615
|
+
* while leaving benign values readable, so the studio's args/identity columns
|
|
1616
|
+
* stay useful. `null` / `undefined` pass through unchanged.
|
|
1617
|
+
*
|
|
1618
|
+
* What `standardRules` actually catches differs by shape, verified against its
|
|
1619
|
+
* real behavior rather than assumed from its name: on a KEYED object (`args`,
|
|
1620
|
+
* `identity`) it also matches by key name, so `{ password: "hunter2" }` and
|
|
1621
|
+
* `{ token: "…" }` ARE masked regardless of the value's shape. On a PLAIN
|
|
1622
|
+
* STRING — which is what `errorMessage`/log `fields`-as-rendered-text are —
|
|
1623
|
+
* only pattern-shaped matches apply: emails, long digit runs / structured
|
|
1624
|
+
* numeric IDs (credit-card, phone, SSN, AWS-access-key-style), and an explicit
|
|
1625
|
+
* `Bearer <token>` / `token=…`-shaped substring. A free-text `password=hunter2`
|
|
1626
|
+
* or a bare provider API key embedded in prose (e.g. `sk-live-…`) is NOT
|
|
1627
|
+
* caught on a plain string — there is no key to match against, and neither is
|
|
1628
|
+
* a recognized value pattern. So this is a PII-pattern net for rendered text,
|
|
1629
|
+
* not a general secrets scrubber; a handler that echoes a raw credential into
|
|
1630
|
+
* an error message or a log string can still leak it through here. Works on a
|
|
1631
|
+
* plain string too (`redact` traverses whatever value it's handed), which is
|
|
1632
|
+
* how {@link appendRequestLogEntry} and {@link emitRequestLogEvent} reuse this
|
|
1633
|
+
* for `errorMessage` — a validation error echoes the offending value, a
|
|
1634
|
+
* constraint error quotes the conflicting row, so the error message is at
|
|
1635
|
+
* least as PII-dense as args and gets the same treatment (with the free-text
|
|
1636
|
+
* caveat above).
|
|
1637
|
+
*
|
|
1638
|
+
* `captureRaw` is the development escape hatch: in a dev environment the dispatch
|
|
1639
|
+
* site (`isDevEnvironment`) passes `true` to skip redaction so a developer can
|
|
1640
|
+
* see real arg/identity/error values; production always redacts. The dev
|
|
1641
|
+
* decision is made at the call site from the deployment env, never inferred
|
|
1642
|
+
* here — so a real deploy that omits the env var stays redacted.
|
|
1643
|
+
*/
|
|
1644
|
+
declare const redactArgs: (value: unknown, captureRaw?: boolean) => unknown;
|
|
1521
1645
|
/**
|
|
1522
1646
|
* Create the `__lunora_reqlog__` table. `seq` is an `AUTOINCREMENT` primary
|
|
1523
1647
|
* key, giving each shard a monotonic cursor the Logs tab pages through; the
|
|
1524
1648
|
* `args`/`identity`/`tables_read`/`tables_written` columns hold JSON and are
|
|
1525
1649
|
* `NULL`/empty when none was recorded. Idempotent, so read and write paths can
|
|
1526
1650
|
* call it defensively.
|
|
1651
|
+
*
|
|
1652
|
+
* `error_fingerprint` is the {@link fingerprintError} grouping hash captured
|
|
1653
|
+
* from the RAW `error_message` at write time, before {@link appendRequestLogEntry}
|
|
1654
|
+
* redacts it — see that function's docstring. It is added via a guarded
|
|
1655
|
+
* `ALTER TABLE` rather than baked into the `CREATE`, mirroring
|
|
1656
|
+
* `function-metrics.ts`'s `ensureFunctionMetricsTables`, so a shard whose
|
|
1657
|
+
* `__lunora_reqlog__` predates this column gains it on the next call without a
|
|
1658
|
+
* 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.
|
|
1527
1660
|
*/
|
|
1528
1661
|
declare const ensureRequestLogTable: (sql: SqlExec) => void;
|
|
1529
1662
|
/**
|
|
1530
1663
|
* Append one dispatch to the request log, then trim the log back to the most
|
|
1531
1664
|
* recent `retention` rows (default {@link REQUEST_LOG_RETENTION}). Creates the
|
|
1532
|
-
* table first so callers needn't. Args/identity are redacted here
|
|
1533
|
-
* never reaches the durable table — callers pass the unredacted
|
|
1534
|
-
* this, unless `captureRaw` (dev only) is set. `retention` is
|
|
1535
|
-
* `LUNORA_REQUEST_LOG_RETENTION` override, threaded in by the
|
|
1665
|
+
* table first so callers needn't. Args/identity/error message are redacted here
|
|
1666
|
+
* so a raw value never reaches the durable table — callers pass the unredacted
|
|
1667
|
+
* entry and rely on this, unless `captureRaw` (dev only) is set. `retention` is
|
|
1668
|
+
* the operator's `LUNORA_REQUEST_LOG_RETENTION` override, threaded in by the
|
|
1669
|
+
* dispatch site.
|
|
1670
|
+
*
|
|
1671
|
+
* The error-grouping fingerprint is computed from `entry.errorMessage` BEFORE
|
|
1672
|
+
* it's redacted below, and the resulting hash is persisted in
|
|
1673
|
+
* `error_fingerprint`. `readErrorIssues` groups off that stored hash instead of
|
|
1674
|
+
* recomputing `fingerprintError` from the (redacted) `error_message` column, so
|
|
1675
|
+
* masking a PII-bearing value — e.g. two different `<n>`-bucketed IDs that
|
|
1676
|
+
* redact to two different tag lengths (`<DL>` vs `<BANKACC>`) — can't split an
|
|
1677
|
+
* existing Issue or change its identity.
|
|
1536
1678
|
*/
|
|
1537
1679
|
declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry, options?: RequestLogWriteOptions) => void;
|
|
1538
1680
|
/**
|
|
@@ -1541,8 +1683,8 @@ declare const appendRequestLogEntry: (sql: SqlExec, entry: AppendRequestLogEntry
|
|
|
1541
1683
|
* NOT reimplement a transport: it produces a richer, lunora-attributed event and
|
|
1542
1684
|
* lets CF's existing trace-log pipe ship it. The event mirrors the durable
|
|
1543
1685
|
* `__lunora_reqlog__` row (function path, shard, user, outcome, duration, tables
|
|
1544
|
-
* read/written, cache hit), with `args` AND `
|
|
1545
|
-
* durable write so no raw PII/secret reaches the log pipeline.
|
|
1686
|
+
* read/written, cache hit), with `args`, `identity`, AND `error` redacted
|
|
1687
|
+
* exactly like the durable write so no raw PII/secret reaches the log pipeline.
|
|
1546
1688
|
*
|
|
1547
1689
|
* An `error` outcome goes to `console.error` (surfacing at error level in the
|
|
1548
1690
|
* trace so a SIEM can alert on it); everything else to `console.log`. The
|
|
@@ -1591,11 +1733,19 @@ declare const parseLogArgs: (args: unknown[], boundFields?: LogFields) => {
|
|
|
1591
1733
|
*
|
|
1592
1734
|
* Structured `fields` (plus `traceId`/`spanId` for correlation) ARE emitted here
|
|
1593
1735
|
* — they are intentional metadata a log pipeline filters on, unlike raw `args`.
|
|
1594
|
-
*
|
|
1595
|
-
*
|
|
1596
|
-
*
|
|
1597
|
-
|
|
1598
|
-
|
|
1736
|
+
* Unlike `args`, `fields` IS redacted before it rides this console line — a
|
|
1737
|
+
* developer can attach anything to a fields bag (`ctx.log.info("charged",
|
|
1738
|
+
* { email, cardLast4 })`), and this is the one line that's told to a SIEM as
|
|
1739
|
+
* trustworthy, exactly like the request-log `args`/`identity`/`error` columns.
|
|
1740
|
+
* `options.captureRaw` (dev only) skips it, mirroring every other redaction
|
|
1741
|
+
* point in this module; the sole current caller (`ShardDO.recordUserLog`)
|
|
1742
|
+
* doesn't yet thread a dev flag through, so `fields` redacts unconditionally
|
|
1743
|
+
* there today — a conservative default, never a correctness gap. A field value
|
|
1744
|
+
* that can't be serialised (a circular object) would make `JSON.stringify`
|
|
1745
|
+
* throw and drop the whole line, so serialisation falls back to a fields-free
|
|
1746
|
+
* line rather than losing the event.
|
|
1747
|
+
*/
|
|
1748
|
+
declare const emitLogEvent: (input: LogEventInput, options?: RequestLogWriteOptions) => void;
|
|
1599
1749
|
/**
|
|
1600
1750
|
* Read request-log entries newest-first, AND-combining the supplied filters
|
|
1601
1751
|
* (function-path prefix, exact userId/shardKey/outcome, and a table-touched
|
|
@@ -1815,4 +1965,4 @@ declare const resolveTraceAnchor: (traceparent: string | undefined) => {
|
|
|
1815
1965
|
sampled: boolean;
|
|
1816
1966
|
traceId: string;
|
|
1817
1967
|
};
|
|
1818
|
-
export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AiRunBinding, type AppendRequestLogEntry, type AuthMetrics, type AuthMetricsBucket, type ContextFetch, type ContextLogLevel, type ContextMetrics, type ContextTracer, DEFAULT_EXPLAIN_ISSUE_MODEL, type DatabaseInstrumentation, type DatabaseTally, 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 FunctionMetricBucket, type FunctionMetricIndexHit, type HostTracingLike, 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 MetricHistoryOptions, type MetricHistoryPoint, type MetricHistorySeries, type MetricSeries, type MetricsDeps, type QueryStatEntry, REQUEST_LOG_TABLE, type RecordAuthEventInput, type RecordFunctionMetricInput, type RequestLogResult, type RequestLogWriteOptions, type SecurityAuditResult, type SecurityFinding, type SecurityFindingKind, type SecurityFindingLevel, SpanBuffer, type SpanCollection, type SpanCollector, type SpanHandle, type TraceAnchor, type TraceSpan, type TraceSummary, 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, resolveTraceAnchor, upsertIssueState };
|
|
1968
|
+
export { AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AiRunBinding, type AppendRequestLogEntry, type AuthMetrics, 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, 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 };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AUTH_METRICS_BUCKETS_TABLE as t,AUTH_METRICS_BUCKET_MS as T,AUTH_METRICS_BUCKET_RETENTION as o,AUTH_METRICS_TABLE as
|
|
1
|
+
import{AUTH_METRICS_BUCKETS_TABLE as t,AUTH_METRICS_BUCKET_MS as T,AUTH_METRICS_BUCKET_RETENTION as o,AUTH_METRICS_TABLE as a,ensureAuthMetricsTables as s,readAuthMetrics as E,recordAuthEvent as _}from"./packem_shared/AUTH_METRICS_BUCKETS_TABLE-C2V-DiGu.mjs";import{createMetrics as S,createSpanCollector as I,createTracedFetch as i,createTracer as n,dispatchRootSpan as M}from"./packem_shared/createMetrics-BXNmqy0o.mjs";import{createDatabaseTally as A,formatTally as C,instrumentDatabase as N}from"./packem_shared/createDatabaseTally-DBs7wDeF.mjs";import{FUNCTION_METRICS_BUCKETS_TABLE as p,FUNCTION_METRICS_BUCKET_MS as d,FUNCTION_METRICS_BUCKET_RETENTION as f,FUNCTION_METRICS_INDEX_TABLE as R,FUNCTION_METRICS_MAX_PATHS as L,FUNCTION_METRICS_READ_LIMIT as m,FUNCTION_METRICS_SCANS_TABLE as x,FUNCTION_METRICS_TABLE as B,ensureFunctionMetricsTables as F,mergeScanAttribution as l,readFunctionMetricBuckets as g,readFunctionMetricIndexHits as O,readFunctionMetricScans as H,readFunctionMetrics as y,readFunctionMetricsTotals as D,recordFunctionMetric as K}from"./packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-BB8kcFBe.mjs";import{DEFAULT_EXPLAIN_ISSUE_MODEL as h,explainIssue as q,parseExplainIssueArgs as v}from"./packem_shared/DEFAULT_EXPLAIN_ISSUE_MODEL-xF11R4vX.mjs";import{ISSUE_SEVERITIES as G,ISSUE_STATE_TABLE as X,ISSUE_STATUSES as k,upsertIssueState as P}from"./packem_shared/ISSUE_SEVERITIES-Js6lGUG5.mjs";import{LogBuffer as V}from"./packem_shared/LogBuffer-bIvCelI-.mjs";import{M as j}from"./packem_shared/metric-buffer-CdgXal7w.mjs";import{readMetricHistory as J,recordMetricHistory as Y}from"./packem_shared/readMetricHistory-CVcUgQEL.mjs";import{readQueryInsights as $,readQueryMetrics as ee,recordQueryMetric as re}from"./packem_shared/readQueryInsights-DN07YcQy.mjs";import{d as Te,k as oe,K as ae,w as se,m as Ee,C as _e,W as ce,U as Se,c as Ie}from"./packem_shared/request-log-BwFic6VV.mjs";import{MIN_ADMIN_TOKEN_LENGTH as ne,MIN_AUTH_SECRET_LENGTH as Me,buildSecurityAudit as ue}from"./packem_shared/MIN_ADMIN_TOKEN_LENGTH-T-zuwRVR.mjs";import{SpanBuffer as Ce,foldTraces as Ne}from"./packem_shared/SpanBuffer-BR6Ff0M-.mjs";import{findDanglingReferences as pe}from"./packem_shared/findDanglingReferences-D-x9LvY0.mjs";import{r as fe}from"./packem_shared/trace-context-DrdF960P.mjs";export{t as AUTH_METRICS_BUCKETS_TABLE,T as AUTH_METRICS_BUCKET_MS,o as AUTH_METRICS_BUCKET_RETENTION,a as AUTH_METRICS_TABLE,h as DEFAULT_EXPLAIN_ISSUE_MODEL,p as FUNCTION_METRICS_BUCKETS_TABLE,d as FUNCTION_METRICS_BUCKET_MS,f as FUNCTION_METRICS_BUCKET_RETENTION,R as FUNCTION_METRICS_INDEX_TABLE,L as FUNCTION_METRICS_MAX_PATHS,m as FUNCTION_METRICS_READ_LIMIT,x as FUNCTION_METRICS_SCANS_TABLE,B as FUNCTION_METRICS_TABLE,G as ISSUE_SEVERITIES,X as ISSUE_STATE_TABLE,k as ISSUE_STATUSES,V as LogBuffer,ne as MIN_ADMIN_TOKEN_LENGTH,Me as MIN_AUTH_SECRET_LENGTH,j as MetricBuffer,Te as REQUEST_LOG_TABLE,Ce as SpanBuffer,oe as appendRequestLogEntry,ue as buildSecurityAudit,A as createDatabaseTally,S as createMetrics,I as createSpanCollector,i as createTracedFetch,n as createTracer,M as dispatchRootSpan,ae as emitLogEvent,se as emitRequestLogEvent,s as ensureAuthMetricsTables,F as ensureFunctionMetricsTables,Ee as ensureRequestLogTable,q as explainIssue,pe as findDanglingReferences,Ne as foldTraces,C as formatTally,N as instrumentDatabase,l as mergeScanAttribution,v as parseExplainIssueArgs,_e as parseLogArgs,E as readAuthMetrics,ce as readErrorIssues,g as readFunctionMetricBuckets,O as readFunctionMetricIndexHits,H as readFunctionMetricScans,y as readFunctionMetrics,D as readFunctionMetricsTotals,J as readMetricHistory,$ as readQueryInsights,ee as readQueryMetrics,Se as readRequestLog,_ as recordAuthEvent,K as recordFunctionMetric,Y as recordMetricHistory,re as recordQueryMetric,Ie as redactArgs,fe as resolveTraceAnchor,P as upsertIssueState};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const E="__lunora_metrics",l="__lunora_metrics_buckets",c="__lunora_metrics_scans",d="__lunora_metrics_index",M=6e4,U=1440,F=5e3,p=1e3,r=(e,a,...s)=>e.exec.call(e,a,...s),A=e=>Math.floor(e/6e4)*6e4,I=e=>{const a=new Map;for(const s of e)a.set(`${s.table}\0${s.index}`,{index:s.index,table:s.table});return[...a.values()]},N=new WeakSet,_=e=>{if(!N.has(e)){r(e,`CREATE TABLE IF NOT EXISTS "${E}" (
|
|
2
|
+
path TEXT PRIMARY KEY,
|
|
3
|
+
calls INTEGER NOT NULL DEFAULT 0,
|
|
4
|
+
errors INTEGER NOT NULL DEFAULT 0,
|
|
5
|
+
conflicts INTEGER NOT NULL DEFAULT 0,
|
|
6
|
+
scans INTEGER NOT NULL DEFAULT 0,
|
|
7
|
+
total_duration_ms REAL NOT NULL DEFAULT 0,
|
|
8
|
+
min_duration_ms REAL,
|
|
9
|
+
max_duration_ms REAL NOT NULL DEFAULT 0,
|
|
10
|
+
last_called_at REAL NOT NULL DEFAULT 0,
|
|
11
|
+
last_error_at REAL,
|
|
12
|
+
last_error_message TEXT
|
|
13
|
+
)`);for(const a of["scans","conflicts"])try{r(e,`ALTER TABLE "${E}" ADD COLUMN ${a} INTEGER NOT NULL DEFAULT 0`)}catch{}r(e,`CREATE TABLE IF NOT EXISTS "${l}" (
|
|
14
|
+
path TEXT NOT NULL,
|
|
15
|
+
bucket_ms INTEGER NOT NULL,
|
|
16
|
+
calls INTEGER NOT NULL DEFAULT 0,
|
|
17
|
+
errors INTEGER NOT NULL DEFAULT 0,
|
|
18
|
+
PRIMARY KEY (path, bucket_ms)
|
|
19
|
+
)`),r(e,`CREATE TABLE IF NOT EXISTS "${c}" (
|
|
20
|
+
path TEXT NOT NULL,
|
|
21
|
+
table_name TEXT NOT NULL,
|
|
22
|
+
scans INTEGER NOT NULL DEFAULT 0,
|
|
23
|
+
PRIMARY KEY (path, table_name)
|
|
24
|
+
)`),r(e,`CREATE TABLE IF NOT EXISTS "${d}" (
|
|
25
|
+
table_name TEXT NOT NULL,
|
|
26
|
+
index_name TEXT NOT NULL,
|
|
27
|
+
reads INTEGER NOT NULL DEFAULT 0,
|
|
28
|
+
PRIMARY KEY (table_name, index_name)
|
|
29
|
+
)`),N.add(e)}},L=new WeakMap,R=e=>{let a=L.get(e);return a===void 0&&(a=new Set,L.set(e,a)),a},O=(e,a)=>{const s=R(e);return s.has(a)?!0:r(e,`SELECT 1 AS c FROM "${E}" WHERE path = ? LIMIT 1`,a).toArray().length>0?(s.add(a),!0):r(e,`SELECT COUNT(*) AS n FROM "${E}"`).one().n>=5e3?!1:(s.add(a),!0)},b=(e,a)=>{if(_(e),!O(e,a.path))return;const s=a.scannedTables?[...new Set(a.scannedTables)]:[],t=s.length,n=I(a.indexHits??[]),T=a.errored?1:0,i=a.conflicted?1:0,u=a.errored?a.ts:null,m=a.errored?a.errorMessage??null:null;r(e,`INSERT INTO "${E}"
|
|
30
|
+
(path, calls, errors, conflicts, scans, total_duration_ms, min_duration_ms, max_duration_ms, last_called_at, last_error_at, last_error_message)
|
|
31
|
+
VALUES (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
32
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
33
|
+
calls = calls + 1,
|
|
34
|
+
errors = errors + excluded.errors,
|
|
35
|
+
conflicts = conflicts + excluded.conflicts,
|
|
36
|
+
scans = scans + excluded.scans,
|
|
37
|
+
total_duration_ms = total_duration_ms + excluded.total_duration_ms,
|
|
38
|
+
min_duration_ms = MIN(COALESCE(min_duration_ms, excluded.min_duration_ms), excluded.min_duration_ms),
|
|
39
|
+
max_duration_ms = MAX(max_duration_ms, excluded.max_duration_ms),
|
|
40
|
+
last_called_at = excluded.last_called_at,
|
|
41
|
+
last_error_at = CASE WHEN excluded.last_error_at IS NULL THEN last_error_at ELSE excluded.last_error_at END,
|
|
42
|
+
last_error_message = CASE WHEN excluded.last_error_at IS NULL THEN last_error_message ELSE excluded.last_error_message END`,a.path,T,i,t,a.durationMs,a.durationMs,a.durationMs,a.ts,u,m);const S=A(a.ts);r(e,`INSERT INTO "${l}" (path, bucket_ms, calls, errors)
|
|
43
|
+
VALUES (?, ?, 1, ?)
|
|
44
|
+
ON CONFLICT(path, bucket_ms) DO UPDATE SET
|
|
45
|
+
calls = calls + 1,
|
|
46
|
+
errors = errors + excluded.errors`,a.path,S,T),r(e,`DELETE FROM "${l}"
|
|
47
|
+
WHERE path = ?
|
|
48
|
+
AND bucket_ms <= (
|
|
49
|
+
SELECT MAX(bucket_ms) - ? FROM "${l}" WHERE path = ?
|
|
50
|
+
)`,a.path,1440*6e4,a.path);for(const o of s)r(e,`INSERT INTO "${c}" (path, table_name, scans)
|
|
51
|
+
VALUES (?, ?, 1)
|
|
52
|
+
ON CONFLICT(path, table_name) DO UPDATE SET
|
|
53
|
+
scans = scans + 1`,a.path,o);for(const o of n)r(e,`INSERT INTO "${d}" (table_name, index_name, reads)
|
|
54
|
+
VALUES (?, ?, 1)
|
|
55
|
+
ON CONFLICT(table_name, index_name) DO UPDATE SET
|
|
56
|
+
reads = reads + 1`,o.table,o.index)},C=e=>{_(e);const a=r(e,`SELECT path, table_name, scans FROM "${c}" ORDER BY scans DESC, path ASC, table_name ASC LIMIT ${String(1e3)}`).toArray(),s=new Map;for(const t of a){const n=s.get(t.path),T={scans:t.scans,table:t.table_name};n===void 0?s.set(t.path,[T]):n.push(T)}return s},h=e=>(_(e),r(e,`SELECT table_name, index_name, reads FROM "${d}" ORDER BY table_name ASC, index_name ASC LIMIT ${String(1e3)}`).toArray().map(a=>({index:a.index_name,reads:a.reads,table:a.table_name}))),D=(e,a)=>{for(const s of a){const t=e.find(n=>n.table===s);t===void 0?e.push({scans:1,table:s}):t.scans+=1}return e.sort((s,t)=>t.scans-s.scans||s.table.localeCompare(t.table)),e},x=e=>{_(e);const a=C(e);return r(e,`SELECT * FROM "${E}" ORDER BY last_called_at DESC LIMIT ${String(1e3)}`).toArray().map(s=>({calls:s.calls,conflicts:s.conflicts,errors:s.errors,lastCalledAt:s.last_called_at,lastErrorAt:s.last_error_at,lastErrorMessage:s.last_error_message,maxDurationMs:s.max_duration_ms,path:s.path,scannedTables:a.get(s.path)??[],scans:s.scans,totalDurationMs:s.total_duration_ms}))},$=(e,a)=>{_(e);const s=a===void 0?r(e,`SELECT path, bucket_ms, calls, errors FROM "${l}" ORDER BY bucket_ms DESC, path ASC LIMIT ${String(1001)}`).toArray():r(e,`SELECT path, bucket_ms, calls, errors FROM "${l}" WHERE path = ? ORDER BY bucket_ms DESC LIMIT ${String(1001)}`,a).toArray(),t=s.length>1e3;return{buckets:(t?s.slice(0,1e3):s).toReversed().map(n=>({bucketMs:n.bucket_ms,calls:n.calls,errors:n.errors,path:n.path})),truncated:t}},f=e=>{_(e);const a=r(e,`SELECT SUM(calls) AS requests, SUM(errors) AS errors FROM "${E}"`).one();return{capped:r(e,`SELECT COUNT(*) AS n FROM "${E}"`).one().n>=5e3,errors:a.errors??0,requests:a.requests??0}};export{l as FUNCTION_METRICS_BUCKETS_TABLE,M as FUNCTION_METRICS_BUCKET_MS,U as FUNCTION_METRICS_BUCKET_RETENTION,d as FUNCTION_METRICS_INDEX_TABLE,F as FUNCTION_METRICS_MAX_PATHS,p as FUNCTION_METRICS_READ_LIMIT,c as FUNCTION_METRICS_SCANS_TABLE,E as FUNCTION_METRICS_TABLE,_ as ensureFunctionMetricsTables,D as mergeScanAttribution,$ as readFunctionMetricBuckets,h as readFunctionMetricIndexHits,C as readFunctionMetricScans,x as readFunctionMetrics,f as readFunctionMetricsTotals,b as recordFunctionMetric};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"@lunora/fingerprint";import"@visulima/redact";import{E as o,d as E,k as g,K as L,w as m,m as p,C as R,W as d,U as n,c as u,y as T}from"./request-log-BwFic6VV.mjs";import"./ISSUE_SEVERITIES-Js6lGUG5.mjs";export{o as REQUEST_LOG_RETENTION,E as REQUEST_LOG_TABLE,g as appendRequestLogEntry,L as emitLogEvent,m as emitRequestLogEvent,p as ensureRequestLogTable,R as parseLogArgs,d as readErrorIssues,n as readRequestLog,u as redactArgs,T as renderLogMessage};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{O as h,t as m}from"./trace-context-DrdF960P.mjs";import{c as b}from"./request-log-BwFic6VV.mjs";const M=100,w=new Set(["aggregate","count","delete","deleteMany","deleteWhere","findFirst","findFirstOrThrow","findMany","get","groupBy","insert","insertMany","insertManyUnsafe","lookupById","patch","patchMany","patchWhere","rank","rankBefore","rankPage","rankPageRows","replace","restore"]),k=new Set(["aggregate","count","deleteWhere","findFirst","findFirstOrThrow","findMany","groupBy","insert","insertMany","insertManyUnsafe","patchWhere","rank","rankBefore","rankPage","rankPageRows"]),O=(t,r)=>{if(!k.has(t))return;const e=r[0];return typeof e=="string"&&e.length>0?e:void 0},T=t=>t instanceof Error?t.message:typeof t=="string"?t:JSON.stringify(t)??String(t),I=t=>{const{deps:r,durationMs:e,failure:n,operation:s,startTs:a,table:o}=t;return{attributes:{"db.operation.name":s,...o===void 0?{}:{"db.collection.name":o},"db.system.name":"sqlite"},durationMs:e,...n===void 0?{}:{error:{message:b(T(n),r.captureRaw),type:m(n)}},functionPath:r.functionPath,kind:"client",name:o===void 0?`db.${s}`:`db.${s} ${o}`,ok:n===void 0,parentSpanId:r.anchor.rootSpanId,shardKey:r.shardKey,spanId:h(8),startTs:a,traceId:r.anchor.traceId,userId:r.userId()}},S=(t,r)=>{if(r.mode==="off")return t;const{tally:e}=r,n=new Map;return new Proxy(t,{get(s,a,o){const d=Reflect.get(s,a,o);if(typeof a!="string"||typeof d!="function"||!w.has(a))return d;const p=n.get(a);if(p!==void 0)return p;const y=d,u=async(...f)=>{const l=Date.now(),g=O(a,f);let c;try{return await y.apply(s,f)}catch(i){throw c=i,i}finally{const i=Date.now()-l;e.calls+=1,e.durationMs+=i,e.perOperation[a]=(e.perOperation[a]??0)+1,c!==void 0&&(e.errors+=1);try{r.mode==="spans"&&(e.spansEmitted>=M?e.spansTruncated=!0:(e.spansEmitted+=1,r.record(I({deps:r,durationMs:i,failure:c,operation:a,startTs:l,table:g}))))}catch{}}};return n.set(a,u),u}})},E=()=>({calls:0,durationMs:0,errors:0,perOperation:{},spansEmitted:0,spansTruncated:!1}),B=t=>{const r={"db.calls":t.calls,"db.duration_ms":t.durationMs};t.errors>0&&(r["db.errors"]=t.errors),t.spansTruncated&&(r["db.spans_truncated"]=!0);for(const[e,n]of Object.entries(t.perOperation))r[`db.op.${e}`]=n;return r};export{E as createDatabaseTally,B as formatTally,S as instrumentDatabase};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{c as K,n as g}from"./request-log-BwFic6VV.mjs";import{w as b,O as M,m as R,t as T}from"./trace-context-DrdF960P.mjs";const x=/[\w.-]/u,D=t=>{let e="";for(const r of t)e+=x.test(r)?r:"_";return e},L=t=>{if(typeof t.name!="string"||t.name.length===0)throw new Error("recordEvaluation requires a non-empty `name`");if(typeof t.score!="number"||!Number.isFinite(t.score))throw new Error("recordEvaluation `score` must be a finite number");const e=D(t.name),r={[`gen_ai.evaluation.${e}.score`]:t.score};return t.label!==void 0&&(r[`gen_ai.evaluation.${e}.label`]=t.label),r},_=t=>{const e=Object.keys(t);return e.length>0&&e.every(r=>r==="attributes"||r==="kind"||r==="links")},F=128,H=128,U=t=>{try{const e=new URL(t);return`${e.protocol}//${e.host}${e.pathname}`}catch{return t}},q=t=>{try{return new URL(t).host}catch{return t}},C=(t,e)=>{try{return t(new URL(e))}catch{return!1}},N=t=>t===void 0?{}:_(t)?t:{attributes:t},z=(t,e)=>{if(t.isTraced){t.setAttribute(b.functionPath,e.functionPath),t.setAttribute(b.ok,e.ok),t.setAttribute(b.durationMs,e.durationMs),e.shardKey!==void 0&&t.setAttribute(b.shardKey,e.shardKey),e.userId!==void 0&&t.setAttribute(b.userId,e.userId),e.error!==void 0&&(t.setAttribute(b.errorType,e.error.type),t.setAttribute(b.errorMessage,e.error.message));for(const[r,s]of Object.entries(e.attributes))(typeof s=="boolean"||typeof s=="number"||typeof s=="string")&&t.setAttribute(`lunora.attr.${r}`,s)}},B=(t,e=!1)=>{const r={attributes:{},events:[],links:[]},s={spanContext:()=>t,addEvent:(n,a)=>{if(r.events.length>=F)return;const o=g(a);r.events.push({...o===void 0?{}:{attributes:o},name:n,ts:Date.now()})},addLink:n=>{if(r.links.length>=H)return;const a=g(n.attributes);r.links.push({...a===void 0?{}:{attributes:a},spanId:n.spanId,traceId:n.traceId})},recordEvaluation:n=>{Object.assign(r.attributes,g(L(n)))},recordException:n=>{const a=n instanceof Error?n.message:String(n);s.addEvent("exception",{"exception.message":K(a,e),...e&&n instanceof Error&&typeof n.stack=="string"?{"exception.stacktrace":n.stack}:{},"exception.type":T(n)})},setAttribute:(n,a)=>{Object.assign(r.attributes,g({[n]:a}))},setAttributes:n=>{Object.assign(r.attributes,g(n))}};return{collected:r,handle:s}},Q=t=>{const{anchor:e,captureRaw:r=!1,fuseHostSpans:s,functionPath:n,record:a,resolveHostTracing:o,shardKey:i,userId:h}=t,u=p=>async(m,d,f)=>{const y=M(8),c=Date.now(),I=N(f),j=g(I.attributes),{collected:w,handle:O}=B({spanId:y,traceId:e.traceId},r),P=async v=>{let k=!0,S;try{return await d(u(y),O)}catch(l){k=!1;const E=l instanceof Error?l.message:String(l);throw S={message:K(E,r),type:T(l)},l}finally{const l=Date.now()-c,E=h(),A={...j,...w.attributes},$=[...I.links??[],...w.links];try{a({...Object.keys(A).length===0?{}:{attributes:A},durationMs:l,...w.events.length===0?{}:{events:w.events},...S===void 0?{}:{error:S},functionPath:n,...I.kind===void 0||I.kind==="internal"?{}:{kind:I.kind},...$.length===0?{}:{links:$},name:m,ok:k,parentSpanId:p,shardKey:i,spanId:y,startTs:c,traceId:e.traceId,userId:E})}catch{}if(v!==void 0)try{z(v,{attributes:A,durationMs:l,error:S,functionPath:n,ok:k,shardKey:i,userId:E})}catch{}}};if(s===!0&&o!==void 0){const v=await o();if(v!==void 0&&typeof v.enterSpan=="function")return await v.enterSpan(m,k=>P(k))}return await P()};return u(e.rootSpanId)},V=(t,e)=>{const{anchor:r,functionPath:s,propagate:n=!0,record:a,shardKey:o,userId:i}=t;return async(h,u)=>{const p=M(8),m=Date.now(),d=new Request(h,u);(typeof n=="function"?C(n,d.url):n)&&d.headers.set("traceparent",R(r.traceId,p,r.sampled??!0));let f,y;try{const c=await e(d);return y=c.status,c.ok||(f={message:`HTTP ${String(c.status)}`,type:`HTTP_${String(c.status)}`}),c}catch(c){throw f={message:c instanceof Error?c.message:String(c),type:T(c)},c}finally{try{a({attributes:{"http.request.method":d.method,...y===void 0?{}:{"http.response.status_code":y},"url.full":U(d.url)},durationMs:Date.now()-m,...f===void 0?{}:{error:f},functionPath:s,kind:"client",name:`${d.method} ${q(d.url)}`,ok:f===void 0,parentSpanId:r.rootSpanId,shardKey:o,spanId:p,startTs:m,traceId:r.traceId,userId:i()})}catch{}}}},W=t=>{const{functionPath:e,record:r,shardKey:s}=t,n=(a,o,i,h)=>{if(!Number.isFinite(i))return;const u=g(h);try{r({...u===void 0?{}:{attributes:u},functionPath:e,kind:a,name:o,shardKey:s,ts:Date.now(),value:i})}catch{}};return{count:(a,o=1,i)=>{n("counter",a,o,i)},gauge:(a,o,i)=>{n("gauge",a,o,i)},record:(a,o,i)=>{n("histogram",a,o,i)}}},X=t=>{const{anchor:e,captureRaw:r=!1,collected:s,durationMs:n,failure:a,functionPath:o,shardKey:i,startTs:h,userId:u}=t,p=s?.attributes??{};return{...Object.keys(p).length===0?{}:{attributes:p},dispatch:!0,durationMs:n,...s===void 0||s.events.length===0?{}:{events:s.events},...a===void 0?{}:{error:{message:K(a.thrown instanceof Error?a.thrown.message:String(a.thrown),r),type:T(a.thrown)}},functionPath:o,...s===void 0||s.links.length===0?{}:{links:s.links},name:o,ok:a===void 0,parentSpanId:"",shardKey:i,spanId:e.rootSpanId,startTs:h,traceId:e.traceId,userId:u}};export{z as applyHostSpanAttributes,W as createMetrics,B as createSpanCollector,V as createTracedFetch,Q as createTracer,X as dispatchRootSpan};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import{m as O,e as S}from"./metric-buffer-CdgXal7w.mjs";const a="__lunora_metric_history",o=6e4,x=1440,d=1e3,l=5e3,n=(e,s,...T)=>e.exec.call(e,s,...T),y=e=>Math.floor(e/o)*o,_=new WeakSet,k=e=>{_.has(e)||(n(e,`CREATE TABLE IF NOT EXISTS "${a}" (
|
|
2
|
+
series_key TEXT NOT NULL,
|
|
3
|
+
bucket_ms INTEGER NOT NULL,
|
|
4
|
+
name TEXT NOT NULL,
|
|
5
|
+
kind TEXT NOT NULL,
|
|
6
|
+
attrs TEXT NOT NULL DEFAULT '{}',
|
|
7
|
+
function_path TEXT NOT NULL DEFAULT '',
|
|
8
|
+
shard_key TEXT,
|
|
9
|
+
count INTEGER NOT NULL DEFAULT 0,
|
|
10
|
+
sum REAL NOT NULL DEFAULT 0,
|
|
11
|
+
min REAL NOT NULL DEFAULT 0,
|
|
12
|
+
max REAL NOT NULL DEFAULT 0,
|
|
13
|
+
last REAL NOT NULL DEFAULT 0,
|
|
14
|
+
last_ts REAL NOT NULL DEFAULT 0,
|
|
15
|
+
exemplar_trace TEXT,
|
|
16
|
+
PRIMARY KEY (series_key, bucket_ms)
|
|
17
|
+
)`),_.add(e))},R=4096,N=new WeakMap,M=e=>{let s=N.get(e);return s===void 0&&(s=new Set,N.set(e,s)),s},p=(e,s)=>n(e,`SELECT COUNT(DISTINCT series_key) AS n FROM "${a}"`).one().n<s,h=(e,s,T,i={})=>{const c=i.maxSeries??d,t=i.retentionBuckets??x;k(e);const r=O(s),E=y(s.ts),u=M(e),L=`${r}\0${E.toString()}`,m=u.has(L)||n(e,`SELECT 1 AS c FROM "${a}" WHERE series_key = ? AND bucket_ms = ? LIMIT 1`,r,E).toArray().length>0;if(!m&&!(n(e,`SELECT 1 AS c FROM "${a}" WHERE series_key = ? LIMIT 1`,r).toArray().length>0)&&!p(e,c))return;const A=T??null;n(e,`INSERT INTO "${a}"
|
|
18
|
+
(series_key, bucket_ms, name, kind, attrs, function_path, shard_key, count, sum, min, max, last, last_ts, exemplar_trace)
|
|
19
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)
|
|
20
|
+
ON CONFLICT(series_key, bucket_ms) DO UPDATE SET
|
|
21
|
+
count = count + 1,
|
|
22
|
+
sum = sum + excluded.sum,
|
|
23
|
+
min = MIN(min, excluded.min),
|
|
24
|
+
max = MAX(max, excluded.max),
|
|
25
|
+
last = excluded.last,
|
|
26
|
+
last_ts = excluded.last_ts,
|
|
27
|
+
exemplar_trace = CASE WHEN excluded.exemplar_trace IS NULL THEN exemplar_trace ELSE excluded.exemplar_trace END`,r,E,s.name,s.kind,S(s.attributes??{}),s.functionPath,s.shardKey??null,s.value,s.value,s.value,s.value,s.ts,A),m||n(e,`DELETE FROM "${a}"
|
|
28
|
+
WHERE series_key = ?
|
|
29
|
+
AND bucket_ms <= (
|
|
30
|
+
SELECT MAX(bucket_ms) - ? FROM "${a}" WHERE series_key = ?
|
|
31
|
+
)`,r,t*o,r),m&&!u.has(L)&&(u.size>=R&&u.clear(),u.add(L))},U=e=>{if(!(e===""||e==="{}"))try{const s=JSON.parse(e);return s!==null&&typeof s=="object"?s:void 0}catch{return}},D=(e,s={})=>{k(e);const T=s.maxSeries??d,i=s.sinceMs===void 0?n(e,`SELECT * FROM "${a}" ORDER BY bucket_ms DESC LIMIT ?`,l).toArray():n(e,`SELECT * FROM "${a}" WHERE bucket_ms >= ? ORDER BY bucket_ms DESC LIMIT ?`,s.sinceMs,l).toArray(),c=new Map;for(const t of i){let r=c.get(t.series_key);if(r===void 0){const E=U(t.attrs);r={...E===void 0?{}:{attributes:E},functionPath:t.function_path,kind:t.kind,name:t.name,points:[],...t.shard_key===null?{}:{shardKey:t.shard_key}},c.set(t.series_key,r)}r.points.push({bucketMs:t.bucket_ms,count:t.count,...t.exemplar_trace===null?{}:{exemplarTraceId:t.exemplar_trace},last:t.last,max:t.max,min:t.min,sum:t.sum})}for(const t of c.values())t.points.sort((r,E)=>r.bucketMs-E.bucketMs);return{capped:n(e,`SELECT COUNT(DISTINCT series_key) AS n FROM "${a}"`).one().n>=T,series:[...c.values()]}};export{D as readMetricHistory,h as recordMetricHistory};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const i="__lunora_metrics_queries",m="__lunora_metrics_queries_buckets",f=6e4,B=90,E=[1,2,5,10,25,50,100,250,500,1e3,5e3],R=512,I=500,o=(e,t,...r)=>e.exec.call(e,t,...r),k=e=>{let t=e.replaceAll(/'(?:[^']|'')*'/g,"?").replaceAll(/\b0x[\da-f]+\b/gi,"?").replaceAll(/(?<=[=,([\s])\d+(?:\.\d+)?/g,"?").replaceAll(/\s+/g," ").trim();return t.length>R&&(t=`${t.slice(0,R-1)}…`),t},U=new WeakSet,N=e=>{U.has(e)||(o(e,`CREATE TABLE IF NOT EXISTS "${i}" (
|
|
2
|
+
normalized_sql TEXT PRIMARY KEY,
|
|
3
|
+
exec_count INTEGER NOT NULL DEFAULT 0,
|
|
4
|
+
total_duration_ms REAL NOT NULL DEFAULT 0,
|
|
5
|
+
rows_read INTEGER NOT NULL DEFAULT 0,
|
|
6
|
+
rows_written INTEGER NOT NULL DEFAULT 0
|
|
7
|
+
)`),U.add(e))},M=new WeakMap,w=e=>Math.floor(e/6e4)*6e4,C=e=>{let t=2166136261;for(let r=0;r<e.length;r+=1)t^=e.codePointAt(r)??0,t=Math.imul(t,16777619)>>>0;return t.toString(16).padStart(8,"0")},b=e=>{const t=E.findIndex(r=>e<=r);return t===-1?E.length:t},T=e=>`lat_${String(e)}`,h=new WeakSet,D=e=>{if(h.has(e))return;const t=Array.from({length:E.length+1},(r,n)=>`${T(n)} INTEGER NOT NULL DEFAULT 0`).join(", ");o(e,`CREATE TABLE IF NOT EXISTS "${m}" (
|
|
8
|
+
sql_hash TEXT NOT NULL,
|
|
9
|
+
bucket_ms INTEGER NOT NULL,
|
|
10
|
+
exec_count INTEGER NOT NULL DEFAULT 0,
|
|
11
|
+
total_duration_ms REAL NOT NULL DEFAULT 0,
|
|
12
|
+
rows_read INTEGER NOT NULL DEFAULT 0,
|
|
13
|
+
rows_written INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
${t},
|
|
15
|
+
PRIMARY KEY (sql_hash, bucket_ms)
|
|
16
|
+
)`),h.add(e)},$=(e,t)=>{o(e,`DELETE FROM "${m}" WHERE bucket_ms < ?`,w(t)-90*6e4)},q=(e,t,r,n,u,_,s=1)=>{try{D(e);const c=s>0?r/s:r,l=T(b(c)),S=`INSERT INTO "${m}" (sql_hash, bucket_ms, exec_count, total_duration_ms, rows_read, rows_written, ${l})
|
|
17
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
18
|
+
ON CONFLICT(sql_hash, bucket_ms) DO UPDATE SET
|
|
19
|
+
exec_count = exec_count + excluded.exec_count,
|
|
20
|
+
total_duration_ms = total_duration_ms + excluded.total_duration_ms,
|
|
21
|
+
rows_read = rows_read + excluded.rows_read,
|
|
22
|
+
rows_written = rows_written + excluded.rows_written,
|
|
23
|
+
${l} = ${l} + excluded.${l}`;o(e,S,C(t),w(_),s,r,n,u,s);const a=w(_);M.get(e)!==a&&(M.set(e,a),$(e,_))}catch{}},x=(e,t)=>{const r=e.reduce((_,s)=>_+s,0);if(r===0)return 0;const n=r*t;let u=0;for(const[_,s]of e.entries())if(u+=s,u>=n)return E[_]??E.at(-1)??0;return E.at(-1)??0},Y=(e,t,r=Date.now())=>{try{N(e),D(e)}catch{return{buckets:[],capped:!1,entries:[],trackedStatements:0}}const n=w(r-t),u=Array.from({length:E.length+1},(a,d)=>`SUM(${T(d)}) AS ${T(d)}`).join(", "),_=o(e,`SELECT sql_hash, SUM(exec_count) AS exec_count, SUM(total_duration_ms) AS total_duration_ms,
|
|
24
|
+
SUM(rows_read) AS rows_read, SUM(rows_written) AS rows_written, ${u}
|
|
25
|
+
FROM "${m}" WHERE bucket_ms >= ? GROUP BY sql_hash`,n).toArray(),s=new Map;for(const a of o(e,`SELECT normalized_sql FROM "${i}"`))s.set(C(a.normalized_sql),a.normalized_sql);const c=_.map(a=>{const d=Array.from({length:E.length+1},(y,g)=>Number(a[T(g)]??0)),L=Number(a.exec_count??0),A=Number(a.total_duration_ms??0);return{avgDurationMs:L>0?A/L:0,execCount:L,normalizedSql:s.get(String(a.sql_hash))??String(a.sql_hash),p50DurationMs:x(d,.5),p95DurationMs:x(d,.95),rowsRead:Number(a.rows_read??0),rowsWritten:Number(a.rows_written??0),totalDurationMs:A}});c.sort((a,d)=>d.totalDurationMs-a.totalDurationMs);const l=o(e,`SELECT bucket_ms, SUM(exec_count) AS exec_count, SUM(total_duration_ms) AS total_duration_ms
|
|
26
|
+
FROM "${m}" WHERE bucket_ms >= ? GROUP BY bucket_ms ORDER BY bucket_ms ASC`,n).toArray().map(a=>({avgDurationMs:a.exec_count>0?a.total_duration_ms/a.exec_count:0,bucketMs:a.bucket_ms,execCount:a.exec_count})),S=o(e,`SELECT COUNT(*) AS n FROM "${i}"`).one().n;return{buckets:l,capped:S>=I,entries:c,trackedStatements:S}},O=new WeakMap,F=e=>{let t=O.get(e);return t===void 0&&(t=new Set,O.set(e,t)),t},p=(e,t)=>{const r=F(e);return r.has(t)?!0:o(e,`SELECT 1 AS c FROM "${i}" WHERE normalized_sql = ? LIMIT 1`,t).toArray().length>0?(r.add(t),!0):o(e,`SELECT COUNT(*) AS n FROM "${i}"`).one().n>=I?!1:(r.add(t),!0)},Q=(e,t,r,n,u,_=Date.now(),s=1)=>{const c=k(t);if(c.length===0||(N(e),!p(e,c)))return;const l=`INSERT INTO "${i}" (normalized_sql, exec_count, total_duration_ms, rows_read, rows_written)
|
|
27
|
+
VALUES (?, ?, ?, ?, ?)
|
|
28
|
+
ON CONFLICT(normalized_sql) DO UPDATE SET
|
|
29
|
+
exec_count = exec_count + excluded.exec_count,
|
|
30
|
+
total_duration_ms = total_duration_ms + excluded.total_duration_ms,
|
|
31
|
+
rows_read = rows_read + excluded.rows_read,
|
|
32
|
+
rows_written = rows_written + excluded.rows_written`;o(e,l,c,s,r,n,u),q(e,c,r,n,u,_,s)},z=e=>(N(e),o(e,`SELECT normalized_sql, exec_count, total_duration_ms, rows_read, rows_written FROM "${i}" ORDER BY total_duration_ms DESC`).toArray().map(t=>({execCount:t.exec_count,normalizedSql:t.normalized_sql,rowsRead:t.rows_read,rowsWritten:t.rows_written,totalDurationMs:t.total_duration_ms})));export{E as LATENCY_BUCKET_EDGES,m as QUERY_BUCKETS_TABLE,f as QUERY_BUCKET_MS,B as QUERY_BUCKET_RETENTION,R as QUERY_METRICS_MAX_SQL_LEN,I as QUERY_METRICS_MAX_STATEMENTS,i as QUERY_METRICS_TABLE,D as ensureQueryBucketsTable,N as ensureQueryMetricsTable,C as hashStatement,b as latencyBucketIndex,k as normalizeSql,x as percentileFrom,$ as pruneQueryBuckets,Y as readQueryInsights,z as readQueryMetrics,q as recordQueryBucket,Q as recordQueryMetric};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import{fingerprintError as I}from"@lunora/fingerprint";import{redact as L,standardRules as R}from"@visulima/redact";import{readIssueStates as M}from"./ISSUE_SEVERITIES-Js6lGUG5.mjs";const P=e=>{if(typeof e=="string")return e;try{return JSON.stringify(e)??String(e)}catch{return String(e)}},m=e=>typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:P(e),v=(e,r)=>{if(e===void 0&&r===void 0)return;const o={};if(r!==void 0)for(const[t,s]of Object.entries(r))o[t]=m(s);if(e!==void 0)for(const[t,s]of Object.entries(e))o[t]=m(s);return Object.keys(o).length===0?void 0:o},d="__lunora_reqlog__",E=1e3,b="lunora",h=(e,r,...o)=>e.exec.call(e,r,...o),c=(e,r=!1)=>r||e===null||e===void 0?e:L(e,R),y=e=>{h(e,`CREATE TABLE IF NOT EXISTS "${d}" (
|
|
2
|
+
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3
|
+
ts REAL NOT NULL,
|
|
4
|
+
function_path TEXT NOT NULL,
|
|
5
|
+
shard_key TEXT,
|
|
6
|
+
user_id TEXT,
|
|
7
|
+
identity TEXT,
|
|
8
|
+
args TEXT,
|
|
9
|
+
outcome TEXT NOT NULL,
|
|
10
|
+
error_message TEXT,
|
|
11
|
+
error_fingerprint TEXT,
|
|
12
|
+
duration_ms REAL NOT NULL,
|
|
13
|
+
tables_read TEXT NOT NULL DEFAULT '[]',
|
|
14
|
+
tables_written TEXT NOT NULL DEFAULT '[]',
|
|
15
|
+
cache_hit INTEGER,
|
|
16
|
+
subscriptions_rerun INTEGER NOT NULL DEFAULT 0
|
|
17
|
+
)`);try{h(e,`ALTER TABLE "${d}" ADD COLUMN error_fingerprint TEXT`)}catch{}},S=e=>JSON.stringify([...new Set(e)].toSorted((r,o)=>r.localeCompare(o))),w=e=>e===void 0?null:e?1:0,x=(e,r,o={})=>{y(e);const t=o.captureRaw??!1,s=o.retention??E,i=r.outcome==="error"&&r.errorMessage!==void 0?I({functionPath:r.functionPath,message:r.errorMessage}).hash:void 0;h(e,`INSERT INTO "${d}"
|
|
18
|
+
(ts, function_path, shard_key, user_id, identity, args, outcome, error_message, error_fingerprint, duration_ms, tables_read, tables_written, cache_hit, subscriptions_rerun)
|
|
19
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,r.ts,r.functionPath,r.shardKey??null,r.userId??null,r.identity===void 0?null:JSON.stringify(c(r.identity,t)),r.redactedArgs===void 0?null:JSON.stringify(c(r.redactedArgs,t)),r.outcome,r.errorMessage===void 0?null:c(r.errorMessage,t),i??null,r.durationMs,S(r.tablesRead),S(r.tablesWritten),w(r.cacheHit),r.subscriptionsReRun??0),h(e,`DELETE FROM "${d}" WHERE seq <= (SELECT MAX(seq) - ? FROM "${d}")`,s)},D=(e,r={})=>{const o=r.captureRaw??!1,t={args:e.redactedArgs===void 0?void 0:c(e.redactedArgs,o),cacheHit:e.cacheHit,durationMs:e.durationMs,error:e.errorMessage===void 0?void 0:c(e.errorMessage,o),function:e.functionPath,identity:e.identity===void 0?void 0:c(e.identity,o),outcome:e.outcome,shard:e.shardKey,source:b,tablesRead:e.tablesRead??[],tablesWritten:e.tablesWritten??[],ts:e.ts,type:"request",userId:e.userId},s=JSON.stringify(t);e.outcome==="error"?console.error(s):console.log(s)},K="log",U=e=>e.map(r=>{if(typeof r=="string")return r;try{return JSON.stringify(r)??String(r)}catch{return String(r)}}).join(" "),C=e=>{if(typeof e!="object"||e===null||Array.isArray(e))return!1;const r=Object.getPrototypeOf(e);return r===Object.prototype||r===null},J=(e,r)=>e.length===2&&typeof e[0]=="string"&&C(e[1])?{fields:v(e[1],r),message:e[0]}:{fields:v(void 0,r),message:U(e)},k=(e,r={})=>{const o=r.captureRaw??!1,t={fields:e.fields===void 0?void 0:c(e.fields,o),function:e.functionPath,level:e.level,message:e.message,shard:e.shardKey,source:b,spanId:e.spanId,traceId:e.traceId,ts:e.ts,type:K,userId:e.userId};let s;try{s=JSON.stringify(t)}catch{s=JSON.stringify({...t,fields:void 0})}e.level==="error"||e.level==="fatal"?console.error(s):e.level==="warn"?console.warn(s):console.log(s)},g=e=>e.replaceAll(/[\\%_]/g,r=>`\\${r}`),N=e=>{try{const r=JSON.parse(e);return Array.isArray(r)?r.filter(o=>typeof o=="string"):[]}catch{return[]}},j=(e,r={})=>{y(e);const o=Math.max(1,Math.min(r.limit??E,1e4)),t=["seq > ?"],s=[r.sinceSeq??0];if(r.functionPathPrefix!==void 0&&r.functionPathPrefix!==""&&(t.push(String.raw`function_path LIKE ? ESCAPE '\'`),s.push(`${g(r.functionPathPrefix)}%`)),r.userId!==void 0&&r.userId!==""&&(t.push("user_id = ?"),s.push(r.userId)),r.shardKey!==void 0&&r.shardKey!==""&&(t.push("shard_key = ?"),s.push(r.shardKey)),r.outcome!==void 0&&(t.push("outcome = ?"),s.push(r.outcome)),r.tableTouched!==void 0&&r.tableTouched!==""){const i=`%${g(JSON.stringify(r.tableTouched))}%`;t.push(String.raw`(tables_read LIKE ? ESCAPE '\' OR tables_written LIKE ? ESCAPE '\')`),s.push(i,i)}return s.push(o),h(e,`SELECT seq, ts, function_path, shard_key, user_id, identity, args, outcome, error_message, duration_ms, tables_read, tables_written, cache_hit, subscriptions_rerun
|
|
20
|
+
FROM "${d}" WHERE ${t.join(" AND ")} ORDER BY seq DESC LIMIT ?`,...s).toArray().map(i=>{const n={durationMs:i.duration_ms,functionPath:i.function_path,outcome:i.outcome==="error"?"error":"ok",seq:i.seq,subscriptionsReRun:i.subscriptions_rerun,tablesRead:N(i.tables_read),tablesWritten:N(i.tables_written),ts:i.ts};return i.shard_key!==null&&(n.shardKey=i.shard_key),i.user_id!==null&&(n.userId=i.user_id),i.identity!==null&&(n.identity=JSON.parse(i.identity)),i.args!==null&&(n.redactedArgs=JSON.parse(i.args)),i.error_message!==null&&(n.errorMessage=i.error_message),i.cache_hit!==null&&(n.cacheHit=i.cache_hit===1),n})},q=(e,r)=>{const o=M(e,[...r.keys()]);for(const t of r.values()){const s=o.get(t.hash);s!==void 0&&(t.stateUpdatedAt=s.updatedAt,s.assignee!==void 0&&(t.assignee=s.assignee),s.severity!==void 0&&(t.severity=s.severity),t.status=s.status==="resolved"&&t.lastSeen>s.updatedAt?"open":s.status)}},W=(e,r={})=>{y(e);const o=Math.max(1,Math.min(r.limit??E,1e4)),t=["outcome = 'error'"],s=[];r.functionPathPrefix!==void 0&&r.functionPathPrefix!==""&&(t.push(String.raw`function_path LIKE ? ESCAPE '\'`),s.push(`${g(r.functionPathPrefix)}%`)),r.userId!==void 0&&r.userId!==""&&(t.push("user_id = ?"),s.push(r.userId)),r.shardKey!==void 0&&r.shardKey!==""&&(t.push("shard_key = ?"),s.push(r.shardKey)),s.push(o);const i=h(e,`SELECT function_path, error_message, error_fingerprint, ts
|
|
21
|
+
FROM "${d}" WHERE ${t.join(" AND ")} ORDER BY seq DESC LIMIT ?`,...s).toArray(),n=new Map,p=new Map;for(const a of i){const f=a.error_message??"",{culprit:O,hash:A,title:T}=I({functionPath:a.function_path,message:f}),l=a.error_fingerprint??A,u=n.get(l);if(u===void 0){n.set(l,{count:1,culprit:O,firstSeen:a.ts,hash:l,lastSeen:a.ts,sampleMessage:f,status:"open",title:T}),p.set(l,a.ts);continue}u.count+=1,u.firstSeen=Math.min(u.firstSeen,a.ts),u.lastSeen=Math.max(u.lastSeen,a.ts),a.ts>(p.get(l)??Number.NEGATIVE_INFINITY)&&(p.set(l,a.ts),u.sampleMessage=f,u.title=T)}q(e,n);const _=[...n.values()];return(r.status===void 0?_:_.filter(a=>a.status===r.status)).toSorted((a,f)=>f.lastSeen-a.lastSeen)};export{J as C,E,k as K,j as U,W,c,d,x as k,y as m,v as n,D as w,U as y};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/observability",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.5",
|
|
4
4
|
"description": "Host-neutral telemetry storage and read models for Lunora, backing the Studio's Logs, Traces, Metrics and Issues views",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lunora",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
46
|
+
"@lunora/errors": "1.0.0-alpha.12",
|
|
47
47
|
"@lunora/fingerprint": "1.0.0-alpha.4",
|
|
48
|
-
"@lunora/shard-engine": "1.0.0-alpha.
|
|
48
|
+
"@lunora/shard-engine": "1.0.0-alpha.6",
|
|
49
49
|
"@visulima/redact": "3.0.0"
|
|
50
50
|
},
|
|
51
51
|
"engines": {
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
const n="__lunora_metrics",_="__lunora_metrics_buckets",c="__lunora_metrics_scans",N="__lunora_metrics_index",I=6e4,R=1440,O=5e3,C=1e3,t=(a,s,...e)=>a.exec.call(a,s,...e),u=a=>Math.floor(a/6e4)*6e4,S=a=>{const s=new Map;for(const e of a)s.set(`${e.table}\0${e.index}`,{index:e.index,table:e.table});return[...s.values()]},l=a=>{t(a,`CREATE TABLE IF NOT EXISTS "${n}" (
|
|
2
|
-
path TEXT PRIMARY KEY,
|
|
3
|
-
calls INTEGER NOT NULL DEFAULT 0,
|
|
4
|
-
errors INTEGER NOT NULL DEFAULT 0,
|
|
5
|
-
conflicts INTEGER NOT NULL DEFAULT 0,
|
|
6
|
-
scans INTEGER NOT NULL DEFAULT 0,
|
|
7
|
-
total_duration_ms REAL NOT NULL DEFAULT 0,
|
|
8
|
-
min_duration_ms REAL,
|
|
9
|
-
max_duration_ms REAL NOT NULL DEFAULT 0,
|
|
10
|
-
last_called_at REAL NOT NULL DEFAULT 0,
|
|
11
|
-
last_error_at REAL,
|
|
12
|
-
last_error_message TEXT
|
|
13
|
-
)`);for(const s of["scans","conflicts"])try{t(a,`ALTER TABLE "${n}" ADD COLUMN ${s} INTEGER NOT NULL DEFAULT 0`)}catch{}t(a,`CREATE TABLE IF NOT EXISTS "${_}" (
|
|
14
|
-
path TEXT NOT NULL,
|
|
15
|
-
bucket_ms INTEGER NOT NULL,
|
|
16
|
-
calls INTEGER NOT NULL DEFAULT 0,
|
|
17
|
-
errors INTEGER NOT NULL DEFAULT 0,
|
|
18
|
-
PRIMARY KEY (path, bucket_ms)
|
|
19
|
-
)`),t(a,`CREATE TABLE IF NOT EXISTS "${c}" (
|
|
20
|
-
path TEXT NOT NULL,
|
|
21
|
-
table_name TEXT NOT NULL,
|
|
22
|
-
scans INTEGER NOT NULL DEFAULT 0,
|
|
23
|
-
PRIMARY KEY (path, table_name)
|
|
24
|
-
)`),t(a,`CREATE TABLE IF NOT EXISTS "${N}" (
|
|
25
|
-
table_name TEXT NOT NULL,
|
|
26
|
-
index_name TEXT NOT NULL,
|
|
27
|
-
reads INTEGER NOT NULL DEFAULT 0,
|
|
28
|
-
PRIMARY KEY (table_name, index_name)
|
|
29
|
-
)`)},U=(a,s)=>{if(l(a),t(a,`SELECT COUNT(*) AS n FROM "${n}"`).one().n>=5e3&&t(a,`SELECT COUNT(*) AS c FROM "${n}" WHERE path = ?`,s.path).one().c===0)return;const e=s.scannedTables?[...new Set(s.scannedTables)]:[],r=e.length,E=S(s.indexHits??[]),T=s.errored?1:0,L=s.conflicted?1:0,d=s.errored?s.ts:null,i=s.errored?s.errorMessage??null:null;t(a,`INSERT INTO "${n}"
|
|
30
|
-
(path, calls, errors, conflicts, scans, total_duration_ms, min_duration_ms, max_duration_ms, last_called_at, last_error_at, last_error_message)
|
|
31
|
-
VALUES (?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
32
|
-
ON CONFLICT(path) DO UPDATE SET
|
|
33
|
-
calls = calls + 1,
|
|
34
|
-
errors = errors + excluded.errors,
|
|
35
|
-
conflicts = conflicts + excluded.conflicts,
|
|
36
|
-
scans = scans + excluded.scans,
|
|
37
|
-
total_duration_ms = total_duration_ms + excluded.total_duration_ms,
|
|
38
|
-
min_duration_ms = MIN(COALESCE(min_duration_ms, excluded.min_duration_ms), excluded.min_duration_ms),
|
|
39
|
-
max_duration_ms = MAX(max_duration_ms, excluded.max_duration_ms),
|
|
40
|
-
last_called_at = excluded.last_called_at,
|
|
41
|
-
last_error_at = CASE WHEN excluded.last_error_at IS NULL THEN last_error_at ELSE excluded.last_error_at END,
|
|
42
|
-
last_error_message = CASE WHEN excluded.last_error_at IS NULL THEN last_error_message ELSE excluded.last_error_message END`,s.path,T,L,r,s.durationMs,s.durationMs,s.durationMs,s.ts,d,i);const m=u(s.ts);t(a,`INSERT INTO "${_}" (path, bucket_ms, calls, errors)
|
|
43
|
-
VALUES (?, ?, 1, ?)
|
|
44
|
-
ON CONFLICT(path, bucket_ms) DO UPDATE SET
|
|
45
|
-
calls = calls + 1,
|
|
46
|
-
errors = errors + excluded.errors`,s.path,m,T),t(a,`DELETE FROM "${_}"
|
|
47
|
-
WHERE path = ?
|
|
48
|
-
AND bucket_ms <= (
|
|
49
|
-
SELECT MAX(bucket_ms) - ? FROM "${_}" WHERE path = ?
|
|
50
|
-
)`,s.path,864e5,s.path);for(const o of e)t(a,`INSERT INTO "${c}" (path, table_name, scans)
|
|
51
|
-
VALUES (?, ?, 1)
|
|
52
|
-
ON CONFLICT(path, table_name) DO UPDATE SET
|
|
53
|
-
scans = scans + 1`,s.path,o);for(const o of E)t(a,`INSERT INTO "${N}" (table_name, index_name, reads)
|
|
54
|
-
VALUES (?, ?, 1)
|
|
55
|
-
ON CONFLICT(table_name, index_name) DO UPDATE SET
|
|
56
|
-
reads = reads + 1`,o.table,o.index)},A=a=>{l(a);const s=t(a,`SELECT path, table_name, scans FROM "${c}" ORDER BY scans DESC, path ASC, table_name ASC LIMIT ${String(1e3)}`).toArray(),e=new Map;for(const r of s){const E=e.get(r.path),T={scans:r.scans,table:r.table_name};E===void 0?e.set(r.path,[T]):E.push(T)}return e},M=a=>(l(a),t(a,`SELECT table_name, index_name, reads FROM "${N}" ORDER BY table_name ASC, index_name ASC LIMIT ${String(1e3)}`).toArray().map(s=>({index:s.index_name,reads:s.reads,table:s.table_name}))),F=(a,s)=>{for(const e of s){const r=a.find(E=>E.table===e);r===void 0?a.push({scans:1,table:e}):r.scans+=1}return a.sort((e,r)=>r.scans-e.scans||e.table.localeCompare(r.table)),a},b=a=>{l(a);const s=A(a);return t(a,`SELECT * FROM "${n}" ORDER BY last_called_at DESC LIMIT ${String(1e3)}`).toArray().map(e=>({calls:e.calls,conflicts:e.conflicts,errors:e.errors,lastCalledAt:e.last_called_at,lastErrorAt:e.last_error_at,lastErrorMessage:e.last_error_message,maxDurationMs:e.max_duration_ms,path:e.path,scannedTables:s.get(e.path)??[],scans:e.scans,totalDurationMs:e.total_duration_ms}))},p=(a,s)=>(l(a),(s===void 0?t(a,`SELECT path, bucket_ms, calls, errors FROM "${_}" ORDER BY bucket_ms DESC, path ASC LIMIT ${String(1e3)}`).toArray():t(a,`SELECT path, bucket_ms, calls, errors FROM "${_}" WHERE path = ? ORDER BY bucket_ms DESC LIMIT ${String(1e3)}`,s).toArray()).toReversed().map(e=>({bucketMs:e.bucket_ms,calls:e.calls,errors:e.errors,path:e.path}))),h=a=>{l(a);const s=t(a,`SELECT SUM(calls) AS requests, SUM(errors) AS errors FROM "${n}"`).one();return{errors:s.errors??0,requests:s.requests??0}};export{_ as FUNCTION_METRICS_BUCKETS_TABLE,I as FUNCTION_METRICS_BUCKET_MS,R as FUNCTION_METRICS_BUCKET_RETENTION,N as FUNCTION_METRICS_INDEX_TABLE,O as FUNCTION_METRICS_MAX_PATHS,C as FUNCTION_METRICS_READ_LIMIT,c as FUNCTION_METRICS_SCANS_TABLE,n as FUNCTION_METRICS_TABLE,l as ensureFunctionMetricsTables,F as mergeScanAttribution,p as readFunctionMetricBuckets,M as readFunctionMetricIndexHits,A as readFunctionMetricScans,b as readFunctionMetrics,h as readFunctionMetricsTotals,U as recordFunctionMetric};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import{fingerprintError as R}from"@lunora/fingerprint";import{redact as v,standardRules as A}from"@visulima/redact";import{n as m}from"./log-fields-d5ouN1Pe.mjs";import{readIssueStates as O}from"./ISSUE_SEVERITIES-Js6lGUG5.mjs";const c="__lunora_reqlog__",f=1e3,I="lunora",h=(s,e,...a)=>s.exec.call(s,e,...a),E=(s,e=!1)=>e||s===null||s===void 0?s:v(s,A),_=s=>{h(s,`CREATE TABLE IF NOT EXISTS "${c}" (
|
|
2
|
-
seq INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3
|
-
ts REAL NOT NULL,
|
|
4
|
-
function_path TEXT NOT NULL,
|
|
5
|
-
shard_key TEXT,
|
|
6
|
-
user_id TEXT,
|
|
7
|
-
identity TEXT,
|
|
8
|
-
args TEXT,
|
|
9
|
-
outcome TEXT NOT NULL,
|
|
10
|
-
error_message TEXT,
|
|
11
|
-
duration_ms REAL NOT NULL,
|
|
12
|
-
tables_read TEXT NOT NULL DEFAULT '[]',
|
|
13
|
-
tables_written TEXT NOT NULL DEFAULT '[]',
|
|
14
|
-
cache_hit INTEGER,
|
|
15
|
-
subscriptions_rerun INTEGER NOT NULL DEFAULT 0
|
|
16
|
-
)`)},S=s=>JSON.stringify([...new Set(s)].toSorted((e,a)=>e.localeCompare(a))),b=s=>s===void 0?null:s?1:0,$=(s,e,a={})=>{_(s);const i=a.captureRaw??!1,t=a.retention??f;h(s,`INSERT INTO "${c}"
|
|
17
|
-
(ts, function_path, shard_key, user_id, identity, args, outcome, error_message, duration_ms, tables_read, tables_written, cache_hit, subscriptions_rerun)
|
|
18
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,e.ts,e.functionPath,e.shardKey??null,e.userId??null,e.identity===void 0?null:JSON.stringify(E(e.identity,i)),e.redactedArgs===void 0?null:JSON.stringify(E(e.redactedArgs,i)),e.outcome,e.errorMessage??null,e.durationMs,S(e.tablesRead),S(e.tablesWritten),b(e.cacheHit),e.subscriptionsReRun??0),h(s,`DELETE FROM "${c}" WHERE seq <= (SELECT MAX(seq) - ? FROM "${c}")`,t)},F=(s,e={})=>{const a=e.captureRaw??!1,i={args:s.redactedArgs===void 0?void 0:E(s.redactedArgs,a),cacheHit:s.cacheHit,durationMs:s.durationMs,error:s.errorMessage,function:s.functionPath,identity:s.identity===void 0?void 0:E(s.identity,a),outcome:s.outcome,shard:s.shardKey,source:I,tablesRead:s.tablesRead??[],tablesWritten:s.tablesWritten??[],ts:s.ts,type:"request",userId:s.userId},t=JSON.stringify(i);s.outcome==="error"?console.error(t):console.log(t)},M="log",P=s=>s.map(e=>{if(typeof e=="string")return e;try{return JSON.stringify(e)??String(e)}catch{return String(e)}}).join(" "),q=s=>{if(typeof s!="object"||s===null||Array.isArray(s))return!1;const e=Object.getPrototypeOf(s);return e===Object.prototype||e===null},J=(s,e)=>s.length===2&&typeof s[0]=="string"&&q(s[1])?{fields:m(s[1],e),message:s[0]}:{fields:m(void 0,e),message:P(s)},X=s=>{const e={fields:s.fields,function:s.functionPath,level:s.level,message:s.message,shard:s.shardKey,source:I,spanId:s.spanId,traceId:s.traceId,ts:s.ts,type:M,userId:s.userId};let a;try{a=JSON.stringify(e)}catch{a=JSON.stringify({...e,fields:void 0})}s.level==="error"||s.level==="fatal"?console.error(a):s.level==="warn"?console.warn(a):console.log(a)},g=s=>s.replaceAll(/[\\%_]/g,e=>`\\${e}`),L=s=>{try{const e=JSON.parse(s);return Array.isArray(e)?e.filter(a=>typeof a=="string"):[]}catch{return[]}},k=(s,e={})=>{_(s);const a=Math.max(1,Math.min(e.limit??f,1e4)),i=["seq > ?"],t=[e.sinceSeq??0];if(e.functionPathPrefix!==void 0&&e.functionPathPrefix!==""&&(i.push(String.raw`function_path LIKE ? ESCAPE '\'`),t.push(`${g(e.functionPathPrefix)}%`)),e.userId!==void 0&&e.userId!==""&&(i.push("user_id = ?"),t.push(e.userId)),e.shardKey!==void 0&&e.shardKey!==""&&(i.push("shard_key = ?"),t.push(e.shardKey)),e.outcome!==void 0&&(i.push("outcome = ?"),t.push(e.outcome)),e.tableTouched!==void 0&&e.tableTouched!==""){const r=`%${g(JSON.stringify(e.tableTouched))}%`;i.push(String.raw`(tables_read LIKE ? ESCAPE '\' OR tables_written LIKE ? ESCAPE '\')`),t.push(r,r)}return t.push(a),h(s,`SELECT seq, ts, function_path, shard_key, user_id, identity, args, outcome, error_message, duration_ms, tables_read, tables_written, cache_hit, subscriptions_rerun
|
|
19
|
-
FROM "${c}" WHERE ${i.join(" AND ")} ORDER BY seq DESC LIMIT ?`,...t).toArray().map(r=>{const n={durationMs:r.duration_ms,functionPath:r.function_path,outcome:r.outcome==="error"?"error":"ok",seq:r.seq,subscriptionsReRun:r.subscriptions_rerun,tablesRead:L(r.tables_read),tablesWritten:L(r.tables_written),ts:r.ts};return r.shard_key!==null&&(n.shardKey=r.shard_key),r.user_id!==null&&(n.userId=r.user_id),r.identity!==null&&(n.identity=JSON.parse(r.identity)),r.args!==null&&(n.redactedArgs=JSON.parse(r.args)),r.error_message!==null&&(n.errorMessage=r.error_message),r.cache_hit!==null&&(n.cacheHit=r.cache_hit===1),n})},w=(s,e)=>{const a=O(s,[...e.keys()]);for(const i of e.values()){const t=a.get(i.hash);t!==void 0&&(i.stateUpdatedAt=t.updatedAt,t.assignee!==void 0&&(i.assignee=t.assignee),t.severity!==void 0&&(i.severity=t.severity),i.status=t.status==="resolved"&&i.lastSeen>t.updatedAt?"open":t.status)}},D=(s,e={})=>{_(s);const a=Math.max(1,Math.min(e.limit??f,1e4)),i=["outcome = 'error'"],t=[];e.functionPathPrefix!==void 0&&e.functionPathPrefix!==""&&(i.push(String.raw`function_path LIKE ? ESCAPE '\'`),t.push(`${g(e.functionPathPrefix)}%`)),e.userId!==void 0&&e.userId!==""&&(i.push("user_id = ?"),t.push(e.userId)),e.shardKey!==void 0&&e.shardKey!==""&&(i.push("shard_key = ?"),t.push(e.shardKey)),t.push(a);const r=h(s,`SELECT function_path, error_message, ts
|
|
20
|
-
FROM "${c}" WHERE ${i.join(" AND ")} ORDER BY seq DESC LIMIT ?`,...t).toArray(),n=new Map,p=new Map;for(const o of r){const l=o.error_message??"",{culprit:N,hash:d,title:T}=R({functionPath:o.function_path,message:l}),u=n.get(d);if(u===void 0){n.set(d,{count:1,culprit:N,firstSeen:o.ts,hash:d,lastSeen:o.ts,sampleMessage:l,status:"open",title:T}),p.set(d,o.ts);continue}u.count+=1,u.firstSeen=Math.min(u.firstSeen,o.ts),u.lastSeen=Math.max(u.lastSeen,o.ts),o.ts>(p.get(d)??Number.NEGATIVE_INFINITY)&&(p.set(d,o.ts),u.sampleMessage=l,u.title=T)}w(s,n);const y=[...n.values()];return(e.status===void 0?y:y.filter(o=>o.status===e.status)).toSorted((o,l)=>l.lastSeen-o.lastSeen)};export{f as REQUEST_LOG_RETENTION,c as REQUEST_LOG_TABLE,$ as appendRequestLogEntry,X as emitLogEvent,F as emitRequestLogEvent,_ as ensureRequestLogTable,J as parseLogArgs,D as readErrorIssues,k as readRequestLog,E as redactArgs,P as renderLogMessage};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{O as h,t as m}from"./trace-context-DrdF960P.mjs";const b=100,M=new Set(["aggregate","count","delete","deleteMany","deleteWhere","findFirst","findFirstOrThrow","findMany","get","groupBy","insert","insertMany","insertManyUnsafe","lookupById","patch","patchMany","patchWhere","rank","rankBefore","rankPage","rankPageRows","replace","restore"]),w=new Set(["aggregate","count","deleteWhere","findFirst","findFirstOrThrow","findMany","groupBy","insert","insertMany","insertManyUnsafe","patchWhere","rank","rankBefore","rankPage","rankPageRows"]),k=(t,e)=>{if(!w.has(t))return;const r=e[0];return typeof r=="string"&&r.length>0?r:void 0},T=t=>t instanceof Error?t.message:typeof t=="string"?t:JSON.stringify(t),I=t=>{const{deps:e,durationMs:r,failure:n,operation:s,startTs:a,table:o}=t;return{attributes:{"db.operation.name":s,...o===void 0?{}:{"db.collection.name":o},"db.system.name":"sqlite"},durationMs:r,...n===void 0?{}:{error:{message:T(n),type:m(n)}},functionPath:e.functionPath,kind:"client",name:o===void 0?`db.${s}`:`db.${s} ${o}`,ok:n===void 0,parentSpanId:e.anchor.rootSpanId,shardKey:e.shardKey,spanId:h(8),startTs:a,traceId:e.anchor.traceId,userId:e.userId()}},v=(t,e)=>{if(e.mode==="off")return t;const{tally:r}=e,n=new Map;return new Proxy(t,{get(s,a,o){const d=Reflect.get(s,a,o);if(typeof a!="string"||typeof d!="function"||!M.has(a))return d;const p=n.get(a);if(p!==void 0)return p;const y=d,u=async(...f)=>{const l=Date.now(),g=k(a,f);let c;try{return await y.apply(s,f)}catch(i){throw c=i,i}finally{const i=Date.now()-l;r.calls+=1,r.durationMs+=i,r.perOperation[a]=(r.perOperation[a]??0)+1,c!==void 0&&(r.errors+=1);try{e.mode==="spans"&&(r.spansEmitted>=b?r.spansTruncated=!0:(r.spansEmitted+=1,e.record(I({deps:e,durationMs:i,failure:c,operation:a,startTs:l,table:g}))))}catch{}}};return n.set(a,u),u}})},P=()=>({calls:0,durationMs:0,errors:0,perOperation:{},spansEmitted:0,spansTruncated:!1}),E=t=>{const e={"db.calls":t.calls,"db.duration_ms":t.durationMs};t.errors>0&&(e["db.errors"]=t.errors),t.spansTruncated&&(e["db.spans_truncated"]=!0);for(const[r,n]of Object.entries(t.perOperation))e[`db.op.${r}`]=n;return e};export{P as createDatabaseTally,E as formatTally,v as instrumentDatabase};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{n as y}from"./log-fields-d5ouN1Pe.mjs";import{w as f,O as A,m as O,t as S}from"./trace-context-DrdF960P.mjs";const j=/[\w.-]/u,x=e=>{let t="";for(const n of e)t+=j.test(n)?n:"_";return t},D=e=>{if(typeof e.name!="string"||e.name.length===0)throw new Error("recordEvaluation requires a non-empty `name`");if(typeof e.score!="number"||!Number.isFinite(e.score))throw new Error("recordEvaluation `score` must be a finite number");const t=x(e.name),n={[`gen_ai.evaluation.${t}.score`]:e.score};return e.label!==void 0&&(n[`gen_ai.evaluation.${t}.label`]=e.label),n},H=e=>{const t=Object.keys(e);return t.length>0&&t.every(n=>n==="attributes"||n==="kind"||n==="links")},L=128,_=128,F=e=>{try{const t=new URL(e);return`${t.protocol}//${t.host}${t.pathname}`}catch{return e}},R=e=>{try{return new URL(e).host}catch{return e}},U=(e,t)=>{try{return e(new URL(t))}catch{return!1}},q=e=>e===void 0?{}:H(e)?e:{attributes:e},C=(e,t)=>{if(e.isTraced){e.setAttribute(f.functionPath,t.functionPath),e.setAttribute(f.ok,t.ok),e.setAttribute(f.durationMs,t.durationMs),t.shardKey!==void 0&&e.setAttribute(f.shardKey,t.shardKey),t.userId!==void 0&&e.setAttribute(f.userId,t.userId),t.error!==void 0&&(e.setAttribute(f.errorType,t.error.type),e.setAttribute(f.errorMessage,t.error.message));for(const[n,r]of Object.entries(t.attributes))(typeof r=="boolean"||typeof r=="number"||typeof r=="string")&&e.setAttribute(`lunora.attr.${n}`,r)}},N=e=>{const t={attributes:{},events:[],links:[]},n={spanContext:()=>e,addEvent:(r,a)=>{if(t.events.length>=L)return;const s=y(a);t.events.push({...s===void 0?{}:{attributes:s},name:r,ts:Date.now()})},addLink:r=>{if(t.links.length>=_)return;const a=y(r.attributes);t.links.push({...a===void 0?{}:{attributes:a},spanId:r.spanId,traceId:r.traceId})},recordEvaluation:r=>{Object.assign(t.attributes,y(D(r)))},recordException:r=>{n.addEvent("exception",{"exception.message":r instanceof Error?r.message:String(r),...r instanceof Error&&typeof r.stack=="string"?{"exception.stacktrace":r.stack}:{},"exception.type":S(r)})},setAttribute:(r,a)=>{Object.assign(t.attributes,y({[r]:a}))},setAttributes:r=>{Object.assign(t.attributes,y(r))}};return{collected:t,handle:n}},G=e=>{const{anchor:t,fuseHostSpans:n,functionPath:r,record:a,resolveHostTracing:s,shardKey:i,userId:c}=e,h=u=>async(v,I,d)=>{const l=A(8),b=Date.now(),o=q(d),$=y(o.attributes),{collected:k,handle:M}=N({spanId:l,traceId:t.traceId}),T=async g=>{let m=!0,w;try{return await I(h(l),M)}catch(p){throw m=!1,w={message:p instanceof Error?p.message:String(p),type:S(p)},p}finally{const p=Date.now()-b,P=c(),E={...$,...k.attributes},K=[...o.links??[],...k.links];try{a({...Object.keys(E).length===0?{}:{attributes:E},durationMs:p,...k.events.length===0?{}:{events:k.events},...w===void 0?{}:{error:w},functionPath:r,...o.kind===void 0||o.kind==="internal"?{}:{kind:o.kind},...K.length===0?{}:{links:K},name:v,ok:m,parentSpanId:u,shardKey:i,spanId:l,startTs:b,traceId:t.traceId,userId:P})}catch{}if(g!==void 0)try{C(g,{attributes:E,durationMs:p,error:w,functionPath:r,ok:m,shardKey:i,userId:P})}catch{}}};if(n===!0&&s!==void 0){const g=await s();if(g!==void 0&&typeof g.enterSpan=="function")return await g.enterSpan(v,m=>T(m))}return await T()};return h(t.rootSpanId)},J=(e,t)=>{const{anchor:n,functionPath:r,propagate:a=!0,record:s,shardKey:i,userId:c}=e;return async(h,u)=>{const v=A(8),I=Date.now(),d=new Request(h,u);(typeof a=="function"?U(a,d.url):a)&&d.headers.set("traceparent",O(n.traceId,v,n.sampled??!0));let l,b;try{const o=await t(d);return b=o.status,o.ok||(l={message:`HTTP ${String(o.status)}`,type:`HTTP_${String(o.status)}`}),o}catch(o){throw l={message:o instanceof Error?o.message:String(o),type:S(o)},o}finally{try{s({attributes:{"http.request.method":d.method,...b===void 0?{}:{"http.response.status_code":b},"url.full":F(d.url)},durationMs:Date.now()-I,...l===void 0?{}:{error:l},functionPath:r,kind:"client",name:`${d.method} ${R(d.url)}`,ok:l===void 0,parentSpanId:n.rootSpanId,shardKey:i,spanId:v,startTs:I,traceId:n.traceId,userId:c()})}catch{}}}},Q=e=>{const{functionPath:t,record:n,shardKey:r}=e,a=(s,i,c,h)=>{if(!Number.isFinite(c))return;const u=y(h);try{n({...u===void 0?{}:{attributes:u},functionPath:t,kind:s,name:i,shardKey:r,ts:Date.now(),value:c})}catch{}};return{count:(s,i=1,c)=>{a("counter",s,i,c)},gauge:(s,i,c)=>{a("gauge",s,i,c)},record:(s,i,c)=>{a("histogram",s,i,c)}}},V=e=>{const{anchor:t,collected:n,durationMs:r,failure:a,functionPath:s,shardKey:i,startTs:c,userId:h}=e,u=n?.attributes??{};return{...Object.keys(u).length===0?{}:{attributes:u},dispatch:!0,durationMs:r,...n===void 0||n.events.length===0?{}:{events:n.events},...a===void 0?{}:{error:{message:a.thrown instanceof Error?a.thrown.message:String(a.thrown),type:S(a.thrown)}},functionPath:s,...n===void 0||n.links.length===0?{}:{links:n.links},name:s,ok:a===void 0,parentSpanId:"",shardKey:i,spanId:t.rootSpanId,startTs:c,traceId:t.traceId,userId:h}};export{C as applyHostSpanAttributes,Q as createMetrics,N as createSpanCollector,J as createTracedFetch,G as createTracer,V as dispatchRootSpan};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
const s=e=>{if(typeof e=="string")return e;try{return JSON.stringify(e)??String(e)}catch{return String(e)}},f=e=>typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:s(e),n=(e,t)=>{if(e===void 0&&t===void 0)return;const r={};if(t!==void 0)for(const[o,i]of Object.entries(t))r[o]=f(i);if(e!==void 0)for(const[o,i]of Object.entries(e))r[o]=f(i);return Object.keys(r).length===0?void 0:r};export{n};
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import{m as A,e as O}from"./metric-buffer-CdgXal7w.mjs";const r="__lunora_metric_history",o=6e4,R=1440,x=1e3,l=5e3,E=(e,s,...T)=>e.exec.call(e,s,...T),y=e=>Math.floor(e/o)*o,_=new WeakSet,d=e=>{_.has(e)||(E(e,`CREATE TABLE IF NOT EXISTS "${r}" (
|
|
2
|
-
series_key TEXT NOT NULL,
|
|
3
|
-
bucket_ms INTEGER NOT NULL,
|
|
4
|
-
name TEXT NOT NULL,
|
|
5
|
-
kind TEXT NOT NULL,
|
|
6
|
-
attrs TEXT NOT NULL DEFAULT '{}',
|
|
7
|
-
function_path TEXT NOT NULL DEFAULT '',
|
|
8
|
-
shard_key TEXT,
|
|
9
|
-
count INTEGER NOT NULL DEFAULT 0,
|
|
10
|
-
sum REAL NOT NULL DEFAULT 0,
|
|
11
|
-
min REAL NOT NULL DEFAULT 0,
|
|
12
|
-
max REAL NOT NULL DEFAULT 0,
|
|
13
|
-
last REAL NOT NULL DEFAULT 0,
|
|
14
|
-
last_ts REAL NOT NULL DEFAULT 0,
|
|
15
|
-
exemplar_trace TEXT,
|
|
16
|
-
PRIMARY KEY (series_key, bucket_ms)
|
|
17
|
-
)`),_.add(e))},S=4096,N=new WeakMap,M=e=>{let s=N.get(e);return s===void 0&&(s=new Set,N.set(e,s)),s},I=(e,s,T,c={})=>{const t=c.maxSeries??x,n=c.retentionBuckets??R;d(e);const a=A(s),i=y(s.ts),u=M(e),L=`${a}\0${i.toString()}`,m=u.has(L)||E(e,`SELECT 1 AS c FROM "${r}" WHERE series_key = ? AND bucket_ms = ? LIMIT 1`,a,i).toArray().length>0;if(!m&&!(E(e,`SELECT 1 AS c FROM "${r}" WHERE series_key = ? LIMIT 1`,a).toArray().length>0)&&E(e,`SELECT COUNT(DISTINCT series_key) AS n FROM "${r}"`).one().n>=t)return;const k=T??null;E(e,`INSERT INTO "${r}"
|
|
18
|
-
(series_key, bucket_ms, name, kind, attrs, function_path, shard_key, count, sum, min, max, last, last_ts, exemplar_trace)
|
|
19
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?, ?)
|
|
20
|
-
ON CONFLICT(series_key, bucket_ms) DO UPDATE SET
|
|
21
|
-
count = count + 1,
|
|
22
|
-
sum = sum + excluded.sum,
|
|
23
|
-
min = MIN(min, excluded.min),
|
|
24
|
-
max = MAX(max, excluded.max),
|
|
25
|
-
last = excluded.last,
|
|
26
|
-
last_ts = excluded.last_ts,
|
|
27
|
-
exemplar_trace = CASE WHEN excluded.exemplar_trace IS NULL THEN exemplar_trace ELSE excluded.exemplar_trace END`,a,i,s.name,s.kind,O(s.attributes??{}),s.functionPath,s.shardKey??null,s.value,s.value,s.value,s.value,s.ts,k),m||E(e,`DELETE FROM "${r}"
|
|
28
|
-
WHERE series_key = ?
|
|
29
|
-
AND bucket_ms <= (
|
|
30
|
-
SELECT MAX(bucket_ms) - ? FROM "${r}" WHERE series_key = ?
|
|
31
|
-
)`,a,n*o,a),m&&!u.has(L)&&(u.size>=S&&u.clear(),u.add(L))},U=e=>{if(!(e===""||e==="{}"))try{const s=JSON.parse(e);return s!==null&&typeof s=="object"?s:void 0}catch{return}},h=(e,s={})=>{d(e);const T=s.sinceMs===void 0?E(e,`SELECT * FROM "${r}" ORDER BY bucket_ms DESC LIMIT ?`,l).toArray():E(e,`SELECT * FROM "${r}" WHERE bucket_ms >= ? ORDER BY bucket_ms DESC LIMIT ?`,s.sinceMs,l).toArray(),c=new Map;for(const t of T){let n=c.get(t.series_key);if(n===void 0){const a=U(t.attrs);n={...a===void 0?{}:{attributes:a},functionPath:t.function_path,kind:t.kind,name:t.name,points:[],...t.shard_key===null?{}:{shardKey:t.shard_key}},c.set(t.series_key,n)}n.points.push({bucketMs:t.bucket_ms,count:t.count,...t.exemplar_trace===null?{}:{exemplarTraceId:t.exemplar_trace},last:t.last,max:t.max,min:t.min,sum:t.sum})}for(const t of c.values())t.points.sort((n,a)=>n.bucketMs-a.bucketMs);return{series:[...c.values()]}};export{h as readMetricHistory,I as recordMetricHistory};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const E="__lunora_metrics_queries",d="__lunora_metrics_queries_buckets",q=6e4,F=90,u=[1,2,5,10,25,50,100,250,500,1e3,5e3],R=512,O=500,a=(e,t,...r)=>e.exec.call(e,t,...r),D=e=>{let t=e.replaceAll(/'(?:[^']|'')*'/g,"?").replaceAll(/\b0x[\da-f]+\b/gi,"?").replaceAll(/(?<=[=,([\s])\d+(?:\.\d+)?/g,"?").replaceAll(/\s+/g," ").trim();return t.length>R&&(t=`${t.slice(0,R-1)}…`),t},N=e=>{a(e,`CREATE TABLE IF NOT EXISTS "${E}" (
|
|
2
|
-
normalized_sql TEXT PRIMARY KEY,
|
|
3
|
-
exec_count INTEGER NOT NULL DEFAULT 0,
|
|
4
|
-
total_duration_ms REAL NOT NULL DEFAULT 0,
|
|
5
|
-
rows_read INTEGER NOT NULL DEFAULT 0,
|
|
6
|
-
rows_written INTEGER NOT NULL DEFAULT 0
|
|
7
|
-
)`)},U=new WeakMap,S=e=>Math.floor(e/6e4)*6e4,h=e=>{let t=2166136261;for(let r=0;r<e.length;r+=1)t^=e.codePointAt(r)??0,t=Math.imul(t,16777619)>>>0;return t.toString(16).padStart(8,"0")},I=e=>{const t=u.findIndex(r=>e<=r);return t===-1?u.length:t},T=e=>`lat_${String(e)}`,x=e=>{const t=Array.from({length:u.length+1},(r,_)=>`${T(_)} INTEGER NOT NULL DEFAULT 0`).join(", ");a(e,`CREATE TABLE IF NOT EXISTS "${d}" (
|
|
8
|
-
sql_hash TEXT NOT NULL,
|
|
9
|
-
bucket_ms INTEGER NOT NULL,
|
|
10
|
-
exec_count INTEGER NOT NULL DEFAULT 0,
|
|
11
|
-
total_duration_ms REAL NOT NULL DEFAULT 0,
|
|
12
|
-
rows_read INTEGER NOT NULL DEFAULT 0,
|
|
13
|
-
rows_written INTEGER NOT NULL DEFAULT 0,
|
|
14
|
-
${t},
|
|
15
|
-
PRIMARY KEY (sql_hash, bucket_ms)
|
|
16
|
-
)`)},b=(e,t)=>{a(e,`DELETE FROM "${d}" WHERE bucket_ms < ?`,S(t)-90*6e4)},g=(e,t,r,_,c,n)=>{try{x(e);const s=T(I(r)),i=`INSERT INTO "${d}" (sql_hash, bucket_ms, exec_count, total_duration_ms, rows_read, rows_written, ${s})
|
|
17
|
-
VALUES (?, ?, 1, ?, ?, ?, 1)
|
|
18
|
-
ON CONFLICT(sql_hash, bucket_ms) DO UPDATE SET
|
|
19
|
-
exec_count = exec_count + 1,
|
|
20
|
-
total_duration_ms = total_duration_ms + excluded.total_duration_ms,
|
|
21
|
-
rows_read = rows_read + excluded.rows_read,
|
|
22
|
-
rows_written = rows_written + excluded.rows_written,
|
|
23
|
-
${s} = ${s} + 1`;a(e,i,h(t),S(n),r,_,c);const m=S(n);U.get(e)!==m&&(U.set(e,m),b(e,n))}catch{}},M=(e,t)=>{const r=e.reduce((n,s)=>n+s,0);if(r===0)return 0;const _=r*t;let c=0;for(const[n,s]of e.entries())if(c+=s,c>=_)return u[n]??u.at(-1)??0;return u.at(-1)??0},$=(e,t,r=Date.now())=>{try{N(e),x(e)}catch{return{buckets:[],capped:!1,entries:[],trackedStatements:0}}const _=S(r-t),c=Array.from({length:u.length+1},(o,l)=>`SUM(${T(l)}) AS ${T(l)}`).join(", "),n=a(e,`SELECT sql_hash, SUM(exec_count) AS exec_count, SUM(total_duration_ms) AS total_duration_ms,
|
|
24
|
-
SUM(rows_read) AS rows_read, SUM(rows_written) AS rows_written, ${c}
|
|
25
|
-
FROM "${d}" WHERE bucket_ms >= ? GROUP BY sql_hash`,_).toArray(),s=new Map;for(const o of a(e,`SELECT normalized_sql FROM "${E}"`))s.set(h(o.normalized_sql),o.normalized_sql);const i=n.map(o=>{const l=Array.from({length:u.length+1},(k,C)=>Number(o[T(C)]??0)),w=Number(o.exec_count??0),A=Number(o.total_duration_ms??0);return{avgDurationMs:w>0?A/w:0,execCount:w,normalizedSql:s.get(String(o.sql_hash))??String(o.sql_hash),p50DurationMs:M(l,.5),p95DurationMs:M(l,.95),rowsRead:Number(o.rows_read??0),rowsWritten:Number(o.rows_written??0),totalDurationMs:A}});i.sort((o,l)=>l.totalDurationMs-o.totalDurationMs);const m=a(e,`SELECT bucket_ms, SUM(exec_count) AS exec_count, SUM(total_duration_ms) AS total_duration_ms
|
|
26
|
-
FROM "${d}" WHERE bucket_ms >= ? GROUP BY bucket_ms ORDER BY bucket_ms ASC`,_).toArray().map(o=>({avgDurationMs:o.exec_count>0?o.total_duration_ms/o.exec_count:0,bucketMs:o.bucket_ms,execCount:o.exec_count})),L=a(e,`SELECT COUNT(*) AS n FROM "${E}"`).one().n;return{buckets:m,capped:L>=O,entries:i,trackedStatements:L}},p=(e,t,r,_,c,n=Date.now())=>{const s=D(t);if(s.length===0||(N(e),a(e,`SELECT COUNT(*) AS n FROM "${E}"`).one().n>=O&&a(e,`SELECT COUNT(*) AS c FROM "${E}" WHERE normalized_sql = ?`,s).one().c===0))return;const i=`INSERT INTO "${E}" (normalized_sql, exec_count, total_duration_ms, rows_read, rows_written)
|
|
27
|
-
VALUES (?, 1, ?, ?, ?)
|
|
28
|
-
ON CONFLICT(normalized_sql) DO UPDATE SET
|
|
29
|
-
exec_count = exec_count + 1,
|
|
30
|
-
total_duration_ms = total_duration_ms + excluded.total_duration_ms,
|
|
31
|
-
rows_read = rows_read + excluded.rows_read,
|
|
32
|
-
rows_written = rows_written + excluded.rows_written`;a(e,i,s,r,_,c),g(e,s,r,_,c,n)},y=e=>(N(e),a(e,`SELECT normalized_sql, exec_count, total_duration_ms, rows_read, rows_written FROM "${E}" ORDER BY total_duration_ms DESC`).toArray().map(t=>({execCount:t.exec_count,normalizedSql:t.normalized_sql,rowsRead:t.rows_read,rowsWritten:t.rows_written,totalDurationMs:t.total_duration_ms})));export{u as LATENCY_BUCKET_EDGES,d as QUERY_BUCKETS_TABLE,q as QUERY_BUCKET_MS,F as QUERY_BUCKET_RETENTION,R as QUERY_METRICS_MAX_SQL_LEN,O as QUERY_METRICS_MAX_STATEMENTS,E as QUERY_METRICS_TABLE,x as ensureQueryBucketsTable,N as ensureQueryMetricsTable,h as hashStatement,I as latencyBucketIndex,D as normalizeSql,M as percentileFrom,b as pruneQueryBuckets,$ as readQueryInsights,y as readQueryMetrics,g as recordQueryBucket,p as recordQueryMetric};
|