ntlogger
v4.0.0
Published
Structured logging with Winston and Pino backends
Downloads
4,899
Maintainers
Readme
NightTimeLogger
NightTimeLogger provides Winston and Pino logging backends with shared logging conventions. Install the backend your application uses.
This maintenance update requires Node.js 22.22.2 or newer; Node 22 and 24 are tested.
Process signal handling is now opt-in. Applications that relied on automatic shutdown must register it once:
const logger = require('ntlogger');
const unregister = logger.setupSignalHandlers({ timeout: 30000 });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 on both backends. See the Pino lifecycle note below for its drain result contract.
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.
Features
- Custom log levels for fine-grained control over logging output.
- Dynamic color generation for visually appealing log messages.
- Custom session ID generation for tracking log sessions.
- Support for both file and console log formatters.
- Ability to configure log levels and formats to suit specific requirements.
- Native call site path reporting - automatically capture file path, line number, and call chain where each log statement is executed.
- Child loggers - Create contextual loggers with persistent metadata (perfect for multi-threaded applications).
- Log sampling and rate limiting - Reduce console spam with configurable sampling rates and rate limits per log level.
- Log deduplication - Automatically group and squish duplicate log messages (e.g., "Ticket already exists (x11)").
- Performance metrics - Development-only performance tracking with
time()andtimeEnd()methods. - Non-blocking operations - All logging operations are asynchronous and won't block your application.
- TypeScript support - Full TypeScript definitions included for excellent IDE integration (IntelliJ, VS Code, etc.).
- Log Contract — cross-language field, level, context, redaction, and OTLP mapping contract shared by Node, Python, and Rust emitters.
Installation
For Pino or Fastify:
npm install ntlogger pinoImport helpers from ntlogger/pino. This installation does not pull in Winston or
its transports. The small split2 dependency supports the development formatter.
For the Winston API and its plugins:
npm install ntlogger winston winston-transportImport ntlogger as before. Both backends are optional peers, so install the one
you use; applications using both entry points should install all three peers.
Upgrade note: Winston and winston-transport were previously installed by
ntlogger automatically. Existing Winston consumers must add them as direct
production dependencies using the command above before upgrading. This is a
breaking installation change in v4.0.0. See release notes
for the complete migration checklist.
Optional plugin backends
Plugin backends are optional peer dependencies - they are not installed for you. Install only the ones whose plugins you actually enable:
| Plugin | Install |
| --- | --- |
| Sentry | npm install @sentry/node |
| MySQL | npm install mysql2 |
| Postgres | npm install pg |
| OpenTelemetry | npm install @opentelemetry/api @opentelemetry/api-logs @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http @opentelemetry/resources @opentelemetry/semantic-conventions |
| Discord, Teams, OpenObserve, Syslog, Jest | none - no extra packages required |
Plugin modules are resolved lazily, so a missing plugin backend never breaks
require('ntlogger') when the Winston peers are installed. The loader reports an actionable error naming the package and
the install command, skips that one plugin, and keeps every other plugin working.
See plugins/README.md
for per-plugin configuration.
Upgrading:
@sentry/node,mysql2andpgused to be hard dependencies ofntlogger. If you relied on them being installed transitively, add them to your ownpackage.json.
Usage
// Import the logger
const logger = require('ntlogger');
// Create a logger instance
const log = logger('MyApp');
// Log messages at different levels
log.info('Informational message');
log.warn('Warning message');
log.error('Error message');
log.debug('Debugging message');
log.trace('Trace message');
// Log internal messages
log.internal('Internal message');Output
Check out Quick Start

Check out Full Configuration

