
Node.js Getting Started with Morgan Logger
Morgan is a popular HTTP request logger middleware for Node.js applications, originally forked from the Express.js project. It’s essential for monitoring application traffic, debugging API calls, and maintaining production logs. In this guide, you’ll learn how to set up Morgan logger, customize its output formats, handle different logging scenarios, and implement it effectively in real-world applications.
How Morgan Works
Morgan sits between your application and incoming HTTP requests as middleware, intercepting each request and response cycle to log relevant information. It operates on Node.js streams, allowing you to pipe log data to files, databases, or external services efficiently.
The middleware captures request details like method, URL, response status codes, response times, and user agents. Morgan uses tokens to format this data, which are essentially placeholders that get replaced with actual request/response values during logging.
Here’s the basic flow:
- Client sends HTTP request to your Node.js server
- Morgan middleware intercepts the request
- Your application processes the request normally
- Morgan captures response data when the response is sent
- Formatted log entry is written to specified output stream
Installation and Basic Setup
Getting Morgan up and running is straightforward. First, install it via npm:
npm install morgan
For a basic Express.js application, here’s the minimal setup:
const express = require('express');
const morgan = require('morgan');
const app = express();
// Use Morgan with 'combined' format
app.use(morgan('combined'));
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
This basic setup will log all HTTP requests to the console using Apache Combined Log Format. Each request will generate output like:
::1 - - [10/Oct/2023:13:55:36 +0000] "GET / HTTP/1.1" 200 12 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
Built-in Formats and Custom Formatting
Morgan comes with several predefined formats that cover most common logging needs:
Format | Description | Use Case |
---|---|---|
combined | Apache Combined Log Format | Production environments, web analytics |
common | Apache Common Log Format | Basic production logging |
dev | Concise colored output | Development debugging |
short | Minimal useful information | Lightweight production logging |
tiny | Most minimal format | High-traffic applications |
Here’s how to use different formats:
// Development - colored output with response time
app.use(morgan('dev'));
// Production - comprehensive logging
app.use(morgan('combined'));
// Custom format using tokens
app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));
You can create completely custom formats using Morgan’s token system:
// Define custom token
morgan.token('id', function getId(req) {
return req.id || 'no-id';
});
// Custom format with timestamp and request ID
const customFormat = ':date[iso] :id :method :url :status :response-time ms';
app.use(morgan(customFormat));
File Logging and Rotation
Logging to files is crucial for production environments. Here’s how to set up file logging with automatic rotation:
const fs = require('fs');
const path = require('path');
const rfs = require('rotating-file-stream');
// Create logs directory if it doesn't exist
const logDirectory = path.join(__dirname, 'logs');
fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory);
// Create rotating write stream
const accessLogStream = rfs.createStream('access.log', {
interval: '1d', // rotate daily
path: logDirectory,
maxFiles: 30, // keep 30 days of logs
compress: 'gzip' // compress rotated files
});
// Log to file
app.use(morgan('combined', { stream: accessLogStream }));
// Also log errors to console in development
if (process.env.NODE_ENV !== 'production') {
app.use(morgan('dev'));
}
For this setup, you’ll need the rotating-file-stream package:
npm install rotating-file-stream
Conditional Logging and Filtering
Not every request needs to be logged. Morgan allows conditional logging based on request/response characteristics:
// Skip logging for successful requests in production
const skip = (req, res) => {
if (process.env.NODE_ENV === 'production') {
return res.statusCode < 400; // Only log errors
}
return false; // Log everything in development
};
app.use(morgan('combined', { skip }));
// Log only POST requests
app.use(morgan('dev', {
skip: (req, res) => req.method !== 'POST'
}));
// Skip static assets
app.use(morgan('combined', {
skip: (req, res) => req.url.match(/\.(js|css|png|jpg|gif|ico|svg)$/)
}));
Advanced Configuration and Integration
For enterprise applications, you’ll often need more sophisticated logging setups. Here’s an advanced configuration that demonstrates multiple loggers, JSON formatting, and integration with external services:
const winston = require('winston');
// Create Winston logger for structured logging
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' })
]
});
// Custom stream that writes to Winston
const winstonStream = {
write: (message) => {
logger.info(message.trim());
}
};
// JSON format for API logging
morgan.token('body', (req) => JSON.stringify(req.body));
const jsonFormat = JSON.stringify({
method: ':method',
url: ':url',
status: ':status',
responseTime: ':response-time',
contentLength: ':res[content-length]',
userAgent: ':user-agent',
body: ':body',
timestamp: ':date[iso]'
});
// Multiple logging strategies
app.use(express.json()); // Parse JSON bodies for logging
// Error logging to Winston
app.use(morgan('combined', {
stream: winstonStream,
skip: (req, res) => res.statusCode < 400
}));
// API request logging in JSON format
app.use('/api', morgan(jsonFormat, {
stream: fs.createWriteStream('./logs/api-requests.log', { flags: 'a' })
}));
// Development console logging
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'));
}
Performance Considerations and Best Practices
Morgan's performance impact varies depending on configuration. Here are key considerations:
Configuration | Performance Impact | Memory Usage | Recommended For |
---|---|---|---|
Console logging ('dev') | Low | Minimal | Development only |
File logging ('combined') | Medium | Low | Production |
Custom tokens with DB queries | High | High | Avoid in high-traffic |
Filtered logging (skip function) | Low-Medium | Low | Production with selective logging |
Best practices for production environments:
- Use file logging instead of console output to avoid blocking I/O
- Implement log rotation to prevent disk space issues
- Skip static asset requests to reduce log volume
- Use buffered streams for high-traffic applications
- Avoid complex custom tokens that perform database queries
- Consider using separate loggers for different route groups
Troubleshooting Common Issues
Here are frequent problems and their solutions:
Missing request body in logs: Morgan captures request data before body parsing middleware. Ensure proper middleware order:
// Correct order
app.use(express.json());
app.use(morgan(':method :url :body')); // Now req.body is available
Log file permissions errors: Ensure your Node.js process has write permissions to the log directory:
const fs = require('fs');
try {
fs.accessSync('./logs', fs.constants.W_OK);
} catch (err) {
console.error('Cannot write to logs directory:', err.message);
process.exit(1);
}
Memory leaks with custom streams: Always properly close streams and handle errors:
const logStream = fs.createWriteStream('access.log', { flags: 'a' });
logStream.on('error', (err) => {
console.error('Log stream error:', err);
});
process.on('SIGTERM', () => {
logStream.end();
});
Real-world Use Cases and Examples
Here are practical scenarios where Morgan proves invaluable:
API Rate Limiting Analysis:
// Track API usage patterns
morgan.token('user-id', (req) => req.user?.id || 'anonymous');
app.use('/api', morgan(':date[iso] :user-id :method :url :status :response-time ms', {
stream: fs.createWriteStream('./logs/api-usage.log', { flags: 'a' })
}));
Security Monitoring:
// Log suspicious requests
const securityLogger = morgan('combined', {
skip: (req, res) => {
const suspicious = req.url.includes('../') ||
req.url.includes('script') ||
res.statusCode === 401 ||
res.statusCode === 403;
return !suspicious;
},
stream: fs.createWriteStream('./logs/security.log', { flags: 'a' })
});
app.use(securityLogger);
Performance Monitoring:
// Track slow endpoints
app.use(morgan('combined', {
skip: (req, res) => {
const responseTime = parseFloat(res.get('X-Response-Time') || '0');
return responseTime < 1000; // Log only requests taking >1 second
}
}));
Comparison with Alternatives
While Morgan is the most popular choice, other logging solutions exist:
Tool | Pros | Cons | Best For |
---|---|---|---|
Morgan | Simple, lightweight, Express-focused | Limited structure, HTTP-only | Express apps, HTTP logging |
Winston | Structured logging, multiple transports | More complex setup | Application-wide logging |
Bunyan | JSON-only, fast, structured | Less flexible formatting | Microservices, JSON-first apps |
Pino | Extremely fast, low overhead | Limited built-in features | High-performance applications |
For comprehensive application logging, combining Morgan with Winston provides the best of both worlds:
// Morgan for HTTP requests
app.use(morgan('combined', { stream: accessLogStream }));
// Winston for application events
const appLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'app.log' })
]
});
app.get('/api/data', (req, res) => {
appLogger.info('Processing data request', { userId: req.user.id });
// Process request...
});
Morgan excels at HTTP request logging and integrates seamlessly with Express.js applications. For additional logging needs like application events, errors, or structured data, combine it with more comprehensive logging libraries. The official Morgan documentation provides extensive examples and API details at https://github.com/expressjs/morgan.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.