Morgan

HTTP request logging middleware specifically for Express.js. Specialized in access log generation, providing predefined formats like common, combined, and dev. Often used in combination with other logging libraries (Winston or Pino).

HTTP ClientExpress.jsMiddlewareAccess LogNode.js

GitHub Overview

expressjs/morgan

HTTP request logger middleware for node.js

Stars8,080
Watchers91
Forks536
Created:February 8, 2014
Language:JavaScript
License:MIT License

Topics

expressjavascriptloggernodejs

Star History

expressjs/morgan Star History
Data as of: 7/17/2025, 10:32 AM

Library

Morgan

Overview

Morgan is a specialized HTTP request logging middleware designed exclusively for Express.js applications. It focuses on generating comprehensive access logs for web applications, providing predefined formats like common, combined, and dev. Frequently used in combination with other logging libraries such as Winston and Pino, Morgan has established itself as the de facto standard for HTTP monitoring in Express.js applications, delivering essential request tracking and performance monitoring capabilities.

Details

Morgan 2025 edition maintains its indispensable position within the Express.js ecosystem. Operating as a lightweight middleware, it efficiently captures detailed HTTP request information including request methods, URLs, status codes, response times, IP addresses, and user agents. The custom token functionality enables creation of unique log formats, while supporting file output and integration with external logging services. With its minimal overhead design and seamless Express.js integration, Morgan continues to be the preferred choice for HTTP request logging in production Node.js applications.

Key Features

  • Predefined Formats: Multiple format options including Apache Common/Combined, dev, short, and tiny
  • Custom Tokens: Ability to define custom log fields and data extraction
  • Conditional Logging: Selective logging based on request criteria and conditions
  • Stream Support: Direct output to files, cloud services, and external systems
  • Lightweight Design: Minimal performance overhead with high-speed operation
  • Express.js Integration: Seamless middleware integration with Express applications

Pros and Cons

Pros

  • Simple setup and immediate usability in Express.js applications
  • Rich predefined formats enable rapid deployment and configuration
  • Custom token functionality provides flexible log customization options
  • Excellent compatibility and integration with major logging libraries
  • Minimal performance impact with lightweight, efficient implementation
  • Long-term stability and reliability with extensive production usage

Cons

  • Limited to Express.js framework, incompatible with other Node.js frameworks
  • Basic HTTP logging functionality only, lacks advanced features independently
  • Performance constraints under extremely high traffic loads
  • Log analysis capabilities require external tools and services
  • Missing real-time monitoring and alerting functionality
  • Limited complex filtering and conditional logic capabilities

Reference Pages

Usage Examples

Installation and Basic Setup

# Install Morgan
npm install morgan

# Install with Express.js
npm install express morgan

# TypeScript type definitions
npm install --save-dev @types/morgan

# Verify installation
node -p "require('morgan')"

Basic Usage with Predefined Formats

const express = require('express');
const morgan = require('morgan');

const app = express();

// Development format (colored output)
app.use(morgan('dev'));
// GET / 200 51.267 ms - 2

// Apache Combined Log Format (production recommended)
app.use(morgan('combined'));
// 127.0.0.1 - - [25/Dec/2023:10:00:00 +0000] "GET / HTTP/1.1" 200 2326 "-" "Mozilla/5.0..."

// Apache Common Log Format
app.use(morgan('common'));
// 127.0.0.1 - - [25/Dec/2023:10:00:00 +0000] "GET / HTTP/1.1" 200 2326

// Short format
app.use(morgan('short'));
// 127.0.0.1 - GET / HTTP/1.1 200 2326 - 1.234 ms

// Minimal format
app.use(morgan('tiny'));
// GET / 200 2326 - 1.234 ms

// Basic routes
app.get('/', (req, res) => {
  res.send('Hello, Morgan Logging!');
});

app.get('/api/users', (req, res) => {
  res.json({ users: ['Alice', 'Bob'] });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Custom Formats and Tokens

const express = require('express');
const morgan = require('morgan');

const app = express();

// Define custom tokens
morgan.token('id', (req) => {
  return req.headers['x-request-id'] || 'unknown';
});

morgan.token('host', (req) => {
  return req.hostname;
});

morgan.token('body', (req) => {
  return JSON.stringify(req.body);
});

morgan.token('query', (req) => {
  return JSON.stringify(req.query);
});

// Custom format string
const customFormat = ':method :host :url :status :res[content-length] - :response-time ms [ID: :id]';

app.use(express.json()); // Body parser
app.use(morgan(customFormat));

// More detailed custom format
const detailedFormat = [
  ':method :url',
  'Status: :status',
  'Size: :res[content-length]',
  'Time: :response-time ms',
  'Host: :host',
  'User-Agent: :user-agent',
  'Query: :query'
].join(' | ');

app.use(morgan(detailedFormat));

// Function-based custom format
morgan.format('detailed', (tokens, req, res) => {
  return [
    new Date().toISOString(),
    tokens.method(req, res),
    tokens.url(req, res),
    tokens.status(req, res),
    tokens.res(req, res, 'content-length'), '-',
    tokens['response-time'](req, res), 'ms',
    'IP:', tokens['remote-addr'](req, res),
    'Referrer:', tokens.referrer(req, res)
  ].join(' ');
});

app.use(morgan('detailed'));

app.get('/custom', (req, res) => {
  res.json({ message: 'Custom logging example' });
});

app.listen(3000);

File Output and Log Rotation

const express = require('express');
const morgan = require('morgan');
const fs = require('fs');
const path = require('path');
const rfs = require('rotating-file-stream'); // npm install rotating-file-stream

const app = express();

// Create log directory
const logDir = path.join(__dirname, 'logs');
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir);
}

