Skip to content

Repository files navigation

🪵 winston-logger

A production-ready, fully structured Node.js logging system built on Winston.

Separate log files per type · 9 custom levels · Sensitive field redaction · Correlation IDs · Express middleware included

Node.js Winston License PRs Welcome


Table of Contents


Features

Feature Details
9 custom log levels fatal error security warn audit info http debug trace
Separated log files Each log type routes to its own file automatically
Sensitive field redaction Passwords, tokens, secrets never reach disk
Correlation IDs Tie all logs from one request together across modules
Child loggers Scoped per module — [auth], [database], [http]
Auto-rotating files Daily rotation with configurable size limit and retention
Express middleware Drop-in requestLogger and errorLogger
Performance timer startTimer() / stop() for measuring operation duration
Dev vs prod formats Colorized readable output in dev, clean JSON in prod
Exception handling Uncaught exceptions and unhandled rejections auto-captured

Project Structure

winston-logger/
├── src/
│   ├── logger/
│   │   ├── index.js        # Main export — logger + all helpers
│   │   ├── levels.js       # Custom log level definitions
│   │   ├── formats.js      # prodFormat, devFormat, auditFormat
│   │   └── transports.js   # File + console transport configs
│   ├── app.js              # Demo — runs all features
│   └── middleware.js       # Express request/error logging middleware
├── logs/                   # Auto-created at runtime (git-ignored)
├── .env                    # Environment config (copy from .env.example)
├── .env.example            # Template — safe to commit
├── .gitignore
└── package.json

Quick Start

# 1. Clone
git clone https://github.com/HaronKhalid/winston-logger.git
cd winston-logger

# 2. Install
npm install

# 3. Configure
cp .env.example .env
# Edit .env to set SERVICE_NAME, LOG_LEVEL, etc.

# 4. Run the demo
npm run dev

After running, check the logs/ folder — each log type is in its own file.


Log Levels

Custom 9-level scale, ordered from most to least critical:

Level Priority Color When to use
fatal 0 🔴 Bold Red System crash — unrecoverable, immediate shutdown
error 1 🔴 Red Operation failed — investigate promptly
security 2 🟣 Magenta Auth failure, access violation, anomaly detected
warn 3 🟡 Yellow Unexpected but recovered — watch for trends
audit 4 🔵 Cyan Compliance trail — who did what and when
info 5 🟢 Green Normal business events, milestones
http 6 🔵 Blue HTTP request/response cycle
debug 7 ⚪ White Developer diagnostics — dev only
trace 8 ⬜ Grey Finest detail — function entry/exit, loops

Winston's level filter is inclusive: setting LOG_LEVEL=info captures fatal through info, excluding http, debug, and trace.


Log Files

File Contents Retention
combined-YYYY-MM-DD.log Every log entry (all levels) 30 days, rotates daily
error.log fatal and error only Permanent (manual cleanup)
security.log security and audit only Permanent — compliance critical
http.log http level only 30 days, rotates daily
debug.log debug and trace only Dev environment only
exceptions.log Uncaught exceptions Permanent
rejections.log Unhandled Promise rejections Permanent

Configuration

Copy .env.example to .env and adjust:

# Environment
NODE_ENV=development           # development | production | test

# Logger
LOG_LEVEL=debug                # trace | debug | info | http | audit | warn | security | error | fatal
SERVICE_NAME=my-app            # appears in every log entry
SERVICE_VERSION=1.0.0

# File paths & rotation
LOG_DIR=logs
LOG_MAX_SIZE=20m               # rotate file after this size
LOG_MAX_FILES=30d              # delete files older than this

# Console
LOG_CONSOLE_SILENT=false       # set true to suppress all console output

Environment presets

Environment Default level Console Debug file
development debug ✅ Colorized ✅ Created
production info ✅ JSON ❌ Skipped
test debug ❌ Silent ✅ Created

API Reference

Import

const {
  logger,           // root logger
  authLogger,       // child: { module: 'auth' }
  dbLogger,         // child: { module: 'database' }
  httpLogger,       // child: { module: 'http' }
  auditLogger,      // child: { module: 'audit' }
  cacheLogger,      // child: { module: 'cache' }
  jobLogger,        // child: { module: 'jobs' }

  createRequestLogger,  // factory: request-scoped child with correlationId
  logRequest,           // helper: log HTTP request/response
  logSecurity,          // helper: log security events → security.log
  logAudit,             // helper: log audit trail → security.log
  logError,             // helper: log errors with full stack context
  startTimer,           // helper: measure operation duration
} = require('./src/logger');

logger[level](message, meta?)

Log at any level directly:

logger.info('Server started', { port: 3000 });
logger.warn('Memory usage high', { usedMb: 480, limitMb: 512 });
logger.error('Redis unreachable', { host: 'localhost', port: 6379 });
logger.fatal('Critical config missing', { key: 'DATABASE_URL' });
logger.debug('Cache lookup', { key: 'user:u_001', hit: false });
logger.trace('Entering getUserById()', { userId: 'u_001' });

logger.child(meta)

Create a scoped logger that adds fixed metadata to every entry:

const paymentsLogger = logger.child({ module: 'payments' });
paymentsLogger.info('Charge succeeded', { amount: 9900, currency: 'USD' });
// → { level: 'info', module: 'payments', message: 'Charge succeeded', amount: 9900, ... }

createRequestLogger(existingId?)

Create a request-scoped logger with a correlation ID. All logs share the same ID, making it easy to trace a single request across dozens of log lines.

// In Express middleware:
app.use((req, res, next) => {
  req.log = createRequestLogger(req.headers['x-request-id']);
  next();
});

