본문 바로가기

JAVA

Logger , LoggerFactory 를 사용하는 이유

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 넣어두면 다중 서비스 추적 쉬움.
    import org.slf4j.MDC;
    MDC.put("reqId", requestId);
    log.info("processing...");
    MDC.clear();
    
    그리고 logback-spring.xml 패턴에 %X{reqId} 추가:
    <property name="FILE_PATTERN"
              value="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %X{reqId} %logger{36} - %msg%n"/>