ntlogger 2.10.0 → 4.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 +465 -5
- package/contract/conformance.json +133 -0
- package/contract/redaction.json +115 -0
- package/eslint-rules/prefer-object-first.js +53 -0
- package/eslint.d.ts +3 -0
- package/eslint.js +5 -0
- package/index.d.ts +202 -183
- package/index.js +18 -1
- package/lib/lifecycle.js +53 -0
- package/lib/logObserver.js +55 -0
- package/lib/logger.js +115 -55
- package/lib/pinoHooks.js +446 -0
- package/lib/pinoOtel.js +43 -0
- package/lib/processHandlers.js +305 -0
- package/lib/secretRedaction.js +684 -0
- package/lib/secretScope.js +24 -0
- package/lib/signalHandler.js +31 -48
- package/package.json +79 -26
- package/pino.d.ts +311 -0
- package/pino.js +535 -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 +193 -12
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 1,
|
|
3
|
+
"keys": [
|
|
4
|
+
"password",
|
|
5
|
+
"passwd",
|
|
6
|
+
"pass",
|
|
7
|
+
"pwd",
|
|
8
|
+
"secret",
|
|
9
|
+
"token",
|
|
10
|
+
"apikey",
|
|
11
|
+
"api_key",
|
|
12
|
+
"x-api-key",
|
|
13
|
+
"authorization",
|
|
14
|
+
"proxy-authorization",
|
|
15
|
+
"cookie",
|
|
16
|
+
"set-cookie",
|
|
17
|
+
"x-device-installation-secret",
|
|
18
|
+
"installationsecret",
|
|
19
|
+
"privatekey",
|
|
20
|
+
"private_key",
|
|
21
|
+
"clientsecret",
|
|
22
|
+
"client_secret",
|
|
23
|
+
"accesstoken",
|
|
24
|
+
"access_token",
|
|
25
|
+
"refreshtoken",
|
|
26
|
+
"refresh_token",
|
|
27
|
+
"idtoken",
|
|
28
|
+
"id_token",
|
|
29
|
+
"sessiontoken",
|
|
30
|
+
"credential",
|
|
31
|
+
"credentials",
|
|
32
|
+
"webhookurl",
|
|
33
|
+
"snmpcommunity",
|
|
34
|
+
"community_string",
|
|
35
|
+
"community",
|
|
36
|
+
"authPassword",
|
|
37
|
+
"privPassword",
|
|
38
|
+
"passphrase"
|
|
39
|
+
],
|
|
40
|
+
"patterns": [
|
|
41
|
+
{
|
|
42
|
+
"source": "-----BEGIN (?:[A-Z]+ )*PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z]+ )*PRIVATE KEY-----",
|
|
43
|
+
"flags": "g"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"source": "\\bAKIA[0-9A-Z]{16}\\b",
|
|
47
|
+
"flags": "g"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"source": "\\beyJ[A-Za-z0-9_-]{5,}\\.[A-Za-z0-9_-]{5,}\\.[A-Za-z0-9_-]{5,}",
|
|
51
|
+
"flags": "g"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"source": "\\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})",
|
|
55
|
+
"flags": "g"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"source": "\\bxox[abpors]-[A-Za-z0-9-]{10,}",
|
|
59
|
+
"flags": "gi"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"source": "\\b(?:[sr]k_(?:live|test)_[A-Za-z0-9]{10,}|whsec_[A-Za-z0-9]{10,})",
|
|
63
|
+
"flags": "g"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"source": "(?<pre>\\bBearer\\s+)[A-Za-z0-9\\-._~+/]{8,}={0,2}",
|
|
67
|
+
"flags": "gi"
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"source": "(?<pre>\\bBasic\\s+)(?=[A-Za-z0-9+/]*[A-Z0-9])[A-Za-z0-9+/]{16,}={0,2}",
|
|
71
|
+
"flags": "g"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"source": "(?<pre>https?:\\/\\/(?:[a-z]+\\.)?discord(?:app)?\\.com\\/api\\/(?:v\\d+\\/)?webhooks\\/\\d+\\/)[A-Za-z0-9_-]+",
|
|
75
|
+
"flags": "gi"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"source": "(?<pre>[\"']?\\b(?:snmp[_-]?community|community(?:[_-]?string)?|auth[_-]?password|priv[_-]?password|passphrase)[\"']?\\s*[:=]\\s*)(?:\"(?:\\\\.|[^\"\\\\])*(?:\"|$)|'(?:\\\\.|[^'\\\\])*(?:'|$)|[^\\s,;}]+)",
|
|
79
|
+
"flags": "gi"
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"source": "(?<pre>\\b(?:api[_-]?key|apikey|access[_-]?key|access[_-]?token|refresh[_-]?token|secret[_-]?key|client[_-]?secret|password|passwd|pwd|auth[_-]?token|private[_-]?key|token|secret)\\s*[:=]\\s*[\"']?)[^\\s'\",;]{4,}",
|
|
83
|
+
"flags": "gi"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"source": "(?<pre>\\b(?:password|passwd|secret|token|api[_-]?key)\\s*[=:]\\s*)(?![\"'[])\\S+",
|
|
87
|
+
"flags": "gi"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"source": "(?<pre>\\b[a-z][a-z0-9+.-]*:\\/\\/[^\\s:/?#@]*:)[^\\s/?#@]+(?<post>@)",
|
|
91
|
+
"flags": "gi"
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
"pythonLevels": {
|
|
95
|
+
"5": 10,
|
|
96
|
+
"10": 20,
|
|
97
|
+
"20": 30,
|
|
98
|
+
"30": 40,
|
|
99
|
+
"40": 50,
|
|
100
|
+
"50": 60
|
|
101
|
+
},
|
|
102
|
+
"contextAliases": {
|
|
103
|
+
"agent_uuid": "agentUuid",
|
|
104
|
+
"tenant_id": "tenantId",
|
|
105
|
+
"command_id": "commandId",
|
|
106
|
+
"collection_cycle_id": "collectionCycleId",
|
|
107
|
+
"correlation_id": "correlationId",
|
|
108
|
+
"run_id": "runId",
|
|
109
|
+
"work_id": "workId",
|
|
110
|
+
"stage_id": "stageId",
|
|
111
|
+
"device_id": "deviceId",
|
|
112
|
+
"trace_id": "traceId",
|
|
113
|
+
"span_id": "spanId"
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function receiverName(node) {
|
|
4
|
+
if (node.type === 'Identifier') return node.name;
|
|
5
|
+
if (node.type === 'ThisExpression') return 'this';
|
|
6
|
+
if (node.type === 'MemberExpression' && !node.computed) {
|
|
7
|
+
const prefix = receiverName(node.object);
|
|
8
|
+
return prefix && `${prefix}.${node.property.name}`;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
module.exports = {
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'suggestion',
|
|
15
|
+
docs: { description: 'Keep interpolated Pino log fields searchable with an object-first call.' },
|
|
16
|
+
fixable: 'code',
|
|
17
|
+
schema: [{ type: 'object', properties: {
|
|
18
|
+
loggerNames: { type: 'array', items: { type: 'string' }, uniqueItems: true },
|
|
19
|
+
}, additionalProperties: false }],
|
|
20
|
+
messages: { structured: 'Pass searchable fields as the first argument to this logger.' },
|
|
21
|
+
},
|
|
22
|
+
create(context) {
|
|
23
|
+
const names = new Set(context.options[0]?.loggerNames || ['log', 'logger', 'req.log', 'request.log', 'app.log', 'fastify.log']);
|
|
24
|
+
const methods = new Set(['trace', 'debug', 'info', 'warn', 'error', 'fatal']);
|
|
25
|
+
const source = context.sourceCode;
|
|
26
|
+
function constantPrimitive(identifier) {
|
|
27
|
+
let scope = source.getScope(identifier);
|
|
28
|
+
let variable;
|
|
29
|
+
while (scope && !variable) { variable = scope.set.get(identifier.name); scope = scope.upper; }
|
|
30
|
+
const def = variable?.defs[0];
|
|
31
|
+
return variable?.defs.length === 1 && def?.parent?.kind === 'const'
|
|
32
|
+
&& def.node.id.type === 'Identifier' && def.node.init?.type === 'Literal'
|
|
33
|
+
&& !def.node.init.regex && typeof def.node.init.value !== 'object';
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
CallExpression(node) {
|
|
37
|
+
const callee = node.callee;
|
|
38
|
+
if (callee.type !== 'MemberExpression' || callee.computed
|
|
39
|
+
|| !methods.has(callee.property.name) || !names.has(receiverName(callee.object))) return;
|
|
40
|
+
const template = node.arguments[0];
|
|
41
|
+
if (template?.type !== 'TemplateLiteral' || !template.expressions.length) return;
|
|
42
|
+
const canFix = node.arguments.length === 1 && template.expressions.every(expr =>
|
|
43
|
+
expr.type === 'Identifier' && expr.name !== '__proto__' && constantPrimitive(expr));
|
|
44
|
+
context.report({ node: template, messageId: 'structured',
|
|
45
|
+
// Keep the original message and its formatting. Only duplicate reads
|
|
46
|
+
// of known primitive consts; calls/getters/mutable bindings need review.
|
|
47
|
+
fix: canFix ? fixer => fixer.insertTextBefore(template,
|
|
48
|
+
`{ ${[...new Set(template.expressions.map(expr => expr.name))].join(', ')} }, `) : undefined,
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
};
|
package/eslint.d.ts
ADDED
package/eslint.js
ADDED
package/index.d.ts
CHANGED
|
@@ -3,214 +3,227 @@
|
|
|
3
3
|
* @file index.d.ts
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
* Log levels supported by NightTimeLogger
|
|
8
|
-
*/
|
|
9
|
-
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'internal';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Sampling configuration for log levels
|
|
13
|
-
*/
|
|
14
|
-
export interface SamplingConfig {
|
|
15
|
-
[level: string]: number; // 0.0 to 1.0, where 1.0 = log all, 0.1 = log 10%
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Rate limit configuration for a log level
|
|
20
|
-
*/
|
|
21
|
-
export interface RateLimitConfig {
|
|
22
|
-
max: number; // Maximum number of logs
|
|
23
|
-
window: number; // Time window in milliseconds
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Rate limiting configuration for log levels
|
|
28
|
-
*/
|
|
29
|
-
export interface RateLimitConfigMap {
|
|
30
|
-
[level: string]: RateLimitConfig;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Deduplication configuration
|
|
35
|
-
*/
|
|
36
|
-
export interface DeduplicationConfig {
|
|
37
|
-
enabled: boolean;
|
|
38
|
-
threshold: number; // Number of duplicates before squishing (default: 3)
|
|
39
|
-
window: number; // Time window in milliseconds (default: 60000)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Plugin configuration
|
|
44
|
-
*/
|
|
45
|
-
export interface PluginConfig {
|
|
46
|
-
name: string;
|
|
47
|
-
enabled?: boolean;
|
|
48
|
-
config?: Record<string, any>;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Logger configuration options
|
|
53
|
-
*/
|
|
54
|
-
export interface LoggerConfig {
|
|
55
|
-
/** Minimum log level to be logged */
|
|
56
|
-
level?: LogLevel;
|
|
57
|
-
/** Enable console logging (default: true) */
|
|
58
|
-
console?: boolean;
|
|
59
|
-
/** Enable file logging (default: true) */
|
|
60
|
-
file?: boolean;
|
|
61
|
-
/** Filename for log file (default: `${location}.log`) */
|
|
62
|
-
filename?: string;
|
|
63
|
-
/** Directory path where log files will be saved (default: './logs') */
|
|
64
|
-
path?: string;
|
|
65
|
-
/** Maximum size of log file in bytes (default: 1048576 = 1MB) */
|
|
66
|
-
maxSize?: number;
|
|
67
|
-
/** Maximum number of log files to retain (default: 5) */
|
|
68
|
-
maxFiles?: number;
|
|
69
|
-
/** Include timestamp in log messages (default: true) */
|
|
70
|
-
timestamp?: boolean;
|
|
71
|
-
/** Skip cache and create new logger instance (default: false) */
|
|
72
|
-
skipCache?: boolean;
|
|
73
|
-
/** Enable call site path reporting (default: false) */
|
|
74
|
-
reportPath?: boolean;
|
|
75
|
-
/** Enable debug mode for logger itself (default: false) */
|
|
76
|
-
debug?: boolean;
|
|
77
|
-
/** Log sampling rates per level */
|
|
78
|
-
sampling?: SamplingConfig;
|
|
79
|
-
/** Rate limiting configuration per level */
|
|
80
|
-
rateLimit?: RateLimitConfigMap;
|
|
81
|
-
/** Log deduplication configuration */
|
|
82
|
-
deduplication?: DeduplicationConfig;
|
|
83
|
-
/** Enable performance metrics (default: NODE_ENV === 'development') */
|
|
84
|
-
performanceMetrics?: boolean;
|
|
85
|
-
/** Interval in milliseconds to log statistics (default: 0 = disabled) */
|
|
86
|
-
statsInterval?: number;
|
|
87
|
-
/** Plugin configurations */
|
|
88
|
-
plugins?: PluginConfig[];
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Statistics object returned by getStats()
|
|
93
|
-
*/
|
|
94
|
-
export interface LoggerStats {
|
|
95
|
-
sampling: {
|
|
96
|
-
total: Record<string, number>;
|
|
97
|
-
sampled: Record<string, number>;
|
|
98
|
-
rateLimited: Record<string, number>;
|
|
99
|
-
} | null;
|
|
100
|
-
deduplication: {
|
|
101
|
-
totalDeduplicated: number;
|
|
102
|
-
uniqueMessages: number;
|
|
103
|
-
activeEntries: number;
|
|
104
|
-
} | null;
|
|
105
|
-
performance: {
|
|
106
|
-
enabled: boolean;
|
|
107
|
-
avgLogProcessingTime?: number;
|
|
108
|
-
avgTransportTime?: number;
|
|
109
|
-
logProcessingSamples?: number;
|
|
110
|
-
transportSamples?: number;
|
|
111
|
-
} | null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Logger instance interface
|
|
116
|
-
*/
|
|
117
|
-
export interface Logger {
|
|
118
|
-
/**
|
|
119
|
-
* Log an informational message
|
|
120
|
-
* @param message - The message to log
|
|
121
|
-
* @param meta - Optional metadata object
|
|
122
|
-
*/
|
|
123
|
-
info(message: string, meta?: Record<string, any>): void;
|
|
124
|
-
info(message: Record<string, any>): void;
|
|
125
|
-
|
|
6
|
+
declare namespace logger {
|
|
126
7
|
/**
|
|
127
|
-
* Log
|
|
128
|
-
* @param message - The message to log
|
|
129
|
-
* @param meta - Optional metadata object
|
|
8
|
+
* Log levels supported by NightTimeLogger
|
|
130
9
|
*/
|
|
131
|
-
warn
|
|
132
|
-
warn(message: Record<string, any>): void;
|
|
10
|
+
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'internal';
|
|
133
11
|
|
|
134
12
|
/**
|
|
135
|
-
*
|
|
136
|
-
* @param message - The message to log
|
|
137
|
-
* @param meta - Optional metadata object
|
|
13
|
+
* Sampling configuration for log levels
|
|
138
14
|
*/
|
|
139
|
-
|
|
140
|
-
|
|
15
|
+
export interface SamplingConfig {
|
|
16
|
+
[level: string]: number; // 0.0 to 1.0, where 1.0 = log all, 0.1 = log 10%
|
|
17
|
+
}
|
|
141
18
|
|
|
142
19
|
/**
|
|
143
|
-
*
|
|
144
|
-
* @param message - The message to log
|
|
145
|
-
* @param meta - Optional metadata object
|
|
20
|
+
* Rate limit configuration for a log level
|
|
146
21
|
*/
|
|
147
|
-
|
|
148
|
-
|
|
22
|
+
export interface RateLimitConfig {
|
|
23
|
+
max: number; // Maximum number of logs
|
|
24
|
+
window: number; // Time window in milliseconds
|
|
25
|
+
}
|
|
149
26
|
|
|
150
27
|
/**
|
|
151
|
-
*
|
|
152
|
-
* @param message - The message to log
|
|
153
|
-
* @param meta - Optional metadata object
|
|
28
|
+
* Rate limiting configuration for log levels
|
|
154
29
|
*/
|
|
155
|
-
|
|
156
|
-
|
|
30
|
+
export interface RateLimitConfigMap {
|
|
31
|
+
[level: string]: RateLimitConfig;
|
|
32
|
+
}
|
|
157
33
|
|
|
158
34
|
/**
|
|
159
|
-
*
|
|
160
|
-
* @param message - The message to log
|
|
161
|
-
* @param meta - Optional metadata object
|
|
35
|
+
* Deduplication configuration
|
|
162
36
|
*/
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
* @param message - The message to log
|
|
169
|
-
* @param meta - Optional metadata object
|
|
170
|
-
*/
|
|
171
|
-
internal(message: string, meta?: Record<string, any>): void;
|
|
172
|
-
internal(message: Record<string, any>): void;
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Create a child logger with persistent context
|
|
176
|
-
* @param context - Context object to merge into all logs
|
|
177
|
-
* @returns Child logger instance
|
|
178
|
-
*/
|
|
179
|
-
child(context: Record<string, any>): Logger;
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Start a performance timer (development mode only)
|
|
183
|
-
* @param label - Timer label
|
|
184
|
-
*/
|
|
185
|
-
time?(label: string): void;
|
|
37
|
+
export interface DeduplicationConfig {
|
|
38
|
+
enabled: boolean;
|
|
39
|
+
threshold: number; // Number of duplicates before squishing (default: 3)
|
|
40
|
+
window: number; // Time window in milliseconds (default: 60000)
|
|
41
|
+
}
|
|
186
42
|
|
|
187
43
|
/**
|
|
188
|
-
*
|
|
189
|
-
* @param label - Timer label
|
|
190
|
-
* @returns Duration in milliseconds or null if timer not found
|
|
44
|
+
* Plugin configuration
|
|
191
45
|
*/
|
|
192
|
-
|
|
46
|
+
export interface PluginConfig {
|
|
47
|
+
name: string;
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
config?: Record<string, any>;
|
|
50
|
+
}
|
|
193
51
|
|
|
194
52
|
/**
|
|
195
|
-
*
|
|
196
|
-
* @returns Statistics object
|
|
53
|
+
* Logger configuration options
|
|
197
54
|
*/
|
|
198
|
-
|
|
55
|
+
export interface LoggerConfig {
|
|
56
|
+
/** Maximum in-flight callback promises; overflow drops newest callback, not the primary log. */
|
|
57
|
+
onLogMaxPending?: number;
|
|
58
|
+
onLog?: (record: Record<string, any>) => void | Promise<void>;
|
|
59
|
+
otel?: boolean | { loggerProvider?: { getLogger(name: string): { emit(record: any): void }; forceFlush?(): Promise<void> }; name?: string };
|
|
60
|
+
redact?: false | { extraKeys?: string[]; patterns?: boolean; replacement?: string; maxDepth?: number; maxStringLength?: number };
|
|
61
|
+
/** Minimum log level to be logged */
|
|
62
|
+
level?: LogLevel;
|
|
63
|
+
/** Enable console logging (default: true) */
|
|
64
|
+
console?: boolean;
|
|
65
|
+
/** Enable file logging (default: true) */
|
|
66
|
+
file?: boolean;
|
|
67
|
+
/** Filename for combined log file (default: combined.log) */
|
|
68
|
+
filename?: string;
|
|
69
|
+
/** Directory path where log files will be saved (default: './logs') */
|
|
70
|
+
path?: string;
|
|
71
|
+
/** Maximum size of log file in bytes (default: 1048576 = 1MB) */
|
|
72
|
+
maxSize?: number;
|
|
73
|
+
/** Maximum number of log files to retain (default: 5) */
|
|
74
|
+
maxFiles?: number;
|
|
75
|
+
/** Include timestamp in log messages (default: true) */
|
|
76
|
+
timestamp?: boolean;
|
|
77
|
+
/** Skip cache and create new logger instance (default: false) */
|
|
78
|
+
skipCache?: boolean;
|
|
79
|
+
/** Enable call site path reporting (default: false) */
|
|
80
|
+
reportPath?: boolean;
|
|
81
|
+
/** Enable debug mode for logger itself (default: false) */
|
|
82
|
+
debug?: boolean;
|
|
83
|
+
/** Log sampling rates per level */
|
|
84
|
+
sampling?: SamplingConfig;
|
|
85
|
+
/** Rate limiting configuration per level */
|
|
86
|
+
rateLimit?: RateLimitConfigMap;
|
|
87
|
+
/** Log deduplication configuration */
|
|
88
|
+
deduplication?: DeduplicationConfig;
|
|
89
|
+
/** Enable performance metrics (default: NODE_ENV === 'development') */
|
|
90
|
+
performanceMetrics?: boolean;
|
|
91
|
+
/** Interval in milliseconds to log statistics (default: 0 = disabled) */
|
|
92
|
+
statsInterval?: number;
|
|
93
|
+
/** Maximum flush/stream shutdown wait in milliseconds (default: 30000). */
|
|
94
|
+
shutdownTimeout?: number;
|
|
95
|
+
/** Plugin configurations */
|
|
96
|
+
plugins?: PluginConfig[];
|
|
97
|
+
}
|
|
199
98
|
|
|
200
99
|
/**
|
|
201
|
-
*
|
|
202
|
-
* @returns Promise that resolves when all logs are flushed
|
|
100
|
+
* Statistics object returned by getStats()
|
|
203
101
|
*/
|
|
204
|
-
|
|
102
|
+
export interface LoggerStats {
|
|
103
|
+
levels: Record<string, number>;
|
|
104
|
+
hookErrors: number;
|
|
105
|
+
onLog: { pending: number; dropped: number; limit: number };
|
|
106
|
+
sampling: {
|
|
107
|
+
total: Record<string, number>;
|
|
108
|
+
sampled: Record<string, number>;
|
|
109
|
+
rateLimited: Record<string, number>;
|
|
110
|
+
} | null;
|
|
111
|
+
deduplication: {
|
|
112
|
+
totalDeduplicated: number;
|
|
113
|
+
uniqueMessages: number;
|
|
114
|
+
activeEntries: number;
|
|
115
|
+
} | null;
|
|
116
|
+
performance: {
|
|
117
|
+
enabled: boolean;
|
|
118
|
+
avgLogProcessingTime?: number;
|
|
119
|
+
avgTransportTime?: number;
|
|
120
|
+
logProcessingSamples?: number;
|
|
121
|
+
transportSamples?: number;
|
|
122
|
+
} | null;
|
|
123
|
+
}
|
|
205
124
|
|
|
206
125
|
/**
|
|
207
|
-
*
|
|
208
|
-
* @returns Promise that resolves when logger is closed
|
|
126
|
+
* Logger instance interface
|
|
209
127
|
*/
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
128
|
+
export interface Logger {
|
|
129
|
+
/**
|
|
130
|
+
* Log an informational message
|
|
131
|
+
* @param message - The message to log
|
|
132
|
+
* @param meta - Optional metadata object
|
|
133
|
+
*/
|
|
134
|
+
info(message: string, meta?: Record<string, any>): void;
|
|
135
|
+
info(message: Record<string, any>): void;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Log a warning message
|
|
139
|
+
* @param message - The message to log
|
|
140
|
+
* @param meta - Optional metadata object
|
|
141
|
+
*/
|
|
142
|
+
warn(message: string, meta?: Record<string, any>): void;
|
|
143
|
+
warn(message: Record<string, any>): void;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Log an error message
|
|
147
|
+
* @param message - The message to log
|
|
148
|
+
* @param meta - Optional metadata object
|
|
149
|
+
*/
|
|
150
|
+
error(message: string, meta?: Record<string, any>): void;
|
|
151
|
+
error(message: Record<string, any>): void;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Log a debug message
|
|
155
|
+
* @param message - The message to log
|
|
156
|
+
* @param meta - Optional metadata object
|
|
157
|
+
*/
|
|
158
|
+
debug(message: string, meta?: Record<string, any>): void;
|
|
159
|
+
debug(message: Record<string, any>): void;
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Log a trace message
|
|
163
|
+
* @param message - The message to log
|
|
164
|
+
* @param meta - Optional metadata object
|
|
165
|
+
*/
|
|
166
|
+
trace(message: string, meta?: Record<string, any>): void;
|
|
167
|
+
trace(message: Record<string, any>): void;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Log a fatal message
|
|
171
|
+
* @param message - The message to log
|
|
172
|
+
* @param meta - Optional metadata object
|
|
173
|
+
*/
|
|
174
|
+
fatal(message: string, meta?: Record<string, any>): void;
|
|
175
|
+
fatal(message: Record<string, any>): void;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Log an internal message (logger system messages)
|
|
179
|
+
* @param message - The message to log
|
|
180
|
+
* @param meta - Optional metadata object
|
|
181
|
+
*/
|
|
182
|
+
internal(message: string, meta?: Record<string, any>): void;
|
|
183
|
+
internal(message: Record<string, any>): void;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a child logger with persistent context
|
|
187
|
+
* @param context - Context object to merge into all logs
|
|
188
|
+
* @returns Child logger instance
|
|
189
|
+
*/
|
|
190
|
+
child(context: Record<string, any>): Logger;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Start a performance timer (development mode only)
|
|
194
|
+
* @param label - Timer label
|
|
195
|
+
*/
|
|
196
|
+
time?(label: string): void;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* End a performance timer and log duration (development mode only)
|
|
200
|
+
* @param label - Timer label
|
|
201
|
+
* @returns Duration in milliseconds or null if timer not found
|
|
202
|
+
*/
|
|
203
|
+
timeEnd?(label: string): number | null;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get logging statistics
|
|
207
|
+
* @returns Statistics object
|
|
208
|
+
*/
|
|
209
|
+
getStats(): LoggerStats;
|
|
210
|
+
resetStats(): void;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Flush all pending logs (returns Promise)
|
|
214
|
+
* @returns Promise that resolves when all logs are flushed
|
|
215
|
+
*/
|
|
216
|
+
flush(): Promise<void>;
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Close the logger and flush all pending logs (returns Promise)
|
|
220
|
+
* @returns Promise that resolves when logger is closed
|
|
221
|
+
*/
|
|
222
|
+
close(): Promise<void>;
|
|
223
|
+
|
|
224
|
+
/** Current log level */
|
|
225
|
+
level: string;
|
|
226
|
+
}
|
|
214
227
|
}
|
|
215
228
|
|
|
216
229
|
/**
|
|
@@ -219,7 +232,13 @@ export interface Logger {
|
|
|
219
232
|
* @param config - Configuration options for the logger
|
|
220
233
|
* @returns Logger instance
|
|
221
234
|
*/
|
|
222
|
-
declare function logger(location?: string, config?: LoggerConfig): Logger;
|
|
235
|
+
declare function logger(location?: string, config?: logger.LoggerConfig): logger.Logger;
|
|
236
|
+
|
|
237
|
+
declare namespace logger {
|
|
238
|
+
/** Opt in to process-wide signal handlers; returns an unregister function. */
|
|
239
|
+
function withSecretValues<T>(values: string[], callback: () => T): T;
|
|
240
|
+
function setupSignalHandlers(options?: { timeout?: number }): () => void;
|
|
241
|
+
}
|
|
223
242
|
|
|
224
243
|
export = logger;
|
|
225
244
|
|
package/index.js
CHANGED
|
@@ -114,4 +114,21 @@
|
|
|
114
114
|
* Initial release
|
|
115
115
|
*/
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
// Only the Winston entry point resolves these peers. Pino consumers never load it.
|
|
118
|
+
for (const peer of ['winston', 'winston-transport']) {
|
|
119
|
+
try {
|
|
120
|
+
require.resolve(peer);
|
|
121
|
+
} catch (cause) {
|
|
122
|
+
if (cause.code !== 'MODULE_NOT_FOUND') throw cause;
|
|
123
|
+
const error = new Error(
|
|
124
|
+
`ntlogger's Winston backend requires ${peer}. Run: npm install winston winston-transport. ` +
|
|
125
|
+
`For Pino, import 'ntlogger/pino' instead.`,
|
|
126
|
+
{ cause },
|
|
127
|
+
);
|
|
128
|
+
error.code = 'MODULE_NOT_FOUND';
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = require('./lib/logger');
|
|
134
|
+
module.exports.withSecretValues = require('./lib/secretScope').withSecretValues;
|
package/lib/lifecycle.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const delay = () => new Promise(resolve => setTimeout(resolve, 1));
|
|
4
|
+
|
|
5
|
+
function withTimeout(operation, timeout, label) {
|
|
6
|
+
let timer;
|
|
7
|
+
return Promise.race([
|
|
8
|
+
operation,
|
|
9
|
+
new Promise((_, reject) => {
|
|
10
|
+
timer = setTimeout(() => reject(new Error(`${label} timed out after ${timeout}ms`)), timeout);
|
|
11
|
+
}),
|
|
12
|
+
]).finally(() => clearTimeout(timer));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Winston uses readable-stream, so inspect both its buffers and file sinks.
|
|
16
|
+
function busy(stream) {
|
|
17
|
+
return !!(stream && (stream._writableState?.length || stream._readableState?.length));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function drain(logger, state, timeout = 5000) {
|
|
21
|
+
const deadline = Date.now() + timeout;
|
|
22
|
+
while (state.pending || busy(logger) || logger.transports.some(t =>
|
|
23
|
+
busy(t) || busy(t._stream) || busy(t._dest) || t._opening || t._rotate)) {
|
|
24
|
+
if (state.error) throw state.error;
|
|
25
|
+
if (Date.now() >= deadline) throw new Error('Logger drain timed out');
|
|
26
|
+
await delay();
|
|
27
|
+
}
|
|
28
|
+
if (state.error) throw state.error;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Winston calls close on unpipe but doesn't await the result. Retain that promise.
|
|
32
|
+
function trackClose(transport) {
|
|
33
|
+
if (transport._ntlClose) return;
|
|
34
|
+
const close = transport.close?.bind(transport);
|
|
35
|
+
let promise;
|
|
36
|
+
transport._ntlClose = () => {
|
|
37
|
+
if (!promise) {
|
|
38
|
+
// File._final already ended and drained this stream before unpipe.
|
|
39
|
+
// Calling File.close(callback) on that finished stream never calls back.
|
|
40
|
+
if (transport instanceof require('winston').transports.File && transport._stream?._writableState.finished) {
|
|
41
|
+
return (promise = Promise.resolve());
|
|
42
|
+
}
|
|
43
|
+
promise = close && close.length > 0
|
|
44
|
+
? new Promise((resolve, reject) => close(err => err ? reject(err) : resolve()))
|
|
45
|
+
: Promise.resolve().then(() => close?.());
|
|
46
|
+
promise.catch(() => {}); // unpipe cannot consume a rejected promise
|
|
47
|
+
}
|
|
48
|
+
return promise;
|
|
49
|
+
};
|
|
50
|
+
transport.close = transport._ntlClose;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { withTimeout, drain, trackClose };
|