【问题标题】:Logging request/response in Nest.js在 Nest.js 中记录请求/响应
【发布时间】:2019-08-01 05:56:28
【问题描述】:

Nest.js 新手,
我正在尝试实现一个简单的记录器来跟踪 HTTP 请求,例如:

:method :url :status :res[content-length] - :response-time ms

据我了解,最好的地方是interceptors。但我也使用Guards 并且如前所述,警卫是在中间件之后但拦截器之前触发的。

意思是,我的禁止访问没有被记录。我可以在两个不同的地方编写日志记录部分,但不能。有什么想法吗?

谢谢!

我的拦截器代码:

import { Injectable, NestInterceptor, ExecutionContext, HttpException, HttpStatus } from '@nestjs/common';
import { Observable, throwError } from 'rxjs';
import { catchError, tap } from 'rxjs/operators';

@Injectable()
export class HTTPLoggingInterceptor implements NestInterceptor {

  intercept(context: ExecutionContext, call$: Observable<any>): Observable<any> {
    const now = Date.now();
    const request = context.switchToHttp().getRequest();

    const method = request.method;
    const url = request.originalUrl;

    return call$.pipe(
      tap(() => {
        const response = context.switchToHttp().getResponse();
        const delay = Date.now() - now;
        console.log(`${response.statusCode} | [${method}] ${url} - ${delay}ms`);
      }),
      catchError((error) => {
        const response = context.switchToHttp().getResponse();
        const delay = Date.now() - now;
        console.error(`${response.statusCode} | [${method}] ${url} - ${delay}ms`);
        return throwError(error);
      }),
    );
  }
}

【问题讨论】:

标签: typescript nestjs


【解决方案1】:

我决定使用 Morgan 作为中间件来拦截请求,因为我喜欢格式化选项,同时使用标准的 Nest Logger 来处理输出以保持与我的应用程序的其余部分的一致性。

// middleware/request-logging.ts
import { Logger } from '@nestjs/common';
import morgan, { format } from 'morgan';

export function useRequestLogging(app) {
    const logger = new Logger('Request');
    app.use(
        morgan('tiny', {
            stream: {
                write: (message) => logger.log(message.replace('\n', '')),
            },
        }),
    );
}
// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { useRequestLogging } from './middleware/request-logging';

async function bootstrap() {
    const app = await NestFactory.create(AppModule);
    useRequestLogging(app);
    await app.listen(configService.get<number>('SERVER_PORT'));
    logger.log(`Application is running on: ${await app.getUrl()}`);
}

