@adonis-agora/telescope 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/providers/telescope_provider.d.ts +16 -0
  3. package/dist/providers/telescope_provider.d.ts.map +1 -1
  4. package/dist/providers/telescope_provider.js +33 -2
  5. package/dist/providers/telescope_provider.js.map +1 -1
  6. package/dist/providers/telescope_ui_provider.d.ts.map +1 -1
  7. package/dist/providers/telescope_ui_provider.js +87 -1
  8. package/dist/providers/telescope_ui_provider.js.map +1 -1
  9. package/dist/providers/telescope_watchers_provider.d.ts +11 -0
  10. package/dist/providers/telescope_watchers_provider.d.ts.map +1 -1
  11. package/dist/providers/telescope_watchers_provider.js +58 -0
  12. package/dist/providers/telescope_watchers_provider.js.map +1 -1
  13. package/dist/src/define_config.d.ts +50 -0
  14. package/dist/src/define_config.d.ts.map +1 -1
  15. package/dist/src/define_config.js +9 -0
  16. package/dist/src/define_config.js.map +1 -1
  17. package/dist/src/index.d.ts +23 -2
  18. package/dist/src/index.d.ts.map +1 -1
  19. package/dist/src/index.js +17 -1
  20. package/dist/src/index.js.map +1 -1
  21. package/dist/src/metrics/metrics_service.d.ts +63 -0
  22. package/dist/src/metrics/metrics_service.d.ts.map +1 -0
  23. package/dist/src/metrics/metrics_service.js +116 -0
  24. package/dist/src/metrics/metrics_service.js.map +1 -0
  25. package/dist/src/metrics/rollup.d.ts +61 -0
  26. package/dist/src/metrics/rollup.d.ts.map +1 -0
  27. package/dist/src/metrics/rollup.js +114 -0
  28. package/dist/src/metrics/rollup.js.map +1 -0
  29. package/dist/src/metrics/stats.d.ts +112 -0
  30. package/dist/src/metrics/stats.d.ts.map +1 -0
  31. package/dist/src/metrics/stats.js +255 -0
  32. package/dist/src/metrics/stats.js.map +1 -0
  33. package/dist/src/metrics/timeseries.d.ts +22 -0
  34. package/dist/src/metrics/timeseries.d.ts.map +1 -0
  35. package/dist/src/metrics/timeseries.js +34 -0
  36. package/dist/src/metrics/timeseries.js.map +1 -0
  37. package/dist/src/metrics/traces.d.ts +27 -0
  38. package/dist/src/metrics/traces.d.ts.map +1 -0
  39. package/dist/src/metrics/traces.js +71 -0
  40. package/dist/src/metrics/traces.js.map +1 -0
  41. package/dist/src/metrics/waterfall.d.ts +44 -0
  42. package/dist/src/metrics/waterfall.d.ts.map +1 -0
  43. package/dist/src/metrics/waterfall.js +99 -0
  44. package/dist/src/metrics/waterfall.js.map +1 -0
  45. package/dist/src/query/n_plus_one.d.ts +60 -0
  46. package/dist/src/query/n_plus_one.d.ts.map +1 -0
  47. package/dist/src/query/n_plus_one.js +102 -0
  48. package/dist/src/query/n_plus_one.js.map +1 -0
  49. package/dist/src/registry.d.ts +9 -0
  50. package/dist/src/registry.d.ts.map +1 -1
  51. package/dist/src/registry.js +6 -0
  52. package/dist/src/registry.js.map +1 -1
  53. package/dist/src/sampling/sampling.d.ts +52 -0
  54. package/dist/src/sampling/sampling.d.ts.map +1 -0
  55. package/dist/src/sampling/sampling.js +111 -0
  56. package/dist/src/sampling/sampling.js.map +1 -0
  57. package/dist/src/sampling/sampling_store.d.ts +33 -0
  58. package/dist/src/sampling/sampling_store.d.ts.map +1 -0
  59. package/dist/src/sampling/sampling_store.js +65 -0
  60. package/dist/src/sampling/sampling_store.js.map +1 -0
  61. package/dist/src/stream/entry_events.d.ts +45 -0
  62. package/dist/src/stream/entry_events.d.ts.map +1 -0
  63. package/dist/src/stream/entry_events.js +56 -0
  64. package/dist/src/stream/entry_events.js.map +1 -0
  65. package/dist/src/stream/index.d.ts +6 -0
  66. package/dist/src/stream/index.d.ts.map +1 -0
  67. package/dist/src/stream/index.js +5 -0
  68. package/dist/src/stream/index.js.map +1 -0
  69. package/dist/src/stream/stream_handler.d.ts +42 -0
  70. package/dist/src/stream/stream_handler.d.ts.map +1 -0
  71. package/dist/src/stream/stream_handler.js +48 -0
  72. package/dist/src/stream/stream_handler.js.map +1 -0
  73. package/dist/src/stream/streaming_store.d.ts +31 -0
  74. package/dist/src/stream/streaming_store.d.ts.map +1 -0
  75. package/dist/src/stream/streaming_store.js +49 -0
  76. package/dist/src/stream/streaming_store.js.map +1 -0
  77. package/dist/src/ui/api.d.ts +39 -1
  78. package/dist/src/ui/api.d.ts.map +1 -1
  79. package/dist/src/ui/api.js +116 -1
  80. package/dist/src/ui/api.js.map +1 -1
  81. package/dist/src/ui/define_config.d.ts +27 -0
  82. package/dist/src/ui/define_config.d.ts.map +1 -1
  83. package/dist/src/ui/define_config.js +6 -0
  84. package/dist/src/ui/define_config.js.map +1 -1
  85. package/dist/src/ui/http.d.ts +40 -0
  86. package/dist/src/ui/http.d.ts.map +1 -1
  87. package/dist/src/ui/http.js +43 -0
  88. package/dist/src/ui/http.js.map +1 -1
  89. package/dist/src/ui/index.d.ts +5 -3
  90. package/dist/src/ui/index.d.ts.map +1 -1
  91. package/dist/src/ui/index.js +3 -1
  92. package/dist/src/ui/index.js.map +1 -1
  93. package/dist/src/ui/request_replay.d.ts +56 -0
  94. package/dist/src/ui/request_replay.d.ts.map +1 -0
  95. package/dist/src/ui/request_replay.js +120 -0
  96. package/dist/src/ui/request_replay.js.map +1 -0
  97. package/dist/src/watchers/define_config.d.ts +17 -1
  98. package/dist/src/watchers/define_config.d.ts.map +1 -1
  99. package/dist/src/watchers/define_config.js +13 -0
  100. package/dist/src/watchers/define_config.js.map +1 -1
  101. package/dist/src/watchers/events_watcher.d.ts +63 -0
  102. package/dist/src/watchers/events_watcher.d.ts.map +1 -0
  103. package/dist/src/watchers/events_watcher.js +81 -0
  104. package/dist/src/watchers/events_watcher.js.map +1 -0
  105. package/dist/src/watchers/index.d.ts +6 -0
  106. package/dist/src/watchers/index.d.ts.map +1 -1
  107. package/dist/src/watchers/index.js +6 -0
  108. package/dist/src/watchers/index.js.map +1 -1
  109. package/dist/src/watchers/queue_watcher.d.ts +97 -0
  110. package/dist/src/watchers/queue_watcher.d.ts.map +1 -0
  111. package/dist/src/watchers/queue_watcher.js +114 -0
  112. package/dist/src/watchers/queue_watcher.js.map +1 -0
  113. package/dist/src/watchers/redis_watcher.d.ts +103 -0
  114. package/dist/src/watchers/redis_watcher.d.ts.map +1 -0
  115. package/dist/src/watchers/redis_watcher.js +161 -0
  116. package/dist/src/watchers/redis_watcher.js.map +1 -0
  117. package/dist/stubs/config/telescope.stub +9 -0
  118. package/dist/stubs/config/telescope_ui.stub +13 -0
  119. package/dist/stubs/config/telescope_watchers.stub +14 -3
  120. package/package.json +1 -1
@@ -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' and 'cache' event contracts are best-effort — those packages
26
- * are not present in this repo, so their event names/payloads could not be
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.2.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": {