表达记录响应机构

Ric*_*ard 51 node.js express

标题应该是非常自我解释的.

出于调试目的,我想表达为每个服务请求打印响应代码和正文.打印响应代码很容易,但打印响应主体比较棘手,因为似乎响应主体不是作为属性提供的.

以下不起作用:

var express = require('express');
var app = express();

// define custom logging format
express.logger.format('detailed', function (token, req, res) {                                    
    return req.method + ': ' + req.path + ' -> ' + res.statusCode + ': ' + res.body + '\n';
});  

// register logging middleware and use custom logging format
app.use(express.logger('detailed'));

// setup routes
app.get(..... omitted ...);

// start server
app.listen(8080);
Run Code Online (Sandbox Code Playgroud)

当然,我可以轻松地在发出请求的客户端上打印响应,但我更喜欢在服务器端进行操作.

PS:如果有帮助,我的所有回复都是json,但希望有一个解决方案适用于一般回复.

Lau*_*rin 71

不确定它是否是最简单的解决方案,但您可以编写中间件来拦截写入响应的数据.确保禁用app.compress().

function logResponseBody(req, res, next) {
  var oldWrite = res.write,
      oldEnd = res.end;

  var chunks = [];

  res.write = function (chunk) {
    chunks.push(chunk);

    oldWrite.apply(res, arguments);
  };

  res.end = function (chunk) {
    if (chunk)
      chunks.push(chunk);

    var body = Buffer.concat(chunks).toString('utf8');
    console.log(req.path, body);

    oldEnd.apply(res, arguments);
  };

  next();
}

app.use(logResponseBody);
Run Code Online (Sandbox Code Playgroud)

  • 有时块可能是字符串,因此为了正确处理它们,请使用 var body = Buffer.concat(chunks.map(x => (typeof (x) === "string" ? Buffer.from(x, '二进制') : x))).toString('utf8'); (4认同)
  • @keegans-hairstyle-82, res.statusCode 致所有人:不要使用箭头函数重写 - oldEnd.apply 需要重写。 (3认同)
  • @npr,您可以拦截标头并使用代码 `const { rawHeaders, headers } = req; 编辑“缓存控制” headers["cache-control"] = "no-cache"` 邮递员会自动将其添加到其标头中。 (3认同)
  • 这里使用类似的、稍微更详细的解决方案:https://github.com/SummerWish/express-minify/blob/master/minify.js#L270 (2认同)
  • 奇怪的是,仅当通过邮递员客户端而不是浏览器发出请求时才会捕获响应。 (2认同)

Ari*_*rik 23

对于某些用例来说,这个解决方案可能不够重量级,但我认为它是最简单的。它也兼容打字稿。如果您只想记录 JSON 响应,则只需将下面代码中的 send 方法替换为 json 方法即可。请注意,我从 Jonathan Turnock 的答案中获得了灵感,但使其变得更简单。

app.use((req, res, next) => {
    let send = res.send;
    res.send = c => {
        console.log(`Code: ${res.statusCode}`);
        console.log("Body: ", c);
        res.send = send;
        return res.send(c);
    }
    next();
});

Run Code Online (Sandbox Code Playgroud)


小智 17

我使用Laurent建议的方法遇到了一个问题.有时chunk是一个字符串,因此在调用Buffer.concat()时会出现问题.无论如何,我发现了一些修改后的东西:

function logResponseBody(req, res, next) {
  var oldWrite = res.write,
      oldEnd = res.end;

  var chunks = [];

  res.write = function (chunk) {
    chunks.push(new Buffer(chunk));

    oldWrite.apply(res, arguments);
  };

  res.end = function (chunk) {
    if (chunk)
      chunks.push(new Buffer(chunk));

    var body = Buffer.concat(chunks).toString('utf8');
    console.log(req.path, body);

    oldEnd.apply(res, arguments);
  };

  next();
}

app.use(logResponseBody);
Run Code Online (Sandbox Code Playgroud)


Man*_*eau 11

您可以使用express-winston并配置使用:

expressWinston.requestWhitelist.push('body');
expressWinston.responseWhitelist.push('body');
Run Code Online (Sandbox Code Playgroud)

coffeescript中的示例:

expressWinston.requestWhitelist.push('body')
expressWinston.responseWhitelist.push('body')
app.use(expressWinston.logger({
      transports: [
        new winston.transports.Console({
          json: true,
          colorize: true
        })
      ],
      meta: true, // optional: control whether you want to log the meta data about the request (default to true)
      msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}"
      expressFormat: true, // Use the default Express/morgan request formatting, with the same colors. Enabling this will override any msg and colorStatus if true. Will only output colors on transports with colorize set to true
      colorStatus: true, // Color the status code, using the Express/morgan color palette (default green, 3XX cyan, 4XX yellow, 5XX red). Will not be recognized if expressFormat is true
      ignoreRoute: function (req, res) { return false; } // optional: allows to skip some log messages based on request and/or response
    }));
Run Code Online (Sandbox Code Playgroud)


Jon*_*ock 11

大多数建议似乎都有点大锤,今晚花了一些时间解决这个问题,并在深入研究了一些库以帮助定制一些东西后写下了我的发现。

//app.js
...
app.use(requestLoggerMiddleware({ logger: console.log }));

app.get(["/", "/api/health"], (req, res) => {
    res.send({ message: "OK", uptime: process.uptime() });
...
});
Run Code Online (Sandbox Code Playgroud)
// middleware.js
/**
 * Interceptor function used to monkey patch the res.send until it is invoked
 * at which point it intercepts the invokation, executes is logic such as res.contentBody = content
 * then restores the original send function and invokes that to finalize the req/res chain.
 *
 * @param res Original Response Object
 * @param send Original UNMODIFIED res.send function
 * @return A patched res.send which takes the send content, binds it to contentBody on
 * the res and then calls the original res.send after restoring it
 */
const resDotSendInterceptor = (res, send) => (content) => {
    res.contentBody = content;
    res.send = send;
    res.send(content);
};

/**
 * Middleware which takes an initial configuration and returns a middleware which will call the
 * given logger with the request and response content.
 *
 * @param logger Logger function to pass the message to
 * @return Middleware to perform the logging
 */
const requestLoggerMiddleware = ({ logger }) => (req, res, next) => {
    logger("RECV <<<", req.method, req.url, req.hostname);
    res.send = resDotSendInterceptor(res, res.send);
    res.on("finish", () => {
        logger("SEND >>>", res.contentBody);
    });
    next();
};

module.exports = { requestLoggerMiddleware };
Run Code Online (Sandbox Code Playgroud)

完整的工作示例和文章位于 git 存储库 https://github.com/JonathanTurnock/ReqResLoggingExample


Joe*_*oel 9

我发现这个问题最简单的解决方案是body在发送响应时向 res 对象添加一个属性,稍后记录器可以访问该属性。我将它添加到我在 req 和 res 对象上维护的我自己的命名空间中以避免命名冲突。例如

res[MY_NAMESPACE].body = ...
Run Code Online (Sandbox Code Playgroud)

我有一个实用方法,可以格式化对我的标准化 API/JSON 响应的所有响应,因此当日志被onFinishedres.

  • 我发现这是最优雅的解决方案。响应对象中甚至考虑到了这一点:https://expressjs.com/en/api.html#res.locals (4认同)

San*_*Ram 9

以上接受的代码与ES6有关.使用以下代码

function logReqRes(req, res, next) {
  const oldWrite = res.write;
  const oldEnd = res.end;

  const chunks = [];

  res.write = (...restArgs) => {
    chunks.push(Buffer.from(restArgs[0]));
    oldWrite.apply(res, restArgs);
  };

  res.end = (...restArgs) => {
    if (restArgs[0]) {
      chunks.push(Buffer.from(restArgs[0]));
    }
    const body = Buffer.concat(chunks).toString('utf8');

    console.log({
      time: new Date().toUTCString(),
      fromIP: req.headers['x-forwarded-for'] || 
      req.connection.remoteAddress,
      method: req.method,
      originalUri: req.originalUrl,
      uri: req.url,
      requestData: req.body,
      responseData: body,
      referer: req.headers.referer || '',
      ua: req.headers['user-agent']
    });

    // console.log(body);
    oldEnd.apply(res, restArgs);
  };

  next();
}

module.exports = logReqRes;
Run Code Online (Sandbox Code Playgroud)


Luk*_*kas 6

基于 Laurent 答案的打字稿解决方案方案:

\n\n
import { NextFunction, Request, Response } from \'express-serve-static-core\';\n//...\n\napp.use(logResponseBody);\n\nfunction logResponseBody(req: Request, res: Response, next: NextFunction | undefined) {\n    const [oldWrite, oldEnd] = [res.write, res.end];\n    const chunks: Buffer[] = [];\n\n    (res.write as unknown) = function(chunk) {\n        chunks.push(Buffer.from(chunk));\n        (oldWrite as Function).apply(res, arguments);\n    };\n\n    res.end = function(chunk) {\n        if (chunk) {\n            chunks.push(Buffer.from(chunk));\n        }\n        const body = Buffer.concat(chunks).toString(\'utf8\');\n        console.log(new Date(), `  \xe2\x86\xaa [${res.statusCode}]: ${body}`);\n        (oldEnd as Function).apply(res, arguments);\n    };\n    if (next) {\n      next();\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n


