16

I want to log every request with response information.

I tried to use middleware, and I have a problem.

res.body is undefined.

app.use((req, res, next) => {

    var time = Date.now();

    res.on('finish', function() {
        var clientIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
        var method = req.method;
        var path = req.baseUrl;
        var duration = Date.now() - time;

        console.log({
            clientIp,
            elapsedTime: `${duration}ms`,
            hostname: req.headers.host,
            level: 'INFO',
            method,
            path,
            phase: process.env.NODE_ENV,
            reqBody: req.body,
            reqHeaders: req.headers,
            resBody: res.body,
            resHeaders: res.getHeaders(),
            status: res.statusCode
        });
    });

    next();
});

Actually there is response data on client.

How can I get response body in middleware?

kkangil
  • 1,616
  • 3
  • 13
  • 27

4 Answers4

13

The response can be intercepted by overriding response.json function. By doing so, and adding our custom function, every time, response.json() is called, our intercepting function is triggered.

middleware/response.filter.js:

// Response Interceptor Middleware
export default (request, response, next) => {
    try {
        const oldJSON = response.json;
        response.json = (data) => {
            // For Async call, handle the promise and then set the data to `oldJson`
            if (data && data.then != undefined) {
                // Resetting json to original to avoid cyclic call.
                return data.then((responseData) => {
                    // Custom logic/code.
                    response.json = oldJSON;
                    return oldJSON.call(response, responseData);
                }).catch((error) => {
                    next(error);
                });
            } else {
                // For non-async interceptor functions
                // Resetting json to original to avoid cyclic call.
                // Custom logic/code.
                response.json = oldJSON;
                return oldJSON.call(response, finalResponse);
            }
        }
    } catch (error) {
        next(error);
    }
}

In the Server.js file, register the middleware:

// Server.js file
import externalResponseFilter from "./middleware/response.filter.js:";

// Create Express server
const app = express();

// Response interceptor - Initialization.
app.use(externalResponseFilter);

And in the controller where you are returning the response, return with response.json() function instead of response.send().

Let me know if any additional explanation is required.

Sagar Chilukuri
  • 1,430
  • 2
  • 17
  • 29
  • Thank you for your answer. I tried your code, but I have a problem that I'm using `apollo-server` so I couldn't catch `res.body`. I think overriding `response.json` function is not running. Do you have any suggestion to solve this problem? – kkangil Aug 19 '19 at 08:01
  • I am not sure of `apollo-server` as it handles the request response communication internally. – Sagar Chilukuri Aug 19 '19 at 08:27
  • 1
    thanks anyway. I think I can use your answer other project. – kkangil Aug 19 '19 at 08:34
  • Sure. Let me know if there are any issues with the same. – Sagar Chilukuri Aug 19 '19 at 08:50
  • 1
    Did you actually test this middleware? I expect that it doesn't work at all, because it doesn't call `next()` unless there's an error, so it breaks the middleware chain entirely, and my very simple test server doesn't even get to the handler function. And it really shouldn't be calling the outer next() inside the overridden function. – cincodenada Jan 22 '22 at 00:31
  • Yes. Can you please share the piece of code where you have difficulty implementing the solution? – Sagar Chilukuri Jan 23 '22 at 11:57
  • how can we do this in typescript? – asim mehmood Sep 23 '22 at 21:43
  • 1
    What is the `finalResponse` object? – Alex Totolici Oct 13 '22 at 19:05
12

Intercept the response body from res.json and store it in res.locals for access later.

app.express.use((req, res, next) => {
  const oldJson = res.json;
  res.json = (body) => {
    res.locals.body = body;
    return oldJson.call(res, body);
  };
  next();
});
Ethan
  • 121
  • 1
  • 2
0

The response object in Express is simply node's http.ServerResponse class. It is a writable Stream so you can interact with it as a stream. It also exposes the underlying net.Socket connection via res.socket.

This is where it gets interesting. Because net.Socket is a plain writable Stream. If you override it's .write() method you will be able to intercept all bytes going out of your socket onto the network:

function interceptorMiddleware (req, res, next) {
    const sock  = req.socket;
    const write = sock.write.bind(sock);

    sock.write = (data, encoding, callback) => {
        console.log(data);
        write(data, encoding, callback);
    }

    sock.on('close', () => console.log('DONE'));

    next();
}

You need to install this middleware very early in your application before anything gets a chance to write to the socket.

Due to the asynchronous nature of node you may get mixed packets in your log output so it may be worth adding a bit of code to identify which socket is generating an output:

console.log(sock.remoteAddress, sock.remotePort, data);

Warning!

console.log() is synchronous when outputting to the terminal or a file. Doing this can potentially severely degrade your server's performance. If you are piping stdout to another process such as pm2 then it's not so bad because it will be async but it may still cost you a minor performance hit.

Use this for debugging ONLY

slebetman
  • 109,858
  • 19
  • 140
  • 171
0

Working perfectly in my project.

log.middleware.js

const uuid = require('uuid').v4; 
const log = require('../logger');

function middleware() {
    return async (req, res, next) => {
        try {
            req.user || (req.user = { trxId: uuid() });
            const { user, url, params, query, body } = req;
            log.debug({ user, url, params, query, reqBody: body }, "request info.");
            const oldJSON = res.json;
            res.json = async (value) => {
                const data = await Promise.resolve(value);
                log.debug({ user, resBody: data }, "response info.");
                return oldJSON.call(res, data);
            }
        } catch (error) {
            log.error(error, "got error");
            return next(error)
        }

        next();
    }
}
module.exports = middleware;

app.js

const logMiddleware = require("./middlewares/log.middleware");
const routes = require('./routes');

const app = express();
app.use(logMiddleware());
app.use("/", routes);
app.listen(8080, () => {
    log.info(`Server started`);
});
M. Hamza Rajput
  • 7,810
  • 2
  • 41
  • 36