nodejs-logging

SKILL.md

Node.js Logging

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: nodejs for comprehensive documentation.

Library Comparison

Library Performance Features Best For
Pino Fastest (10x+ faster) JSON-native, low overhead High-performance APIs
Winston Good Flexible transports, formatting Enterprise apps, flexibility
Bunyan Good JSON-native, streams Legacy projects
console Basic Built-in, no deps Simple scripts, debugging

Pino (Recommended for Performance)

Installation

npm install pino pino-pretty  # pino-pretty for dev

Basic Setup

import pino from 'pino';

const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  transport: process.env.NODE_ENV !== 'production'
    ? { target: 'pino-pretty', options: { colorize: true } }
    : undefined,
});

// Usage
logger.info('Server started');
logger.info({ userId: 123, action: 'login' }, 'User logged in');
logger.error({ err }, 'Database connection failed');

Child Loggers (Request Context)

import { Request, Response, NextFunction } from 'express';

export function requestLogger(req: Request, res: Response, next: NextFunction) {
  req.log = logger.child({
    requestId: crypto.randomUUID(),
    method: req.method,
    path: req.path,
  });

  req.log.info('Request started');

  res.on('finish', () => {
    req.log.info({ statusCode: res.statusCode }, 'Request completed');
  });

  next();
}

Express Integration

import express from 'express';
import pino from 'pino';
import pinoHttp from 'pino-http';

const app = express();
const logger = pino();

app.use(pinoHttp({ logger }));

app.get('/users/:id', (req, res) => {
  req.log.info({ userId: req.params.id }, 'Fetching user');
  // ...
});

NestJS Integration

// logger.module.ts
import { Module, Global } from '@nestjs/common';
import { LoggerModule as PinoLoggerModule } from 'nestjs-pino';

@Global()
@Module({
  imports: [
    PinoLoggerModule.forRoot({
      pinoHttp: {
        level: process.env.LOG_LEVEL || 'info',
        transport: process.env.NODE_ENV !== 'production'
          ? { target: 'pino-pretty' }
          : undefined,
      },
    }),
  ],
})
export class LoggerModule {}

// usage in service
import { Logger } from 'nestjs-pino';

@Injectable()
export class UserService {
  constructor(private readonly logger: Logger) {}

  findUser(id: string) {
    this.logger.log({ userId: id }, 'Finding user');
  }
}

Winston

Installation

npm install winston

Basic Setup

import winston from 'winston';

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: { service: 'my-service' },
  transports: [
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' }),
  ],
});

// Pretty console in development
if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.combine(
      winston.format.colorize(),
      winston.format.simple()
    ),
  }));
}

export { logger };

Usage

import { logger } from './logger';

// Basic logging
logger.info('Server started on port 3000');
logger.warn('Cache miss for key: user:123');
logger.error('Database connection failed', { error: err });

// With metadata
logger.info('User action', {
  userId: user.id,
  action: 'purchase',
  amount: 99.99,
});

Child Loggers

const orderLogger = logger.child({ module: 'orders' });
orderLogger.info('Order created', { orderId: '12345' });
// Output: { module: 'orders', orderId: '12345', message: 'Order created', ... }

Custom Transports

import winston from 'winston';
import DailyRotateFile from 'winston-daily-rotate-file';

const rotateTransport = new DailyRotateFile({
  filename: 'logs/app-%DATE%.log',
  datePattern: 'YYYY-MM-DD',
  maxSize: '100m',
  maxFiles: '30d',
  zippedArchive: true,
});

rotateTransport.on('rotate', (oldFilename, newFilename) => {
  logger.info('Log file rotated', { oldFilename, newFilename });
});

logger.add(rotateTransport);

Express Middleware

import expressWinston from 'express-winston';

app.use(expressWinston.logger({
  winstonInstance: logger,
  meta: true,
  msg: 'HTTP {{req.method}} {{req.url}} {{res.statusCode}} {{res.responseTime}}ms',
  expressFormat: false,
  colorize: false,
}));

app.use(expressWinston.errorLogger({
  winstonInstance: logger,
}));

Log Levels

