티스토리 뷰
서론
애플리케이션에서 로깅(logging)은 디버깅, 모니터링, 그리고 성능 최적화를 위한 필수적인 요소입니다.
특히, 대규모 시스템에서는 로깅의 중요성이 더욱 강조됩니다.
Spring 애플리케이션에서 Logback을 활용하여 로깅 시스템을 구축하는 방법에 대해 알아보고자 합니다.
Logback은 SLF4J의 구현체 중 하나로, 강력한 성능과 유연성을 제공합니다.
XML 구성이나 annotation을 통해 다양한 로깅 환경을 세밀하게 설정할 수 있습니다.
Logback 초기 설정
Logback을 설정하기 위해서 dependency를 가장 먼저 설정해줘야 하는데
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
다음과 같은 logging을 넣어줘도 되지만 spring-boot-starter-web에도 이미 포함되어 있기 때문에
별도의 dependency를 사용할 필요는 없습니다.
이후에 가장 먼저 만들어줘야 할 파일은 resources 폴더 밑에 logback-spring.xml 파일을 만들어줘야 합니다.
위 파일에서는 profile 설정에 따라서 logback 파일이 다르게 들어가는 것을 넣어주면 됩니다.
(쉽게 생각해서 application.yml, application-local.yml 의 세팅을 생각하시면 됩니다.)
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
<springProfile name="local">
<include resource="logback-spring-local.xml"/>
</springProfile>
<springProfile name="prod">
<include resource="logback-spring-prod.xml"/>
</springProfile>
</configuration>
위에서 보서 defaults.xml 은 기본 logback의 설정파일을 포함시켜주는 것이고,
아래는 profile 에 맞는 logback 설정 파일이 들어가게 만들어주는 설정입니다.
Logback local 설정하기
만약 위에만 설정하고 logback-spring-local.xml을 만들고 난 후 실행을 해본다면,
아무 로그도 찍히지 않는 것을 확인 할 수 있을 것입니다.
왜냐하면 console-appender.xml에 가장 초기의 설정이 담겨있기 때문입니다.
그렇다면 해당 파일을 먼저 살펴보면 logback의 가장 기본 설정을 확인 할 수 있습니다.
org/springframework/boot/logging/logback/console-appender.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
Console appender logback configuration provided for import, equivalent to the programmatic
initialization performed by Boot
-->
<included>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>${CONSOLE_LOG_THRESHOLD}</level>
</filter>
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>${CONSOLE_LOG_CHARSET}</charset>
</encoder>
</appender>
</included>
이 것을 참고하여 간단한 logback 설정을 만들어볼 수 있습니다.
level과 pattern은 각각 출력 로그레벨과 어떠한 패턴으로 출력할 지를 정하는 것입니다.
또한 해당 값들은 defaults.xml에서 가져오는 것입니다.
아래는 위를 참고하여 만든 logback 파일입니다.
<included>
<include resource="org/springframework/boot/logging/logback/console-appender.xml" />
<appender name="CONSOLE2" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<layout>
<pattern>
[CONSOLE2] [%-5level] %d{yyyy-MM-dd HH:mm:ss} [%thread] [%logger{40}:%line] - %msg%n
</pattern>
</layout>
</appender>
<root level="DEBUG">
<appender-ref ref="CONSOLE2" />
</root>
</included>
위 처럼 하게 되면 우리가 설정한 것의 패턴으로 로그가 찍히게 됩니다.
여기서 추가적으로 설명할 부분인 <root>에 대해서 root안의 appender만 로그에서 찍하게 되고,
또한 root의 레벨 설정보다 위의 <appender>의 레벨 설정이 우선 순위가 높습니다.
이제 어플리케이션을 실행해보면 다음과 같이 출력되는 것을 확인해 볼 수 있습니다.
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.2)
[CONSOLE2] [INFO ] 2024-03-04 23:46:36 [main] [com.sihun.logback.LogbackApplication:50] - Starting LogbackApplication using Java 17 with PID 9248 (D:\dev\spring-til\logback\build\classes\java\main started by sihun in D:\dev\spring-til)
[CONSOLE2] [INFO ] 2024-03-04 23:46:36 [main] [com.sihun.logback.LogbackApplication:660] - The following 1 profile is active: "local"
[CONSOLE2] [INFO ] 2024-03-04 23:46:37 [main] [o.s.b.w.embedded.tomcat.TomcatWebServer:109] - Tomcat initialized with port 8080 (http)
[CONSOLE2] [INFO ] 2024-03-04 23:46:37 [main] [o.apache.catalina.core.StandardService:173] - Starting service [Tomcat]
[CONSOLE2] [INFO ] 2024-03-04 23:46:37 [main] [org.apache.catalina.core.StandardEngine:173] - Starting Servlet engine: [Apache Tomcat/10.1.18]
[CONSOLE2] [INFO ] 2024-03-04 23:46:37 [main] [o.a.c.c.C.[Tomcat].[localhost].[/]:173] - Initializing Spring embedded WebApplicationContext
[CONSOLE2] [INFO ] 2024-03-04 23:46:37 [main] [o.s.b.w.s.c.ServletWebServerApplicationContext:296] - Root WebApplicationContext: initialization completed in 528 ms
[CONSOLE2] [INFO ] 2024-03-04 23:46:37 [main] [o.s.b.w.embedded.tomcat.TomcatWebServer:241] - Tomcat started on port 8080 (http) with context path ''
[CONSOLE2] [INFO ] 2024-03-04 23:46:37 [main] [com.sihun.logback.LogbackApplication:56] - Started LogbackApplication in 1.057 seconds (process running for 4.023)
Logback prod 설정하기
local과 prod의 가장 큰 차이점은 prod는 로그 파일을 남겨야 하고,
남겨진 로그 파일들의 수명관리를 해야하는 점이 있습니다.
우선 logback-spring-prod.xml을 만들기 전에 위에서 기본 appender였던 console-appender.xml처럼
설정들을 외부 파일로 받아 올 수 있게끔 logback-variables.properties를 만들겠습니다.
LOG_DIR=logback/logs
LOG_PATTERN=[%-5level] %d{yyyy-MM-dd HH:mm:ss} [%thread] [%logger{40}:%line] - %msg%n
위처럼 간단하게 log를 저정할 위치와 패턴만 설정하여 동일하게 resources 폴더 밑에 저장해줍니다.
그리고 logback-spring-prod.xml 파일에 property 이름을 넣어주면 됩니다.
또한 appender를 만들때 앞선 local과는 다르게 rollingPolicy를 통해서 파일 크기와 보관 주기를 설정해주면 됩니다.
<included>
<property resource="logback-variables.properties" />
<appender name="REQUEST1" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/request1.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/archive/request1.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<maxFileSize>1KB</maxFileSize> <!-- 로그파일의 최대 크기-->
<maxHistory>30</maxHistory> <!-- 로그파일 최대 보관주기(단위 : 일)-->
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
[REQUEST1] ${LOG_PATTERN}
</pattern>
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="REQUEST1" />
</root>
</included>
앞서 설명드린 것을 간단하게 적용하면 위처럼 xml 파일을 만들 수 있습니다.
또한 outputPatterAsHeader는 파일 헤더에 어떠한 형식으로 출력할지 알려주는 것입니다.
MDC 설정
MDC 설정은 멀티스레드 환경에서 각 요청이나 사용자 세션에 대한 정보를
로그에 포함시켜 문제를 추적하기 용이하게 만들어줍니다.
우선 MDC 설정을 한 로그를 출력하고 싶다면 MDC 라이브러리를 아래와 같이 사용해줘야 합니다.
key, value 형태로 값을 put으로 넣어주고 항상 로그가 끝나는 시점에 clear를 호출해줘야 합니다.
@GetMapping("/mdc")
public String mdc(){
MDC.put("userId", "user123");
log.trace("log --> TRACE");
log.debug("log --> DEBUG");
log.info("log --> INFO");
log.warn("log --> WARN");
log.error("log --> ERROR");
MDC.clear();
return "mdc";
}
기본적으로 MDC는 Map 자료구조를 사용하기 때문에 key값을 기준으로 로그를 출력할 수 있습니다.
아래는 MDC의 구현 코드 입니다.
public void put(String key, String val) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
} else {
Map<String, String> map = (Map)this.inheritableThreadLocal.get();
if (map == null) {
map = new HashMap();
this.inheritableThreadLocal.set(map);
}
((Map)map).put(key, val);
}
}
그리고 xml 패턴을 %X를 통해서 value 값을 출력이 가능합니다.
아래와 같이 appender를 만드는 것이 가능합니다.
<appender name="MDC" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/mdc.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/archive/mdc.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<maxFileSize>10KB</maxFileSize> <!-- 로그파일의 최대 크기-->
<maxHistory>30</maxHistory> <!-- 로그파일 최대 보관주기(단위 : 일)-->
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
[MDC] %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %X{userId} %msg%n
</pattern>
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
</appender>
Error, Warn만 따로 출력하기
Error나 Warn은 local에서 사용했던 filter 옵션을 사용해주면 됩니다.
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/error.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/archive/error.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<maxFileSize>10KB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
[ERROR] ${LOG_PATTERN}
</pattern>
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
</appender>
<appender name="WARN" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_DIR}/warn.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_DIR}/archive/warn.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<maxFileSize>10KB</maxFileSize>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>
[WARN] ${LOG_PATTERN}
</pattern>
<outputPatternAsHeader>true</outputPatternAsHeader>
</encoder>
</appender>
이전 local과는 다르게 새로운 onMatch와 onMismatch가 있는데,
현재 level보다 상위 레벨이 아닌 딱 지정한 레벨만 출력할 수 있도록 하는 것입니다.
특정 Controller에 대해서 logging 적용하기
다른 logback과 별반 다르지 않고 appender는 동일하게 이용이 가능합니다.
먼저 log를 적용한 @Slf4j에 topic 옵션을 지정해주면 됩니다.
@Slf4j(topic = "MY_SERVICE")
@Service
public class MyService {
public String method(){
log.trace("log --> TRACE");
log.debug("log --> DEBUG");
log.info("log --> INFO");
log.warn("log --> WARN");
log.error("log --> ERROR");
return "method";
}
}
그리고 위에 사용했던 Request1을 재활용 한다면 logback xml 파일에는 아래처럼 설정해주면 된다.
<logger name="MY_SERVICE" level="INFO" additivitty="false">
<appender-ref ref="REQEUST1" />
</logger>
이번 포스팅에서는 logback에 대해서 자세하게 알아보았습니다.
하지만 대용량 트래픽이 발생하는 서비스는 log는 단순히 파일로 떨군다고 끝이 아니라
방대한 로그를 데이터로서 저장하는 엔지니어링이 필요하다고 생각합니다.
엔지니어링은 어떻게 보면 나만의 강점?이라고도 할 수 있고,
시간이 있다면 ELK stack이나 Kafka를 이용한 log 데이터 엔지니어링 포스팅을 해보고자 합니다.
긴글 읽어 주셔서 감사합니다.
'Back End > Spring' 카테고리의 다른 글
[Test] Spring boot, Junit, Testcontainers를 이용한 테스트 환경 만들기 (0) | 2024.04.11 |
---|---|
[Spring Data] Spring Data MongoDB - insert() vs save() (0) | 2024.04.06 |
[Spring] Feign Client - (2) logger, interceptor, error decoder (1) | 2024.02.22 |
[Spring] Feign Client - (1) Naver API 호출하기 (0) | 2024.02.18 |
[Spring] prototype scope 사용 예시 (0) | 2024.02.15 |
- Total
- Today
- Yesterday
- 프론트엔드
- centos
- broker
- Java
- K8S
- apache kafka
- JPA
- apache
- Linux
- Producer
- zookeeper
- 리액트
- Data Engineering
- spring boot
- React
- logback
- Front
- API
- rhel
- frontend
- feign client
- KAFKA
- OS
- Firebase
- cs
- Container
- NextJS
- consumer
- docker
- spring
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |