ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • nestjs-pino 로깅 처리
    NodeJS 2023. 5. 30. 13:58

    nestjs-pino 로깅 처리

    상황 정리

     

    위와 같은 WEB 환경에서 Some logging의 경우 Request(요청) 정보가 없기 때문에 동시 다발적인 이벤트의 로그들을 추적하기가 불가능 합니다.

    이를 nestjs-pino 이용해서 각 로그별로 동일한 요청의 경우 연결 처리 할 수 있도록 작성합니다

    nestjs-pino에서도 적혀 있지만, pino-http모듈을 nestjs에 녹인 프로젝트입니다.

    필요 모듈 정보

    • nestjs-pino : nestjs와 연동 처리된 모듈
    • pino-http : pino 로그에 request, response 정보를 bind 처리한 모듈
    • file-stream-rotator : 파일 스트림을 기반으로 파일의 생명주기를 관리하는 모듈
    • pino-pretty : pino 로그의 결과를 이쁘게 정렬하여 표기하는 모듈

    기본 모듈 설치 및 설정

    nestjs-pino 는 기본적으로 로그 파일 저장시 파일을 나눠주거나, 관리를 지원하지 않습니다.

    이를 file-stream-rotator 모듈을 이용해서 로그 파일에 대한 생명주기 및 적재 관리를 합니다.

    다음 명령어로 기본적인 모듈을 설치합니다.

    $ npm i nestjs-pino pino-http file-stream-rotator

    nestjs 설정하기

    먼저, nestjs의 main.ts에서 Logging을 설정 합니다.

    import { Logger } from "nestjs-pino";
    
    const app = await NestFactory.create(AppModule, { bufferLogs: true });
    app.useLogger(app.get(Logger));

    이후 app.module.ts에 아래와 같이 설정 합니다.

    import { LoggerModule } from "nestjs-pino";
    
    @Module({
        imports: [LoggerModule.forRoot()],
    })
    class AppModule {}

    위의 예제는 기본 설정입니다.

    위와 같이 설정 후 아래와 같이 로깅을 하면,

    // NestJS standard built-in logger.
    // Logs will be produced by pino internally
    import { Logger } from '@nestjs/common';
    
    export class MyService {
      private readonly logger = new Logger(MyService.name);
      foo() {
        // All logger methods have args format the same as pino, but pino methods
        // `trace` and `info` are mapped to `verbose` and `log` to satisfy
        // `LoggerService` interface of NestJS:
        this.logger.verbose({ foo: 'bar' }, 'baz %s', 'qux');
        this.logger.debug('foo %s %o', 'bar', { baz: 'qux' });
        this.logger.log('foo');
      }
    }

    아래와 같이 표출 됩니다.

    // Logs by injected Logger and PinoLogger in Services/Controllers. Every log
    // has it's request data and unique `req.id` (by default id is unique per
    // process, but you can set function to generate it from request context and
    // for example pass here incoming `X-Request-ID` header or generate UUID)
    {"level":10,"time":1629823792023,"pid":15067,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","query":{},"params":{"0":""},"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"},"remoteAddress":"::1","remotePort":63822},"context":"MyService","foo":"bar","msg":"baz qux"}
    {"level":20,"time":1629823792023,"pid":15067,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","query":{},"params":{"0":""},"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"},"remoteAddress":"::1","remotePort":63822},"context":"MyService","msg":"foo bar {\"baz\":\"qux\"}"}
    {"level":30,"time":1629823792023,"pid":15067,"hostname":"my-host","req":{"id":1,"method":"GET","url":"/","query":{},"params":{"0":""},"headers":{"host":"localhost:3000","user-agent":"curl/7.64.1","accept":"*/*"},"remoteAddress":"::1","remotePort":63822},"context":"MyService","msg":"foo"}

    로깅 결과 이쁘게 표시 하기

    pino-pretty는 stdout 리디렉션을 사용하기 때문에 경우에 따라 셸 제한으로 인해 명령이 오류와 함께 종료될 수 있습니다. 따라서 운영에서는 사용하지 말고, 개발시에만 사용하세요.

    로깅을 이쁘게 표기하기 위해서는 pino-pretty모듈을 설치하면 됩니다.

    $ npm install pino-pretty

    설정은 아래와 같이 할 수 있습니다.

    import { LoggerModule } from 'nestjs-pino';
    
    @Module({
      imports: [
        LoggerModule.forRoot({
          pinoHttp: [
            {
              name: 'add some name to every JSON line',
              level: process.env.NODE_ENV !== 'production' ? 'debug' : 'info',
              // install 'pino-pretty' package in order to use the following option
              transport: process.env.NODE_ENV !== 'production'
                ? { target: 'pino-pretty' }
                : undefined,
              useLevelLabels: true,
              // and all the others...
            },
          ],
        })
      ],
      ...
    })
    class MyModule {}

    처리를 하면 다음과 같이 정렬된 로그를 확인 가능합니다.

    [17:05:49.172] INFO (20249): GET / 200 168 - Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 ::1
       {
      "level": 10,
      "time": 1629823792023,
      "pid": 15067,
      "hostname": "my-host",
      "req": {
        "id": 1,
        "method": "GET",
        "url": "/",
        "query": {
    
        },
        "params": {
          "0": ""
        },
        "headers": {
          "host": "localhost:3000",
          "user-agent": "curl/7.64.1",
          "accept": "*/*"
        },
        "remoteAddress": "::1",
        "remotePort": 63822
      },
      "context": "MyService",
      "foo": "bar",
      "msg": "baz qux"
    }
    

    로그 설정 파일 관리

    다음은 pinoLogging.ts로 로깅 설정에 대한 정보입니다.

    import pino from 'pino';
    import * as FileStreamRotator from 'file-stream-rotator';
    import { v4 } from 'uuid';
    
    type Level = 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace';
    
    export default () => {
      const LOG_LEVEL = process.env.LOGGING_DEBUG ? 'debug' : 'info';
      // 로그 파일 관리 스트림 생성
      const rotatingLogStream = FileStreamRotator.getStream({
        filename: `${process.env.LOGGING_PATH}/${LOG_LEVEL}/${LOG_LEVEL}-%DATE%`, // 파일 위치 & 이름
        frequency: '1h', // 주기 설정
        date_format: 'YYYY-MM-DD-HH', // 데이터 포멧 설정
        size: process.env.LOGGING_MAXSIZE, // 최대 파일 크기 설정
        max_logs: process.env.LOGGING_MAXFILES, // 파일 로깅
        audit_file: `${process.env.LOGGING_PATH}/audit.json`, // 정보 파일
        extension: '.log', // 로그 확장자
        create_symlink: true, // 링크 파일 여부
        symlink_name: 'tail-current.log', //링크 파일 명
      });
    
      return {
        logginConfig: {
          pinoHttp: {
            genReqId: function (req, res) { // req - id 를 uuid로 생성
              const uuid = v4();
              res.header('X-Request-Id', uuid);
              return uuid;
            },
            transport: LOG_LEVEL === 'debug' // debug일 경우 pretty 처리
            ? { target: 'pino-pretty' }
            : undefined,
            level: LOG_LEVEL, // 여기에도 있고, stream 상세에도 있어야 정상 동작 한다
            stream: pino.multistream([ // multistream으로 여러군데 동시 출력
              {
                stream: rotatingLogStream,
                level: LOG_LEVEL as Level,
              },
              {
                stream: process.stdout, // 콘솔에 출력
                level: process.env.LOGGING_CONSOLE_LEVEL as Level,
              },
            ]),
            formatters: { // 로그 표시시 포멧
              level(level) {
                return { level };
              },
            },
            redact: { // 로그 표기시 제외 처리
              remove: true,
              paths: [
                'email',
                'password',
                'req.query',
                'req.params',
                'req.query',
                'res.headers',
                'req.headers.host',
                'req.headers.connection',
                'req.headers.accept',
                'req.headers.origin',
                'req.headers.referer',
                'req.headers["content-type"]',
                'req.headers["sec-ch-ua"]',
                'req.headers["sec-ch-ua-mobile"]',
                'req.headers["user-agent"]',
                'req.headers["sec-ch-ua-platform"]',
                'req.headers["sec-fetch-site"]',
                'req.headers["sec-fetch-mode"]',
                'req.headers["sec-fetch-dest"]',
                'req.headers["accept-encoding"]',
                'req.headers["accept-language"]',
                'req.headers["if-none-match"]',
              ],
            },
            timestamp: pino.stdTimeFunctions.isoTime,
          },
        },
      };
    };
    

    전체 예제 코드 바로 가기

    전체 예제 코드를 보면, nestjs-pino 외에도 nestjs에서 사용되는 여러 모듈의 사용법을 정리해 두었습니다.

    참고자료

    'NodeJS' 카테고리의 다른 글

    정규식을 이용해서 package-lock.json의 주소를 변경 처리  (0) 2023.06.02
    VSCode에서 Jest Debug 설정  (0) 2023.05.31
    Node.js 메모리 옵션  (0) 2023.05.29
    Nodejs 동작 훑어보기  (0) 2023.05.27
    SLACK 봇 알림 처리  (0) 2023.05.26
Designed by Tistory.