스프링부트 핵심 가이드 책을 읽고 작성한 내용입니다.
로깅이란?
로깅(logging)은 애플리케이션이 실행하는 동안 발생하는 이벤트, 트랜잭션, 오류 등의 정보를 시간순으로 기록하는 것을 의미한다.
로깅은 개발영역 중 '비기능 요구사항'에 속하지만 디버깅 또는 개발 이후 발생한 이슈를 해결할 때 원인을 분석하는 데 꼭 필요한 요소이다.
이런 로깅을 효과적으로 수행하려면 로그 메서지의 포맷, 로그 레벌, 로그 저장 위치, 보존 기간 등을 적절히 결정하고 관리해야한다. 많은 프로그래밍 언어나 프레임워크에서는 로깅을 지원하는 라이브러리나 도구를 제공하며, 자바 진영에서 가장 많이 사용되는 것이 오늘 정리할 'Logback'이다.
Logback 이란?
Logback이란 log4j 이후에 출시된 로깅 프레임워크로 SLF4J를 기반으로 구현되었으며, 로깅 작업을 쉽고 효율적으로 수행하도록 도와준다.
또한, 스프링부트 'spring-boot-starter-web' 라이브러리 내부에 내장되어 별도의 의존성 추가 없이 사용이 가능하다.
Logback의 주요 특징은 다음과 같다.
- 크게 5개의 로그 레벨(TRACE, DEBUG, INFO, WARN, ERROR)을 설정할 수 있다.
- ERROR : 로직 수행 중에 시스템에 심각한 문제가 발생해서 애플리케이션의 작동이 불가능한 경우를 의미한다.
- WARN : 시스템 에러의 원인이 될 수 있는 경고 레벨을 의미한다.
- INFO : 애플리케이션의 상태 변경과 같은 정보 전달을 위해 사용한다.
- DEBUG : 애플리케이션의 디버깅을 위한 메시지를 표시하는 레벨을 의미한다.
- TRACE : DEBUG 레벨보다 더 상세한 메세지를 표현하기 위한 레벨을 의미한다.
- 실제 운영 환경과 개발 환경에서 각각 다른 출력 레벨을 설정해서 로그를 확인할 수 있다.
- Logback의 설정 파일을 일정 시간마다 스캔하여 애플리케이션을 재가동하지 않아도 설정을 변경할 수 있다. (자동 리로드)
- 별도의 프로그램 지원 없이도 자체적으로 로그 파일을 압축할 수 있다.
- 저장된 로그 파일에 대한 보관 기간 등을 설정해서 관리할 수 있다.
각 로그 레벨에 대해서는 아파치 Log4j의 레벨 공식문서를 보면 도움이 됩니다.
https://logging.apache.org/log4j/1.2/apidocs/org/apache/log4j/Level.html
Logback 설정
1. 의존성 확인
'spring-boot-starter-web'을 사용한다면 이미 해당 의존성은 포함되어 있다. 그렇지 않은 경우 해당 의존성을 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
2. Logback 구성 파일 추가
'src/main/resources' 디렉터리에 'logback-spring.xml' 파일을 생성한다. 일반적인 자바나 스프링 프로젝트에서는 'logback.xml' 파일 이름으로 참조하지만 스프링 부트에서는 'logback-spring.xml' 파일을 참조한다.
3. logback-spring.xml 파일 작성
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATH" value="./logs"/>
<!-- Appenders -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
</encoder>
</appender>
<appender name="INFO_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<file>${LOG_PATH}/info.log</file>
<append>true</append>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/info_${type}.%d{yyyy-MM-dd}.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
</encoder>
</appender>
<!-- TRACE > DEBUG > INFO > WARN > ERROR > OFF -->
<!-- Root Logger -->
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="INFO_LOG"/>
</root>
</configuration>
3번 줄 : Property 영역으로 "LOG_PATH"라는 이름으로 속성을 정의하며, 값으로 "./logs"를 지정한다. 이는 로그파일들이 저장될 디렉터리를 의미한다.
6~13, 15~28번 줄 : Appender 영역으로 6~13줄은 console 출력을 위한 Appender를 정의한다.
'<filter class="..."> ... </filter>' 는 로그 레벨 필터를 설정하며, 코드상에는 INFO 레벨 이상만 출력하도록 설정되어 있다.
'<encoder> ... </encoder>'는 로그 메시지의 포맷을 설정한다.
15~28줄은 파일 출력을 위한 Appender를 정의한다.
'<file>${LOG_PATH}/info.log</file>' 기본 로그 파일의 경로를 지정한다.
'<append>true</append>' 는 기존 로그에 이어서 기록하도록 설정한다.
'<rollingPolicy class="..."> ... </rollingPolicy>' 로그 파일의 롤링 정책을 설정하며, 여기서는 시간 기반 롤링 정책을 사용한다. 로그 파일은 매일 새로 생성되며, 30일 한다. (위 코드에서는)
'<encoder> ... </encoder>'는 위와 동일하게 로그 메시지의 포맷을 설정한다.
32~35 번 줄: Root 영역이며, Level를 지정할 수 있다. 로그 레벌 순서는 주석을 참고한다. 루트 로거는 위에 지정한 두 Appender를 모두 사용하도록 설정되어 있다.
정리하자면, INFO 레벨 이상의 로그는 콘솔과 './logs/info.log' 파일에 기록되게 된다.
Appender 영역과 Root 영역
각 영역 가운데 Logback 설정에서 가장 중요한 Appender 영역과 Root 영역에 대해 알아보자.
Appender 영역
Appender 영역은 로그의 형태를 설정하고 어떤 방법으로 출력할지를 설정하는 곳이다. Appender 자체는 하나의 인터페이스를 의미하며, 하위에 여러 구현체가 존재한다. Appender의 상속 구조는 아래 이미지와 같다.
Appender의 대표적인 구현체는 다음과 같다.
- ConsoleAppender : 로그 메시지를 콘솔에 출력
- FileAppender : 로그 메세지를 파일에 출력
- RollingFileAppender : 로그 파일의 크기가 특정 크기에 도달하면 새 파일을 생성하는 방식으로 로그 파일을 관리
- SMTPAppender : 메일로 로그를 전송
- DBAppender : 로그 메세지를 데이터베이스에 저장
로그가 어떤 방식으로 저장할지 지정하는 방법을 살펴보자. 위에서 작성한 설정 코드를 참고하여 설명해 보면 appender 요소의 class 속성에 각 구현체를 정의하는 것을 알 수 있다.
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
[...]
</appender>
<appender name="INFO_LOG" class="ch.qos.logback.core.rolling.RollingFileAppender">
[...]
</appender>
그리고 하단에 filter 요소로 각 Appender가 어떤 레벨로 로그를 기록하는지 지정한다.
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<encoder>
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
</encoder>
</appender>
다음으로 encoder 요소를 보면 로그의 표현 형식을 <pattern>으로 정의하는 것을 알 수 있다. 사용 가능한 패턴은 몇 가지 정해져 있으며, 대표적인 패턴은 다음과 같다.
패턴 | 의미 |
%logger{length} | 패키지의 포함 로거의 이름 나타내며, 축약할 수 있는 자리수 설정을 제공한다. {length}는 최대 자리 수 |
%-5level | 로그 레벨, -5는 출력 고정폭의 값(5글자), 로깅레벨이 Info일 경우 빈칸 하나 추가 |
%msg(=%message) | 로그 메세지 |
%d | 로그 기록 시간 |
%p | 로깅 레벨 |
%F | 로깅이 발생한 애플리케이션 파일명 |
%M | 로깅이 발생한 메서드 이름 |
%I | 로깅이 발생한 호출지의 정보 |
%thread | 현재 스래드 명 |
%t | 로깅이 발생한 스레드 명 |
%c | 로깅이 발생한 카테고리 |
%C | 로깅이 발생한 클래스명 |
%m | 로그 메세지 |
%n | 줄바꿈 |
%r | 애플리케이션 실행 후 로깅이 발생한 시점까지의 시간 |
%L | 로길이 발생한 호출 지점의 라인 수 |
Root 영역
설정파일에서 정의한 Appender를 활용하려면 Root 영역에서 Appender를 참조해서 로깅레벨을 설정해야 한다. 만약 특정 패키지에 다른 로깅 레벨을 설정하고 싶으면 root 대신 logger를 사용해야 한다.
<root level="INFO">
<appender-ref ref="console"/>
<appender-ref ref="INFO_LOG"/>
</root>
또는
<logger name="com.springboot.api.controller" level="DEBUG" additivity="false">
<appender-ref ref="console"/>
<appender-ref ref="INFO_LOG"/>
</logger>
logger를 사용할 때는 name 속성에는 패키지 단위로 범위를 지정하고, level 속성으로 그 로그 레벨을 지정한다. additivity 속성은 앞에서 지정한 패키지 범위에 하위 패키지를 포함시킬 여부를 결정하는 것이며, 기본값을 true이다. 이 경우 하위 패키지를 모두 포함시킨다.
Logback 적용
설정이 끝났으니 실제 적용을 해보자.
package com.core.testproject.controller;
[ ... ]
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {
private final Logger LOGGER = LoggerFactory.getLogger(GetController.class);
[ ... ]
}
특정 컨트롤러에 들어가 Logger 객체를 선언해 준다. Logger는 LoggerFactory를 통해 객체를 생성한다. 이때 클래스의 이름을 함께 지정해 클래스의 정보를 Logger에서 가져가게 설정한다.
주의할 점은 Logger의 import 클래스를 다양하게 나오니 위 코드와 같이 slf4j를 사용하도록 하자.
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String getHello(){
LOGGER.info("getHello 메서드가 호출되었습니다.");
return "Hello World";
}
@GetMapping(value = "/variable1/{variable}")
public String getVariable1(@PathVariable String variable) {
// 예제 5.28
LOGGER.info("@PathVariable을 통해 들어온 값 : {}", variable);
return variable;
}
위 코드는 Logger 객체를 활용해 로깅처리를 한 예시이다. getHello() 메서드처럼 단순히 info레벨의 로그를 기록할 수도 있고, getVariable1() 메서드처럼 변수로 들어오는 값을 로깅처리 할 수도 있다.
위 예제는 단순히 로그를 찍는 법에 대한 내용이다. 기존 내가 한 프로젝트에서는 로깅 처리를 하긴 했으나 특정 가이드를 만들어서 진행하지는 않았다. 그렇다 보니 로깅 파일의 관리나 로그를 찍어도 크게 활용할 수가 없었는데 다음 프로젝트를 진행할 때는 로깅에 대한 가이드를 정해서 진행하고 보편적으로 쓰는 방법이 있다면 추가적으로 정리를 해야 될 것 같다.
'Framework > Spring' 카테고리의 다른 글
[스진초5기/Spring] Controller, Service, Repository (3계층) (0) | 2023.10.31 |
---|---|
[스진초5기/Spring] IoC, DI 정리 (0) | 2023.10.30 |
[Spring] REST API를 명세하는 방법 - Swagger (1) | 2023.10.11 |
[Spring] POST, PUT, DELETE API (0) | 2023.10.11 |
[Spring] GET API를 구현하는 다양한 방법 (0) | 2023.10.06 |