@adonis-agora/telescope 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/dist/providers/telescope_provider.d.ts +16 -0
- package/dist/providers/telescope_provider.d.ts.map +1 -1
- package/dist/providers/telescope_provider.js +33 -2
- package/dist/providers/telescope_provider.js.map +1 -1
- package/dist/providers/telescope_ui_provider.d.ts.map +1 -1
- package/dist/providers/telescope_ui_provider.js +87 -1
- package/dist/providers/telescope_ui_provider.js.map +1 -1
- package/dist/providers/telescope_watchers_provider.d.ts +11 -0
- package/dist/providers/telescope_watchers_provider.d.ts.map +1 -1
- package/dist/providers/telescope_watchers_provider.js +58 -0
- package/dist/providers/telescope_watchers_provider.js.map +1 -1
- package/dist/src/define_config.d.ts +50 -0
- package/dist/src/define_config.d.ts.map +1 -1
- package/dist/src/define_config.js +9 -0
- package/dist/src/define_config.js.map +1 -1
- package/dist/src/index.d.ts +23 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +17 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/metrics/metrics_service.d.ts +63 -0
- package/dist/src/metrics/metrics_service.d.ts.map +1 -0
- package/dist/src/metrics/metrics_service.js +116 -0
- package/dist/src/metrics/metrics_service.js.map +1 -0
- package/dist/src/metrics/rollup.d.ts +61 -0
- package/dist/src/metrics/rollup.d.ts.map +1 -0
- package/dist/src/metrics/rollup.js +114 -0
- package/dist/src/metrics/rollup.js.map +1 -0
- package/dist/src/metrics/stats.d.ts +112 -0
- package/dist/src/metrics/stats.d.ts.map +1 -0
- package/dist/src/metrics/stats.js +255 -0
- package/dist/src/metrics/stats.js.map +1 -0
- package/dist/src/metrics/timeseries.d.ts +22 -0
- package/dist/src/metrics/timeseries.d.ts.map +1 -0
- package/dist/src/metrics/timeseries.js +34 -0
- package/dist/src/metrics/timeseries.js.map +1 -0
- package/dist/src/metrics/traces.d.ts +27 -0
- package/dist/src/metrics/traces.d.ts.map +1 -0
- package/dist/src/metrics/traces.js +71 -0
- package/dist/src/metrics/traces.js.map +1 -0
- package/dist/src/metrics/waterfall.d.ts +44 -0
- package/dist/src/metrics/waterfall.d.ts.map +1 -0
- package/dist/src/metrics/waterfall.js +99 -0
- package/dist/src/metrics/waterfall.js.map +1 -0
- package/dist/src/query/n_plus_one.d.ts +60 -0
- package/dist/src/query/n_plus_one.d.ts.map +1 -0
- package/dist/src/query/n_plus_one.js +102 -0
- package/dist/src/query/n_plus_one.js.map +1 -0
- package/dist/src/registry.d.ts +9 -0
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +6 -0
- package/dist/src/registry.js.map +1 -1
- package/dist/src/sampling/sampling.d.ts +52 -0
- package/dist/src/sampling/sampling.d.ts.map +1 -0
- package/dist/src/sampling/sampling.js +111 -0
- package/dist/src/sampling/sampling.js.map +1 -0
- package/dist/src/sampling/sampling_store.d.ts +33 -0
- package/dist/src/sampling/sampling_store.d.ts.map +1 -0
- package/dist/src/sampling/sampling_store.js +65 -0
- package/dist/src/sampling/sampling_store.js.map +1 -0
- package/dist/src/stream/entry_events.d.ts +45 -0
- package/dist/src/stream/entry_events.d.ts.map +1 -0
- package/dist/src/stream/entry_events.js +56 -0
- package/dist/src/stream/entry_events.js.map +1 -0
- package/dist/src/stream/index.d.ts +6 -0
- package/dist/src/stream/index.d.ts.map +1 -0
- package/dist/src/stream/index.js +5 -0
- package/dist/src/stream/index.js.map +1 -0
- package/dist/src/stream/stream_handler.d.ts +42 -0
- package/dist/src/stream/stream_handler.d.ts.map +1 -0
- package/dist/src/stream/stream_handler.js +48 -0
- package/dist/src/stream/stream_handler.js.map +1 -0
- package/dist/src/stream/streaming_store.d.ts +31 -0
- package/dist/src/stream/streaming_store.d.ts.map +1 -0
- package/dist/src/stream/streaming_store.js +49 -0
- package/dist/src/stream/streaming_store.js.map +1 -0
- package/dist/src/ui/api.d.ts +39 -1
- package/dist/src/ui/api.d.ts.map +1 -1
- package/dist/src/ui/api.js +116 -1
- package/dist/src/ui/api.js.map +1 -1
- package/dist/src/ui/define_config.d.ts +27 -0
- package/dist/src/ui/define_config.d.ts.map +1 -1
- package/dist/src/ui/define_config.js +6 -0
- package/dist/src/ui/define_config.js.map +1 -1
- package/dist/src/ui/http.d.ts +40 -0
- package/dist/src/ui/http.d.ts.map +1 -1
- package/dist/src/ui/http.js +43 -0
- package/dist/src/ui/http.js.map +1 -1
- package/dist/src/ui/index.d.ts +5 -3
- package/dist/src/ui/index.d.ts.map +1 -1
- package/dist/src/ui/index.js +3 -1
- package/dist/src/ui/index.js.map +1 -1
- package/dist/src/ui/request_replay.d.ts +56 -0
- package/dist/src/ui/request_replay.d.ts.map +1 -0
- package/dist/src/ui/request_replay.js +120 -0
- package/dist/src/ui/request_replay.js.map +1 -0
- package/dist/src/watchers/define_config.d.ts +17 -1
- package/dist/src/watchers/define_config.d.ts.map +1 -1
- package/dist/src/watchers/define_config.js +13 -0
- package/dist/src/watchers/define_config.js.map +1 -1
- package/dist/src/watchers/events_watcher.d.ts +63 -0
- package/dist/src/watchers/events_watcher.d.ts.map +1 -0
- package/dist/src/watchers/events_watcher.js +81 -0
- package/dist/src/watchers/events_watcher.js.map +1 -0
- package/dist/src/watchers/index.d.ts +6 -0
- package/dist/src/watchers/index.d.ts.map +1 -1
- package/dist/src/watchers/index.js +6 -0
- package/dist/src/watchers/index.js.map +1 -1
- package/dist/src/watchers/queue_watcher.d.ts +97 -0
- package/dist/src/watchers/queue_watcher.d.ts.map +1 -0
- package/dist/src/watchers/queue_watcher.js +114 -0
- package/dist/src/watchers/queue_watcher.js.map +1 -0
- package/dist/src/watchers/redis_watcher.d.ts +103 -0
- package/dist/src/watchers/redis_watcher.d.ts.map +1 -0
- package/dist/src/watchers/redis_watcher.js +161 -0
- package/dist/src/watchers/redis_watcher.js.map +1 -0
- package/dist/stubs/config/telescope.stub +9 -0
- package/dist/stubs/config/telescope_ui.stub +13 -0
- package/dist/stubs/config/telescope_watchers.stub +14 -3
- package/package.json +8 -8
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import diagnostics_channel from 'node:diagnostics_channel';
|
|
2
|
+
import { currentTraceId } from '../context_accessor.js';
|
|
3
|
+
import { EntryType } from '../entry.js';
|
|
4
|
+
import { safeRecord } from './record.js';
|
|
5
|
+
/**
|
|
6
|
+
* The `node:diagnostics_channel` tracing channel `@boringnode/queue` (the engine
|
|
7
|
+
* behind the OPTIONAL peer `@adonisjs/queue`) publishes a job EXECUTION trace on.
|
|
8
|
+
* A tracing channel fires the sub-channels `<name>:start`, `:end`, `:asyncStart`,
|
|
9
|
+
* `:asyncEnd`, and `:error` around the traced async operation; we read the outcome
|
|
10
|
+
* off `:asyncEnd` (status/duration/error are mutated onto the same message during
|
|
11
|
+
* the trace's async lifecycle).
|
|
12
|
+
*
|
|
13
|
+
* `@adonisjs/queue` is not installed in this repo, so this channel name + payload
|
|
14
|
+
* shape are sourced from the engine's `src/tracing_channels.ts` rather than
|
|
15
|
+
* verified against installed types (the engine is pre-1.0, so this surface can
|
|
16
|
+
* drift). The watcher degrades to a pure no-op when nobody publishes on the
|
|
17
|
+
* channel — i.e. when the peer is absent.
|
|
18
|
+
*/
|
|
19
|
+
export const QUEUE_EXECUTE_CHANNEL = 'boringqueue.job.execute';
|
|
20
|
+
/** Sub-channel the tracing channel fires once a traced execution settles. */
|
|
21
|
+
const ASYNC_END_CHANNEL = `${QUEUE_EXECUTE_CHANNEL}:asyncEnd`;
|
|
22
|
+
/** Default slow-job threshold in milliseconds. */
|
|
23
|
+
const DEFAULT_SLOW_MS = 1000;
|
|
24
|
+
/** Narrow an unknown tracing message to the structural shape we read. */
|
|
25
|
+
function toMessage(msg) {
|
|
26
|
+
return typeof msg === 'object' && msg !== null ? msg : {};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Records every `@adonisjs/queue` job EXECUTION as a `job` telescope entry —
|
|
30
|
+
* capturing the queue, job name, payload, outcome (completed/failed/retrying),
|
|
31
|
+
* attempts and duration, correlated to the active trace.
|
|
32
|
+
*
|
|
33
|
+
* ## How it works
|
|
34
|
+
* `@adonisjs/queue`'s engine (`@boringnode/queue`) publishes a
|
|
35
|
+
* `node:diagnostics_channel` TRACING channel per execution attempt
|
|
36
|
+
* ({@link QUEUE_EXECUTE_CHANNEL}). The watcher subscribes to its `:asyncEnd`
|
|
37
|
+
* sub-channel — fired once the execution settles, with `status`/`duration`/`error`
|
|
38
|
+
* populated — and records one entry per attempt. Subscribing is what flips the
|
|
39
|
+
* channel's `hasSubscribers` on; with no publisher (peer absent) it is a no-op.
|
|
40
|
+
*
|
|
41
|
+
* Like the other watchers, recording is fire-and-forget and fully guarded: a
|
|
42
|
+
* telescope failure can never break or block a job.
|
|
43
|
+
*/
|
|
44
|
+
export class QueueWatcher {
|
|
45
|
+
type = EntryType.Job;
|
|
46
|
+
slowMs;
|
|
47
|
+
handler = null;
|
|
48
|
+
constructor(options = {}) {
|
|
49
|
+
this.slowMs = options.slowMs ?? DEFAULT_SLOW_MS;
|
|
50
|
+
}
|
|
51
|
+
/** Subscribe to the queue execution trace. Idempotent. */
|
|
52
|
+
start() {
|
|
53
|
+
if (this.handler)
|
|
54
|
+
return;
|
|
55
|
+
const handler = (msg) => this.handle(msg);
|
|
56
|
+
this.handler = handler;
|
|
57
|
+
diagnostics_channel.channel(ASYNC_END_CHANNEL).subscribe(handler);
|
|
58
|
+
}
|
|
59
|
+
/** Unsubscribe from the queue execution trace. Safe to call when never started. */
|
|
60
|
+
stop() {
|
|
61
|
+
if (!this.handler)
|
|
62
|
+
return;
|
|
63
|
+
diagnostics_channel.channel(ASYNC_END_CHANNEL).unsubscribe(this.handler);
|
|
64
|
+
this.handler = null;
|
|
65
|
+
}
|
|
66
|
+
handle(msg) {
|
|
67
|
+
safeRecord(buildJobEntry(toMessage(msg), this.slowMs), 'QueueWatcher');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Map a queue execution message to a telescope {@link RecordInput}. Exported so
|
|
71
|
+
* the entry shape can be unit-tested without a real diagnostics publish. */
|
|
72
|
+
export function buildJobEntry(message, slowMs = DEFAULT_SLOW_MS) {
|
|
73
|
+
const job = message.job ?? {};
|
|
74
|
+
const id = typeof job.id === 'string' ? job.id : null;
|
|
75
|
+
const name = typeof job.name === 'string' ? job.name : null;
|
|
76
|
+
const queue = typeof message.queue === 'string' ? message.queue : null;
|
|
77
|
+
const status = message.status ?? 'completed';
|
|
78
|
+
const attempts = typeof job.attempts === 'number' ? job.attempts : null;
|
|
79
|
+
const durationMs = typeof message.duration === 'number' ? Math.max(0, message.duration) : null;
|
|
80
|
+
const failed = status === 'failed' || status === 'retrying';
|
|
81
|
+
const failureReason = failed && message.error !== undefined ? errorMessage(message.error) : null;
|
|
82
|
+
const traceId = currentTraceId();
|
|
83
|
+
const content = {
|
|
84
|
+
id,
|
|
85
|
+
name,
|
|
86
|
+
queue,
|
|
87
|
+
payload: job.payload ?? null,
|
|
88
|
+
status,
|
|
89
|
+
attempts,
|
|
90
|
+
failureReason,
|
|
91
|
+
traceId,
|
|
92
|
+
};
|
|
93
|
+
const tags = ['queue', `status:${status}`];
|
|
94
|
+
if (queue)
|
|
95
|
+
tags.push(`queue:${queue}`);
|
|
96
|
+
if (name)
|
|
97
|
+
tags.push(`job:${name}`);
|
|
98
|
+
if (failed)
|
|
99
|
+
tags.push('failed');
|
|
100
|
+
if (durationMs !== null && durationMs >= slowMs)
|
|
101
|
+
tags.push('slow');
|
|
102
|
+
return {
|
|
103
|
+
type: EntryType.Job,
|
|
104
|
+
familyHash: [queue, name].filter(Boolean).join(':') || null,
|
|
105
|
+
content,
|
|
106
|
+
durationMs,
|
|
107
|
+
traceId,
|
|
108
|
+
tags,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function errorMessage(error) {
|
|
112
|
+
return error instanceof Error ? error.message : String(error);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=queue_watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue_watcher.js","sourceRoot":"","sources":["../../../src/watchers/queue_watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type RecordInput } from '../entry.js';
|
|
2
|
+
/**
|
|
3
|
+
* A single ioredis command, structurally. `name` is the command (e.g. `get`),
|
|
4
|
+
* `args` are its arguments. This is the shape of the ioredis `Command` object the
|
|
5
|
+
* client funnels every command through (`Command.name` / `Command.args`).
|
|
6
|
+
*/
|
|
7
|
+
export interface RedisCommandLike {
|
|
8
|
+
name?: string;
|
|
9
|
+
args?: unknown[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The structural ioredis client surface this watcher wraps. `@adonisjs/redis`
|
|
13
|
+
* exposes the raw ioredis client on `connection().ioConnection`; ioredis funnels
|
|
14
|
+
* EVERY command through `sendCommand(command)`, so wrapping that single method
|
|
15
|
+
* captures everything (including pipelined / multi commands). `Command.promise`
|
|
16
|
+
* resolves with the reply, letting us time the round-trip.
|
|
17
|
+
*/
|
|
18
|
+
export interface RedisClientLike {
|
|
19
|
+
sendCommand(command: RedisCommandLike, ...rest: unknown[]): unknown;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The structural slice of a `@adonisjs/redis` connection: the raw ioredis client
|
|
23
|
+
* under `ioConnection`, and (when subscribing has occurred) the separate
|
|
24
|
+
* subscriber client. A connection name is read for tagging when present.
|
|
25
|
+
*/
|
|
26
|
+
export interface RedisConnectionLike {
|
|
27
|
+
connectionName?: string;
|
|
28
|
+
ioConnection?: unknown;
|
|
29
|
+
ioSubscriberConnection?: unknown;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* The structural slice of the `@adonisjs/redis` manager: it exposes the active
|
|
33
|
+
* connections and emits a `'connection'` event (via `emittery`, returning an
|
|
34
|
+
* unsubscribe function) as each new connection is created. The watcher uses both
|
|
35
|
+
* to instrument current AND future connections.
|
|
36
|
+
*/
|
|
37
|
+
export interface RedisManagerLike {
|
|
38
|
+
activeConnections?: Record<string, RedisConnectionLike> | undefined;
|
|
39
|
+
on?(event: 'connection', listener: (connection: RedisConnectionLike) => void): () => void;
|
|
40
|
+
}
|
|
41
|
+
/** The recorded body of a `redis` entry. */
|
|
42
|
+
export interface RedisEntryContent {
|
|
43
|
+
/** The command name, upper-cased (e.g. `'GET'`). */
|
|
44
|
+
command: string;
|
|
45
|
+
/** The command arguments, in order (redaction applies downstream). */
|
|
46
|
+
args: unknown[];
|
|
47
|
+
/** The connection name (e.g. `'main'`), or `null`. */
|
|
48
|
+
connection: string | null;
|
|
49
|
+
/** Round-trip duration in ms, or `null` when not awaitable. */
|
|
50
|
+
durationMs: number | null;
|
|
51
|
+
/** The active trace id at command time, or `null`. */
|
|
52
|
+
traceId: string | null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Records every Redis command issued through `@adonisjs/redis` as a `redis`
|
|
56
|
+
* telescope entry — the command name, args, connection and round-trip duration,
|
|
57
|
+
* correlated to the active trace.
|
|
58
|
+
*
|
|
59
|
+
* ## How it works
|
|
60
|
+
* `@adonisjs/redis` is an OPTIONAL peer (not installed in this repo, so its surface
|
|
61
|
+
* is sourced from its types, not verified here). Its connection wrapper exposes the
|
|
62
|
+
* raw ioredis client on `connection().ioConnection`, and ioredis funnels every
|
|
63
|
+
* command through `sendCommand(command)`. The watcher monkey-patches that single
|
|
64
|
+
* method on each connection's ioredis client: it captures `{ name, args }`, times
|
|
65
|
+
* the round-trip via the command's returned promise, and records the entry. The
|
|
66
|
+
* original is always called and its result returned/thrown unchanged — recording
|
|
67
|
+
* failures are swallowed so a telescope error can never alter a command's outcome.
|
|
68
|
+
*
|
|
69
|
+
* The watcher is constructed with the `@adonisjs/redis` manager: at {@link start}
|
|
70
|
+
* it instruments every already-active connection and arms the manager's
|
|
71
|
+
* `'connection'` event so connections created later are instrumented too. Patching
|
|
72
|
+
* is per-client and idempotent; {@link stop} restores every original `sendCommand`.
|
|
73
|
+
*
|
|
74
|
+
* ## Caveat
|
|
75
|
+
* It records exactly what each wrapped client does. If telescope's own redis
|
|
76
|
+
* storage shares a connection, those commands would be captured too — use a
|
|
77
|
+
* dedicated connection for telescope storage to avoid that noise.
|
|
78
|
+
*/
|
|
79
|
+
export declare class RedisWatcher {
|
|
80
|
+
readonly type: "redis";
|
|
81
|
+
private readonly manager;
|
|
82
|
+
/** Clients we patched, with their connection name, for clean restore on stop. */
|
|
83
|
+
private readonly wrapped;
|
|
84
|
+
private unsubscribeManager;
|
|
85
|
+
/** Construct with the resolved `@adonisjs/redis` manager (or `null` when the
|
|
86
|
+
* optional peer is absent — the watcher then no-ops). */
|
|
87
|
+
constructor(manager: unknown);
|
|
88
|
+
/** Instrument current + future connections. Idempotent. A no-op when the peer
|
|
89
|
+
* is absent. */
|
|
90
|
+
start(): void;
|
|
91
|
+
/** Restore every wrapped `sendCommand` and stop watching for new connections. */
|
|
92
|
+
stop(): void;
|
|
93
|
+
/** Wrap the ioredis client(s) backing a connection. */
|
|
94
|
+
private instrument;
|
|
95
|
+
/** Monkey-patch one ioredis client's `sendCommand`. No-op when it lacks the
|
|
96
|
+
* method or is already wrapped. */
|
|
97
|
+
private wrap;
|
|
98
|
+
private record;
|
|
99
|
+
}
|
|
100
|
+
/** Map a captured Redis command to a telescope {@link RecordInput}. Exported so
|
|
101
|
+
* the entry shape can be unit-tested without a real ioredis client. */
|
|
102
|
+
export declare function buildRedisEntry(command: RedisCommandLike, connection: string | null, durationMs: number | null): RecordInput<RedisEntryContent>;
|
|
103
|
+
//# sourceMappingURL=redis_watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis_watcher.d.ts","sourceRoot":"","sources":["../../../src/watchers/redis_watcher.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1D;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,EAAE,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,OAAO,EAAE,gBAAgB,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;CACrE;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,GAAG,SAAS,CAAC;IACpE,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,UAAU,EAAE,mBAAmB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;CAC3F;AAED,4CAA4C;AAC5C,MAAM,WAAW,iBAAiB;IAChC,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,sEAAsE;IACtE,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,sDAAsD;IACtD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,+DAA+D;IAC/D,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,sDAAsD;IACtD,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAoCD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,YAAY;IACvB,QAAQ,CAAC,IAAI,UAAmB;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwE;IAChG,OAAO,CAAC,kBAAkB,CAA6B;IAEvD;8DAC0D;gBAC9C,OAAO,EAAE,OAAO;IAI5B;qBACiB;IACjB,KAAK,IAAI,IAAI;IAgBb,iFAAiF;IACjF,IAAI,IAAI,IAAI;IAcZ,uDAAuD;IACvD,OAAO,CAAC,UAAU;IAOlB;wCACoC;IACpC,OAAO,CAAC,IAAI;IAgCZ,OAAO,CAAC,MAAM;CAOf;AAOD;wEACwE;AACxE,wBAAgB,eAAe,CAC7B,OAAO,EAAE,gBAAgB,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,UAAU,EAAE,MAAM,GAAG,IAAI,GACxB,WAAW,CAAC,iBAAiB,CAAC,CAoBhC"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { currentTraceId } from '../context_accessor.js';
|
|
2
|
+
import { EntryType } from '../entry.js';
|
|
3
|
+
import { safeRecord } from './record.js';
|
|
4
|
+
/** Marks a client whose `sendCommand` we've already wrapped, so the same instance
|
|
5
|
+
* (or two watchers / package copies sharing it) is never double-wrapped. A
|
|
6
|
+
* `Symbol.for` so both copies resolve the same brand. */
|
|
7
|
+
const PATCHED = Symbol.for('@agora/telescope:redisPatched');
|
|
8
|
+
/** Narrow an arbitrary value to one with a usable `sendCommand`. */
|
|
9
|
+
function hasSendCommand(value) {
|
|
10
|
+
if (typeof value !== 'object' || value === null || !('sendCommand' in value))
|
|
11
|
+
return false;
|
|
12
|
+
return typeof value.sendCommand === 'function';
|
|
13
|
+
}
|
|
14
|
+
/** Best-effort high-resolution clock; falls back to `Date.now()`. */
|
|
15
|
+
function now() {
|
|
16
|
+
return typeof performance === 'object' && typeof performance.now === 'function'
|
|
17
|
+
? performance.now()
|
|
18
|
+
: Date.now();
|
|
19
|
+
}
|
|
20
|
+
/** True when a value looks like a thenable (so the round-trip can be timed). */
|
|
21
|
+
function isThenable(value) {
|
|
22
|
+
return (typeof value === 'object' &&
|
|
23
|
+
value !== null &&
|
|
24
|
+
'then' in value &&
|
|
25
|
+
typeof value.then === 'function');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Records every Redis command issued through `@adonisjs/redis` as a `redis`
|
|
29
|
+
* telescope entry — the command name, args, connection and round-trip duration,
|
|
30
|
+
* correlated to the active trace.
|
|
31
|
+
*
|
|
32
|
+
* ## How it works
|
|
33
|
+
* `@adonisjs/redis` is an OPTIONAL peer (not installed in this repo, so its surface
|
|
34
|
+
* is sourced from its types, not verified here). Its connection wrapper exposes the
|
|
35
|
+
* raw ioredis client on `connection().ioConnection`, and ioredis funnels every
|
|
36
|
+
* command through `sendCommand(command)`. The watcher monkey-patches that single
|
|
37
|
+
* method on each connection's ioredis client: it captures `{ name, args }`, times
|
|
38
|
+
* the round-trip via the command's returned promise, and records the entry. The
|
|
39
|
+
* original is always called and its result returned/thrown unchanged — recording
|
|
40
|
+
* failures are swallowed so a telescope error can never alter a command's outcome.
|
|
41
|
+
*
|
|
42
|
+
* The watcher is constructed with the `@adonisjs/redis` manager: at {@link start}
|
|
43
|
+
* it instruments every already-active connection and arms the manager's
|
|
44
|
+
* `'connection'` event so connections created later are instrumented too. Patching
|
|
45
|
+
* is per-client and idempotent; {@link stop} restores every original `sendCommand`.
|
|
46
|
+
*
|
|
47
|
+
* ## Caveat
|
|
48
|
+
* It records exactly what each wrapped client does. If telescope's own redis
|
|
49
|
+
* storage shares a connection, those commands would be captured too — use a
|
|
50
|
+
* dedicated connection for telescope storage to avoid that noise.
|
|
51
|
+
*/
|
|
52
|
+
export class RedisWatcher {
|
|
53
|
+
type = EntryType.Redis;
|
|
54
|
+
manager;
|
|
55
|
+
/** Clients we patched, with their connection name, for clean restore on stop. */
|
|
56
|
+
wrapped = [];
|
|
57
|
+
unsubscribeManager = null;
|
|
58
|
+
/** Construct with the resolved `@adonisjs/redis` manager (or `null` when the
|
|
59
|
+
* optional peer is absent — the watcher then no-ops). */
|
|
60
|
+
constructor(manager) {
|
|
61
|
+
this.manager = isManager(manager) ? manager : null;
|
|
62
|
+
}
|
|
63
|
+
/** Instrument current + future connections. Idempotent. A no-op when the peer
|
|
64
|
+
* is absent. */
|
|
65
|
+
start() {
|
|
66
|
+
if (!this.manager)
|
|
67
|
+
return;
|
|
68
|
+
if (this.unsubscribeManager)
|
|
69
|
+
return;
|
|
70
|
+
const active = this.manager.activeConnections;
|
|
71
|
+
if (active && typeof active === 'object') {
|
|
72
|
+
for (const connection of Object.values(active))
|
|
73
|
+
this.instrument(connection);
|
|
74
|
+
}
|
|
75
|
+
if (typeof this.manager.on === 'function') {
|
|
76
|
+
this.unsubscribeManager = this.manager.on('connection', (connection) => this.instrument(connection));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** Restore every wrapped `sendCommand` and stop watching for new connections. */
|
|
80
|
+
stop() {
|
|
81
|
+
this.unsubscribeManager?.();
|
|
82
|
+
this.unsubscribeManager = null;
|
|
83
|
+
for (const { client } of this.wrapped) {
|
|
84
|
+
const original = client.__telescopeOriginalSendCommand__;
|
|
85
|
+
if (original) {
|
|
86
|
+
client.sendCommand = original;
|
|
87
|
+
client[PATCHED] = false;
|
|
88
|
+
client.__telescopeOriginalSendCommand__ = undefined;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
this.wrapped.length = 0;
|
|
92
|
+
}
|
|
93
|
+
/** Wrap the ioredis client(s) backing a connection. */
|
|
94
|
+
instrument(connection) {
|
|
95
|
+
if (!connection || typeof connection !== 'object')
|
|
96
|
+
return;
|
|
97
|
+
const name = typeof connection.connectionName === 'string' ? connection.connectionName : null;
|
|
98
|
+
this.wrap(connection.ioConnection, name);
|
|
99
|
+
this.wrap(connection.ioSubscriberConnection, name);
|
|
100
|
+
}
|
|
101
|
+
/** Monkey-patch one ioredis client's `sendCommand`. No-op when it lacks the
|
|
102
|
+
* method or is already wrapped. */
|
|
103
|
+
wrap(candidate, connection) {
|
|
104
|
+
if (!hasSendCommand(candidate))
|
|
105
|
+
return;
|
|
106
|
+
const client = candidate;
|
|
107
|
+
if (client[PATCHED])
|
|
108
|
+
return;
|
|
109
|
+
client[PATCHED] = true;
|
|
110
|
+
const watcher = this;
|
|
111
|
+
// Keep the EXACT original reference for a clean restore on stop(); call it
|
|
112
|
+
// with the client as `this` so binding is not baked into what we restore.
|
|
113
|
+
const original = client.sendCommand;
|
|
114
|
+
client.__telescopeOriginalSendCommand__ = original;
|
|
115
|
+
this.wrapped.push({ client, connection });
|
|
116
|
+
client.sendCommand = function patchedSendCommand(command, ...rest) {
|
|
117
|
+
const startedAt = now();
|
|
118
|
+
const result = original.call(client, command, ...rest);
|
|
119
|
+
if (isThenable(result)) {
|
|
120
|
+
const finalize = () => {
|
|
121
|
+
watcher.record(command, connection, now() - startedAt);
|
|
122
|
+
};
|
|
123
|
+
result.then(finalize, finalize);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
watcher.record(command, connection, null);
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
record(command, connection, durationMs) {
|
|
132
|
+
safeRecord(buildRedisEntry(command, connection, durationMs), 'RedisWatcher');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Narrow an arbitrary value to a {@link RedisManagerLike}. */
|
|
136
|
+
function isManager(value) {
|
|
137
|
+
return typeof value === 'object' && value !== null;
|
|
138
|
+
}
|
|
139
|
+
/** Map a captured Redis command to a telescope {@link RecordInput}. Exported so
|
|
140
|
+
* the entry shape can be unit-tested without a real ioredis client. */
|
|
141
|
+
export function buildRedisEntry(command, connection, durationMs) {
|
|
142
|
+
const name = typeof command.name === 'string' ? command.name.toUpperCase() : String(command.name ?? '');
|
|
143
|
+
const args = Array.isArray(command.args) ? command.args : [];
|
|
144
|
+
const traceId = currentTraceId();
|
|
145
|
+
const content = {
|
|
146
|
+
command: name,
|
|
147
|
+
args,
|
|
148
|
+
connection,
|
|
149
|
+
durationMs: durationMs === null ? null : Math.max(0, durationMs),
|
|
150
|
+
traceId,
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
type: EntryType.Redis,
|
|
154
|
+
familyHash: `redis:${name}`,
|
|
155
|
+
content,
|
|
156
|
+
durationMs: content.durationMs,
|
|
157
|
+
traceId,
|
|
158
|
+
tags: ['redis', `redis:${name}`, ...(connection ? [`connection:${connection}`] : [])],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=redis_watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis_watcher.js","sourceRoot":"","sources":["../../../src/watchers/redis_watcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -53,4 +53,13 @@ export default defineConfig({
|
|
|
53
53
|
// enabled: true,
|
|
54
54
|
// keys: ['ssn', 'credit_card'],
|
|
55
55
|
// },
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Live SSE streaming of new entries to the dashboard. ENABLED by default and
|
|
59
|
+
* zero-overhead while no dashboard is connected. Set `enabled: false` to turn
|
|
60
|
+
* the `<telescope>/api/stream` endpoint off entirely.
|
|
61
|
+
*/
|
|
62
|
+
// stream: {
|
|
63
|
+
// enabled: true,
|
|
64
|
+
// },
|
|
56
65
|
})
|
|
@@ -37,4 +37,17 @@ export default defineConfig({
|
|
|
37
37
|
// token: env.get('TELESCOPE_UI_TOKEN'),
|
|
38
38
|
// basic: { username: 'admin', password: env.get('TELESCOPE_UI_PASSWORD') },
|
|
39
39
|
// },
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Request REPLAY: re-issue a captured request from the dashboard against the
|
|
43
|
+
* LOCAL server. DISABLED BY DEFAULT because it re-runs a real request that may
|
|
44
|
+
* MUTATE state (a captured POST/DELETE runs again). It only ever targets
|
|
45
|
+
* `127.0.0.1:<port>` (same-origin, never an arbitrary URL) and strips
|
|
46
|
+
* cookie/authorization/host headers. Enable it knowingly:
|
|
47
|
+
*/
|
|
48
|
+
// replay: {
|
|
49
|
+
// enabled: true,
|
|
50
|
+
// // port: 3333, // defaults to PORT env or 3000
|
|
51
|
+
// // timeoutMs: 30000,
|
|
52
|
+
// },
|
|
40
53
|
})
|
|
@@ -21,10 +21,21 @@ export default defineConfig({
|
|
|
21
21
|
* - 'cache' — records @adonisjs/cache hit/miss/write/delete events.
|
|
22
22
|
* - 'http-client' — records every outbound fetch call (method, url, status, duration).
|
|
23
23
|
* - 'logs' — records AdonisJS logger output (level, message, structured fields).
|
|
24
|
+
* - 'queue' — records @adonisjs/queue job executions (optional peer; engine
|
|
25
|
+
* diagnostics-channel trace). No-op when the peer is absent.
|
|
26
|
+
* - 'events' — records every event emitted through the core Emitter (onAny),
|
|
27
|
+
* with an ignore-list (db:query / mail:sent are excluded by default).
|
|
28
|
+
* - 'redis' — records @adonisjs/redis commands (optional peer; wraps the
|
|
29
|
+
* ioredis sendCommand). No-op when the peer is absent.
|
|
24
30
|
*
|
|
25
|
-
* Note: the 'mail'
|
|
26
|
-
* are not present in this repo, so their event names/payloads could
|
|
27
|
-
* verified against their types. See the package README.
|
|
31
|
+
* Note: the 'mail' / 'cache' / 'queue' / 'redis' event contracts are best-effort —
|
|
32
|
+
* those packages are not present in this repo, so their event names/payloads could
|
|
33
|
+
* not be verified against their types. See the package README.
|
|
34
|
+
*
|
|
35
|
+
* There is intentionally NO 'schedule' watcher: AdonisJS ships no first-party
|
|
36
|
+
* scheduler and community schedulers expose no event/hook surface to tap. In the
|
|
37
|
+
* Agora ecosystem, @adonis-agora/durable bridges scheduled/cron runs onto the
|
|
38
|
+
* diagnostics bus, which the diagnostics watcher already records.
|
|
28
39
|
*/
|
|
29
40
|
// watchers: ['query'],
|
|
30
41
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adonis-agora/telescope",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Laravel Telescope-style headless observability for AdonisJS — records HTTP requests and every Agora diagnostics event as queryable entries, plus opt-in subpaths for per-technology watchers, a web dashboard, AI exception diagnosis, and alerting.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -85,10 +85,10 @@
|
|
|
85
85
|
"**/registry.ts"
|
|
86
86
|
],
|
|
87
87
|
"peerDependencies": {
|
|
88
|
-
"@adonisjs/core": "^
|
|
89
|
-
"@adonisjs/lucid": "^
|
|
90
|
-
"@adonisjs/mail": "^
|
|
91
|
-
"@adonisjs/cache": "^1.0
|
|
88
|
+
"@adonisjs/core": "^7.3.0",
|
|
89
|
+
"@adonisjs/lucid": "^22.4.0",
|
|
90
|
+
"@adonisjs/mail": "^10.2.0",
|
|
91
|
+
"@adonisjs/cache": "^2.1.0",
|
|
92
92
|
"@anthropic-ai/sdk": "^0.32.0"
|
|
93
93
|
},
|
|
94
94
|
"peerDependenciesMeta": {
|
|
@@ -106,9 +106,9 @@
|
|
|
106
106
|
}
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
|
-
"@adonisjs/assembler": "^
|
|
110
|
-
"@adonisjs/core": "^
|
|
111
|
-
"@adonisjs/lucid": "^
|
|
109
|
+
"@adonisjs/assembler": "^8.4.0",
|
|
110
|
+
"@adonisjs/core": "^7.3.0",
|
|
111
|
+
"@adonisjs/lucid": "^22.4.0",
|
|
112
112
|
"@anthropic-ai/sdk": "^0.32.0",
|
|
113
113
|
"@types/better-sqlite3": "^7.6.0",
|
|
114
114
|
"@types/node": "^20.0.0",
|