Sir*_*dge 5

我实际上制作了这个漂亮的小npm来解决这个确切的问题,希望您喜欢!

https://www.npmjs.com/package/morgan-body

摩根身体在行动!


Joe*_*587 5

可能这会帮助那些希望记录响应的人因此,我们使用中间件在请求被提供给客户端之前拦截请求。然后,如果我们使用 res.send 方法发送数据,请覆盖中间件中的方法并确保控制台记录正文。如果您打算单独使用 res.send 那么这应该可以正常工作,但是如果您使用 res.end 或 res.sendFile,则覆盖这些方法并仅记录所需的内容(显然记录整个文件的八位字节流不应该出于性能目的进行记录。

这里我使用 pino 作为记录器。将其创建为单例服务。

// LoggingResponseRouter.js

var loggingResponseRouter = require('express').Router();
var loggingService = require('./../service/loggingService');
var appMethodInstance = require('./../constants/appMethod');
var path = require('path');
var fs = require('fs');
var timeZone = require('moment-timezone');
var pino = require('pino')();


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

    // set the fileName it needs to log
    appMethodInstance.setFileName(__filename.substring(__filename.lastIndexOf(path.sep) + 1, __filename.length - 3));
    //loggingService.debugAndInfolog().info('logging response body', appMethodInstance.getFileName()); 
    let send = res.send;
    res.send = function(body){
        loggingService.debugAndInfolog().info('Response body before sending: ', body);
        send.call(this, body);
    }
    next();
});
module.exports = loggingResponseRouter;
Run Code Online (Sandbox Code Playgroud)

主文件 - Main.js

const corsRouter = require('./app/modules/shared/router/corsRouter');
const logRequestRouter = require('./app/modules/shared/router/loggingRequestRouter');
const loggingResponseRouter = require('./app/modules/shared/router/loggingResponseRouter');
const express = require('express');
var path = require('path');
const app = express();


// define bodyparser middleware
const bodyParser = require('body-parser');

const port = process.env.PORT || 3000;

// Now use the middleware prior to any others
app.use(bodyParser.json());
// use this to read url form encoded values as wwell
app.use(bodyParser.urlencoded({extended:true}));
console.log('before calling cors router in main js');
app.use(corsRouter);
app.use(logRequestRouter);
app.use(loggingResponseRouter);


app.get('/api', (req, res) => {
    console.log('inside api call');
    res.send('aapi');
});


app.listen(port, () => {
    console.log('starting the server');
});
Run Code Online (Sandbox Code Playgroud)

这是 loggingService - loggingService.js

var pino = require('pino');
var os = require('os');
var appMethodInstance = require('./../constants/appMethod'); 
var pinoPretty = require('pino-pretty');
var moment = require('moment');
var timeZone = require('moment-timezone');


class Logger{
    constructor(){
        this.appName = 'Feedback-backend';
        this.filenameval = '';

    }

    getFileName(){
        console.log('inside get filename');
        console.log(appMethodInstance.getFileName());
        if(appMethodInstance.getFileName() === null || appMethodInstance.getFileName() === undefined){
            this.filenameval = 'bootstrapping...'
        }else {
            this.filenameval = appMethodInstance.getFileName();
        }
        console.log('end');
        return this.filenameval;
    }

    debugAndInfolog(){
        return pino({
                    name: 'feedback-backend',
                    base: {
                        pid: process.pid,
                        fileName: this.getFileName(),
                        moduleName: 'modulename',
                        timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),

                        hostName: os.hostname()
                    },
                    level: 'info',
                    timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
                    messageKey: 'logMessage',
                    prettyPrint: {
                        messageKey: 'logMessage'
                    }
    });
}

errorAndFatalLog(){

    return pino({
        name: 'feedback-backend',
        base: {
            pid: process.pid,
            fileName: this.getFileName(),
            moduleName: 'modulename',
            timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
            hostName: os.hostname()
        },
        level: 'error',
        timestamp: timeZone().tz('America/New_York').format('YYYY-MM-DD HH:mm:ss.ms'),
        prettyPrint: {
            messageKey: 'FeedbackApp'
        }
    });
}
}




module.exports = new Logger(); 
Run Code Online (Sandbox Code Playgroud)