ntlogger 2.10.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +237 -2
- package/index.d.ts +192 -183
- package/lib/lifecycle.js +53 -0
- package/lib/logger.js +100 -57
- package/lib/pinoHooks.js +446 -0
- package/lib/processHandlers.js +305 -0
- package/lib/secretRedaction.js +675 -0
- package/lib/signalHandler.js +31 -48
- package/package.json +44 -18
- package/pino.d.ts +287 -0
- package/pino.js +434 -13
- package/plugins/README.md +129 -1
- package/plugins/discord.js +30 -48
- package/plugins/index.js +27 -12
- package/plugins/lib/httpDelivery.js +98 -0
- package/plugins/lib/syslogClient.js +18 -8
- package/plugins/mysql.js +73 -83
- package/plugins/openobserve.js +33 -207
- package/plugins/otel.js +5 -7
- package/plugins/postgres.js +69 -67
- package/plugins/sentry.js +36 -3
- package/plugins/syslog.js +6 -9
- package/plugins/teams.js +16 -67
- package/transports/pino.js +188 -12
package/lib/logger.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
const winston = require('winston');
|
|
13
13
|
const crypto = require('crypto');
|
|
14
|
+
const { withTimeout, drain, trackClose } = require('./lifecycle');
|
|
14
15
|
|
|
15
16
|
const { setupSignalHandlers } = require('./signalHandler');
|
|
16
17
|
const { initPlugins } = require('../plugins/index');
|
|
@@ -26,8 +27,7 @@ const levels = require('./levels');
|
|
|
26
27
|
// Define a map to hold the logger instances by their location
|
|
27
28
|
const loggerInstances = new Map();
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
setupSignalHandlers(loggerInstances);
|
|
30
|
+
const activeLoggers = new Set();
|
|
31
31
|
|
|
32
32
|
const customSettings = {
|
|
33
33
|
levels: levels,
|
|
@@ -116,7 +116,7 @@ const consoleFormatter = (config) => winston.format.combine(
|
|
|
116
116
|
|
|
117
117
|
// Apply the color to the padded level and the session ID
|
|
118
118
|
// Note: The session ID color remains based on the 'color' variable (pre-calculated)
|
|
119
|
-
return `${timestamp}
|
|
119
|
+
return `${config.timestamp === false ? "" : timestamp + " "}[${levelColor}${paddedLevel}${resetCode}] [${colorAnsiCode}ID: ${shortSessionId}${resetCode}] [${locationStr}]: ${levelColor}${message}${resetCode}`;
|
|
120
120
|
}),
|
|
121
121
|
);
|
|
122
122
|
|
|
@@ -136,7 +136,7 @@ const fileFormatter = (config) => winston.format.combine(
|
|
|
136
136
|
|
|
137
137
|
// Include all metadata in JSON (filePath is already shown in locationStr but kept in JSON for structured data)
|
|
138
138
|
const metaKeys = Object.keys(meta);
|
|
139
|
-
return `${timestamp}
|
|
139
|
+
return `${config.timestamp === false ? "" : timestamp + " "}[${paddedLevel}] [ID: ${sessionId}] [${locationStr}]: ${message}${
|
|
140
140
|
metaKeys.length ? ' ' + JSON.stringify(meta, null, 2) : ''
|
|
141
141
|
}`;
|
|
142
142
|
}),
|
|
@@ -157,15 +157,20 @@ function wrapLoggerMethod(originalMethod, options = {}) {
|
|
|
157
157
|
childContext = null,
|
|
158
158
|
loggerInstance = null,
|
|
159
159
|
level = 'info',
|
|
160
|
+
state,
|
|
160
161
|
} = options;
|
|
161
162
|
|
|
162
163
|
return function(...args) {
|
|
164
|
+
const target = loggerInstance || this;
|
|
165
|
+
if (state.closing) throw new Error('Logger is closed');
|
|
166
|
+
if (!target.isLevelEnabled(level) || target.transports.length === 0) return;
|
|
167
|
+
const eventTimestamp = new Date().toISOString();
|
|
163
168
|
// Capture stack trace BEFORE setImmediate to get the actual call site
|
|
164
169
|
let capturedCallSite = null;
|
|
165
170
|
let capturedCallerOfCaller = null;
|
|
166
171
|
if (reportPathEnabled) {
|
|
172
|
+
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
167
173
|
try {
|
|
168
|
-
const originalPrepareStackTrace = Error.prepareStackTrace;
|
|
169
174
|
Error.prepareStackTrace = (_, stack) => stack;
|
|
170
175
|
|
|
171
176
|
const err = new Error();
|
|
@@ -215,28 +220,30 @@ function wrapLoggerMethod(originalMethod, options = {}) {
|
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
} catch (err) {
|
|
218
|
-
//
|
|
223
|
+
// Path reporting is optional.
|
|
224
|
+
} finally {
|
|
225
|
+
Error.prepareStackTrace = originalPrepareStackTrace;
|
|
219
226
|
}
|
|
220
227
|
}
|
|
221
228
|
|
|
222
|
-
|
|
229
|
+
state.pending++;
|
|
223
230
|
setImmediate(() => {
|
|
224
231
|
const startTime = performanceTracker?.enabled ? performance.now() : null;
|
|
225
232
|
|
|
226
233
|
try {
|
|
227
234
|
// Extract message and metadata (Winston format: method(message, meta) or method(message))
|
|
228
235
|
let message = args[0];
|
|
229
|
-
let meta = {};
|
|
236
|
+
let meta = { timestamp: eventTimestamp };
|
|
230
237
|
|
|
231
238
|
if (args.length > 1 && typeof args[args.length - 1] === 'object' && args[args.length - 1] !== null && !Array.isArray(args[args.length - 1])) {
|
|
232
239
|
// Standard format: method(message, meta)
|
|
233
|
-
meta = { ...args[args.length - 1] };
|
|
240
|
+
meta = { ...args[args.length - 1], timestamp: eventTimestamp };
|
|
234
241
|
message = args[0];
|
|
235
242
|
} else if (args.length === 1) {
|
|
236
243
|
if (typeof args[0] === 'object' && args[0] !== null && !Array.isArray(args[0])) {
|
|
237
244
|
// Object format: method({ message: '...', ...other })
|
|
238
|
-
message = args[0].message
|
|
239
|
-
meta = { ...args[0] };
|
|
245
|
+
message = args[0].message ?? JSON.stringify(args[0]);
|
|
246
|
+
meta = { ...args[0], timestamp: eventTimestamp };
|
|
240
247
|
delete meta.message;
|
|
241
248
|
} else {
|
|
242
249
|
// Just a message string
|
|
@@ -244,6 +251,10 @@ function wrapLoggerMethod(originalMethod, options = {}) {
|
|
|
244
251
|
}
|
|
245
252
|
}
|
|
246
253
|
|
|
254
|
+
if (args[0] instanceof Error) {
|
|
255
|
+
message = args[0].message;
|
|
256
|
+
meta = { ...meta, name: args[0].name, stack: args[0].stack, cause: args[0].cause };
|
|
257
|
+
}
|
|
247
258
|
// Merge child context if available
|
|
248
259
|
if (childContext) {
|
|
249
260
|
meta = { ...childContext, ...meta };
|
|
@@ -303,7 +314,10 @@ function wrapLoggerMethod(originalMethod, options = {}) {
|
|
|
303
314
|
// Last resort - log to console
|
|
304
315
|
console.error('Logger wrapper error:', err);
|
|
305
316
|
console.error('Fallback also failed:', fallbackErr);
|
|
317
|
+
state.error = fallbackErr;
|
|
306
318
|
}
|
|
319
|
+
} finally {
|
|
320
|
+
state.pending--;
|
|
307
321
|
}
|
|
308
322
|
});
|
|
309
323
|
};
|
|
@@ -352,7 +366,7 @@ const createLoggerInstance = (location = "Unknown", config = {}, transports, par
|
|
|
352
366
|
logTransport.push(
|
|
353
367
|
new winston.transports.File({ filename: `${path}/fatal.log`, level: 'fatal', format: fileFormat, maxsize: maxSize, maxFiles: maxFiles }),
|
|
354
368
|
new winston.transports.File({ filename: `${path}/error.log`, level: 'error', format: fileFormat, maxsize: maxSize, maxFiles: maxFiles }),
|
|
355
|
-
new winston.transports.File({ filename: `${path}
|
|
369
|
+
new winston.transports.File({ filename: `${path}/${config.filename || "combined.log"}`, format: fileFormat, maxsize: maxSize, maxFiles: maxFiles }),
|
|
356
370
|
);
|
|
357
371
|
}
|
|
358
372
|
if (transports) {logTransport = logTransport.concat
|
|
@@ -370,6 +384,12 @@ const createLoggerInstance = (location = "Unknown", config = {}, transports, par
|
|
|
370
384
|
}
|
|
371
385
|
});
|
|
372
386
|
|
|
387
|
+
const state = { pending: 0, closing: false, error: null };
|
|
388
|
+
logger.on('error', error => { state.error = error; });
|
|
389
|
+
logger.transports.forEach(trackClose);
|
|
390
|
+
const add = logger.add.bind(logger);
|
|
391
|
+
logger.add = transport => { trackClose(transport); return add(transport); };
|
|
392
|
+
|
|
373
393
|
// Initialize feature modules
|
|
374
394
|
const sampler = (Object.keys(sampling).length > 0 || Object.keys(rateLimit).length > 0)
|
|
375
395
|
? new LogSampler({ sampling, rateLimit })
|
|
@@ -392,6 +412,7 @@ const createLoggerInstance = (location = "Unknown", config = {}, transports, par
|
|
|
392
412
|
performanceTracker: perfTracker,
|
|
393
413
|
childContext,
|
|
394
414
|
loggerInstance: logger,
|
|
415
|
+
state,
|
|
395
416
|
};
|
|
396
417
|
|
|
397
418
|
const boundLogger = logger;
|
|
@@ -403,15 +424,30 @@ const createLoggerInstance = (location = "Unknown", config = {}, transports, par
|
|
|
403
424
|
logger.fatal = wrapLoggerMethod(boundLogger.fatal.bind(boundLogger), { ...wrapOptions, level: 'fatal' });
|
|
404
425
|
logger.internal = wrapLoggerMethod(boundLogger.internal.bind(boundLogger), { ...wrapOptions, level: 'internal' });
|
|
405
426
|
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
427
|
+
// Children share the root stream and transports, but never own their shutdown.
|
|
428
|
+
const makeChild = (context = {}) => {
|
|
429
|
+
const child = Object.create(logger);
|
|
430
|
+
const frozen = Object.freeze({ ...context });
|
|
431
|
+
let closed = false;
|
|
432
|
+
for (const name of Object.keys(levels)) {
|
|
433
|
+
child[name] = (...args) => {
|
|
434
|
+
if (closed) throw new Error('Child logger is closed');
|
|
435
|
+
let message = args[0];
|
|
436
|
+
let meta = { ...frozen, ...(args[1] || {}) };
|
|
437
|
+
if (message && typeof message === 'object' && !(message instanceof Error)) {
|
|
438
|
+
meta = { ...frozen, ...message };
|
|
439
|
+
message = message.message ?? JSON.stringify(message);
|
|
440
|
+
delete meta.message;
|
|
441
|
+
}
|
|
442
|
+
return logger[name](message, meta);
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
child.child = extra => makeChild({ ...frozen, ...extra });
|
|
446
|
+
child.flush = () => logger.flush();
|
|
447
|
+
child.close = () => { closed = true; return logger.flush(); };
|
|
448
|
+
return child;
|
|
414
449
|
};
|
|
450
|
+
logger.child = makeChild;
|
|
415
451
|
|
|
416
452
|
// Add performance tracking methods
|
|
417
453
|
if (perfTracker) {
|
|
@@ -437,48 +473,52 @@ const createLoggerInstance = (location = "Unknown", config = {}, transports, par
|
|
|
437
473
|
return stats;
|
|
438
474
|
};
|
|
439
475
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
});
|
|
468
|
-
});
|
|
476
|
+
const timeout = config.shutdownTimeout ?? 30000;
|
|
477
|
+
if (!Number.isFinite(timeout) || timeout <= 0) throw new Error('Invalid shutdownTimeout');
|
|
478
|
+
let closePromise;
|
|
479
|
+
let statsTimer;
|
|
480
|
+
logger.flush = () => withTimeout((async () => {
|
|
481
|
+
await drain(logger, state, timeout);
|
|
482
|
+
await Promise.all(logger.transports.map(t => t.flush?.()));
|
|
483
|
+
await drain(logger, state, timeout);
|
|
484
|
+
})(), timeout, 'Logger flush');
|
|
485
|
+
|
|
486
|
+
logger.close = () => {
|
|
487
|
+
if (closePromise) return closePromise;
|
|
488
|
+
state.closing = true;
|
|
489
|
+
clearInterval(statsTimer);
|
|
490
|
+
if (loggerInstances.get(location) === logger) loggerInstances.delete(location);
|
|
491
|
+
const owned = logger.transports.slice();
|
|
492
|
+
closePromise = withTimeout((async () => {
|
|
493
|
+
let failure;
|
|
494
|
+
try { await logger.flush(); } catch (error) { failure = error; }
|
|
495
|
+
try {
|
|
496
|
+
await withTimeout(new Promise((resolve, reject) => {
|
|
497
|
+
if (logger._writableState.finished) return resolve();
|
|
498
|
+
const onError = error => { logger.removeListener('finish', onFinish); reject(error); };
|
|
499
|
+
const onFinish = () => { logger.removeListener('error', onError); resolve(); };
|
|
500
|
+
logger.once('error', onError);
|
|
501
|
+
logger.once('finish', onFinish);
|
|
502
|
+
logger.end();
|
|
503
|
+
}), timeout, 'Logger stream close');
|
|
504
|
+
} catch (error) { failure ||= error; }
|
|
505
|
+
const results = await Promise.allSettled(owned.map(t => t._ntlClose()));
|
|
506
|
+
failure ||= results.find(result => result.status === 'rejected')?.reason;
|
|
507
|
+
if (failure) throw failure;
|
|
508
|
+
})(), timeout * 2, 'Logger close').finally(() => {
|
|
509
|
+
if (sampler) sampler.destroy();
|
|
510
|
+
if (deduplicator) deduplicator.destroy();
|
|
511
|
+
activeLoggers.delete(logger);
|
|
469
512
|
});
|
|
513
|
+
return closePromise;
|
|
470
514
|
};
|
|
471
515
|
|
|
472
|
-
// Statistics reporting interval
|
|
473
516
|
if (statsInterval > 0 && (sampler || deduplicator)) {
|
|
474
|
-
setInterval(() =>
|
|
475
|
-
|
|
476
|
-
logger.internal('Logger Statistics:', stats);
|
|
477
|
-
}, statsInterval);
|
|
517
|
+
statsTimer = setInterval(() => logger.internal('Logger Statistics:', logger.getStats()), statsInterval);
|
|
518
|
+
statsTimer.unref();
|
|
478
519
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
loggerInstances.set(location, logger);
|
|
520
|
+
if (!skipCache) loggerInstances.set(location, logger);
|
|
521
|
+
activeLoggers.add(logger);
|
|
482
522
|
|
|
483
523
|
if (debug === true) {
|
|
484
524
|
// Use pre-calculated color values
|
|
@@ -524,6 +564,7 @@ const logger = (location = "Unknown", config = {}) => {
|
|
|
524
564
|
// If cached logger doesn't have the features but config requests them, recreate
|
|
525
565
|
if (!cachedHasFeatures) {
|
|
526
566
|
// Remove from cache and recreate with new config
|
|
567
|
+
cachedLogger.close().catch(error => global.console.error('Logger replacement cleanup failed:', error));
|
|
527
568
|
loggerInstances.delete(location);
|
|
528
569
|
} else {
|
|
529
570
|
// Cached logger has features, return it
|
|
@@ -547,5 +588,7 @@ const logger = (location = "Unknown", config = {}) => {
|
|
|
547
588
|
return createLoggerInstance(location, config, transports);
|
|
548
589
|
};
|
|
549
590
|
|
|
591
|
+
logger.setupSignalHandlers = options => setupSignalHandlers(activeLoggers, options);
|
|
592
|
+
|
|
550
593
|
// Export a function to create or return the existing logger instance
|
|
551
594
|
module.exports = logger;
|