Logger , LoggerFactory
— logger랑 LoggerFactory는 로그(log) 를 남기기 위한 핵심 객체
자바에서 System.out.println() 대신 프로덕션용으로 로그를 관리하기 위한 구조
1. Logger란?
Logger는 로그를 찍는 역할을 하는 객체
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyService {
private static final Logger logger = LoggerFactory.getLogger(MyService.class);
public void run() {
logger.info("서비스 시작");
logger.debug("디버그 정보");
logger.error("에러 발생!");
}
}
2. LoggerFactory란?
이건 Logger 객체를 생성해주는 클래스
즉, LoggerFactory.getLogger(클래스명)으로 로그 객체를 만들어준다
- LoggerFactory는 보통 SLF4J(Simple Logging Facade for Java) 라이브러리에서 제공
- LoggerFactory는 내부적으로 Logback, Log4j, java.util.logging 같은 구현체를 연결해준다.
즉, SLF4J는 로깅 인터페이스,
Logback/Log4j는 로깅 구현체이다.
3. logger 사용 예시
public void processData(String data) {
logger.info("데이터 처리 시작: {}", data);
try {
int result = Integer.parseInt(data);
logger.debug("변환 결과 = {}", result);
} catch (NumberFormatException e) {
logger.error("숫자 변환 실패: {}", data, e);
}
}
로그 레벨
레벨 설명 사용 예시
| trace | 아주 상세한 내부 흐름 | 루프 내부 디버깅 |
| debug | 개발 중 디버깅용 | 파라미터, 흐름 추적 |
| info | 정상 동작 정보 | 시작/종료, 상태 메시지 |
| warn | 경고 상황 | 잠재적 문제, 복구 가능한 오류 |
| error | 오류 상황 | 예외, 실패 등 |
4. 왜 System.out.println 대신 쓰나?
구분 System.out.println Logger
| 출력 위치 | 콘솔만 | 콘솔, 파일, 외부 서버 등 다양 |
| 로그 레벨 | 없음 | debug/info/warn/error 등 지원 |
| 비활성화 | 불가능 | 설정 파일로 출력 레벨 조절 가능 |
| 성능 | 느림 | 비동기 로깅 가능(Logback) |
| 유지보수 | 어렵다 | 로그 포맷, 타임스탬프 자동 관리 |
5. 사용 구조 요약
Logger logger = LoggerFactory.getLogger(클래스명);
→ LoggerFactory가 내부에서 적절한 로깅 프레임워크(Logback 등)에 연결
→ logger 객체로 info(), debug(), error() 호출하여 로그 남김
한줄 요약
Logger = 로그를 찍는 도구
LoggerFactory = 로그 객체(Logger)를 만들어주는 공장
보통 SLF4J 인터페이스를 통해 Logback이나 Log4j 같은 구현체와 연결된다.
그렇다면 그냥 스프링 부트를 실행하면 터미널에 뜨는 로그로는 부족할까 ?
1️⃣ 스프링 부트가 기본으로 띄우는 로그는 “프레임워크 로그”
스프링 부트 실행 시 터미널에 뜨는 이런 로그는
2025-11-11 22:35:10.123 INFO 12345 --- [ main] o.s.b.SpringApplication : Starting ...
2025-11-11 22:35:11.456 INFO 12345 --- [ main] o.s.b.SpringApplication : Started ...
이건 스프링 프레임워크 내부 코드가 찍는 로그
→ 즉, Spring Boot / Hibernate / Tomcat 같은 라이브러리들이 자기 동작상태를 기록하는 것
(개발자)가 작성한 비즈니스 코드의 로그는 여기에 포함되지 않음.
2️⃣ Logger / LoggerFactory는 “내 코드의 로그”
LoggerFactory를 통해 생성한 Logger는
👉 내 클래스 내부에서 무슨 일이 일어나는지 기록하는 도구
예시:
@Service
public class OrderService {
private static final Logger log = LoggerFactory.getLogger(OrderService.class);
public void placeOrder(Order order) {
log.info("주문 시작: {}", order.getId());
// 비즈니스 로직...
log.info("주문 완료: {}", order.getId());
}
}
이렇게 하면 스프링의 로그에 내 애플리케이션 동작 로그가 합쳐져서 찍힌다.
즉,
- 스프링 내부 로그 +
- 내가 남긴 애플리케이션 로그
이 같은 로깅 시스템(Logback)을 통해 함께 출력된다
3️⃣ LoggerFactory를 안 쓰면?
- 네 코드에서는 System.out.println()밖에 쓸 방법이 없음.
- 이건 로그 파일에 기록되지 않음.
- 운영 서버에선 콘솔 로그가 사라지거나 관리가 안 됨.
- 로그 레벨(Info, Debug, Error 등) 제어 불가능.
- 배포 시 필터링이나 포맷 일관성도 없음.
결국,
LoggerFactory를 안 쓰면 “내 코드의 상태를 로그 시스템에 정식으로 남길 수 없다.”
4️⃣ 실제 차이 요약
구분 스프링 기본 로그 LoggerFactory 사용
| 발생 주체 | 프레임워크(Spring, Hibernate 등) | 개발자(내 코드) |
| 목적 | 시스템 동작 알림 | 비즈니스 로직 상태 추적 |
| 제어 가능 여부 | 일부 가능 (application.yml) | 완전 제어 가능 (레벨, 포맷, 파일 경로 등) |
| 출력 위치 | 콘솔(default) | 콘솔 + 파일 + 외부 서버 |
| 형식 | 스프링 내부 포맷 | 동일 포맷으로 통합 관리 가능 |
5️⃣ 실제 운영에서의 구조
운영환경에서는 모든 로그를 한 시스템(Logback 등) 으로 통합 관리를 한다
📁 예시
logs/
├─ spring.log ← 스프링 내부 로그
├─ app-info.log ← 내 코드의 info 로그
├─ app-error.log ← 내 코드의 error 로그
→ ELK(Stack)나 Grafana Loki, CloudWatch 등으로 수집·모니터링함.
결론
스프링이 띄우는 로그는 “프레임워크 로그”.
LoggerFactory를 써야 “내 코드 로그”를 공식 로깅 시스템에 통합할 수 있음.
logger 사용 예시
1) application.yml에서 로깅 레벨·포맷 기본 설정
간단한 레벨 제어/포맷/콘솔 패턴은 application.yml로 충분.
여러 파일로 분리(Info/Warn/Error) 하고 롤링하려면 → logback-spring.xml 이 필요.
# src/main/resources/application.yml
spring:
profiles:
active: local
logging:
level:
root: INFO
org.springframework.web: INFO
org.hibernate.SQL: WARN
com.myapp: DEBUG # 내 코드 패키지
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"
file:
name: logs/spring.log # 단일 파일 출력(간단용)
2) logback-spring.xml로 로그 파일 분리 + 롤링 + 비동기
INFO 이상은 app-info.log, ERROR 이상은 app-error.log 로 분리 저장.
날짜/크기 롤링, 콘솔 출력, 비동기 처리까지 포함.
<!-- src/main/resources/logback-spring.xml -->
<configuration scan="true" scanPeriod="60 seconds">
<property name="LOG_PATH" value="logs"/>
<property name="APP_NAME" value="myapp"/>
<!-- 콘솔 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 공통 패턴 -->
<property name="FILE_PATTERN"
value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n"/>
<!-- INFO 이상 (INFO/WARN/ERROR) -->
<appender name="APP_INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}-info.log</file>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>INFO</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<encoder><pattern>${FILE_PATTERN}</pattern></encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}-info.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- ERROR 이상만 별도 파일 -->
<appender name="APP_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_PATH}/${APP_NAME}-error.log</file>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>ERROR</level>
</filter>
<encoder><pattern>${FILE_PATTERN}</pattern></encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/${APP_NAME}-error.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>20MB</maxFileSize>
<maxHistory>60</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
</appender>
<!-- 비동기(성능) -->
<appender name="ASYNC_INFO" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>8192</queueSize>
<discardingThreshold>0</discardingThreshold>
<appender-ref ref="APP_INFO"/>
</appender>
<appender name="ASYNC_ERROR" class="ch.qos.logback.classic.AsyncAppender">
<queueSize>2048</queueSize>
<appender-ref ref="APP_ERROR"/>
</appender>
<!-- 패키지별 레벨 -->
<logger name="org.springframework" level="INFO"/>
<logger name="org.hibernate" level="WARN"/>
<logger name="com.myapp" level="DEBUG"/>
<!-- 루트: 콘솔 + 파일 -->
<root level="INFO">
<appender-ref ref="CONSOLE"/>
<appender-ref ref="ASYNC_INFO"/>
<appender-ref ref="ASYNC_ERROR"/>
</root>
</configuration>
3) System.out.println → logger 전환 예시
문자열 덧셈 금지, 플레이스홀더 사용이 핵심.
예외는 logger.error("...", e)로 붙여야 스택트레이스가 찍힘.
(Before)
public void createUser(String name) {
System.out.println("create user: " + name);
try {
// ...
} catch (Exception e) {
System.out.println("error: " + e.getMessage());
e.printStackTrace();
}
}
(After)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class UserService {
private static final Logger log = LoggerFactory.getLogger(UserService.class);
public void createUser(String name) {
log.info("Create user start: {}", name);
try {
// ... 비즈니스 로직
log.debug("DB insert ready. name={}", name);
log.info("Create user done: {}", name);
} catch (Exception e) {
log.error("Create user failed. name={}", name, e);
}
}
}
레벨 가드(비싼 메시지 만들 때)
if (log.isDebugEnabled()) {
log.debug("Heavy debug payload: {}", expensiveToBuild());
}
4) 실무 팁 (꼭 지키면 좋음)
- 민감정보(비번/주민번호/토큰) 로그 금지 → 마스킹/필터 적용.
- 플레이스홀더 {} 사용 (문자열 + 연산은 비용↑, 레벨 OFF여도 연산 발생).
- 에러는 반드시 logger.error("msg", e) 로 스택트레이스 남기기.
- 패키지별 레벨 관리: 개발 중 com.myapp: DEBUG, 운영 INFO.
- MDC(요청 추적): 트레이스/요청ID 넣어두면 다중 서비스 추적 쉬움.
그리고 logback-spring.xml 패턴에 %X{reqId} 추가:import org.slf4j.MDC; MDC.put("reqId", requestId); log.info("processing..."); MDC.clear();<property name="FILE_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %X{reqId} %logger{36} - %msg%n"/>
'JAVA' 카테고리의 다른 글
| 상속 extends 와 규약 interface 의 차이 (0) | 2025.11.12 |
|---|---|
| abstract 추상 클래스 or 추상 메서드 (0) | 2025.11.12 |
| @Configuration 과 xml 과 yml 설정 파일의 차이 (1) | 2025.11.12 |
| @SuppressWarnings 어노테이션 (0) | 2025.11.12 |
| JDK, JRE ,JVM (0) | 2025.08.28 |