본문 바로가기

이노베이션캠프/TIL

winston 라이브러리를 이용해 로그 미들웨어 구현



로그(log) 미들웨어

로그미들웨어란?

클라이언트의 모든 요청 사항을 기록하여 서버의 상태를 모니터링하기 위한 미들웨어입니다.

 

  • 클라이언트의 요청을 기록하여 어플케이션을 모니터링하고 문제가 발새알 때 빠르게 진달 할 수 있습니다.
  • 사용자의 행동을 분석하는 등 데이터 분석 작업에도 활용할 수 있습니다.

규모가 큰 프로젝트에서 모든 로그를 일일이 확인하는 것은 불가능

 

이런 경우를 대비해 로그 기능을 지원하는 morgan, winston과 같은 라이브러리를 사용하거나 AWS CloudWatch, Datadog와 같은 외부 모니터링 솔루션 서비스를 이용해 로그를 수집하거나 관리할 수 있습니다.

 

winston 라이버러리로 로그 미들웨어 구현

먼저 log.middleware.js을 파일을 만들고 winston을 import 해줍니다.

log.middleware.js

import winston from 'winston';

 

그리고 다음과 같이 가져올 로그를 설정 해줍니다.

const logger = winston.createLogger({
  level: 'info', // 로그 레벨을 'info'로 설정합니다. 로그 제목입니다.
  format: winston.format.json(), // 로그 포맷을 JSON 형식으로 설정합니다.
  transports: [
    new winston.transports.Console(), // 로그를 콘솔에 출력합니다.
  ],
});

 

다음에 외부에서 사요알 함수를 다음과 같이 만들어줍니다.

export default function (req, res, next) {
  // 클라이언트의 요청이 시작된 시간을 기록합니다.
  const start = new Date().getTime();

  // 응답이 완료되면 로그를 기록합니다.
  res.on('finish', () => {
    const duration = new Date().getTime() - start; //걸린 시간
    logger.info(
      `Method: ${req.method}, URL: ${req.url}, Status: ${res.statusCode}, Duration: ${duration}ms`,
    );
  });

  next();
}

 

마지막으로 사용할 app.js 파일에 로그 미들웨어를 express에 등록 해줍니다.

// app.js

import LogMiddleware from './middlewares/log.middleware.js';

app.use(LogMiddleware);

 

로그 레벨(level)은 로그의 중요도를 나타냅니다. 지금 사요안 것은 클라이언트의 요청 사항을 기록하기 위해 "info" 레벨을 사용하지만, "error", "warn", "debug" 등 다양한 로그 레벨이 있으며, 특정 상황에 따라 출력하는 레벨을 다르게 구현할 수 있습니다.

 

로그 미들웨어는 클라이언트의 요청이 발생하였을 때, 가장 먼저 실행되어야 하는 미들웨어 입니다. 그렇기 때문에, app.uuse 를 이용한 전역 미들웨어 중에서 가장 최상단에 위치시켜 줍니다.

 


에러 처리 미들웨어

에러 처리 미들웨어란?

일반적으로 Express.js에서 컨트롤러(라우터 핸들러) 안에서 에러가 발생하면, 많은 초보자들은 아래처럼 if 문을 이용해 직접 res.status()를 반환하는 방식으로 처리하곤 합니다

app.get('/user/:id', (req, res) => {
  const user = getUser(req.params.id);
  if (!user) {
    return res.status(404).json({ message: '사용자를 찾을 수 없습니다.' });
  }
  res.json(user);
});

 

이 방식은 간단하지만, 모든 라우터마다 비슷한 에러 처리 로직을 반복해야 한다는 단점이 있습니다. 규모가 커질수록 코드 중복이 많아지고 유지보수가 힘들어지죠

 

Express.js는 에러를 한 곳에서 처리할 수 있도록 에러 처리 전용 미들웨어를 제공합니다. 이 미들웨어는 function (err, req, res, next) 시그니처를 가지며, next() 로 전달된 에러나 핸들러에서 발생한 에러를 통합적으로 처리할 수 있습니다.

 

👉 핵심 포인트:

  • 모든 라우터에서 발생한 에러를 한 곳에서 모아 처리 가능
  • if문으로 일일이 return하지 않고, throw 또는 next(err) 로 에러를 전달하면 됨
  • 코드 중복 감소, 유지보수성 향상
// 라우터 핸들러
app.get('/user/:id', (req, res, next) => {
  try {
    const user = getUser(req.params.id);
    if (!user) {
      // 직접 res를 보내지 않고, 에러를 던지거나 next()로 전달
      const error = new Error('사용자를 찾을 수 없습니다.');
      error.status = 404;
      throw error;
    }
    res.json(user);
  } catch (err) {
    next(err); // 에러 미들웨어로 전달
  }
});

// 에러 처리 미들웨어 (맨 마지막에 선언해야 함)
app.use((err, req, res, next) => {
  console.error(err.stack); // 로그 남기기
  res.status(err.status || 500).json({
    message: err.message || '서버 에러가 발생했습니다.',
  });
});

 

에러 미들웨어를 사용했을 때의 장점

중앙 집중화된 관리
모든 에러가 한곳으로 모여들기 때문에, 에러 처리 로직을 한 번만 작성하면 된다.

코드 중복 제거
각 라우터에서 if (!user) return res.status(404)… 같은 코드 반복이 사라진다.

유지보수 용이
에러 응답 형식을 변경하고 싶을 때, 에러 미들웨어 한 곳만 수정하면 전체 API에 반영된다.

 

 

📌 정리

  • 기존에는 라우터 안에서 if 문으로 직접 응답을 보내는 방식 → 각 라우터마다 중복된 코드
  • 에러 미들웨어를 사용하면, 에러를 next(err)로 넘겨서 한 곳에서 통합 처리 가능
  • 구현 시 맨 마지막에 에러 미들웨어를 등록해야 한다.