pbl fullstack 프로젝트를 시작하면서 백엔드 초기화를 담당했다.
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은 주로 http 요청과 응답에 대한 로그를 남기는데 사용되고, jet-logger는 애플리케이션 내의 상황을 모니터링하기에 적합하다.
Morgan은 로그를 생성하고, 해당 로그를 콘솔에 출력하거나 또는 파일에 직접 기록하는 방식으로 사용된다. 하지만 이러면 콘솔 로그와 파일 로그가 각각 따로 관리될 수 있고, 애플리케이션의 다른 로깅 시스템과 일관성을 유지하지 못할 수 있다.
이를 해결하기 위해 Morgan의 stream 옵션을 사용하여 Morgan이 생성한 로그를 Jet-Logger로 전달하고, Jet-Logger가 이를 처리하는 방식으로 구현한다.
morgan이 기본적으로 제시하는 로그 방법은 combined, common, dev 등이 있다. combined 에서 몇가지 빠진게 common이고, dev는 아주 간결한 표현이다. 자세한건 공식문서를 확인하고, 내가 하고싶었던 건 요청시 body 값이나 query 값을 같이 로깅하고 싶었다.
그래서 직접 req에서 필요한 정보를 반환하는 토큰을 만들었고 format string에서 원하는 자리에 출력하도록 설정했다.
// 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에게 코드를 짜달라고했다.



굳!
morgan과 jet-logger의 용도가 http 요청 vs 앱 모니터링 으로 살짝 다르다는걸 알게 되었다.
그리고 둘을 합치지 않으면 로그 데이터가 저장될 때 일관성이 깨질 수 있다는 것을 알게 되었다.
일단 급하니까 jet-logger + morgan 조합으로 끝냈지만, 기회가 된다면 winston을 사용해서 외부 서비스(Sentry, Datadog, Elasticsearch)로 보내고, 애플리케이션 상태를 실시간으로 모니터링할 수 있게 업그레이드하고싶다.
언제쯤 로그 데이터까지 관리하는 멋진 프로젝트를 해볼 수 있을까?