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/README.md
CHANGED
|
@@ -4,6 +4,19 @@ NightTimeLogger is a custom logging wrapper built on top of the Winston logging
|
|
|
4
4
|
|
|
5
5
|
[](https://github.com/NightSquawk/NightTimeLogger/actions/workflows/main.yml)
|
|
6
6
|
|
|
7
|
+
This maintenance update requires Node.js 22.22.2 or newer; Node 22 and 24 are tested.
|
|
8
|
+
|
|
9
|
+
Process signal handling is now **opt-in**. Applications that relied on automatic shutdown must register it once:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
const logger = require('ntlogger');
|
|
13
|
+
const unregister = logger.setupSignalHandlers({ timeout: 30000 });
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Alternatively, keep your application's own shutdown handlers and `await log.close()` there. Importing the package no longer changes process exception or signal handling. Flush and close reject on delivery errors/timeouts, so callers should handle rejected promises. Close is idempotent, waits for transport cleanup, and removes the logger from its cache. Children share their parent's transports; closing a child flushes but does not close the parent. Log calls after close throw an error.
|
|
17
|
+
|
|
18
|
+
OpenTelemetry users must upgrade the logs SDK/API/HTTP exporter together to `^0.222.0` and resources to `^2.11.0`. Earlier SDK versions are no longer advertised as compatible. Database transports now own separate pools, use event timestamps in UTC, accept `ssl` configuration, and require simple SQL table identifiers. Existing tables are not altered automatically.
|
|
19
|
+
|
|
7
20
|
## Features
|
|
8
21
|
|
|
9
22
|
- Custom log levels for fine-grained control over logging output.
|
|
@@ -18,6 +31,7 @@ NightTimeLogger is a custom logging wrapper built on top of the Winston logging
|
|
|
18
31
|
- **Performance metrics** - Development-only performance tracking with `time()` and `timeEnd()` methods.
|
|
19
32
|
- **Non-blocking operations** - All logging operations are asynchronous and won't block your application.
|
|
20
33
|
- **TypeScript support** - Full TypeScript definitions included for excellent IDE integration (IntelliJ, VS Code, etc.).
|
|
34
|
+
- [Log Contract](docs/log-contract.md) — cross-language field, level, context, redaction, and OTLP mapping contract shared by Node, Python, and Rust emitters.
|
|
21
35
|
|
|
22
36
|
## Installation
|
|
23
37
|
|
|
@@ -27,6 +41,32 @@ To install NightTimeLogger, use npm:
|
|
|
27
41
|
npm install ntlogger
|
|
28
42
|
```
|
|
29
43
|
|
|
44
|
+
The core package depends only on `winston` and `winston-transport`, so a default
|
|
45
|
+
install stays small.
|
|
46
|
+
|
|
47
|
+
### Optional plugin backends
|
|
48
|
+
|
|
49
|
+
Plugin backends are **optional peer dependencies** - they are not installed for you.
|
|
50
|
+
Install only the ones whose plugins you actually enable:
|
|
51
|
+
|
|
52
|
+
| Plugin | Install |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| Sentry | `npm install @sentry/node` |
|
|
55
|
+
| MySQL | `npm install mysql2` |
|
|
56
|
+
| Postgres | `npm install pg` |
|
|
57
|
+
| OpenTelemetry | `npm install @opentelemetry/api @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions` |
|
|
58
|
+
| Discord, Teams, OpenObserve, Syslog, Jest | none - no extra packages required |
|
|
59
|
+
|
|
60
|
+
Plugin modules are resolved lazily, so a missing peer never breaks
|
|
61
|
+
`require('ntlogger')`. The loader reports an actionable error naming the package and
|
|
62
|
+
the install command, skips that one plugin, and keeps every other plugin working.
|
|
63
|
+
See [plugins/README.md](https://github.com/NightSquawk/NightTimeLogger/blob/main/plugins/README.md)
|
|
64
|
+
for per-plugin configuration.
|
|
65
|
+
|
|
66
|
+
> **Upgrading:** `@sentry/node`, `mysql2` and `pg` used to be hard dependencies of
|
|
67
|
+
> `ntlogger`. If you relied on them being installed transitively, add them to your own
|
|
68
|
+
> `package.json`.
|
|
69
|
+
|
|
30
70
|
## Usage
|
|
31
71
|
|
|
32
72
|
```javascript
|
|
@@ -62,9 +102,13 @@ Check out [Full Configuration](https://github.com/NightSquawk/NightTimeLogger/bl
|
|
|
62
102
|
- `console`: Whether to enable console logging. Defaults to `true`.
|
|
63
103
|
- `file`: Whether to enable file logging. Defaults to `true`.
|
|
64
104
|
- `path`: The directory path where log files will be saved.
|
|
105
|
+
- `filename`: Combined log filename (default: `combined.log`); error/fatal files retain their names.
|
|
106
|
+
- `shutdownTimeout`: Maximum wait in milliseconds for flush or stream shutdown (default: `30000`).
|
|
65
107
|
- `maxSize`: The maximum size (in bytes) for each log file.
|
|
66
108
|
- `maxFiles`: The maximum number of log files to retain (rotating file strategy).
|
|
67
109
|
- `timestamp`: Whether to include timestamps in log messages. Defaults to `true`.
|
|
110
|
+
- `skipCache`: Whether to bypass the logger instance cache and always build a new instance for this location name. Defaults to `false` (instances are cached per location name; child loggers always skip the cache).
|
|
111
|
+
- `plugins`: Array of plugin configurations, each `{ name, enabled, config }`. All three fields are required - `enabled` must be `true` and `config` must be an object (use `{}` when the plugin takes no options), otherwise the plugin is skipped. Backends whose optional peer dependency is missing are reported and skipped without affecting the others. Defaults to `[]`.
|
|
68
112
|
- `debug`: Whether to enable debug mode, which logs internal messages. Defaults to `false`.
|
|
69
113
|
- `reportPath`: Whether to enable call site path reporting. When enabled, automatically captures the file path, line number, column number, and call chain where each log statement is executed. The path is added as metadata (JSON field `filePath`), not in the formatted message string. Defaults to `false`.
|
|
70
114
|
- `sampling`: Object with level-based sampling rates (e.g., `{ debug: 0.01, trace: 0.001 }`). Values between 0.0 and 1.0, where 1.0 = log all, 0.1 = log 10%. Defaults to `{}` (no sampling).
|
|
@@ -204,6 +248,192 @@ await log.flush();
|
|
|
204
248
|
await log.close();
|
|
205
249
|
```
|
|
206
250
|
|
|
251
|
+
## Pino Support
|
|
252
|
+
|
|
253
|
+
`ntlogger/pino` is a separate entry point for projects that already use [Pino](https://getpino.io).
|
|
254
|
+
It provides an NTL-styled transport plus `createLogger()`, which wires the NTL conventions
|
|
255
|
+
(module label, structured context, secret redaction, sampling, graceful shutdown) onto a real
|
|
256
|
+
Pino logger.
|
|
257
|
+
|
|
258
|
+
`pino` is an optional peer dependency and `split2` an optional dependency — Winston-only users
|
|
259
|
+
install neither.
|
|
260
|
+
|
|
261
|
+
### Transport only
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
const pino = require('pino');
|
|
265
|
+
|
|
266
|
+
const log = pino({
|
|
267
|
+
transport: { target: 'ntlogger/pino', options: { colorize: true, defaultModule: 'MyApp' } },
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
log.info({ tenantId: 'acme' }, 'server started');
|
|
271
|
+
// 2025-01-01 12:00:00 [info ] [MyApp]: {tenantId=acme} server started
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
The transport renders a dim `{key=value ...}` suffix for the top-level context keys
|
|
275
|
+
`tenantId`, `userId`, `correlationId`, `traceId`, `spanId`, `jobId`, `agentUuid`, `commandId`
|
|
276
|
+
(exported as `CONTEXT_KEYS`), and pairs pino-http/Fastify request/response lines.
|
|
277
|
+
|
|
278
|
+
### `createLogger(opts)`
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
const { createLogger } = require('ntlogger/pino');
|
|
282
|
+
|
|
283
|
+
const log = createLogger({ module: 'OrderService', service: 'checkout-api' });
|
|
284
|
+
|
|
285
|
+
log.info({ tenantId: 'acme' }, 'order received');
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`createLogger()` returns a **real Pino logger**, so the call signature is object-first:
|
|
289
|
+
`log.info({ tenantId }, 'message')` — not Winston's `log.info('message', meta)`.
|
|
290
|
+
|
|
291
|
+
In development it attaches the NTL transport for human-readable output; with
|
|
292
|
+
`NODE_ENV=production` it writes plain NDJSON so log shippers get clean JSON.
|
|
293
|
+
|
|
294
|
+
| Option | Type | Default | Purpose |
|
|
295
|
+
| --- | --- | --- | --- |
|
|
296
|
+
| `level` | `string` | `LOG_LEVEL`, else `debug` (dev) / `info` (production) | Pino level. |
|
|
297
|
+
| `module` | `string` | — | Adds `module` to every line and labels the transport output. |
|
|
298
|
+
| `defaultModule` | `string` | — | Transport label when `module` is not set. |
|
|
299
|
+
| `colorize` | `boolean` | transport default | Forwarded to the NTL transport. |
|
|
300
|
+
| `service` | `string` | — | Constant top-level `service` field (a core field of the log contract). |
|
|
301
|
+
| `context` | `object` | — | Static top-level fields merged into every line. |
|
|
302
|
+
| `contextProvider` | `() => object` | — | Called per log call; its result is merged at the top level. |
|
|
303
|
+
| `contextKeys` | `string[]` | — | When set, only these keys are taken from the provider result. |
|
|
304
|
+
| `redact` | `false \| string[] \| object` | defaults on | Secret redaction (see below). |
|
|
305
|
+
| `sampling` | `object` | — | Per-level sampling rates, same shape as the Winston path. |
|
|
306
|
+
| `rateLimit` | `object` | — | Per-level rate limits, same shape as the Winston path. |
|
|
307
|
+
| `deduplication` | `object \| boolean` | — | Duplicate-message collapsing, same shape as the Winston path. |
|
|
308
|
+
| `silent` | `boolean` | see below | Force silent mode on or off. |
|
|
309
|
+
| `processHandlers` | `boolean \| object` | `false` | Opt-in crash/signal handlers. |
|
|
310
|
+
| `destination` | Pino destination | — | Test/advanced escape hatch; replaces the NTL transport. |
|
|
311
|
+
|
|
312
|
+
The returned logger additionally exposes `getStats()`, `resetStats()`, `close(timeout?)` and —
|
|
313
|
+
when `processHandlers` is used — `uninstallProcessHandlers()`. These are own properties of the
|
|
314
|
+
returned instance, so `logger.child()` inherits them without anything being written to Pino's
|
|
315
|
+
shared prototype.
|
|
316
|
+
|
|
317
|
+
### Request context with AsyncLocalStorage
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
321
|
+
const { createLogger } = require('ntlogger/pino');
|
|
322
|
+
|
|
323
|
+
const als = new AsyncLocalStorage();
|
|
324
|
+
|
|
325
|
+
const log = createLogger({
|
|
326
|
+
module: 'API',
|
|
327
|
+
context: { region: 'us-east-1' }, // constant
|
|
328
|
+
contextProvider: () => als.getStore(), // per call
|
|
329
|
+
contextKeys: ['tenantId', 'userId', 'correlationId'],
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
app.use((req, res, next) => als.run({ tenantId: req.tenantId, correlationId: req.id }, next));
|
|
333
|
+
|
|
334
|
+
log.info('handled'); // -> { module: 'API', region: 'us-east-1', tenantId: ..., correlationId: ... }
|
|
335
|
+
log.info({ tenantId: 'override' }, 'handled');
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Precedence, lowest to highest:
|
|
339
|
+
|
|
340
|
+
`service`/`pid`/`hostname` → `module` → `context` → `contextProvider` → fields passed on the log call.
|
|
341
|
+
|
|
342
|
+
`contextKeys` lets an application hand the provider a large store and expose only a few fields.
|
|
343
|
+
A provider that throws never breaks logging: the context is dropped and a single
|
|
344
|
+
`NTLoggerContextWarning` is emitted via `process.emitWarning`. Non-object return values
|
|
345
|
+
(including arrays and `null`) are ignored.
|
|
346
|
+
|
|
347
|
+
### Secret redaction
|
|
348
|
+
|
|
349
|
+
Redaction is **on by default**. It covers the message string, string and plain-object
|
|
350
|
+
interpolation arguments, the merge object (recursively), `Error` messages and stacks, and child
|
|
351
|
+
bindings. Keys such as `password`, `token`, `authorization`, `apiKey` and `cookie` are replaced
|
|
352
|
+
wholesale; known secret shapes (Bearer/Basic headers, JWTs, AWS/GitHub/Slack/Stripe keys, PEM
|
|
353
|
+
blocks, URL credentials, Discord webhook tokens) are replaced inside free text.
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
createLogger({ redact: false }); // disable entirely
|
|
357
|
+
createLogger({ redact: ['deviceFingerprint'] }); // add app-specific key names
|
|
358
|
+
createLogger({ redact: ['payload.cardNumber'] }); // Pino-style path -> Pino's own redact
|
|
359
|
+
createLogger({ redact: { replacement: '***', extraPatterns: [/CUST-\d{8}/g] } });
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
A string array is split: entries containing `.`, `[`, `]` or `*` are forwarded to Pino's native
|
|
363
|
+
`redact` option (which replaces with Pino's `[Redacted]`), everything else becomes `extraKeys`
|
|
364
|
+
for ntlogger's redactor (which replaces with `[REDACTED]`). An object is passed straight to
|
|
365
|
+
`createRedactor()` and accepts `keys`, `extraKeys`, `patterns`, `extraPatterns`, `replacement`,
|
|
366
|
+
`maxDepth` and `maxStringLength`.
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
log.error(new Error('payment failed for Bearer abc123def456...'));
|
|
370
|
+
// -> { "err": { "type": "Error", "message": "payment failed for Bearer [REDACTED]", "stack": "..." },
|
|
371
|
+
// "msg": "payment failed for Bearer [REDACTED]" }
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Sampling, rate limiting and deduplication
|
|
375
|
+
|
|
376
|
+
The same configuration shapes as the Winston path (see *Log Sampling and Rate Limiting* and
|
|
377
|
+
*Log Deduplication*), applied through Pino's single `hooks.logMethod`.
|
|
378
|
+
|
|
379
|
+
```javascript
|
|
380
|
+
const log = createLogger({
|
|
381
|
+
module: 'Ingest',
|
|
382
|
+
sampling: { debug: 0.1 },
|
|
383
|
+
rateLimit: { info: { max: 100, window: 60000 } },
|
|
384
|
+
deduplication: { enabled: true, threshold: 3, window: 60000 },
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
log.getStats(); // { sampling: {...}, deduplication: {...} }
|
|
388
|
+
log.resetStats();
|
|
389
|
+
await log.close(); // releases the sampler/dedup timers, then flushes the destination
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
`fatal` is never sampled, rate limited or deduplicated unless the configuration names it
|
|
393
|
+
explicitly. Zero-config loggers install no feature hook at all.
|
|
394
|
+
|
|
395
|
+
### Silent mode
|
|
396
|
+
|
|
397
|
+
Resolution order:
|
|
398
|
+
|
|
399
|
+
1. `opts.silent`, when it is a boolean
|
|
400
|
+
2. `NTLOGGER_SILENT` — `1`/`true`/`yes` → silent, `0`/`false`/`no` → not silent
|
|
401
|
+
3. `NODE_ENV === 'test'` → silent
|
|
402
|
+
|
|
403
|
+
A silent logger runs at Pino level `silent`, attaches no transport and installs no hooks, so
|
|
404
|
+
test suites pay nothing for logging. `isSilent(opts)` is exported for testing the resolution.
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
const log = createLogger({ module: 'API' }); // silent under Jest (NODE_ENV=test)
|
|
408
|
+
const log = createLogger({ module: 'API', silent: false }); // force output in a test
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Node's built-in test runner does **not** set `NODE_ENV`, so projects using `node --test` should
|
|
412
|
+
export `NTLOGGER_SILENT=1` in their test script instead.
|
|
413
|
+
|
|
414
|
+
### Process handlers
|
|
415
|
+
|
|
416
|
+
Opt-in only — importing the library never installs process listeners.
|
|
417
|
+
|
|
418
|
+
```javascript
|
|
419
|
+
const log = createLogger({
|
|
420
|
+
module: 'API',
|
|
421
|
+
processHandlers: {
|
|
422
|
+
timeout: 4000,
|
|
423
|
+
signals: ['SIGTERM', 'SIGINT'],
|
|
424
|
+
onShutdown: async reason => { await server.close(); },
|
|
425
|
+
},
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// later
|
|
429
|
+
log.uninstallProcessHandlers();
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
`installProcessHandlers(logger, opts)` and `drainLogger(logger, timeout)` are also exported
|
|
433
|
+
directly for loggers not built by `createLogger()`.
|
|
434
|
+
|
|
435
|
+
See `examples/pino-advanced.js` for all of the above in one runnable file.
|
|
436
|
+
|
|
207
437
|
## Custom Levels and Colors
|
|
208
438
|
NightTimeLogger provides custom log levels and colors for enhanced logging experience:
|
|
209
439
|
|
|
@@ -215,7 +445,7 @@ NightTimeLogger provides custom log levels and colors for enhanced logging exper
|
|
|
215
445
|
- warn: 2
|
|
216
446
|
- error: 1
|
|
217
447
|
- fatal: 0
|
|
218
|
-
- internal:
|
|
448
|
+
- internal: 6
|
|
219
449
|
|
|
220
450
|
### Colors:
|
|
221
451
|
|
|
@@ -231,4 +461,9 @@ NightTimeLogger provides custom log levels and colors for enhanced logging exper
|
|
|
231
461
|
NightTimeLogger supports both file and console log formatters. File-formatted logs are stored in the project's root `/logs` directory.
|
|
232
462
|
|
|
233
463
|
## License
|
|
234
|
-
NightTimeLogger is licensed under the [GPL-3.0 License](https://opensource.org/licenses/GPL-3.0).
|
|
464
|
+
NightTimeLogger is licensed under the [GPL-3.0 License](https://opensource.org/licenses/GPL-3.0).
|
|
465
|
+
## Maintenance checks
|
|
466
|
+
|
|
467
|
+
Run `npm ci` and `npm run test:coverage` for the complete suite. MySQL and PostgreSQL integration tests start disposable Docker containers; CI sets `REQUIRE_DOCKER_TESTS=1` so they cannot silently skip. `npm run test:unit` excludes those two Docker suites for local work. `npm audit` checks all dependencies; `npm audit --omit=dev` checks production dependencies.
|
|
468
|
+
|
|
469
|
+
HTTP transports (Discord, Teams, OpenObserve) accept `timeout` (5000 ms per attempt), `maxRetries` (3), `retryDelay` (1000 ms), and `maxPending` (100 outstanding deliveries). They retry connection failures, HTTP 429 and 5xx with bounded backoff. Queue overflow, terminal HTTP errors, and timeouts surface through callbacks/flush. Delivery is best-effort with retries, not durable storage; an ambiguous network failure can lead to a duplicate on retry. OpenObserve retains batching and flush now waits for responses. Increase `shutdownTimeout` if you configure longer retries.
|