Logging is a critical part of any web application, as it helps developers track, debug, and understand the flow of an application. Effective logging allows you to capture information about incoming requests, responses, errors, and other significant events in your Express.js application. This chapter will provide an in-depth look at logging middleware in Express.js, covering everything from basic to advanced techniques. We will explore how to implement custom logging, integrate popular logging libraries, and follow best practices to ensure your logging system is robust and scalable.
Logging involves recording information about the operation of your application. This can include details about incoming HTTP requests, responses sent to clients, errors encountered, and other significant events. Logs are essential for monitoring, debugging, and auditing an application.
Express.js allows you to create custom middleware for logging purposes. Let’s start with a basic example that logs details about every incoming request.
app.js
const express = require('express');
const app = express();
// Simple logging middleware
app.use((req, res, next) => {
console.log(`${req.method} ${req.url} - ${new Date().toISOString()}`);
next();
});
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
next()
: This function passes control to the next middleware in the stack, ensuring that the request is processed.Output: When you visit http://localhost:3000/
, the console will display:
GET / - 2024-08-15T14:35:12.345Z
This log entry shows the request method (GET
), the requested URL (/
), and the timestamp of the request.
You can also log information about the response, such as the status code and response time.
app.js
(continued)
const express = require('express');
const app = express();
// Logging middleware for requests and responses
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
console.log(`${req.method} ${req.url} - ${res.statusCode} - ${duration}ms`);
});
next();
});
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
duration
).res.on('finish')
: This event listener triggers when the response is finished, ensuring that the response status code is available for logging.Output: After requesting http://localhost:3000/
, the console might display:
GET / - 200 - 15ms
This log entry includes the request method, URL, status code (200
), and response time (15ms
).
morgan
?morgan
is a popular logging middleware for Express.js that simplifies the process of logging HTTP requests. It provides predefined formats for logging, making it easy to set up consistent and informative logs.
morgan
You can install morgan
using npm:
npm install morgan
To use morgan
in your application:
app.js
(continued)
const express = require('express');
const morgan = require('morgan');
const app = express();
// Use 'morgan' for logging
app.use(morgan('combined'));
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
morgan('combined')
: This format logs comprehensive information including remote address, user agent, request method, URL, status code, and response time.Output: After accessing http://localhost:3000/
, the console might display something like:
::1 - - [15/Aug/2024:14:40:12 +0000] "GET / HTTP/1.1" 200 13 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36"
This log entry contains detailed information about the request, including the client’s IP address, timestamp, request method, URL, protocol, status code, response size, referrer, and user agent.
morgan
Formatsmorgan
allows you to customize the log format or create your own.
app.js
(continued)
const express = require('express');
const morgan = require('morgan');
const app = express();
// Custom log format with 'morgan'
morgan.token('body', (req) => JSON.stringify(req.body));
app.use(morgan(':method :url :status :res[content-length] - :response-time ms :body'));
app.use(express.json());
app.post('/data', (req, res) => {
res.send('Data received');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
body
token is added to log the request body in the logs.Output: After making a POST request to http://localhost:3000/data
with a JSON body, the console might display:
POST /data 200 13 - 3.056 ms {"name":"John"}
This log entry includes the request method, URL, status code, response size, response time, and the JSON body sent with the request.
While morgan
is powerful, there might be scenarios where you need more control over the logging process. In such cases, you can create custom logging middleware.
app.js
const express = require('express');
const fs = require('fs');
const path = require('path');
const app = express();
// Custom logging middleware
app.use((req, res, next) => {
const log = `${req.method} ${req.url} - ${new Date().toISOString()}\n`;
// Write logs to a file
fs.appendFile(path.join(__dirname, 'access.log'), log, (err) => {
if (err) {
console.error('Failed to write to log file');
}
});
console.log(log);
next();
});
app.get('/', (req, res) => {
res.send('Hello, World!');
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
access.log
.fs.appendFile()
: This function appends the log to the file, creating it if it doesn’t exist.Output: Each request to http://localhost:3000/
will append a line to access.log
and print it in the console:
GET / - 2024-08-15T14:45:12.345Z
Logging errors is crucial for debugging and monitoring the health of your application. You can create custom error logging middleware to capture and log errors in a structured format.
app.js
(continued)
// Error logging middleware
app.use((err, req, res, next) => {
const errorLog = `${err.stack} - ${new Date().toISOString()}\n`;
// Write error logs to a file
fs.appendFile(path.join(__dirname, 'error.log'), errorLog, (err) => {
if (err) {
console.error('Failed to write to error log file');
}
});
console.error(errorLog);
res.status(500).send('Internal Server Error');
});
// Simulate an error for testing
app.get('/error', (req, res) => {
throw new Error('This is a test error');
});
error.log
file, and sends a 500 status code to the client./error
will trigger an error, allowing you to test the error logging.Output: After accessing http://localhost:3000/error
, the error.log
will contain:
Error: This is a test error
at /path/to/app.js:36:9
...
- 2024-08-15T14:50:12.345Z
Avoid logging sensitive information like passwords, API keys, or personal data. Always sanitize logs to prevent leaking sensitive information.
Use a consistent structure for your logs to make them easier to parse and analyze. Consider using JSON format for logs if they need to be consumed by other systems.
For more advanced logging, consider using libraries like winston
or bunyan
, which provide features like log levels, custom transports, and structured logging.
Regularly monitor your logs for errors and performance issues. Implement log rotation to prevent log files from consuming too much disk space.
winston
?winston
is a versatile logging library for Node.js that supports various log transports (e.g., files, databases, console) and allows for structured, leveled logging.
winston
To use winston
, install it via npm:
npm install winston
app.js
(continued)
const express = require('express');
const winston = require('winston');
const app = express();
// Set up 'winston' logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
],
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
}
// Middleware to log all requests
app.use((req, res, next) => {
logger.info(`${req.method} ${req.url}`);
next();
});
// Example route
app.get('/', (req, res) => {
res.send('Hello, World!');
});
// Simulate an error for testing
app.get('/error', (req, res) => {
throw new Error('This is a test error');
});
// Error handling middleware
app.use((err, req, res, next) => {
logger.error(err.stack);
res.status(500).send('Internal Server Error');
});
app.listen(3000, () => {
logger.info('Server running on http://localhost:3000');
});
winston
allows you to set log levels (e.g., info
, error
). Logs with a severity below the specified level are ignored.error.log
, combined.log
, console).combined.log
: Logs all requests and info-level messages.error.log
: Logs error-level messages, such as the simulated error.Logging is a fundamental aspect of building reliable and maintainable Express.js applications. By effectively implementing logging middleware, you can gain deep insights into your application's behavior, improve your ability to debug issues, and enhance overall performance. Whether using basic logging, the morgan middleware, or advanced libraries like winston, a well-structured logging system is essential for any Express.js project. Happy coding !❤️