Logging in a web application built with Express.js is super important for keeping tabs on what’s happening and fixing issues when they pop up. One great way to manage your logs is by combining morgan
middleware and rotating-file-stream
. This duo helps you store logs in rotating files so they stay organized and don’t grow endlessly.
First off, you’ll need to install both morgan
and rotating-file-stream
using npm. It’s as simple as running:
npm install morgan rotating-file-stream
Once you’ve got them installed, you can set up your Express app to use these modules.
Here’s a basic setup for logging requests into a file that rotates daily:
const express = require('express');
const morgan = require('morgan');
const rfs = require("rotating-file-stream");
const app = express();
const port = 3000;
// Create a rotating write stream
const accessLogStream = rfs.createStream("access.log", {
interval: "1d", // rotate daily
path: __dirname + "/log"
});
// Setup the logger
app.use(morgan("combined", { stream: accessLogStream }));
app.get('/', (req, res) => {
res.send('hello, world!');
});
app.listen(port, () => {
console.debug(`App listening on :${port}`);
});
In this example, rotating-file-stream
creates a new log file every day, and morgan
logs all requests in the Apache combined format to this rotating log stream.
If you want to customize your log format and add more details like the user’s IP or a unique request ID, you can do it this way:
const express = require('express');
const morgan = require('morgan');
const rfs = require("rotating-file-stream");
const uuid = require('node-uuid');
const app = express();
const port = 3000;
// Create a rotating write stream
const accessLogStream = rfs.createStream("access.log", {
interval: "1d", // rotate daily
path: __dirname + "/log"
});
// Define custom tokens
morgan.token('id', req => req.id);
morgan.token('ip', req => req.headers['x-forwarded-for'] || req.connection.remoteAddress);
// Assign a unique ID to each request
app.use((req, res, next) => {
req.id = uuid.v4();
next();
});
// Setup the logger with custom format
app.use(morgan(':id :ip ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"', { stream: accessLogStream }));
app.get('/', (req, res) => {
res.send('hello, world!');
});
app.listen(port, () => {
console.debug(`App listening on :${port}`);
});
In this setup, every log entry includes a unique request ID and the user’s IP address.
Sometimes you might need to rotate logs not just daily, but also based on size. This makes sure no single log file gets too big:
const express = require('express');
const morgan = require('morgan');
const rfs = require("rotating-file-stream");
const app = express();
const port = 3000;
// Create a rotating write stream based on size
const accessLogStream = rfs.createStream("access.log", {
size: "10M", // rotate every 10MB written
interval: "1d", // rotate daily
compress: "gzip" // compress rotated files
});
// Setup the logger
app.use(morgan("combined", { stream: accessLogStream }));
app.get('/', (req, res) => {
res.send('hello, world!');
});
app.listen(port, () => {
console.debug(`App listening on :${port}`);
});
This way, logs will rotate daily and whenever the file size exceeds 10MB.
You can also make your logging setup more flexible using environment variables:
require('dotenv').config(); // load variables from .env file
const express = require('express');
const morgan = require('morgan');
const rfs = require("rotating-file-stream");
const app = express();
const port = 3000;
// Create a rotating write stream
const rfsStream = rfs.createStream(process.env.LOG_FILE || 'access.log', {
size: process.env.LOG_SIZE || '10M',
interval: process.env.LOG_INTERVAL || '1d',
compress: 'gzip' // compress rotated files
});
// Setup the logger
const format = process.env.LOG_FORMAT || "dev";
app.use(morgan(format, { stream: rfsStream }));
app.use(morgan(format));
app.get('/', (req, res) => {
res.send('hello, world!');
});
app.listen(port, () => {
console.debug(`App listening on :${port}`);
});
This setup lets you control log file names, sizes, intervals, and formats using environment variables defined in a .env
file.
Another handy feature is logging errors and successes separately. This way, you can easily track down issues without sifting through all other logs:
const express = require('express');
const morgan = require('morgan');
const rfs = require("rotating-file-stream");
const app = express();
const port = 3000;
// Create rotating write streams
const errorLogStream = rfs.createStream("error.log", {
interval: "1d", // rotate daily
path: __dirname + "/log"
});
const successLogStream = rfs.createStream("success.log", {
interval: "1d", // rotate daily
path: __dirname + "/log"
});
// Skip logic for error and success logs
const skipSuccess = (req, res) => res.statusCode < 400;
const skipError = (req, res) => res.statusCode >= 400;
// Setup the logger for errors
app.use(morgan("combined", { skip: skipSuccess, stream: errorLogStream }));
// Setup the logger for successes
app.use(morgan("combined", { skip: skipError, stream: successLogStream }));
app.get('/', (req, res) => {
res.send('hello, world!');
});
app.listen(port, () => {
console.debug(`App listening on :${port}`);
});
In this config, error responses (4xx and 5xx status codes) go into one file and successful responses into another.
At times, you might run into issues where log rotation doesn’t work as expected. A common fix for daily rotation issues is to ensure the file name generator is set up right:
const express = require('express');
const morgan = require('morgan');
const rfs = require("rotating-file-stream");
const path = require('path');
const { format } = require('date-fns');
const app = express();
const port = 3000;
function logFilename() {
return `${format(new Date(), 'yyyy-MM-dd')}-access.log`;
}
const accessLogStream = rfs.createStream(logFilename(), {
interval: "1d", // rotate daily
path: path.resolve(__dirname, '..', 'log'),
});
// Setup the logger
app.use(morgan(':method :url :status :res[content-length] ":referrer" ":user-agent"', { stream: accessLogStream }));
app.get('/', (req, res) => {
res.send('hello, world!');
});
app.listen(port, () => {
console.debug(`App listening on :${port}`);
});
Here, the logFilename
function makes sure the log file name includes the current date, which helps in daily rotation.
Combining morgan
with rotating-file-stream
provides a solid logging solution for your Express.js apps. You can customize your log formats, rotate logs by size or time, and even use environment variables to keep everything in check. This keeps your logs well-organized and easier to analyze, making debugging and monitoring a breeze.