@rawnodes/logger 1.6.0 → 1.8.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 +425 -147
- package/dist/index.d.mts +151 -3
- package/dist/index.d.ts +151 -3
- package/dist/index.js +538 -25
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +532 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# @rawnodes/logger
|
|
2
2
|
|
|
3
|
-
Flexible Winston-based logger with AsyncLocalStorage context, level
|
|
3
|
+
Flexible Winston-based logger with AsyncLocalStorage context, level rules, and multiple output formats.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
+
- **Multiple Formats** - JSON, plain (colored), logfmt, simple
|
|
7
8
|
- **Context Propagation** - Automatic context via AsyncLocalStorage
|
|
8
|
-
- **Level
|
|
9
|
+
- **Level Rules** - Configure log levels per module/context in config
|
|
9
10
|
- **Lazy Meta** - Defer metadata creation until log level check passes
|
|
10
11
|
- **Timing Utilities** - Built-in performance measurement
|
|
11
12
|
- **Request ID** - Generate and extract request IDs
|
|
12
|
-
- **Secret Masking** -
|
|
13
|
-
- **Singleton Factory** - Easy setup with `createSingletonLogger()`
|
|
13
|
+
- **Secret Masking** - Automatic masking of sensitive data
|
|
14
14
|
- **TypeScript First** - Full generic type support
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
@@ -23,241 +23,519 @@ npm install @rawnodes/logger
|
|
|
23
23
|
|
|
24
24
|
## Quick Start
|
|
25
25
|
|
|
26
|
-
### 1. Create your app logger
|
|
27
|
-
|
|
28
26
|
```typescript
|
|
29
|
-
|
|
30
|
-
import { createSingletonLogger, type LoggerContext } from '@rawnodes/logger';
|
|
27
|
+
import { Logger } from '@rawnodes/logger';
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
29
|
+
const logger = Logger.create({
|
|
30
|
+
level: 'info',
|
|
31
|
+
console: { format: 'plain' },
|
|
32
|
+
});
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
logger.info('Hello world');
|
|
35
|
+
logger.info('User logged in', { userId: 123 });
|
|
38
36
|
```
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
### Basic Config
|
|
41
41
|
|
|
42
42
|
```typescript
|
|
43
|
-
|
|
44
|
-
import { AppLogger } from './logger/app.logger.js';
|
|
43
|
+
import { Logger } from '@rawnodes/logger';
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
level: 'info',
|
|
48
|
-
console: {
|
|
49
|
-
file: {
|
|
45
|
+
const logger = Logger.create({
|
|
46
|
+
level: 'info', // default log level
|
|
47
|
+
console: { format: 'plain' }, // console output format
|
|
48
|
+
file: { // optional file output
|
|
49
|
+
format: 'json',
|
|
50
50
|
dirname: 'logs',
|
|
51
51
|
filename: 'app-%DATE%.log',
|
|
52
|
-
level: 'info',
|
|
53
52
|
datePattern: 'YYYY-MM-DD',
|
|
54
53
|
maxFiles: '14d',
|
|
54
|
+
maxSize: '20m',
|
|
55
55
|
},
|
|
56
56
|
});
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
###
|
|
59
|
+
### Level Rules
|
|
60
|
+
|
|
61
|
+
Configure different log levels for specific modules or contexts:
|
|
60
62
|
|
|
61
63
|
```typescript
|
|
62
|
-
|
|
64
|
+
const logger = Logger.create({
|
|
65
|
+
level: {
|
|
66
|
+
default: 'info',
|
|
67
|
+
rules: [
|
|
68
|
+
{ match: { context: 'auth' }, level: 'debug' }, // debug for auth module
|
|
69
|
+
{ match: { context: 'database' }, level: 'warn' }, // warn for database
|
|
70
|
+
{ match: { userId: 123 }, level: 'debug' }, // debug for user 123
|
|
71
|
+
{ match: { context: 'api', userId: 456 }, level: 'silly' }, // combined match
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
console: { format: 'plain' },
|
|
75
|
+
});
|
|
63
76
|
|
|
64
|
-
const
|
|
77
|
+
const authLogger = logger.for('auth');
|
|
78
|
+
authLogger.debug('This will be logged'); // matches rule
|
|
65
79
|
|
|
66
|
-
logger.
|
|
67
|
-
|
|
80
|
+
const dbLogger = logger.for('database');
|
|
81
|
+
dbLogger.info('This will NOT be logged'); // level is warn
|
|
68
82
|
```
|
|
69
83
|
|
|
70
|
-
|
|
84
|
+
Rules from config are **readonly** and cannot be removed via API.
|
|
85
|
+
|
|
86
|
+
### Output Formats
|
|
87
|
+
|
|
88
|
+
| Format | Description | Example |
|
|
89
|
+
|--------|-------------|---------|
|
|
90
|
+
| `json` | Structured JSON | `{"level":"info","message":"hello","timestamp":"..."}` |
|
|
91
|
+
| `plain` | Colored, human-readable | `[2025-01-01T12:00:00] info [APP] hello` |
|
|
92
|
+
| `logfmt` | Key=value pairs | `level=info msg=hello context=APP ts=2025-01-01T12:00:00` |
|
|
93
|
+
| `simple` | Minimal | `[2025-01-01T12:00:00] info: hello` |
|
|
71
94
|
|
|
72
95
|
```typescript
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
// Different formats for console and file
|
|
97
|
+
const logger = Logger.create({
|
|
98
|
+
level: 'info',
|
|
99
|
+
console: { format: 'plain' }, // colored for development
|
|
100
|
+
file: {
|
|
101
|
+
format: 'json', // structured for log aggregation
|
|
102
|
+
dirname: 'logs',
|
|
103
|
+
filename: 'app-%DATE%.log',
|
|
104
|
+
},
|
|
80
105
|
});
|
|
81
106
|
```
|
|
82
107
|
|
|
83
|
-
|
|
108
|
+
### Per-Transport Level
|
|
109
|
+
|
|
110
|
+
Each transport can have its own log level:
|
|
84
111
|
|
|
85
112
|
```typescript
|
|
86
|
-
|
|
87
|
-
level:
|
|
113
|
+
const logger = Logger.create({
|
|
114
|
+
level: 'silly', // accept all at logger level
|
|
115
|
+
console: {
|
|
116
|
+
format: 'plain',
|
|
117
|
+
level: 'debug', // console: debug and above
|
|
118
|
+
},
|
|
119
|
+
file: {
|
|
120
|
+
format: 'json',
|
|
121
|
+
level: 'warn', // file: only warn and error
|
|
122
|
+
dirname: 'logs',
|
|
123
|
+
filename: 'app-%DATE%.log',
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| Scenario | Console | File |
|
|
129
|
+
|----------|---------|------|
|
|
130
|
+
| Development | `debug` | — |
|
|
131
|
+
| Production | `info` | `warn` |
|
|
132
|
+
| Troubleshooting | `debug` | `info` |
|
|
133
|
+
| Critical alerts | `info` | `error` |
|
|
134
|
+
|
|
135
|
+
This is useful for sending only critical errors to alerting systems while keeping verbose logs in console.
|
|
136
|
+
|
|
137
|
+
### Per-Transport Rules
|
|
88
138
|
|
|
139
|
+
Each transport can have its own filtering rules. Use `level: 'off'` with rules to create a whitelist pattern:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const logger = Logger.create({
|
|
143
|
+
level: 'info',
|
|
144
|
+
console: { format: 'plain' },
|
|
145
|
+
file: {
|
|
146
|
+
format: 'json',
|
|
147
|
+
level: 'off', // off by default
|
|
148
|
+
rules: [
|
|
149
|
+
{ match: { context: 'payments' }, level: 'error' }, // only errors from payments
|
|
150
|
+
{ match: { context: 'auth' }, level: 'warn' }, // warn+ from auth
|
|
151
|
+
],
|
|
152
|
+
dirname: 'logs',
|
|
153
|
+
filename: 'critical-%DATE%.log',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Only error logs from 'payments' context go to file
|
|
158
|
+
logger.for('payments').error('Payment failed'); // → file
|
|
159
|
+
logger.for('payments').info('Processing'); // ✗ not logged to file
|
|
160
|
+
logger.for('other').error('Generic error'); // ✗ not logged to file (no matching rule)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Rules can also match store context (AsyncLocalStorage):
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const logger = Logger.create({
|
|
167
|
+
level: 'info',
|
|
168
|
+
console: { format: 'plain' },
|
|
169
|
+
file: {
|
|
170
|
+
format: 'json',
|
|
171
|
+
level: 'off',
|
|
172
|
+
rules: [
|
|
173
|
+
{ match: { userId: 123 }, level: 'debug' }, // debug for specific user
|
|
174
|
+
{ match: { context: 'api', premium: true }, level: 'debug' }, // debug for premium API users
|
|
175
|
+
],
|
|
176
|
+
dirname: 'logs',
|
|
177
|
+
filename: 'debug-%DATE%.log',
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Logs for user 123 go to file
|
|
182
|
+
store.run({ userId: 123 }, () => {
|
|
183
|
+
logger.debug('User action'); // → file
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Use `level: 'off'` in rules to suppress specific contexts:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
const logger = Logger.create({
|
|
191
|
+
level: 'debug',
|
|
89
192
|
console: {
|
|
90
|
-
|
|
91
|
-
|
|
193
|
+
format: 'plain',
|
|
194
|
+
level: 'debug',
|
|
195
|
+
rules: [
|
|
196
|
+
{ match: { context: 'noisy-module' }, level: 'off' }, // suppress noisy logs
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
});
|
|
92
200
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
201
|
+
logger.for('noisy-module').debug('Spam'); // ✗ not logged
|
|
202
|
+
logger.for('other').debug('Useful info'); // → logged
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## External Transports
|
|
206
|
+
|
|
207
|
+
### Discord
|
|
208
|
+
|
|
209
|
+
Send logs to Discord via webhooks with rich embeds:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
const logger = Logger.create({
|
|
213
|
+
level: 'info',
|
|
214
|
+
console: { format: 'plain' },
|
|
215
|
+
discord: {
|
|
216
|
+
webhookUrl: 'https://discord.com/api/webhooks/xxx/yyy',
|
|
217
|
+
level: 'error', // only errors to Discord
|
|
218
|
+
username: 'My App Logger', // optional bot name
|
|
219
|
+
avatarUrl: 'https://...', // optional avatar
|
|
220
|
+
embedColors: { // optional custom colors
|
|
221
|
+
error: 0xFF0000,
|
|
222
|
+
warn: 0xFFAA00,
|
|
223
|
+
},
|
|
224
|
+
batchSize: 10, // messages per batch (default: 10)
|
|
225
|
+
flushInterval: 2000, // flush interval ms (default: 2000)
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Telegram
|
|
231
|
+
|
|
232
|
+
Send logs to Telegram chats/channels:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const logger = Logger.create({
|
|
236
|
+
level: 'info',
|
|
237
|
+
console: { format: 'plain' },
|
|
238
|
+
telegram: {
|
|
239
|
+
botToken: process.env.TG_BOT_TOKEN!,
|
|
240
|
+
chatId: process.env.TG_CHAT_ID!,
|
|
241
|
+
level: 'warn', // warn and above
|
|
242
|
+
parseMode: 'Markdown', // 'Markdown' | 'MarkdownV2' | 'HTML'
|
|
243
|
+
disableNotification: false, // mute non-error by default
|
|
244
|
+
threadId: 123, // optional forum topic ID
|
|
245
|
+
replyToMessageId: 456, // optional reply to message
|
|
246
|
+
batchSize: 20, // default: 20
|
|
247
|
+
flushInterval: 1000, // default: 1000
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Use `level: 'off'` with rules for selective logging:
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
telegram: {
|
|
256
|
+
botToken: '...',
|
|
257
|
+
chatId: '...',
|
|
258
|
+
level: 'off', // off by default
|
|
259
|
+
rules: [
|
|
260
|
+
{ match: { context: 'payments' }, level: 'error' }, // only payment errors
|
|
261
|
+
],
|
|
102
262
|
}
|
|
103
263
|
```
|
|
104
264
|
|
|
105
|
-
|
|
265
|
+
### CloudWatch
|
|
106
266
|
|
|
107
|
-
|
|
267
|
+
Send logs to AWS CloudWatch Logs:
|
|
108
268
|
|
|
109
269
|
```typescript
|
|
110
|
-
|
|
111
|
-
|
|
270
|
+
const logger = Logger.create({
|
|
271
|
+
level: 'info',
|
|
272
|
+
console: { format: 'plain' },
|
|
273
|
+
cloudwatch: {
|
|
274
|
+
logGroupName: '/app/my-service',
|
|
275
|
+
logStreamName: `${hostname}-${Date.now()}`,
|
|
276
|
+
region: 'us-east-1',
|
|
277
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
278
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
279
|
+
createLogGroup: true, // auto-create group (default: false)
|
|
280
|
+
createLogStream: true, // auto-create stream (default: true)
|
|
281
|
+
batchSize: 100, // default: 100
|
|
282
|
+
flushInterval: 1000, // default: 1000
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Transport Options
|
|
288
|
+
|
|
289
|
+
All external transports support:
|
|
290
|
+
|
|
291
|
+
| Option | Default | Description |
|
|
292
|
+
|--------|---------|-------------|
|
|
293
|
+
| `level` | — | Log level filter |
|
|
294
|
+
| `rules` | — | Per-transport filtering rules |
|
|
295
|
+
| `batchSize` | varies | Max messages per batch |
|
|
296
|
+
| `flushInterval` | varies | Flush interval in ms |
|
|
297
|
+
| `maxRetries` | 3 | Retry count on failure |
|
|
298
|
+
| `retryDelay` | 1000 | Base retry delay in ms |
|
|
299
|
+
|
|
300
|
+
## Singleton Pattern
|
|
301
|
+
|
|
302
|
+
For app-wide logging:
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// src/logger.ts
|
|
306
|
+
import { createSingletonLogger, type LoggerContext } from '@rawnodes/logger';
|
|
307
|
+
|
|
308
|
+
export interface AppContext extends LoggerContext {
|
|
309
|
+
userId?: number;
|
|
310
|
+
requestId?: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export const AppLogger = createSingletonLogger<AppContext>();
|
|
314
|
+
|
|
315
|
+
// src/main.ts
|
|
316
|
+
import { AppLogger } from './logger.js';
|
|
317
|
+
|
|
318
|
+
AppLogger.init({
|
|
319
|
+
level: {
|
|
320
|
+
default: 'info',
|
|
321
|
+
rules: [{ match: { context: 'debug-module' }, level: 'debug' }],
|
|
322
|
+
},
|
|
323
|
+
console: { format: 'plain' },
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
// Anywhere in your app
|
|
327
|
+
const logger = AppLogger.for('UserService');
|
|
328
|
+
logger.info('User created', { userId: 123 });
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
## Context Propagation
|
|
332
|
+
|
|
333
|
+
Automatically include context in all logs within an async scope:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
// Express middleware
|
|
337
|
+
app.use((req, res, next) => {
|
|
338
|
+
const context = {
|
|
339
|
+
userId: req.user?.id,
|
|
340
|
+
requestId: req.headers['x-request-id'] || generateRequestId(),
|
|
341
|
+
};
|
|
342
|
+
AppLogger.getStore().run(context, () => next());
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// All logs within this request will include userId and requestId
|
|
346
|
+
logger.info('Processing request');
|
|
347
|
+
// Output: [2025-01-01T12:00:00] info [APP] Processing request
|
|
348
|
+
// userId: 123
|
|
349
|
+
// requestId: abc-123
|
|
350
|
+
```
|
|
112
351
|
|
|
113
|
-
|
|
114
|
-
const authLogger = logger.child('auth');
|
|
115
|
-
logger.setLevelOverride({ context: 'auth' }, 'debug');
|
|
352
|
+
## Dynamic Level Overrides
|
|
116
353
|
|
|
117
|
-
|
|
118
|
-
|
|
354
|
+
Add/remove level overrides at runtime:
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
// Enable debug for specific user (e.g., for troubleshooting)
|
|
358
|
+
logger.setLevelOverride({ userId: 123 }, 'debug');
|
|
119
359
|
|
|
120
|
-
//
|
|
121
|
-
|
|
360
|
+
// Enable debug for specific module
|
|
361
|
+
logger.setLevelOverride({ context: 'payments' }, 'debug');
|
|
122
362
|
|
|
123
|
-
// Remove override
|
|
124
|
-
logger.removeLevelOverride({
|
|
363
|
+
// Remove override
|
|
364
|
+
logger.removeLevelOverride({ userId: 123 });
|
|
125
365
|
|
|
126
|
-
//
|
|
366
|
+
// Clear all dynamic overrides (keeps config rules)
|
|
127
367
|
logger.clearLevelOverrides();
|
|
368
|
+
|
|
369
|
+
// Get all overrides
|
|
370
|
+
const overrides = logger.getLevelOverrides();
|
|
371
|
+
// [{ match: { context: 'payments' }, level: 'debug', readonly: false }]
|
|
128
372
|
```
|
|
129
373
|
|
|
130
374
|
## Lazy Meta
|
|
131
375
|
|
|
132
|
-
|
|
376
|
+
Defer expensive object creation:
|
|
133
377
|
|
|
134
378
|
```typescript
|
|
135
|
-
// Object
|
|
136
|
-
logger.debug('
|
|
379
|
+
// BAD: Object created even if debug is disabled
|
|
380
|
+
logger.debug('Data processed', { result: expensiveSerialize(data) });
|
|
137
381
|
|
|
138
|
-
// Function
|
|
139
|
-
logger.debug('
|
|
382
|
+
// GOOD: Function only called when debug is enabled
|
|
383
|
+
logger.debug('Data processed', () => ({ result: expensiveSerialize(data) }));
|
|
140
384
|
```
|
|
141
385
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
## Timing
|
|
386
|
+
## Child Loggers
|
|
145
387
|
|
|
146
|
-
|
|
388
|
+
Create scoped loggers:
|
|
147
389
|
|
|
148
390
|
```typescript
|
|
149
|
-
const logger = AppLogger.
|
|
391
|
+
const logger = AppLogger.for('PaymentService');
|
|
392
|
+
logger.info('Processing payment');
|
|
393
|
+
// Output: [timestamp] info [PaymentService] Processing payment
|
|
394
|
+
|
|
395
|
+
const stripeLogger = logger.for('Stripe');
|
|
396
|
+
stripeLogger.info('Charging card');
|
|
397
|
+
// Output: [timestamp] info [Stripe] Charging card
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
## Utilities
|
|
401
|
+
|
|
402
|
+
### Timing
|
|
150
403
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
await db.query('SELECT ...');
|
|
154
|
-
logger.timeEnd(timer); // Logs: "database-query completed in 45.23ms"
|
|
404
|
+
```typescript
|
|
405
|
+
import { measureAsync, measureSync } from '@rawnodes/logger';
|
|
155
406
|
|
|
156
|
-
// Async
|
|
157
|
-
const
|
|
407
|
+
// Async
|
|
408
|
+
const { result, timing } = await measureAsync('fetch-users', async () => {
|
|
158
409
|
return await userService.findAll();
|
|
159
410
|
});
|
|
411
|
+
console.log(timing); // { label: 'fetch-users', durationMs: 45.23, durationFormatted: '45.23ms' }
|
|
412
|
+
|
|
413
|
+
// Sync
|
|
414
|
+
const { result, timing } = measureSync('compute', () => {
|
|
415
|
+
return heavyComputation();
|
|
416
|
+
});
|
|
160
417
|
```
|
|
161
418
|
|
|
162
|
-
|
|
419
|
+
### Request ID
|
|
163
420
|
|
|
164
421
|
```typescript
|
|
165
|
-
import { generateRequestId, getOrGenerateRequestId } from '@rawnodes/logger';
|
|
422
|
+
import { generateRequestId, extractRequestId, getOrGenerateRequestId } from '@rawnodes/logger';
|
|
166
423
|
|
|
167
|
-
// Generate new
|
|
168
|
-
|
|
169
|
-
//
|
|
424
|
+
// Generate new
|
|
425
|
+
generateRequestId(); // "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
426
|
+
generateRequestId({ short: true }); // "a1b2c3d4"
|
|
427
|
+
generateRequestId({ prefix: 'req' }); // "req-a1b2c3d4-e5f6-..."
|
|
170
428
|
|
|
171
|
-
//
|
|
172
|
-
|
|
173
|
-
// => "a1b2c3d4"
|
|
429
|
+
// Extract from headers (checks x-request-id, x-correlation-id, x-trace-id)
|
|
430
|
+
extractRequestId(req.headers); // string | undefined
|
|
174
431
|
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
// => "req-a1b2c3d4-e5f6-..."
|
|
178
|
-
|
|
179
|
-
// Extract from headers or generate new
|
|
180
|
-
const id = getOrGenerateRequestId(req.headers);
|
|
432
|
+
// Extract or generate
|
|
433
|
+
getOrGenerateRequestId(req.headers); // always returns string
|
|
181
434
|
```
|
|
182
435
|
|
|
183
|
-
|
|
436
|
+
### Secret Masking
|
|
437
|
+
|
|
438
|
+
Automatically masks sensitive fields in logs:
|
|
184
439
|
|
|
185
440
|
```typescript
|
|
186
|
-
import { maskSecrets } from '@rawnodes/logger';
|
|
441
|
+
import { maskSecrets, createMasker } from '@rawnodes/logger';
|
|
187
442
|
|
|
188
|
-
|
|
443
|
+
maskSecrets({
|
|
189
444
|
user: 'admin',
|
|
190
445
|
password: 'secret123',
|
|
191
|
-
|
|
192
|
-
url: 'postgres://user:pass@localhost/db',
|
|
446
|
+
apiKey: 'key_abc123',
|
|
193
447
|
});
|
|
448
|
+
// { user: 'admin', password: '***', apiKey: '***' }
|
|
194
449
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
// url: 'postgres://user:***@localhost/db'
|
|
201
|
-
// }
|
|
450
|
+
// Custom masker
|
|
451
|
+
const masker = createMasker({
|
|
452
|
+
patterns: ['ssn', 'creditCard'],
|
|
453
|
+
mask: '[REDACTED]'
|
|
454
|
+
});
|
|
202
455
|
```
|
|
203
456
|
|
|
204
|
-
|
|
457
|
+
**Default masked patterns:** `password`, `secret`, `token`, `apikey`, `api_key`, `api-key`, `auth`, `credential`, `private`
|
|
458
|
+
|
|
459
|
+
## Logfmt Utilities
|
|
205
460
|
|
|
206
|
-
|
|
461
|
+
Helper functions for logfmt format:
|
|
207
462
|
|
|
208
463
|
```typescript
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
464
|
+
import { flattenObject, formatLogfmt, formatLogfmtValue } from '@rawnodes/logger';
|
|
465
|
+
|
|
466
|
+
flattenObject({ user: { id: 123, name: 'John' } });
|
|
467
|
+
// { 'user.id': 123, 'user.name': 'John' }
|
|
212
468
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const childLogger = baseLogger.getChildLogger('EmailService');
|
|
469
|
+
formatLogfmt({ level: 'info', msg: 'hello', userId: 123 });
|
|
470
|
+
// "level=info msg=hello userId=123"
|
|
216
471
|
```
|
|
217
472
|
|
|
218
473
|
## API Reference
|
|
219
474
|
|
|
220
|
-
###
|
|
221
|
-
|
|
222
|
-
Creates a singleton logger factory.
|
|
475
|
+
### Logger
|
|
223
476
|
|
|
224
|
-
Returns:
|
|
225
477
|
```typescript
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
478
|
+
class Logger<TContext> {
|
|
479
|
+
static create(config: LoggerConfig, store?: LoggerStore): Logger;
|
|
480
|
+
|
|
229
481
|
for(context: string): Logger;
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
482
|
+
getStore(): LoggerStore<TContext>;
|
|
483
|
+
|
|
484
|
+
// Logging
|
|
485
|
+
error(message: string, error?: Error, meta?: Meta): void;
|
|
486
|
+
warn(message: string, meta?: Meta): void;
|
|
487
|
+
info(message: string, meta?: Meta): void;
|
|
488
|
+
http(message: string, meta?: Meta): void;
|
|
489
|
+
verbose(message: string, meta?: Meta): void;
|
|
490
|
+
debug(message: string, meta?: Meta): void;
|
|
491
|
+
silly(message: string, meta?: Meta): void;
|
|
492
|
+
|
|
493
|
+
// Level overrides
|
|
494
|
+
setLevelOverride(match: LevelOverrideMatch, level: LogLevel): void;
|
|
495
|
+
removeLevelOverride(match: LevelOverrideMatch): boolean;
|
|
233
496
|
clearLevelOverrides(): void;
|
|
497
|
+
getLevelOverrides(): LevelOverride[];
|
|
498
|
+
|
|
499
|
+
// Winston profiling
|
|
500
|
+
profile(id: string, meta?: object): void;
|
|
234
501
|
}
|
|
235
502
|
```
|
|
236
503
|
|
|
237
|
-
###
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
504
|
+
### Types
|
|
505
|
+
|
|
506
|
+
```typescript
|
|
507
|
+
type LogLevel = 'error' | 'warn' | 'info' | 'http' | 'verbose' | 'debug' | 'silly' | 'off';
|
|
508
|
+
type LogFormat = 'json' | 'plain' | 'logfmt' | 'simple';
|
|
509
|
+
type Meta = object | (() => object);
|
|
510
|
+
|
|
511
|
+
interface LoggerConfig {
|
|
512
|
+
level: LogLevel | {
|
|
513
|
+
default: LogLevel;
|
|
514
|
+
rules?: LevelRule[];
|
|
515
|
+
};
|
|
516
|
+
console: {
|
|
517
|
+
format: LogFormat;
|
|
518
|
+
level?: LogLevel; // optional, overrides global level
|
|
519
|
+
rules?: LevelRule[]; // optional, per-transport filtering
|
|
520
|
+
};
|
|
521
|
+
file?: {
|
|
522
|
+
format: LogFormat;
|
|
523
|
+
level?: LogLevel; // optional, overrides global level
|
|
524
|
+
rules?: LevelRule[]; // optional, per-transport filtering
|
|
525
|
+
dirname: string;
|
|
526
|
+
filename: string;
|
|
527
|
+
datePattern?: string;
|
|
528
|
+
maxFiles?: string;
|
|
529
|
+
maxSize?: string;
|
|
530
|
+
zippedArchive?: boolean;
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
interface LevelRule {
|
|
535
|
+
match: Record<string, unknown> & { context?: string };
|
|
536
|
+
level: LogLevel;
|
|
537
|
+
}
|
|
538
|
+
```
|
|
261
539
|
|
|
262
540
|
## Integration Examples
|
|
263
541
|
|
|
@@ -295,7 +573,7 @@ export class LoggerMiddleware implements NestMiddleware {
|
|
|
295
573
|
}
|
|
296
574
|
```
|
|
297
575
|
|
|
298
|
-
### Telegraf
|
|
576
|
+
### Telegraf
|
|
299
577
|
|
|
300
578
|
```typescript
|
|
301
579
|
import { Telegraf } from 'telegraf';
|