Back to Logging guides

Morgan Logging in Node.js

Stanley Ulili
Updated on December 9, 2024

Morgan is a popular used HTTP request logger for Node.js applications, especially those built with Express or similar frameworks. It provides a straightforward, consistent way to log incoming HTTP requests, capturing details like methods, URLs, response times, and status codes.

Morgan comes with predefined log formats—ranging from Apache-style combined logs to simple one-line entries—and also allows custom token-based formats for more fine-grained control.

In this guide, you’ll learn how to set up Morgan, integrate it with popular frameworks, refine your log output, and route logs to various destinations.

Prerequisites

Before you begin, ensure you have a recent version of Node.js and npm installed. This article also assumes basic familiarity with building an Express application.

Setting up the project directory

In this section, you'll create a directory for a simple Express application and set up the necessary configurations to get started with logging using Morgan.

First, create a new directory for your project and navigate into it. Then, initialize a new Node.js project using npm:

 
mkdir morgan-logging && cd morgan-logging
 
npm init -y

To use modern JavaScript features such as import statements, enable ECMAScript Modules in your project by updating the package.json file:

 
npm pkg set type=module

This command sets the type field to module in your package.json, allowing you to use import and export syntax in your JavaScript files.

Next, install Express, a minimal and flexible Node.js web application framework:

 
npm install express

Open your preferred code editor and create an index.js file in the root of your project directory with the following contents:

index.js
import express from "express";
const app = express();
const PORT = 3000

app.get("/", (req, res) => {
  res.send("Welcome to the Home Page!");
});

app.get("/about", (req, res) => {
  res.send("About Us");
});

app.listen(PORT, () => {
  console.log("Server running on http://localhost:3000");
});

With that, you are now ready to proceed to the next section.

Why do you need Morgan?

Let’s start by testing our Express application without Morgan to understand the limitations of operating without a dedicated logging middleware. This demonstration will help illustrate why adding Morgan can significantly enhance your application's observability and debugging capabilities.

First, launch the server with:

 
node --watch index.js
Output
Server running on http://localhost:3000

Next, open your browser and visit the following endpoints:

  • http://localhost:3000/
  • http://localhost:3000/about

After loading these pages, recheck your console. Notice that only the initial startup message is displayed:

Output
Server running on http://localhost:3000

The output has no logs indicating that the endpoints were accessed. This lack of request visibility introduces several significant drawbacks:

  • Without logs, you can’t see which routes are being accessed or how frequently
  • Identifying and diagnosing issues becomes challenging without a record of incoming requests and their outcomes
  • You have no data on response times or status codes, making assessing and improving performance hard.
  • Suspicious activities or potential security threats go unnoticed without logging.

These limitations underscore the need for a logging solution like Morgan, which provides comprehensive and consistent logging of HTTP requests, improving your ability to monitor, troubleshoot, and secure your application.

Getting started with Morgan

In this section, you will enhance your existing Express application by adding Morgan. With Morgan, your application will gain the ability to log detailed information about incoming HTTP requests, making monitoring traffic and troubleshooting issues easier.

First, install Morgan on your machine:

 
npm install morgan

Next, update your index.js file to use one of Morgan’s built-in formats. For example, let’s use the “combined” format, which closely resembles the Apache combined log style:

index.js
import express from "express";
import morgan from "morgan";
const app = express(); const PORT = 3000;
app.use(morgan("combined"));
app.get("/", (req, res) => { res.send("Welcome to the Home Page!"); }); ...

Upon saving the changes, Node.js will automatically restart the server. Revisit the following endpoints:

  • http://localhost:3000/
  • http://localhost:3000/about

You will now see more detailed log entries that include the status code, response size, and user-agent:

Output
 ::1 - - [09/Dec/2024:10:04:38 +0000] "GET / HTTP/1.1" 304 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
::1 - - [09/Dec/2024:10:04:43 +0000] "GET /about HTTP/1.1" 304 - "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"

These entries follow the Apache combined log format and provide comprehensive details such as the client’s IP address (::1 for local IPv6), timestamp, HTTP method and URL (GET /), status code (304), response size, referrer, and the User-Agent string.

This structured information provides valuable insights into how users interact with your application.

Understanding Morgan’s logging formats

