@geekmidas/telescope 9.0.2 → 10.0.0-alpha.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/README.md +14 -4
- package/dist/{http-D5kn3_Mt.d.mts → http-DULM1u2L.d.cts} +3 -3
- package/dist/{http-Dvca6fC3.d.cts.map → http-DULM1u2L.d.cts.map} +1 -1
- package/dist/{http-Dvca6fC3.d.cts → http-GB-Nlvwc.d.mts} +3 -3
- package/dist/{http-D5kn3_Mt.d.mts.map → http-GB-Nlvwc.d.mts.map} +1 -1
- package/dist/instrumentation/http.d.cts +1 -1
- package/dist/instrumentation/http.d.mts +1 -1
- package/dist/instrumentation/index.cjs +2 -2
- package/dist/instrumentation/index.d.cts +3 -3
- package/dist/instrumentation/index.d.mts +3 -3
- package/dist/instrumentation/index.mjs +2 -2
- package/dist/instrumentation/middleware.cjs +1 -1
- package/dist/instrumentation/middleware.d.cts +1 -1
- package/dist/instrumentation/middleware.d.mts +1 -1
- package/dist/instrumentation/middleware.mjs +1 -1
- package/dist/instrumentation/otel.cjs +1 -1
- package/dist/instrumentation/otel.d.cts +1 -1
- package/dist/instrumentation/otel.d.mts +1 -1
- package/dist/instrumentation/otel.mjs +1 -1
- package/dist/logger/console.d.cts.map +1 -1
- package/dist/logger/console.d.mts.map +1 -1
- package/dist/logger/pino.d.cts +2 -2
- package/dist/{middleware-DqPQpcup.cjs → middleware-Bcq6XXGG.cjs} +2 -2
- package/dist/middleware-Bcq6XXGG.cjs.map +1 -0
- package/dist/{middleware-4-GrQlOT.mjs → middleware-D1XEHvyy.mjs} +2 -2
- package/dist/middleware-D1XEHvyy.mjs.map +1 -0
- package/dist/{middleware-CA84nsNN.d.cts → middleware-PeyS-9Ny.d.cts} +2 -2
- package/dist/{middleware-CA84nsNN.d.cts.map → middleware-PeyS-9Ny.d.cts.map} +1 -1
- package/dist/{middleware-DAceVpfw.d.mts → middleware-oHvo_ZgU.d.mts} +2 -2
- package/dist/{middleware-DAceVpfw.d.mts.map → middleware-oHvo_ZgU.d.mts.map} +1 -1
- package/dist/{otel-40udNP5g.mjs → otel-B9bNoeZ2.mjs} +3 -3
- package/dist/otel-B9bNoeZ2.mjs.map +1 -0
- package/dist/{otel-1bJPb2Z7.d.mts → otel-Bk89b1lX.d.cts} +3 -3
- package/dist/{otel-1bJPb2Z7.d.mts.map → otel-Bk89b1lX.d.cts.map} +1 -1
- package/dist/{otel-COrpz_GZ.cjs → otel-BzjOtVxZ.cjs} +3 -3
- package/dist/otel-BzjOtVxZ.cjs.map +1 -0
- package/dist/{otel-SBVoVEHr.d.cts → otel-lPMsM26e.d.mts} +3 -3
- package/dist/{otel-SBVoVEHr.d.cts.map → otel-lPMsM26e.d.mts.map} +1 -1
- package/dist/storage/kysely.cjs +48 -38
- package/dist/storage/kysely.cjs.map +1 -1
- package/dist/storage/kysely.d.cts +29 -9
- package/dist/storage/kysely.d.cts.map +1 -1
- package/dist/storage/kysely.d.mts +29 -9
- package/dist/storage/kysely.d.mts.map +1 -1
- package/dist/storage/kysely.mjs +48 -38
- package/dist/storage/kysely.mjs.map +1 -1
- package/dist/tracing-BX8uY2nR.d.cts.map +1 -1
- package/dist/types-B1EIa_cQ.d.mts.map +1 -1
- package/dist/types-l7Rlb5Mg.d.cts.map +1 -1
- package/package.json +6 -3
- package/CHANGELOG.md +0 -72
- package/dist/middleware-4-GrQlOT.mjs.map +0 -1
- package/dist/middleware-DqPQpcup.cjs.map +0 -1
- package/dist/otel-40udNP5g.mjs.map +0 -1
- package/dist/otel-COrpz_GZ.cjs.map +0 -1
- package/src/Telescope.ts +0 -561
- package/src/__tests__/Telescope.spec.ts +0 -438
- package/src/__tests__/pino-transport.spec.ts +0 -115
- package/src/__tests__/redact.spec.ts +0 -336
- package/src/adapters/__tests__/lambda.spec.ts +0 -318
- package/src/adapters/__tests__/middy.spec.ts +0 -741
- package/src/adapters/index.ts +0 -22
- package/src/adapters/lambda.ts +0 -432
- package/src/adapters/middy.ts +0 -402
- package/src/adapters/types.ts +0 -181
- package/src/core.ts +0 -51
- package/src/index.ts +0 -34
- package/src/instrumentation/__tests__/core.spec.ts +0 -248
- package/src/instrumentation/__tests__/hono.spec.ts +0 -348
- package/src/instrumentation/__tests__/middleware.spec.ts +0 -528
- package/src/instrumentation/__tests__/otel.spec.ts +0 -291
- package/src/instrumentation/__tests__/tracing.spec.ts +0 -208
- package/src/instrumentation/core.ts +0 -228
- package/src/instrumentation/hono.ts +0 -270
- package/src/instrumentation/http.ts +0 -306
- package/src/instrumentation/index.ts +0 -90
- package/src/instrumentation/middleware.ts +0 -297
- package/src/instrumentation/otel.ts +0 -271
- package/src/instrumentation/setup.ts +0 -246
- package/src/instrumentation/tracing.ts +0 -145
- package/src/logger/__tests__/console.spec.ts +0 -366
- package/src/logger/__tests__/pino.spec.ts +0 -217
- package/src/logger/console.ts +0 -230
- package/src/logger/pino.ts +0 -201
- package/src/metrics/MetricsAggregator.ts +0 -555
- package/src/metrics/__tests__/MetricsAggregator.spec.ts +0 -298
- package/src/metrics/index.ts +0 -2
- package/src/metrics/storage/InMemoryMetricsStorage.ts +0 -380
- package/src/metrics/storage/MetricsStorage.ts +0 -74
- package/src/metrics/storage/index.ts +0 -3
- package/src/metrics/storage/types.ts +0 -243
- package/src/otlp/__tests__/receiver.spec.ts +0 -367
- package/src/otlp/__tests__/transformer.spec.ts +0 -455
- package/src/otlp/hono.ts +0 -120
- package/src/otlp/index.ts +0 -22
- package/src/otlp/receiver.ts +0 -162
- package/src/otlp/transformer.ts +0 -367
- package/src/otlp/types.ts +0 -364
- package/src/redact.ts +0 -116
- package/src/server/__tests__/hono.spec.ts +0 -814
- package/src/server/hono.ts +0 -442
- package/src/storage/__tests__/kysely.spec.ts +0 -715
- package/src/storage/__tests__/memory.spec.ts +0 -694
- package/src/storage/kysely.ts +0 -599
- package/src/storage/memory.ts +0 -204
- package/src/types.ts +0 -447
- package/src/ui-assets.ts +0 -40
- package/tsconfig.json +0 -9
- package/ui/CHANGELOG.md +0 -7
- package/ui/index.html +0 -12
- package/ui/node_modules/.bin/tsc +0 -21
- package/ui/node_modules/.bin/tsserver +0 -21
- package/ui/node_modules/.bin/vite +0 -21
- package/ui/package.json +0 -24
- package/ui/src/App.tsx +0 -595
- package/ui/src/api.ts +0 -88
- package/ui/src/components/ExceptionDetail.tsx +0 -100
- package/ui/src/components/FilterBar.tsx +0 -162
- package/ui/src/components/LogDetail.tsx +0 -91
- package/ui/src/components/RequestDetail.tsx +0 -143
- package/ui/src/main.tsx +0 -10
- package/ui/src/styles.css +0 -11
- package/ui/src/types.ts +0 -63
- package/ui/src/vite-env.d.ts +0 -1
- package/ui/src/vite-plugin-gkm-config.ts +0 -54
- package/ui/tsconfig.json +0 -20
- package/ui/tsconfig.tsbuildinfo +0 -1
- package/ui/vite.config.ts +0 -13
package/src/Telescope.ts
DELETED
|
@@ -1,561 +0,0 @@
|
|
|
1
|
-
import { nanoid } from 'nanoid';
|
|
2
|
-
import {
|
|
3
|
-
MetricsAggregator,
|
|
4
|
-
type MetricsAggregatorOptions,
|
|
5
|
-
} from './metrics/MetricsAggregator';
|
|
6
|
-
import { createRedactor, type Redactor } from './redact';
|
|
7
|
-
import type {
|
|
8
|
-
EndpointDetails,
|
|
9
|
-
EndpointMetrics,
|
|
10
|
-
ExceptionEntry,
|
|
11
|
-
LogEntry,
|
|
12
|
-
MetricsQueryOptions,
|
|
13
|
-
MetricsSnapshot,
|
|
14
|
-
NormalizedTelescopeOptions,
|
|
15
|
-
QueryOptions,
|
|
16
|
-
RequestEntry,
|
|
17
|
-
RequestMetrics,
|
|
18
|
-
StackFrame,
|
|
19
|
-
StatusDistribution,
|
|
20
|
-
TelescopeEvent,
|
|
21
|
-
TelescopeOptions,
|
|
22
|
-
TelescopeStorage,
|
|
23
|
-
} from './types';
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Framework-agnostic Telescope class for debugging and monitoring applications.
|
|
27
|
-
* Use framework-specific adapters (e.g., @geekmidas/telescope/hono) for integration.
|
|
28
|
-
*/
|
|
29
|
-
export class Telescope {
|
|
30
|
-
private storage: TelescopeStorage;
|
|
31
|
-
private options: NormalizedTelescopeOptions;
|
|
32
|
-
private wsClients = new Set<WebSocket>();
|
|
33
|
-
private pruneInterval?: ReturnType<typeof setInterval>;
|
|
34
|
-
private metricsBroadcastInterval?: ReturnType<typeof setInterval>;
|
|
35
|
-
private redactor?: Redactor;
|
|
36
|
-
private metricsAggregator: MetricsAggregator;
|
|
37
|
-
|
|
38
|
-
constructor(
|
|
39
|
-
options: TelescopeOptions & { metrics?: MetricsAggregatorOptions },
|
|
40
|
-
) {
|
|
41
|
-
this.storage = options.storage;
|
|
42
|
-
this.options = this.normalizeOptions(options);
|
|
43
|
-
this.redactor = createRedactor(options.redact);
|
|
44
|
-
this.metricsAggregator = new MetricsAggregator(options.metrics);
|
|
45
|
-
|
|
46
|
-
// Set up auto-pruning if configured
|
|
47
|
-
if (this.options.pruneAfterHours) {
|
|
48
|
-
const intervalMs = 60 * 60 * 1000; // 1 hour
|
|
49
|
-
this.pruneInterval = setInterval(() => {
|
|
50
|
-
this.autoPrune().catch(console.error);
|
|
51
|
-
}, intervalMs);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// ============================================
|
|
56
|
-
// Public API - Recording
|
|
57
|
-
// ============================================
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Record a request entry
|
|
61
|
-
*/
|
|
62
|
-
async recordRequest(
|
|
63
|
-
entry: Omit<RequestEntry, 'id' | 'timestamp'>,
|
|
64
|
-
): Promise<string> {
|
|
65
|
-
if (!this.options.enabled) return '';
|
|
66
|
-
|
|
67
|
-
// Use injected request ID from context if available, otherwise generate one
|
|
68
|
-
const id = this.options.getRequestId?.() ?? nanoid();
|
|
69
|
-
let fullEntry: RequestEntry = {
|
|
70
|
-
...entry,
|
|
71
|
-
id,
|
|
72
|
-
timestamp: new Date(),
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
// Apply redaction if configured
|
|
76
|
-
if (this.redactor) {
|
|
77
|
-
fullEntry = this.redactor(fullEntry);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Record metrics (before storage for real-time aggregation)
|
|
81
|
-
this.metricsAggregator.record(fullEntry);
|
|
82
|
-
|
|
83
|
-
await this.storage.saveRequest(fullEntry);
|
|
84
|
-
this.broadcast({
|
|
85
|
-
type: 'request',
|
|
86
|
-
payload: fullEntry,
|
|
87
|
-
timestamp: Date.now(),
|
|
88
|
-
});
|
|
89
|
-
return id;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Log entry input for batch operations
|
|
94
|
-
*/
|
|
95
|
-
private async saveLogEntries(entries: LogEntry[]): Promise<void> {
|
|
96
|
-
if (this.storage.saveLogs) {
|
|
97
|
-
await this.storage.saveLogs(entries);
|
|
98
|
-
} else {
|
|
99
|
-
await Promise.all(entries.map((entry) => this.storage.saveLog(entry)));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
for (const entry of entries) {
|
|
103
|
-
this.broadcast({ type: 'log', payload: entry, timestamp: Date.now() });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Record log entries in batch.
|
|
109
|
-
* More efficient than individual calls for database storage.
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* await telescope.log([
|
|
113
|
-
* { level: 'info', message: 'Request started' },
|
|
114
|
-
* { level: 'debug', message: 'Processing...', context: { step: 1 } },
|
|
115
|
-
* ]);
|
|
116
|
-
*/
|
|
117
|
-
async log(
|
|
118
|
-
entries: Array<{
|
|
119
|
-
level: 'debug' | 'info' | 'warn' | 'error';
|
|
120
|
-
message: string;
|
|
121
|
-
context?: Record<string, unknown>;
|
|
122
|
-
requestId?: string;
|
|
123
|
-
}>,
|
|
124
|
-
): Promise<void> {
|
|
125
|
-
if (!this.options.enabled || entries.length === 0) return;
|
|
126
|
-
|
|
127
|
-
const timestamp = new Date();
|
|
128
|
-
const logEntries: LogEntry[] = entries.map((e) => {
|
|
129
|
-
const entry: LogEntry = {
|
|
130
|
-
id: nanoid(),
|
|
131
|
-
level: e.level,
|
|
132
|
-
message: e.message,
|
|
133
|
-
context: e.context,
|
|
134
|
-
requestId: e.requestId,
|
|
135
|
-
timestamp,
|
|
136
|
-
};
|
|
137
|
-
return this.redactor ? this.redactor(entry) : entry;
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
await this.saveLogEntries(logEntries);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Log a debug message
|
|
145
|
-
*/
|
|
146
|
-
async debug(
|
|
147
|
-
message: string,
|
|
148
|
-
context?: Record<string, unknown>,
|
|
149
|
-
requestId?: string,
|
|
150
|
-
): Promise<void> {
|
|
151
|
-
return this.logSingle('debug', message, context, requestId);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Log an info message
|
|
156
|
-
*/
|
|
157
|
-
async info(
|
|
158
|
-
message: string,
|
|
159
|
-
context?: Record<string, unknown>,
|
|
160
|
-
requestId?: string,
|
|
161
|
-
): Promise<void> {
|
|
162
|
-
return this.logSingle('info', message, context, requestId);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Log a warning message
|
|
167
|
-
*/
|
|
168
|
-
async warn(
|
|
169
|
-
message: string,
|
|
170
|
-
context?: Record<string, unknown>,
|
|
171
|
-
requestId?: string,
|
|
172
|
-
): Promise<void> {
|
|
173
|
-
return this.logSingle('warn', message, context, requestId);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Log an error message
|
|
178
|
-
*/
|
|
179
|
-
async error(
|
|
180
|
-
message: string,
|
|
181
|
-
context?: Record<string, unknown>,
|
|
182
|
-
requestId?: string,
|
|
183
|
-
): Promise<void> {
|
|
184
|
-
return this.logSingle('error', message, context, requestId);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
private async logSingle(
|
|
188
|
-
level: LogEntry['level'],
|
|
189
|
-
message: string,
|
|
190
|
-
context?: Record<string, unknown>,
|
|
191
|
-
requestId?: string,
|
|
192
|
-
): Promise<void> {
|
|
193
|
-
if (!this.options.enabled) return;
|
|
194
|
-
|
|
195
|
-
let entry: LogEntry = {
|
|
196
|
-
id: nanoid(),
|
|
197
|
-
level,
|
|
198
|
-
message,
|
|
199
|
-
context,
|
|
200
|
-
requestId,
|
|
201
|
-
timestamp: new Date(),
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
if (this.redactor) {
|
|
205
|
-
entry = this.redactor(entry);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
await this.storage.saveLog(entry);
|
|
209
|
-
this.broadcast({ type: 'log', payload: entry, timestamp: Date.now() });
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Record an exception
|
|
214
|
-
*/
|
|
215
|
-
async exception(error: Error, requestId?: string): Promise<void> {
|
|
216
|
-
if (!this.options.enabled) return;
|
|
217
|
-
|
|
218
|
-
const stack = this.parseStack(error.stack || '');
|
|
219
|
-
|
|
220
|
-
const entry: ExceptionEntry = {
|
|
221
|
-
id: nanoid(),
|
|
222
|
-
name: error.name,
|
|
223
|
-
message: error.message,
|
|
224
|
-
stack,
|
|
225
|
-
requestId,
|
|
226
|
-
timestamp: new Date(),
|
|
227
|
-
handled: false,
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
await this.storage.saveException(entry);
|
|
231
|
-
this.broadcast({
|
|
232
|
-
type: 'exception',
|
|
233
|
-
payload: entry,
|
|
234
|
-
timestamp: Date.now(),
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// ============================================
|
|
239
|
-
// Public API - Data Access
|
|
240
|
-
// ============================================
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Get requests from storage
|
|
244
|
-
*/
|
|
245
|
-
async getRequests(options?: QueryOptions): Promise<RequestEntry[]> {
|
|
246
|
-
return this.storage.getRequests(options);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Get a single request by ID
|
|
251
|
-
*/
|
|
252
|
-
async getRequest(id: string): Promise<RequestEntry | null> {
|
|
253
|
-
return this.storage.getRequest(id);
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Get exceptions from storage
|
|
258
|
-
*/
|
|
259
|
-
async getExceptions(options?: QueryOptions): Promise<ExceptionEntry[]> {
|
|
260
|
-
return this.storage.getExceptions(options);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Get a single exception by ID
|
|
265
|
-
*/
|
|
266
|
-
async getException(id: string): Promise<ExceptionEntry | null> {
|
|
267
|
-
return this.storage.getException(id);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Get logs from storage
|
|
272
|
-
*/
|
|
273
|
-
async getLogs(options?: QueryOptions): Promise<LogEntry[]> {
|
|
274
|
-
return this.storage.getLogs(options);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Get storage statistics
|
|
279
|
-
*/
|
|
280
|
-
async getStats() {
|
|
281
|
-
return this.storage.getStats();
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// ============================================
|
|
285
|
-
// Public API - Metrics
|
|
286
|
-
// ============================================
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Get aggregated request metrics
|
|
290
|
-
*/
|
|
291
|
-
getMetrics(options?: MetricsQueryOptions): RequestMetrics {
|
|
292
|
-
return this.metricsAggregator.getMetrics(options);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Get metrics for individual endpoints
|
|
297
|
-
*/
|
|
298
|
-
getEndpointMetrics(options?: MetricsQueryOptions): EndpointMetrics[] {
|
|
299
|
-
return this.metricsAggregator.getEndpointMetrics(options);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Get detailed metrics for a specific endpoint
|
|
304
|
-
*/
|
|
305
|
-
getEndpointDetails(
|
|
306
|
-
method: string,
|
|
307
|
-
path: string,
|
|
308
|
-
options?: MetricsQueryOptions,
|
|
309
|
-
): EndpointDetails | null {
|
|
310
|
-
return this.metricsAggregator.getEndpointDetails(method, path, options);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Get status code distribution
|
|
315
|
-
*/
|
|
316
|
-
getStatusDistribution(options?: MetricsQueryOptions): StatusDistribution {
|
|
317
|
-
return this.metricsAggregator.getStatusDistribution(options);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* Reset metrics data
|
|
322
|
-
*/
|
|
323
|
-
resetMetrics(): void {
|
|
324
|
-
this.metricsAggregator.reset();
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* Get a real-time metrics snapshot for broadcasting
|
|
329
|
-
*/
|
|
330
|
-
getMetricsSnapshot(): MetricsSnapshot {
|
|
331
|
-
const metrics = this.metricsAggregator.getMetrics();
|
|
332
|
-
const statusDist = this.metricsAggregator.getStatusDistribution();
|
|
333
|
-
|
|
334
|
-
return {
|
|
335
|
-
timestamp: Date.now(),
|
|
336
|
-
totalRequests: metrics.totalRequests,
|
|
337
|
-
requestsPerSecond: metrics.requestsPerSecond,
|
|
338
|
-
avgDuration: metrics.avgDuration,
|
|
339
|
-
errorRate: metrics.errorRate,
|
|
340
|
-
p50: metrics.p50Duration,
|
|
341
|
-
p95: metrics.p95Duration,
|
|
342
|
-
p99: metrics.p99Duration,
|
|
343
|
-
statusDistribution: statusDist,
|
|
344
|
-
};
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Start broadcasting metrics to WebSocket clients at a given interval.
|
|
349
|
-
* Call this after setting up WebSocket to enable real-time metrics updates.
|
|
350
|
-
*
|
|
351
|
-
* @param intervalMs - Broadcast interval in milliseconds (default: 5000)
|
|
352
|
-
*/
|
|
353
|
-
startMetricsBroadcast(intervalMs = 5000): void {
|
|
354
|
-
if (this.metricsBroadcastInterval) {
|
|
355
|
-
return; // Already running
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
this.metricsBroadcastInterval = setInterval(() => {
|
|
359
|
-
if (this.wsClients.size > 0) {
|
|
360
|
-
const snapshot = this.getMetricsSnapshot();
|
|
361
|
-
this.broadcast({
|
|
362
|
-
type: 'metrics',
|
|
363
|
-
payload: snapshot,
|
|
364
|
-
timestamp: Date.now(),
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
}, intervalMs);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/**
|
|
371
|
-
* Stop broadcasting metrics to WebSocket clients
|
|
372
|
-
*/
|
|
373
|
-
stopMetricsBroadcast(): void {
|
|
374
|
-
if (this.metricsBroadcastInterval) {
|
|
375
|
-
clearInterval(this.metricsBroadcastInterval);
|
|
376
|
-
this.metricsBroadcastInterval = undefined;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// ============================================
|
|
381
|
-
// Public API - WebSocket
|
|
382
|
-
// ============================================
|
|
383
|
-
|
|
384
|
-
/**
|
|
385
|
-
* Add a WebSocket client for real-time updates
|
|
386
|
-
*/
|
|
387
|
-
addWsClient(ws: WebSocket): void {
|
|
388
|
-
this.wsClients.add(ws);
|
|
389
|
-
this.broadcast({
|
|
390
|
-
type: 'connected',
|
|
391
|
-
payload: { clientCount: this.wsClients.size },
|
|
392
|
-
timestamp: Date.now(),
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Remove a WebSocket client
|
|
398
|
-
*/
|
|
399
|
-
removeWsClient(ws: WebSocket): void {
|
|
400
|
-
this.wsClients.delete(ws);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Broadcast an event to all connected WebSocket clients
|
|
405
|
-
*/
|
|
406
|
-
broadcast(event: TelescopeEvent): void {
|
|
407
|
-
const data = JSON.stringify(event);
|
|
408
|
-
for (const client of this.wsClients) {
|
|
409
|
-
try {
|
|
410
|
-
client.send(data);
|
|
411
|
-
} catch {
|
|
412
|
-
this.wsClients.delete(client);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// ============================================
|
|
418
|
-
// Public API - Lifecycle
|
|
419
|
-
// ============================================
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Manually prune old entries
|
|
423
|
-
*/
|
|
424
|
-
async prune(olderThan: Date): Promise<number> {
|
|
425
|
-
return this.storage.prune(olderThan);
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Clean up resources
|
|
430
|
-
*/
|
|
431
|
-
destroy(): void {
|
|
432
|
-
if (this.pruneInterval) {
|
|
433
|
-
clearInterval(this.pruneInterval);
|
|
434
|
-
}
|
|
435
|
-
this.stopMetricsBroadcast();
|
|
436
|
-
this.wsClients.clear();
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// ============================================
|
|
440
|
-
// Public API - Configuration
|
|
441
|
-
// ============================================
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Get the telescope path
|
|
445
|
-
*/
|
|
446
|
-
get path(): string {
|
|
447
|
-
return this.options.path;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Check if telescope is enabled
|
|
452
|
-
*/
|
|
453
|
-
get enabled(): boolean {
|
|
454
|
-
return this.options.enabled;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
/**
|
|
458
|
-
* Check if body recording is enabled
|
|
459
|
-
*/
|
|
460
|
-
get recordBody(): boolean {
|
|
461
|
-
return this.options.recordBody;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/**
|
|
465
|
-
* Get max body size
|
|
466
|
-
*/
|
|
467
|
-
get maxBodySize(): number {
|
|
468
|
-
return this.options.maxBodySize;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Check if a path should be ignored
|
|
473
|
-
*/
|
|
474
|
-
shouldIgnore(path: string): boolean {
|
|
475
|
-
// Always ignore telescope's own routes
|
|
476
|
-
if (path.startsWith(this.options.path)) {
|
|
477
|
-
return true;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
return this.options.ignorePatterns.some((pattern) => {
|
|
481
|
-
if (pattern.includes('*')) {
|
|
482
|
-
const regex = new RegExp(
|
|
483
|
-
`^${pattern.replace(/\*/g, '.*').replace(/\?/g, '.')}$`,
|
|
484
|
-
);
|
|
485
|
-
return regex.test(path);
|
|
486
|
-
}
|
|
487
|
-
return path.startsWith(pattern);
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// ============================================
|
|
492
|
-
// Private Methods
|
|
493
|
-
// ============================================
|
|
494
|
-
|
|
495
|
-
private normalizeOptions(
|
|
496
|
-
options: TelescopeOptions,
|
|
497
|
-
): NormalizedTelescopeOptions {
|
|
498
|
-
return {
|
|
499
|
-
storage: options.storage,
|
|
500
|
-
enabled: options.enabled ?? true,
|
|
501
|
-
path: options.path ?? '/__telescope',
|
|
502
|
-
recordBody: options.recordBody ?? true,
|
|
503
|
-
maxBodySize: options.maxBodySize ?? 64 * 1024, // 64KB
|
|
504
|
-
ignorePatterns: options.ignorePatterns ?? [],
|
|
505
|
-
pruneAfterHours: options.pruneAfterHours,
|
|
506
|
-
getRequestId: options.getRequestId,
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
private parseStack(stack: string): StackFrame[] {
|
|
511
|
-
const lines = stack.split('\n').slice(1);
|
|
512
|
-
const frames: StackFrame[] = [];
|
|
513
|
-
|
|
514
|
-
for (const line of lines) {
|
|
515
|
-
// Match: " at functionName (file:line:column)"
|
|
516
|
-
// or: " at file:line:column"
|
|
517
|
-
const match =
|
|
518
|
-
line.match(/at\s+(.+?)\s+\((.+):(\d+):(\d+)\)/) ||
|
|
519
|
-
line.match(/at\s+(.+):(\d+):(\d+)/);
|
|
520
|
-
|
|
521
|
-
if (match) {
|
|
522
|
-
if (
|
|
523
|
-
match.length === 5 &&
|
|
524
|
-
match[1] &&
|
|
525
|
-
match[2] &&
|
|
526
|
-
match[3] &&
|
|
527
|
-
match[4]
|
|
528
|
-
) {
|
|
529
|
-
// Has function name
|
|
530
|
-
frames.push({
|
|
531
|
-
function: match[1],
|
|
532
|
-
file: match[2],
|
|
533
|
-
line: parseInt(match[3], 10),
|
|
534
|
-
column: parseInt(match[4], 10),
|
|
535
|
-
isApp: !match[2].includes('node_modules'),
|
|
536
|
-
});
|
|
537
|
-
} else if (match.length === 4 && match[1] && match[2] && match[3]) {
|
|
538
|
-
// No function name
|
|
539
|
-
frames.push({
|
|
540
|
-
function: '<anonymous>',
|
|
541
|
-
file: match[1],
|
|
542
|
-
line: parseInt(match[2], 10),
|
|
543
|
-
column: parseInt(match[3], 10),
|
|
544
|
-
isApp: !match[1].includes('node_modules'),
|
|
545
|
-
});
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
return frames;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
private async autoPrune(): Promise<void> {
|
|
554
|
-
if (!this.options.pruneAfterHours) return;
|
|
555
|
-
|
|
556
|
-
const olderThan = new Date(
|
|
557
|
-
Date.now() - this.options.pruneAfterHours * 60 * 60 * 1000,
|
|
558
|
-
);
|
|
559
|
-
await this.storage.prune(olderThan);
|
|
560
|
-
}
|
|
561
|
-
}
|