logger-logs
v2.0.0
Published
logger-logs is a fast, lightweight, structured logger for Node.js, TypeScript & JavaScript. Zero-dependency logging with log levels, JSON output, file/line numbers, timestamps, child loggers, pluggable transports, and built-in sensitive-data redaction.
Maintainers
Keywords
Readme
📜 logger-logs
The fast, zero-dependency structured logger for Node.js, TypeScript & JavaScript
Levels · JSON & pretty output · file & line numbers · child loggers · async context · rotating file transport · automatic secret redaction — all with zero runtime dependencies.
Quick Start · Features · API · Examples · FAQ · Compare
Why logger-logs?
console.log doesn't scale — no levels, no structure, no context, no redaction, no way to route output. logger-logs gives you everything a production app needs in a tiny, dependency-free package you can read in one sitting.
// ❌ console.log — flat, unstructured, leaks secrets, no source
console.log('user login', { id: 42, password: 'hunter2' });
// ✅ logger-logs — leveled, structured, redacted, with source + timestamp
import { log } from 'logger-logs';
log.info('user login', { id: 42, password: 'hunter2' });12:34:56.789 INFO user login id=42 password=[REDACTED] (src/auth.ts:18)…and the same call in production (NODE_ENV=production) emits machine-readable JSON your log platform ingests natively:
{"level":"info","time":"2026-06-06T12:34:56.789Z","pid":4242,"hostname":"web-1","msg":"user login","id":42,"password":"[REDACTED]","caller":"src/auth.ts:18"}📦 Installation
npm install logger-logsAlso available via yarn add logger-logs · pnpm add logger-logs · bun add logger-logs — requires Node.js 14+, works in both ESM (import) and CommonJS (require).
🚀 Quick Start
import { log, createLogger } from 'logger-logs';
// 1) Use the ready-made default logger
log.info('server started', { port: 3000 });
log.error('payment failed', new Error('gateway timeout'));
// 2) Or configure your own
const logger = createLogger({
level: 'debug',
name: 'api',
context: { service: 'checkout' },
});
logger.debug('processing order', { orderId: 'o-123' });const { createLogger } = require('logger-logs');
const logger = createLogger({ level: 'info' });
logger.info('hello from CommonJS');The package ships dual ESM + CommonJS builds with a correct exports map, so both module systems work out of the box.
✨ Features
| | |
| --- | --- |
| 🎚️ Log levels | trace · debug · info · warn · error · fatal with a runtime-adjustable threshold |
| 🧱 Structured output | pretty (colorized), json (NDJSON), and logfmt formatters |
| 📍 Source & time | File name, line number, ISO timestamp, pid & hostname on every entry |
| 🌳 Child loggers | Bind service / requestId once, inherit transports & config |
| 🧵 Async context | Auto-propagate request & trace IDs via AsyncLocalStorage |
| 🔌 Transports | Console (stdout/stderr split) + rotating file, or bring your own |
| 🔒 Redaction | Masks passwords, tokens & auth headers — deep & case-insensitive |
| 🛡️ Safe serialization | Circular refs, BigInt, functions & Error.cause never crash logging |
| ⚡ Performance | Lazy call-site capture, sampling, hooks, timers |
| 🟦 TypeScript-first | Written in TS, ships .d.ts — no @types needed |
| 🪶 Zero dependencies | Nothing to audit, tiny install footprint |
🎚️ Log Levels
Six severities — trace (10), debug (20), info (30), warn (40), error (50), fatal (60). Anything below the logger's level is dropped before any work happens.
const logger = createLogger({ level: 'warn' });
logger.info('skipped'); // below threshold → no output
logger.error('emitted');
logger.setLevel('debug'); // change at runtime
logger.isLevelEnabled('trace'); // → false💡 Set the default level from the environment with
LOG_LEVEL=debug.
🧱 Structured Logging & Formats
Pass a message plus any number of context objects. Objects are merged into structured fields; Errors are serialized under err automatically.
logger.info('payment captured',
{ amount: 4200, currency: 'USD' },
{ gateway: 'stripe' },
);Pick the output format that fits the destination:
| Format | Best for | Looks like |
| --- | --- | --- |
| pretty | local development | 12:34 INFO msg key=value |
| json | Datadog · Loki · ELK · CloudWatch | {"level":"info",...} |
| logfmt | Grafana · Splunk | level=info msg="..." key=value |
createLogger({ format: 'json' });
createLogger({ format: 'logfmt' });
createLogger({ format: 'pretty', colors: true });🧵 Async Context (Request / Trace IDs)
Stop threading a logger through every function. Bind context once at the edge of a request and every log in that async call tree carries it automatically — powered by Node's AsyncLocalStorage.
import { log, runWithContext, bindContext } from 'logger-logs';
app.use((req, _res, next) =>
runWithContext({ requestId: req.id, traceId: req.headers['x-trace-id'] }, next),
);
// Anywhere downstream — no logger passing required:
function chargeCard() {
log.info('charging card'); // → automatically includes requestId + traceId
bindContext({ userId: 'u-42' }); // add more to the current scope
}Perfect for correlating logs across a request and emitting OpenTelemetry-style trace_id / span_id fields.
🌳 Child Loggers
const logger = createLogger({ name: 'api' });
const reqLog = logger.child({ requestId: 'req-9f2', userId: 'u-42' });
reqLog.info('handling request'); // includes requestId + userId
reqLog.error('db error', new Error('deadlock'));Children inherit their parent's transports and configuration, and can be nested arbitrarily.
🔌 Transports
Transports decide where logs go. Each can carry its own level and formatter.
import { createLogger, consoleTransport, fileTransport, jsonFormatter } from 'logger-logs';
const logger = createLogger({
transports: [
consoleTransport(),
fileTransport({
path: 'logs/app.log',
format: jsonFormatter,
level: 'info',
maxSize: 10 * 1024 * 1024, // rotate at 10 MB
maxFiles: 5, // keep app.log.1 … app.log.5
}),
],
});
await logger.flush(); // drain before exit; logger.close() also releases handlesA transport is just an object with a write method — ship logs anywhere (HTTP, queue, socket):
const httpTransport = {
name: 'http',
write(line, entry) {
fetch('https://logs.example.com', { method: 'POST', body: line });
},
};
createLogger({ transports: [httpTransport] });🔒 Sensitive Data Redaction
Secrets should never reach your logs. Common keys are redacted by default — recursively, case-insensitively, and without mutating your original objects.
logger.info('login', { user: 'alice', password: 'hunter2', token: 'abc123' });
// → user=alice password=[REDACTED] token=[REDACTED]
createLogger({ redact: ['email', 'phone'] }); // add your own keys
createLogger({ redact: { keys: ['email'], censor: '***' } }); // custom maskDefault keys include password, token, apiKey, authorization, secret, cookie, ssn, creditCard and more.
🛠️ Real-World Example (Express)
import express from 'express';
import { createLogger, runWithContext, fileTransport, consoleTransport } from 'logger-logs';
const logger = createLogger({
name: 'api',
level: process.env.LOG_LEVEL ?? 'info',
transports: [
consoleTransport(),
fileTransport({ path: 'logs/api.log', maxSize: 5_000_000, maxFiles: 10 }),
],
});
const app = express();
// Attach a request-scoped logger + correlation id to every request.
app.use((req, res, next) => {
const requestId = crypto.randomUUID();
runWithContext({ requestId, method: req.method, path: req.path }, () => {
const timer = logger.startTimer();
res.on('finish', () =>
timer.done('request completed', { status: res.statusCode }),
);
next();
});
});
app.get('/health', (_req, res) => {
logger.debug('health check');
res.json({ ok: true });
});
app.listen(3000, () => logger.info('server listening', { port: 3000 }));12:00:00.001 INFO [api] server listening port=3000
12:00:03.114 INFO [api] request completed requestId=8f3c… method=GET path=/health status=200 durationMs=12🪝 Advanced: Hooks, Serializers & Sampling
createLogger({
// Drop or mutate entries right before they're written.
hooks: [(entry) => (entry.context.healthcheck ? false : undefined)],
// Transform values bound under a key (pino-style serializers).
serializers: { user: (u) => ({ id: u.id }) },
// Emit only 10% of info/debug logs; warn and above are always emitted.
sampleRate: 0.1,
// Skip stack capture on hot paths for max throughput.
captureCallSite: false,
});📖 API Reference
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| level | LevelName | LOG_LEVEL or 'info' | Minimum level to emit |
| name | string | – | Logger name added to every entry |
| context | object | {} | Base fields merged into every entry |
| format | 'pretty' \| 'json' \| 'logfmt' \| Formatter | env-based | Output format |
| colors | boolean \| 'auto' | 'auto' | ANSI colors for pretty |
| captureCallSite | boolean | true | Capture file/line (disable on hot paths) |
| transports | Transport[] | [consoleTransport()] | Where logs go |
| redact | string[] \| RedactionOptions | defaults | Keys to mask |
| serializers | Record<string, Serializer> | {} | Per-key value transforms |
| hooks | BeforeLogHook[] | [] | Pre-write middleware |
| sampleRate | number | 1 | Fraction of info/debug to emit |
| Method | Description |
| --- | --- |
| trace / debug / info / warn / error / fatal(...args) | Log at a level |
| log(level, ...args) | Log at an explicit level |
| child(bindings, { name? }) | Derive a context-bound logger |
| setLevel(level) · isLevelEnabled(level) | Inspect/change threshold |
| startTimer() → { done(msg?, context?) } | Measure & log durations |
| flush() · close() | Drain / release transports |
log (default instance) · createLogger · Logger · consoleTransport · fileTransport · jsonFormatter · prettyFormatter · logfmtFormatter · runWithContext · bindContext · getContext · serializeError · safeStringify · LEVELS · logger (v1 compat).
🔄 Migrating from v1
The original logger() function still works unchanged — no breaking changes to your existing calls:
import { logger } from 'logger-logs';
logger('still works', { like: 'before' });
// → Path::/abs/file.js, Line::2, Message::still works { "like": "before" }For new code, prefer the structured API (log.info(...) / createLogger(...)). See the CHANGELOG for the full v2 release notes.
📊 Comparison with winston & pino
| | logger-logs | winston | pino | | --- | :---: | :---: | :---: | | Runtime dependencies | 0 | many | few | | Log levels | ✅ | ✅ | ✅ | | JSON output | ✅ | ✅ | ✅ | | Pretty dev output | ✅ built-in | plugin | separate pkg | | File & line numbers | ✅ | ❌ | ❌ | | Child loggers | ✅ | ✅ | ✅ | | Async context | ✅ built-in | ❌ | via plugin | | Secret redaction | ✅ built-in | ❌ | ✅ | | Dual ESM + CJS + types | ✅ | partial | ✅ | | Install footprint | tiny | large | small |
logger-logs aims to be the simplest structured logger that still covers levels, transports, context, and redaction — a focused winston / pino alternative for apps that want power without the weight.
❓ FAQ
🤝 Contributing
Contributions are welcome!
npm install
npm run build # bundle ESM + CJS + types
npm test # run the Jest suite
npm run lint # eslint
npm run typecheck # tsc --noEmitFound a bug or have an idea? Open an issue or a pull request.
📩 Support & License
- 🐛 Bugs & requests: GitHub Issues
- ✉️ Contact: [email protected]
- ⭐ If logger-logs helps you, please star it on GitHub — it helps others discover it.
Released under the ISC License © Satyam Kushwaha.
logger-logs — node.js logger · javascript logger · typescript logger · structured logging · json logger · winston alternative · pino alternative · log levels · child logger · log redaction · async context logging · zero-dependency logger · rotating file logger
If this package saved you time, a ⭐ on GitHub means a lot.
