@adonis-agora/telescope 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +26 -0
- package/dist/configure.d.ts +16 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +75 -0
- package/dist/configure.js.map +1 -0
- package/dist/providers/telescope_ai_provider.d.ts +20 -0
- package/dist/providers/telescope_ai_provider.d.ts.map +1 -0
- package/dist/providers/telescope_ai_provider.js +45 -0
- package/dist/providers/telescope_ai_provider.js.map +1 -0
- package/dist/providers/telescope_alerts_provider.d.ts +23 -0
- package/dist/providers/telescope_alerts_provider.d.ts.map +1 -0
- package/dist/providers/telescope_alerts_provider.js +72 -0
- package/dist/providers/telescope_alerts_provider.js.map +1 -0
- package/dist/providers/telescope_provider.d.ts +43 -0
- package/dist/providers/telescope_provider.d.ts.map +1 -0
- package/dist/providers/telescope_provider.js +103 -0
- package/dist/providers/telescope_provider.js.map +1 -0
- package/dist/providers/telescope_ui_provider.d.ts +21 -0
- package/dist/providers/telescope_ui_provider.d.ts.map +1 -0
- package/dist/providers/telescope_ui_provider.js +119 -0
- package/dist/providers/telescope_ui_provider.js.map +1 -0
- package/dist/providers/telescope_watchers_provider.d.ts +31 -0
- package/dist/providers/telescope_watchers_provider.d.ts.map +1 -0
- package/dist/providers/telescope_watchers_provider.js +116 -0
- package/dist/providers/telescope_watchers_provider.js.map +1 -0
- package/dist/src/ai/define_config.d.ts +56 -0
- package/dist/src/ai/define_config.d.ts.map +1 -0
- package/dist/src/ai/define_config.js +39 -0
- package/dist/src/ai/define_config.js.map +1 -0
- package/dist/src/ai/diagnoser.d.ts +34 -0
- package/dist/src/ai/diagnoser.d.ts.map +1 -0
- package/dist/src/ai/diagnoser.js +74 -0
- package/dist/src/ai/diagnoser.js.map +1 -0
- package/dist/src/ai/diagnosis_cache.d.ts +43 -0
- package/dist/src/ai/diagnosis_cache.d.ts.map +1 -0
- package/dist/src/ai/diagnosis_cache.js +56 -0
- package/dist/src/ai/diagnosis_cache.js.map +1 -0
- package/dist/src/ai/factory.d.ts +15 -0
- package/dist/src/ai/factory.d.ts.map +1 -0
- package/dist/src/ai/factory.js +24 -0
- package/dist/src/ai/factory.js.map +1 -0
- package/dist/src/ai/index.d.ts +14 -0
- package/dist/src/ai/index.d.ts.map +1 -0
- package/dist/src/ai/index.js +15 -0
- package/dist/src/ai/index.js.map +1 -0
- package/dist/src/ai/prompt.d.ts +31 -0
- package/dist/src/ai/prompt.d.ts.map +1 -0
- package/dist/src/ai/prompt.js +66 -0
- package/dist/src/ai/prompt.js.map +1 -0
- package/dist/src/ai/telescope_ai_diagnoser.d.ts +79 -0
- package/dist/src/ai/telescope_ai_diagnoser.d.ts.map +1 -0
- package/dist/src/ai/telescope_ai_diagnoser.js +111 -0
- package/dist/src/ai/telescope_ai_diagnoser.js.map +1 -0
- package/dist/src/alerts/alert_channel.d.ts +69 -0
- package/dist/src/alerts/alert_channel.d.ts.map +1 -0
- package/dist/src/alerts/alert_channel.js +114 -0
- package/dist/src/alerts/alert_channel.js.map +1 -0
- package/dist/src/alerts/alert_rule.d.ts +86 -0
- package/dist/src/alerts/alert_rule.d.ts.map +1 -0
- package/dist/src/alerts/alert_rule.js +2 -0
- package/dist/src/alerts/alert_rule.js.map +1 -0
- package/dist/src/alerts/alerter.d.ts +72 -0
- package/dist/src/alerts/alerter.d.ts.map +1 -0
- package/dist/src/alerts/alerter.js +248 -0
- package/dist/src/alerts/alerter.js.map +1 -0
- package/dist/src/alerts/define_config.d.ts +68 -0
- package/dist/src/alerts/define_config.d.ts.map +1 -0
- package/dist/src/alerts/define_config.js +57 -0
- package/dist/src/alerts/define_config.js.map +1 -0
- package/dist/src/alerts/exception_source.d.ts +44 -0
- package/dist/src/alerts/exception_source.d.ts.map +1 -0
- package/dist/src/alerts/exception_source.js +79 -0
- package/dist/src/alerts/exception_source.js.map +1 -0
- package/dist/src/alerts/index.d.ts +16 -0
- package/dist/src/alerts/index.d.ts.map +1 -0
- package/dist/src/alerts/index.js +17 -0
- package/dist/src/alerts/index.js.map +1 -0
- package/dist/src/alerts/new_exception_tracker.d.ts +50 -0
- package/dist/src/alerts/new_exception_tracker.d.ts.map +1 -0
- package/dist/src/alerts/new_exception_tracker.js +74 -0
- package/dist/src/alerts/new_exception_tracker.js.map +1 -0
- package/dist/src/alerts/parse_duration.d.ts +10 -0
- package/dist/src/alerts/parse_duration.d.ts.map +1 -0
- package/dist/src/alerts/parse_duration.js +27 -0
- package/dist/src/alerts/parse_duration.js.map +1 -0
- package/dist/src/alerts/slack_format.d.ts +60 -0
- package/dist/src/alerts/slack_format.d.ts.map +1 -0
- package/dist/src/alerts/slack_format.js +122 -0
- package/dist/src/alerts/slack_format.js.map +1 -0
- package/dist/src/context_accessor.d.ts +30 -0
- package/dist/src/context_accessor.d.ts.map +1 -0
- package/dist/src/context_accessor.js +20 -0
- package/dist/src/context_accessor.js.map +1 -0
- package/dist/src/define_config.d.ts +109 -0
- package/dist/src/define_config.d.ts.map +1 -0
- package/dist/src/define_config.js +38 -0
- package/dist/src/define_config.js.map +1 -0
- package/dist/src/diagnostics_registry.d.ts +46 -0
- package/dist/src/diagnostics_registry.d.ts.map +1 -0
- package/dist/src/diagnostics_registry.js +34 -0
- package/dist/src/diagnostics_registry.js.map +1 -0
- package/dist/src/diagnostics_watcher.d.ts +72 -0
- package/dist/src/diagnostics_watcher.d.ts.map +1 -0
- package/dist/src/diagnostics_watcher.js +119 -0
- package/dist/src/diagnostics_watcher.js.map +1 -0
- package/dist/src/entry.d.ts +81 -0
- package/dist/src/entry.d.ts.map +1 -0
- package/dist/src/entry.js +34 -0
- package/dist/src/entry.js.map +1 -0
- package/dist/src/exception_family_hash.d.ts +29 -0
- package/dist/src/exception_family_hash.d.ts.map +1 -0
- package/dist/src/exception_family_hash.js +30 -0
- package/dist/src/exception_family_hash.js.map +1 -0
- package/dist/src/exception_watcher.d.ts +66 -0
- package/dist/src/exception_watcher.d.ts.map +1 -0
- package/dist/src/exception_watcher.js +94 -0
- package/dist/src/exception_watcher.js.map +1 -0
- package/dist/src/extension/registry.d.ts +17 -0
- package/dist/src/extension/registry.d.ts.map +1 -0
- package/dist/src/extension/registry.js +56 -0
- package/dist/src/extension/registry.js.map +1 -0
- package/dist/src/extension/types.d.ts +158 -0
- package/dist/src/extension/types.d.ts.map +1 -0
- package/dist/src/extension/types.js +5 -0
- package/dist/src/extension/types.js.map +1 -0
- package/dist/src/index.d.ts +36 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +28 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/redaction/redact.d.ts +93 -0
- package/dist/src/redaction/redact.d.ts.map +1 -0
- package/dist/src/redaction/redact.js +184 -0
- package/dist/src/redaction/redact.js.map +1 -0
- package/dist/src/redaction/redacting_store.d.ts +28 -0
- package/dist/src/redaction/redacting_store.d.ts.map +1 -0
- package/dist/src/redaction/redacting_store.js +49 -0
- package/dist/src/redaction/redacting_store.js.map +1 -0
- package/dist/src/registry.d.ts +26 -0
- package/dist/src/registry.d.ts.map +1 -0
- package/dist/src/registry.js +28 -0
- package/dist/src/registry.js.map +1 -0
- package/dist/src/request_watcher.d.ts +44 -0
- package/dist/src/request_watcher.d.ts.map +1 -0
- package/dist/src/request_watcher.js +37 -0
- package/dist/src/request_watcher.js.map +1 -0
- package/dist/src/service.d.ts +36 -0
- package/dist/src/service.d.ts.map +1 -0
- package/dist/src/service.js +65 -0
- package/dist/src/service.js.map +1 -0
- package/dist/src/store.d.ts +56 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +2 -0
- package/dist/src/store.js.map +1 -0
- package/dist/src/stores/factory.d.ts +61 -0
- package/dist/src/stores/factory.d.ts.map +1 -0
- package/dist/src/stores/factory.js +42 -0
- package/dist/src/stores/factory.js.map +1 -0
- package/dist/src/stores/lucid.d.ts +138 -0
- package/dist/src/stores/lucid.d.ts.map +1 -0
- package/dist/src/stores/lucid.js +257 -0
- package/dist/src/stores/lucid.js.map +1 -0
- package/dist/src/stores/memory.d.ts +31 -0
- package/dist/src/stores/memory.d.ts.map +1 -0
- package/dist/src/stores/memory.js +117 -0
- package/dist/src/stores/memory.js.map +1 -0
- package/dist/src/telescope_middleware.d.ts +19 -0
- package/dist/src/telescope_middleware.d.ts.map +1 -0
- package/dist/src/telescope_middleware.js +56 -0
- package/dist/src/telescope_middleware.js.map +1 -0
- package/dist/src/ui/api.d.ts +49 -0
- package/dist/src/ui/api.d.ts.map +1 -0
- package/dist/src/ui/api.js +155 -0
- package/dist/src/ui/api.js.map +1 -0
- package/dist/src/ui/dashboard.d.ts +8 -0
- package/dist/src/ui/dashboard.d.ts.map +1 -0
- package/dist/src/ui/dashboard.html +626 -0
- package/dist/src/ui/dashboard.js +29 -0
- package/dist/src/ui/dashboard.js.map +1 -0
- package/dist/src/ui/define_config.d.ts +87 -0
- package/dist/src/ui/define_config.d.ts.map +1 -0
- package/dist/src/ui/define_config.js +104 -0
- package/dist/src/ui/define_config.js.map +1 -0
- package/dist/src/ui/extension_api.d.ts +23 -0
- package/dist/src/ui/extension_api.d.ts.map +1 -0
- package/dist/src/ui/extension_api.js +50 -0
- package/dist/src/ui/extension_api.js.map +1 -0
- package/dist/src/ui/guard.d.ts +33 -0
- package/dist/src/ui/guard.d.ts.map +1 -0
- package/dist/src/ui/guard.js +47 -0
- package/dist/src/ui/guard.js.map +1 -0
- package/dist/src/ui/http.d.ts +47 -0
- package/dist/src/ui/http.d.ts.map +1 -0
- package/dist/src/ui/http.js +43 -0
- package/dist/src/ui/http.js.map +1 -0
- package/dist/src/ui/index.d.ts +12 -0
- package/dist/src/ui/index.d.ts.map +1 -0
- package/dist/src/ui/index.js +13 -0
- package/dist/src/ui/index.js.map +1 -0
- package/dist/src/watchers/cache_watcher.d.ts +60 -0
- package/dist/src/watchers/cache_watcher.d.ts.map +1 -0
- package/dist/src/watchers/cache_watcher.js +72 -0
- package/dist/src/watchers/cache_watcher.js.map +1 -0
- package/dist/src/watchers/define_config.d.ts +38 -0
- package/dist/src/watchers/define_config.d.ts.map +1 -0
- package/dist/src/watchers/define_config.js +17 -0
- package/dist/src/watchers/define_config.js.map +1 -0
- package/dist/src/watchers/emitter.d.ts +32 -0
- package/dist/src/watchers/emitter.d.ts.map +1 -0
- package/dist/src/watchers/emitter.js +2 -0
- package/dist/src/watchers/emitter.js.map +1 -0
- package/dist/src/watchers/http_client_watcher.d.ts +74 -0
- package/dist/src/watchers/http_client_watcher.d.ts.map +1 -0
- package/dist/src/watchers/http_client_watcher.js +168 -0
- package/dist/src/watchers/http_client_watcher.js.map +1 -0
- package/dist/src/watchers/index.d.ts +19 -0
- package/dist/src/watchers/index.d.ts.map +1 -0
- package/dist/src/watchers/index.js +19 -0
- package/dist/src/watchers/index.js.map +1 -0
- package/dist/src/watchers/logs_watcher.d.ts +82 -0
- package/dist/src/watchers/logs_watcher.d.ts.map +1 -0
- package/dist/src/watchers/logs_watcher.js +145 -0
- package/dist/src/watchers/logs_watcher.js.map +1 -0
- package/dist/src/watchers/lucid_query_watcher.d.ts +64 -0
- package/dist/src/watchers/lucid_query_watcher.d.ts.map +1 -0
- package/dist/src/watchers/lucid_query_watcher.js +84 -0
- package/dist/src/watchers/lucid_query_watcher.js.map +1 -0
- package/dist/src/watchers/mail_watcher.d.ts +51 -0
- package/dist/src/watchers/mail_watcher.d.ts.map +1 -0
- package/dist/src/watchers/mail_watcher.js +93 -0
- package/dist/src/watchers/mail_watcher.js.map +1 -0
- package/dist/src/watchers/normalize_http_target.d.ts +17 -0
- package/dist/src/watchers/normalize_http_target.d.ts.map +1 -0
- package/dist/src/watchers/normalize_http_target.js +41 -0
- package/dist/src/watchers/normalize_http_target.js.map +1 -0
- package/dist/src/watchers/query_family_hash.d.ts +8 -0
- package/dist/src/watchers/query_family_hash.d.ts.map +1 -0
- package/dist/src/watchers/query_family_hash.js +31 -0
- package/dist/src/watchers/query_family_hash.js.map +1 -0
- package/dist/src/watchers/record.d.ts +22 -0
- package/dist/src/watchers/record.d.ts.map +1 -0
- package/dist/src/watchers/record.js +48 -0
- package/dist/src/watchers/record.js.map +1 -0
- package/dist/stubs/config/telescope.stub +56 -0
- package/dist/stubs/config/telescope_ai.stub +36 -0
- package/dist/stubs/config/telescope_alerts.stub +47 -0
- package/dist/stubs/config/telescope_ui.stub +40 -0
- package/dist/stubs/config/telescope_watchers.stub +30 -0
- package/dist/stubs/database/migrations/create_telescope_entries_table.stub +39 -0
- package/dist/stubs/main.d.ts +6 -0
- package/dist/stubs/main.d.ts.map +1 -0
- package/dist/stubs/main.js +7 -0
- package/dist/stubs/main.js.map +1 -0
- package/package.json +140 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type DiagnosticEvent } from './diagnostics_registry.js';
|
|
2
|
+
import { type RecordInput } from './entry.js';
|
|
3
|
+
import type { TelescopeStore } from './store.js';
|
|
4
|
+
/** Telescope entry `type` produced by this watcher. */
|
|
5
|
+
export declare const DIAGNOSTIC_ENTRY_TYPE: "diagnostic";
|
|
6
|
+
/**
|
|
7
|
+
* What a single recorded diagnostic entry looks like. Mirrors the
|
|
8
|
+
* {@link DiagnosticEvent} envelope, with the library-defined data preserved
|
|
9
|
+
* verbatim under `payload`.
|
|
10
|
+
*/
|
|
11
|
+
export interface DiagnosticEntryContent {
|
|
12
|
+
/** Envelope schema version, or `null` on a legacy (pre-versioning) envelope. */
|
|
13
|
+
v: number | null;
|
|
14
|
+
/** The emitting library, e.g. `'billing'`. */
|
|
15
|
+
lib: string;
|
|
16
|
+
/** The event within that library, e.g. `'invoice-paid'`. */
|
|
17
|
+
event: string;
|
|
18
|
+
/** Epoch millis the producer stamped the event with. */
|
|
19
|
+
ts: number;
|
|
20
|
+
/** The trace id the producer resolved, or `null` when none. */
|
|
21
|
+
traceId: string | null;
|
|
22
|
+
/** The library-defined payload, recorded as-is. */
|
|
23
|
+
payload: unknown;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The ONE generic watcher behind `@adonis-agora/telescope`'s diagnostics integration. It
|
|
27
|
+
* records every event any `@adonis-agora/*` library emits through `@adonis-agora/diagnostics` —
|
|
28
|
+
* one `diagnostic` entry per `agora:<lib>:<event>` publish — without a bespoke
|
|
29
|
+
* watcher per library. This is the Agora equivalent of NestJS's
|
|
30
|
+
* `@dudousxd/nestjs-diagnostics-telescope` extension.
|
|
31
|
+
*
|
|
32
|
+
* ## Cross-repo decoupling
|
|
33
|
+
* Telescope CANNOT import `@adonis-agora/diagnostics`. Instead it reads the registry
|
|
34
|
+
* `@adonis-agora/diagnostics` publishes on `Symbol.for('@agora/diagnostics:registry')`
|
|
35
|
+
* (`{ channels, listeners }`) and subscribes to each channel via the Node builtin
|
|
36
|
+
* `node:diagnostics_channel` — no Agora import needed.
|
|
37
|
+
*
|
|
38
|
+
* ## How it auto-subscribes to current + future channels
|
|
39
|
+
* `node:diagnostics_channel` has no wildcard, so on {@link start} the watcher:
|
|
40
|
+
* 1. subscribes to every channel already in `registry.channels`, and
|
|
41
|
+
* 2. adds a listener to `registry.listeners` so any channel that appears later
|
|
42
|
+
* (a library's first emit) is subscribed too.
|
|
43
|
+
*
|
|
44
|
+
* Subscribing also flips each producer's `channel.hasSubscribers` to `true`, which
|
|
45
|
+
* is what makes the producer build + publish envelopes at all (zero-overhead when
|
|
46
|
+
* nobody listens).
|
|
47
|
+
*/
|
|
48
|
+
export declare class DiagnosticsWatcher {
|
|
49
|
+
private readonly store;
|
|
50
|
+
readonly type: "diagnostic";
|
|
51
|
+
private started;
|
|
52
|
+
/** The listener we added to `registry.listeners`, for exact removal on stop. */
|
|
53
|
+
private registryListener;
|
|
54
|
+
/** name → the subscribe handler we attached, so cleanup can detach exactly. */
|
|
55
|
+
private readonly subscriptions;
|
|
56
|
+
constructor(store: TelescopeStore);
|
|
57
|
+
/**
|
|
58
|
+
* Begin recording. Subscribes to every currently-registered channel and arms a
|
|
59
|
+
* listener for future ones. A no-op when `@adonis-agora/diagnostics` is not loaded
|
|
60
|
+
* (the registry slot is absent) — telescope degrades gracefully.
|
|
61
|
+
*/
|
|
62
|
+
start(): void;
|
|
63
|
+
/** Unsubscribe from every channel and stop watching for new ones. */
|
|
64
|
+
stop(): void;
|
|
65
|
+
/** Subscribe once to `name`, recording each publish as a `diagnostic` entry. */
|
|
66
|
+
private subscribe;
|
|
67
|
+
/** Validate + record, swallowing any failure so a producer can never break. */
|
|
68
|
+
private safeRecord;
|
|
69
|
+
}
|
|
70
|
+
/** Map a {@link DiagnosticEvent} envelope to a Telescope {@link RecordInput}. */
|
|
71
|
+
export declare function buildDiagnosticEntry(msg: DiagnosticEvent): RecordInput<DiagnosticEntryContent>;
|
|
72
|
+
//# sourceMappingURL=diagnostics_watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics_watcher.d.ts","sourceRoot":"","sources":["../../src/diagnostics_watcher.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,eAAe,EAGrB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,uDAAuD;AACvD,eAAO,MAAM,qBAAqB,cAAuB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjB,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,KAAK,EAAE,MAAM,CAAC;IACd,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,+DAA+D;IAC/D,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mDAAmD;IACnD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,kBAAkB;IAQjB,OAAO,CAAC,QAAQ,CAAC,KAAK;IAPlC,QAAQ,CAAC,IAAI,eAAyB;IACtC,OAAO,CAAC,OAAO,CAAS;IACxB,gFAAgF;IAChF,OAAO,CAAC,gBAAgB,CAAyC;IACjE,+EAA+E;IAC/E,OAAO,CAAC,QAAQ,CAAC,aAAa,CAA6C;gBAE9C,KAAK,EAAE,cAAc;IAElD;;;;OAIG;IACH,KAAK,IAAI,IAAI;IAab,qEAAqE;IACrE,IAAI,IAAI,IAAI;IAYZ,gFAAgF;IAChF,OAAO,CAAC,SAAS;IAQjB,+EAA+E;IAC/E,OAAO,CAAC,UAAU;CAcnB;AAED,iFAAiF;AACjF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,GAAG,WAAW,CAAC,sBAAsB,CAAC,CAoB9F"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import diagnostics_channel from 'node:diagnostics_channel';
|
|
2
|
+
import { getDiagnosticsRegistry, isDiagnosticEvent, } from './diagnostics_registry.js';
|
|
3
|
+
import { EntryType } from './entry.js';
|
|
4
|
+
/** Telescope entry `type` produced by this watcher. */
|
|
5
|
+
export const DIAGNOSTIC_ENTRY_TYPE = EntryType.Diagnostic;
|
|
6
|
+
/**
|
|
7
|
+
* The ONE generic watcher behind `@adonis-agora/telescope`'s diagnostics integration. It
|
|
8
|
+
* records every event any `@adonis-agora/*` library emits through `@adonis-agora/diagnostics` —
|
|
9
|
+
* one `diagnostic` entry per `agora:<lib>:<event>` publish — without a bespoke
|
|
10
|
+
* watcher per library. This is the Agora equivalent of NestJS's
|
|
11
|
+
* `@dudousxd/nestjs-diagnostics-telescope` extension.
|
|
12
|
+
*
|
|
13
|
+
* ## Cross-repo decoupling
|
|
14
|
+
* Telescope CANNOT import `@adonis-agora/diagnostics`. Instead it reads the registry
|
|
15
|
+
* `@adonis-agora/diagnostics` publishes on `Symbol.for('@agora/diagnostics:registry')`
|
|
16
|
+
* (`{ channels, listeners }`) and subscribes to each channel via the Node builtin
|
|
17
|
+
* `node:diagnostics_channel` — no Agora import needed.
|
|
18
|
+
*
|
|
19
|
+
* ## How it auto-subscribes to current + future channels
|
|
20
|
+
* `node:diagnostics_channel` has no wildcard, so on {@link start} the watcher:
|
|
21
|
+
* 1. subscribes to every channel already in `registry.channels`, and
|
|
22
|
+
* 2. adds a listener to `registry.listeners` so any channel that appears later
|
|
23
|
+
* (a library's first emit) is subscribed too.
|
|
24
|
+
*
|
|
25
|
+
* Subscribing also flips each producer's `channel.hasSubscribers` to `true`, which
|
|
26
|
+
* is what makes the producer build + publish envelopes at all (zero-overhead when
|
|
27
|
+
* nobody listens).
|
|
28
|
+
*/
|
|
29
|
+
export class DiagnosticsWatcher {
|
|
30
|
+
store;
|
|
31
|
+
type = DIAGNOSTIC_ENTRY_TYPE;
|
|
32
|
+
started = false;
|
|
33
|
+
/** The listener we added to `registry.listeners`, for exact removal on stop. */
|
|
34
|
+
registryListener = null;
|
|
35
|
+
/** name → the subscribe handler we attached, so cleanup can detach exactly. */
|
|
36
|
+
subscriptions = new Map();
|
|
37
|
+
constructor(store) {
|
|
38
|
+
this.store = store;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Begin recording. Subscribes to every currently-registered channel and arms a
|
|
42
|
+
* listener for future ones. A no-op when `@adonis-agora/diagnostics` is not loaded
|
|
43
|
+
* (the registry slot is absent) — telescope degrades gracefully.
|
|
44
|
+
*/
|
|
45
|
+
start() {
|
|
46
|
+
if (this.started)
|
|
47
|
+
return;
|
|
48
|
+
this.started = true;
|
|
49
|
+
const registry = getDiagnosticsRegistry();
|
|
50
|
+
if (!registry)
|
|
51
|
+
return;
|
|
52
|
+
for (const name of registry.channels)
|
|
53
|
+
this.subscribe(name);
|
|
54
|
+
const listener = (name) => this.subscribe(name);
|
|
55
|
+
this.registryListener = listener;
|
|
56
|
+
registry.listeners.add(listener);
|
|
57
|
+
}
|
|
58
|
+
/** Unsubscribe from every channel and stop watching for new ones. */
|
|
59
|
+
stop() {
|
|
60
|
+
if (this.registryListener) {
|
|
61
|
+
getDiagnosticsRegistry()?.listeners.delete(this.registryListener);
|
|
62
|
+
this.registryListener = null;
|
|
63
|
+
}
|
|
64
|
+
for (const [name, handler] of this.subscriptions) {
|
|
65
|
+
diagnostics_channel.channel(name).unsubscribe(handler);
|
|
66
|
+
}
|
|
67
|
+
this.subscriptions.clear();
|
|
68
|
+
this.started = false;
|
|
69
|
+
}
|
|
70
|
+
/** Subscribe once to `name`, recording each publish as a `diagnostic` entry. */
|
|
71
|
+
subscribe(name) {
|
|
72
|
+
if (this.subscriptions.has(name))
|
|
73
|
+
return;
|
|
74
|
+
const handler = (msg) => this.safeRecord(msg);
|
|
75
|
+
this.subscriptions.set(name, handler);
|
|
76
|
+
const channel = diagnostics_channel.channel(name);
|
|
77
|
+
channel.subscribe(handler);
|
|
78
|
+
}
|
|
79
|
+
/** Validate + record, swallowing any failure so a producer can never break. */
|
|
80
|
+
safeRecord(msg) {
|
|
81
|
+
try {
|
|
82
|
+
if (!isDiagnosticEvent(msg))
|
|
83
|
+
return;
|
|
84
|
+
// This runs inside a synchronous `node:diagnostics_channel` subscriber, so we
|
|
85
|
+
// CANNOT await the now-async store. Fire-and-forget and swallow rejections —
|
|
86
|
+
// telescope must never break (or block) an emitting code path.
|
|
87
|
+
void this.store.record(buildDiagnosticEntry(msg)).catch((err) => {
|
|
88
|
+
console.error('DiagnosticsWatcher: failed to record diagnostic event:', err);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
// NOT rethrown — telescope must never break an emitting code path.
|
|
93
|
+
console.error('DiagnosticsWatcher: failed to record diagnostic event:', err);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Map a {@link DiagnosticEvent} envelope to a Telescope {@link RecordInput}. */
|
|
98
|
+
export function buildDiagnosticEntry(msg) {
|
|
99
|
+
const traceId = msg.traceId ?? null;
|
|
100
|
+
const content = {
|
|
101
|
+
// Tolerate envelopes from emitters that predate schema versioning.
|
|
102
|
+
v: msg.v ?? null,
|
|
103
|
+
lib: msg.lib,
|
|
104
|
+
event: msg.event,
|
|
105
|
+
ts: msg.ts,
|
|
106
|
+
traceId,
|
|
107
|
+
payload: msg.payload,
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
type: DIAGNOSTIC_ENTRY_TYPE,
|
|
111
|
+
// Group by lib + event so a dashboard can roll up "billing:invoice-paid".
|
|
112
|
+
familyHash: `${msg.lib}:${msg.event}`,
|
|
113
|
+
tags: [`lib:${msg.lib}`, `event:${msg.event}`, ...(traceId ? [`trace:${traceId}`] : [])],
|
|
114
|
+
content,
|
|
115
|
+
// Carry the producer-resolved trace id (it knows the emitting context best).
|
|
116
|
+
traceId,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=diagnostics_watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnostics_watcher.js","sourceRoot":"","sources":["../../src/diagnostics_watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in Telescope entry types shipped by `@adonis-agora/telescope`. Adapted from
|
|
3
|
+
* the NestJS `nestjs-telescope` core, trimmed to the headless slice this package
|
|
4
|
+
* actually records (request + diagnostic). The remaining values are reserved so a
|
|
5
|
+
* future watcher (a Lucid query watcher, a mailer watcher, …) can record under a
|
|
6
|
+
* stable, already-documented type without a breaking change.
|
|
7
|
+
*/
|
|
8
|
+
export declare const EntryType: {
|
|
9
|
+
/** An inbound HTTP request, recorded by {@link TelescopeMiddleware}. */
|
|
10
|
+
readonly Request: "request";
|
|
11
|
+
/** A single `agora:<lib>:<event>` diagnostics publish. */
|
|
12
|
+
readonly Diagnostic: "diagnostic";
|
|
13
|
+
readonly Query: "query";
|
|
14
|
+
readonly Job: "job";
|
|
15
|
+
readonly Exception: "exception";
|
|
16
|
+
readonly Mail: "mail";
|
|
17
|
+
readonly Cache: "cache";
|
|
18
|
+
readonly Redis: "redis";
|
|
19
|
+
readonly Event: "event";
|
|
20
|
+
readonly Log: "log";
|
|
21
|
+
/** An OUTBOUND HTTP call, recorded by the http-client watcher. */
|
|
22
|
+
readonly HttpClient: "http-client";
|
|
23
|
+
};
|
|
24
|
+
export type BuiltinEntryType = (typeof EntryType)[keyof typeof EntryType];
|
|
25
|
+
/**
|
|
26
|
+
* Where a batch of entries originated. An entry recorded inside an HTTP request
|
|
27
|
+
* is `http`; one recorded by a queue worker is `queue`; a diagnostic recorded
|
|
28
|
+
* with no active request defaults to `manual`.
|
|
29
|
+
*/
|
|
30
|
+
declare const BATCH_ORIGINS: readonly ["http", "queue", "schedule", "cli", "manual"];
|
|
31
|
+
export type BatchOrigin = (typeof BATCH_ORIGINS)[number];
|
|
32
|
+
export declare function isBatchOrigin(value: unknown): value is BatchOrigin;
|
|
33
|
+
/**
|
|
34
|
+
* A captured, persisted observability record. `content` is type-specific (e.g.
|
|
35
|
+
* {@link RequestEntryContent} for a `request`, {@link DiagnosticEntryContent} for
|
|
36
|
+
* a `diagnostic`).
|
|
37
|
+
*/
|
|
38
|
+
export interface Entry<TContent = unknown> {
|
|
39
|
+
/** Unique id of this entry. */
|
|
40
|
+
id: string;
|
|
41
|
+
/** The entry type, one of {@link EntryType} (or a custom string). */
|
|
42
|
+
type: string;
|
|
43
|
+
/**
|
|
44
|
+
* Stable grouping key — entries that share a `familyHash` are "the same kind of
|
|
45
|
+
* thing" (e.g. all `billing:invoice-paid` diagnostics). `null` when the entry
|
|
46
|
+
* is not groupable.
|
|
47
|
+
*/
|
|
48
|
+
familyHash: string | null;
|
|
49
|
+
/** The type-specific payload. */
|
|
50
|
+
content: TContent;
|
|
51
|
+
/** Searchable labels, e.g. `lib:billing`, `event:invoice-paid`, `status:500`. */
|
|
52
|
+
tags: string[];
|
|
53
|
+
/** Monotonic record order within this process. */
|
|
54
|
+
sequence: number;
|
|
55
|
+
/** Wall-clock duration of the recorded operation, when known. */
|
|
56
|
+
durationMs: number | null;
|
|
57
|
+
/** Where the recording happened. */
|
|
58
|
+
origin: BatchOrigin;
|
|
59
|
+
/** The active trace id at record time, or `null` when no context. */
|
|
60
|
+
traceId: string | null;
|
|
61
|
+
/** When the entry was recorded. */
|
|
62
|
+
createdAt: Date;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* What a watcher hands to {@link TelescopeStore.record}. The store fills in `id`,
|
|
66
|
+
* `sequence`, `createdAt`, and resolves `traceId`/`origin` from the ambient
|
|
67
|
+
* context when the caller omits them.
|
|
68
|
+
*/
|
|
69
|
+
export interface RecordInput<TContent = unknown> {
|
|
70
|
+
type: string;
|
|
71
|
+
content: TContent;
|
|
72
|
+
familyHash?: string | null;
|
|
73
|
+
tags?: string[];
|
|
74
|
+
durationMs?: number | null;
|
|
75
|
+
/** Override the resolved trace id; defaults to the ambient `@adonis-agora/context`. */
|
|
76
|
+
traceId?: string | null;
|
|
77
|
+
/** Override the batch origin; defaults to `http` when a request is active. */
|
|
78
|
+
origin?: BatchOrigin;
|
|
79
|
+
}
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/entry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,SAAS;IACpB,wEAAwE;;IAExE,0DAA0D;;;;;;;;;;IAW1D,kEAAkE;;CAE1D,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,OAAO,SAAS,CAAC,CAAC;AAE1E;;;;GAIG;AACH,QAAA,MAAM,aAAa,yDAA0D,CAAC;AAC9E,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,aAAa,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAElE;AAED;;;;GAIG;AACH,MAAM,WAAW,KAAK,CAAC,QAAQ,GAAG,OAAO;IACvC,+BAA+B;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,iCAAiC;IACjC,OAAO,EAAE,QAAQ,CAAC;IAClB,iFAAiF;IACjF,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,iEAAiE;IACjE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oCAAoC;IACpC,MAAM,EAAE,WAAW,CAAC;IACpB,qEAAqE;IACrE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,mCAAmC;IACnC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW,CAAC,QAAQ,GAAG,OAAO;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,QAAQ,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uFAAuF;IACvF,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,8EAA8E;IAC9E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The built-in Telescope entry types shipped by `@adonis-agora/telescope`. Adapted from
|
|
3
|
+
* the NestJS `nestjs-telescope` core, trimmed to the headless slice this package
|
|
4
|
+
* actually records (request + diagnostic). The remaining values are reserved so a
|
|
5
|
+
* future watcher (a Lucid query watcher, a mailer watcher, …) can record under a
|
|
6
|
+
* stable, already-documented type without a breaking change.
|
|
7
|
+
*/
|
|
8
|
+
export const EntryType = {
|
|
9
|
+
/** An inbound HTTP request, recorded by {@link TelescopeMiddleware}. */
|
|
10
|
+
Request: 'request',
|
|
11
|
+
/** A single `agora:<lib>:<event>` diagnostics publish. */
|
|
12
|
+
Diagnostic: 'diagnostic',
|
|
13
|
+
// — reserved for deferred per-tech watchers (see DESIGN.md) —
|
|
14
|
+
Query: 'query',
|
|
15
|
+
Job: 'job',
|
|
16
|
+
Exception: 'exception',
|
|
17
|
+
Mail: 'mail',
|
|
18
|
+
Cache: 'cache',
|
|
19
|
+
Redis: 'redis',
|
|
20
|
+
Event: 'event',
|
|
21
|
+
Log: 'log',
|
|
22
|
+
/** An OUTBOUND HTTP call, recorded by the http-client watcher. */
|
|
23
|
+
HttpClient: 'http-client',
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Where a batch of entries originated. An entry recorded inside an HTTP request
|
|
27
|
+
* is `http`; one recorded by a queue worker is `queue`; a diagnostic recorded
|
|
28
|
+
* with no active request defaults to `manual`.
|
|
29
|
+
*/
|
|
30
|
+
const BATCH_ORIGINS = ['http', 'queue', 'schedule', 'cli', 'manual'];
|
|
31
|
+
export function isBatchOrigin(value) {
|
|
32
|
+
return typeof value === 'string' && BATCH_ORIGINS.includes(value);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/entry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,wEAAwE;IACxE,OAAO,EAAE,SAAS;IAClB,0DAA0D;IAC1D,UAAU,EAAE,YAAY;IACxB,8DAA8D;IAC9D,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,KAAK;IACV,SAAS,EAAE,WAAW;IACtB,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,OAAO;IACd,GAAG,EAAE,KAAK;IACV,kEAAkE;IAClE,UAAU,EAAE,aAAa;CACjB,CAAC;AAIX;;;;GAIG;AACH,MAAM,aAAa,GAAG,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,CAAU,CAAC;AAG9E,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAK,aAAmC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AAC3F,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The grouping key for "the same error". Two occurrences share a family when
|
|
3
|
+
* they have the same name, the same message, AND originate at the same top stack
|
|
4
|
+
* frame — so a `TypeError: x is undefined` thrown from two unrelated call sites
|
|
5
|
+
* stays two families, while the same bug recurring stays ONE. This is what the
|
|
6
|
+
* dashboard's exception groupings and the `new-exception` alert dedup on, so it
|
|
7
|
+
* must be deterministic across processes and across recording sources.
|
|
8
|
+
*
|
|
9
|
+
* Ported from the NestJS `nestjs-telescope` core. Why include the top frame:
|
|
10
|
+
* name+message alone over-groups (the same generic message from different code
|
|
11
|
+
* paths collapses into one family) and a host that embeds ids in messages would
|
|
12
|
+
* otherwise under-group; pinning to the first frame is the pragmatic middle
|
|
13
|
+
* ground used by most error trackers.
|
|
14
|
+
*/
|
|
15
|
+
export interface ExceptionFamilyParts {
|
|
16
|
+
/** Error class/name (e.g. `TypeError`); empty string when unknown. */
|
|
17
|
+
name: string;
|
|
18
|
+
/** Error message; empty string when unknown. */
|
|
19
|
+
message: string;
|
|
20
|
+
/** Full stack string, or `null` — only its FIRST frame contributes. */
|
|
21
|
+
stack: string | null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the stable family hash for an exception from its name, message and top
|
|
25
|
+
* stack frame. Pure string concatenation (no hashing) keeps it human-readable in
|
|
26
|
+
* the dashboard and trivially equal across every recording path.
|
|
27
|
+
*/
|
|
28
|
+
export declare function exceptionFamilyHash(parts: ExceptionFamilyParts): string;
|
|
29
|
+
//# sourceMappingURL=exception_family_hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exception_family_hash.d.ts","sourceRoot":"","sources":["../../src/exception_family_hash.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAsBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAGvE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract the first stack FRAME line (the deepest call site), skipping the
|
|
3
|
+
* leading `Name: message` header that V8 and most browsers prepend. Returns an
|
|
4
|
+
* empty string when no frame line is present, so the family key degrades to
|
|
5
|
+
* name+message rather than throwing.
|
|
6
|
+
*/
|
|
7
|
+
function topStackFrame(stack) {
|
|
8
|
+
if (stack === null)
|
|
9
|
+
return '';
|
|
10
|
+
for (const rawLine of stack.split('\n')) {
|
|
11
|
+
const line = rawLine.trim();
|
|
12
|
+
// The first line is usually the `Error: message` header (no `at `). Frame
|
|
13
|
+
// lines start with `at ` in V8; browsers vary, so also accept an `@`-form
|
|
14
|
+
// (Firefox/Safari: `fn@url:line:col`). Take the first that looks like a frame.
|
|
15
|
+
if (line.startsWith('at ') || line.includes('@')) {
|
|
16
|
+
return line;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return '';
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build the stable family hash for an exception from its name, message and top
|
|
23
|
+
* stack frame. Pure string concatenation (no hashing) keeps it human-readable in
|
|
24
|
+
* the dashboard and trivially equal across every recording path.
|
|
25
|
+
*/
|
|
26
|
+
export function exceptionFamilyHash(parts) {
|
|
27
|
+
const frame = topStackFrame(parts.stack);
|
|
28
|
+
return `${parts.name}:${parts.message}:${frame}`;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=exception_family_hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exception_family_hash.js","sourceRoot":"","sources":["../../src/exception_family_hash.ts"],"names":[],"mappings":"AAuBA;;;;;GAKG;AACH,SAAS,aAAa,CAAC,KAAoB;IACzC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC9B,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,0EAA0E;QAC1E,0EAA0E;QAC1E,+EAA+E;QAC/E,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACjD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA2B;IAC7D,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACzC,OAAO,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,IAAI,KAAK,EAAE,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type Entry, type RecordInput } from './entry.js';
|
|
2
|
+
import type { TelescopeStore } from './store.js';
|
|
3
|
+
/** The recorded body of an `exception` entry. */
|
|
4
|
+
export interface ExceptionEntryContent {
|
|
5
|
+
/** Error class/name (e.g. `TypeError`, `Error`). */
|
|
6
|
+
name: string;
|
|
7
|
+
/** Error message. */
|
|
8
|
+
message: string;
|
|
9
|
+
/** Full stack string, or `null` when none was captured. */
|
|
10
|
+
stack: string | null;
|
|
11
|
+
/** HTTP method, when the exception was recorded inside a request. */
|
|
12
|
+
method: string | null;
|
|
13
|
+
/** Request url (no query string), when recorded inside a request. */
|
|
14
|
+
url: string | null;
|
|
15
|
+
/** The active trace id at record time, or `null`. */
|
|
16
|
+
traceId: string | null;
|
|
17
|
+
}
|
|
18
|
+
/** Optional request/correlation context for {@link recordException}. */
|
|
19
|
+
export interface RecordExceptionContext {
|
|
20
|
+
/** HTTP method (upper-cased by the watcher), when applicable. */
|
|
21
|
+
method?: string;
|
|
22
|
+
/** Request url; the query string is stripped. */
|
|
23
|
+
url?: string;
|
|
24
|
+
/** Override the trace id; defaults to the ambient `@adonis-agora/context`. */
|
|
25
|
+
traceId?: string | null;
|
|
26
|
+
/** Override the batch origin; defaults to the store's resolution. */
|
|
27
|
+
origin?: RecordInput['origin'];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the `exception` {@link RecordInput} for `error` + optional context. Pure
|
|
31
|
+
* and side-effect free so it is trivially unit-testable; both the middleware
|
|
32
|
+
* auto-capture and {@link recordException} build through it. The family hash
|
|
33
|
+
* groups same-signature errors (name + message + top stack frame).
|
|
34
|
+
*/
|
|
35
|
+
export declare function buildExceptionInput(error: unknown, context?: RecordExceptionContext): RecordInput<ExceptionEntryContent>;
|
|
36
|
+
/**
|
|
37
|
+
* Record an `exception` entry into `store` from a thrown value. The pure,
|
|
38
|
+
* framework-agnostic core used by the request middleware. Resolves to the
|
|
39
|
+
* recorded {@link Entry}; backfills the content trace id from whatever the store
|
|
40
|
+
* resolved (the ambient `@adonis-agora/context`).
|
|
41
|
+
*/
|
|
42
|
+
export declare function recordExceptionInStore(store: TelescopeStore, error: unknown, context?: RecordExceptionContext): Promise<Entry<ExceptionEntryContent>>;
|
|
43
|
+
/**
|
|
44
|
+
* Standalone exception capture for manual / non-HTTP code paths — queue workers,
|
|
45
|
+
* ace commands, and an app's `app/exceptions/handler.ts` `report()`. Reads the
|
|
46
|
+
* active store from the global telescope runtime slot (the same handle the
|
|
47
|
+
* watchers record through) so callers need no DI wiring, and is a no-op when
|
|
48
|
+
* telescope is disabled / not booted.
|
|
49
|
+
*
|
|
50
|
+
* Fire-and-forget-safe: never throws. A missing store or a failing store is
|
|
51
|
+
* swallowed (warn-logged) so capturing an exception can never mask or replace the
|
|
52
|
+
* original error in the caller's failure path.
|
|
53
|
+
*
|
|
54
|
+
* ```ts
|
|
55
|
+
* // app/exceptions/handler.ts
|
|
56
|
+
* import { recordException } from '@adonis-agora/telescope'
|
|
57
|
+
* async report(error: unknown, ctx: HttpContext) {
|
|
58
|
+
* recordException(error, { method: ctx.request.method(), url: ctx.request.url() })
|
|
59
|
+
* return super.report(error, ctx)
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* Resolves to the recorded {@link Entry}, or `null` when nothing was recorded.
|
|
64
|
+
*/
|
|
65
|
+
export declare function recordException(error: unknown, context?: RecordExceptionContext): Promise<Entry<ExceptionEntryContent> | null>;
|
|
66
|
+
//# sourceMappingURL=exception_watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exception_watcher.d.ts","sourceRoot":"","sources":["../../src/exception_watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAa,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAGrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,iDAAiD;AACjD,MAAM,WAAW,qBAAqB;IACpC,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,qEAAqE;IACrE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,qEAAqE;IACrE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,qDAAqD;IACrD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACrC,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iDAAiD;IACjD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8EAA8E;IAC9E,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,qEAAqE;IACrE,MAAM,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;CAChC;AAqBD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,sBAA2B,GACnC,WAAW,CAAC,qBAAqB,CAAC,CAgBpC;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAIvC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,sBAA2B,GACnC,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,GAAG,IAAI,CAAC,CAoB9C"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { EntryType } from './entry.js';
|
|
2
|
+
import { exceptionFamilyHash } from './exception_family_hash.js';
|
|
3
|
+
import { getTelescopeRuntime } from './registry.js';
|
|
4
|
+
/** Coerce an unknown thrown value into name/message/stack. */
|
|
5
|
+
function normalizeError(error) {
|
|
6
|
+
if (error instanceof Error) {
|
|
7
|
+
return {
|
|
8
|
+
name: error.name || 'Error',
|
|
9
|
+
message: error.message,
|
|
10
|
+
stack: typeof error.stack === 'string' ? error.stack : null,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
// Non-Error throws (strings, objects) still deserve an entry.
|
|
14
|
+
return { name: 'Error', message: String(error), stack: null };
|
|
15
|
+
}
|
|
16
|
+
/** Drop a `?query=string` suffix from a url. */
|
|
17
|
+
function stripQuery(url) {
|
|
18
|
+
const q = url.indexOf('?');
|
|
19
|
+
return q === -1 ? url : url.slice(0, q);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Build the `exception` {@link RecordInput} for `error` + optional context. Pure
|
|
23
|
+
* and side-effect free so it is trivially unit-testable; both the middleware
|
|
24
|
+
* auto-capture and {@link recordException} build through it. The family hash
|
|
25
|
+
* groups same-signature errors (name + message + top stack frame).
|
|
26
|
+
*/
|
|
27
|
+
export function buildExceptionInput(error, context = {}) {
|
|
28
|
+
const { name, message, stack } = normalizeError(error);
|
|
29
|
+
const method = context.method !== undefined ? context.method.toUpperCase() : null;
|
|
30
|
+
const url = context.url !== undefined ? stripQuery(context.url) : null;
|
|
31
|
+
const familyHash = exceptionFamilyHash({ name, message, stack });
|
|
32
|
+
const tags = [`exception:${name}`, ...(method !== null ? [`method:${method}`] : [])];
|
|
33
|
+
return {
|
|
34
|
+
type: EntryType.Exception,
|
|
35
|
+
content: { name, message, stack, method, url, traceId: context.traceId ?? null },
|
|
36
|
+
familyHash,
|
|
37
|
+
tags,
|
|
38
|
+
...(context.traceId !== undefined ? { traceId: context.traceId } : {}),
|
|
39
|
+
...(context.origin !== undefined ? { origin: context.origin } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Record an `exception` entry into `store` from a thrown value. The pure,
|
|
44
|
+
* framework-agnostic core used by the request middleware. Resolves to the
|
|
45
|
+
* recorded {@link Entry}; backfills the content trace id from whatever the store
|
|
46
|
+
* resolved (the ambient `@adonis-agora/context`).
|
|
47
|
+
*/
|
|
48
|
+
export async function recordExceptionInStore(store, error, context = {}) {
|
|
49
|
+
const recorded = await store.record(buildExceptionInput(error, context));
|
|
50
|
+
recorded.content.traceId = recorded.traceId;
|
|
51
|
+
return recorded;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Standalone exception capture for manual / non-HTTP code paths — queue workers,
|
|
55
|
+
* ace commands, and an app's `app/exceptions/handler.ts` `report()`. Reads the
|
|
56
|
+
* active store from the global telescope runtime slot (the same handle the
|
|
57
|
+
* watchers record through) so callers need no DI wiring, and is a no-op when
|
|
58
|
+
* telescope is disabled / not booted.
|
|
59
|
+
*
|
|
60
|
+
* Fire-and-forget-safe: never throws. A missing store or a failing store is
|
|
61
|
+
* swallowed (warn-logged) so capturing an exception can never mask or replace the
|
|
62
|
+
* original error in the caller's failure path.
|
|
63
|
+
*
|
|
64
|
+
* ```ts
|
|
65
|
+
* // app/exceptions/handler.ts
|
|
66
|
+
* import { recordException } from '@adonis-agora/telescope'
|
|
67
|
+
* async report(error: unknown, ctx: HttpContext) {
|
|
68
|
+
* recordException(error, { method: ctx.request.method(), url: ctx.request.url() })
|
|
69
|
+
* return super.report(error, ctx)
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* Resolves to the recorded {@link Entry}, or `null` when nothing was recorded.
|
|
74
|
+
*/
|
|
75
|
+
export async function recordException(error, context = {}) {
|
|
76
|
+
let store;
|
|
77
|
+
try {
|
|
78
|
+
store = getTelescopeRuntime().store;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (store === null)
|
|
84
|
+
return null;
|
|
85
|
+
try {
|
|
86
|
+
return await recordExceptionInStore(store, error, context);
|
|
87
|
+
}
|
|
88
|
+
catch (recordError) {
|
|
89
|
+
// Observability must never break the path it observes.
|
|
90
|
+
console.warn(`Telescope: failed to record exception: ${recordError instanceof Error ? recordError.message : String(recordError)}`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=exception_watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exception_watcher.js","sourceRoot":"","sources":["../../src/exception_watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { DashboardSpec, DataProvider, ExtensionContext, ExtensionEntryType, TelescopeExtension } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Eagerly validates and collects every contribution from the registered extensions at boot. Enforces
|
|
4
|
+
* uniqueness — two extensions cannot contribute the same entry-type id, dashboard id, or provider
|
|
5
|
+
* name; a collision throws at boot (fail-closed), naming both owners, so drift is a startup error
|
|
6
|
+
* rather than a confusing runtime one. Accessors return copies; `findProvider` also tracks the owning
|
|
7
|
+
* extension so the HTTP layer can validate the `/ext/:ext/data/:provider` namespace.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ExtensionRegistry {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(extensions: readonly TelescopeExtension[], ctx: ExtensionContext);
|
|
12
|
+
entryTypes(): ExtensionEntryType[];
|
|
13
|
+
dashboards(): DashboardSpec[];
|
|
14
|
+
findProvider(name: string): DataProvider | undefined;
|
|
15
|
+
providerOwner(name: string): string | undefined;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/extension/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAEpB;;;;;;GAMG;AACH,qBAAa,iBAAiB;;gBAMhB,UAAU,EAAE,SAAS,kBAAkB,EAAE,EAAE,GAAG,EAAE,gBAAgB;IAwC5E,UAAU,IAAI,kBAAkB,EAAE;IAGlC,UAAU,IAAI,aAAa,EAAE;IAG7B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAGpD,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAGhD"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Eagerly validates and collects every contribution from the registered extensions at boot. Enforces
|
|
3
|
+
* uniqueness — two extensions cannot contribute the same entry-type id, dashboard id, or provider
|
|
4
|
+
* name; a collision throws at boot (fail-closed), naming both owners, so drift is a startup error
|
|
5
|
+
* rather than a confusing runtime one. Accessors return copies; `findProvider` also tracks the owning
|
|
6
|
+
* extension so the HTTP layer can validate the `/ext/:ext/data/:provider` namespace.
|
|
7
|
+
*/
|
|
8
|
+
export class ExtensionRegistry {
|
|
9
|
+
#entryTypes = [];
|
|
10
|
+
#dashboards = [];
|
|
11
|
+
#providers = new Map();
|
|
12
|
+
#providerOwners = new Map();
|
|
13
|
+
constructor(extensions, ctx) {
|
|
14
|
+
const entryOwners = new Map();
|
|
15
|
+
const dashOwners = new Map();
|
|
16
|
+
for (const ext of extensions) {
|
|
17
|
+
for (const et of ext.entryTypes?.(ctx) ?? []) {
|
|
18
|
+
const prev = entryOwners.get(et.id);
|
|
19
|
+
if (prev !== undefined) {
|
|
20
|
+
throw new Error(`Telescope entry type "${et.id}" is contributed by both "${prev}" and "${ext.name}". Entry-type ids must be unique.`);
|
|
21
|
+
}
|
|
22
|
+
entryOwners.set(et.id, ext.name);
|
|
23
|
+
this.#entryTypes.push(et);
|
|
24
|
+
}
|
|
25
|
+
for (const d of ext.dashboards?.(ctx) ?? []) {
|
|
26
|
+
const prev = dashOwners.get(d.id);
|
|
27
|
+
if (prev !== undefined) {
|
|
28
|
+
throw new Error(`Telescope dashboard "${d.id}" is contributed by both "${prev}" and "${ext.name}". Dashboard ids must be unique.`);
|
|
29
|
+
}
|
|
30
|
+
dashOwners.set(d.id, ext.name);
|
|
31
|
+
this.#dashboards.push(d);
|
|
32
|
+
}
|
|
33
|
+
for (const p of ext.dataProviders?.(ctx) ?? []) {
|
|
34
|
+
const prev = this.#providerOwners.get(p.name);
|
|
35
|
+
if (prev !== undefined) {
|
|
36
|
+
throw new Error(`Telescope data provider "${p.name}" is contributed by both "${prev}" and "${ext.name}". Provider names must be unique.`);
|
|
37
|
+
}
|
|
38
|
+
this.#providerOwners.set(p.name, ext.name);
|
|
39
|
+
this.#providers.set(p.name, p);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
entryTypes() {
|
|
44
|
+
return [...this.#entryTypes];
|
|
45
|
+
}
|
|
46
|
+
dashboards() {
|
|
47
|
+
return [...this.#dashboards];
|
|
48
|
+
}
|
|
49
|
+
findProvider(name) {
|
|
50
|
+
return this.#providers.get(name);
|
|
51
|
+
}
|
|
52
|
+
providerOwner(name) {
|
|
53
|
+
return this.#providerOwners.get(name);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=registry.js.map
|