1️⃣ 로깅 전체 구조 한 번에 정리
로깅은 3단 구조
[내 코드]
|
| (SLF4J API)
v
[로깅 인터페이스 (SLF4J)]
|
| (Binding)
v
[로깅 구현체 (Logback or Log4j2)]
|
v
[콘솔 / 파일 / 롤링파일 / ELK / DB 등]
✅ SLF4J = 표준 인터페이스 (API)
✅ Logback / Log4j2 = 실제 구현체 (엔진)
✅ 개발자는 SLF4J만 의존하고, 구현체는 바꿔 끼울 수 있게 설계
SLF4J (로깅 API / Facade)
✔️ 정체
- Simple Logging Facade for Java
- "로깅을 어떻게 부를지에 대한 표준 인터페이스"
- 실제로 로그를 찍지는 않음 (엔진 아님)
✔️ 존재 이유
"로깅 라이브러리를 바꿔도 수정 안하려고"
✔️ 개발자 입장에서의 역할
private static final Logger log = LoggerFactory.getLogger(MyService.class);
log.info("회원 가입 시작");
log.debug("userId={}", userId);
log.error("에러 발생", e);
이 코드는
- Logback을 쓰든
- Log4j2를 쓰든
절대 안 바뀜
✔️ 장점
- 로깅 구현체 교체 가능 (Logback ↔ Log4j2)
- 라이브러리마다 제각각인 로깅 API 통일
- 프레임워크/외부 라이브러리 충돌 줄어듬
LogBack (로깅 구현체)
✔️ 정체
- SLF4J 개발자가 만든 로깅 엔진
- Spring Boot 기본 로깅 구현체
✔️ 특징
- 설정이 비교적 단순
- Spring Boot랑 궁합이 매우 좋음
- 기본 제공 설정이 괜찮아서 "생각 없이 써도 평균 이상"
✔️ 설정 파일
- logback-spring.xml (Spring Boot 권장)
- logback.xml
✔️ 장점
- Spring Boot 기본 값
- 설정 쉬움
- 안정적
✔️ 단점
- Log4j2보다 고급 기능/성능 튜징 옵션은 적음
- 대규모 트래픽 환경에서 성능 차이를 느끼는 팀도 있음
Log4j2 (로깅 구현체)
✔️ 정체
- Apache 재단 로깅 엔진
- Log4j 1.x의 후속 (완전히 새로 만든 버전)
✔️ 특징
- 비동기 로직(Async Logger) 성능 좋음
- Appender, Filter, Routing 등 고급 기능 풍부
- 대형 서비스, 금융권, 트래픽 많은 시스템에서 선호
✔️ 설정 파일
- log4j2.xml
- log4j2-spring.xml (Spring Boot 용)
- JSON, YAML 도 가능
✔️ 장점
- 성능 좋음
- 기능 많음
- 로그 라우팅, 조건별 분기 등 고급 설정 가능
✔️ 단점
- 설정 복잡
- 초반 진입장벽 있음
2️⃣ Lombok (@Slf4j)
Spring / Spring Boot 프로젝트에서 로그를 찍을 때,
매 클래스마다 LoggerFactory.getLogger(클래스명.class) 를 직접 선언하는 대신 Lombok의 @Slf4j 어노테이션을 사용하면 로거 객체를 자동 생성할 수 있다.
이는 로깅 라이브러리가 추가로 생기는게 아니라,
SLF4J 로거 객체를 생성하는 보일러플레이트 코드를 줄여주는 편의 기능이다.
사용 방법
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MyService {
public void work() {
log.info("작업 시작");
log.debug("디버그 로그");
log.error("에러 발생");
}
}
내부 동작 원리
@Slf4j를 붙이면 Lombok이 컴파일 시점에 아래 코드를 자동 생성한다.
private static final org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(MyService.class);
즉,
- 개발자가 직접 작성하던 SLF4J Logger 선언 코드를
- Lombok이 대신 생성해주는 것 뿐이며
- 실제 로깅 처리(Logback, Log4j2 등)는 기존과 동일하다
구조 정리
@Slf4j
↓ (Lombok 컴파일 시 코드 생성)
private static final Logger log = LoggerFactory.getLogger(현재 클래스)
log.info("메시지");
↓ (SLF4J API 호출)
Logback 또는 Log4j2가 실제 로그 출력
왜 SLF4J 방식(@Slf4j)이 권장되는가?
1️⃣ 구현체 (Log4j2, Logback)에 코드가 종속되지 않음
아래와 같은 구현체 직접 사용 방식은 권장되지 않는다.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
private static final Logger log = LogManager.getLogger(MyService.class);
이 방식은 코드가 Log4j2에 종속되므로,
나중에 Logback 등 다른 구현체로 변경할 경우,
모든 클래스의 로거 선언 코드를 수정해야 하는 문제가 발생한다.
반면, SLF4J 방식은 구현체가 바뀌어도 코드 수정이 필요 없다.
private static final org.slf4j.Logger log =
org.slf4j.LoggerFactory.getLogger(MyService.class);
@Slf4j는 위 SLF4J 방식을 그대로 따르기 때문에
구현체 교체에 유연한 구조를 유지할 수 있다.
2️⃣ 프레임워크 및 라이브러리와의 호환성
Spring, Spring Boot 및 대부분의 오픈소스 라이브러리는
기본적으로 SLF4J를 기준으로 로깅을 제공한다.
따라서 애플리케이션 코드도 SLF4J 기준으로 통일하면
로깅 체계를 일관되게 유지할 수 있고,
브리지 설정이나 충돌 이슈를 최소할 수 있다.
3️⃣ 설정 변경만으로 구현체 교체 가능
코드는 SLF4J 기준으로 유지한 채,
환경(개발/운영)에 따라 로깅 구현체만 교체할 수 있다.
[Application Code] → SLF4J(API) → Logback 또는 Log4j2(구현체)
- 개발 환경: Logback
- 운영 환경: Log4j2
과 같이 설정과 의존성 변경만으로 전환 가능하다.
3️⃣ Maven 의존성 구성
코드는 SLF4J로 통일하고, 구현체(실제 출력 엔진)는 Logback 또는 Log4j2 중 하나만 선택한다.
Maven에서는 "내가 넣지 않아도 딸려오는(transitive) 로깅 의존성" 때문에 충돌이 자주 발생하므로, <exclude>로 정리하는 패턴을 알아두는게 핵심이다.
기본 개념 ( 왜 충돌이 나는가? )
- slf4j-api : 로그 API(인터페이스). log.info() 같은 호출은 여기서 정의됨.
- Logback / Log4j2 : 구현체(엔진). 실제로 콘솔/파일/롤링 출력 수행
- 충돌의 본질: SLF4J 호출을 "어느 구현체로 보낼지" 결정하는 바인딩(binding)이 2개 이상 들어오면 충돌한다.
✅ 바인딩은 딱 1개만 있어야 한다.
- Logback 사용 시 바인딩: logback-classic
- Log4j2 사용시 바인딩: log4j-slf4j2-impl
케이스 A : SLF4J + Logback ( Boot 기본 )
(A-1) Spring Boot면 보통 추가 작업 없음
spring-boot-start-web 같은 스타터에 기본 로깅(Logback)이 포함되어 동작한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
(A-2) 레거시 (Spring)에서 Logback 사용
<dependencies>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<!-- Logback 구현체 + SLF4J 바인딩 -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.16</version>
</dependency>
</dependencies>
logback-classic 이 SLF4J 바인딩 역할까지 포함된다.
케이스 B : SLF4J + Log4j2
(B-1) Spring Boot에서 Log4j2로 교체
Spring Boot 기본(Logback)을 제거하고 Log4j2 스타터를 추가한다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<!-- Boot 기본 로깅(Logback) 제거 -->
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- Log4j2 스타터 추가 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
(B-2) 레거시 (Spring)에서 Log4j2 사용
<dependencies>
<!-- SLF4J API -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<!-- Log4j2 구현체 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.24.1</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.24.1</version>
</dependency>
<!-- SLF4J -> Log4j2 바인딩 (핵심) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.24.1</version>
</dependency>
<!-- (선택) 웹 환경 초기화 도움 -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-web</artifactId>
<version>2.24.1</version>
<scope>runtime</scope>
</dependency>
</dependencies>
충돌이 나는 대표 케이스
Logback 바인딩 + Log4j2 바인딩이 같이 들어옴
- logback-classic 와
- log4j-slf4j2-impl
이 둘이 같이 있으면 "SLF4J 로그를 내가 받겠다"가 2개가 되어 충돌한다.
<exclusions>는 왜 쓰나?
Maven은 의존성을 추가하면 그 의존성을 다시 다른 의존성을 딸려오게(transitive dependency)한다.
그 과정에서 원치 않는 로깅 구현체/바인딩이 같이 들어와 충돌을 만들 수 있다.
그래서 "딸려온 로깅 의존성"을 제외하려고 <exclusions>를 쓴다.
예시) 어떤 라이브러리가 slf4j-simple을 끌고 올 때
<dependency>
<groupId>com.some</groupId>
<artifactId>some-lib</artifactId>
<version>1.0.0</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
</exclusions>
</dependency>
POM 구성 체크리스트
- 구현체를 무엇을 쓸지 경정 (Logback vs Log4j2)
- SLF4J 바인딩이 2개 이상 들어왔는지 확인
- "딸려온(transitive)" 로깅 의존성은 <exclusions>로 제거
- mvn dependency:tree 로 누가 무엇을 끌고 오는지 확인
- (WAR / WAS 환경) WAS 자체 lib에 로깅 jar가 있으면 WAR 내부와 충돌 가능 -> 서버별 동작 차이의 흔한 원인
4️⃣ xml 작성법 (log4j2.xml, logback.xml)
로깅 설정 파일의 정확한 파일명 규칙,
기본 XML 작성 구조,
설정 예시,
Log4j2와 Logback의 차이점 까지 정리한다.
설정 파일명 규칙 (대소문자 포함, 매우 중요)
✅ Log4j2
- 표준 파일명: log4j2.xml
- Spring Boot 권장 파일명: log4j2-spring.xml
- 주의:
- 리눅스 서버에서는 대소문자 구분 -> Log4j2.xml ❌
- 기본 규칙에 맞지 않으면 설정 파일을 로드하지 못함
✅ Logback
- 표준 파일명: logback.xml
- Spring Boot 권장 파일명: logback-spring.xml
- 주의:
- Boot에서 Spring 프로퍼티를 설정 파일 안에서 쓰려면 *-spring.xml 권장
커스텀 경로/파일명을 쓰고 싶으면 JVM옵션으로 지정 가능
(Log4j2 예: -Dlog4j.configurationFile=/applog/config/log4j2.xml)
공통 구조 개념
둘 다 경국 아래 3가지를 정의하는 설정 파일이다.
- Appender : 로그 출력 대상 (Console, File, RollingFile 등)
- Logger: 특정 패키지/클래스의 로그 레벨
- Root Logger: 전체 기본 로그 레벨
Log4j2 XML 작성법
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="LOG_PATH">/applog/myapp</Property>
<Property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %c - %m%n</Property>
</Properties>
<Appenders>
<Console name="CONSOLE" target="SYSTEM_OUT">
<PatternLayout pattern="${PATTERN}"/>
</Console>
<RollingFile name="FILE"
fileName="${LOG_PATH}/app.log"
filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd}-%i.log.gz">
<PatternLayout pattern="${PATTERN}"/>
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="CONSOLE"/>
<AppenderRef ref="FILE"/>
</Root>
<Logger name="com.myapp" level="DEBUG" additivity="false">
<AppenderRef ref="CONSOLE"/>
<AppenderRef ref="FILE"/>
</Logger>
</Loggers>
</Configuration>
1. <Configuration> (전체 설정 루트)
<Configuration status="WARN">
- Log4j2 설정 파일의 최상위 태그
- status="WARN"
- Log4j2 엔진이 자기 자신이 남기는 로그 레벨
- 설정 파일이 로드되지 않을 때 디버깅 용도로 INFO / DEBUG로 열려서 확인
1️⃣ status의 정체
👉 status는 "Log4j2 엔진 자기 자신이 남기는 로그 레벨" 이다.
- 애플리케이션 로그 레벨이 아님
- log.info(), log.error() 같은 업무 로그와는 전혀 다른 영역
- Log4j2가 설정 파일을 읽고, Appender를 초기화 하고, 에러가 났을 때
자기 내부 동작 과정을 콘솔에 찍는 로그의 레벨
즉,
status = "Log4j2 내부 디버깅 로그 레벨"
2️⃣ 왜 필요하냐? (언제 쓰는 옵션이냐?)
보통 로그가 안 찍히거나, 설정 파일이 안 먹을 때 원인 찾기가 제일 빡셈.
그럴 때:
- 설정 파일이 로딩은 됐는지?
- Appender 초기화하다가 에러 난 건지?
- 파일 경로 권한이 없어서 실패한 건지?
이걸 Log4j2 내부 로그로 확인하려고 쓰는게 status
👉 평소엔 거의 신경 안 써도 되고,
👉 문제 생겼을 때 디버깅용 스위치라고 보면 됨.
3️⃣ status 에 들어갈 수 있는 값(종류)
Log4j2의 로그 레벨 체계를 그대로 사용함.
| 값 | 의미 | 언제 쓰나 |
| OFF | Log4j2 내부 로그 전부 끔 | 거의 안 씀 |
| FATAL | 치명적 오류만 출력 | 잘 안 씀 |
| ERROR | 오류만 출력 | 설정 오류만 보고 싶을 때 |
| WARN | 경고 + 오류 | 기본값에 가까움 (무난) |
| INFO | 정보 + 경고 + 오류 | 설정 로딩 흐름 보고 싶을 때 |
| DEBUG | 디버그 정보까지 출력 | 설정 안 먹을 때 추적 |
| TRACE | 내부 동작 전부 출력 | 거의 디버깅용(로그 폭탄 주의) |
4️⃣ 실제로 뭐가 찍히는데?
예를 들어 status="DEBUG"로 두면 이런 로그가 콘솔에 나올 수 있음:
- "log4j2.xml 로딩 시작"
- "RollingFileAppender 초기화"
- "/applog/myapp 경로 접근 실패"
- "AppenderRef 매핑 완료"
- "이 설정 파일은 어디서 로딩됨"
👉 즉, "설정 파일이 제대로 적용됐는지"를 추적할 수 있는 내부 로그
5️⃣ status는 어디로 출력되나?
기본적으로:
- 표준 출력(stdout) 또는 표준 에러(stderr)
- WAS 환경에서는:
- JEUS:
- 보통:
- JeusServer.log
- 또는 JEUS 콘솔 로그 파일
- 실제로는 JEUS가 stdout/stderr를 자체 로그 파일로 받아서 기록함
- 보통:
- Tomcat
- 보통:
- catalina.out
- 또는 logs/ 디렉토리 안의 콘솔 로그
- 보통:
- JEUS:
즉, 애플리케이션 로그 파일(app.log)로 들어가는게 아니라,
서버 콘솔 로그 (또는 WAS 기본 로그) 쪽으로 찍힌다.
⚠️ 애플리케이션 로그 레벨이 아니라 "Log4j2 내부 로그 레벨"
2. <Properties> / <Property> (변수 선언)
<Property name="LOG_PATH">/applog/myapp</Property>
<Property name="PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%t] %c - %m%n</Property>
- 설정 파일 내에서 재사용할 상수/변수 정의
- ${LOG_PATH}, ${PATTERN} 형태로 참조
- 경로/패턴을 한 곳에서 관리 -> 유지보수 편함
3. <Apenders> (출력 대상 묶음)
<Appenders> ... </Appenders>
- Appender = 로그를 어디로 보낼지
- 콘솔, 파일, 롤링파일, 에러 전용 파일 등 정의
1️⃣ Appender란?
로그 메시지를 "어디에 저장/출력할지"를 결정하는 대상(출구)
우리는 코드에서 이렇게 로그를 찍는다.
log.info("회원 가입 시작");
이때 이 메시지는 "어디론가" 나가야 한다:
- 콘솔 화면?
- 파일?
- 일별로 쪼개진 파일?
- 에러 전용 파일?
- 원격 서버(ELK, Splunk)?
- DB?
👉 이 '어디로 보낼지'를 정하는게 Appender
2️⃣ Appender 종류
| Appender | 의미 |
| Console | 콘솔(stdout/stderr)로 출력 |
| File | 하나의 파일에 계속 기록 |
| RollingFile | 파일 크기/날짜 기준으로 분할 저장 |
| Async | 비동기 처리 (성능 향상) |
| Socket | 원격 서버로 전송 |
| JDBC | DB에 저장 (실무에서는 드묾) |
4. <Console> (콘솔 출력)
<Console name="CONSOLE" target="SYSTEM_OUT">
- name="CONSOLE"
-> Appender 식별자 (Logger에서 ref로 참조) - target="SYSTEM_OUT"
-> 표준 출력(stdout)- SYSTEM_ERR도 가능(에러 전용)
WAS 환경에서는 stdout/stderr가 WAS 로그 파일로 리다이렉트되는 경우가 많음
1️⃣ <Console> 이란?
- 로컬 실행 시 → 터미널 / IDE 콘솔 창
- WAS 실행 시 → WAS가 stdout/stderr를 모아 기록하는 기본 로그 파일
즉,
log.info("메시지") → 화면 (또는 서버 콘솔 로그)에 바로 찍히는 로그
2️⃣ 주요 속성
✅ name="CONSOLE"
<Console name="CONSOLE" ...>
- 이 Appender의 이름(식별자)
- Logger/Root에서 이렇게 참조됨:
<AppenderRef ref="CONSOLE"/>
👉 이름은 아무거나 가능(STDOUT, CONSOLE_APPENDER 등)
👉 단, AppenderRef의 ref와 정확히 일치해야 함
✅ target="SYSTEM_OUT"
<Console name="CONSOLE" target="SYSTEM_OUT">
콘솔 중에서도 "표준 출력(stdout)"인지, "표준 에러(stderr)"인지 선택
| 값 | 의미 |
| SYSTEM_OUT | 표준 출력 (stdout) |
| SYSTEM_ERR | 표준 에러 (stderr) |
3️⃣ stdout / stderr
운영체제/자바 프로그램에는 기본적으로 출력 통로가 2개가 있음:
- stdout(표준 출력)
- 일반적인 출력용
- System.out.println() 이 나가는 곳
- stderr (표준 에러)
- 에러/경고 출력용
- System.err.println() 이 나가는 곳
Log4j2에서는 이 둘 중 어디로 보낼지 target으로 고르는 것.
4️⃣ WAS 환경에서는 "콘솔"이 어디로 가나?
✔️ 로컬 실행
- Intellij / Eclipse 콘솔
- 터미널 화면
✔️ Tomcat
- catalina.out
(stdout/stderr가 이 파일로 모이는 경우가 많음)
✔️ JEUS
- JeusServer.log
(stdout/stderr를 JEUS 기본 로그로 수집)
👉 그래서 운영 서버에서는
<Console> 로그 = WAS 기본 로그 파일에 남는 로그
이 경우가 대부분임.
5. <PatternLayout> (로그 포맷)
<PatternLayout pattern="${PATTERN}"/>
패턴 문자열 해부:
| 패턴 | 의미 |
| %d{yyyy-MM-dd HH:mm:ss.SSS} | 로그 시각 |
| %-5p | 로그 레벨 (5칸 확보) |
| %t | 쓰레드명 |
| %C | 로거 이름 (클래스/패키지) |
| %m | 메시지 |
| %n | 줄바꿈 |
👉 "로그 한 줄이 어떻게 생길지"를 결정
1️⃣ %d{...} - 날짜/시간 (Date)
%d{yyyy-MM-dd HH:mm:ss.SSS}
- 로그가 찍힌 시간
- {} 안에는 날짜 포맷 지정
| 포맷 | 의미 |
| yyyy | 연도 |
| MM | 월 |
| dd | 일 |
| HH | 시(24시간) |
| mm | 분 |
| ss | 초 |
| SSS | 밀리초 |
👉 {} 를 생략하면 기본 포맷으로 찍힘
2️⃣ %p 또는 %level - 로그 레벨 (Priority / Level)
%p
또는 (Logback에서 자주 보임)
%level
- INFO, DEBUG, WARN, ERROR 같은 로그 레벨 출력
'%-5p' 의 의미
%-5p
- 5 : 5칸 확보
- - : 왼쪽 정렬
예)
- INFO → INFO (뒤에 공백)
- ERROR → ERROR
👉 로그가 컬럼처럼 정렬되어 보이게 만드는 용도
3️⃣ %t - 쓰레드 이름 (Thread)
- 로그를 찍은 쓰레드 이름
- 웹 서버에서 보면 보통:
- http-nio-8080-exec-1
- DefaultExecutor-3
- main
4️⃣ %c / %logger - 로거 이름 (클래스/패키지)
%c
또는
%logger
- 보통 클래스 전체 경로가 찍힘
- com.myapp.service.UserService
길이 제한
%c{36}
- 최대 36자까지만 표시
- Logback에서는 %logger{36}
5️⃣ %m / %msg - 로그 메시지
%m
또는
%msg
- log.info("회원 가입 시작"); 에서 "회원 가입 시작" 부분
6️⃣ %n - 줄바꿈 (newline)
%n
- 로그 한 줄 끝에 줄바꿈
- 이거 안 쓰면 로그가 한 줄로 쭉 이어져서 보기 지옥 됨
7️⃣ Log4j2 vs Logback 패턴 차이
| 개념 | Log4j2 | Logback |
| 레벨 | %p | %level |
| 로거 | %c | %logger |
| 메시지 | %m | %msg |
6. <RollingFile> (파일 + 롤링)
<RollingFile name="FILE"
fileName="${LOG_PATH}/app.log"
filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd}-%i.log.gz">
- fileName : 현재 쓰는 로그 파일
- filePattern : 롤링 시 생성될 이름 규칙
- %d{yyyy-MM-dd} : 날짜
- %i : 같은 날짜에 여러 개 생성 시 번호 증가
- .gz : 압축 저장
<RollingFile>만 있어도 파일로 정상 저장될까?
⭕️ <File> Appenrder는 따로 필요 없다.
<RollingFile name="FILE"
fileName="${LOG_PATH}/app.log"
filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd}-%i.log.gz">
이 설정 하나만으로도:
- 현재 로그는 → ${LOG_PATH}/app.log에 계속 쌓이고
- 롤링 시 → ${LOG_PATH}/app-2026-02-07-1.log.gz 처럼 분할 저장됨
👉 <RollingFile> 자체가 파일에 쓰는 Appender + 롤링 기능까지 포함항 "상위 개념"
7. <Policies> (언제 롤링할지)
<Policies>
<TimeBasedTriggeringPolicy interval="1"/>
<SizeBasedTriggeringPolicy size="100MB"/>
</Policies>
- 날짜 기준으로 롤링 + 용량 초과 시 추가 롤링
- 운영에서 가장 흔한 조합
1️⃣ TimeBasedTriggeringPolicy의 원리
<TimeBasedTriggeringPolicy interval="1"/>
이 정책은:
filePattern에 포함된 "시간 단위"가 바뀔 때 롤링한다.
즉, "하루 단위냐 / 한 시간 단위냐 / 한 달 단위냐"는
interval 이 아니라 filePattern의 %d{...} 포맷이 결정함
2️⃣ 하루 단위 롤링이 되는 이유
filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd}-%i.log.gz"
<TimeBasedTriggeringPolicy interval="1"/>
- %d{yyyy-MM-dd} → 날짜 단위
- interval="1" → 날짜가 1번 바뀔 때마다 롤링
3️⃣ 시간 단위로 바꾸면?
filePattern="${LOG_PATH}/app-%d{yyyy-MM-dd-HH}.log.gz"
<TimeBasedTriggeringPolicy interval="1"/>
- %d{yyyy-MM-dd-HH} → 시간 단위
- interval="1" → 1시간마다 롤링
4️⃣ modulate="true" 옵션
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
- 롤링 시점을 "정각/자정"에 맞춰줌
- 예:
- 서버가 10:23에 기동되어도
- 자정 (00:00)에 맞춰 롤링 발생
👉 운영에서는 modulate="true" 붙여주는 경우 많음
8. <DefaultRolloverStrategy> (보관 개수)
<DefaultRolloverStrategy max="30"/>
- 롤링 파일 최대 30개 유지
- 초과 시 오래된 로그부터 삭제
9. <Loggers> / <Root> (기본 로그 정책)
<Root level="INFO">
- 모든 로거의 기본 레벨 = INFO
- DEBUG/TRACE는 기본적으로 안 찍힘
레벨 (level)의 순서
TRACE < DEBUG < INFO < WARN < ERROR < FATAL
- TRACE : 아주아주 자세힌 내부 흐름 (거의 개발자 디버깅용)
- DEBUG : 디버깅에 필요한 상세 정보
- INFO : 정상 흐름에서 의미 있는 이벤트 (기동, 배치 완료, 주요 처리 시작/완료 등)
- WARN : 문제는 아니지만 "이상징후" (예: 리트라이, 느린 응답, 데이터 누락 가능성)
- ERROR : 기능 실패 / 예외 발생 (처리실패)
- FATAL : 시스템 수준 치명적 (프로세스 종료급) - 자바에서는 흔하지 않음
동작 규칙
설정한 레벨 "이상"만 출력된다.
예를 들어:
✅ level=INFO 이면
- 출력: INFO, WARN, ERROR (그리고 FATAL)
- 미출력: DEBUG, TRACE
흔한 레벨 사용 예시
TRACE
- "메서드 들어옴/나감", "루프 1회마다 값" 같은 초미세 로그
→ 운영 금지급
DEBUG
- 파라미터 값, 분기 선택, DB 조회 결과 건수, 외부 API 응답 요약
→ 문제 재현/원인 추적용
INFO
- "배치 시작/종료", "서버 기동 완료", "회원가입 완료(요약)"
→ 운영에서 기본으로 보는 로그
WARN
- "응답이 느린(타임아웃 직전)", "재시도 발생", "핈후 값 누락 가능성"
→ 당장 장애는 아니지만 관찰 필요
ERROR
- 예외로 인해 기능 실패, 트랙잭션 롤백, 외부 API 호출 실패
→ 장애 분석에 직접 쓰임
레벨과 성능
1. DEBUG가 운영에서 위험한 이유
- 로그량 폭증 → 디스크I/O 부담
- CPU 사용 증가 (포맷팅/출력 비용)
- 중요한 ERROR가 묻힘
2. 문자열 더하기는 조심
log.debug("result=" + result);
DEBUG가 꺼져 있어도 문자열 결합은 먼저 수행될 수 있음
✅ 권장(SLF4J 파라미터)
log.debug("result={}", result);
필요할 때만 치환됨(대부분의 구현체가 최적화)
10. <Logger> (패키지별 로그 레벨)
<Logger name="com.myapp" level="DEBUG" additivity="false">
- name : 패키지 / 클래스 범위
- level : 이 범위만 DEBUG 허용
- additivity="false" : Root로 전파 막아서 중복 로그 방지
우선순위
더 구체적인 Logger가 우선 적용된다.
<Root level="INFO" />
<Logger name="com.myapp" level="DEBUG" />
<Logger name="com.myapp.service" level="ERROR" />
| 클래스 위치 | 적용되는 레벨 |
| com.myapp.controller.* | DEBUG |
| com.myapp.service.* | ERROR |
| com.other.* | INFO (Root) |
11. <AppenderRef> (Appender 연결)
<AppenderRef ref="CONSOLE"/>
<AppenderRef ref="FILE"/>
- 어떤 Appender로 보낼지 지정
- ref는 Appender의 name과 동일해야 함
Logback XML 작성 법
<configuration>
<property name="LOG_PATH" value="/applog/myapp"/>
<property name="PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>10GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>${PATTERN}</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="FILE"/>
</root>
<logger name="com.myapp" level="DEBUG"/>
</configuration>
1. <configuration> (루트)
- Logback 설정의 최상위 태그
2. <property> (변수)
<property name="LOG_PATH" value="/applog/myapp"/>
- ${LOG_PATH} 형태로 참조
- Log4j2의 <Properties>와 동일한 역할
3. <appender> (출력 대상)
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- name: 식별자
- class: Appender 구현 클래스
- 콘솔 : ConsoleAppender
- 파일 롤링: RollingFileAppender
Appender 종류
| Appender | 의미 |
| ConsoleAppender | 콘솔 출력 |
| FileAppender | 파일 저장 |
| RollingFileAppender | 롤링 파일 |
| AsyncAppender | 비동기 출력 |
4. <encoder> / <pattern> (로그 포맷)
- Log4j2의 <PatternLayout>와 동일 개념
- Logback은 <encoder><pattern>구조
패턴 의미는 동일:
- %d, %level, %thread, %logger, %msg, %n
5. <rollingPolicy> (롤링 정책)
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
- 날짜 기준 롤링
- <maxHistory>: 보관 개수
- <totalSizeCap> : 전체 로그 용량 제한
6. <root> (기본 로그 정책)
<root level="INFO">
- 모든 로그 기본 레벨
7. <logger> (패키지별 레벨)
<logger name="com.myapp" level="DEBUG"/>
- 특정 패키지만 DEBUG 허용
- Appender를 따로 지정하지 않았기 때문에
- 기본적으로 Root에 붙어있는 appender를 그대로 따라감 (상속)
- Logback에서 이 상속이 바로 additivity(기본값 true)
Log4j2 vs Logback 핵심 차이 요약
| 항목 | Log4j2 | Logback |
| 설정 난이도 | 상대적으로 높음 | 비교적 쉬움 |
| 성능/기능 | 고급 기능 많음 | 기본 기능 위주 |
| Spring Boot 기본 | ❌ | ⭕ |
| XML 문법 | <Configuration>, <Appenders> | <configuration>, <appender> |
| 패턴 | <PatternLayout> | <encoder><pattern> |
'Spring' 카테고리의 다른 글
| [Spring] @Bean, 싱글톤, ThreadLocal (0) | 2022.09.04 |
|---|---|
| [Spring] 프록시 패턴, 데코레이션 패턴 (0) | 2022.08.29 |
| [SpringBoot] 컴포넌트스캔 (0) | 2022.08.29 |
| [Spring] Spring MVC 프로젝트 XML로 초기 세팅하기 (1) | 2022.01.02 |
| [Spring] Missing artifact javax.servlet.jsp.jstl:jstl:jar:1.2 오류 (0) | 2022.01.02 |