在 Nest.js 中记录请求/响应

Jul*_*ien 26 typescript nestjs

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

:method :url :status :res[content-length] - :response-time ms
Run Code Online (Sandbox Code Playgroud)

根据我的理解,最好的地方是拦截器。但是我也使用了Guards,正如前面提到的,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);
      }),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

Jul*_*ien 13

我最终在原始应用程序上注入了一个经典的记录器。这个解决方案不是最好的,因为它没有集成到 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));
Run Code Online (Sandbox Code Playgroud)

  • 你可以实现一个基于类的“中间件”,这样你就可以利用Nestjs的“依赖注入”。甚至“morgan”也使用一些 hack 来记录请求和响应。所以我们可以类似地实现我们的“中间件”。 (4认同)

hoj*_*jin 9

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

并在 AppModule 中

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

  • 但关闭回调永远不会被调用 (2认同)
  • 响应对象中缺少响应数据? (2认同)

Sta*_*eon 5

如何使用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();
  }
}
Run Code Online (Sandbox Code Playgroud)

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

参考

01. 关于response事件的节点文档
02. Github 问题

  • 如何获取该中间件中响应对象发送给用户的数据?@斯塔克·乔恩 (2认同)

Cra*_*les 5

我决定使用 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', '')),
            },
        }),
    );
}
Run Code Online (Sandbox Code Playgroud)
// 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()}`);
}
Run Code Online (Sandbox Code Playgroud)