akjfal

nextjs와 pino를 이용한 로그 처리 및 로그 파일 저장 본문

react

nextjs와 pino를 이용한 로그 처리 및 로그 파일 저장

akjfal 2022. 7. 12. 18:28

Nextjs 공식 문서를 찾아보면, 로그에 관련되서 구조화된 로크 패키지를 원한다면 Pino를 사용할 것을 권하고 있습니다.

참고

 

Going to Production | Next.js

Before taking your Next.js application to production, here are some recommendations to ensure the best user experience.

nextjs.org

해당 로그들을 파일로 뽑아내야 할 일이 있어 Pino를 사용해 getServerSideProps에서 나오는 로그들을 뽑아내보았습니다.

아래는 버전 참고하시면 될듯합니다.

// package.json
{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev -p 3000",
    "build": "next build",
    "start": "NODE_ENV=production node server.js"
  },
  "dependencies": {
    "next": "^12.2.2",
    "pino": "^8.1.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

Pino를 사용한 기본 로깅

 

우선 pino를 사용한 코드를 작성해줍니다.

// logger.js
import pino from 'pino';

const logger = pino();

export const log = (msg) => logger.info(msg);

다음으로 페이지 코드를 작성하는데, getServerSideProps의 에러를 잡아내기 위한 코드만 적었습니다.

// pages/index.js

import { log } from '../next-pino/logger';

...

export const getServerSideProps = async () => {
    try {
        // 에러 강제 발생
        throw new Error('make error');
        return {
            props: {},
        };
    } catch (error) {
        console.log('에러 로그를 찍습니다');
        log(error);
        return { props: {} };
    }
};

해당 페이지를 실행시키면 에러가 발생하고 서버에서 아래와 같이 출력됩니다.

로그를 간단히 살펴보면 아래와 같습니다.

  • level : trace : 10, debug: 20, info : 30, warn: 40, error: 50, fatal: 60으로 지정되어져있고 커스텀 가능합니다.
  • time : 에러가 기록된 시간입니다.
  • err : msg로 넣은 메시지가 들어가게 됩니다.

이를 통해서 getServerSideProps에서 발생하는 에러들을 출력시켰습니다.


Pino 환경 설정 커스텀

 level에 따른 로그 저장이 다르다고 했는데, 아래와 같이 각자 저장하는 곳을 따로 가지고 있습니다.

// logger.js
import pino from 'pino';

const traceLogger = pino();
const debugLogger = pino();
const infoLogger = pino();
const warnLogger = pino();
const errorLogger = pino();
const fatalLogger = pino();

export const traceLog = (msg) => traceLogger.trace(msg);

export const debugLog = (msg) => debugLogger.debug(msg);

export const infoLog = (msg) => infoLogger.info(msg);

export const warnLog = (msg) => warnLogger.warn(msg);

export const errorLog = (msg) => errorLogger.error(msg);

export const fatalLog = (msg) => fatalLogger.fatal(msg);

단 아래와 같이 모든 trace와 debug의 경우 에러에서는 로그를 작성하지 않고 있는데 상황에 따라 저장되는 사항이 다릅니다. 참고

export const getServerSideProps = async () => {
    try {
        throw new Error('make error');
        return {
            props: {},
        };
    } catch (error) {
        console.log('에러 로그를 찍습니다');
        traceLog(error);
        debugLog(error);
        infoLog(error);
        warnLog(error);
        errorLog(error);
        fatalLog(error);
        return { props: {} };
    }
};

해당 결과는 이렇게 발생합니다. trace와 debug는 찍히지 않고, 보면 level이 30부터 시작하는 것을 알 수 있습니다. 

 


시간 표현 변경

로그에 문제가 있는데 우선 time이 유닉스 시간으로 되어있다는 점입니다. 해당 표현은 가독성이 나빠 이를 수정해보려고 합니다. 참고

// logger.js
import pino from 'pino';

/** custom time */
const logger = pino({ timestamp: pino.stdTimeFunctions.isoTime });

export const infoTimeLogger = (msg) => logger.info(msg)
// index.js
export const getServerSideProps = async () => {
    try {
        throw new Error('make error');
        return {
            props: {},
        };
    } catch (error) {
        console.log('에러 로그를 찍습니다');
        infoTimeLogger(error);
        return { props: {} };
    }
};

해당 코드를 실행하게 되면 아래와 같이 시간 표현 방식이 바뀝니다.

 


Pino-Pretty 적용

하지만 아직까지 데이터 표현방식의 가독성이 나쁩니다.

이를 위해서 pino-pretty를 사용할 예정인데 주의점이 있습니다.

  1. import 로 pino-pretty 라이브러리를 불러올 시 해당 파일은 nodejs에 맞게 되어있는 라이브러리라서 에러가 발생한다.
  2. transport를 이용하면 되는데 v7부터 생겼으므로 설치된 버전을 확인해야 합니다.
import pino from 'pino';

/** pino pretty */
const log = pino(
    {
        timestamp: pino.stdTimeFunctions.isoTime,
        transport: {
            target: 'pino-pretty',
            options: {
                // colorize: true, // boolean
                // levelFirst: true, // level이 가장 앞으로 오도록 설정
                // ignore: 'pid,hostname', // 해당 형식 무시
                // destination: './log/info.log',
            },
        },
    },
);
export const getServerSideProps = async () => {
    try {
        throw new Error('make error')
        return {
            props: {},
        };
    } catch (error) {
        console.log('에러 로그를 찍습니다');
        prettyLog(error);
        return { props: {} };
    }
};

이렇게 되면 아래와 같이 정리된 방식으로 출력이 됩니다.

위에 transport에 있는 옵션들은 해당 페이지를 참고하면 됩니다.


파일에 로그 남기기

option에서 사용된 변수 중 destination은 파일에 로그를 저장하는데, 다른 방식으로도 호출할 수 있습니다.

단 경로의 경우 /는 c드라이브로 잡히게 되고 ./는 프로젝트의 루트가 잡히게 됩니다.

const logger = pino(pino.destination('./log/info.log'));


로그 일괄 처리하기

아래 함수를 통해서 getServerSideProps를 받아서 로그를 동일하게 처리할 수 있습니다.

// logger.js

export function withGetServerSideProps(getServerSideProps) {
    return async (context) => {
        try {
            return await getServerSideProps(context);
        } catch (error) {
            log.info(error);
            return {
                props: {},
            };
            // throw error;
        }
    };
}
// index.js

export const getServerSideProps = withGetServerSideProps() => {
    // 동작 작성
    return {
        props: {
        },
    };
});

위와 같이 함수를 작성해 줘서 로그 동작을 일괄로 처리 할 수 도있다.

Comments