Introduction
Logging errors is crucial for debugging and monitoring your Express.js applications. While Pino is known for its high-performance logging, it doesn’t automatically log errors unless explicitly configured. In this guide, we’ll walk through a simple method to automatically capture and log errors using pino-http
in Express.
Step 1: Install Pino and Pino-HTTP
First, install the required dependencies:
npm install pino pino-http
Step 2: Set Up Pino-HTTP Middleware
To log incoming requests and responses, use pino-http
as middleware:
const express = require('express');
const pino = require('pino');
const pinoHttp = require('pino-http');
const logger = pino({
level: 'info', // Set log level
transport: {
target: 'pino-pretty' // Optional: Pretty-print logs during development
}
});
const app = express();
// Apply Pino-HTTP middleware for logging requests
app.use(pinoHttp({ logger }));
Now, every incoming request will be logged automatically in a structured JSON format.
By default, Pino logs output to the console, but you can configure it to write logs to a file (explained below).
Step 3: Automatically Log Errors
To capture and log errors, add an error-handling middleware at the end of the middleware chain:
app.use((err, req, res, next) => {
req.log.error({
message: err.message,
stack: err.stack,
status: res.statusCode
}, 'Unhandled error occurred');
res.status(500).send('Internal Server Error');
});
How Errors Are Logged
This middleware does two things:
- Logs the error details using
req.log.error
, including the message, stack trace, and response status.
- Sends a
500 Internal Server Error
response to the client.
By default, these logs appear in the console, but they can be redirected to a file.
Example of a logged error:
{
"level": 50,
"time": 1672028160000,
"pid": 1234,
"hostname": "localhost",
"msg": "Unhandled error occurred",
"message": "Something went wrong!",
"stack": "Error: Something went wrong! at ...",
"status": 500
}
Step 4: Writing Logs to a File
If you want to save logs to a file instead of just displaying them in the console, you can create a writable stream:
const fs = require('fs');
const logStream = fs.createWriteStream('./logs.log', { flags: 'a' });
const logger = pino(logStream);
Now, all logs will be written to logs.log
.
Alternatively, you can run the app and redirect logs to a file:
node app.js > logs.log 2>&1
This will save all logs, including errors, to logs.log
.
Step 5: Testing Error Logging
Add a test route that intentionally throws an error:
app.get('/error', (req, res, next) => {
next(new Error('Something went wrong!'));
});
Run your server and make a request to /error
. You should see an error log similar to the example above, either in the console or in the log file (if configured).
Configuring Pino for Different Environments
You can adjust Pino configurations based on the environment:
const logger = pino({
level: process.env.NODE_ENV === 'production' ? 'error' : 'debug',
transport: process.env.NODE_ENV !== 'production' ? { target: 'pino-pretty' } : undefined
});
- Production: Logs only
error
level messages.
- Development: Uses
pino-pretty
for human-readable logs.
Conclusion
With just a few lines of code, you can automatically log errors in your Express app using Pino. By default, logs are printed to the console, but they can also be saved to a file for persistent storage.
Now, every unexpected error will be captured and logged efficiently, complete with configurations for different environments. Happy coding! 🚀
Walter Guevara is a Computer Scientist, software engineer, startup founder and previous mentor for a coding bootcamp. He has been creating software for the past 20 years.