본문 바로가기

더 나은 엔지니어가 되기 위해/지금은 안쓰는 자바

[부스트코스 웹 프로그래밍] 로깅

부스트코스 웹 프로그래밍 BE 영상을 보며 공부한 것을 간단히 정리한다.

1. 로깅

로깅

로깅은 로그를 남기는 작업으로, 프로그램 실행 동작을 일련의 기록으로 남겨놓는 일이다.
로그는 재현하기 힘든 버그, 성능에 대한 통계 등에 대한 유용한 정보를 제공할 수 있다.
따라서, 로깅 작업은 실제 서비스 개발에 필수적.

로그 관련 라이브러리

  • java.util.logging
    • JDK 1.4 부터 표준 로깅 API
  • Apache Commons logging
    • 아파치 재단에 Commons 라이브러리 중에 로그 출력을 제공하는 라이브러리
    • Spirng 은 기본적으로 이 라이브러리를 사용
  • Log4j
    • 아파치 제단에서 제공하며 가장 많이 사용되는 로깅 라이브러리
  • Logback
    • Log4j의 단점 개선 및 기능을 추가하여 개발한 로깅 라이브러리

SLF4J

다양한 로깅 라이브러리들을 하나의 통일된 방식으로 사용할 수 있는 방법을 제공해주는 라이브러리.
로깅에 대한 추상 레이어를 제공하는 것이고 interface의 모음.

Log level과 Appender

  • Log level 은 보통 다음과 같이 정의되어 있음.
    • trace : debug보다 세분화된 정보
    • debug : 디버깅하는데 유용한 세분화된 정보
    • info : 진행상황 같은 일반 정보
    • warn : 오류는 아니지만 잠재적인 오류 원인이 될 수 있는 경고성 정보
    • error : 요청을 처리하는 중 문제가 발생한 오류 정보
  • Appender 는 로그 출력과 관련한 설정임.
    • ConsoleAppender : 콘솔에 로그를 어떤 포맷으로 출력할지를 설정할 때 사용한다.
    • FileAppender : 파일에 로그를 어떤 포맷으로 출력할지를 설정한다.
    • RollingFileAppender : 로그의 양이 많아지면, 하나의 파일로 관리하기 어려워지는 경우가 생긴다. 이런 문제를 해결하기 위해 하루 단위로 로그를 관리하고자 할 경우 사용된다.

구현

여기서는 logback 을 이용하여 로깅 작업을 해본다.

1) dependency 추가

사용할 라이브러리들은 다음과 같다.

  • logback
    • 로깅 시스템
  • slf4j-api
    • 로깅 라이브러리들의 통일된 인터페이스 제공
  • jcl-over-slf4j
    • 스프링에서 쓰는 로그 형태로 인터페이스 제공

pom.xml 에 필요한 라이브러리를 추가한다.

<dependencies>

  ...

  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>1.7.25</version>
  </dependency>
  <dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
  </dependency>
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.25</version>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>${spring.version}</version>
    <exclusions>
      <exclusion>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
      </exclusion>
    </exclusions>
  </dependency>
</dependencies>

외부 로그 라이브러리를 사용하려면, 스프링에서 기존에 사용 중인 commons-logging 를 off 시켜야 하기 때문에 exclusions 구문이 들어가있다.

2) logback.xml 설정

resources 경로에 logback.xml 을 작성한다.
여기에는 Loglevel 과 Appender 에 대한 설정이 담긴다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="30 seconds">
  <!-- CONOSEL Appender 설정 -->
  <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
    <!-- 출력 포맷 설정 -->
    <encoder>
      <Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
    </encoder>
  </appender>

  <!-- FILE Appender 설정 -->
  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <!-- 로그를 담는 FILE 이름 설정 -->
    <file>/tmp/access.log</file>
    <!-- 로그 파일 관리 관련 설정 -->
    <!-- 여기서는 하루 단위로, 최대 30개의 파일까지 저장 -->
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>/tmp/access-%d{yyyy-MM-dd}.log</fileNamePattern>
      <maxHistory>30</maxHistory>
    </rollingPolicy>

    <encoder>
      <Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
    </encoder>
  </appender>

  <!-- 패키지 내 클래스의 로그 레벨(이상) 설정 -->
  <logger name="org.springframework" level="info"/>
  <logger name="kr.or.connect" level="debug"/>

  <!-- 모든 대상에게 Appender 적용 -->
  <root level="debug">
    <appender-ref ref="CONSOLE"/>
    <appender-ref ref="FILE"/>
  </root>
</configuration>

3) 사용

기존의 인터셉터 위치에다가 System.out.println() 대신 logger 사용

public class LogInterceptor extends HandlerInterceptorAdapter {
  private Logger logger = LoggerFactory.getLogger(this.getClass());

  @Override
  public boolean preHandle(HttpServletRequest request, 
                           HttpServletResponse response, 
                           Object handler) throws Exception {
    logger.debug("{} 를 호출했습니다.", handler.toString());
    // System.out.println(handler.toString() + " 를 호출했습니다.");
    return true;
  }

  @Override
  public void postHandle(HttpServletRequest request, 
                         HttpServletResponse response,
                         Object handler, 
                         ModelAndView modelAndView) throws Exception {
    logger.debug("{} 가 종료되었습니다.", handler.toString());
    logger.debug("{} 을 view로 사용합니다.", modelAndView.getViewName());
    // System.out.println(handler.toString() + " 가 종료되었습니다.");
    // System.out.println(modelAndView.getViewName() + "을 view로 사용합니다.");
  }
}