로깅에서 사용되는 log4j, logback, log4j2에 대해 알아보자 ❕
이 세가지는 Java기반 애플리케이션에서 로그를 생성하고 관리하기 위한 대표적인 로깅 프레임워크이다. 이번 시간에는 로그의 필요성과 Log4j, Logback, Log4j2에 대해 정리하고, Spring에서 Logback을 사용하는 방법까지 정리해보려고 한다.
1. Log(로그)
로그는 애플리케이션이나 시스템에서 발생하는 이벤트, 상태, 오류, 결과 등을 기록한 데이터를 말한다. 주로 텍스트 형식으로 기록되며, 개발자가 시스템 동작을 이해하고 문제를 진단하는데 사용된다. 따라서 로그를 통해 서비스의 품질과 운영 효율을 높일 수 있다.
try {
...
} catch (Exception e) {
log.error("class={}, method={}, parameter={}, time={}, errorMessage={}",
className, methodName, parameter, time, e.getMessage());
}
이처럼 어디에서 발생한 에러이고, 파라미터, 시간, 에러 메세지 등을 상세하게 남겨 로그의 의미를 쉽게 파악할 수 있도록 해야 한다. 이 로그를 읽고 누가 봐도 의미를 파악할 수 있도록 해주는 것이 중요하다. (물론 로그에서 기본적으로 시간 정보와 로그레벨, 메세지 등은 기록해준다)
또한, 로그를 통해서 사용자 행동 패턴이나 시스템 사용량을 분석할 수도 있어 로그는 유용하게 사용될 수 있다. 하지만 로그를 남기는 것도 비용이 발생하기 때문에, 적절한 포인트에 로그를 작성해주는 것이 중요하다.
System.out.println()과 비교
보통 로깅을 할 때 System.out.println() 대신 로깅 라이브러리를 사용하도록 권장하는데, 이는 다음과 같은 이유에서 때문이다.
1. 동기화로 인한 성능 저하
println()을 확인해보면 synchronized 블록으로 구성되어 있다. 따라서 멀티스레드 환경에서 한 스레드씩 처리가 가능하고, 나머지는 대기하게 된다. 따라서 이 과정에서 성능 저하가 발생할 수 있다.
2. 로그 레벨과 로그 데이터가 기록되지 않는다.
기본적으로 로그는 기록 시점, 로그 레벨, 메세지, 스레드 정보 등의 데이터가 기록된다. 하지만 println()을 사용하면 어떤 레벨에서 발생했는지 알 수 없고 언제, 어디서 발생했는지 추가적으로 명시해줘야 한다.
⇒ 정말 간단한 출력과 확인을 위해서라면 println()을 사용하는 것이 더 나을 수도 있다. 하지만 시스템 운영에서 데이터를 기록하고 효율을 높이려면 로그를 사용하는 것이 적절하다.
2. Log4J, Logback, Log4J2
Log4J
log4j는 Apache에서 개발한 초기 로깅 프레임워크로, 현재는 유지보수가 중단되었고 2021년에 *취약점이 발견되어 지금은 잘 사용되지 않는다.
* Log4J 취약점 ?
2021년 발견된 심각한 보안 결함으로, Log4Shell이라고 알려져 있으며 공격자가 시스템에 원하는 코드를 실행할 수 있는 RCE(Remote Code Execution)를 허용해 시스템에 치명적인 결과를 초래할 수 있게 된다.
JNDI 조회 기능에서 취약점이 발생되었으며, Log4j는 로그 메세지에 포함된 특정 문자열을 처리할 때 JNDI(Java Naming and Directory Interface)를 사용해 원격 자원을 조회할 수 있었다. 이 때, 외부 입력값을 제대로 검증하지 않아 공격자가 악의적인 코드를 전달하고 실행할 수 있게 된 것이다.
^즉, 간단한 입력값으로 서버를 제어할 수 있게 허용하는 보안 결함인 것이다.^
Logback
logback은 log4j의 단점을 개선하기 위해 만든 프레임워크이다. log4j보다 빠르게 수행되며 메모리 사용량이 적고, 로그 파일을 시간이나 크기 기준으로 롤링이 가능하다. 스프링 부트의 spring-boot-starter-web
안에 spring-boot-starter-logging
에 구현체가 있는데, 여기서 기본으로 logback이 사용된다.
Log4J2
log4j의 후속 버전으로, Apache에서 개발하였으며 log4j의 단점을 개선한 프레임워크이다. 비동기 로깅, 유연한 설정, 롤링, 고성능 이라는 장점이 있다. 스프링 부트의 spring-boot-starter-log4j2
의존성을 추가해, 기존의 logback과 충돌나지 않도록 exclude해줘야 한다.
Log4j2 의존성 설정 (maven)
<!-- 변경 전 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 변경 후 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
+ SLF4J
SLF4J는 Simple Logging Facade For Java의 약자로, Java 애플리케이션에서 로깅 추상화를 제공하는 라이브러리이다. 로깅 프레임워크(Log4j
, Logback
, Log4j2
등)와 애플리케이션 코드 사이에 추상화 계층을 추가하여, 특정 로깅 구현체에 종속되지 않고 쉽게 교체하거나 변경할 수 있도록 설계되었다. 스프링에서도 이 라이브러리를 사용하면 쉽게 로그를 설정할 수 있고, 각 프레임워크를 유연하게 변경할 수 있다.
Lombok을 사용한다면 다음 처럼 쉽게 @Slf4j로 바로 log를 사용할 수 있다.
@Slf4j
public class Hello {
public void world() {
log.trace("trace msg");
log.debug("debug msg");
log.info("info msg");
log.warn("warn msg");
log.error("error msg");
}
}
Lombok을 사용하지 않는다면, Logger 객체를 직접 생성해야 한다.
public class Hello {
private static final Logger logger = LoggerFactory.getLogger(Hello.class);
public void world() {
...
}
}
3. 로깅 설정
로그 레벨
로그는 정도에 따라 레벨을 설정하여 나타낼 수 있는데, 보통 5가지 trace, debug, info, warn, error로 나타낸다.
다음과 같은 순서로 레벨이 나누어지며, 디폴트 설정이 info로 되어있다. 자신보다 높은 번호는 표시하지 않으며, 만약 info 레벨로 설정했다면 debug와 trace 로그는 표시되지 않는다.
- ERROR : 오류가 발생한 경우
- WARN : 처리 가능한 문제, 향후 시스템 에러의 원인이 될 수 있는 경고성 메시지를 표시
- INFO : 상태 변경과 같은 정보 표시
- DEBUG : 디버깅하기 위한 정보 표시
- TRACE : DEBUG 레벨 보다 상세한 정보 표시
default 상태에서 확인해보면, 콘솔창에서 info, warn, error 로그가 기록되는 것을 볼 수 있다.
설정파일에서 쉽게 로그 레벨을 설정할 수 있고, 패키지별로 다르게 설정할 수도 있다.
logging:
level:
root: warn
logback-spring.xml 설정
위 처럼 application.yml이나 application.properties에 간단하게 설정을 변경할 수 있지만, 이는 스프링 부트 위에서만 가능한 추상화된 방식이고 logback의 고급 기능을 사용하기에는 제한적이다. 따라서 별도의 xml파일을 생성해 다양한 기능을 설정할 수 있다.
src > main > resources 하위에 logback-spring.xml
을 생성하고, 주로 appender와 logger을 작성해주면 된다.
(spring이나 일반 자바프로그램의 경우 resources하위에 logback.xml 파일을 참조하고, spring boot의 경우는 resources하위에 logback-spring.xml 파일을 참조한다)
Chapter 4: Appenders
There is so much to tell about the Western country in that day that it is hard to know where to start. One thing sets off a hundred others. The problem is to decide which one to tell first. —JOHN STEINBECK, East of Eden Chapter 4: Appenders What is an Ap
logback.qos.ch
- Appender : 로그 출력 위치 설정
- ConsoleAppender : 콘솔에 로그를 찍음
- FileAppender : 파일에 로그를 찍음
- RollingFileAppender : 여러 파일을 롤링, 순회하면서 로그를 찍음
- …
- Loggers : 로깅 작업 설정
- Root : 전역 설정
- Logger : 지역 설정
4. 실습
다음과 같이 설정하고 xml 파일을 수정해가면서, 로그를 확인해 보자 (xml 코드는 https://goddaehee.tistory.com/206를 참고했다)
1. ConsoleAppender 추가
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<property name="LOG_LEVEL" value="DEBUG"/>
<property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>
<!-- 콘솔 출력 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<root level="${LOG_LEVEL}">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>
실행해보면, 콘솔창에 로그가 출력됨을 확인할 수 있다.
2. RollingFileAppender 추가
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<property name="LOG_LEVEL" value="DEBUG"/>
<property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>
<property name="LOG_PATH" value="logs"/>
<property name="LOG_FILE_NAME" value="spring-mvc-logback"/>
...
<!-- 파일 출력 -->
<appender name="ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${LOG_FILE_NAME}.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<!-- 롤링 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFileNamingAndTriggeringPolicy">
<maxFileSize>200KB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level="${LOG_LEVEL}">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ROLLING_FILE"/>
</root>
</configuration>
RollingFileAppender는 FileAppender를 상속하여 로그 파일을 rollover한다. rollover는 다음 파일로 이동하는 행위로 특정 기준에 따라 기록하는 파일 대상을 바꿔주는 것이다.
실행해보면, 설정했던 logs 하위에 spring-mvc-logback.log 파일이 생성됨을 확인할 수 있다.
3. error 레벨의 로그만 분리
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<property name="LOG_LEVEL" value="DEBUG"/>
<property name="LOG_PATTERN" value="%-5level %d{yy-MM-dd HH:mm:ss}[%thread] [%logger{0}:%line] - %msg%n"/>
<property name="LOG_PATH" value="logs"/>
<property name="LOG_FILE_NAME" value="spring-mvc-logback"/>
<property name="ERROR_LOG_FILE_NAME" value="error-spring-mvc-logback"/>
...
<!-- 에러 파일 출력 (에러 로그 분리) -->
<appender name="ERROR_ROLLING_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<file>${LOG_PATH}/${ERROR_LOG_FILE_NAME}.log</file>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${LOG_PATTERN}</pattern>
</encoder>
<!-- 롤링 정책 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${ERROR_LOG_FILE_NAME}.%d{yyyy-MM-dd}_%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFileNamingAndTriggeringPolicy">
<maxFileSize>200KB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
</appender>
<root level="${LOG_LEVEL}">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ROLLING_FILE"/>
<appender-ref ref="ERROR_ROLLING_FILE"/>
</root>
</configuration>
실행해보면, 에러 로그 파일이 분리되고 에러 레벨만 기록되어 있음을 확인할 수 있다.
5. 마무리
로깅의 필요성과 다양한 로깅 프레임워크에 대해 학습하고, Spring에서 기본으로 사용하는 Logback을 실습해보았다. 이전에는 프로젝트 규모가 작아 로깅의 중요성을 크게 느끼지 못했지만, 실제 운영 환경에서는 로깅을 효과적으로 설정하고 활용하는 것이 시스템 안정성과 서비스 개선에도 큰 도움이 될 것으로 보인다. 이번 실습에서는 간단한 설정만 진행했지만, 추후에는 로깅 파일 관리와 고급 설정, Log4j2의 활용 방법에 대해서도 추가로 학습하려고 한다.
참고 자료 😃
https://junuuu.tistory.com/773
https://loosie.tistory.com/829
https://meetup.nhncloud.com/posts/149
https://velog.io/@wooryung/Spring-Boot-로그-설정하기-Logback
https://goddaehee.tistory.com/206
'Spring' 카테고리의 다른 글
[Spring] Spring WebFlux 개념 및 실습 (1) | 2024.12.12 |
---|---|
[Spring] Apache Poi 엑셀 다운로드 개선 및 성능 확인 (+ SXSSF) (1) | 2024.12.06 |
[Spring] SpringBoot에서 Redis 적용하기 (+부하 테스트) (8) | 2024.11.08 |
[Spring] Spring에서 동시성 이슈를 해결하는 방법 (1) | 2024.10.02 |
[Spring] Apache POI (+ Multipart, Spring 구현) (0) | 2024.05.14 |