ntlogger 2.8.2 → 2.9.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/lib/logger.js +49 -8
- package/package.json +21 -2
- package/pino.js +36 -0
- package/transports/pino.js +169 -0
package/lib/logger.js
CHANGED
|
@@ -86,15 +86,26 @@ const colorRgb = {
|
|
|
86
86
|
};
|
|
87
87
|
const colorAnsiCode = `\x1b[38;2;${colorRgb.r};${colorRgb.g};${colorRgb.b}m`;
|
|
88
88
|
|
|
89
|
+
// Helper function to strip ANSI codes from a string
|
|
90
|
+
function stripAnsiCodes(str) {
|
|
91
|
+
if (typeof str !== 'string') {
|
|
92
|
+
return str;
|
|
93
|
+
}
|
|
94
|
+
// Remove ANSI escape codes: \u001b[ followed by numbers, semicolons, and ending with 'm'
|
|
95
|
+
return str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
96
|
+
}
|
|
97
|
+
|
|
89
98
|
// Custom console formatter with random color for session ID and set color for log level and message
|
|
90
99
|
const consoleFormatter = (config) => winston.format.combine(
|
|
91
100
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
92
101
|
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
93
102
|
const shortSessionId = sessionId.substring(sessionId.length - 6); // Get the last 6 characters of the session ID
|
|
94
|
-
|
|
103
|
+
// Strip ANSI codes and normalize the level string before padding to ensure consistent alignment
|
|
104
|
+
const cleanLevel = stripAnsiCodes(String(level)).trim();
|
|
105
|
+
const paddedLevel = cleanLevel.padEnd(8); // Pad the level to ensure consistent spacing
|
|
95
106
|
|
|
96
107
|
// Retrieve the corresponding ANSI color code for the level from custom settings
|
|
97
|
-
const levelColor = customSettings.colors[level] || ''; // Default to no color if the level is unknown
|
|
108
|
+
const levelColor = customSettings.colors[cleanLevel] || customSettings.colors[level] || ''; // Default to no color if the level is unknown
|
|
98
109
|
const resetCode = '\x1b[0m';
|
|
99
110
|
|
|
100
111
|
// Build location string (optionally include filePath if reportPath is enabled)
|
|
@@ -113,7 +124,9 @@ const consoleFormatter = (config) => winston.format.combine(
|
|
|
113
124
|
const fileFormatter = (config) => winston.format.combine(
|
|
114
125
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
115
126
|
winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
116
|
-
|
|
127
|
+
// Strip ANSI codes and normalize the level string before padding to ensure consistent alignment
|
|
128
|
+
const cleanLevel = stripAnsiCodes(String(level)).trim();
|
|
129
|
+
const paddedLevel = cleanLevel.padEnd(8); // Pad the level to ensure consistent spacing
|
|
117
130
|
|
|
118
131
|
// Build location string (optionally include filePath if reportPath is enabled)
|
|
119
132
|
let locationStr = meta.location || 'Unknown';
|
|
@@ -489,11 +502,39 @@ const createLoggerInstance = (location = "Unknown", config = {}, transports, par
|
|
|
489
502
|
const logger = (location = "Unknown", config = {}) => {
|
|
490
503
|
// Check if a logger for the given location already exists
|
|
491
504
|
if (loggerInstances.has(location) && !config.skipCache) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
505
|
+
const cachedLogger = loggerInstances.get(location);
|
|
506
|
+
|
|
507
|
+
// Check if config has advanced features (sampling, rateLimit, deduplication)
|
|
508
|
+
// If so, we need to recreate the logger to apply these features
|
|
509
|
+
const hasAdvancedFeatures = (
|
|
510
|
+
(config.sampling && Object.keys(config.sampling).length > 0) ||
|
|
511
|
+
(config.rateLimit && Object.keys(config.rateLimit).length > 0) ||
|
|
512
|
+
(config.deduplication && config.deduplication.enabled === true)
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
// If advanced features are requested but logger was cached without them, recreate it
|
|
516
|
+
if (hasAdvancedFeatures) {
|
|
517
|
+
// Check if cached logger has these features by checking if getStats returns non-null values
|
|
518
|
+
const stats = cachedLogger.getStats ? cachedLogger.getStats() : null;
|
|
519
|
+
const cachedHasFeatures = stats && (
|
|
520
|
+
(stats.sampling !== null) ||
|
|
521
|
+
(stats.deduplication !== null)
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
// If cached logger doesn't have the features but config requests them, recreate
|
|
525
|
+
if (!cachedHasFeatures) {
|
|
526
|
+
// Remove from cache and recreate with new config
|
|
527
|
+
loggerInstances.delete(location);
|
|
528
|
+
} else {
|
|
529
|
+
// Cached logger has features, return it
|
|
530
|
+
cachedLogger.internal(`Logger instance retrieved for location ${location}`);
|
|
531
|
+
return cachedLogger;
|
|
532
|
+
}
|
|
533
|
+
} else {
|
|
534
|
+
// No advanced features requested, return cached logger
|
|
535
|
+
cachedLogger.internal(`Logger instance retrieved for location ${location}`);
|
|
536
|
+
return cachedLogger;
|
|
537
|
+
}
|
|
497
538
|
}
|
|
498
539
|
|
|
499
540
|
let transports = null;
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ntlogger",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "A Custom Ready-To-Go Logging Wrapper Built on Winston",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"custom logger",
|
|
7
7
|
"discord compatible",
|
|
8
|
+
"fastify logger",
|
|
8
9
|
"log management",
|
|
9
10
|
"logger",
|
|
10
11
|
"logger wrapper",
|
|
11
12
|
"logging",
|
|
12
13
|
"logging utility",
|
|
13
14
|
"mysql compatible",
|
|
15
|
+
"pino transport",
|
|
14
16
|
"sentry compatible",
|
|
15
17
|
"syslog compatible",
|
|
16
18
|
"teams compatible",
|
|
@@ -24,11 +26,17 @@
|
|
|
24
26
|
"type": "git",
|
|
25
27
|
"url": "git+https://github.com/NightSquawk/NightTimeLogger.git"
|
|
26
28
|
},
|
|
29
|
+
"exports": {
|
|
30
|
+
".": "./index.js",
|
|
31
|
+
"./pino": "./pino.js"
|
|
32
|
+
},
|
|
27
33
|
"files": [
|
|
28
34
|
"index.js",
|
|
29
35
|
"index.d.ts",
|
|
36
|
+
"pino.js",
|
|
30
37
|
"lib/*.js",
|
|
31
|
-
"plugins/**/*"
|
|
38
|
+
"plugins/**/*",
|
|
39
|
+
"transports/"
|
|
32
40
|
],
|
|
33
41
|
"scripts": {
|
|
34
42
|
"test": "jest"
|
|
@@ -40,6 +48,17 @@
|
|
|
40
48
|
"winston": "^3.18.3",
|
|
41
49
|
"winston-transport": "^4.9.0"
|
|
42
50
|
},
|
|
51
|
+
"optionalDependencies": {
|
|
52
|
+
"pino-abstract-transport": "^2.0.0"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"pino": ">=8.0.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"pino": {
|
|
59
|
+
"optional": true
|
|
60
|
+
}
|
|
61
|
+
},
|
|
43
62
|
"devDependencies": {
|
|
44
63
|
"@sentry/profiling-node": "^10.25.0",
|
|
45
64
|
"dotenv": "^17.2.3",
|
package/pino.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Re-export the transport for use with Pino's transport option
|
|
4
|
+
// Usage: transport: { target: 'ntlogger/pino' }
|
|
5
|
+
module.exports = require('./transports/pino');
|
|
6
|
+
|
|
7
|
+
// Helper for creating a pre-configured Pino instance
|
|
8
|
+
module.exports.createLogger = function (opts = {}) {
|
|
9
|
+
const pino = require('pino');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
13
|
+
const level = opts.level || process.env.LOG_LEVEL || (isDev ? 'debug' : 'info');
|
|
14
|
+
|
|
15
|
+
const loggerOpts = {
|
|
16
|
+
level,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Add module name as a mixin if provided
|
|
20
|
+
if (opts.module) {
|
|
21
|
+
loggerOpts.mixin = () => ({ module: opts.module });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// In dev: use NTL formatter. In production: plain JSON.
|
|
25
|
+
if (isDev) {
|
|
26
|
+
loggerOpts.transport = {
|
|
27
|
+
target: path.join(__dirname, 'transports', 'pino.js'),
|
|
28
|
+
options: {
|
|
29
|
+
defaultModule: opts.module || opts.defaultModule,
|
|
30
|
+
colorize: opts.colorize,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return pino(loggerOpts);
|
|
36
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const build = require('pino-abstract-transport');
|
|
4
|
+
const colors = require('../lib/colors');
|
|
5
|
+
|
|
6
|
+
const levelColors = colors.console;
|
|
7
|
+
|
|
8
|
+
// Utility ANSI codes not exported by lib/colors.js
|
|
9
|
+
const RESET = '\x1b[0m';
|
|
10
|
+
const DIM = '\x1b[2m';
|
|
11
|
+
const GREY = '\x1b[90m';
|
|
12
|
+
const GREEN = '\x1b[32m';
|
|
13
|
+
const MAGENTA = '\x1b[35m';
|
|
14
|
+
const CYAN = '\x1b[36m';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Map Pino numeric levels to NTL level names and colors.
|
|
18
|
+
*
|
|
19
|
+
* Pino levels: 10=trace, 20=debug, 30=info, 40=warn, 50=error, 60=fatal
|
|
20
|
+
* NTL levels: trace(5), debug(4), info(3), warn(2), error(1), fatal(0)
|
|
21
|
+
*
|
|
22
|
+
* We only need the display names and colors here, not NTL's numeric ordering.
|
|
23
|
+
*/
|
|
24
|
+
const PINO_LEVEL_MAP = {
|
|
25
|
+
10: { name: 'trace', color: levelColors.trace },
|
|
26
|
+
20: { name: 'debug', color: levelColors.debug },
|
|
27
|
+
30: { name: 'info', color: levelColors.info },
|
|
28
|
+
40: { name: 'warn', color: levelColors.warn },
|
|
29
|
+
50: { name: 'error', color: levelColors.error },
|
|
30
|
+
60: { name: 'fatal', color: levelColors.fatal },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Status code colors
|
|
34
|
+
const STATUS_COLORS = {
|
|
35
|
+
2: GREEN,
|
|
36
|
+
3: CYAN,
|
|
37
|
+
4: '\x1b[33m',
|
|
38
|
+
5: '\x1b[31m',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Format a timestamp to YYYY-MM-DD HH:mm:ss (NTL style).
|
|
43
|
+
*/
|
|
44
|
+
function formatTimestamp(epoch) {
|
|
45
|
+
const d = new Date(epoch);
|
|
46
|
+
const Y = d.getFullYear();
|
|
47
|
+
const M = String(d.getMonth() + 1).padStart(2, '0');
|
|
48
|
+
const D = String(d.getDate()).padStart(2, '0');
|
|
49
|
+
const h = String(d.getHours()).padStart(2, '0');
|
|
50
|
+
const m = String(d.getMinutes()).padStart(2, '0');
|
|
51
|
+
const s = String(d.getSeconds()).padStart(2, '0');
|
|
52
|
+
return `${Y}-${M}-${D} ${h}:${m}:${s}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Pad level name to 8 chars (NTL style: [info ], [error ], [warn ]).
|
|
57
|
+
*/
|
|
58
|
+
function padLevel(name) {
|
|
59
|
+
return name.padEnd(8);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Format a single Pino log object into an NTL-style string.
|
|
64
|
+
*
|
|
65
|
+
* @param {Object} obj - Pino JSON log object
|
|
66
|
+
* @param {Object} opts - Transport options
|
|
67
|
+
* @returns {string} Formatted log line
|
|
68
|
+
*/
|
|
69
|
+
function formatLine(obj, opts = {}) {
|
|
70
|
+
const parts = [];
|
|
71
|
+
|
|
72
|
+
// 1. Timestamp
|
|
73
|
+
const ts = formatTimestamp(obj.time);
|
|
74
|
+
parts.push(`${GREY}${ts}${RESET}`);
|
|
75
|
+
|
|
76
|
+
// 2. Level — [info ] padded and colored
|
|
77
|
+
const levelInfo = PINO_LEVEL_MAP[obj.level] || { name: 'unknown', color: '' };
|
|
78
|
+
parts.push(`${levelInfo.color}[${padLevel(levelInfo.name)}]${RESET}`);
|
|
79
|
+
|
|
80
|
+
// 3. Request/Session ID — [ID: xxx]
|
|
81
|
+
const id = obj.reqId || obj.requestId || obj.sessionId || null;
|
|
82
|
+
if (id) {
|
|
83
|
+
parts.push(`${MAGENTA}[ID: ${id}]${RESET}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 4. Location/Module label — [ModuleName]
|
|
87
|
+
const location = obj.module || obj.location || obj.name || opts.defaultModule || null;
|
|
88
|
+
if (location) {
|
|
89
|
+
parts.push(`${GREEN}[${location}]${RESET}:`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 5. Message
|
|
93
|
+
let msg = obj.msg || obj.message || '';
|
|
94
|
+
|
|
95
|
+
// 6. HTTP request completion — format as: METHOD /path — STATUS (TIMEms)
|
|
96
|
+
if (obj.res && obj.responseTime !== undefined) {
|
|
97
|
+
const status = obj.res.statusCode;
|
|
98
|
+
const statusGroup = Math.floor(status / 100);
|
|
99
|
+
const statusColor = STATUS_COLORS[statusGroup] || '';
|
|
100
|
+
const method = (obj.req && obj.req.method) || '';
|
|
101
|
+
const url = (obj.req && obj.req.url) || '';
|
|
102
|
+
const time = typeof obj.responseTime === 'number'
|
|
103
|
+
? obj.responseTime.toFixed(1)
|
|
104
|
+
: obj.responseTime;
|
|
105
|
+
|
|
106
|
+
if (method && url) {
|
|
107
|
+
msg = `${method} ${url} — ${statusColor}${status}${RESET} ${GREY}(${time}ms)${RESET}`;
|
|
108
|
+
} else if (msg) {
|
|
109
|
+
msg = `${msg} — ${statusColor}${status}${RESET} ${GREY}(${time}ms)${RESET}`;
|
|
110
|
+
} else {
|
|
111
|
+
msg = `${statusColor}${status}${RESET} ${GREY}(${time}ms)${RESET}`;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 7. Error stack traces
|
|
116
|
+
if (obj.err) {
|
|
117
|
+
const stack = obj.err.stack || obj.err.message || '';
|
|
118
|
+
if (stack) {
|
|
119
|
+
msg += `\n${levelInfo.color}${stack}${RESET}`;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
parts.push(msg);
|
|
124
|
+
|
|
125
|
+
return parts.join(' ');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* NightTimeLogger Pino Transport.
|
|
130
|
+
*
|
|
131
|
+
* Usage with Fastify:
|
|
132
|
+
* const app = Fastify({
|
|
133
|
+
* logger: {
|
|
134
|
+
* transport: {
|
|
135
|
+
* target: 'ntlogger/pino',
|
|
136
|
+
* options: { defaultModule: 'API' }
|
|
137
|
+
* }
|
|
138
|
+
* }
|
|
139
|
+
* });
|
|
140
|
+
*
|
|
141
|
+
* Usage with plain Pino:
|
|
142
|
+
* const pino = require('pino');
|
|
143
|
+
* const logger = pino({
|
|
144
|
+
* transport: {
|
|
145
|
+
* target: 'ntlogger/pino',
|
|
146
|
+
* options: { defaultModule: 'MyApp' }
|
|
147
|
+
* }
|
|
148
|
+
* });
|
|
149
|
+
*
|
|
150
|
+
* Options:
|
|
151
|
+
* - defaultModule: Default location label when obj.module is not set (e.g., 'API')
|
|
152
|
+
*
|
|
153
|
+
* @param {Object} opts - Transport options
|
|
154
|
+
*/
|
|
155
|
+
module.exports = function (opts = {}) {
|
|
156
|
+
return build(async function (source) {
|
|
157
|
+
for await (const obj of source) {
|
|
158
|
+
const line = formatLine(obj, opts);
|
|
159
|
+
process.stdout.write(line + '\n');
|
|
160
|
+
}
|
|
161
|
+
}, {
|
|
162
|
+
parse: 'lines',
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
// Export internals for testing
|
|
167
|
+
module.exports.formatLine = formatLine;
|
|
168
|
+
module.exports.formatTimestamp = formatTimestamp;
|
|
169
|
+
module.exports.padLevel = padLevel;
|