nodejs-logging
SKILL.md
Node.js Logging
Deep Knowledge: Use
mcp__documentation__fetch_docswith technology:nodejsfor 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-loggingskill instead - Java/Spring Boot: Use
slf4jandlogbackskills instead - Browser/frontend logging: Different APIs and requirements
- Simple CLI tools: console.log may be sufficient
- Framework-specific details: Use
winstonorpinoskills 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
Repository
claude-dev-suit…ev-suiteGitHub Stars
2
First Seen
9 days ago
Security Audits
Installed on
cursor9
gemini-cli9
amp9
cline9
github-copilot9
codex9