시스템이 실행되면서 필연적으로 에러가 발생하게 되는데, 시스템이 동작하는 과정을 하루종일 모니터링하고 있을 순 없다. 그렇기 때문에 로깅을 통해 시스템의 상태와 정보를 기록해두어야 한다. 너무 세세한 정보를 기록하면 방대한 양의 로그가 쌓여 원하는 정보를 얻기 힘들기 때문에 적절한 포맷과 정보를 로깅하는 것이 중요하다.
Log4j? Logback? Log4j2?
Spring에 로깅 구현체를 검색하다보면 해당 3가지의 구현체가 가장 많이 나온다.
Log4j는 2015년에 지원이 중단된 오래된 프레임워크이고,
Logback은 Spring의 spring-boot-starter-logging에서 지원해주는 디폴트 구현체이다.
Logback 이후 Log4j2가 나오게 되었는데, 다른 로깅 프레임워크보다 많은 처리량과 짧은 대기 시간이라는 장점을 가지고 있고, 람다식과 lazy evaluation을 지원한다.
그러면 spring의 디폴트 로깅 구현체는 왜 logback일까?
Log4j가 2015년에 지원이 중단되고, spring-boot-starter-logging이 2014년에 지원된 것으로 미루어 보아 당시엔 logback이 더 발전된 프레임워크가 아니었을까 추측한다.
spring 개발자도 Log4j2가 Logback보다 더 뛰어난 프레임워크인 것은 인정하지만, 한번에 로깅시스템을 변경하기엔 기존의 logback을 사용중인 사용자가 많기 때문에 우려해서 변경하지 않는 것 같다.
spring-boot-starter-logging이 최근에 출시되었다면 Log4j2를 선택했을 것 같다는 답변이 있었다. 참고
성능 차이
Log4j2와 Logback의 성능 차이를 검색하다보면 해당 이미지가 가장 많이 나온다.
하지만 해당 이미지는 AsyncAppender, AsyncLogger를 사용했을때 성능이고, Async를 적용하려면 다양한 비동기 관련 이슈를 설정해야 하기 때문에 항상 Async만을 사용하는 것은 아니다.
그래서 비동기가 아닌 동기처리 속도는 Logback과 얼마나 차이가 나는 것일까 궁금해서 찾아보았다.
동기 처리 속도 또한 Log4j2가 더 좋은 처리량을 보여주고 있다. 파일 로깅 응답 시간또한 Logback에 비해 짧은 응답 속도를 보여준다. 그 외 다양한 통계를 참고하고 싶다면 공식문서를 참조하자.
Slf4j
Simple Logging Facade For JAVA의 약자로, 자체적인 로깅 프레임워크가 아닌 logger의 추상체
logback이나 log4j와 같은 로깅 프레임워크의 인터페이스 역할을 해준다.
단독으로 사용할 수 없고 Binding 모듈을 함께 사용해야 한다.
private static final Logger LOGGER = LoggerFactory.getLogger();
lombok의 @Slf4j 어노테이션을 사용함으로써 해당 코드를 생략할 수 있게 된다.
Slf4j를 왜 사용해야 할까?
로깅엔 다양한 라이브러리가 있다. logback, log4j 등등
만약 logback에서 log4j로 변경하라는 요구사항이 들어온다면? ( 경험담이다.. )
log4j를 import한 파일들을 모두 수정해야 할 것이다. ( import logback -> import log4j2 )
하지만 slf4j를 사용한다면 gradle의 dependency만 변경해주면 된다.
또한 사용하는 오픈소스 모듈들이 다 다른 로깅 모듈을 사용한다면 해당하는 로깅 모듈의 종속성을 모두 가져가야할 것이다. slf4j를 사용한다면 모두 동일한 구현체로 사용이 가능하다.
Dependency
spring의 기본 구현 구현체는 logback이므로, log4j를 위해서는 추가 dependency 설정이 필요하다. 다음과 같이 spring-boot-starter-web 과 같은 모듈에 spring-boot-starter-logging이 내장되어 있다.
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-log4j2")
modules {
module("org.springframework.boot:spring-boot-starter-logging") {
replacedBy("org.springframework.boot:spring-boot-starter-log4j2")
}
}
compileOnly("org.projectlombok:lombok")
annotationProcessor("org.projectlombok:lombok")
}
따라서 다음과 같이 spring-boot-starter-logging을 spring-boot-starter-log4j2로 대체해주어야 한다.
Slf4j 사용을 위해 lombok도 추가해주자.
Configuration
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration status="INFO">
<Properties>
<Property name="LOG_DATEFORMAT_PATTERN">{yyyy-MM-dd HH:mm:ss.SSS}</Property>
<Property name="LOG_LEVEL_PATTERN">{-%5p}{FATAL=red blink, ERROR=red, WARN=yellow bold, INFO=green, DEBUG=green bold, TRACE=blue} %style{${sys:PID}}{magenta} [%15.15t] %style{%-40.40C{1.}}{cyan} : %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}</Property>
</Properties>
<Appenders>
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout disableAnsi="false" pattern="%d${LOG_DATEFORMAT_PATTERN} %highlight${LOG_LEVEL_PATTERN}"/>
</Console>
<RollingFile name="FileAppender"
fileName="logs/spring.log"
filePattern="logs/spring-%d{yyyy-MM-dd}-%i.log">
<JsonLayout complete="false" compact="false" charset="UTF-8">
<KeyValuePair key="service" value="simple" />
</JsonLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" />
<SizeBasedTriggeringPolicy size="10000KB" />
</Policies>
<DefaultRolloverStrategy max="20" fileIndex="min" />
</RollingFile>
</Appenders>
<Loggers>
<Root level="info">
<AppenderRef ref="ConsoleAppender" />
<AppenderRef ref="FileAppender" />
</Root>
</Loggers>
</Configuration>
resources/log4j2.xml 이 아닌 다른 폴더, 다른 이름으로 저장하고 싶다면 application.yml에 logging.config.classpath를 통해 설정해주어야 한다.
spring profile은 log4j2-dev.xml과 같은 방식으로 설정이 가능하다.
status
로깅 레벨 설정
로깅 레벨
TRACE < DEBUG < INFO < WARN < ERROR < FATAL
왼쪽으로 갈 수록 상세한 정보를 출력한다.
Properties
configuration에서 사용할 변수를 정의한다.
Appenders
전달된 로그를 출력할 대상을 지정한다.
출력할 대상은 Console, File, Socket, Mail, Message, DB등 다양하다.
Console
콘솔에 출력될 로그를 설정한다. 우리가 스프링을 실행했을때 보는 콘솔창 로그이다.
RollingFile
여러 파일을 순회하면서 로그 저장하는 appender
로그가 한 파일에 저장된다면 파일이 너무 커져서 실행이 불가능하거나 로그가 전부 날아갈 수도 있다. 따라서 Policy에 따라 로그를 각자 다른 파일에 저장할 수 있도록 해준다.
- name: appender 이름
- fileName: 파일 이름
- filePattern: 롤링 정책에 따른 파일 이름 패턴
- Policies: 정책
- TimeBasedTriggeringPolicy: interval(1일) 단위로 새로운 파일에 로그 기록
- SizeBasedTriggeringPolicy: 파일 사이즈가 초과할 경우 새로운 파일에 기록
- DefaultRolloverStrategy: 파일 생성 최대 개수 설정
그외 JDBCAppender, SMTPAppender 등이 있다.
Layout
Pattern, Json, Yaml 등 다양한 형태가 있다.
해당 코드에선 콘솔은 pattern 으로, file은 json으로 저장했다.
patternlayout은 우리가 스프링부트를 실행했을때 나오는 콘솔 패턴으로 지정해주었고, file은 추후 kibana 형태를 고려해 json으로 지정해주었다.
여기서 disableAnsi="false" 와 컬러를 지정해주지 않으면 콘솔이 모두 하얀색으로 출력되므로 추가적인 설정이 필요하다.
위 코드로 지정해주었을땐 다음과 같이 에러별로 다른 색상의 로그를 보여주게 된다.
다른 형태의 로그로 출력하고 싶다면 공식문서를 참조해보자.
Loggers
로그 파일을 작성하는 클래스 자체로 로깅 작업의 주체이다.
출력할 메세지를 Appender에게 전달하며 패키지별로 설정 가능하다.
Root
모든 패키지에 대한 로깅을 위한 정책 (한개만 설정 가능)
- AppenderRef: appender의 name을 통해 참조한다.
MDC
message diagnostic context로, 멀티 클라이언트 환경에서 각 클라이언트별로 값을 구별하여 로그를 추적할 수 있도록 제공되는 map이다.
ThreadLocal에 키 값을 저장하며, Thread가 존재하는 동안 사용할 수 있다.
MSA 와 같이 하나의 클라이언트에 서로 다른 서비스들이 호출되는 아키텍처에선 Spring Cloud의 Sleuth 와 Zipkin을 연동하여 사용한다.
요기에선 지정해주지 않고 추후 추가할 예정
Log4j2 설정을 완료하고 어플리케이션을 구동하게 되면, 다음과 같이 logs/spring.log 파일에 json 형태로 로그가 저장되는 모습을 볼 수 있다.
전체 코드는 깃허브에서 확인할 수 있다.
다음엔 적재된 로그를 통해 ELK로 관리 및 모니터링해보자.
참고
https://www.baeldung.com/java-log-json-output
https://meetup.toast.com/posts/149
https://deeplify.dev/back-end/spring/logging
https://055055.tistory.com/96
https://www.slideshare.net/whiteship/ss-47273947
https://zzang9ha.tistory.com/387
https://minkwon4.tistory.com/161
https://dololak.tistory.com/632
https://jordy-torvalds.tistory.com/entry/Java-Logging-Framework-About-slf4j-log4j-logback-log4j2
https://inyl.github.io/programming/2017/05/05/slf4j.html
'Java > spring' 카테고리의 다른 글
[Spring] WireMock 을 사용한 HTTP Client 유닛테스트 (0) | 2022.12.11 |
---|---|
[Spring] Spring Batch에 Hexagonal Architecture를 적용해보자 (0) | 2022.06.06 |
[Spring Security] FormLogin에서 Custom Filter 처리 이슈 (0) | 2022.02.21 |
[Spring Security] LoginSuccessHandler와 FailureHandler 호출 원리 (0) | 2022.02.19 |
[Spring Cloud] FeignClient Logging 방법 정리 (0) | 2022.02.16 |