Morgan provides several predefined log formats:

  • combined: Similar to the Apache combined format; verbose and includes details like referrers and user-agents.
  • common: A standard Apache common log output without referrers and user agents.
  • dev: A concise color-coded output for development.
  • short: A shorter version of the common format.
  • tiny: The smallest output format, including basic request info and status code.

Switching formats is as easy as changing the string passed to morgan():

index.js
...
app.use(morgan("tiny"));
app.get("/", (req, res) => { res.send("Welcome to the Home Page!"); }); ...

Now visit the http://localhost:3000/ in your browser. You’ll see a simplified output such as the following in the terminal output:

 
GET / 304 - - 8.003 ms

This minimal format is easier to scan during local development, but less comprehensive than combined.

Customizing Morgan’s output

Morgan allows you to customize its log outputs to better suit your needs. In addition to using predefined log formats, you can create your own by defining custom format strings or even supplying a function that returns a log line.

Using custom tokens

Tokens are placeholders that Morgan substitutes with request-specific data at runtime. Morgan includes built-in tokens such as :method, :url, :status, and :response-time.

Creating custom tokens can significantly enhance the usefulness of your logs. One particularly valuable addition is a UUID (Universally Unique Identifier) for each request, ensuring that every log entry can be uniquely traced throughout your application.

To begin, install the uuid package:

 
npm install uuid

Update index.js to generate and log a UUID with each request:

index.js
import { v4 as uuidv4 } from "uuid";
...
const PORT = 3000;

app.use((req, res, next) => {
req.id = uuidv4();
next();
});
// Define a custom Morgan token for Request ID
morgan.token("id", (req) => req.id);
// Use Morgan with the custom token in the log format
app.use(
morgan(":id :method :url :status :res[content-length] - :response-time ms")
);
app.get("/", (req, res) => { ... });

Now, visit the endpoints again:

  • http://localhost:3000/
  • http://localhost:3000/about
Output
c13071fa-6f9e-490b-8c50-58f943eb9356 GET / 304 - - 2.924 ms
72fe0fd4-f7c0-496c-bb5a-d94f16c1a6c2 GET /about 304 - - 4.642 ms

Each log line now includes a unique UUID for every request, allowing you to trace individual requests through your application more effectively.

Using a custom format function

Building on the custom UUID token, you can achieve even greater control over your log output by defining a custom format function. Instead of relying on a predefined format string, you’ll supply a function that determines exactly how each log entry is structured. This level of customization is especially useful for producing machine-readable formats like JSON, which integrate smoothly with centralized logging systems.

To implement a custom format function, pass a logging function to Morgan that receives (tokens, req, res) and returns a string. This approach gives you the flexibility to include any data you need and format it however you like:

index.js
...
morgan.token("id", (req) => req.id);

app.use(
morgan(function (tokens, req, res) {
return JSON.stringify({
requestId: tokens.id(req, res),
method: tokens.method(req, res),
url: tokens.url(req, res),
status: parseInt(tokens.status(req, res), 10),
responseTime: `${tokens["response-time"](req, res)} ms`,
});
})
);
...

With this configuration in place, your logs will now appear as JSON:

 
{"requestId":"b60ce6d8-9702-4b57-8f08-d158f10ea1f1","method":"GET","url":"/","status":304,"responseTime":"3.255 ms"}
{"requestId":"aa34c908-7aea-4b94-9e28-2c357e71bfc6","method":"GET","url":"/about","status":304,"responseTime":"1.708 ms"}

Producing JSON logs makes it easier for automated systems to parse and index your log data. This structured format is especially beneficial in production environments where logs are aggregated, analyzed, and monitored through centralized platforms.

Prettifying logs for development

When developing your application, having human-readable and color-coded logs can significantly improve the debugging experience. Morgan’s dev format is handy during development, providing colorized output and highlighting essential details like status codes.

To enable the dev format, adjust your code as follows:

index.js
...
const PORT = 3000;

app.use(morgan("dev"));
app.get("/", (req, res) => { ... }) ...

After refreshing the endpoint, you’ll see output similar to the following:

Screenshot of ...

The dev format uses colors to distinguish successful requests (green) from errors (red) and so on, improving readability at a glance.

Centralizing and monitoring your logs

