@peers-app/peers-sdk 0.7.21 → 0.7.23
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 +92 -225
- 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,13 @@
|
|
|
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
|
-
|
|
8
|
-
|
|
7
|
+
const lodash_1 = require("lodash");
|
|
8
|
+
const serial_json_1 = require("../serial-json");
|
|
9
9
|
const utils_1 = require("../utils");
|
|
10
10
|
const console_logs_table_1 = require("./console-logs.table");
|
|
11
|
-
const startupDelayForWritingLogsToDB = Date.now() + 2_000;
|
|
12
11
|
// Store original console methods
|
|
13
12
|
const originalConsole = {
|
|
14
13
|
debug: console.debug,
|
|
@@ -17,35 +16,32 @@ const originalConsole = {
|
|
|
17
16
|
warn: console.warn,
|
|
18
17
|
error: console.error,
|
|
19
18
|
};
|
|
20
|
-
let
|
|
19
|
+
let isConsoleLogsProxySetup = false;
|
|
21
20
|
let currentProcessName = 'unknown';
|
|
22
21
|
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
22
|
// Guard against cross-process infinite loops
|
|
28
23
|
// Track recent log signatures (hash of message + level + source)
|
|
29
24
|
const recentLogSignatures = new Map();
|
|
30
25
|
const LOG_SIGNATURE_WINDOW_MS = 5000; // 5 second window
|
|
31
26
|
const MAX_IDENTICAL_LOGS_IN_WINDOW = 10; // Max same log in window
|
|
32
27
|
// Track initialization state
|
|
33
|
-
let isInitialized = false;
|
|
34
|
-
const pendingLogs = [];
|
|
35
28
|
const MAX_PENDING_LOGS = 1000; // Prevent memory buildup if initialization never happens
|
|
29
|
+
let defaultLogger = undefined;
|
|
30
|
+
let consoleLogsProxySubscription = undefined;
|
|
36
31
|
/**
|
|
37
32
|
* Setup console proxy to capture all console output and write to ConsoleLogs table.
|
|
38
33
|
* Also subscribes to dataChanged events to output logs from other process instances.
|
|
39
34
|
* @param processName - The name of the process (e.g., 'main', 'renderer')
|
|
40
35
|
*/
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
36
|
+
function setupConsoleLogsProxy(processName) {
|
|
37
|
+
if (isConsoleLogsProxySetup) {
|
|
38
|
+
originalConsole.warn('Console logs proxy already setup, skipping...');
|
|
44
39
|
return;
|
|
45
40
|
}
|
|
46
|
-
|
|
41
|
+
isConsoleLogsProxySetup = true;
|
|
47
42
|
currentProcessName = processName;
|
|
48
43
|
currentProcessInstanceId = (0, utils_1.newid)(); // Unique ID for this process instance
|
|
44
|
+
defaultLogger = new Logger(currentProcessName);
|
|
49
45
|
const levels = ['debug', 'info', 'log', 'warn', 'error'];
|
|
50
46
|
// Monkey-patch console methods to capture local logs
|
|
51
47
|
levels.forEach((level) => {
|
|
@@ -53,130 +49,62 @@ async function setupConsoleProxy(processName) {
|
|
|
53
49
|
console[level] = function (...args) {
|
|
54
50
|
// Always call original console method first (preserve terminal output)
|
|
55
51
|
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
|
-
});
|
|
52
|
+
defaultLogger.writeLog(level, args);
|
|
61
53
|
};
|
|
62
54
|
});
|
|
63
55
|
// 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
|
-
|
|
56
|
+
(0, console_logs_table_1.ConsoleLogs)().then(async (consoleLogsTable) => {
|
|
57
|
+
try {
|
|
58
|
+
consoleLogsProxySubscription = consoleLogsTable.dataChanged.subscribe((evt) => {
|
|
59
|
+
const log = evt.dataObject;
|
|
60
|
+
// Skip if this is our own log
|
|
61
|
+
if (log.processInstanceId === currentProcessInstanceId) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// Output log from another process with prefix
|
|
65
|
+
const colorCode = log.process === 'renderer' ? '\x1b[36m' : '\x1b[35m'; // cyan for renderer, magenta for main
|
|
66
|
+
const resetCode = '\x1b[0m';
|
|
67
|
+
const prefix = `${colorCode}[${log.process}]${resetCode}`;
|
|
68
|
+
const logLevel = log.level;
|
|
69
|
+
const logArgs = [prefix, log.message];
|
|
70
|
+
if (log.context) {
|
|
71
|
+
logArgs.push(log.context);
|
|
72
|
+
}
|
|
73
|
+
if (originalConsole[logLevel]) {
|
|
74
|
+
originalConsole[logLevel](...logArgs);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
originalConsole.log({ logLevel }, ...logArgs);
|
|
78
|
+
}
|
|
79
|
+
// Also output stack trace for errors
|
|
80
|
+
if (log.stackTrace) {
|
|
81
|
+
originalConsole.error(log.stackTrace);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
originalConsole.error('Failed to subscribe to console logs dataChanged:', err);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
originalConsole.log(`Console proxy initialized for process: ${processName} (instance: ${currentProcessInstanceId})`);
|
|
97
90
|
}
|
|
98
91
|
/**
|
|
99
92
|
* Restore original console methods
|
|
100
93
|
*/
|
|
101
94
|
function restoreConsole() {
|
|
102
|
-
if (!
|
|
95
|
+
if (!isConsoleLogsProxySetup) {
|
|
103
96
|
return;
|
|
104
97
|
}
|
|
98
|
+
consoleLogsProxySubscription?.unsubscribe();
|
|
99
|
+
consoleLogsProxySubscription = undefined;
|
|
105
100
|
console.debug = originalConsole.debug;
|
|
106
101
|
console.info = originalConsole.info;
|
|
107
102
|
console.log = originalConsole.log;
|
|
108
103
|
console.warn = originalConsole.warn;
|
|
109
104
|
console.error = originalConsole.error;
|
|
110
|
-
|
|
105
|
+
isConsoleLogsProxySetup = false;
|
|
111
106
|
originalConsole.log('Console proxy removed, original console restored');
|
|
112
107
|
}
|
|
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
108
|
/**
|
|
181
109
|
* Extract core message by removing common prefixes that might be added by different processes
|
|
182
110
|
* This helps detect cross-process loops where each process adds its own prefix
|
|
@@ -226,42 +154,6 @@ function checkLogSignature(signature) {
|
|
|
226
154
|
}
|
|
227
155
|
return true;
|
|
228
156
|
}
|
|
229
|
-
/**
|
|
230
|
-
* Format log arguments into a single message string
|
|
231
|
-
*/
|
|
232
|
-
function formatLogMessage(args) {
|
|
233
|
-
return args.map((arg) => {
|
|
234
|
-
if (typeof arg === 'string') {
|
|
235
|
-
return arg;
|
|
236
|
-
}
|
|
237
|
-
if (arg instanceof Error) {
|
|
238
|
-
return `${arg.name}: ${arg.message}`;
|
|
239
|
-
}
|
|
240
|
-
if (typeof arg === 'object') {
|
|
241
|
-
try {
|
|
242
|
-
return JSON.stringify(arg);
|
|
243
|
-
}
|
|
244
|
-
catch {
|
|
245
|
-
return String(arg);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return String(arg);
|
|
249
|
-
}).join(' ');
|
|
250
|
-
}
|
|
251
|
-
/**
|
|
252
|
-
* Extract structured context objects from log arguments
|
|
253
|
-
*/
|
|
254
|
-
function extractContext(args) {
|
|
255
|
-
const objects = args.filter((arg) => arg && typeof arg === 'object' && !(arg instanceof Error) && !(arg instanceof Date));
|
|
256
|
-
if (objects.length === 0) {
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
|
-
if (objects.length === 1) {
|
|
260
|
-
return objects[0];
|
|
261
|
-
}
|
|
262
|
-
// Merge multiple objects
|
|
263
|
-
return Object.assign({}, ...objects);
|
|
264
|
-
}
|
|
265
157
|
/**
|
|
266
158
|
* Get current stack trace
|
|
267
159
|
*/
|
|
@@ -306,71 +198,37 @@ function extractSource(stackTrace) {
|
|
|
306
198
|
}
|
|
307
199
|
return undefined;
|
|
308
200
|
}
|
|
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
201
|
/**
|
|
340
202
|
* Write a log entry to the ConsoleLogs table with a specific source
|
|
341
203
|
*/
|
|
342
204
|
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
205
|
try {
|
|
206
|
+
const timestamp = (0, utils_1.getTimestamp)();
|
|
361
207
|
// Format message from arguments
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
//
|
|
366
|
-
|
|
367
|
-
// Guard against cross-process infinite loops
|
|
208
|
+
let message = typeof args[0] === 'string' ?
|
|
209
|
+
args.shift() :
|
|
210
|
+
(0, serial_json_1.toJSONString)(args[0]);
|
|
211
|
+
// Check for infinite loop before writing
|
|
212
|
+
// Extract core message to detect cross-process loops where prefixes are added
|
|
368
213
|
const coreMessage = extractCoreMessage(message);
|
|
369
|
-
const signature = `${level}:${
|
|
214
|
+
const signature = `${level}:${coreMessage}:${source}`;
|
|
370
215
|
if (!checkLogSignature(signature)) {
|
|
371
|
-
|
|
216
|
+
// This log is suspected to be part of an infinite loop, drop it
|
|
372
217
|
return;
|
|
373
218
|
}
|
|
219
|
+
// Extract context objects from arguments
|
|
220
|
+
for (const a of [...args]) {
|
|
221
|
+
if (typeof a === 'object' && !((0, lodash_1.isDate)(a) || a === null)) {
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
message += ' ' + args.shift();
|
|
225
|
+
}
|
|
226
|
+
let context = args.length === 0 ?
|
|
227
|
+
undefined : args.length === 1 ?
|
|
228
|
+
args[0] :
|
|
229
|
+
args;
|
|
230
|
+
// Get stack trace for errors
|
|
231
|
+
const stackTrace = level === 'error' ? getStackTrace() : undefined;
|
|
374
232
|
const logRecord = {
|
|
375
233
|
logId: (0, utils_1.newid)(),
|
|
376
234
|
timestamp,
|
|
@@ -386,11 +244,7 @@ async function writeLogToDatabaseWithSource(level, source, args) {
|
|
|
386
244
|
await consoleLogsTable.insert(logRecord);
|
|
387
245
|
}
|
|
388
246
|
catch (err) {
|
|
389
|
-
|
|
390
|
-
}
|
|
391
|
-
finally {
|
|
392
|
-
isWritingLog = false;
|
|
393
|
-
recursionAttempts = 0;
|
|
247
|
+
originalConsole.error('[console-logger] Failed to write log to database:', err);
|
|
394
248
|
}
|
|
395
249
|
}
|
|
396
250
|
/**
|
|
@@ -405,6 +259,8 @@ async function writeLogToDatabaseWithSource(level, source, args) {
|
|
|
405
259
|
*/
|
|
406
260
|
class Logger {
|
|
407
261
|
source;
|
|
262
|
+
pendingLogs = [];
|
|
263
|
+
waitingForConsoleLogsTable = true;
|
|
408
264
|
constructor(source) {
|
|
409
265
|
// If no source provided, try to extract from caller's location
|
|
410
266
|
if (!source) {
|
|
@@ -412,9 +268,10 @@ class Logger {
|
|
|
412
268
|
source = extractSource(stack) || 'unknown';
|
|
413
269
|
}
|
|
414
270
|
this.source = source;
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
271
|
+
(0, console_logs_table_1.ConsoleLogs)().then(() => {
|
|
272
|
+
this.waitingForConsoleLogsTable = false;
|
|
273
|
+
this.flushPendingLogs();
|
|
274
|
+
});
|
|
418
275
|
}
|
|
419
276
|
/**
|
|
420
277
|
* Log a debug message
|
|
@@ -455,14 +312,14 @@ class Logger {
|
|
|
455
312
|
* Internal method to write log to database
|
|
456
313
|
*/
|
|
457
314
|
writeLog(level, args) {
|
|
458
|
-
if (
|
|
315
|
+
if (this.waitingForConsoleLogsTable) {
|
|
459
316
|
// Queue the log until system is initialized
|
|
460
|
-
if (pendingLogs.length < MAX_PENDING_LOGS) {
|
|
461
|
-
pendingLogs.push({ level,
|
|
317
|
+
if (this.pendingLogs.length < MAX_PENDING_LOGS) {
|
|
318
|
+
this.pendingLogs.push({ level, args });
|
|
462
319
|
}
|
|
463
320
|
else {
|
|
464
321
|
// Drop logs if queue is full to prevent memory issues
|
|
465
|
-
if (pendingLogs.length === MAX_PENDING_LOGS) {
|
|
322
|
+
if (this.pendingLogs.length === MAX_PENDING_LOGS) {
|
|
466
323
|
originalConsole.warn('[console-logger] Pending log queue is full, dropping new logs until initialization');
|
|
467
324
|
}
|
|
468
325
|
}
|
|
@@ -473,5 +330,15 @@ class Logger {
|
|
|
473
330
|
originalConsole.error('[console-logger] Failed to write log:', err);
|
|
474
331
|
});
|
|
475
332
|
}
|
|
333
|
+
async flushPendingLogs() {
|
|
334
|
+
const _pendingLogs = [...this.pendingLogs];
|
|
335
|
+
this.pendingLogs.length = 0;
|
|
336
|
+
for (const { level, args } of _pendingLogs) {
|
|
337
|
+
await (0, utils_1.sleep)(10); // small delay to avoid stampede
|
|
338
|
+
await writeLogToDatabaseWithSource(level, this.source, args).catch((err) => {
|
|
339
|
+
originalConsole.error('[console-logger] Failed to flush pending log:', err);
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
476
343
|
}
|
|
477
344
|
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
|
-
*/
|