// In route handler:
req.log.info('Fetching user',  { userId: req.params.id });
req.log.info('User found',     { email: user.email });
req.log.debug('Cache primed',  { key: `user:${req.params.id}` });
// All three share the same correlationId automatically

logSecurity(event, meta)

Log to the security level — routed to security.log with integrity tagging:

logSecurity('Failed login attempt', {
  userId:   'u_456',
  ip:       '203.0.113.42',
  attempts: 5,
  blocked:  true,
});

logSecurity('Unauthorized access', {
  userId:   'u_321',
  resource: '/admin/users',
  method:   'DELETE',
});

logAudit(action, meta)

Log to the audit level — compliance trail, routed to security.log:

logAudit('User role changed', {
  actor:      'admin_1',
  targetUser: 'u_789',
  oldRole:    'viewer',
  newRole:    'editor',
  reason:     'Promotion approved by manager',
});

logAudit('Record deleted', {
  actor:     'u_123',
  table:     'invoices',
  recordId:  'inv_9901',
});

logError(message, err, meta?)

Log an error with full stack trace always captured:

try {
  await db.query(sql, params);
} catch (err) {
  logError('Database query failed', err, {
    query:      sql,
    params,
    durationMs: 5012,
  });
}

startTimer(label, meta?)

Profile how long any operation takes. Returns a stop() function:

const timer = startTimer('db-query', { table: 'orders' });
const rows  = await db.query('SELECT * FROM orders');
timer.stop({ rows: rows.length });
// → debug log: "db-query completed" { durationMs: 42, table: 'orders', rows: 500 }

logRequest(req, res, ms)

Log a completed HTTP request. Status code drives the log level automatically:

Status Level
2xx / 3xx http
4xx warn
5xx error
// Manual usage:
const start = Date.now();
res.on('finish', () => logRequest(req, res, Date.now() - start));

// Or just use the middleware (see below) — it handles this automatically.

Express Integration

Add two lines to your Express app:

const express = require('express');
const { requestLogger, errorLogger } = require('./src/middleware');

const app = express();

app.use(requestLogger);   // ← before all routes

app.get('/api/users', (req, res) => {
  req.log.info('Fetching users');   // req.log is available everywhere
  res.json({ users: [] });
});

app.use(errorLogger);     // ← after all routes (4-argument signature is required by Express)

requestLogger does the following automatically on every request:

  • Generates a correlationId (UUID) or reads it from X-Request-Id header
  • Attaches req.log — a child logger with the correlation ID pre-bound
  • Sets X-Request-Id response header
  • Logs the completed request with method, URL, status, duration, IP, and user agent

Log Output Examples

Development console (colorized)

[09:44:47] INFO     [auth]: User login successful  {"userId":"u_123","ip":"192.168.1.10"}
[09:44:47] ERROR    [database]: Query timeout  {"query":"SELECT * FROM orders","durationMs":5012}
[09:44:47] SECURITY [auth]: Failed login attempt  {"userId":"u_456","attempts":5,"blocked":true}
[09:44:47] AUDIT    [audit]: User role changed  {"actor":"admin_1","oldRole":"viewer","newRole":"editor"}
[09:44:47] HTTP     [http] {_xyz_789}: HTTP request  {"method":"GET","status":200,"durationMs":24}

Production file (JSON)

{
  "level": "error",
  "message": "Database query timeout",
  "service": "my-app",
  "version": "1.0.0",
  "environment": "production",
  "module": "database",
  "timestamp": "2025-05-15T09:44:47.880+00:00",
  "pid": 1234,
  "host": "prod-server-01",
  "error": "Query exceeded 5000ms",
  "stack": "Error: Query exceeded 5000ms\n    at ...",
  "query": "SELECT * FROM orders WHERE status = ?",
  "durationMs": 5012
}

security.log entry

{
  "level": "security",
  "logType": "security",
  "integrityVersion": "1",
  "message": "Failed login attempt",
  "userId": "u_456",
  "ip": "203.0.113.42",
  "attempts": 5,
  "blocked": true,
  "timestamp": "2025-05-15T09:44:47.875+00:00",
  "service": "my-app",
  "pid": 1234,
  "host": "prod-server-01"
}

Security & Redaction

The following field name patterns are automatically redacted before any log is written to any destination — including console, files, and remote transports:

password · token · secret · authorization · creditcard · ssn · apikey · api_key

Matching is case-insensitive and substring-based:

logger.info('User signup', {
  userId:      'u_999',
  email:       'jane@example.com',  // ✅ logged as-is
  password:    'hunter2',           // → [REDACTED]
  accessToken: 'eyJhbG...',         // → [REDACTED]  (contains "token")
  apiKey:      'sk-abc123',         // → [REDACTED]  (contains "apikey")
});

To add more sensitive field patterns, edit SENSITIVE_KEYS in src/logger/formats.js.


Environment Guide

Development

npm run dev
# NODE_ENV=development — debug level, colorized console, debug.log created

Production

npm run prod
# NODE_ENV=production — info level, JSON console, debug.log skipped

Custom level

LOG_LEVEL=warn node src/app.js
# Only fatal, error, security, warn are written

Silence console (container environments)

LOG_CONSOLE_SILENT=true

Useful when your container runtime (Docker, Kubernetes) already collects stdout and you want logs only in files.


Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feat/your-feature
  3. Commit with a clear message: git commit -m "feat: add MongoDB transport"
  4. Push and open a Pull Request

Please follow the existing code style — inline comments explaining why, not just what.


License

MIT © 2026 — see LICENSE for full text.

About

A production-ready, fully structured Node.js logging system.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages