- 스프링 프로젝트 작성 시 디버깅을 위해
System.out.println()을 사용함- 하지만 운영 환경에서는 반드시 로깅 라이브러리를 통해 로그를 남겨야 함
로그 레벨로 출력 범위 조절 가능 (예 : 운영 환경에서는 info만, 개발 환경은 debug 까지)
출력 포맷, 시간, 쓰레드명, 클래스명 등 부가 정보 자동 포함
콘솔 외에도 파일, 네트워크, 원격 서버 등에 로그 저장 가능
성능도 더 좋음(버퍼링, 비동기 등)
로그 분할(일자/용량 기준 분할 저장 등) 가능
SLF4J(Simple Logging Facade for Java) : 인터페이스Logback : 실제 구현체(기본 설정)
SLF4J는 여러 로깅 라이브러리(Log4j, Logback 등)를 추상화해서 통합해주는 역할
package hello.springmvc.basic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogTestController {
private final Logger log = LoggerFactory.getLogger(getClass());
@RequestMapping("/log-test")
public String logTest() {
String name = "Spring";
log.trace("trace log={}", name);
log.debug("debug log={}", name);
log.info(" info log={}", name);
log.warn(" warn log={}", name);
log.error("error log={}", name);
// 잘못된 사용법: 로그 출력 여부와 관계없이 문자열 더하기 연산 발생
log.debug("String concat log=" + name);
return "ok";
}
}
TRACE > DEBUG > INFO > WARN > ERROR
# 전체 로그 레벨 (기본: info)
logging.level.root=info
# 특정 패키지 로그 레벨 설정
logging.level.hello.springmvc=debug
log.debug("data=" + data);
log.debug("data={}", data);
@Slf4j
public class LogTestController {
@RequestMapping("/log-test")
public String logTest() {
log.info("hello");
return "ok";
}
}
LoggerFactory.getLogger()를 직접 작성할 필요 없이 간편하게 사용 가능lombok 의존성이 필요함
- 스프링 부트는 기본적으로
application.properties나application.yml을 통해 간단한 설정이 가능하지만,- 보다 세부적인 설정을 원할 경우에는
logback-spring.xml파일을 사용해 커스터마이징할 수 있음
src/main/resource 폴더 안에 생성logback-spring.xml로 해야 Spring Boot에서 프로파일을 인식함 !Logback이라는 로깅 시스템의 XML 설정 파일<?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} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 파일 출력 (일별로 생성) -->
<appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory> <!-- 최근 30일치 보관 -->
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 전체 로그 레벨 설정 -->
<root level="INFO">
<appender-ref ref="Console" />
<appender-ref ref="File" />
</root>
</configuration>
<appender name="Console" class="ch.qos.logback.core.ConsoleAppender">
Console이라는 이름의 로그 출력기(appender) 정의<appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender">
File이라는 이름의 파일 출력용 Appenderlogs/app.log 파일에 기록함<file>logs/app.log</file> : 기본 로그 파일 경로 <encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
encoder - pattern : 로그 메시지를 출력할 형식을 지정하는 부분%d{yyyy-MM-dd HH:mm:ss} → 로그 발생 시간[%thread] → 실행 중인 쓰레드 이름 %-5level → 로그 레벨 (INFO, ERROR 등)%logger{36} → 로거 이름(보통 클래스 이름)%msg → 실제 로그 메시지%n → 줄 바꿈<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
logs/app.2025-08-03.log 이런 식으로 생성됨maxHistory=30이면 최근 30일치만 보관하고, 이전 것은 자동 삭제됨<encoder>
<pattern>...</pattern>
</encoder>
<root level="INFO">
<appender-ref ref="Console" />
<appender-ref ref="File" />
</root>
- 동기 방식의 로그는 성능에 영향을 줄 수 있음
- 실무에서는 비동기 로그 처리를 통해 성능을 개선할 수 있음
[비즈니스 로직 수행] → [로그 출력] → [다음 작업]
[비즈니스 로직 수행] → [로그 큐에 넣음] → [다음 작업]
↘ [백그라운드에서 기록]
<appender name="AsyncFile" class="ch.qos.logback.classic.AsyncAppender">
<!-- 비동기 처리할 실제 대상 Appender -->
<appender-ref ref="File"/>
<!-- 큐가 꽉 찼을 때 어떻게 할지 설정 (기본 false면 로그 손실 가능) -->
<discardingThreshold>0</discardingThreshold>
<!-- 로그 큐 최대 크기 (기본: 256) -->
<queueSize>512</queueSize>
<!-- 예외 발생 시 즉시 출력할지 여부 -->
<includeCallerData>true</includeCallerData>
</appender>
| 속성 | 설명 |
|---|---|
appender-ref | 실제 로그 출력을 담당할 Appender를 지정 (File, Console 등) |
discardingThreshold | 로그 큐가 꽉 찼을 때 버릴 로그 레벨 기준 (0이면 전부 보존 시도) |
queueSize | 로그를 임시 저장할 큐 크기 (기본 256개, 많을수록 메모리 사용 증가) |
includeCallerData | 로그 발생한 클래스/메서드 정보까지 포함할지 여부 (true 추천) |
<configuration>
<!-- 실제 파일 로그 Appender -->
<appender name="File" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/app.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 비동기 로그 Appender (File을 감싸서 비동기 처리) -->
<appender name="AsyncFile" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="File"/>
<queueSize>512</queueSize>
<discardingThreshold>0</discardingThreshold>
<includeCallerData>true</includeCallerData>
</appender>
<!-- 루트 로그 설정: AsyncFile 사용 -->
<root level="INFO">
<appender-ref ref="Console" />
<appender-ref ref="AsyncFile" />
</root>
</configuration>
| 상황 | 설정 방법 |
|---|---|
| 트래픽이 많고 I/O 병목이 있을 때 | AsyncAppender로 감싸기 |
| 로그 손실이 나면 안 될 때 | discardingThreshold=0, queueSize 늘리기 |
| 로그가 너무 많아서 메모리 부담될 때 | 큐 크기 조절 또는 로그 레벨 조정 |
| 항목 | 권장 방식 |
|---|---|
| 로그 출력 방식 | log.info("data={}", data) |
| 로그 선언 방식 | @Slf4j (Lombok 활용) |
| 로그 저장 | logback-spring.xml로 파일 저장 설정 |
| 비동기 처리 | AsyncAppender 사용 |
| 운영 vs 개발 | 운영: info 이상, 개발: debug 이상 |