Configuration Options
level: The default log level for the logger instance.console: Whether to enable console logging. Defaults totrue.file: Whether to enable file logging. Defaults totrue.path: The directory path where log files will be saved.filename: Combined log filename (default:combined.log); error/fatal files retain their names.shutdownTimeout: Maximum wait in milliseconds for flush or stream shutdown (default:30000).maxSize: The maximum size (in bytes) for each log file.maxFiles: The maximum number of log files to retain (rotating file strategy).timestamp: Whether to include timestamps in log messages. Defaults totrue.skipCache: Whether to bypass the logger instance cache and always build a new instance for this location name. Defaults tofalse(instances are cached per location name; child loggers always skip the cache).plugins: Array of plugin configurations, each{ name, enabled, config }. All three fields are required -enabledmust betrueandconfigmust 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[].debug: Whether to enable debug mode, which logs internal messages. Defaults tofalse.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 fieldfilePath), not in the formatted message string. Defaults tofalse.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).rateLimit: Object with level-based rate limits (e.g.,{ error: { max: 10, window: 60000 } }). Prevents console spam by limiting logs per level within a time window. Defaults to{}(no rate limiting).deduplication: Object with{ enabled: boolean, threshold: number, window: number }. Groups similar log messages and squishes duplicates (e.g., "Ticket already exists (x11)"). Defaults to{ enabled: false, threshold: 3, window: 60000 }.performanceMetrics: Enable performance tracking (default:NODE_ENV === 'development'). Addstime()andtimeEnd()methods.statsInterval: Interval in milliseconds to automatically log statistics (default:0= disabled). Useful for tuning sampling/rate limiting parameters.
Call Site Path Reporting
When reportPath is enabled, each log entry includes a filePath field in its metadata showing where the log was called:
const logger = require('ntlogger');
const log = logger('MyApp', {
reportPath: true
});
log.info('User logged in'); // filePath will show: "./src/routes/auth.js:45:12 [handleLogin ← router.post]"The filePath field appears in:
- JSON metadata (for plugins like OpenObserve)
- Console output (appended to location)
- File logs (appended to location)
Smart Internal Function Filtering: The logger automatically filters out internal Node.js functions (like _onTimeout, listOnTimeout, setImmediate, etc.) from the call chain, ensuring you only see your actual application code. This works even when logs are called from within setTimeout, setImmediate, or Promise callbacks.
Note: The location field represents the logger instance name, while filePath shows the actual call site where the log statement was written.
Child Loggers
Create child loggers with persistent context that's automatically merged into all log entries. Perfect for request-scoped logging or multi-threaded applications:
const logger = require('ntlogger');
const log = logger('MyApp');
// Create a child logger with context
const requestLogger = log.child({
requestId: 'abc123',
userId: 456,
endpoint: '/api/users'
});
// All logs from requestLogger automatically include the context
requestLogger.info('Processing request');
// Logs: { requestId: 'abc123', userId: 456, endpoint: '/api/users', message: 'Processing request' }
// Support nested children
const operationLogger = requestLogger.child({ operation: 'validate' });
operationLogger.debug('Validating input'); // Includes all parent contextLog Sampling and Rate Limiting
Reduce console spam with configurable sampling and rate limiting:
const log = logger('MyApp', {
// Sample 1% of debug logs, 0.1% of trace logs
sampling: {
debug: 0.01,
trace: 0.001
},
// Limit errors to 10 per minute
rateLimit: {
error: { max: 10, window: 60000 }
},
// Log statistics every 5 minutes to help tune parameters
statsInterval: 300000
});
// Get statistics
const stats = log.getStats();
console.log(stats);
// {
// sampling: { total: { error: 150 }, sampled: { debug: 99 }, rateLimited: { error: 5 } },
// deduplication: { totalDeduplicated: 50, uniqueMessages: 20 },
// performance: { enabled: true, avgLogProcessingTime: 0.5 }
// }Log Deduplication
Automatically group and squish duplicate log messages:
const log = logger('MyApp', {
deduplication: {
enabled: true,
threshold: 3, // After 3 duplicates, start squishing
window: 60000 // 60 second window
}
});
// If this message appears 11 times:
log.debug('Ticket already exists for threshold a481199f-ed1c-47c6-834d-9cf54cdc394e and device 2642');
// It will be logged once as:
// "Ticket already exists for threshold * and device * (x11)"Performance Metrics (Development Only)
Track performance in development mode:
const log = logger('MyApp', {
performanceMetrics: true // Auto-enabled in development
});
log.time('database-query');
// ... do work ...
const duration = log.timeEnd('database-query');
// Logs: "Timer 'database-query' completed in 45.23ms"Non-Blocking Operations
All logging operations are non-blocking and return immediately:
const log = logger('MyApp');
// All these return immediately, processing happens asynchronously
log.info('Message 1');
log.info('Message 2');
log.info('Message 3');
// Flush all pending logs (returns Promise)
await log.flush();
// Close logger gracefully (returns Promise)
await log.close();Pino Support
ntlogger/pino is a separate entry point for projects that already use Pino.
It provides an NTL-styled transport plus createLogger(), which wires the NTL conventions
(module label, structured context, secret redaction, sampling, graceful shutdown) onto a real
Pino logger.
pino is an optional peer dependency, so Winston-only users do not install it.
split2 is a small optional dependency installed by default for the Pino formatter.
Transport only
const pino = require('pino');
const log = pino({
transport: { target: 'ntlogger/pino', options: { colorize: true, defaultModule: 'MyApp' } },
});
log.info({ tenantId: 'acme' }, 'server started');
// 2025-01-01 12:00:00 [info ] [MyApp]: {tenantId=acme} server startedThe transport renders a dim {key=value ...} suffix for the top-level context keys
tenantId, userId, correlationId, traceId, spanId, jobId, agentUuid, commandId
(exported as CONTEXT_KEYS), and pairs pino-http/Fastify request/response lines.
createLogger(opts)
const { createLogger } = require('ntlogger/pino');
const log = createLogger({ module: 'OrderService', service: 'checkout-api' });
log.info({ tenantId: 'acme' }, 'order received');createLogger() returns a real Pino logger, so the call signature is object-first:
log.info({ tenantId }, 'message') — not Winston's log.info('message', meta).
In development it attaches the NTL transport for human-readable output; with
NODE_ENV=production it writes plain NDJSON so log shippers get clean JSON.
| Option | Type | Default | Purpose |
| --- | --- | --- | --- |
| level | string | LOG_LEVEL, else debug (dev) / info (production) | Pino level. |
| module | string | — | Adds module to every line and labels the transport output. |
| defaultModule | string | — | Transport label when module is not set. |
| colorize | boolean | transport default | Forwarded to the NTL transport. |
| service | string | — | Constant top-level service field (a core field of the log contract). |
| context | object | — | Static top-level fields merged into every line. |
| contextProvider | () => object | — | Called per log call; its result is merged at the top level. |
| contextKeys | string[] | — | When set, only these keys are taken from the provider result. |
| redact | false \| string[] \| object | defaults on | Secret redaction (see below). |
| sampling | object | — | Per-level sampling rates, same shape as the Winston path. |
| rateLimit | object | — | Per-level rate limits, same shape as the Winston path. |
| deduplication | object \| boolean | — | Duplicate-message collapsing, same shape as the Winston path. |
| silent | boolean | see below | Force silent mode on or off. |
| processHandlers | boolean \| object | false | Opt-in crash/signal handlers. |
| destination | Pino destination | — | Test/advanced escape hatch; replaces the NTL transport. |
The returned logger additionally exposes getStats(), resetStats(), close(timeout?) and —
when processHandlers is used — uninstallProcessHandlers(). These are own properties of the
returned instance, so logger.child() inherits them without anything being written to Pino's
shared prototype.
Request context with AsyncLocalStorage
const { AsyncLocalStorage } = require('async_hooks');
const { createLogger } = require('ntlogger/pino');
const als = new AsyncLocalStorage();
const log = createLogger({
module: 'API',
context: { region: 'us-east-1' }, // constant
contextProvider: () => als.getStore(), // per call
contextKeys: ['tenantId', 'userId', 'correlationId'],
});
app.use((req, res, next) => als.run({ tenantId: req.tenantId, correlationId: req.id }, next));
log.info('handled'); // -> { module: 'API', region: 'us-east-1', tenantId: ..., correlationId: ... }
log.info({ tenantId: 'override' }, 'handled');Precedence, lowest to highest:
service/pid/hostname → module → context → contextProvider → fields passed on the log call.
contextKeys lets an application hand the provider a large store and expose only a few fields.
A provider that throws never breaks logging: the context is dropped and a single
NTLoggerContextWarning is emitted via process.emitWarning. Non-object return values
(including arrays and null) are ignored.
Secret redaction
Redaction is on by default. It covers the message string, string and plain-object
interpolation arguments, the merge object (recursively), Error messages and stacks, and child
bindings. Keys such as password, token, authorization, apiKey and cookie are replaced
wholesale; known secret shapes (Bearer/Basic headers, JWTs, AWS/GitHub/Slack/Stripe keys, PEM
blocks, URL credentials, Discord webhook tokens) are replaced inside free text.
createLogger({ redact: false }); // disable entirely
createLogger({ redact: ['deviceFingerprint'] }); // add app-specific key names
createLogger({ redact: ['payload.cardNumber'] }); // Pino-style path -> Pino's own redact
createLogger({ redact: { replacement: '***', extraPatterns: [/CUST-\d{8}/g] } });A string array is split: entries containing ., [, ] or * are forwarded to Pino's native
redact option (which replaces with Pino's [Redacted]), everything else becomes extraKeys
for ntlogger's redactor (which replaces with [REDACTED]). An object is passed straight to
createRedactor() and accepts keys, extraKeys, patterns, extraPatterns, replacement,
maxDepth and maxStringLength.
log.error(new Error('payment failed for Bearer abc123def456...'));
// -> { "err": { "type": "Error", "message": "payment failed for Bearer [REDACTED]", "stack": "..." },
// "msg": "payment failed for Bearer [REDACTED]" }Sampling, rate limiting and deduplication
The same configuration shapes as the Winston path (see Log Sampling and Rate Limiting and
Log Deduplication), applied through Pino's single hooks.logMethod.
const log = createLogger({
module: 'Ingest',
sampling: { debug: 0.1 },
rateLimit: { info: { max: 100, window: 60000 } },
deduplication: { enabled: true, threshold: 3, window: 60000 },
});
log.getStats(); // { sampling: {...}, deduplication: {...}, levels: {...}, hookErrors: 0 }
log.resetStats();
await log.close(); // releases the sampler/dedup timers, then flushes the destinationfatal is never sampled, rate limited or deduplicated unless the configuration names it
explicitly. Per-level counts are collected even without sampling or deduplication.
Silent mode
Resolution order:
opts.silent, when it is a booleanNTLOGGER_SILENT—1/true/yes→ silent,0/false/no→ not silentNODE_ENV === 'test'or nonemptyNODE_TEST_CONTEXT→ silent
A silent logger runs at Pino level silent, attaches no transport and emits no
records, counts, or observer callbacks. isSilent(opts) is exported for testing the resolution.
const log = createLogger({ module: 'API' }); // silent under Jest (NODE_ENV=test)
const log = createLogger({ module: 'API', silent: false }); // force output in a testNode's built-in test runner is detected through NODE_TEST_CONTEXT; node --test
scripts no longer need NTLOGGER_SILENT=1 for the Pino API.
Fastify integration
Use the plain options factory to retain Fastify's normal route logger types:
const Fastify = require('fastify');
const { createPinoOptions } = require('ntlogger/pino');
const app = Fastify({
logger: createPinoOptions({
service: 'api',
contextProvider: () => requestContext.getStore(), // your AsyncLocalStorage
serializers: {
req: req => ({ method: req.method, url: req.url, headers: req.headers }),
res: reply => ({ statusCode: reply.statusCode, headers: reply.getHeaders() }),
},
}),
});
app.addHook('preHandler', async req => {
req.log.debug({ body: req.body }, 'parsed request body');
});The factory emits JSON options without creating a transport or installing process
handlers. Fastify owns the logger and shutdown. It shares createLogger()'s
service/module fields, context provider, silent-mode resolution, and redaction.
Default request/response serializers retain Fastify's summary fields; headers and
bodies are opt-in. Bodies are available after parsing, not in the initial request
log. See Fastify's logging documentation.
Pass custom serializers into the factory so redaction runs on their output.
Replacing returned formatters, hooks, or serializers (including route-level serializer
overrides) can bypass protection. Ordinary log objects, message strings, and serialized
request/response/error fields are redacted. Arbitrary child bindings are not covered by
the factory's general deep redactor: keep them to identifiers such as reqId and
tenantId, or protect specific binding paths with redact: ['account.secret'].
createLogger() additionally redacts arbitrary child bindings.
Sampling, rate limiting, deduplication, destinations, and process handlers are not
accepted by this factory; use createLogger() for those features. Framework-owned
loggers do not gain ntlogger's getStats(), resetStats(), or close() methods.
Capturing test logs
const { createTestLogger } = require('ntlogger/pino');
const { logger, records } = createTestLogger();
logger.child({ jobId: 'j1' }).warn({ password: 'secret' }, 'retry');
// records[0] includes level: 40, jobId: 'j1', password: '[REDACTED]', msg: 'retry'
await logger.close();Records are captured synchronously after Pino serialization and redaction, with no
console output or worker transport. The helper defaults to level trace and overrides
automatic/environment silence; explicit silent: true still captures nothing.
Sampling and level filtering apply normally. Clear captured records with
records.length = 0 when needed.
Pino lifecycle
Pino's close(timeout?) marks the logger closed immediately, releases root feature
timers, and drains the destination and pending observer work. It is idempotent and
resolves to { drained: true } or { drained: false, error }; inspect that result.
Winston's close continues to reject on flush failures.
Calls after close throw, including disabled levels, previously extracted log methods, and descendants of a closed logger. Closing a child leaves its parent and siblings open and keeps shared feature timers running. Changing a closed logger's level does not reopen it. Shutdown does not end a caller-supplied destination or shut down an application-owned OTel provider.
Tee callbacks and emitted counts
Both createLogger() from ntlogger/pino and the Winston configuration accept onLog:
const { createLogger } = require('ntlogger/pino');
const log = createLogger({
onLog: record => dashboard.publish(record), // your dashboard/WebSocket adapter
});
log.child({ jobId: 'j1' }).warn({ deviceId: 'd1' }, 'retry');
console.log(log.getStats().levels.warn); // 1
await log.close();The callback receives a detached record after redaction, context merging and filtering.
Pino supplies its final serialized fields, including numeric level and msg. Winston
supplies its record before transport-specific formatting, with a level name and message. Winston now also applies the default redactor before dispatching to its
transports; redact: false explicitly disables protection on either backend.
getStats().levels contains zero-initialized trace/debug/info/warn/error/fatal/internal
counts shared with children. These count records prepared for output, not successful
network delivery. Sampling/rate-limit/dedup drops and disabled levels do not count.
Winston's asynchronous calls are reflected after await log.flush().
resetStats() clears level, sampling, dedup and hookErrors counters.
Callback exceptions and rejected promises do not interrupt primary logging; they
increment hookErrors. Logs made by callbacks still reach primary output but do not
invoke observers again, including across asynchronous continuations. Async callbacks
are awaited by Pino close and Winston flush/close, bounded by the shutdown timeout.
onLogMaxPending caps in-flight callback promises (default 100; zero disables callbacks).
At capacity, the newest callback is dropped; the primary log and OTel export continue.
Already-running promises are never cancelled. getStats().onLog reports pending,
dropped, and limit; reset clears drops but preserves the live pending gauge.
Callback mutation cannot change the primary log or its OTel export. Return the delivery
promise if it needs to be included in shutdown; detached background work is not tracked.
Pino 9.14 or newer is required for the serialized-output hook. This raises the old Pino 8 minimum as part of the v4.0.0 installation migration.
Scoped credential values
Scanner defaults include community, authPassword, privPassword, and passphrase,
including case/hyphen/underscore aliases and short or quoted free-text values. For an
opaque credential that might appear in an error without a recognizable key:
const { createLogger, withSecretValues } = require('ntlogger/pino');
const log = createLogger({ module: 'scanner' });
await withSecretValues([credentialValue], async () => {
log.info({ runId, workId }, 'scan started');
await runScan();
});withSecretValues is also exported by the Winston entry point. It scopes literal-value
redaction to the callback and its async descendants, restores the parent afterward,
and handles JSON-escaped forms. Use nonempty strings, at most 128 distinct values of
65536 characters each. Short values can obscure unrelated text. Descendant tasks inherit
secrets until they finish; avoid unrelated background work inside the scope. Explicit
redact: false disables this protection along with other redaction. Context fields
runId, workId, stageId, deviceId, and collectionCycleId now render in Pino's
pretty context suffix as well as remaining searchable in JSON.
Python companion and shared contract
A dependency-free Python 3.10+ companion is maintained in python/.
It provides protect_handler, NdjsonFormatter, SafeFormatter, log_context,
secret_values, SnapshotFilter, and create_test_logger. Existing agent handlers,
per-mode filenames, rotation and permissions remain application-owned. The companion
adds sanitized structured records and exception details without requiring Node.
Install locally with python -m pip install ./python; the new Python package is not
published. Agent adoption is documented rather than applied to its repository.
Shared redaction snapshots and conformance fixtures are exported at
ntlogger/contract/redaction.json and ntlogger/contract/conformance.json. The Python
wheel bundles the same defaults. npm run test:contract verifies generation; both
language suites run the fixtures, including Python TRACE 5 → Pino 10.
OpenTelemetry correlation and export
Use your existing logs SDK provider and processors; ntlogger does not create a global provider or a second pipeline. With a batch processor and OTLP exporter already attached:
const { createLogger } = require('ntlogger/pino');
const log = createLogger({ otel: { loggerProvider, name: 'worker' } });
// Inside your application's active span:
log.info({ jobId: 'j1' }, 'job started');
const result = await log.close(); // calls the supplied provider's forceFlush(), if present
// Shut down the application-owned provider separately when all producers have stopped.Enable a context manager/tracing SDK in the application so the active span is available.
The bridge adds traceId, spanId and traceFlags to Pino JSON and supplies the active
context to the OTel SDK, with correct severity mapping and redacted attributes. Nested
attributes are JSON strings; Pino error fields also map to OTel exception attributes.
No active span means no invented trace IDs. otel: true uses the globally registered
logs provider; without a configured SDK provider that provider is a no-op. Install
@opentelemetry/api and @opentelemetry/api-logs plus your chosen SDK/exporter packages
only when enabling this option. Both backends support the option; do not also enable
the Winston OTel plugin for the same output or records will be exported twice.
createPinoOptions() also accepts onLog and otel. Fastify still owns its lifecycle;
track async callback promises and flush the provider in your application shutdown hook.
It does not gain ntlogger's counters or close method. SDK exporters can report delivery
errors through their own diagnostics rather than rejecting provider flush; a successful
drain alone is not a delivery acknowledgment.
The library tests verify real SDK records and HTTP OTLP delivery to a local collector. The API/worker deployments still need adoption and a check of their own collector routing.
Structured logging lint rule
The optional ESLint plugin supports ESLint 9+ and has no runtime logger dependencies. Enable it for Pino call sites in a flat configuration:
const ntlogger = require('ntlogger/eslint');
module.exports = [{
files: ['src/**/*.js'],
plugins: { ntlogger },
rules: {
'ntlogger/prefer-object-first': ['warn', {
loggerNames: ['log', 'logger', 'req.log', 'request.log'],
}],
},
}];It reports interpolated first-argument template strings. --fix adds searchable fields
while retaining the original message only when each expression is a known primitive
const identifier and there are no other arguments. Calls, getters, mutable values and
complex expressions are reported without a fix. Convert those manually, for example
log.info({ deviceId: device.id }, 'scan started'). Receiver matching is configurable
and syntactic; scope the rule to Pino code, since Winston's message-first methods have
a different contract. Consumer call-site migrations are not performed in this repository.
Process handlers
Opt-in only — importing the library never installs process listeners.
const log = createLogger({
module: 'API',
processHandlers: {
timeout: 4000,
signals: ['SIGTERM', 'SIGINT'],
onShutdown: async reason => { await server.close(); },
},
});
// later
log.uninstallProcessHandlers();installProcessHandlers(logger, opts) and drainLogger(logger, timeout) are also exported
directly for loggers not built by createLogger().
See examples/pino-advanced.js for all of the above in one runnable file.
Custom Levels and Colors
NightTimeLogger provides custom log levels and colors for enhanced logging experience:
Levels:
- trace: 5
- debug: 4
- info: 3
- warn: 2
- error: 1
- fatal: 0
- internal: 6
Colors:
- trace: Light gray
- debug: White
- info: Green
- warn: Yellow
- error: Red
- fatal: Magenta
- internal: Bright yellow
File and Console Formatters
NightTimeLogger supports both file and console log formatters. File-formatted logs are stored in the project's root /logs directory.
License
NightTimeLogger is licensed under the GPL-3.0 License.
Maintenance checks
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.
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.
Packed installation checks: run npm run test:package. This requires npm registry access and uses temporary consumer directories to verify Pino-only and Winston-only installs.
