ntlogger 2.9.1 → 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 CHANGED
@@ -4,6 +4,19 @@ NightTimeLogger is a custom logging wrapper built on top of the Winston logging
4
4
 
5
5
  [![Node.js Package](https://github.com/NightSquawk/NightTimeLogger/actions/workflows/main.yml/badge.svg?branch=main)](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: -1
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.