BLOG POSTS
Node.js Getting Started with Morgan Logger

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.

Leave a reply

Your email address will not be published. Required fields are marked