본문 바로가기
JAVA

SLF4J 이용하여 로그 남기는 방법 (with Logback)

by enai 2019. 9. 12.

로그에 대한 내용은 아래 글에 있다.

로깅에 대하여

 

 

로그를 위한 라이브러리는 다양한 종류가 있다.

이 다양한 라이브러리를 같은 방법으로 사용할 수 있도록 도와주는 SLF4J에 대해 알아보도록 하자.

 

 

 

 

 

1. SLF4J란?

 

SLF4J는 로깅 Facade(퍼사드)이다. 로깅에 대한 추상 레이어를 제공하는 interface의 모음이다.

여러 로깅 라이브러리를 하나의 통일된 방식으로 사용할 수 있는 방법을 제공한다.

 

 

아래 그림을 보면, 초록색 박스의 application들은 하늘색의 SLF4J API를 사용하고 있다.

이렇게 application은 SLF4J를 이용하여, 로깅 라이브러리가 어떤 것이든 같은 방법으로 로그를 남길 수 있게 되는 것이다.

나중에 더 좋은 라이브러리가 생겨 교체하더라도, application의 코드를 변경할 필요가 없다는 장점이 있다.

 

 

 

 

 

2. SLF4J를 Spring에서 사용하는 방법

 

여기선 로깅 라이브러리를 Logback으로 사용할 것이다.

 

 

 

1) 라이브러리 추가하기

 

SLF4J와 Logback을 사용하기 위해 각 라이브러리 의존성(slf4j-api, logback-classic)을 추가해준다.

pom.xml 파일의 다음 부분을

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>${spring.version}</version>
</dependency>

 

아래 소스로 수정한다.

<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>

 

참고로 logback-classic 라이브러리는 slf4j-api 1.7.25에 의존성을 가지고 있기 때문에,

logback-classic 라이브러리를 추가했다면 slf4j 라이브러리를 추가하지 않아도 자동으로 설치가 된다.

 

Spring은 기본적으로 아파치 재단의 commons-logging을 이용해 로그를 남긴다.

Logback 라이브러리로 로그를 남기려면 commons-logging이 사용되지 않도록 설정을 해야 한다.

그런데 Spring은 내부적으로 commons-logging을 찾아 로깅을 하려고 해서, commons-logging을 사용하지 못하도록 설정하면 ClassNotFoundException이 발생한다.

 

이 문제를 해결하기 위해, SLF4J에서 제공하는 jcl-over-slf4j 라이브러리를 추가한 것이다.

jcl-over-slf4j는 commons-logging과 동일한 구조를 가진 라이브러리이며, 내부적으로 SLF4J를 사용하여 로그를 남긴다.

이런 식으로 Spring은 commons-logging 대신에 jcl-over-slf4j를 이용하여 로그를 남기게 된다.

 

 

 

2) logback 설정하기

 

SLF4J는 로그를 남기기 위한 공통 인터페이스이다.

SLF4J로 logback 라이브러리를 이용하여 로그를 남기는 것이므로, 실제 로그를 남기는 것은 logback 라이브러리다.

그래서 logback 설정을 해줘야 한다.

 

logback 설정 파일은 logback.xml 파일이다.

resource 폴더에 logback.xml 파일을 작성한다.

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

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>/tmp/access.log</file>
        <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"/>

    <root level="debug">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>
</configuration>

 

이 설정 파일의 루트 요소는 configuration이며, 해당 루트 요소 안에는 appender, logger, root 등에 대한 설정을 하게 된다.

 

먼저 Appender라고 하는 것에 대해 알아보자.

Appender는 어디에 어떤 포맷으로 로그를 남길 것인지 정할 수 있는 방법을 제공한다.

대표적으로 ConsoleAppender, FileAppender, RollingFileAppender 등이 있다.

이름에서 알 수 있듯이, ConsoleAppender는 콘솔에 로그를 어떤 포맷으로 출력할지 설정할 때 사용하는 것이고,

FileAppender는 파일에 로그를 어떤 포맷으로 출력할지 설정할 때 사용한다.

RollingFileAppender는 로그 양이 많아지면 하나의 파일로 관리하기가 어려워진다. 이런 문제를 방지하기 위해 하루 단위로 로그 파일을 백업하면서 로그를 남기고자 할 때 사용할 수 있다.

 

