Express Request Logging
Logging incoming HTTP requests is a crucial part of web application development. It helps you track user activity, debug issues, and monitor your application's performance. In this guide, we'll explore how to implement request logging in Express.js applications.
Introduction to Request Logging
Request logging is the process of recording information about incoming HTTP requests to your Express.js application. A good logging system captures details such as:
- Request method (GET, POST, PUT, DELETE, etc.)
- URL path
- Request timestamp
- Response status code
- Response time
- Client IP address
- User agent information
Properly implemented logging helps with:
- Debugging application issues
- Tracking user behavior
- Monitoring application performance
- Security analysis and intrusion detection
- Meeting compliance requirements
Using the Built-in Express Logger
Express itself provides minimal built-in logging capabilities, outputting only basic information to the console. For more robust logging, most developers use middleware like morgan
.
Basic Express Logging
Here's how you can implement a very simple logger using Express middleware:
const express = require('express');
const app = express();
// Custom logging middleware
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next();
});
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
When someone visits your homepage, you'll see output like:
[2023-10-15T14:32:45.123Z] GET /
This is quite basic, but it gives you an idea of how middleware can be used for logging.
Morgan: The Express Logging Solution
Morgan is a popular HTTP request logger middleware for Node.js that works seamlessly with Express. It offers more features and flexibility than a custom solution.
Installing Morgan
First, you need to install Morgan:
npm install morgan
Basic Usage of Morgan
Here's a simple example of using Morgan with its predefined "common" format:
const express = require('express');
const morgan = require('morgan');
const app = express();
// Use morgan middleware with the 'common' format
app.use(morgan('common'));
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
When you access your application, Morgan will log something like:
::1 - - [15/Oct/2023:14:32:45 +0000] "GET / HTTP/1.1" 200 11
Morgan Log Formats
Morgan provides several predefined formats:
-
combined: The standard Apache combined log output
::1 - - [15/Oct/2023:14:32:45 +0000] "GET / HTTP/1.1" 200 11 "http://localhost:3000/" "Mozilla/5.0..."
-
common: The standard Apache common log output
::1 - - [15/Oct/2023:14:32:45 +0000] "GET / HTTP/1.1" 200 11
-
dev: Colored by response status for development use
GET / 200 11.291 ms - 11
-
short: Shorter than default, including response time
::1 - GET / HTTP/1.1 200 11 - 5.432 ms
-
tiny: The minimal output
GET / 200 11 - 5.432 ms
To use any of these formats:
app.use(morgan('dev')); // Using the 'dev' format
Custom Log Formats with Morgan
You can create custom log formats with Morgan using a format string:
const express = require('express');
const morgan = require('morgan');
const app = express();
// Define a custom format
morgan.token('custom-date', () => {
return new Date().toISOString();
});
// Use the custom format
app.use(morgan(':custom-date :method :url :status :res[content-length] - :response-time ms'));
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
This will produce logs like:
2023-10-15T14:32:45.123Z GET / 200 11 - 5.432 ms
Logging to Files
Console logging is useful during development, but in production, you'll want to save logs to files. Here's how to do it with Morgan and the fs
module:
const express = require('express');
const morgan = require('morgan');
const fs = require('fs');
const path = require('path');
const app = express();
// Create a write stream (in append mode)
const accessLogStream = fs.createWriteStream(
path.join(__dirname, 'access.log'),
{ flags: 'a' }
);
// Setup the logger middleware
app.use(morgan('combined', { stream: accessLogStream }));
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
This will save all logs to an access.log
file in your application directory.
Log Rotation
For production applications, log files can grow very large over time. You should implement log rotation to manage this. The rotating-file-stream
package works well with Morgan:
npm install rotating-file-stream
Example implementation:
const express = require('express');
const morgan = require('morgan');
const path = require('path');
const rfs = require('rotating-file-stream');
const app = express();
// Create a rotating write stream
const accessLogStream = rfs.createStream('access.log', {
interval: '1d', // Rotate daily
path: path.join(__dirname, 'logs')
});
// Setup the logger middleware
app.use(morgan('combined', { stream: accessLogStream }));
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
This will create a new log file each day and store them in a logs
directory.
Advanced Logging with Winston
For more advanced logging needs, you might want to consider Winston, a versatile logging library:
npm install winston express-winston
Example of using Winston with Express:
const express = require('express');
const winston = require('winston');
const expressWinston = require('express-winston');
const app = express();
// Request logging middleware
app.use(expressWinston.logger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'requests.log' })
],
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
meta: true, // Include metadata (req, res)
expressFormat: true, // Use the default Express/morgan request formatting
colorize: false, // Colorize the output (only for console)
}));
app.get('/', (req, res) => {
res.send('Hello World!');
});
// Error logging middleware (must be after routes)
app.use(expressWinston.errorLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'errors.log' })
],
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
)
}));
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Winston provides more flexibility for handling different log levels, formats, and destinations.
Real-world Example: Logging API Requests
Let's create a more complete example showing how to log API requests in a RESTful service:
const express = require('express');
const morgan = require('morgan');
const rfs = require('rotating-file-stream');
const path = require('path');
const app = express();
// Create log directory if it doesn't exist
const logDirectory = path.join(__dirname, 'logs');
fs.existsSync(logDirectory) || fs.mkdirSync(logDirectory);
// Create a rotating write stream for access logs
const accessLogStream = rfs.createStream('access.log', {
interval: '1d',
path: logDirectory
});
// Add request body token to morgan
morgan.token('body', (req) => JSON.stringify(req.body));
// Parse JSON request bodies
app.use(express.json());
// Log all requests
app.use(morgan(':remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" :response-time ms :body', {
stream: accessLogStream
}));
// Development console logging
if (app.get('env') === 'development') {
app.use(morgan('dev'));
}
// API Routes
app.get('/api/users', (req, res) => {
res.json([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);
});
app.post('/api/users', (req, res) => {
// Log successful creation
console.log(`User created: ${JSON.stringify(req.body)}`);
res.status(201).json({ id: 3, ...req.body });
});
// Start server
app.listen(3000, () => {
console.log('Server running on port 3000');
});
This example:
- Sets up file-based logging with rotation
- Adds request body logging for POST requests
- Uses different logging for development and production
- Demonstrates API endpoint logging
Security Considerations
When implementing logging, consider these security best practices:
- Avoid logging sensitive data: Never log passwords, tokens, or personal information
- Sanitize user input: Be cautious about logging user-provided data to prevent log injection
- Secure log files: Ensure log files have appropriate file permissions
- Consider compliance requirements: For applications handling sensitive data, ensure logging meets GDPR, HIPAA, or other relevant regulations
Summary
Request logging is an essential part of Express application monitoring and maintenance. In this guide, we've covered:
- Basic logging concepts and their importance
- Using Morgan for HTTP request logging
- Customizing log formats
- Saving logs to files and implementing log rotation
- Advanced logging with Winston
- Security considerations and best practices
Implementing proper request logging from the start of your project will save you countless hours of debugging and provide valuable insights into your application's usage patterns.
Exercises
- Implement basic Morgan logging in an Express application with the "dev" format.
- Create a custom log format that includes the request IP, method, URL, status code, and response time.
- Set up file-based logging with daily rotation.
- Implement different logging for different routes (e.g., more detailed logging for API routes).
- Create a logging system that filters out sensitive data (like passwords or API keys) from request bodies.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)