【讨论】:

    【解决方案2】:

    如何使用finish 事件而不是close 事件。

    import { Request, Response, NextFunction } from "express";
    import { Injectable, NestMiddleware, Logger } from "@nestjs/common";
    
    @Injectable()
    export class LoggerMiddleware implements NestMiddleware {
      private logger = new Logger("HTTP");
    
      use(request: Request, response: Response, next: NextFunction): void {
        const { ip, method, originalUrl } = request;
        const userAgent = request.get("user-agent") || "";
    
        response.on("finish", () => {
          const { statusCode } = response;
          const contentLength = response.get("content-length");
    
          this.logger.log(
            `${method} ${originalUrl} ${statusCode} ${contentLength} - ${userAgent} ${ip}`,
          );
        });
    
        next();
      }
    }
    

    因为据了解express 在发送响应后保持连接。
    所以close事件不能被触发

    参考

    01. Node document about response event.
    02. Github issue

    【讨论】:

    • 如何在该中间件的响应对象中获取发送给用户的数据? @斯塔克乔恩
    • 其实我猜你可以看看responseside
    • 我没有在 Response 对象中找到数据?你能检查一下吗?@Stark
    【解决方案3】:

    https://github.com/julien-sarazin/nest-playground/issues/1#issuecomment-682588094

    您可以为此使用中间件。

    import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
    
    import { Request, Response, NextFunction } from 'express';
    
    @Injectable()
    export class AppLoggerMiddleware implements NestMiddleware {
      private logger = new Logger('HTTP');
    
      use(request: Request, response: Response, next: NextFunction): void {
        const { ip, method, path: url } = request;
        const userAgent = request.get('user-agent') || '';
    
        response.on('close', () => {
          const { statusCode } = response;
          const contentLength = response.get('content-length');
    
          this.logger.log(
            `${method} ${url} ${statusCode} ${contentLength} - ${userAgent} ${ip}`
          );
        });
    
        next();
      }
    }
    

    在 AppModule 中

    export class AppModule implements NestModule {
      configure(consumer: MiddlewareConsumer): void {
        consumer.apply(AppLoggerMiddleware).forRoutes('*');
      }
    }
    

    【讨论】:

    • 但是关闭回调永远不会被调用
    • 响应对象中缺少响应数据?
    【解决方案4】:

    试试这个代码 =>

    @Injectable()
    export class LoggerMiddleware implements NestMiddleware {
        use(req: Request, res: Response, next: Function) {
            const { ip, method, originalUrl: url  } = req;
            const hostname = require('os').hostname();
            const userAgent = req.get('user-agent') || '';
            const referer = req.get('referer') || '';
    
            res.on('close', () => {
                const { statusCode, statusMessage } = res;
                const contentLength = res.get('content-length');
                logger.log(`[${hostname}] "${method} ${url}" ${statusCode} ${statusMessage} ${contentLength} "${referer}" "${userAgent}" "${ip}"`);
            });
    
            next();
        }
    }
    

    【讨论】:

      【解决方案5】:

      您可以按照官方文档中提供的LoggerMiddleware 来实现您的自定义记录器。

      您可以将记录器应用于通配符 * 路由,以记录所有请求和响应。在 logger 类中,您可以在请求之前和之后选择要记录的自定义字段:

      export class AppModule implements NestModule {
        configure(consumer: MiddlewareConsumer) {
          consumer
            .apply(LoggerMiddleware)
            .forRoutes('*');
        }
      }
      
      class LoggerMiddleware implements NestMiddleware {
        use(req: Request, res: Response, next: Function) {
          console.log('Request', req.method, req.originalUrl, /*...*/);
          next();
          console.log('Response', res.statusCode, res.statusMessage, /*...*/);
        }
      }
      

      【讨论】:

      • res.statusCode 始终为 200, res.finised 始终为 false。
      【解决方案6】:

      我最终在原始应用程序中注入了一个经典记录器。 这个解决方案不是最好的,因为它没有集成到 Nest 流中,但可以很好地满足标准的日志记录需求。

      import { NestFactory } from '@nestjs/core';
      import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
      import { ApplicationModule } from './app.module';
      import * as morgan from 'morgan';
      
      async function bootstrap() {
          const app = await NestFactory.create<NestFastifyApplication>(ApplicationModule, new FastifyAdapter());
          app.use(morgan('tiny'));
      
          await app.listen(process.env.PORT, '0.0.0.0');
      }
      
      if (isNaN(parseInt(process.env.PORT))) {
          console.error('No port provided. ?');
          process.exit(666);
      }
      
      bootstrap().then(() => console.log('Service listening ?: ', process.env.PORT));
      

      【讨论】:

      • 你可以实现一个基于类的middleware,这样你就可以利用Nestjs dependency injection。甚至morgan 也使用了一些技巧来记录请求和响应。所以我们可以类似地实现我们的middleware
      【解决方案7】:

      由于过滤器在拦截器之后运行,我在记录正确的状态代码时遇到了类似的问题。我能想到的唯一让我满意的解决方案是在拦截器中实现日志记录。与您在代码中所做的非常相似。当过滤器在拦截器运行后运行时,可以利用 observable 在成功完成或出错后执行函数。

      对我来说,诀窍是即使在 tapcatchError 运算符中也不能保证正确设置响应上的状态码。我通过检查请求的方法解决了这个问题,如果它是 POST 方法,那么我知道成功的响应是 201,否则它总是 200。
      如果我收到错误,我会从错误中获取状态代码并使用它来代替响应对象上的状态代码。由于我的 Exception 过滤器将在 observable 完成之前运行,我知道此时我的错误对象上将存在一个 statusCode。

      【讨论】:

      • 感谢您的帖子。我不确定这是一种让我感到舒服的方式。在我的服务定义中,许多操作是通过 POST 请求完成的,但其中一半以 204 回复,因为不需要返回数据。此外,我不明白为什么这种基本需求需要“解决方法”解决方案。我的意思是我有一段时间以来一直是 nodeJS 的重度用户,使用了大量的框架(express、Hapi、Loopback、Sails),我什至开发了自己的.. 看到 Nest 的设计有多好,我绝对确定我们错过了一些东西。我会在找到解决方案后尽快回复。
      • 我对此也不是很满意。我同意这似乎是您可以在 express 等框架中轻松完成的事情。我发现有效的另一个选项是将实际记录请求的逻辑放在 setTimeout 和时间 0 中。这似乎允许正确设置过滤器中的状态代码,我不必假设状态码。但是,我认为这个解决方案比假设 201 和 200 更不理想。如果您找到更理想的解决方案,请回复。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多