Level Priority Usage
fatal 60 App crash imminent
error 50 Error conditions
warn 40 Warning conditions
info 30 Normal operations
debug 20 Debug information
trace 10 Very detailed tracing
// Pino
logger.fatal('Uncaught exception');
logger.error('Failed to connect to database');
logger.warn('Deprecated API called');
logger.info('Server started');
logger.debug('Query executed');
logger.trace('Entering function');

// Winston (no fatal, uses error)
logger.error('Critical error');
logger.warn('Warning');
logger.info('Info');
logger.verbose('Verbose');  // between info and debug
logger.debug('Debug');
logger.silly('Trace-level'); // lowest

Structured Logging Best Practices

DO

// Include context
logger.info({ userId, orderId, action: 'checkout' }, 'Order placed');

// Log errors properly
logger.error({ err, userId }, 'Payment failed');

// Use child loggers for context
const reqLogger = logger.child({ requestId, userId });
reqLogger.info('Processing request');

DON'T

// Don't log sensitive data
logger.info({ password, creditCard }, 'User registered'); // BAD!

// Don't use string interpolation for objects
logger.info(`User ${JSON.stringify(user)} logged in`); // BAD!

// Don't log PII without masking
logger.info({ email: user.email }); // Consider masking

Sensitive Data Handling

import pino from 'pino';

const logger = pino({
  redact: {
    paths: ['password', 'creditCard', 'req.headers.authorization'],
    censor: '[REDACTED]',
  },
});

// Logs: { password: '[REDACTED]', username: 'john' }
logger.info({ password: 'secret123', username: 'john' });

Production Configuration

Environment Variables

LOG_LEVEL=info          # Log level
LOG_FORMAT=json         # json or pretty
LOG_FILE=logs/app.log   # File output

Docker/Container Logging

// Log to stdout (Docker captures this)
const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  // No file transport - Docker handles log collection
});

// Ensure logs are flushed
process.on('SIGTERM', () => {
  logger.info('Received SIGTERM, shutting down');
  logger.flush();
  process.exit(0);
});

Correlation IDs

import { AsyncLocalStorage } from 'async_hooks';

const asyncLocalStorage = new AsyncLocalStorage<{ requestId: string }>();

export function withRequestContext(req: Request, res: Response, next: NextFunction) {
  const requestId = req.headers['x-request-id'] || crypto.randomUUID();
  asyncLocalStorage.run({ requestId }, () => next());
}

export function getRequestId(): string | undefined {
  return asyncLocalStorage.getStore()?.requestId;
}

// In logger
const logger = pino({
  mixin() {
    return { requestId: getRequestId() };
  },
});

When NOT to Use This Skill

  • Python applications: Use python-logging skill instead
  • Java/Spring Boot: Use slf4j and logback skills instead
  • Browser/frontend logging: Different APIs and requirements
  • Simple CLI tools: console.log may be sufficient
  • Framework-specific details: Use winston or pino skills for deep dives

Anti-Patterns

Anti-Pattern Why It's Bad Solution
Using console.log in production No control, no persistence Use Winston or Pino
Synchronous file logging Blocks event loop Use async transports
Logging entire request/response objects Contains sensitive data, huge size Log only necessary fields
No request correlation IDs Can't trace requests across services Use AsyncLocalStorage or child loggers
String interpolation in logs Always evaluated Use template strings or parameterized logging
Missing error stack traces Loses debugging context Always include error object: logger.error({ err })

Quick Troubleshooting

Issue Cause Solution
Performance degradation Synchronous logging Switch to Pino or async Winston transports
Logs not appearing Wrong log level Check LOG_LEVEL environment variable
Unreadable JSON in console No pretty-printer Add pino-pretty or Winston simple format for dev
Memory leak Log files growing unbounded Configure log rotation (daily-rotate-file)
Missing request context No correlation ID Use child loggers or AsyncLocalStorage
Duplicate logs Multiple loggers/transports Consolidate to single logger instance

Reference

Weekly Installs
10
GitHub Stars
2
First Seen
9 days ago
Installed on
cursor9
gemini-cli9
amp9
cline9
github-copilot9
codex9