As your application grows, logs generated by different services, servers, and containers become scattered and difficult to analyze. Without a unified view, identifying issues, tracking performance trends, and maintaining security standards is time-consuming and error-prone. Centralizing your logs in a single platform transforms scattered data into actionable intelligence, making your application more resilient and high-performing.

Better Stack provides a great solution for centralizing and monitoring your Node.js logs. By directing Morgan’s structured, JSON-formatted logs to Better Stack, you gain the ability to:

  • View all logs from your entire infrastructure in one place, eliminating the need to check multiple sources.
  • Filter and query logs based on response times, status codes, or URLs
  • Detect irregular patterns, suspicious activities, and errors before they escalate, maintaining a clear audit trail.
  • Collaborate more effectively by sharing dashboards, alerts, and search results so everyone can respond with the same context.

You can route Morgan’s output to Better Stack using our Node.js client library, which requires a source token from Better Stack.

To get the source token, sign up for a free account with Better Stack. Once you’re logged in, navigate to the Sources link on the left and click the Connect source button:

Screenshot of Better Stack sources page

Give your source a descriptive name, then select JavaScript • Node.js under the logs section. Finally, click the Connect source button:

Screenshot of source selection

After the source is created, copy the Source token to your clipboard:

Screenshot showing the Source token

Now, update your application’s logging configuration to output JSON logs as shown earlier:

index.js
...
app.use((req, res, next) => {
req.id = uuidv4();
next();
});
morgan.token("id", (req) => req.id);
app.use(
morgan(function (tokens, req, res) {
return JSON.stringify({
requestId: tokens.id(req, res),
method: tokens.method(req, res),
url: tokens.url(req, res),
status: parseInt(tokens.status(req, res), 10),
responseTime: `${tokens["response-time"](req, res)} ms`,
});
})
);
...

To send these logs to Better Stack, install the Logtail client and integrate it with Morgan like this:

index.js
...
import { Logtail } from "@logtail/node"
...
const logtail = new Logtail("<your_better_stack_source_token>");
... app.use( morgan((tokens, req, res) => {
const entry = {
requestId: tokens.id(req, res),
method: tokens.method(req, res),
url: tokens.url(req, res),
status: parseInt(tokens.status(req, res), 10),
response_time: tokens["response-time"](req, res),
};
logtail.info("HTTP request", entry);
return null; // We return null so nothing prints to stdout
})
);

After saving your changes, the server will restart. Revisit the endpoints.

Return to Better Stack, and your source will update to show “Logs received!”. Click See Live tail to view your logs in real-time:

Screenshot of live tail in Better Stack

You’ll now see your logs appear within the Better Stack interface:

Screenshot of logs in Better Stack

With that, you can centralize and monitor your logs using Better Stack

Final thoughts

This article focused on Morgan, a straightforward yet effective middleware for logging HTTP requests in Node.js applications. While Morgan is an excellent choice for enhancing HTTP request visibility, it’s not your only option.

For instance, if you’re already using a comprehensive logging library like Pino, you might consider integrating pino-http instead of Morgan to handle HTTP request logging. This approach consolidates your logging stack under a single tool, reducing complexity and overhead.

Thanks for reading, and happy logging!

Author's avatar
Article by
Stanley Ulili
Stanley Ulili is a technical educator at Better Stack based in Malawi. He specializes in backend development and has freelanced for platforms like DigitalOcean, LogRocket, and AppSignal. Stanley is passionate about making complex topics accessible to developers.
Got an article suggestion? Let us know
Next article
A Comprehensive Guide to Logging in Python
Python provides a built-in logging module in its standard library that provides comprehensive logging capabilities for Python programs
Licensed under CC-BY-NC-SA

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Make your mark

Join the writer's program

Are you a developer and love writing and sharing your knowledge with the world? Join our guest writing program and get paid for writing amazing technical guides. We'll get them to the right readers that will appreciate them.

Write for us
Writer of the month
Marin Bezhanov
Marin is a software engineer and architect with a broad range of experience working...
Build on top of Better Stack

Write a script, app or project on top of Better Stack and share it with the world. Make a public repository and share it with us at our email.

community@betterstack.com

or submit a pull request and help us build better products for everyone.

See the full list of amazing projects on github