Jet-Logger + Morgan

하람·2024년 9월 26일

Express 서버

목록 보기
4/7

pbl fullstack 프로젝트를 시작하면서 백엔드 초기화를 담당했다.

Custom Logging

jet-logger와 morgan 라이브러리로 로깅을 커스텀했다.

advanced logging 이라는 키워드로 검색해본 결과, winston + morgan 조합이 가장 많이 나왔는데, winston의 장점은 다음과 같다.

Winston 사용을 고려할 때의 장점
1. 강력한 트랜스포트 시스템: 로깅 대상이 다양하고, 로그를 여러 장소로 동시에 전송할 수 있는 기능은 대규모 시스템에서 매우 유용합니다.
2. 로그 포맷 커스터마이징: JSON 형식의 로그를 사용하거나, 로그 내용을 특정 포맷으로 변경하여 분석 툴과 쉽게 통합할 수 있습니다.
3. 로그 로테이션 및 관리: 로그 파일의 크기가 커질 경우, 자동으로 분할하거나, 날짜별로 파일을 나누는 로테이션 기능을 제공하므로 장기적으로 로그를 관리할 수 있습니다.
4. 외부 서비스 통합: 오류 추적 및 모니터링을 위한 Sentry, 성능 모니터링을 위한 Datadog, 로그 저장을 위한 Elasticsearch와 같은 서비스와 쉽게 연동할 수 있어 DevOps 및 운영 환경에서 유리합니다.

확장성이 크고 제공하는 기능이 많은 만큼 무겁다고했다.
일단 로그 관리를 많이 해보지도 않아서 얼마나 중요한지, 뭘 어떻게 더 커스텀해야 잘 쓸 수 있을지도 모르겠고
일단 풀스택 프로젝트를 빨리 시작해야하기 때문에, express-generator-typescript 라이브러리에서 기본으로 제시하는 가벼운 jet-logger에다가 morgan을 합쳐서 사용하기로 했다.

// /src/middleware/logger.ts
import morgan, { StreamOptions } from "morgan";
import logger from "jet-logger";
import { Request } from "express";

// 1. Morgan이 로그를 Jet-Logger로 보내도록 설정
const stream: StreamOptions = {
  write: (message: string) => logger.info(message.trim()),
};

// 2. environment === prod ? 'combined' : 'dev'
const morganFormat =
  process.env.NODE_ENV === "production"
    ? `:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent" \nquery :query \nbody :body`
    : `:method :url :status :response-time ms - :res[content-length]  \nquery :query \nbody :body`;

morgan.token("body", (req: Request) => {
  return JSON.stringify(req.body); // 요청 본문 데이터를 JSON 문자열로 변환
});

morgan.token("query", (req: Request) => {
  return JSON.stringify(req.query); // 요청 쿼리 데이터를 JSON 문자열로 변환
});

const morganMW = morgan(morganFormat, { stream });

export const logInfo = (message: string) => logger.info(message);
export const logError = (message: string) => logger.err(message);
export const logWarn = (message: string) => logger.warn(message);

export { logger, morganMW };

morgan vs jet-logger

morgan은 주로 http 요청과 응답에 대한 로그를 남기는데 사용되고, jet-logger는 애플리케이션 내의 상황을 모니터링하기에 적합하다.

  • Morgan은 주로 HTTP 요청과 응답 로그를 처리하는 미들웨어인데, Morgan 자체로는 로그를 기록할 때 다양한 로그 목적지(파일, 외부 서비스 등)에 보내거나 로그 레벨을 구분할 수 없다.
  • Jet-Logger는 단순한 info, warn, error 등의 로그 레벨을 제공하며, 파일 로깅, 콘솔 출력을 쉽게 설정할 수 있다.

1. stream 합치기

Morgan은 로그를 생성하고, 해당 로그를 콘솔에 출력하거나 또는 파일에 직접 기록하는 방식으로 사용된다. 하지만 이러면 콘솔 로그와 파일 로그가 각각 따로 관리될 수 있고, 애플리케이션의 다른 로깅 시스템과 일관성을 유지하지 못할 수 있다.

이를 해결하기 위해 Morgan의 stream 옵션을 사용하여 Morgan이 생성한 로그를 Jet-Logger로 전달하고, Jet-Logger가 이를 처리하는 방식으로 구현한다.

2. custom format

morgan이 기본적으로 제시하는 로그 방법은 combined, common, dev 등이 있다. combined 에서 몇가지 빠진게 common이고, dev는 아주 간결한 표현이다. 자세한건 공식문서를 확인하고, 내가 하고싶었던 건 요청시 body 값이나 query 값을 같이 로깅하고 싶었다.

그래서 직접 req에서 필요한 정보를 반환하는 토큰을 만들었고 format string에서 원하는 자리에 출력하도록 설정했다.

3. middleware 설정

// app.ts
app.use(morganMW); // HTTP 요청 로깅

이제 직접 커스텀한 morganMW를 사용한다.

번외) 컬러 수정

// ANSI 색상 코드 적용 함수
const colorizeStatus = (status: number) => {
  if (status >= 500) {
    return `\x1b[31m${status}\x1b[0m`; // 빨간색 (500번대 에러)
  } else if (status >= 400) {
    return `\x1b[33m${status}\x1b[0m`; // 노란색 (400번대 에러)
  } else if (status >= 300) {
    return `\x1b[36m${status}\x1b[0m`; // 청록색 (300번대 리다이렉션)
  } else if (status >= 200) {
    return `\x1b[32m${status}\x1b[0m`; // 초록색 (200번대 성공)
  }
  return `\x1b[37m${status}\x1b[0m`; // 흰색 (기타)
};

morgan.token("status", (req, res) => {
  const status = res.statusCode;
  return colorizeStatus(status); // 상태 코드에 ANSI 색상 코드 적용
});

응답 상태 코드에 따라 콘솔에 찍히는 컬러를 수정하면 디버깅 시 눈에 잘 띄고 개발이 편해질 것 같았다.
chalk라는 패키지를 많이 쓰던데 굳이 패키지까지 추가하고싶지 않아서 그냥 GPT에게 코드를 짜달라고했다.



굳!

TIL

  • morgan과 jet-logger의 용도가 http 요청 vs 앱 모니터링 으로 살짝 다르다는걸 알게 되었다.

  • 그리고 둘을 합치지 않으면 로그 데이터가 저장될 때 일관성이 깨질 수 있다는 것을 알게 되었다.

비고

일단 급하니까 jet-logger + morgan 조합으로 끝냈지만, 기회가 된다면 winston을 사용해서 외부 서비스(Sentry, Datadog, Elasticsearch)로 보내고, 애플리케이션 상태를 실시간으로 모니터링할 수 있게 업그레이드하고싶다.

언제쯤 로그 데이터까지 관리하는 멋진 프로젝트를 해볼 수 있을까?

profile
강하고 담대하라 두려워하지 말라

0개의 댓글