ConsoleAppender는 다음과 같은 방법으로 설정한다.

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
  <encoder>
    <Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
  </encoder>
</appender>

appender 요소 안에 name 속성 값은 보통 CONSOLE이라 정하고, class는 ConsoleAppender를 지정한다.

encoder 안의 Pattern이란 요소엔 로그를 출력할 형태의 포맷을 지정한다.

%d{HH:mm}은 로그가 출력되는 시간이 출력된다. 중괄호{ } 안은 이 시간의 포맷이다.

%-5level은 로그 레벨을 5의 고정폭 값으로 출력하라는 것을 의미한다.

%logger는 logger의 이름을 축약해서 출력한다. 중괄호{ } 안에는 length이다. 최대 자릿수를 의미한다.

%msg는 %message의 alias로, 사용자가 출력한 메시지가 출력된다.

%n은 줄바꿈이다.

 

RollingFileAppender 설정하는 방법은 다음과 같다.

<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>access.log</file>
  <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
    <fileNamePattern>access-%d{yyyy-MM-dd}.log</fileNamePattern>
    <maxHistory>30</maxHistory>
  </rollingPolicy>
  <encoder>
    <Pattern>%d{HH:mm} %-5level %logger{36} - %msg%n</Pattern>
  </encoder>
</appender>

appender의 name은 FILE, class는 RollingFileAppender로 설정한다.

기본적으로 기록되는 파일명은 file 요소에 입력되어 있는 access.log이다.

디렉토리 경로가 없기 때문에, 이클립스에서 실행한다면 이클립스 설치 경로에 로그 파일이 생성된다.

rollingPolicy 엘리먼트 안엔 파일이 언제 백업될지 결정짓는 내용들이 있다.

위 소스의 내용에 따르면, access-로 시작하면서 년, 월, 일에 대한 정보를 가지는 로그 파일(fileNamePattern 요소 안의 내용)이 최대 30개(maxHistory 요소의 내용) 생성된다.

최대 30개가 다 차면, 이전 로그 파일을 삭제하고 생성한다.

 

encoder 안에 있는 Pattern 요소 안에는 로그를 출력할 형태를 지정할 수 있다.

 

 

로그는 보통 다섯 가지의 레벨로 나뉜다.

Log Level (아래쪽으로 갈수록 심각한 오류를 의미한다)

  1. trace
  2. debug
  3. info
  4. warn
  5. error

프로젝트를 할 때, 어떤 오류를 어떤 방식으로 남길지 결정하고 로그를 남겨야 한다.

개발할 땐 조금 더 자세한 로그를 보고 싶을 수도 있고, 실제 운영 중일 땐 그 정도까지 필요는 없을 수 있기 때문이다.

각 레벨들을 필요에 따라 조정해서, 원하는 레벨로 로그를 남기게 설정할 수도 있다.

 

logback 설정에서 logger라는 엘리먼트는 어떤 패키지 이하의 클래스에서 어떤 레벨 이상의 로그를 출력할지 결정할 때 사용한다.

위 예시 소스 중 logger 엘리먼트를 다시 한번 보면 다음과 같다.

    <logger name="org.springframework" level="info"/>
    <logger name="kr.or.connect" level="debug"/>

이는 org.springframework로 시작하는 패키지에 속한 클래스에서 출력하는 로그는 info 이상의 레벨에 해당하는 것으로 출력하라는 의미이다.

(info, warn, error와 관련된 로그가 출력될 것이다)

kr.or.connect로 시작하는 패키지의 클래스에서는 debug 이상의 레벨에 해당하는 로그를 출력하라는 의미이다.

(debug, info, warn, error와 관련된 로그가 출력될 것이다)

 

만약

    <root level="debug">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="FILE"/>
    </root>

이렇게 root 태그에 설정을 한다면, 모든 대상에 CONSOLE, FILE Appender를 적용하라는 의미이다.

이렇게 하면 콘솔과 파일로 두 번 로그를 출력하게 된다.

 

 

이제 로그 설정은 마쳤고, 로그를 남기기 원하는 클래스에서 SLF4J 라이브러리를 이용하여 로그를 남기면 된다.

 

 

 

3) Logger 객체 선언

 

