Spring Framework

교과목 내용을 정리하기 위한 글입니다. 틀린 내용이 있을 수 있으니 참고하세요
들어가며
왜 간단하게 System.out.print()으로 로그를 출력하지 않을까?
유연성을 위해서
- 선택 가능한 우선순위(
DEBUG
,INFO
...) 수준 이상의 출력 메시지를 표시할 수 있음 - 모든 모듈이나 특정 모듈/클래스에 대해서만 메시지를 출력할 수 있음
- 이러한 메시지들의 형식을 어떻게 지정할지 제어할 수 있음
- 어디로 보낼지 결정할 수 있음
Logging Framework
- 네이티브인
java.util.logging
은 잘 사용하지 않음 Log4J
- 몇 년간 사실상 표준(de facto)이었음Logback
- Log4J의 개발자가 만든 후속작으로 현재 많은 프로젝트에서 사용됨SLF4J
(Simple Logging Facade for Java) - Facade 패턴을 사용하는 인터페이스로 Log4J, Logback 등 실제 로깅 프레임워크의 공통된 인터페이스 역할
Facade Pattern (퍼사드 패턴)
Facade Pattern
- 클라이언트는 퍼사드(Facade)에 요청을 보내는 방식으로 서브시스템과 소통하며, 퍼사드는 해당 요청을 적절한 서브시스템 객체로 전달함
- 실제 작업은 서브시스템 객체들이 수행하지만, 퍼사드는 자신의 인터페이스를 서브시스템 인터페이스로 변환하기 위한 작업을 수행해야 할 수 있음
- 클라이언트가 서브 시스템에 직접 접근하지 않아도 됨
※ 캡슐화(정보 은닉)와 목적이 다름
SLF4J (Simple Logging Facade for Java)
SLF4J
java.util.logging
,Log4j
,Logback
등 다양한 로깅 프레임워크에 대해 간단한 퍼사드(혹은 추상화 레이어) 역할- slf4j-api-2.0.12.jar 하나를 의존성에 추가해야 함
- 클래스패스에 로깅 프레임워크와 연결(binding)되는 구현체가 없다면, SLF4J는 아무 동작도 하지 않는(no-operation) 상태로 동작
Logback
log4j의 후속작으로 더 빠르고 가벼움
Logback
logback-classic 모듈을 사용하려면, 클래스패스에 다음 JAR 파일들이 반드시 포함되어야 한다.
- slf4j-api.jar
- logback-core.jar
- 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.
- 위 예제는 logback 관련 클래스는 전혀 사용하지 않았다는 점에 주목
- 오직 SLF4J의 클래스만 import 하면 됨
- 따라서, 코드는 SLF4J API만 사용하고, Logback이 존재한다는 사실을 몰라도 됨
1. Logger
Logger는 이름을 가진 엔티티(객체)로 이름을 대소문자를 구분하며 계층적인 이름 규칙을 따름
kr.ac.hansung
이름의 Logger는kr.ac.hansung.cse
Logger의 부모- root logger는 계층 구조의 최상위
Logging Level
Logger에 레벨을 지정할 수 있다
- 로그 레벨은
TRACE
→DEBUG
→INFO
→WARN
→ERROR
순으로 **중요도(심각도)**가 점점 높아짐 - 명시적으로 레벨이 지정되어 있지 않다면 가장 가까운 상위 로거의 레벨을 상속 받음
- root logger는 항상 레벨이 지정되어 있으며, 기본값은
INFO
임
예시
Logger name | Assigned level | Effective level |
---|---|---|
root | DEBUG | DEBUG |
X | INFO | INFO |
X.Y | none | INFO |
X.Y.Z | ERROR | ERROR |
Printing method
- 어떤 출력 메서드를 사용하는지에 따라 로그 요청의 레벨이 결정됨
→logger.warn("hello");
는WARN
레벨의 로그 요청
Logback
- 로그 요청의 레벨이 해당 로거의 유효 레벨(effective level)보다 크거나 같으면, 그 로그 요청은 **활성화(enabled)**되었다고 한다.
- 그렇지 않으면, 즉 로깅 요청의 레벨이 더 낮으면, 그 로그 요청은 **비활성화(disabled)**되었다고 한다.
로그 요청 허용 여부 (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.
2. Appenders
- 로그 요청은 하나 이상의 출력 대상(destination)으로 출력될 수 있음
- 각 출력 대상은 appender(출력기)로 표현되며, 다음과 같은 종류 가능
→console
,files
(plain text, HTML...),remote socket servers
,databases
Appenders
특정 로거의 활성화된 로그 요청은, 해당 로거에 설정된 모든 appender뿐 아니라 상위 계층의 appender들에게도 전달된다.
- 즉, appender는 로거 계층에서 누적적으로 상속된다.이를 Appender Additivity이라고 부른다.
- 예를 들어, root 로거에
console appender
가 추가되어 있다면 모든 활성 로그는 최소한 콘솔에 출력된다. - 여기에 추가로 어떤 로거 L에
file appender
를 설정하면, 및 그 하위 로거의 로그는 파일과 콘솔에 모두 출력된다. - 하지만 이러한 기본 동작은 설정을 통해 변경할 수 있다. 즉, additivity 플래그를
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. |
1. ConsoleAppender
<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 | 로그 메시지 |
2. FileAppender
<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)에 동시에 출력
3. RollingFileAppender
- 롤오버 파일(Rollover files): 로그를 하나의 파일에 기록하다가, 특정 조건이 되면 새로운 출력 파일로 전환하는 방식
- 이러한 조건은 롤링 정책(rolling policies)으로 지정하며, 가장 많이 사용되는 것은 TimeBasedRollingPolicy로,
→ 매달, 매주, 매일, 매시간마다 새 로그 파일로 전환됩니다.
<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>
3. Logback Configuration
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>