【问题标题】:express logging response body快速记录响应正文
【发布时间】:2013-10-13 10:45:22
【问题描述】:

标题应该是自我解释的。

出于调试目的,我想快速打印每个服务请求的响应代码和正文。打印响应代码很容易,但打印响应正文比较棘手,因为看起来响应正文并不容易作为属性使用。

以下方法不起作用:

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);

当然,我可以轻松地在发出请求的客户端打印响应,但我也更喜欢在服务器端打印。

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

【问题讨论】:

    标签: node.js express


    【解决方案1】:

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

    function logResponseBody(req, res, next) {
      var oldWrite = res.write,
          oldEnd = res.end;
    
      var chunks = [];
    
      res.write = function (chunk) {
        chunks.push(chunk);
    
        return 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);
    

    【讨论】:

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

    您可以使用express-winston 并使用以下方式进行配置:

    expressWinston.requestWhitelist.push('body');
    expressWinston.responseWhitelist.push('body');
    

    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
        }));
    

    【讨论】:

    • 不记录响应
    • 在我看来这是最干净的解决方案,并且在我的代码中它确实可以完美运行:唯一的区别是我放置了“responseWhitelist.push('body');”在 app.use 之后。 app.use(expressWinston.logger({ winstonInstance: LogHelper })); expressWinston.requestWhitelist.push('body'); expressWinston.responseWhitelist.push('body');
    • 由于某种原因,白名单功能对我不起作用。
    【解决方案3】:

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

    res[MY_NAMESPACE].body = ...
    

    我有一个实用方法,它可以格式化对我的标准化 API/JSON 响应的所有响应,因此当日志被 onFinished res 事件触发时,在那里添加这一行会暴露响应正文。

    【讨论】:

    【解决方案4】:

    我使用 Laurent 建议的方法遇到了一个问题。有时块是一个字符串,因此会导致调用 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);
    

    【讨论】:

    • 我刚刚意识到我在很短的时间内以某种方式做到了。但删除了代码,因为它正在打印字节数组。我应该把它转换成字符串。 :(
    • 这样做,我会失去原来的 res 行为(res.write、res.send、res.end ...)吗?应用 oldEnd.apply(res, arguments) 可以避免这种情况吗?非常感谢。
    • 免责声明,如果您在应用程序中上传文件,这将变得非常丑陋。它将记录每一块例如。一个 200KB 的文件(这是很多日志记录)并且还会减慢文件上传速度。确保保护记录器不要记录来自例如请求的响应。 /fileUpload 路径
    【解决方案5】:

    上面接受的代码在 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;
    

    【讨论】:

    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

      这可能会对希望记录响应的人有所帮助 因此,我们使用中间件在将请求提供给客户端之前拦截请求。然后,如果我们使用 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;
      

      主文件 - 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');
      });
      

      这是 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(); 
      

      【讨论】:

        【解决方案8】:

        Typescript 解决方案基于 Laurent 的answer

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

        【讨论】:

          【解决方案9】:

          大多数建议似乎有点大锤,今晚花了一些时间处理这个问题,并在挖掘了一些库以帮助定制一些东西后写下了我的发现。

          //app.js
          ...
          app.use(requestLoggerMiddleware({ logger: console.log }));
          
          app.get(["/", "/api/health"], (req, res) => {
              res.send({ message: "OK", uptime: process.uptime() });
          ...
          });
          
          // 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 };
          

          git repo 中的完整工作示例和文章 https://github.com/JonathanTurnock/ReqResLoggingExample

          【讨论】:

          【解决方案10】:

          我对这个问题也有类似的需求。

          根据接受的答案,我只在它是 json 时使用代理和跟踪响应正文对其进行修改。

          const traceMiddleware = (req, res, next) => {
            const buffers = []
            const proxyHandler = {
              apply(target, thisArg, argumentsList) {
                const contentType = res.getHeader('content-type')
                if (
                  typeof contentType === 'string' && contentType.includes('json') && argumentsList[0]
                ) {
                  buffers.push(argumentsList[0])
                }
                return target.call(thisArg, ...argumentsList)
              }
            }
            res.write = new Proxy(res.write, proxyHandler)
            res.end = new Proxy(res.end, proxyHandler)
            res.on('finish', () => {
              // tracing logic inside
              trace(req, res, Buffer.concat(buffers).toString('utf8'))
            })
            next()
          }
          

          【讨论】:

            【解决方案11】:

            对于某些用例而言,此解决方案可能不够重量级,但我认为它是最简单的。它也与打字稿兼容。如果您只想记录 JSON 响应,您所要做的就是用下面代码中的 json 方法替换 send 方法。请注意,我从 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();
            });
            
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-03-14
              • 2021-07-21
              • 2018-05-02
              • 2011-03-15
              • 1970-01-01
              • 2012-12-29
              • 2019-02-17
              • 2012-10-26
              相关资源
              最近更新 更多