@peers-app/peers-sdk 0.7.21 → 0.7.22
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/dist/logging/console-logger.d.ts +7 -11
- package/dist/logging/console-logger.js +85 -194
- package/dist/logging/console-logger.test.js +46 -46
- package/dist/logging/console-logs.table.d.ts +3 -3
- package/dist/logging/console-logs.table.js +1 -1
- package/dist/rpc-types.js +2 -1
- package/dist/utils.js +4 -0
- package/package.json +1 -1
- package/dist/logging/logger-example.d.ts +0 -41
- package/dist/logging/logger-example.js +0 -74
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
type ConsoleLevel = 'debug' | 'info' | 'log' | 'warn' | 'error';
|
|
1
2
|
/**
|
|
2
3
|
* Setup console proxy to capture all console output and write to ConsoleLogs table.
|
|
3
4
|
* Also subscribes to dataChanged events to output logs from other process instances.
|
|
4
5
|
* @param processName - The name of the process (e.g., 'main', 'renderer')
|
|
5
6
|
*/
|
|
6
|
-
export declare function
|
|
7
|
+
export declare function setupConsoleLogsProxy(processName: string): void;
|
|
7
8
|
/**
|
|
8
9
|
* Restore original console methods
|
|
9
10
|
*/
|
|
@@ -14,15 +15,6 @@ export declare function restoreConsole(): void;
|
|
|
14
15
|
* @internal Exported for testing
|
|
15
16
|
*/
|
|
16
17
|
export declare function extractCoreMessage(message: string): string;
|
|
17
|
-
/**
|
|
18
|
-
* Mark the logging system as initialized and flush any pending logs
|
|
19
|
-
*/
|
|
20
|
-
export declare function markLoggingInitialized(): void;
|
|
21
|
-
/**
|
|
22
|
-
* Reset logging initialization state (primarily for testing)
|
|
23
|
-
* @internal
|
|
24
|
-
*/
|
|
25
|
-
export declare function resetLoggingState(): void;
|
|
26
18
|
/**
|
|
27
19
|
* Logger class for creating source-specific loggers
|
|
28
20
|
*
|
|
@@ -35,6 +27,8 @@ export declare function resetLoggingState(): void;
|
|
|
35
27
|
*/
|
|
36
28
|
export declare class Logger {
|
|
37
29
|
private source;
|
|
30
|
+
private pendingLogs;
|
|
31
|
+
private waitingForConsoleLogsTable;
|
|
38
32
|
constructor(source?: string);
|
|
39
33
|
/**
|
|
40
34
|
* Log a debug message
|
|
@@ -59,5 +53,7 @@ export declare class Logger {
|
|
|
59
53
|
/**
|
|
60
54
|
* Internal method to write log to database
|
|
61
55
|
*/
|
|
62
|
-
|
|
56
|
+
writeLog(level: ConsoleLevel, args: any[]): void;
|
|
57
|
+
flushPendingLogs(): Promise<void>;
|
|
63
58
|
}
|
|
59
|
+
export {};
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Logger = void 0;
|
|
4
|
-
exports.
|
|
4
|
+
exports.setupConsoleLogsProxy = setupConsoleLogsProxy;
|
|
5
5
|
exports.restoreConsole = restoreConsole;
|
|
6
6
|
exports.extractCoreMessage = extractCoreMessage;
|
|
7
|
-
exports.markLoggingInitialized = markLoggingInitialized;
|
|
8
|
-
exports.resetLoggingState = resetLoggingState;
|
|
9
7
|
const utils_1 = require("../utils");
|
|
10
8
|
const console_logs_table_1 = require("./console-logs.table");
|
|
11
|
-
const startupDelayForWritingLogsToDB = Date.now() + 2_000;
|
|
12
9
|
// Store original console methods
|
|
13
10
|
const originalConsole = {
|
|
14
11
|
debug: console.debug,
|
|
@@ -17,35 +14,32 @@ const originalConsole = {
|
|
|
17
14
|
warn: console.warn,
|
|
18
15
|
error: console.error,
|
|
19
16
|
};
|
|
20
|
-
let
|
|
17
|
+
let isConsoleLogsProxySetup = false;
|
|
21
18
|
let currentProcessName = 'unknown';
|
|
22
19
|
let currentProcessInstanceId = '';
|
|
23
|
-
// Guard against infinite recursion during log processing (single process)
|
|
24
|
-
let isWritingLog = false;
|
|
25
|
-
let recursionAttempts = 0;
|
|
26
|
-
const MAX_RECURSION_ATTEMPTS = 3;
|
|
27
20
|
// Guard against cross-process infinite loops
|
|
28
21
|
// Track recent log signatures (hash of message + level + source)
|
|
29
22
|
const recentLogSignatures = new Map();
|
|
30
23
|
const LOG_SIGNATURE_WINDOW_MS = 5000; // 5 second window
|
|
31
24
|
const MAX_IDENTICAL_LOGS_IN_WINDOW = 10; // Max same log in window
|
|
32
25
|
// Track initialization state
|
|
33
|
-
let isInitialized = false;
|
|
34
|
-
const pendingLogs = [];
|
|
35
26
|
const MAX_PENDING_LOGS = 1000; // Prevent memory buildup if initialization never happens
|
|
27
|
+
let defaultLogger = undefined;
|
|
28
|
+
let consoleLogsProxySubscription = undefined;
|
|
36
29
|
/**
|
|
37
30
|
* Setup console proxy to capture all console output and write to ConsoleLogs table.
|
|
38
31
|
* Also subscribes to dataChanged events to output logs from other process instances.
|
|
39
32
|
* @param processName - The name of the process (e.g., 'main', 'renderer')
|
|
40
33
|
*/
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
34
|
+
function setupConsoleLogsProxy(processName) {
|
|
35
|
+
if (isConsoleLogsProxySetup) {
|
|
36
|
+
originalConsole.warn('Console logs proxy already setup, skipping...');
|
|
44
37
|
return;
|
|
45
38
|
}
|
|
46
|
-
|
|
39
|
+
isConsoleLogsProxySetup = true;
|
|
47
40
|
currentProcessName = processName;
|
|
48
41
|
currentProcessInstanceId = (0, utils_1.newid)(); // Unique ID for this process instance
|
|
42
|
+
defaultLogger = new Logger(currentProcessName);
|
|
49
43
|
const levels = ['debug', 'info', 'log', 'warn', 'error'];
|
|
50
44
|
// Monkey-patch console methods to capture local logs
|
|
51
45
|
levels.forEach((level) => {
|
|
@@ -53,130 +47,62 @@ async function setupConsoleProxy(processName) {
|
|
|
53
47
|
console[level] = function (...args) {
|
|
54
48
|
// Always call original console method first (preserve terminal output)
|
|
55
49
|
original.apply(console, args);
|
|
56
|
-
|
|
57
|
-
writeLogToDatabase(level, args).catch((err) => {
|
|
58
|
-
// Use original console to avoid infinite recursion
|
|
59
|
-
originalConsole.error('Failed to write log to database:', err);
|
|
60
|
-
});
|
|
50
|
+
defaultLogger.writeLog(level, args);
|
|
61
51
|
};
|
|
62
52
|
});
|
|
63
53
|
// Subscribe to dataChanged events to output logs from OTHER process instances
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
54
|
+
(0, console_logs_table_1.ConsoleLogs)().then(async (consoleLogsTable) => {
|
|
55
|
+
try {
|
|
56
|
+
consoleLogsProxySubscription = consoleLogsTable.dataChanged.subscribe((evt) => {
|
|
57
|
+
const log = evt.dataObject;
|
|
58
|
+
// Skip if this is our own log
|
|
59
|
+
if (log.processInstanceId === currentProcessInstanceId) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
// Output log from another process with prefix
|
|
63
|
+
const colorCode = log.process === 'renderer' ? '\x1b[36m' : '\x1b[35m'; // cyan for renderer, magenta for main
|
|
64
|
+
const resetCode = '\x1b[0m';
|
|
65
|
+
const prefix = `${colorCode}[${log.process}]${resetCode}`;
|
|
66
|
+
const logLevel = log.level;
|
|
67
|
+
const logArgs = [prefix, log.message];
|
|
68
|
+
if (log.context) {
|
|
69
|
+
logArgs.push(log.context);
|
|
70
|
+
}
|
|
71
|
+
if (originalConsole[logLevel]) {
|
|
72
|
+
originalConsole[logLevel](...logArgs);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
originalConsole.log({ logLevel }, ...logArgs);
|
|
76
|
+
}
|
|
77
|
+
// Also output stack trace for errors
|
|
78
|
+
if (log.stackTrace) {
|
|
79
|
+
originalConsole.error(log.stackTrace);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
originalConsole.error('Failed to subscribe to console logs dataChanged:', err);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
originalConsole.log(`Console proxy initialized for process: ${processName} (instance: ${currentProcessInstanceId})`);
|
|
97
88
|
}
|
|
98
89
|
/**
|
|
99
90
|
* Restore original console methods
|
|
100
91
|
*/
|
|
101
92
|
function restoreConsole() {
|
|
102
|
-
if (!
|
|
93
|
+
if (!isConsoleLogsProxySetup) {
|
|
103
94
|
return;
|
|
104
95
|
}
|
|
96
|
+
consoleLogsProxySubscription?.unsubscribe();
|
|
97
|
+
consoleLogsProxySubscription = undefined;
|
|
105
98
|
console.debug = originalConsole.debug;
|
|
106
99
|
console.info = originalConsole.info;
|
|
107
100
|
console.log = originalConsole.log;
|
|
108
101
|
console.warn = originalConsole.warn;
|
|
109
102
|
console.error = originalConsole.error;
|
|
110
|
-
|
|
103
|
+
isConsoleLogsProxySetup = false;
|
|
111
104
|
originalConsole.log('Console proxy removed, original console restored');
|
|
112
105
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Write a log entry to the ConsoleLogs table
|
|
115
|
-
*/
|
|
116
|
-
async function writeLogToDatabase(level, args) {
|
|
117
|
-
const timestamp = (0, utils_1.getTimestamp)();
|
|
118
|
-
if (timestamp < startupDelayForWritingLogsToDB) {
|
|
119
|
-
await (0, utils_1.sleep)(startupDelayForWritingLogsToDB - timestamp);
|
|
120
|
-
}
|
|
121
|
-
// Guard against infinite recursion (same process)
|
|
122
|
-
if (isWritingLog) {
|
|
123
|
-
recursionAttempts++;
|
|
124
|
-
if (recursionAttempts >= MAX_RECURSION_ATTEMPTS) {
|
|
125
|
-
// Break the loop by using original console
|
|
126
|
-
originalConsole.error(`[console-logger] Infinite loop detected after ${recursionAttempts} attempts, dropping log write`, { level, args });
|
|
127
|
-
// Reset counter after a delay to allow recovery
|
|
128
|
-
setTimeout(() => {
|
|
129
|
-
recursionAttempts = 0;
|
|
130
|
-
}, 1000);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
// Allow a few attempts in case of legitimate nested logging
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
isWritingLog = true;
|
|
137
|
-
try {
|
|
138
|
-
// Format message from arguments
|
|
139
|
-
const message = formatLogMessage(args);
|
|
140
|
-
// Extract context objects from arguments
|
|
141
|
-
const context = extractContext(args);
|
|
142
|
-
// Get stack trace for errors
|
|
143
|
-
const stackTrace = level === 'error' ? getStackTrace() : undefined;
|
|
144
|
-
// Extract source from stack trace
|
|
145
|
-
const source = extractSource(stackTrace);
|
|
146
|
-
// Guard against cross-process infinite loops
|
|
147
|
-
// Extract core message to detect loops even when processes add prefixes
|
|
148
|
-
const coreMessage = extractCoreMessage(message);
|
|
149
|
-
// Create signature from level + source + core message (not the full message)
|
|
150
|
-
const signature = `${level}:${source || 'unknown'}:${coreMessage.substring(0, 100)}`;
|
|
151
|
-
if (!checkLogSignature(signature)) {
|
|
152
|
-
// Too many identical logs in window, likely an infinite loop
|
|
153
|
-
originalConsole.warn(`[console-logger] Cross-process infinite loop detected for log: ${signature.substring(0, 80)}...`);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const logRecord = {
|
|
157
|
-
logId: (0, utils_1.newid)(),
|
|
158
|
-
timestamp,
|
|
159
|
-
level,
|
|
160
|
-
process: currentProcessName,
|
|
161
|
-
processInstanceId: currentProcessInstanceId,
|
|
162
|
-
source,
|
|
163
|
-
message,
|
|
164
|
-
context,
|
|
165
|
-
stackTrace,
|
|
166
|
-
};
|
|
167
|
-
const consoleLogsTable = await (0, console_logs_table_1.ConsoleLogs)();
|
|
168
|
-
await consoleLogsTable.insert(logRecord);
|
|
169
|
-
}
|
|
170
|
-
catch (err) {
|
|
171
|
-
originalConsole.error(`error while trying to write console log`, err);
|
|
172
|
-
// Silently fail if table not available (e.g., during initialization)
|
|
173
|
-
// Don't use console here to avoid infinite recursion
|
|
174
|
-
}
|
|
175
|
-
finally {
|
|
176
|
-
isWritingLog = false;
|
|
177
|
-
recursionAttempts = 0;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
106
|
/**
|
|
181
107
|
* Extract core message by removing common prefixes that might be added by different processes
|
|
182
108
|
* This helps detect cross-process loops where each process adds its own prefix
|
|
@@ -252,15 +178,16 @@ function formatLogMessage(args) {
|
|
|
252
178
|
* Extract structured context objects from log arguments
|
|
253
179
|
*/
|
|
254
180
|
function extractContext(args) {
|
|
255
|
-
|
|
256
|
-
if (objects.length === 0) {
|
|
181
|
+
if (args.length === 0) {
|
|
257
182
|
return undefined;
|
|
258
183
|
}
|
|
259
|
-
if (
|
|
260
|
-
|
|
184
|
+
if (args.length === 1) {
|
|
185
|
+
const singleArg = args[0];
|
|
186
|
+
if (typeof singleArg !== 'object') {
|
|
187
|
+
return { value: singleArg };
|
|
188
|
+
}
|
|
261
189
|
}
|
|
262
|
-
|
|
263
|
-
return Object.assign({}, ...objects);
|
|
190
|
+
return args;
|
|
264
191
|
}
|
|
265
192
|
/**
|
|
266
193
|
* Get current stack trace
|
|
@@ -306,71 +233,26 @@ function extractSource(stackTrace) {
|
|
|
306
233
|
}
|
|
307
234
|
return undefined;
|
|
308
235
|
}
|
|
309
|
-
/**
|
|
310
|
-
* Mark the logging system as initialized and flush any pending logs
|
|
311
|
-
*/
|
|
312
|
-
function markLoggingInitialized() {
|
|
313
|
-
isInitialized = true;
|
|
314
|
-
// Flush pending logs sequentially with small delays to avoid recursion guard
|
|
315
|
-
if (pendingLogs.length > 0) {
|
|
316
|
-
originalConsole.log(`[console-logger] Flushing ${pendingLogs.length} pending logs...`);
|
|
317
|
-
const logsToFlush = [...pendingLogs]; // Copy array
|
|
318
|
-
pendingLogs.length = 0; // Clear the original array
|
|
319
|
-
// Flush logs with small delays between them
|
|
320
|
-
let delay = 0;
|
|
321
|
-
for (const { level, source, args } of logsToFlush) {
|
|
322
|
-
setTimeout(() => {
|
|
323
|
-
writeLogToDatabaseWithSource(level, source, args).catch((err) => {
|
|
324
|
-
originalConsole.error('[console-logger] Failed to flush pending log:', err);
|
|
325
|
-
});
|
|
326
|
-
}, delay);
|
|
327
|
-
delay += 10; // 10ms between each log
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
/**
|
|
332
|
-
* Reset logging initialization state (primarily for testing)
|
|
333
|
-
* @internal
|
|
334
|
-
*/
|
|
335
|
-
function resetLoggingState() {
|
|
336
|
-
isInitialized = false;
|
|
337
|
-
pendingLogs.length = 0;
|
|
338
|
-
}
|
|
339
236
|
/**
|
|
340
237
|
* Write a log entry to the ConsoleLogs table with a specific source
|
|
341
238
|
*/
|
|
342
239
|
async function writeLogToDatabaseWithSource(level, source, args) {
|
|
343
|
-
const timestamp = (0, utils_1.getTimestamp)();
|
|
344
|
-
if (timestamp < startupDelayForWritingLogsToDB) {
|
|
345
|
-
await (0, utils_1.sleep)(startupDelayForWritingLogsToDB - timestamp);
|
|
346
|
-
}
|
|
347
|
-
// Guard against infinite recursion (same process)
|
|
348
|
-
if (isWritingLog) {
|
|
349
|
-
recursionAttempts++;
|
|
350
|
-
if (recursionAttempts >= MAX_RECURSION_ATTEMPTS) {
|
|
351
|
-
originalConsole.error(`[console-logger] Infinite loop detected after ${recursionAttempts} attempts, dropping log write`, { level, source, args });
|
|
352
|
-
setTimeout(() => {
|
|
353
|
-
recursionAttempts = 0;
|
|
354
|
-
}, 1000);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
isWritingLog = true;
|
|
360
240
|
try {
|
|
241
|
+
const timestamp = (0, utils_1.getTimestamp)();
|
|
361
242
|
// Format message from arguments
|
|
362
243
|
const message = formatLogMessage(args);
|
|
363
|
-
//
|
|
364
|
-
|
|
365
|
-
// Get stack trace for errors
|
|
366
|
-
const stackTrace = level === 'error' ? getStackTrace() : undefined;
|
|
367
|
-
// Guard against cross-process infinite loops
|
|
244
|
+
// Check for infinite loop before writing
|
|
245
|
+
// Extract core message to detect cross-process loops where prefixes are added
|
|
368
246
|
const coreMessage = extractCoreMessage(message);
|
|
369
|
-
const signature = `${level}:${
|
|
247
|
+
const signature = `${level}:${coreMessage}:${source}`;
|
|
370
248
|
if (!checkLogSignature(signature)) {
|
|
371
|
-
|
|
249
|
+
// This log is suspected to be part of an infinite loop, drop it
|
|
372
250
|
return;
|
|
373
251
|
}
|
|
252
|
+
// Extract context objects from arguments
|
|
253
|
+
const context = extractContext(args);
|
|
254
|
+
// Get stack trace for errors
|
|
255
|
+
const stackTrace = level === 'error' ? getStackTrace() : undefined;
|
|
374
256
|
const logRecord = {
|
|
375
257
|
logId: (0, utils_1.newid)(),
|
|
376
258
|
timestamp,
|
|
@@ -386,11 +268,7 @@ async function writeLogToDatabaseWithSource(level, source, args) {
|
|
|
386
268
|
await consoleLogsTable.insert(logRecord);
|
|
387
269
|
}
|
|
388
270
|
catch (err) {
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
finally {
|
|
392
|
-
isWritingLog = false;
|
|
393
|
-
recursionAttempts = 0;
|
|
271
|
+
originalConsole.error('[console-logger] Failed to write log to database:', err);
|
|
394
272
|
}
|
|
395
273
|
}
|
|
396
274
|
/**
|
|
@@ -405,6 +283,8 @@ async function writeLogToDatabaseWithSource(level, source, args) {
|
|
|
405
283
|
*/
|
|
406
284
|
class Logger {
|
|
407
285
|
source;
|
|
286
|
+
pendingLogs = [];
|
|
287
|
+
waitingForConsoleLogsTable = true;
|
|
408
288
|
constructor(source) {
|
|
409
289
|
// If no source provided, try to extract from caller's location
|
|
410
290
|
if (!source) {
|
|
@@ -412,9 +292,10 @@ class Logger {
|
|
|
412
292
|
source = extractSource(stack) || 'unknown';
|
|
413
293
|
}
|
|
414
294
|
this.source = source;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
295
|
+
(0, console_logs_table_1.ConsoleLogs)().then(() => {
|
|
296
|
+
this.waitingForConsoleLogsTable = false;
|
|
297
|
+
this.flushPendingLogs();
|
|
298
|
+
});
|
|
418
299
|
}
|
|
419
300
|
/**
|
|
420
301
|
* Log a debug message
|
|
@@ -455,14 +336,14 @@ class Logger {
|
|
|
455
336
|
* Internal method to write log to database
|
|
456
337
|
*/
|
|
457
338
|
writeLog(level, args) {
|
|
458
|
-
if (
|
|
339
|
+
if (this.waitingForConsoleLogsTable) {
|
|
459
340
|
// Queue the log until system is initialized
|
|
460
|
-
if (pendingLogs.length < MAX_PENDING_LOGS) {
|
|
461
|
-
pendingLogs.push({ level,
|
|
341
|
+
if (this.pendingLogs.length < MAX_PENDING_LOGS) {
|
|
342
|
+
this.pendingLogs.push({ level, args });
|
|
462
343
|
}
|
|
463
344
|
else {
|
|
464
345
|
// Drop logs if queue is full to prevent memory issues
|
|
465
|
-
if (pendingLogs.length === MAX_PENDING_LOGS) {
|
|
346
|
+
if (this.pendingLogs.length === MAX_PENDING_LOGS) {
|
|
466
347
|
originalConsole.warn('[console-logger] Pending log queue is full, dropping new logs until initialization');
|
|
467
348
|
}
|
|
468
349
|
}
|
|
@@ -473,5 +354,15 @@ class Logger {
|
|
|
473
354
|
originalConsole.error('[console-logger] Failed to write log:', err);
|
|
474
355
|
});
|
|
475
356
|
}
|
|
357
|
+
async flushPendingLogs() {
|
|
358
|
+
const _pendingLogs = [...this.pendingLogs];
|
|
359
|
+
this.pendingLogs.length = 0;
|
|
360
|
+
for (const { level, args } of _pendingLogs) {
|
|
361
|
+
await (0, utils_1.sleep)(10); // small delay to avoid stampede
|
|
362
|
+
await writeLogToDatabaseWithSource(level, this.source, args).catch((err) => {
|
|
363
|
+
originalConsole.error('[console-logger] Failed to flush pending log:', err);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
476
367
|
}
|
|
477
368
|
exports.Logger = Logger;
|
|
@@ -11,12 +11,16 @@ jest.mock('./console-logs.table', () => {
|
|
|
11
11
|
};
|
|
12
12
|
return {
|
|
13
13
|
ConsoleLogs: jest.fn().mockResolvedValue(mockTable),
|
|
14
|
+
__mockTable: mockTable, // Export for test access
|
|
14
15
|
};
|
|
15
16
|
});
|
|
17
|
+
// Get reference to mock table
|
|
18
|
+
const { __mockTable: mockTable } = require('./console-logs.table');
|
|
16
19
|
// Mock utils
|
|
17
20
|
jest.mock('../utils', () => ({
|
|
18
21
|
newid: () => 'test-id-123',
|
|
19
22
|
getTimestamp: () => '2024-01-01T00:00:00.000Z',
|
|
23
|
+
sleep: jest.fn().mockResolvedValue(undefined),
|
|
20
24
|
}));
|
|
21
25
|
describe('console-logger', () => {
|
|
22
26
|
let originalConsole;
|
|
@@ -62,6 +66,8 @@ describe('console-logger', () => {
|
|
|
62
66
|
jest.clearAllMocks();
|
|
63
67
|
// Restore console before each test
|
|
64
68
|
(0, console_logger_1.restoreConsole)();
|
|
69
|
+
// Reset mock table
|
|
70
|
+
mockTable.insert.mockClear();
|
|
65
71
|
});
|
|
66
72
|
afterAll(() => {
|
|
67
73
|
// Restore original console methods
|
|
@@ -73,8 +79,6 @@ describe('console-logger', () => {
|
|
|
73
79
|
});
|
|
74
80
|
describe('Single-process infinite loop detection', () => {
|
|
75
81
|
it('should detect infinite loop when logging from within log handler', async () => {
|
|
76
|
-
const { ConsoleLogs } = require('./console-logs.table');
|
|
77
|
-
const mockTable = await ConsoleLogs();
|
|
78
82
|
// Setup mock to throw error that triggers another log
|
|
79
83
|
let callCount = 0;
|
|
80
84
|
mockTable.insert.mockImplementation(() => {
|
|
@@ -85,23 +89,25 @@ describe('console-logger', () => {
|
|
|
85
89
|
}
|
|
86
90
|
return Promise.resolve();
|
|
87
91
|
});
|
|
88
|
-
|
|
92
|
+
(0, console_logger_1.setupConsoleLogsProxy)('test-process');
|
|
93
|
+
// Wait for setup to complete
|
|
94
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
89
95
|
// This should trigger the loop
|
|
90
96
|
console.error('Initial error');
|
|
91
97
|
// Wait for async operations
|
|
92
98
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
93
99
|
// Should have called insert but stopped early due to recursion guard
|
|
94
100
|
expect(mockTable.insert).toHaveBeenCalled();
|
|
95
|
-
// The
|
|
96
|
-
|
|
97
|
-
expect(callCount).toBeLessThanOrEqual(2); // Setup log + 1 error attempt
|
|
101
|
+
// The calls should be limited
|
|
102
|
+
expect(callCount).toBeLessThanOrEqual(5);
|
|
98
103
|
});
|
|
99
104
|
});
|
|
100
105
|
describe('Cross-process infinite loop detection', () => {
|
|
101
106
|
it('should detect when same log is written too many times in window', async () => {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await (
|
|
107
|
+
(0, console_logger_1.setupConsoleLogsProxy)('test-process');
|
|
108
|
+
// Wait for setup
|
|
109
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
110
|
+
mockTable.insert.mockClear();
|
|
105
111
|
// Log the same message many times quickly (simulating cross-process loop)
|
|
106
112
|
for (let i = 0; i < 15; i++) {
|
|
107
113
|
console.error('Same error message');
|
|
@@ -111,14 +117,13 @@ describe('console-logger', () => {
|
|
|
111
117
|
// Wait for async operations
|
|
112
118
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
113
119
|
// Should have stopped writing after threshold (10)
|
|
114
|
-
// Plus 1 for the setup log
|
|
115
120
|
expect(mockTable.insert.mock.calls.length).toBeLessThan(15);
|
|
116
121
|
expect(mockTable.insert.mock.calls.length).toBeGreaterThan(0);
|
|
117
122
|
});
|
|
118
123
|
it('should NOT detect loop for different messages', async () => {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
await (
|
|
124
|
+
(0, console_logger_1.setupConsoleLogsProxy)('test-process');
|
|
125
|
+
// Wait for setup
|
|
126
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
122
127
|
// Clear setup log
|
|
123
128
|
mockTable.insert.mockClear();
|
|
124
129
|
// Log different messages
|
|
@@ -133,9 +138,10 @@ describe('console-logger', () => {
|
|
|
133
138
|
expect(mockTable.insert.mock.calls.length).toBeLessThanOrEqual(15);
|
|
134
139
|
});
|
|
135
140
|
it('should detect loop even with modified messages (prefix added)', async () => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
await (
|
|
141
|
+
(0, console_logger_1.setupConsoleLogsProxy)('test-process');
|
|
142
|
+
// Wait for setup
|
|
143
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
144
|
+
mockTable.insert.mockClear();
|
|
139
145
|
// Simulate cross-process loop where each process adds a prefix
|
|
140
146
|
// This represents Process A logging "Error X"
|
|
141
147
|
// Process B seeing it and logging "[main] Error X"
|
|
@@ -170,9 +176,10 @@ describe('console-logger', () => {
|
|
|
170
176
|
});
|
|
171
177
|
describe('Loop detection recovery', () => {
|
|
172
178
|
it('should allow logging again after window expires', async () => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
await (
|
|
179
|
+
(0, console_logger_1.setupConsoleLogsProxy)('test-process');
|
|
180
|
+
// Wait for setup
|
|
181
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
182
|
+
mockTable.insert.mockClear();
|
|
176
183
|
// Trigger loop detection
|
|
177
184
|
for (let i = 0; i < 12; i++) {
|
|
178
185
|
console.error('Same error');
|
|
@@ -190,10 +197,9 @@ describe('console-logger', () => {
|
|
|
190
197
|
});
|
|
191
198
|
describe('Normal logging behavior', () => {
|
|
192
199
|
it('should write logs to database for normal usage', async () => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const mockTable = await ConsoleLogs();
|
|
200
|
+
(0, console_logger_1.setupConsoleLogsProxy)('test-process');
|
|
201
|
+
// Wait for setup to complete
|
|
202
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
197
203
|
mockTable.insert.mockClear();
|
|
198
204
|
console.log('Test message');
|
|
199
205
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
@@ -208,15 +214,10 @@ describe('console-logger', () => {
|
|
|
208
214
|
});
|
|
209
215
|
});
|
|
210
216
|
describe('Logger class', () => {
|
|
211
|
-
beforeEach(() => {
|
|
212
|
-
// Reset state before each Logger test
|
|
213
|
-
(0, console_logger_1.resetLoggingState)();
|
|
214
|
-
});
|
|
215
217
|
it('should create logger with specified source', async () => {
|
|
216
|
-
const { ConsoleLogs } = require('./console-logs.table');
|
|
217
|
-
const mockTable = await ConsoleLogs();
|
|
218
|
-
(0, console_logger_1.markLoggingInitialized)();
|
|
219
218
|
const logger = new console_logger_1.Logger('MyModule');
|
|
219
|
+
// Wait for Logger to initialize
|
|
220
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
220
221
|
mockTable.insert.mockClear();
|
|
221
222
|
logger.log('Test message');
|
|
222
223
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -226,10 +227,9 @@ describe('console-logger', () => {
|
|
|
226
227
|
expect(call[0].message).toBe('Test message');
|
|
227
228
|
});
|
|
228
229
|
it('should support all log levels', async () => {
|
|
229
|
-
const { ConsoleLogs } = require('./console-logs.table');
|
|
230
|
-
const mockTable = await ConsoleLogs();
|
|
231
|
-
(0, console_logger_1.markLoggingInitialized)();
|
|
232
230
|
const logger = new console_logger_1.Logger('TestModule');
|
|
231
|
+
// Wait for Logger to initialize
|
|
232
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
233
233
|
mockTable.insert.mockClear();
|
|
234
234
|
logger.debug('Debug message');
|
|
235
235
|
await new Promise(resolve => setTimeout(resolve, 20));
|
|
@@ -248,11 +248,14 @@ describe('console-logger', () => {
|
|
|
248
248
|
const uniqueLevels = new Set(levels);
|
|
249
249
|
expect(uniqueLevels.size).toBeGreaterThanOrEqual(4);
|
|
250
250
|
});
|
|
251
|
-
it('should queue logs before initialization', async () => {
|
|
251
|
+
it('should queue logs before initialization and flush after', async () => {
|
|
252
|
+
// Create a delayed promise for ConsoleLogs
|
|
253
|
+
let resolveConsoleLogs;
|
|
254
|
+
const delayedPromise = new Promise((resolve) => {
|
|
255
|
+
resolveConsoleLogs = resolve;
|
|
256
|
+
});
|
|
252
257
|
const { ConsoleLogs } = require('./console-logs.table');
|
|
253
|
-
|
|
254
|
-
// Ensure we're not initialized
|
|
255
|
-
// (resetLoggingState was called in beforeEach)
|
|
258
|
+
ConsoleLogs.mockReturnValueOnce(delayedPromise);
|
|
256
259
|
const logger = new console_logger_1.Logger('EarlyModule');
|
|
257
260
|
// Log before initialization
|
|
258
261
|
logger.log('Early message 1');
|
|
@@ -260,21 +263,19 @@ describe('console-logger', () => {
|
|
|
260
263
|
logger.error('Early message 3');
|
|
261
264
|
// Small delay to ensure logs are queued
|
|
262
265
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
263
|
-
mockTable.insert.mockClear();
|
|
264
266
|
// Should not have written yet
|
|
265
267
|
expect(mockTable.insert).not.toHaveBeenCalled();
|
|
266
|
-
// Now
|
|
267
|
-
(
|
|
268
|
+
// Now resolve the ConsoleLogs promise
|
|
269
|
+
resolveConsoleLogs(mockTable);
|
|
268
270
|
// Wait for flush
|
|
269
271
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
270
272
|
// Should have flushed pending logs
|
|
271
273
|
expect(mockTable.insert.mock.calls.length).toBeGreaterThanOrEqual(2);
|
|
272
274
|
});
|
|
273
275
|
it('should write logs directly after initialization', async () => {
|
|
274
|
-
const { ConsoleLogs } = require('./console-logs.table');
|
|
275
|
-
const mockTable = await ConsoleLogs();
|
|
276
|
-
(0, console_logger_1.markLoggingInitialized)();
|
|
277
276
|
const logger = new console_logger_1.Logger('LateModule');
|
|
277
|
+
// Wait for initialization
|
|
278
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
278
279
|
mockTable.insert.mockClear();
|
|
279
280
|
logger.log('Direct message');
|
|
280
281
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
@@ -282,11 +283,10 @@ describe('console-logger', () => {
|
|
|
282
283
|
expect(mockTable.insert.mock.calls[0][0].message).toBe('Direct message');
|
|
283
284
|
});
|
|
284
285
|
it('should use reliable source from constructor', async () => {
|
|
285
|
-
const { ConsoleLogs } = require('./console-logs.table');
|
|
286
|
-
const mockTable = await ConsoleLogs();
|
|
287
|
-
(0, console_logger_1.markLoggingInitialized)();
|
|
288
286
|
const logger1 = new console_logger_1.Logger('SourceA');
|
|
289
287
|
const logger2 = new console_logger_1.Logger('SourceB');
|
|
288
|
+
// Wait for initialization
|
|
289
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
290
290
|
mockTable.insert.mockClear();
|
|
291
291
|
logger1.log('From A');
|
|
292
292
|
await new Promise(resolve => setTimeout(resolve, 50));
|
|
@@ -9,7 +9,7 @@ export declare const consoleLogSchema: z.ZodObject<{
|
|
|
9
9
|
processInstanceId: z.ZodString;
|
|
10
10
|
source: z.ZodOptional<z.ZodString>;
|
|
11
11
|
message: z.ZodString;
|
|
12
|
-
context: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodAny, z.objectOutputType<{}, z.ZodAny, "strip">, z.objectInputType<{}, z.ZodAny, "strip"
|
|
12
|
+
context: z.ZodOptional<z.ZodUnion<[z.ZodObject<{}, "strip", z.ZodAny, z.objectOutputType<{}, z.ZodAny, "strip">, z.objectInputType<{}, z.ZodAny, "strip">>, z.ZodArray<z.ZodAny, "many">]>>;
|
|
13
13
|
stackTrace: z.ZodOptional<z.ZodString>;
|
|
14
14
|
}, "strip", z.ZodTypeAny, {
|
|
15
15
|
message: string;
|
|
@@ -19,7 +19,7 @@ export declare const consoleLogSchema: z.ZodObject<{
|
|
|
19
19
|
process: string;
|
|
20
20
|
processInstanceId: string;
|
|
21
21
|
source?: string | undefined;
|
|
22
|
-
context?: z.objectOutputType<{}, z.ZodAny, "strip"> | undefined;
|
|
22
|
+
context?: any[] | z.objectOutputType<{}, z.ZodAny, "strip"> | undefined;
|
|
23
23
|
stackTrace?: string | undefined;
|
|
24
24
|
}, {
|
|
25
25
|
message: string;
|
|
@@ -29,7 +29,7 @@ export declare const consoleLogSchema: z.ZodObject<{
|
|
|
29
29
|
processInstanceId: string;
|
|
30
30
|
source?: string | undefined;
|
|
31
31
|
timestamp?: number | undefined;
|
|
32
|
-
context?: z.objectInputType<{}, z.ZodAny, "strip"> | undefined;
|
|
32
|
+
context?: any[] | z.objectInputType<{}, z.ZodAny, "strip"> | undefined;
|
|
33
33
|
stackTrace?: string | undefined;
|
|
34
34
|
}>;
|
|
35
35
|
export type IConsoleLog = z.infer<typeof consoleLogSchema>;
|
|
@@ -51,7 +51,7 @@ exports.consoleLogSchema = zod_1.z.object({
|
|
|
51
51
|
processInstanceId: zod_1.z.string().describe('Unique ID for this process instance to filter own logs'),
|
|
52
52
|
source: zod_1.z.string().optional().describe('The source file or module that generated the log'),
|
|
53
53
|
message: zod_1.z.string().describe('The log message'),
|
|
54
|
-
context: zod_types_1.
|
|
54
|
+
context: zod_types_1.zodAnyObjectOrArray.optional().describe('Additional structured context data'),
|
|
55
55
|
stackTrace: zod_1.z.string().optional().describe('Stack trace for errors'),
|
|
56
56
|
});
|
|
57
57
|
const metaData = {
|
package/dist/rpc-types.js
CHANGED
|
@@ -28,7 +28,8 @@ exports.rpcServerCalls = {
|
|
|
28
28
|
};
|
|
29
29
|
exports.rpcClientCalls = {
|
|
30
30
|
ping: async (msg) => `pong: ${msg}`,
|
|
31
|
-
emitEvent: rpcStub('emitEvent'),
|
|
31
|
+
// emitEvent: rpcStub('emitEvent') as ((event: IEventData) => Promise<boolean>),
|
|
32
|
+
emitEvent: ((...args) => Promise.resolve(false)),
|
|
32
33
|
setClientPath: rpcStub('setClientPath'),
|
|
33
34
|
openThread: rpcStub('openThread'),
|
|
34
35
|
};
|
package/dist/utils.js
CHANGED
|
@@ -235,6 +235,10 @@ async function retryOnErrorOrTimeout({ fn, connection, opts = {}, }) {
|
|
|
235
235
|
throw finalError;
|
|
236
236
|
}
|
|
237
237
|
else {
|
|
238
|
+
const weightedTimeout = timeoutCount * 0.1;
|
|
239
|
+
const weightedInverse = 1 - weightedTimeout;
|
|
240
|
+
connection.errorRate = (connection.errorRate * weightedInverse) + weightedTimeout;
|
|
241
|
+
connection.latencyMs = (weightedTimeout * timeout) + (connection.latencyMs * weightedInverse);
|
|
238
242
|
throw new Error(`Timeout after ${timeoutCount} retries waiting for ${timeout}ms`);
|
|
239
243
|
}
|
|
240
244
|
}
|
package/package.json
CHANGED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example usage of the Logger class
|
|
3
|
-
*
|
|
4
|
-
* This file demonstrates how to use the Logger class for reliable, source-specific logging
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Example function that uses the logger
|
|
8
|
-
*/
|
|
9
|
-
export declare function processData(data: any): void;
|
|
10
|
-
/**
|
|
11
|
-
* Example initialization flow
|
|
12
|
-
*/
|
|
13
|
-
export declare function initializeApp(): Promise<void>;
|
|
14
|
-
/**
|
|
15
|
-
* Benefits of using Logger class:
|
|
16
|
-
*
|
|
17
|
-
* 1. **Reliable Source Tracking**: Source is set once at construction time,
|
|
18
|
-
* not extracted from stack traces which can be unreliable
|
|
19
|
-
*
|
|
20
|
-
* 2. **Initialization Safety**: Logs before system is ready are queued and
|
|
21
|
-
* flushed once markLoggingInitialized() is called
|
|
22
|
-
*
|
|
23
|
-
* 3. **Infinite Loop Protection**: Inherits all the cross-process and
|
|
24
|
-
* single-process infinite loop detection
|
|
25
|
-
*
|
|
26
|
-
* 4. **Type Safety**: Full TypeScript support with proper typing
|
|
27
|
-
*
|
|
28
|
-
* 5. **Console Passthrough**: All logs still appear in the console immediately,
|
|
29
|
-
* database writes happen asynchronously
|
|
30
|
-
*
|
|
31
|
-
* Usage pattern:
|
|
32
|
-
* ```typescript
|
|
33
|
-
* // At the top of each file:
|
|
34
|
-
* const logger = new Logger('FeatureName');
|
|
35
|
-
*
|
|
36
|
-
* // Throughout your code:
|
|
37
|
-
* logger.log('Something happened');
|
|
38
|
-
* logger.error('Something went wrong', error);
|
|
39
|
-
* logger.debug('Detailed debugging info', { context });
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Example usage of the Logger class
|
|
4
|
-
*
|
|
5
|
-
* This file demonstrates how to use the Logger class for reliable, source-specific logging
|
|
6
|
-
*/
|
|
7
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.processData = processData;
|
|
9
|
-
exports.initializeApp = initializeApp;
|
|
10
|
-
const console_logger_1 = require("./console-logger");
|
|
11
|
-
// Create a logger at the top of your file with a descriptive source name
|
|
12
|
-
const logger = new console_logger_1.Logger('MyModule');
|
|
13
|
-
// You can also let it auto-detect the source from the filename
|
|
14
|
-
// const logger = new Logger();
|
|
15
|
-
/**
|
|
16
|
-
* Example function that uses the logger
|
|
17
|
-
*/
|
|
18
|
-
function processData(data) {
|
|
19
|
-
logger.log('Processing data', { dataSize: data.length });
|
|
20
|
-
try {
|
|
21
|
-
// Do some work
|
|
22
|
-
if (!data) {
|
|
23
|
-
logger.warn('No data provided');
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
// More processing...
|
|
27
|
-
logger.debug('Data processing in progress', { step: 'validation' });
|
|
28
|
-
// Success
|
|
29
|
-
logger.info('Data processed successfully');
|
|
30
|
-
}
|
|
31
|
-
catch (error) {
|
|
32
|
-
logger.error('Failed to process data', error);
|
|
33
|
-
throw error;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Example initialization flow
|
|
38
|
-
*/
|
|
39
|
-
async function initializeApp() {
|
|
40
|
-
// Early logging (before system is ready) - these get queued
|
|
41
|
-
logger.log('Application starting...');
|
|
42
|
-
// ... Initialize databases, connections, etc ...
|
|
43
|
-
// Mark logging as initialized - this flushes all queued logs
|
|
44
|
-
(0, console_logger_1.markLoggingInitialized)();
|
|
45
|
-
logger.log('Application fully initialized');
|
|
46
|
-
}
|
|
47
|
-
/**
|
|
48
|
-
* Benefits of using Logger class:
|
|
49
|
-
*
|
|
50
|
-
* 1. **Reliable Source Tracking**: Source is set once at construction time,
|
|
51
|
-
* not extracted from stack traces which can be unreliable
|
|
52
|
-
*
|
|
53
|
-
* 2. **Initialization Safety**: Logs before system is ready are queued and
|
|
54
|
-
* flushed once markLoggingInitialized() is called
|
|
55
|
-
*
|
|
56
|
-
* 3. **Infinite Loop Protection**: Inherits all the cross-process and
|
|
57
|
-
* single-process infinite loop detection
|
|
58
|
-
*
|
|
59
|
-
* 4. **Type Safety**: Full TypeScript support with proper typing
|
|
60
|
-
*
|
|
61
|
-
* 5. **Console Passthrough**: All logs still appear in the console immediately,
|
|
62
|
-
* database writes happen asynchronously
|
|
63
|
-
*
|
|
64
|
-
* Usage pattern:
|
|
65
|
-
* ```typescript
|
|
66
|
-
* // At the top of each file:
|
|
67
|
-
* const logger = new Logger('FeatureName');
|
|
68
|
-
*
|
|
69
|
-
* // Throughout your code:
|
|
70
|
-
* logger.log('Something happened');
|
|
71
|
-
* logger.error('Something went wrong', error);
|
|
72
|
-
* logger.debug('Detailed debugging info', { context });
|
|
73
|
-
* ```
|
|
74
|
-
*/
|