javascript

Is Your Node.js App Missing the Magic of Morgan for Logging?

Mastering Web Application Logging with Morgan in Node.js and Express

Is Your Node.js App Missing the Magic of Morgan for Logging?

Building a web application using Node.js and Express involves a lot of moving parts. One of the critical aspects often overlooked is logging HTTP requests. Proper logging is crucial for debugging, monitoring, and understanding how users interact with your application. Enter Morgan, a popular middleware designed for logging HTTP requests efficiently.

To start using Morgan, you first need to install it. This can be done via npm with a single command:

npm install morgan

With Morgan installed, integrating it into an Express application is straightforward. Let’s take a simple example to get the ball rolling:

const express = require('express');
const morgan = require('morgan');
const app = express();
const port = 3000;

app.use(morgan('combined'));

app.get('/', (req, res) => {
    res.send('Hello, World!');
});

app.listen(port, () => {
    console.log(`Server listening at http://localhost:${port}`);
});

In this snippet, morgan('combined') is used as middleware to log incoming requests in the Apache combined format. When a request hits the root URL (/), Morgan logs the request details to the console.

Morgan offers several predefined logging formats to suit different needs. Here are some of the popular ones:

  • combined: This is the Apache combined log format, containing details like the client’s IP, request method, URL, HTTP version, status code, and more.

    app.use(morgan('combined'));
    

    Example output:

    ::1 - - [26/Apr/2020:16:58:09 +0000] "GET / HTTP/1.1" 200 13 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.122 Safari/537.36"
    
  • common: Similar to the combined format but without the user agent and referrer.

    app.use(morgan('common'));
    

    Example output:

    ::1 - - [26/Apr/2020:16:58:09 +0000] "GET / HTTP/1.1" 200 13
    
  • dev: A more concise format with method, URL, status code, response time, and content length.

    app.use(morgan('dev'));
    

    Example output:

    GET / 200 13.066 ms - 13
    
  • tiny: A minimal format with method, URL, status code, and response time.

    app.use(morgan('tiny'));
    

    Example output:

    GET / 200 13ms - 13
    
  • short: A shorter version of the combined format, excluding the user agent and referrer.

    app.use(morgan('short'));
    

    Example output:

    ::1 - GET / HTTP/1.1 200 13 - 13ms
    

Predefined formats are super convenient, but sometimes you might need more customized logging. That’s where Morgan’s flexibility shines. You can create custom logging formats using tokens or even custom functions.

Here’s an example of using predefined tokens to create a custom format:

app.use(morgan(':method :url :status :res[content-length] - :response-time ms'));

This will log the request method, URL, status code, response content length, and response time.

For more complex needs, you can define a custom function to generate the log entry:

const customFormat = (tokens, req, res) => {
    return [
        tokens.method(req, res),
        tokens.url(req, res),
        tokens.status(req, res),
        tokens.res(req, res, 'content-length'),
        '-',
        tokens['response-time'](req, res),
        'ms'
    ].join(' ');
};

app.use(morgan(customFormat));

This function logs the same information as the previous example but offers more flexibility.

Sometimes, the predefined tokens might not cover all the information you need. Morgan allows the addition of custom tokens using functions. For instance, if you need to log the request host:

morgan.token('host', (req, res) => {
    return req.headers['host'];
});

app.use(morgan(':host :method :url :status :res[content-length] - :response-time ms'));

This will include the request host in the log output.

By default, Morgan logs to the standard output, usually the terminal. However, in a production environment, you might want to log to a file or another output stream. This can be done by specifying a stream in the options:

const fs = require('fs');
const logStream = fs.createWriteStream('access.log', { flags: 'a' });

app.use(morgan('combined', { stream: logStream }));

This will write the logs to a file named access.log.

Morgan is also smart enough to handle different logging scenarios. For example, you can log different types of requests to different destinations. Here’s how you can do it:

const fs = require('fs');
const errorLogStream = fs.createWriteStream('error.log', { flags: 'a' });
const accessLogStream = fs.createWriteStream('access.log', { flags: 'a' });

app.use(morgan('combined', { stream: accessLogStream }));
app.use(morgan('combined', {
    skip: (req, res) => res.statusCode < 400,
    stream: errorLogStream
}));

In this setup, all requests are logged to access.log, while error responses (status code 400 and above) are logged to error.log.

Morgan is a robust and adaptable tool for logging HTTP requests in Node.js applications. Whether it’s debugging, monitoring traffic, or analyzing performance, it provides vital insights into your application’s operations. By seamlessly integrating into Express and offering various logging options, Morgan proves to be an invaluable tool in any Node.js developer’s arsenal.

Cookie-cutter templates are convenient, but the magic of Morgan lies in its customizability. Whether it’s tweaking formats, creating custom tokens, or directing logs to different destinations, Morgan ensures that logging comes with the flexibility and control essential for maintaining and scaling modern web applications.

Keywords: Node.js, Express, web application, logging HTTP requests, Morgan middleware, npm install, debugging, monitoring, custom logging formats, Apache combined format



Similar Posts
Blog Image
Internationalization in Angular: Go Global with Transloco!

Transloco simplifies Angular app internationalization. Install, configure, create JSON files for languages, use translate pipe in templates, and TranslocoService in code. Change languages easily, handle variables, and organize translations efficiently.

Blog Image
JavaScript Atomics and SharedArrayBuffer: Boost Your Code's Performance Now

JavaScript's Atomics and SharedArrayBuffer enable low-level concurrency. Atomics manage shared data access, preventing race conditions. SharedArrayBuffer allows multiple threads to access shared memory. These features boost performance in tasks like data processing and simulations. However, they require careful handling to avoid bugs. Security measures are needed when using SharedArrayBuffer due to potential vulnerabilities.

Blog Image
The Ultimate Guide to Building a Custom Node.js CLI from Scratch

Create a Node.js CLI to boost productivity. Use package.json, shebang, and npm link. Add interactivity with commander, color with chalk, and API calls with axios. Organize code and publish to npm.

Blog Image
Mastering Node.js Memory: Advanced Techniques for Efficient and Scalable Applications

Node.js memory optimization: Tune garbage collection, use profiling tools, manage references, utilize WeakMap/WeakSet, implement streams, handle closures carefully, and remove event listeners properly.

Blog Image
How Can Helmet.js Make Your Express.js App Bulletproof?

Fortify Your Express.js App with Helmet: Your Future-Self Will Thank You