// Simple file output
const accessLogStream = fs.createWriteStream(path.join(logDir, 'access.log'), { flags: 'a' });
app.use(morgan('combined', { stream: accessLogStream }));

// Daily rotation
const dailyLogStream = rfs.createStream('access.log', {
  interval: '1d', // Rotate daily
  path: logDir,
  compress: 'gzip' // Compress old logs
});

app.use(morgan('combined', { stream: dailyLogStream }));

// Size-based rotation
const sizeBasedStream = rfs.createStream('access.log', {
  size: '10M', // Rotate every 10MB
  maxFiles: 5, // Keep maximum 5 files
  path: logDir
});

app.use(morgan('combined', { stream: sizeBasedStream }));

// Multiple outputs (console + file)
app.use(morgan('dev')); // Console output
app.use(morgan('combined', { stream: accessLogStream })); // File output

// Conditional logging
app.use(morgan('combined', {
  stream: accessLogStream,
  skip: (req, res) => {
    // Skip successful responses (2xx) from file logging
    return res.statusCode < 400;
  }
}));

// Error-only log stream
const errorLogStream = fs.createWriteStream(path.join(logDir, 'error.log'), { flags: 'a' });
app.use(morgan('combined', {
  stream: errorLogStream,
  skip: (req, res) => res.statusCode < 400 // Only 4xx, 5xx errors
}));

app.get('/error', (req, res) => {
  res.status(500).json({ error: 'Internal Server Error' });
});

app.listen(3000);

Integration with Winston and Pino

const express = require('express');
const morgan = require('morgan');
const winston = require('winston');
const pino = require('pino');

const app = express();

// Winston integration
const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' }),
    new winston.transports.Console()
  ]
});

// Winston stream
const winstonStream = {
  write: (message) => {
    logger.info(message.trim());
  }
};

app.use(morgan('combined', { stream: winstonStream }));

// Pino integration
const pinoLogger = pino({
  level: 'info',
  transport: {
    target: 'pino-pretty',
    options: {
      colorize: true
    }
  }
});

const pinoStream = {
  write: (message) => {
    pinoLogger.info(message.trim());
  }
};

app.use(morgan('combined', { stream: pinoStream }));

// Structured logging integration
morgan.format('json', (tokens, req, res) => {
  return JSON.stringify({
    timestamp: new Date().toISOString(),
    method: tokens.method(req, res),
    url: tokens.url(req, res),
    status: Number(tokens.status(req, res)),
    contentLength: tokens.res(req, res, 'content-length'),
    responseTime: Number(tokens['response-time'](req, res)),
    remoteAddr: tokens['remote-addr'](req, res),
    userAgent: tokens['user-agent'](req, res),
    referrer: tokens.referrer(req, res)
  });
});

const structuredStream = {
  write: (message) => {
    const logData = JSON.parse(message);
    logger.info('HTTP Request', logData);
  }
};

app.use(morgan('json', { stream: structuredStream }));

app.get('/winston', (req, res) => {
  res.json({ logger: 'winston' });
});

app.get('/pino', (req, res) => {
  res.json({ logger: 'pino' });
});

app.listen(3000);

Advanced Configuration and Filtering

const express = require('express');
const morgan = require('morgan');

const app = express();

// Environment-based configuration
const isProduction = process.env.NODE_ENV === 'production';
const isDevelopment = process.env.NODE_ENV === 'development';

if (isDevelopment) {
  // Development: detailed colored output
  app.use(morgan('dev'));
} else if (isProduction) {
  // Production: structured logs
  app.use(morgan('combined'));
}

// Conditional logging
app.use(morgan('combined', {
  skip: (req, res) => {
    // Skip health check endpoints
    if (req.url === '/health') return true;
    
    // Skip static files
    if (req.url.match(/\.(css|js|png|jpg|ico)$/)) return true;
    
    // Skip 2xx responses (error-only logging)
    if (res.statusCode >= 200 && res.statusCode < 300) return true;
    
    return false;
  }
}));

