Logging with SLF4J and Logback

왜 간단하게 System.out.print()으로 로그를 출력하지 않을까?
DEBUG, INFO...) 수준 이상의 출력 메시지를 표시할 수 있음java.util.logging은 잘 사용하지 않음Log4J - 몇 년간 사실상 표준(de facto)이었음Logback - Log4J의 개발자가 만든 후속작으로 현재 많은 프로젝트에서 사용됨SLF4J (Simple Logging Facade for Java) - Facade 패턴을 사용하는 인터페이스로 Log4J, Logback 등 실제 로깅 프레임워크의 공통된 인터페이스 역할
Facade Pattern
※ 캡슐화(정보 은닉)와 목적이 다름
SLF4J
java.util.logging, Log4j, Logback 등 다양한 로깅 프레임워크에 대해 간단한 퍼사드(혹은 추상화 레이어) 역할log4j의 후속작으로 더 빠르고 가벼움
Logback
logback-classic 모듈을 사용하려면, 클래스패스에 다음 JAR 파일들이 반드시 포함되어야 한다.
※ 스프링부트에서 spring-boot-starter-logging은 로킹 프레임 워크를 포함함
package kr.ac.hansung.cse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger("kr.ac.hansung.cse.HelloWorld");
logger.debug("Hello world.");
}
}20:49:07.962 [main] DEBUG kr.ac.hansung.cse.HelloWorld - Hello world.Logger는 이름을 가진 엔티티(객체)로 이름을 대소문자를 구분하며 계층적인 이름 규칙을 따름
kr.ac.hansung 이름의 Logger는 kr.ac.hansung.cse Logger의 부모Logger에 레벨을 지정할 수 있다
TRACE → DEBUG → INFO → WARN → ERROR 순으로 **중요도(심각도)**가 점점 높아짐INFO임| Logger name | Assigned level | Effective level |
|---|---|---|
| root | DEBUG | DEBUG |
| X | INFO | INFO |
| X.Y | none | INFO |
| X.Y.Z | ERROR | ERROR |
logger.warn("hello");는 WARN 레벨의 로그 요청
Logback
YES = 출력됨, NO = 무시됨)Logging Request p ↓ \ Logger Effective Level q → | TRACE | DEBUG | INFO | WARN | ERROR | OFF |
|---|---|---|---|---|---|---|
| TRACE | YES | NO | NO | NO | NO | NO |
| DEBUG | YES | YES | NO | NO | NO | NO |
| INFO | YES | YES | YES | NO | NO | NO |
| WARN | YES | YES | YES | YES | NO | NO |
| ERROR | YES | YES | YES | YES | YES | NO |
package kr.ac.hansung.example;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
public class LoggerLevelExample {
public static void main(String[] args) {
// "kr.ac.hansung.example" 이름의 로거 생성
Logger logger = (Logger) LoggerFactory.getLogger("kr.ac.hansung.example");
// 로거 레벨을 INFO로 설정
logger.setLevel(Level.INFO);
// 출력됨: WARN >= INFO
logger.warn("⚠️ Low fuel level.");
// 출력 안 됨: DEBUG < INFO
logger.debug("🔍 Starting search for nearest gas station.");
// 출력됨: ERROR >= INFO
logger.error("❗ Out of fuel! Engine stopped.");
// 출력 안 됨: TRACE < INFO
logger.trace("Trace log for diagnostic purposes.");
}
}[main] WARN kr.ac.hansung.example - ⚠️ Low fuel level.
[main] ERROR kr.ac.hansung.example - ❗ Out of fuel! Engine stopped.console, files (plain text, HTML...), remote socket servers, databases
Appenders
특정 로거의 활성화된 로그 요청은, 해당 로거에 설정된 모든 appender뿐 아니라 상위 계층의 appender들에게도 전달된다.
console appender가 추가되어 있다면 모든 활성 로그는 최소한 콘솔에 출력된다.file appender를 설정하면, 및 그 하위 로거의 로그는 파일과 콘솔에 모두 출력된다.false로 설정하면 상위 appender 상속을 막을 수 있다.| Logger Name | Attached Appenders | Additivity Flag | Output Targets | Comment |
|---|---|---|---|---|
root | A1 | not applicable | A1 | Since the root logger stands at the top of the logger hierarchy, the additivity flag does not apply to it. |
x | A-x1, A-x2 | true | A1, A-x1, A-x2 | Appenders of "x" and of root. |
x.y | none | true | A1, A-x1, A-x2 | Appenders of "x" and of root. |
x.y.z | A-xyz1 | true | A1, A-x1, A-x2, A-xyz1 | Appenders of "x.y.z", "x" and of root. |
security | A-sec | false | A-sec | No appender accumulation since the additivity flag is set to false. Only appender A-sec will be used. |
security.access | none | true | A-sec | Only appenders of "security" because the additivity flag in "security" is set to false. |
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern> %d{HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n </pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
</root>
<logger name="myPackage.Heater" level="warn"/>
</configuration>| 패턴 | 의미 |
|---|---|
%d{pattern} | 로그 발생 시각 |
%thread | 스레드 이름 |
%-5level | 로그 레벨 (5자 너비로 좌측 정렬) |
%logger{length} | 로거 이름 (최대 길이 지정 가능) |
%msg | 로그 메시지 |
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<append>true</append> <!-- default -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} %msg%n</pattern>
</encoder>
<file>test.dat</file>
</appender>
<root level="info">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>로그는 콘솔과 파일(test.dat)에 동시에 출력
<appender name="log-file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>my-application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- rotate every day for log collection and archiving -->
<fileNamePattern>my-application.%d{yyyyMMdd}.log</fileNamePattern>
</rollingPolicy>
</appender>classpath에 위치한 logback.xml 파일
Logback Configuration
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>TRACE</level>
</filter>
</appender>
<appender name="DAILY_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/rest-demo.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/rest-demo.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<cleanHistoryOnStart>true</cleanHistoryOnStart>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{35} - %msg%n</pattern>
</encoder>
</appender>
<logger name="kr.ac.hansung" level="DEBUG" additivity="false">
<appender-ref ref="DAILY_FILE"/>
<appender-ref ref="CONSOLE"/>
</logger>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>