@sschepis/magazine 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 +167 -0
- package/dist/magazine.cjs.js +5 -0
- package/dist/magazine.cjs.js.map +1 -0
- package/dist/magazine.es.js +4149 -0
- package/dist/magazine.es.js.map +1 -0
- package/dist/magazine.umd.js +5 -0
- package/dist/magazine.umd.js.map +1 -0
- package/package.json +74 -0
- package/src/components/ConfigManager.js +202 -0
- package/src/components/ConfigSchema.js +506 -0
- package/src/components/DataCompressor.js +378 -0
- package/src/components/DataTransformer.js +1172 -0
- package/src/components/DebugUtils.js +657 -0
- package/src/components/EventFilter.js +474 -0
- package/src/components/EventMetadata.js +273 -0
- package/src/components/EventStore.js +443 -0
- package/src/components/EventSyncer.js +353 -0
- package/src/components/Logger.js +299 -0
- package/src/components/NetworkManager.js +461 -0
- package/src/components/Paginator.js +343 -0
- package/src/index.js +571 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DebugUtils - Debugging utilities and health check endpoints
|
|
3
|
+
*/
|
|
4
|
+
export default class DebugUtils {
|
|
5
|
+
constructor(magazine, logger) {
|
|
6
|
+
this.magazine = magazine;
|
|
7
|
+
this.logger = logger.child({ context: 'DebugUtils' });
|
|
8
|
+
this.logger.info('DebugUtils initialized');
|
|
9
|
+
|
|
10
|
+
// Debug mode state
|
|
11
|
+
this.debugMode = false;
|
|
12
|
+
|
|
13
|
+
// Performance metrics
|
|
14
|
+
this.metrics = {
|
|
15
|
+
syncOperations: [],
|
|
16
|
+
queryOperations: [],
|
|
17
|
+
storageOperations: [],
|
|
18
|
+
networkCalls: []
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Log buffer (circular, max 200 entries)
|
|
22
|
+
this._logBuffer = [];
|
|
23
|
+
this._logBufferMax = 200;
|
|
24
|
+
|
|
25
|
+
// Map to store original methods before instrumentation
|
|
26
|
+
this._originalMethods = new Map();
|
|
27
|
+
|
|
28
|
+
// Verbose error mode flag
|
|
29
|
+
this._verboseErrors = false;
|
|
30
|
+
|
|
31
|
+
// Health check thresholds
|
|
32
|
+
this.healthThresholds = {
|
|
33
|
+
syncDelay: 300000, // 5 minutes
|
|
34
|
+
errorRate: 0.1, // 10%
|
|
35
|
+
responseTime: 5000, // 5 seconds
|
|
36
|
+
storageUsage: 0.9 // 90%
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Perform comprehensive health check
|
|
42
|
+
* @returns {Promise<Object>} Health check results
|
|
43
|
+
*/
|
|
44
|
+
async healthCheck() {
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
const results = {
|
|
47
|
+
status: 'healthy',
|
|
48
|
+
timestamp: new Date().toISOString(),
|
|
49
|
+
checks: {},
|
|
50
|
+
issues: []
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Check network connectivity
|
|
55
|
+
results.checks.network = await this._checkNetwork();
|
|
56
|
+
|
|
57
|
+
// Check Gun.js database
|
|
58
|
+
results.checks.database = await this._checkDatabase();
|
|
59
|
+
|
|
60
|
+
// Check sync status
|
|
61
|
+
results.checks.sync = await this._checkSyncStatus();
|
|
62
|
+
|
|
63
|
+
// Check storage
|
|
64
|
+
results.checks.storage = await this._checkStorage();
|
|
65
|
+
|
|
66
|
+
// Check performance metrics
|
|
67
|
+
results.checks.performance = this._checkPerformance();
|
|
68
|
+
|
|
69
|
+
// Determine overall status
|
|
70
|
+
const issues = [];
|
|
71
|
+
Object.entries(results.checks).forEach(([check, result]) => {
|
|
72
|
+
if (result.status !== 'healthy') {
|
|
73
|
+
issues.push({
|
|
74
|
+
check,
|
|
75
|
+
status: result.status,
|
|
76
|
+
message: result.message
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (issues.length > 0) {
|
|
82
|
+
results.status = issues.some(i => i.status === 'critical') ? 'critical' : 'degraded';
|
|
83
|
+
results.issues = issues;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
results.duration = Date.now() - startTime;
|
|
87
|
+
|
|
88
|
+
this.logger.info('Health check completed', {
|
|
89
|
+
status: results.status,
|
|
90
|
+
duration: results.duration,
|
|
91
|
+
issueCount: issues.length
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return results;
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
this.logger.error('Health check failed', { error: error.message });
|
|
98
|
+
return {
|
|
99
|
+
status: 'error',
|
|
100
|
+
timestamp: new Date().toISOString(),
|
|
101
|
+
error: error.message,
|
|
102
|
+
duration: Date.now() - startTime
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get debug information about the current state
|
|
109
|
+
* @returns {Promise<Object>} Debug information
|
|
110
|
+
*/
|
|
111
|
+
async getDebugInfo() {
|
|
112
|
+
const debugInfo = {
|
|
113
|
+
timestamp: new Date().toISOString(),
|
|
114
|
+
configuration: this._getConfigInfo(),
|
|
115
|
+
network: await this._getNetworkInfo(),
|
|
116
|
+
storage: await this._getStorageInfo(),
|
|
117
|
+
performance: this._getPerformanceInfo(),
|
|
118
|
+
logs: this._getRecentLogs()
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
this.logger.debug('Debug info generated');
|
|
122
|
+
return debugInfo;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Record a metric
|
|
127
|
+
* @param {string} type - Metric type
|
|
128
|
+
* @param {Object} data - Metric data
|
|
129
|
+
*/
|
|
130
|
+
recordMetric(type, data) {
|
|
131
|
+
const metric = {
|
|
132
|
+
timestamp: Date.now(),
|
|
133
|
+
...data
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
if (this.metrics[type]) {
|
|
137
|
+
this.metrics[type].push(metric);
|
|
138
|
+
|
|
139
|
+
// Keep only last 1000 metrics per type
|
|
140
|
+
if (this.metrics[type].length > 1000) {
|
|
141
|
+
this.metrics[type] = this.metrics[type].slice(-1000);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get performance report
|
|
148
|
+
* @param {Object} options - Report options
|
|
149
|
+
* @returns {Object} Performance report
|
|
150
|
+
*/
|
|
151
|
+
getPerformanceReport(options = {}) {
|
|
152
|
+
const {
|
|
153
|
+
timeRange = 3600000, // Last hour
|
|
154
|
+
aggregation = 'average'
|
|
155
|
+
} = options;
|
|
156
|
+
|
|
157
|
+
const now = Date.now();
|
|
158
|
+
const startTime = now - timeRange;
|
|
159
|
+
|
|
160
|
+
const report = {};
|
|
161
|
+
|
|
162
|
+
Object.entries(this.metrics).forEach(([type, metrics]) => {
|
|
163
|
+
const recentMetrics = metrics.filter(m => m.timestamp >= startTime);
|
|
164
|
+
|
|
165
|
+
if (recentMetrics.length === 0) {
|
|
166
|
+
report[type] = { count: 0, average: 0, min: 0, max: 0 };
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const durations = recentMetrics.map(m => m.duration || 0);
|
|
171
|
+
const errors = recentMetrics.filter(m => m.error).length;
|
|
172
|
+
|
|
173
|
+
report[type] = {
|
|
174
|
+
count: recentMetrics.length,
|
|
175
|
+
errors,
|
|
176
|
+
errorRate: (errors / recentMetrics.length * 100).toFixed(2) + '%',
|
|
177
|
+
average: Math.round(durations.reduce((a, b) => a + b, 0) / durations.length),
|
|
178
|
+
min: Math.min(...durations),
|
|
179
|
+
max: Math.max(...durations),
|
|
180
|
+
p95: this._calculatePercentile(durations, 95),
|
|
181
|
+
p99: this._calculatePercentile(durations, 99)
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
timeRange: {
|
|
187
|
+
start: new Date(startTime).toISOString(),
|
|
188
|
+
end: new Date(now).toISOString(),
|
|
189
|
+
duration: timeRange
|
|
190
|
+
},
|
|
191
|
+
metrics: report
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Enable debug mode
|
|
197
|
+
* @param {Object} options - Debug options
|
|
198
|
+
*/
|
|
199
|
+
enableDebugMode(options = {}) {
|
|
200
|
+
const {
|
|
201
|
+
logLevel = 'debug',
|
|
202
|
+
captureMetrics = true,
|
|
203
|
+
verboseErrors = true
|
|
204
|
+
} = options;
|
|
205
|
+
|
|
206
|
+
// Set log level
|
|
207
|
+
this.magazine.setLogLevel(logLevel);
|
|
208
|
+
|
|
209
|
+
// Enable metric capture
|
|
210
|
+
if (captureMetrics) {
|
|
211
|
+
this._instrumentMethods();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Enable verbose errors
|
|
215
|
+
if (verboseErrors) {
|
|
216
|
+
this._enableVerboseErrors();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
this.logger.info('Debug mode enabled', options);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Export debug data
|
|
224
|
+
* @returns {Promise<Object>} Exportable debug data
|
|
225
|
+
*/
|
|
226
|
+
async exportDebugData() {
|
|
227
|
+
const data = {
|
|
228
|
+
version: '1.0.0',
|
|
229
|
+
exportedAt: new Date().toISOString(),
|
|
230
|
+
debugInfo: await this.getDebugInfo(),
|
|
231
|
+
healthCheck: await this.healthCheck(),
|
|
232
|
+
performanceReport: this.getPerformanceReport(),
|
|
233
|
+
recentMetrics: {
|
|
234
|
+
sync: this.metrics.syncOperations.slice(-100),
|
|
235
|
+
query: this.metrics.queryOperations.slice(-100),
|
|
236
|
+
storage: this.metrics.storageOperations.slice(-100),
|
|
237
|
+
network: this.metrics.networkCalls.slice(-100)
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
this.logger.info('Debug data exported');
|
|
242
|
+
return data;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check network connectivity
|
|
247
|
+
* @private
|
|
248
|
+
*/
|
|
249
|
+
async _checkNetwork() {
|
|
250
|
+
try {
|
|
251
|
+
const startTime = Date.now();
|
|
252
|
+
const blockNumber = await this.magazine.getLatestBlockNumber();
|
|
253
|
+
const duration = Date.now() - startTime;
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
status: duration < this.healthThresholds.responseTime ? 'healthy' : 'degraded',
|
|
257
|
+
latestBlock: blockNumber,
|
|
258
|
+
responseTime: duration,
|
|
259
|
+
message: `Network response time: ${duration}ms`
|
|
260
|
+
};
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return {
|
|
263
|
+
status: 'critical',
|
|
264
|
+
error: error.message,
|
|
265
|
+
message: 'Network connection failed'
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Check database connectivity
|
|
272
|
+
* @private
|
|
273
|
+
*/
|
|
274
|
+
async _checkDatabase() {
|
|
275
|
+
try {
|
|
276
|
+
const startTime = Date.now();
|
|
277
|
+
const gun = this.magazine.eventStore.getGunInstance();
|
|
278
|
+
|
|
279
|
+
// Try to read a test value
|
|
280
|
+
await new Promise((resolve, reject) => {
|
|
281
|
+
gun.get('_health_check').put({
|
|
282
|
+
timestamp: Date.now()
|
|
283
|
+
}, (ack) => {
|
|
284
|
+
if (ack.err) reject(new Error(ack.err));
|
|
285
|
+
else resolve(ack);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const duration = Date.now() - startTime;
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
status: duration < 1000 ? 'healthy' : 'degraded',
|
|
293
|
+
responseTime: duration,
|
|
294
|
+
message: `Database response time: ${duration}ms`
|
|
295
|
+
};
|
|
296
|
+
} catch (error) {
|
|
297
|
+
return {
|
|
298
|
+
status: 'critical',
|
|
299
|
+
error: error.message,
|
|
300
|
+
message: 'Database connection failed'
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Check sync status
|
|
307
|
+
* @private
|
|
308
|
+
*/
|
|
309
|
+
async _checkSyncStatus() {
|
|
310
|
+
try {
|
|
311
|
+
const lastSync = this.magazine.eventSyncer.lastSyncTime || 0;
|
|
312
|
+
const timeSinceSync = Date.now() - lastSync;
|
|
313
|
+
|
|
314
|
+
if (lastSync === 0) {
|
|
315
|
+
return {
|
|
316
|
+
status: 'warning',
|
|
317
|
+
message: 'No sync performed yet'
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
status: timeSinceSync < this.healthThresholds.syncDelay ? 'healthy' : 'warning',
|
|
323
|
+
lastSync: new Date(lastSync).toISOString(),
|
|
324
|
+
timeSinceSync,
|
|
325
|
+
message: `Last sync: ${Math.round(timeSinceSync / 1000)}s ago`
|
|
326
|
+
};
|
|
327
|
+
} catch (error) {
|
|
328
|
+
return {
|
|
329
|
+
status: 'error',
|
|
330
|
+
error: error.message,
|
|
331
|
+
message: 'Could not check sync status'
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Check storage status
|
|
338
|
+
* @private
|
|
339
|
+
*/
|
|
340
|
+
async _checkStorage() {
|
|
341
|
+
try {
|
|
342
|
+
const stats = await this.magazine.getCompressionStats();
|
|
343
|
+
const usage = stats.totalCompressedSize / (100 * 1024 * 1024); // Assume 100MB limit
|
|
344
|
+
|
|
345
|
+
return {
|
|
346
|
+
status: usage < this.healthThresholds.storageUsage ? 'healthy' : 'warning',
|
|
347
|
+
usage: `${(usage * 100).toFixed(2)}%`,
|
|
348
|
+
compressed: stats.totalCompressedSize,
|
|
349
|
+
saved: stats.spaceSaved,
|
|
350
|
+
message: `Storage usage: ${(usage * 100).toFixed(2)}%`
|
|
351
|
+
};
|
|
352
|
+
} catch (error) {
|
|
353
|
+
return {
|
|
354
|
+
status: 'error',
|
|
355
|
+
error: error.message,
|
|
356
|
+
message: 'Could not check storage status'
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Check performance metrics
|
|
363
|
+
* @private
|
|
364
|
+
*/
|
|
365
|
+
_checkPerformance() {
|
|
366
|
+
const report = this.getPerformanceReport({ timeRange: 300000 }); // Last 5 minutes
|
|
367
|
+
let status = 'healthy';
|
|
368
|
+
const issues = [];
|
|
369
|
+
|
|
370
|
+
Object.entries(report.metrics).forEach(([type, metrics]) => {
|
|
371
|
+
if (metrics.errorRate && parseFloat(metrics.errorRate) > this.healthThresholds.errorRate * 100) {
|
|
372
|
+
status = 'degraded';
|
|
373
|
+
issues.push(`High error rate in ${type}: ${metrics.errorRate}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (metrics.average > this.healthThresholds.responseTime) {
|
|
377
|
+
status = 'degraded';
|
|
378
|
+
issues.push(`Slow ${type} operations: ${metrics.average}ms average`);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
return {
|
|
383
|
+
status,
|
|
384
|
+
metrics: report.metrics,
|
|
385
|
+
message: issues.length > 0 ? issues.join(', ') : 'Performance metrics within normal range'
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get configuration info
|
|
391
|
+
* @private
|
|
392
|
+
*/
|
|
393
|
+
_getConfigInfo() {
|
|
394
|
+
const config = this.magazine.configManager.getAll();
|
|
395
|
+
return {
|
|
396
|
+
name: config.name,
|
|
397
|
+
address: config.address,
|
|
398
|
+
networkId: config.networkId,
|
|
399
|
+
logLevel: this.magazine.logger.level,
|
|
400
|
+
compression: {
|
|
401
|
+
enabled: this.magazine.eventStore.compressionEnabled,
|
|
402
|
+
strategy: this.magazine.eventStore.compressionStrategy
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Get network info
|
|
409
|
+
* @private
|
|
410
|
+
*/
|
|
411
|
+
async _getNetworkInfo() {
|
|
412
|
+
try {
|
|
413
|
+
const info = await this.magazine.getNetworkInfo();
|
|
414
|
+
return {
|
|
415
|
+
...info,
|
|
416
|
+
connected: true
|
|
417
|
+
};
|
|
418
|
+
} catch (error) {
|
|
419
|
+
return {
|
|
420
|
+
connected: false,
|
|
421
|
+
error: error.message
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Get storage info
|
|
428
|
+
* @private
|
|
429
|
+
*/
|
|
430
|
+
async _getStorageInfo() {
|
|
431
|
+
try {
|
|
432
|
+
const compressionStats = await this.magazine.getCompressionStats();
|
|
433
|
+
const eventCount = (await this.magazine.getAllEvents({ pagination: { page: 1, pageSize: 1 } })).meta.totalItems;
|
|
434
|
+
|
|
435
|
+
return {
|
|
436
|
+
eventCount,
|
|
437
|
+
...compressionStats
|
|
438
|
+
};
|
|
439
|
+
} catch (error) {
|
|
440
|
+
return {
|
|
441
|
+
error: error.message
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Get performance info
|
|
448
|
+
* @private
|
|
449
|
+
*/
|
|
450
|
+
_getPerformanceInfo() {
|
|
451
|
+
return this.getPerformanceReport({ timeRange: 3600000 }); // Last hour
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Capture a log entry into the circular buffer
|
|
456
|
+
* @param {Object} entry - Log entry to capture
|
|
457
|
+
*/
|
|
458
|
+
captureLog(entry) {
|
|
459
|
+
this._logBuffer.push(entry);
|
|
460
|
+
if (this._logBuffer.length > this._logBufferMax) {
|
|
461
|
+
this._logBuffer.shift();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Get recent logs from the circular buffer
|
|
467
|
+
* @param {number} [count=50] - Number of recent log entries to return
|
|
468
|
+
* @returns {Array} Recent log entries
|
|
469
|
+
* @private
|
|
470
|
+
*/
|
|
471
|
+
_getRecentLogs(count = 50) {
|
|
472
|
+
const n = Math.min(count, this._logBuffer.length);
|
|
473
|
+
return this._logBuffer.slice(-n);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Calculate percentile
|
|
478
|
+
* @private
|
|
479
|
+
*/
|
|
480
|
+
_calculatePercentile(values, percentile) {
|
|
481
|
+
if (values.length === 0) return 0;
|
|
482
|
+
|
|
483
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
484
|
+
const index = Math.ceil((percentile / 100) * sorted.length) - 1;
|
|
485
|
+
return sorted[index];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Instrument methods for metric collection
|
|
490
|
+
* @private
|
|
491
|
+
*/
|
|
492
|
+
_instrumentMethods() {
|
|
493
|
+
const mag = this.magazine;
|
|
494
|
+
const methodsToInstrument = [
|
|
495
|
+
'syncEvents',
|
|
496
|
+
'getAllEvents',
|
|
497
|
+
'getEvents',
|
|
498
|
+
'query',
|
|
499
|
+
'transformEvent'
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
for (const methodName of methodsToInstrument) {
|
|
503
|
+
if (typeof mag[methodName] !== 'function') {
|
|
504
|
+
this.logger.debug(`Skipping instrumentation for missing method: ${methodName}`);
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// Skip if already instrumented
|
|
509
|
+
if (this._originalMethods.has(methodName)) {
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const original = mag[methodName].bind(mag);
|
|
514
|
+
this._originalMethods.set(methodName, mag[methodName]);
|
|
515
|
+
|
|
516
|
+
const self = this;
|
|
517
|
+
mag[methodName] = function (...args) {
|
|
518
|
+
const startTime = Date.now();
|
|
519
|
+
let metricType;
|
|
520
|
+
if (methodName === 'syncEvents') {
|
|
521
|
+
metricType = 'syncOperations';
|
|
522
|
+
} else if (methodName === 'getAllEvents' || methodName === 'getEvents' || methodName === 'query') {
|
|
523
|
+
metricType = 'queryOperations';
|
|
524
|
+
} else {
|
|
525
|
+
metricType = 'storageOperations';
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
const result = original(...args);
|
|
530
|
+
|
|
531
|
+
// Handle both sync and async results
|
|
532
|
+
if (result && typeof result.then === 'function') {
|
|
533
|
+
return result.then((value) => {
|
|
534
|
+
const duration = Date.now() - startTime;
|
|
535
|
+
self.recordMetric(metricType, {
|
|
536
|
+
method: methodName,
|
|
537
|
+
duration,
|
|
538
|
+
success: true
|
|
539
|
+
});
|
|
540
|
+
self.logger.debug(`${methodName} completed in ${duration}ms`);
|
|
541
|
+
return value;
|
|
542
|
+
}).catch((error) => {
|
|
543
|
+
const duration = Date.now() - startTime;
|
|
544
|
+
self.recordMetric(metricType, {
|
|
545
|
+
method: methodName,
|
|
546
|
+
duration,
|
|
547
|
+
success: false,
|
|
548
|
+
error: error.message
|
|
549
|
+
});
|
|
550
|
+
self.logger.debug(`${methodName} failed after ${duration}ms: ${error.message}`);
|
|
551
|
+
throw error;
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const duration = Date.now() - startTime;
|
|
556
|
+
self.recordMetric(metricType, {
|
|
557
|
+
method: methodName,
|
|
558
|
+
duration,
|
|
559
|
+
success: true
|
|
560
|
+
});
|
|
561
|
+
self.logger.debug(`${methodName} completed in ${duration}ms`);
|
|
562
|
+
return result;
|
|
563
|
+
} catch (error) {
|
|
564
|
+
const duration = Date.now() - startTime;
|
|
565
|
+
self.recordMetric(metricType, {
|
|
566
|
+
method: methodName,
|
|
567
|
+
duration,
|
|
568
|
+
success: false,
|
|
569
|
+
error: error.message
|
|
570
|
+
});
|
|
571
|
+
self.logger.debug(`${methodName} failed after ${duration}ms: ${error.message}`);
|
|
572
|
+
throw error;
|
|
573
|
+
}
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
this.logger.debug('Method instrumentation enabled');
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Enable verbose error logging
|
|
582
|
+
* @private
|
|
583
|
+
*/
|
|
584
|
+
_enableVerboseErrors() {
|
|
585
|
+
this._verboseErrors = true;
|
|
586
|
+
|
|
587
|
+
const mag = this.magazine;
|
|
588
|
+
if (typeof mag.retryOperation === 'function' && !this._originalRetryOperation) {
|
|
589
|
+
const originalRetry = mag.retryOperation.bind(mag);
|
|
590
|
+
this._originalRetryOperation = mag.retryOperation;
|
|
591
|
+
const self = this;
|
|
592
|
+
|
|
593
|
+
mag.retryOperation = async function (operation, context, ...rest) {
|
|
594
|
+
try {
|
|
595
|
+
return await originalRetry(operation, context, ...rest);
|
|
596
|
+
} catch (error) {
|
|
597
|
+
if (self._verboseErrors) {
|
|
598
|
+
self.logger.error('Verbose error context for retryOperation failure', {
|
|
599
|
+
operationContext: context,
|
|
600
|
+
errorMessage: error.message,
|
|
601
|
+
errorStack: error.stack,
|
|
602
|
+
errorName: error.name,
|
|
603
|
+
timestamp: new Date().toISOString()
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
throw error;
|
|
607
|
+
}
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
this.logger.debug('Verbose error logging enabled');
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* Set debug mode on/off
|
|
616
|
+
* @param {boolean} enabled - Whether to enable debug mode
|
|
617
|
+
*/
|
|
618
|
+
setDebugMode(enabled) {
|
|
619
|
+
this.debugMode = !!enabled;
|
|
620
|
+
this.logger.info(`Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
621
|
+
|
|
622
|
+
if (enabled) {
|
|
623
|
+
this._instrumentMethods();
|
|
624
|
+
this._enableVerboseErrors();
|
|
625
|
+
|
|
626
|
+
// Monkey-patch the logger's _log method to capture entries
|
|
627
|
+
if (this.logger._log && !this._originalLoggerLog) {
|
|
628
|
+
this._originalLoggerLog = this.logger._log.bind(this.logger);
|
|
629
|
+
const self = this;
|
|
630
|
+
this.logger._log = function (level, message, data) {
|
|
631
|
+
const result = self._originalLoggerLog(level, message, data);
|
|
632
|
+
self.captureLog({
|
|
633
|
+
timestamp: Date.now(),
|
|
634
|
+
level,
|
|
635
|
+
message,
|
|
636
|
+
data
|
|
637
|
+
});
|
|
638
|
+
return result;
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
} else {
|
|
642
|
+
// Restore logger _log if it was patched
|
|
643
|
+
if (this._originalLoggerLog) {
|
|
644
|
+
this.logger._log = this._originalLoggerLog;
|
|
645
|
+
this._originalLoggerLog = null;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Check if debug mode is enabled
|
|
652
|
+
* @returns {boolean} Debug mode status
|
|
653
|
+
*/
|
|
654
|
+
isDebugMode() {
|
|
655
|
+
return !!this.debugMode;
|
|
656
|
+
}
|
|
657
|
+
}
|