// IP address filtering
app.use(morgan('combined', {
  skip: (req) => {
    const clientIP = req.ip || req.connection.remoteAddress;
    const skipIPs = ['127.0.0.1', '::1', '10.0.0.0']; // Skip internal IPs
    return skipIPs.includes(clientIP);
  }
}));

// Response time-based conditional logging
app.use(morgan('combined', {
  skip: (req, res) => {
    const responseTime = parseFloat(res.get('X-Response-Time')) || 0;
    return responseTime < 100; // Skip requests faster than 100ms
  }
}));

// Custom filter functions
const isAPIRequest = (req) => req.url.startsWith('/api/');
const isStaticRequest = (req) => req.url.match(/\.(css|js|png|jpg|ico|woff|woff2)$/);

// API-only logger
app.use(morgan('detailed', {
  skip: (req) => !isAPIRequest(req)
}));

// Static files logger (simplified)
app.use(morgan('tiny', {
  skip: (req) => !isStaticRequest(req)
}));

// Security logging (login attempts, etc.)
morgan.format('security', (tokens, req, res) => {
  return JSON.stringify({
    type: 'security',
    timestamp: new Date().toISOString(),
    method: tokens.method(req, res),
    url: tokens.url(req, res),
    status: tokens.status(req, res),
    ip: tokens['remote-addr'](req, res),
    userAgent: tokens['user-agent'](req, res),
    suspicious: res.statusCode === 401 || res.statusCode === 403
  });
});

app.use('/auth', morgan('security'));

// Rate limiting logs
app.use(morgan('combined', {
  skip: (req, res) => res.statusCode !== 429 // Only Too Many Requests
}));

app.get('/api/data', (req, res) => {
  res.json({ data: 'API response' });
});

app.get('/health', (req, res) => {
  res.json({ status: 'OK' });
});

app.listen(3000);

Analytics and Metrics Collection

const express = require('express');
const morgan = require('morgan');
const fs = require('fs');

const app = express();

// Metrics collection
let requestCount = 0;
let totalResponseTime = 0;
let errorCount = 0;

morgan.token('metrics', (req, res) => {
  requestCount++;
  const responseTime = parseFloat(res.get('X-Response-Time')) || 0;
  totalResponseTime += responseTime;
  
  if (res.statusCode >= 400) {
    errorCount++;
  }
  
  return `[Metrics: requests=${requestCount}, avgTime=${(totalResponseTime/requestCount).toFixed(2)}ms, errors=${errorCount}]`;
});

app.use(morgan(':method :url :status :metrics'));

// Performance analysis
morgan.format('performance', (tokens, req, res) => {
  const responseTime = parseFloat(tokens['response-time'](req, res));
  const contentLength = parseInt(tokens.res(req, res, 'content-length') || '0');
  
  return JSON.stringify({
    url: tokens.url(req, res),
    method: tokens.method(req, res),
    status: parseInt(tokens.status(req, res)),
    responseTime: responseTime,
    contentLength: contentLength,
    throughput: contentLength / responseTime, // bytes per ms
    slow: responseTime > 1000, // Mark as slow if > 1 second
    timestamp: new Date().toISOString()
  });
});

const performanceLogStream = fs.createWriteStream('logs/performance.log', { flags: 'a' });
app.use(morgan('performance', { stream: performanceLogStream }));

// Statistics reporting
setInterval(() => {
  const avgResponseTime = requestCount > 0 ? (totalResponseTime / requestCount).toFixed(2) : 0;
  const errorRate = requestCount > 0 ? ((errorCount / requestCount) * 100).toFixed(2) : 0;
  
  console.log(`
=== Server Statistics ===
Total Requests: ${requestCount}
Average Response Time: ${avgResponseTime}ms
Error Rate: ${errorRate}%
Total Errors: ${errorCount}
========================
  `);
}, 60000); // Every minute

// User agent analysis
const userAgentStats = {};
morgan.token('user-agent-stats', (req) => {
  const ua = req.get('User-Agent') || 'Unknown';
  const browser = ua.includes('Chrome') ? 'Chrome' : 
                 ua.includes('Firefox') ? 'Firefox' : 
                 ua.includes('Safari') ? 'Safari' : 'Other';
  
  userAgentStats[browser] = (userAgentStats[browser] || 0) + 1;
  return `[UA: ${JSON.stringify(userAgentStats)}]`;
});

app.use(morgan(':method :url :user-agent-stats'));

app.get('/stats', (req, res) => {
  res.json({
    requests: requestCount,
    averageResponseTime: (totalResponseTime / requestCount).toFixed(2),
    errorRate: ((errorCount / requestCount) * 100).toFixed(2),
    userAgents: userAgentStats
  });
});

app.listen(3000);