TIL

Docker 로그와 PM2, Logger, 색상 출력 – 실무에서 겪은 이야기

하얀잔디 2025. 8. 15. 21:14

 

최근에 서버 로그 환경을 손보면서, PM2 + winston logger + Docker 조합을 쓰다 보니 예상 못한 부분에서 꽤 헤맸다.


처음에는 “그냥 로그 찍으면 되지”였는데, 막상 운영 환경에 들어가니까 로그 색상, 포맷, pm2-runtime 옵션, 그리고 Docker 로그 수집 방식까지 다 얽히면서 재미있는(?) 상황이 벌어졌다.

 


1. PM2와 logger의 첫 만남

원래 Node.js에서 console.log()로 로그를 찍고 있었다.
근데 운영 환경에서는 이게 너무 단순하다 보니, 로그 파일 분리나 회전(rotation)도 없고, JSON 포맷도 아니어서 검색이나 필터링이 힘들었다.

그래서 winston + winston-daily-rotate-file 조합으로 logger를 도입했다.

const logger = winston.createLogger({
    level: logLevel,
    format: winston.format.combine(
        winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
        winston.format.errors({ stack: true }),
        winston.format.json()
    ),
    transports: [
        new DailyRotateFile({
            filename: 'app-%DATE%.log',
            datePattern: 'YYYY-MM-DD',
            maxSize: '20m',
            maxFiles: '14d'
        })
    ]
});
 
 

이렇게 하면 파일이 날짜별로 잘 잘리고, JSON으로 남아서 나중에 LokiElasticsearch 같은 로그 시스템에도 잘 들어간다.

 

 


 

2. 색깔 출력이 사라진 이유

 

문제는 여기서부터였다.
개발 환경에서는 예쁘게 색깔 들어간 로그가 잘 보였는데, Docker + PM2로 돌리니까 색깔이 다 빠지고 흑백만 나왔다.

이유는 간단하다.

  • winston에서 색상 포맷을 따로 넣지 않았고
  • PM2가 stdout을 잡아서 Docker 로그로 넘기는 과정에서 ANSI 색상 코드가 빠지는 경우가 있었다.

해결 방법은 winston 포맷에 colorize()를 추가하는 거다.

 
format: winston.format.combine( winston.format.colorize(), winston.format.simple() )
 
 

근데 여기서도 한 가지 고려할 점이 있다.
Promtail, Loki 같은 로그 수집기가 색상 코드를 싫어한다.


색상 ANSI 코드가 들어가면 JSON 파싱이 깨질 수도 있고, 검색이 꼬일 수 있다.
그래서 운영 환경에서는 색상 없이 JSON 로그, 로컬 개발 환경에서는 색상 있는 콘솔 로그로 분리하는 게 깔끔하다.

 

 


3. pm2-runtime과 --raw 옵션

 

다음 문제는 PM2 runtime이었다.
Dockerfile에서 기존에는 이렇게 쓰고 있었다.

 
CMD ["pm2", "start", "ecosystem.config.js", "--env", "dev"]
 
 

이걸 pm2-runtime으로 바꾸면,

 
CMD ["pm2-runtime", "ecosystem.config.js", "--env", "dev"]
 
 

Docker에서 종료 시그널 처리나 재시작 관리가 더 안전해진다.

근데 여기서 --raw 옵션을 붙이면

 
CMD ["pm2-runtime", "ecosystem.config.js", "--env", "dev", "--raw"]
 

 

 


4. Docker 로그와 out_file, merge_logs

또 하나 헷갈렸던 건 pm2 설정의 이 부분이었다.

 
out_file: '/dev/stdout', error_file: '/dev/stderr', merge_logs: true

이걸 넣으면 PM2가 찍는 로그가 전부 Docker의 stdout/stderr로 바로 전달된다.
덕분에 docker logs로 다 볼 수 있다.
안 그러면 PM2 내부 로그 파일로만 쌓이고 Docker 쪽에서는 안 보이는 경우가 생긴다.

 

 


5. 결론 – 환경별 로그 전략

내가 겪어본 바로는 이렇게 정리된다.

  • 개발 환경
    • colorize된 콘솔 로그
    • pm2 --raw 옵션 ON
    • 파일 로그는 최소화
  • 운영 환경
    • JSON 포맷 (color X)
    • pm2-runtime + prefix 유지
    • stdout/stderr로 로그 출력 → Loki/Promtail 수집
    • winston-daily-rotate-file로 백업 로그도 남김

6. 덤 – 색상 로깅과 Loki

색상 코드를 넣으면 사람 눈에는 좋지만, Loki에서는 이렇게 나온다.

 
\u001b[32minfo\u001b[39m: 서버가 시작되었습니다.
 

그래서 운영 Loki에 색상 코드가 들어가면 필터링할 때 좀 빡세진다.
차라리 색상은 개발자 로컬에서만 즐기는 걸로 하고, 운영은 깔끔한 JSON이 낫다.

 

 


 

 

이렇게 정리하고 나니까, 처음에 왜 색상이 안 나오고 로그가 두 배로 찍히고 그랬는지 이해가 됐다.
PM2, logger, Docker 로그 수집기는 각각 “자기 방식”이 있다 보니, 셋을 잘 맞춰주는 게 포인트다.