@peers-app/peers-sdk 0.7.17 → 0.7.19

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.
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Logger = void 0;
3
4
  exports.setupConsoleProxy = setupConsoleProxy;
4
5
  exports.restoreConsole = restoreConsole;
6
+ exports.extractCoreMessage = extractCoreMessage;
7
+ exports.markLoggingInitialized = markLoggingInitialized;
8
+ exports.resetLoggingState = resetLoggingState;
5
9
  const utils_1 = require("../utils");
6
10
  const console_logs_table_1 = require("./console-logs.table");
11
+ const startupDelayForWritingLogsToDB = Date.now() + 2_000;
7
12
  // Store original console methods
8
13
  const originalConsole = {
9
14
  debug: console.debug,
@@ -15,6 +20,19 @@ const originalConsole = {
15
20
  let isProxySetup = false;
16
21
  let currentProcessName = 'unknown';
17
22
  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
+ // Guard against cross-process infinite loops
28
+ // Track recent log signatures (hash of message + level + source)
29
+ const recentLogSignatures = new Map();
30
+ const LOG_SIGNATURE_WINDOW_MS = 5000; // 5 second window
31
+ const MAX_IDENTICAL_LOGS_IN_WINDOW = 10; // Max same log in window
32
+ // Track initialization state
33
+ let isInitialized = false;
34
+ const pendingLogs = [];
35
+ const MAX_PENDING_LOGS = 1000; // Prevent memory buildup if initialization never happens
18
36
  /**
19
37
  * Setup console proxy to capture all console output and write to ConsoleLogs table.
20
38
  * Also subscribes to dataChanged events to output logs from other process instances.
@@ -96,6 +114,26 @@ function restoreConsole() {
96
114
  * Write a log entry to the ConsoleLogs table
97
115
  */
98
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;
99
137
  try {
100
138
  // Format message from arguments
101
139
  const message = formatLogMessage(args);
@@ -105,9 +143,19 @@ async function writeLogToDatabase(level, args) {
105
143
  const stackTrace = level === 'error' ? getStackTrace() : undefined;
106
144
  // Extract source from stack trace
107
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
+ }
108
156
  const logRecord = {
109
157
  logId: (0, utils_1.newid)(),
110
- timestamp: (0, utils_1.getTimestamp)(),
158
+ timestamp,
111
159
  level,
112
160
  process: currentProcessName,
113
161
  processInstanceId: currentProcessInstanceId,
@@ -120,9 +168,63 @@ async function writeLogToDatabase(level, args) {
120
168
  await consoleLogsTable.insert(logRecord);
121
169
  }
122
170
  catch (err) {
171
+ originalConsole.error(`error while trying to write console log`, err);
123
172
  // Silently fail if table not available (e.g., during initialization)
124
173
  // Don't use console here to avoid infinite recursion
125
174
  }
175
+ finally {
176
+ isWritingLog = false;
177
+ recursionAttempts = 0;
178
+ }
179
+ }
180
+ /**
181
+ * Extract core message by removing common prefixes that might be added by different processes
182
+ * This helps detect cross-process loops where each process adds its own prefix
183
+ * @internal Exported for testing
184
+ */
185
+ function extractCoreMessage(message) {
186
+ let core = message;
187
+ // Remove common process/context prefixes like [main], [renderer], [process-name]
188
+ // Keep removing until no more matches found
189
+ let previousCore = '';
190
+ while (core !== previousCore) {
191
+ previousCore = core;
192
+ // Remove [xxx] prefixes
193
+ core = core.replace(/^\[[^\]]+\]\s*/g, '');
194
+ // Remove common timestamp patterns
195
+ core = core.replace(/^\d{4}-\d{2}-\d{2}[T\s]\d{2}:\d{2}:\d{2}[^\s]*\s*/g, '');
196
+ // Remove "Error: " prefix that might be added
197
+ core = core.replace(/^Error:\s*/i, '');
198
+ }
199
+ return core.trim();
200
+ }
201
+ /**
202
+ * Check if a log signature is within acceptable frequency limits
203
+ * Returns false if this log should be dropped due to suspected infinite loop
204
+ */
205
+ function checkLogSignature(signature) {
206
+ const now = Date.now();
207
+ // Clean up old entries outside the window
208
+ for (const [key, value] of recentLogSignatures.entries()) {
209
+ if (now - value.firstSeen > LOG_SIGNATURE_WINDOW_MS) {
210
+ recentLogSignatures.delete(key);
211
+ }
212
+ }
213
+ // Check current signature
214
+ const existing = recentLogSignatures.get(signature);
215
+ if (!existing) {
216
+ // First time seeing this log
217
+ recentLogSignatures.set(signature, { count: 1, firstSeen: now });
218
+ return true;
219
+ }
220
+ // Within window, increment count
221
+ existing.count++;
222
+ if (existing.count > MAX_IDENTICAL_LOGS_IN_WINDOW) {
223
+ // Too many identical logs, likely infinite loop
224
+ // Keep the entry to continue blocking (will be cleaned up after window expires)
225
+ return false;
226
+ }
227
+ return true;
126
228
  }
127
229
  /**
128
230
  * Format log arguments into a single message string
@@ -204,3 +306,172 @@ function extractSource(stackTrace) {
204
306
  }
205
307
  return undefined;
206
308
  }
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
+ /**
340
+ * Write a log entry to the ConsoleLogs table with a specific source
341
+ */
342
+ 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
+ try {
361
+ // Format message from arguments
362
+ const message = formatLogMessage(args);
363
+ // Extract context objects from arguments
364
+ const context = extractContext(args);
365
+ // Get stack trace for errors
366
+ const stackTrace = level === 'error' ? getStackTrace() : undefined;
367
+ // Guard against cross-process infinite loops
368
+ const coreMessage = extractCoreMessage(message);
369
+ const signature = `${level}:${source}:${coreMessage.substring(0, 100)}`;
370
+ if (!checkLogSignature(signature)) {
371
+ originalConsole.warn(`[console-logger] Cross-process infinite loop detected for log: ${signature.substring(0, 80)}...`);
372
+ return;
373
+ }
374
+ const logRecord = {
375
+ logId: (0, utils_1.newid)(),
376
+ timestamp,
377
+ level,
378
+ process: currentProcessName,
379
+ processInstanceId: currentProcessInstanceId,
380
+ source,
381
+ message,
382
+ context,
383
+ stackTrace,
384
+ };
385
+ const consoleLogsTable = await (0, console_logs_table_1.ConsoleLogs)();
386
+ await consoleLogsTable.insert(logRecord);
387
+ }
388
+ catch (err) {
389
+ // Silently fail if table not available
390
+ }
391
+ finally {
392
+ isWritingLog = false;
393
+ recursionAttempts = 0;
394
+ }
395
+ }
396
+ /**
397
+ * Logger class for creating source-specific loggers
398
+ *
399
+ * Usage:
400
+ * ```typescript
401
+ * const logger = new Logger('MyModule');
402
+ * logger.log('Something happened');
403
+ * logger.error('An error occurred');
404
+ * ```
405
+ */
406
+ class Logger {
407
+ source;
408
+ constructor(source) {
409
+ // If no source provided, try to extract from caller's location
410
+ if (!source) {
411
+ const stack = new Error().stack;
412
+ source = extractSource(stack) || 'unknown';
413
+ }
414
+ this.source = source;
415
+ // ConsoleLogs().then(() => {
416
+ // markLoggingInitialized();
417
+ // })
418
+ }
419
+ /**
420
+ * Log a debug message
421
+ */
422
+ debug(...args) {
423
+ originalConsole.debug(...args);
424
+ this.writeLog('debug', args);
425
+ }
426
+ /**
427
+ * Log an info message
428
+ */
429
+ info(...args) {
430
+ originalConsole.info(...args);
431
+ this.writeLog('info', args);
432
+ }
433
+ /**
434
+ * Log a general message
435
+ */
436
+ log(...args) {
437
+ originalConsole.log(...args);
438
+ this.writeLog('log', args);
439
+ }
440
+ /**
441
+ * Log a warning message
442
+ */
443
+ warn(...args) {
444
+ originalConsole.warn(...args);
445
+ this.writeLog('warn', args);
446
+ }
447
+ /**
448
+ * Log an error message
449
+ */
450
+ error(...args) {
451
+ originalConsole.error(...args);
452
+ this.writeLog('error', args);
453
+ }
454
+ /**
455
+ * Internal method to write log to database
456
+ */
457
+ writeLog(level, args) {
458
+ if (!isInitialized) {
459
+ // Queue the log until system is initialized
460
+ if (pendingLogs.length < MAX_PENDING_LOGS) {
461
+ pendingLogs.push({ level, source: this.source, args });
462
+ }
463
+ else {
464
+ // Drop logs if queue is full to prevent memory issues
465
+ if (pendingLogs.length === MAX_PENDING_LOGS) {
466
+ originalConsole.warn('[console-logger] Pending log queue is full, dropping new logs until initialization');
467
+ }
468
+ }
469
+ return;
470
+ }
471
+ // System is initialized, write directly
472
+ writeLogToDatabaseWithSource(level, this.source, args).catch((err) => {
473
+ originalConsole.error('[console-logger] Failed to write log:', err);
474
+ });
475
+ }
476
+ }
477
+ exports.Logger = Logger;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,300 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const console_logger_1 = require("./console-logger");
4
+ // Mock the ConsoleLogs table
5
+ jest.mock('./console-logs.table', () => {
6
+ const mockTable = {
7
+ insert: jest.fn().mockResolvedValue(undefined),
8
+ dataChanged: {
9
+ subscribe: jest.fn(),
10
+ },
11
+ };
12
+ return {
13
+ ConsoleLogs: jest.fn().mockResolvedValue(mockTable),
14
+ };
15
+ });
16
+ // Mock utils
17
+ jest.mock('../utils', () => ({
18
+ newid: () => 'test-id-123',
19
+ getTimestamp: () => '2024-01-01T00:00:00.000Z',
20
+ }));
21
+ describe('console-logger', () => {
22
+ let originalConsole;
23
+ describe('extractCoreMessage', () => {
24
+ it('should extract core message from messages with [prefix] tags', () => {
25
+ expect((0, console_logger_1.extractCoreMessage)('[main] Error message')).toBe('Error message');
26
+ expect((0, console_logger_1.extractCoreMessage)('[renderer] [main] Error message')).toBe('Error message');
27
+ expect((0, console_logger_1.extractCoreMessage)('[process-1] [process-2] [process-3] Error message')).toBe('Error message');
28
+ });
29
+ it('should remove Error: prefix', () => {
30
+ expect((0, console_logger_1.extractCoreMessage)('Error: Something went wrong')).toBe('Something went wrong');
31
+ expect((0, console_logger_1.extractCoreMessage)('[main] Error: Something went wrong')).toBe('Something went wrong');
32
+ });
33
+ it('should remove timestamp patterns', () => {
34
+ expect((0, console_logger_1.extractCoreMessage)('2024-01-01T12:00:00.000Z Error message')).toBe('Error message');
35
+ expect((0, console_logger_1.extractCoreMessage)('2024-01-01 12:00:00 Error message')).toBe('Error message');
36
+ });
37
+ it('should handle combinations of prefixes', () => {
38
+ expect((0, console_logger_1.extractCoreMessage)('[renderer] 2024-01-01T12:00:00.000Z Error: Failed to write log')).toBe('Failed to write log');
39
+ expect((0, console_logger_1.extractCoreMessage)('[main] [renderer] Error: Failed to write log')).toBe('Failed to write log');
40
+ });
41
+ it('should return the same message if no prefixes found', () => {
42
+ expect((0, console_logger_1.extractCoreMessage)('Plain error message')).toBe('Plain error message');
43
+ });
44
+ it('should handle empty or whitespace messages', () => {
45
+ expect((0, console_logger_1.extractCoreMessage)('')).toBe('');
46
+ expect((0, console_logger_1.extractCoreMessage)(' ')).toBe('');
47
+ expect((0, console_logger_1.extractCoreMessage)('[main]')).toBe('');
48
+ });
49
+ });
50
+ beforeAll(() => {
51
+ // Save original console methods
52
+ originalConsole = {
53
+ debug: console.debug,
54
+ info: console.info,
55
+ log: console.log,
56
+ warn: console.warn,
57
+ error: console.error,
58
+ };
59
+ });
60
+ beforeEach(() => {
61
+ // Clear all mocks
62
+ jest.clearAllMocks();
63
+ // Restore console before each test
64
+ (0, console_logger_1.restoreConsole)();
65
+ });
66
+ afterAll(() => {
67
+ // Restore original console methods
68
+ console.debug = originalConsole.debug;
69
+ console.info = originalConsole.info;
70
+ console.log = originalConsole.log;
71
+ console.warn = originalConsole.warn;
72
+ console.error = originalConsole.error;
73
+ });
74
+ describe('Single-process infinite loop detection', () => {
75
+ 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
+ // Setup mock to throw error that triggers another log
79
+ let callCount = 0;
80
+ mockTable.insert.mockImplementation(() => {
81
+ callCount++;
82
+ if (callCount < 10) {
83
+ // This would trigger another log attempt
84
+ throw new Error('Database error');
85
+ }
86
+ return Promise.resolve();
87
+ });
88
+ await (0, console_logger_1.setupConsoleProxy)('test-process');
89
+ // This should trigger the loop
90
+ console.error('Initial error');
91
+ // Wait for async operations
92
+ await new Promise(resolve => setTimeout(resolve, 100));
93
+ // Should have called insert but stopped early due to recursion guard
94
+ expect(mockTable.insert).toHaveBeenCalled();
95
+ // The recursion guard should stop it after just 1 attempt
96
+ // (because isWritingLog prevents re-entry)
97
+ expect(callCount).toBeLessThanOrEqual(2); // Setup log + 1 error attempt
98
+ });
99
+ });
100
+ describe('Cross-process infinite loop detection', () => {
101
+ it('should detect when same log is written too many times in window', async () => {
102
+ const { ConsoleLogs } = require('./console-logs.table');
103
+ const mockTable = await ConsoleLogs();
104
+ await (0, console_logger_1.setupConsoleProxy)('test-process');
105
+ // Log the same message many times quickly (simulating cross-process loop)
106
+ for (let i = 0; i < 15; i++) {
107
+ console.error('Same error message');
108
+ // Small delay to ensure they're processed
109
+ await new Promise(resolve => setTimeout(resolve, 10));
110
+ }
111
+ // Wait for async operations
112
+ await new Promise(resolve => setTimeout(resolve, 100));
113
+ // Should have stopped writing after threshold (10)
114
+ // Plus 1 for the setup log
115
+ expect(mockTable.insert.mock.calls.length).toBeLessThan(15);
116
+ expect(mockTable.insert.mock.calls.length).toBeGreaterThan(0);
117
+ });
118
+ it('should NOT detect loop for different messages', async () => {
119
+ const { ConsoleLogs } = require('./console-logs.table');
120
+ const mockTable = await ConsoleLogs();
121
+ await (0, console_logger_1.setupConsoleProxy)('test-process');
122
+ // Clear setup log
123
+ mockTable.insert.mockClear();
124
+ // Log different messages
125
+ for (let i = 0; i < 15; i++) {
126
+ console.error(`Different error message ${i}`);
127
+ await new Promise(resolve => setTimeout(resolve, 10));
128
+ }
129
+ // Wait for async operations
130
+ await new Promise(resolve => setTimeout(resolve, 100));
131
+ // Should have written most/all messages (allow for timing variance)
132
+ expect(mockTable.insert.mock.calls.length).toBeGreaterThanOrEqual(14);
133
+ expect(mockTable.insert.mock.calls.length).toBeLessThanOrEqual(15);
134
+ });
135
+ it('should detect loop even with modified messages (prefix added)', async () => {
136
+ const { ConsoleLogs } = require('./console-logs.table');
137
+ const mockTable = await ConsoleLogs();
138
+ await (0, console_logger_1.setupConsoleProxy)('test-process');
139
+ // Simulate cross-process loop where each process adds a prefix
140
+ // This represents Process A logging "Error X"
141
+ // Process B seeing it and logging "[main] Error X"
142
+ // Process A seeing that and logging "[renderer] [main] Error X"
143
+ const baseMessage = 'Failed to write log to database';
144
+ const messages = [
145
+ baseMessage,
146
+ `[main] ${baseMessage}`,
147
+ `[renderer] [main] ${baseMessage}`,
148
+ `[main] [renderer] [main] ${baseMessage}`,
149
+ `[renderer] [main] [renderer] [main] ${baseMessage}`,
150
+ ];
151
+ // Log these messages in sequence, simulating the loop
152
+ // Need enough to trigger the threshold (MAX_IDENTICAL_LOGS_IN_WINDOW = 10)
153
+ for (let cycle = 0; cycle < 3; cycle++) {
154
+ for (const msg of messages) {
155
+ console.error(msg);
156
+ await new Promise(resolve => setTimeout(resolve, 10));
157
+ }
158
+ }
159
+ // Wait for async operations
160
+ await new Promise(resolve => setTimeout(resolve, 100));
161
+ // Should detect this as a loop even though messages have different prefixes
162
+ // because extractCoreMessage normalizes them all to the same core message
163
+ // We logged 15 messages (5 variants x 3 cycles), but they all have the same core
164
+ // So after 10, it should start blocking
165
+ const insertCount = mockTable.insert.mock.calls.length;
166
+ // Should be: 1 (setup) + 10 (threshold) = 11, might be a bit more due to timing
167
+ expect(insertCount).toBeGreaterThan(1); // At least setup + some logs
168
+ expect(insertCount).toBeLessThan(17); // Definitely less than all 15 + setup
169
+ });
170
+ });
171
+ describe('Loop detection recovery', () => {
172
+ it('should allow logging again after window expires', async () => {
173
+ const { ConsoleLogs } = require('./console-logs.table');
174
+ const mockTable = await ConsoleLogs();
175
+ await (0, console_logger_1.setupConsoleProxy)('test-process');
176
+ // Trigger loop detection
177
+ for (let i = 0; i < 12; i++) {
178
+ console.error('Same error');
179
+ await new Promise(resolve => setTimeout(resolve, 10));
180
+ }
181
+ await new Promise(resolve => setTimeout(resolve, 100));
182
+ const firstBatchCount = mockTable.insert.mock.calls.length;
183
+ // Wait for window to expire (5 seconds in real code, but we can't easily test that)
184
+ // Just verify the blocking is in place
185
+ console.error('Same error');
186
+ await new Promise(resolve => setTimeout(resolve, 100));
187
+ // Should still be blocked
188
+ expect(mockTable.insert.mock.calls.length).toBe(firstBatchCount);
189
+ });
190
+ });
191
+ describe('Normal logging behavior', () => {
192
+ it('should write logs to database for normal usage', async () => {
193
+ const { ConsoleLogs } = require('./console-logs.table');
194
+ await (0, console_logger_1.setupConsoleProxy)('test-process');
195
+ // Get fresh mock reference after setup
196
+ const mockTable = await ConsoleLogs();
197
+ mockTable.insert.mockClear();
198
+ console.log('Test message');
199
+ await new Promise(resolve => setTimeout(resolve, 50));
200
+ console.error('Test error');
201
+ await new Promise(resolve => setTimeout(resolve, 50));
202
+ console.warn('Test warning');
203
+ await new Promise(resolve => setTimeout(resolve, 50));
204
+ // Verify at least the logs were attempted
205
+ expect(mockTable.insert).toHaveBeenCalled();
206
+ expect(mockTable.insert.mock.calls.length).toBeGreaterThanOrEqual(1);
207
+ expect(mockTable.insert.mock.calls.length).toBeLessThanOrEqual(3);
208
+ });
209
+ });
210
+ describe('Logger class', () => {
211
+ beforeEach(() => {
212
+ // Reset state before each Logger test
213
+ (0, console_logger_1.resetLoggingState)();
214
+ });
215
+ 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
+ const logger = new console_logger_1.Logger('MyModule');
220
+ mockTable.insert.mockClear();
221
+ logger.log('Test message');
222
+ await new Promise(resolve => setTimeout(resolve, 100));
223
+ expect(mockTable.insert).toHaveBeenCalled();
224
+ const call = mockTable.insert.mock.calls[0];
225
+ expect(call[0].source).toBe('MyModule');
226
+ expect(call[0].message).toBe('Test message');
227
+ });
228
+ 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
+ const logger = new console_logger_1.Logger('TestModule');
233
+ mockTable.insert.mockClear();
234
+ logger.debug('Debug message');
235
+ await new Promise(resolve => setTimeout(resolve, 20));
236
+ logger.info('Info message');
237
+ await new Promise(resolve => setTimeout(resolve, 20));
238
+ logger.log('Log message');
239
+ await new Promise(resolve => setTimeout(resolve, 20));
240
+ logger.warn('Warn message');
241
+ await new Promise(resolve => setTimeout(resolve, 20));
242
+ logger.error('Error message');
243
+ await new Promise(resolve => setTimeout(resolve, 100));
244
+ // Should have most/all logs
245
+ expect(mockTable.insert.mock.calls.length).toBeGreaterThanOrEqual(4);
246
+ // Check that different levels were used
247
+ const levels = mockTable.insert.mock.calls.map((call) => call[0].level);
248
+ const uniqueLevels = new Set(levels);
249
+ expect(uniqueLevels.size).toBeGreaterThanOrEqual(4);
250
+ });
251
+ it('should queue logs before initialization', async () => {
252
+ const { ConsoleLogs } = require('./console-logs.table');
253
+ const mockTable = await ConsoleLogs();
254
+ // Ensure we're not initialized
255
+ // (resetLoggingState was called in beforeEach)
256
+ const logger = new console_logger_1.Logger('EarlyModule');
257
+ // Log before initialization
258
+ logger.log('Early message 1');
259
+ logger.warn('Early message 2');
260
+ logger.error('Early message 3');
261
+ // Small delay to ensure logs are queued
262
+ await new Promise(resolve => setTimeout(resolve, 50));
263
+ mockTable.insert.mockClear();
264
+ // Should not have written yet
265
+ expect(mockTable.insert).not.toHaveBeenCalled();
266
+ // Now initialize
267
+ (0, console_logger_1.markLoggingInitialized)();
268
+ // Wait for flush
269
+ await new Promise(resolve => setTimeout(resolve, 300));
270
+ // Should have flushed pending logs
271
+ expect(mockTable.insert.mock.calls.length).toBeGreaterThanOrEqual(2);
272
+ });
273
+ 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
+ const logger = new console_logger_1.Logger('LateModule');
278
+ mockTable.insert.mockClear();
279
+ logger.log('Direct message');
280
+ await new Promise(resolve => setTimeout(resolve, 100));
281
+ expect(mockTable.insert).toHaveBeenCalledTimes(1);
282
+ expect(mockTable.insert.mock.calls[0][0].message).toBe('Direct message');
283
+ });
284
+ 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
+ const logger1 = new console_logger_1.Logger('SourceA');
289
+ const logger2 = new console_logger_1.Logger('SourceB');
290
+ mockTable.insert.mockClear();
291
+ logger1.log('From A');
292
+ await new Promise(resolve => setTimeout(resolve, 50));
293
+ logger2.log('From B');
294
+ await new Promise(resolve => setTimeout(resolve, 100));
295
+ expect(mockTable.insert).toHaveBeenCalledTimes(2);
296
+ expect(mockTable.insert.mock.calls[0][0].source).toBe('SourceA');
297
+ expect(mockTable.insert.mock.calls[1][0].source).toBe('SourceB');
298
+ });
299
+ });
300
+ });
@@ -13,9 +13,9 @@ export declare const consoleLogSchema: z.ZodObject<{
13
13
  stackTrace: z.ZodOptional<z.ZodString>;
14
14
  }, "strip", z.ZodTypeAny, {
15
15
  message: string;
16
+ level: "error" | "debug" | "info" | "log" | "warn";
16
17
  timestamp: number;
17
18
  logId: string;
18
- level: "error" | "debug" | "info" | "log" | "warn";
19
19
  process: string;
20
20
  processInstanceId: string;
21
21
  source?: string | undefined;
@@ -23,8 +23,8 @@ export declare const consoleLogSchema: z.ZodObject<{
23
23
  stackTrace?: string | undefined;
24
24
  }, {
25
25
  message: string;
26
- logId: string;
27
26
  level: "error" | "debug" | "info" | "log" | "warn";
27
+ logId: string;
28
28
  process: string;
29
29
  processInstanceId: string;
30
30
  source?: string | undefined;
@@ -0,0 +1,41 @@
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
+ */