로그를 남기려는 클래스에 로거 객체를 필드로 선언한다.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

.......

private Logger logger = LoggerFactory.getLogger(this.getClass());

org.slf4j 패키지의 LoggerFactory를 이용하여 Logger 객체를 생성하는 것이다.

 

이제 여기서

logger.trace("{} {} 출력", "값1", "값2");
logger.debug("{} {} 출력", "값1", "값2");
logger.info("{} {} 출력", "값1", "값2");
logger.warn("{} {} 출력", "값1", "값2");
logger.error("{} {} 출력", "값1", "값2");

이렇게 logger가 가지고 있는 다섯 가지 메서드 trace, debug, info, warn, error를 이용하여 로그를 출력하면 된다.

 

String을 +연산자로 연결하면 속도가 굉장히 느려진다.

SLF4J의 메서드는 +연산자를 이용하여 메시지를 구성할 필요가 없다.

이 SLF4J의 로그 출력 메서드는 첫 번째 인자로는 출력할 로그 문자열을 받고,

두 번째부터는 가변 인자를 받는다.

가변 인자란 0개 이상의 인자를 전달할 수 있다는 것을 의미한다.

 

또, 첫 번째 인자의 로그 문자열에 변수를 출력하고 싶다면 그 위치에 중괄호{}를 넣어주면 된다.

변수 값은 중괄호를 열고 닫은 수만큼 로그의 문자열 뒤의 가변 인자로 넘겨주면 된다.

 

 

 

컨트롤러 메서드가 실행될 때마다 로그가 출력되게 하기 위해 LogInterceptor를 생성하고, 거기에 Logger 객체를 사용해보도록 하겠다.

(인터셉터란 → 인터셉터(Interceptor) - Cotroller 공통 로직 처리하기 )

 

package kr.or.connect.guestbook.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class LogInterceptor extends HandlerInterceptorAdapter {
	private Logger logger = LoggerFactory.getLogger(this.getClass());
	
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
		//System.out.println(handler.toString() + " 가 종료되었습니다.  " + modelAndView.getViewName() + "을 view로 사용합니다.");
		logger.debug("{} 가종료되었습니다. {} 를 view로 사용합니다.", handler.toString(), modelAndView.getViewName());
	}

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

 

postHandle은 핸들러가 요청 작업을 마치고 응답을 보낼 때 동작하고,

preHandle은 핸들러를 호출해 요청 작업을 보낼 때 동작한다.

 

그럴 때마다 어떤 클래스의 어떤 메서드(어떤 핸들러)가 호출되고, 종료되고, 어떤 화면을 가져오는지를 로그로 출력해보려고 한다.

기존에는 System.out.println을 통해 출력했지만, SLF4J를 배웠으므로 이를 이용해 위와 같이 고쳐보았다.

 

 

해당 프로젝트를 실행시키면 위 로거에 의해 출력되는 로그는 다음과 같다.


19:36 DEBUG k.o.c.g.interceptor.LogInterceptor - public java.lang.String kr.or.connect.guestbook.controller.GuestbookController.list(int,org.springframework.ui.ModelMap,java.lang.String,javax.servlet.http.HttpServletResponse,kr.or.connect.guestbook.argumentresolver.HeaderInfo) 를 호출했습니다.

19:36 DEBUG k.o.c.g.interceptor.LogInterceptor - public java.lang.String kr.or.connect.guestbook.controller.GuestbookController.list(int,org.springframework.ui.ModelMap,java.lang.String,javax.servlet.http.HttpServletResponse,kr.or.connect.guestbook.argumentresolver.HeaderInfo) 가종료되었습니다. list 를 view로 사용합니다.


 

 

loginform으로 이동하면 다음과 같은 로그가 출력된다.


19:38 DEBUG k.o.c.g.interceptor.LogInterceptor - public java.lang.String kr.or.connect.guestbook.controller.GuestbookAdminController.loginform() 를 호출했습니다.

19:38 DEBUG k.o.c.g.interceptor.LogInterceptor - public java.lang.String kr.or.connect.guestbook.controller.GuestbookAdminController.loginform() 가종료되었습니다. loginform 를 view로 사용합니다.


 

 

 

 

 

 

 

출처)

edwith 부스트코스 웹 프로그래밍

- slf4j 설정하기

 

댓글0