왜 간단하게 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>