Java/일반

Java Logging

창욱씨 2020. 4. 3. 14:30
반응형

1. Java Logging 개요

Java Logging은 애플리케이션의 실행 이벤트, 오류, 디버깅 정보를 기록하는 메커니즘입니다.

  • ✅ 문제 진단 : 운영 중 발생한 오류 추적
  • ✅ 모니터링 : 시스템 상태 실시간 파악
  • ✅ 감사(Audit) : 보안 이벤트 및 사용자 행동 기록

2. 주요 로깅 프레임워크

프레임워크 역할 특징
java.util.logging (JUL) 구현체 JDK 기본 제공, 별도 의존성 없음
Log4j 2 구현체 고성능, 비동기 로깅 지원
Logback 구현체 SLF4J 네이티브 구현체, Spring Boot 기본
SLF4J 추상화(Facade) 인터페이스만 제공, 직접 출력 불가

3. 로그 레벨 상세 설명

로그 레벨은 메시지의 중요도를 나타내며, 설정된 레벨 이상의 로그만 출력됩니다.

예) 레벨을 INFO로 설정 → INFO, WARN, ERROR, FATAL만 출력 (TRACE/DEBUG 무시)

🔹 레벨 계층 구조 (낮음 → 높음)

TRACE  <  DEBUG  <  INFO  <  WARN  <  ERROR  <  FATAL
(상세)                                          (치명)

🔹 각 레벨 상세

레벨 용도 사용 예시 권장 환경
TRACE 가장 상세한 흐름 추적 메서드 진입/종료, 루프 내부 변수값 개발, 특정 이슈 디버깅
DEBUG 개발 중 디버깅 정보 SQL 쿼리, 파라미터 값, 분기 조건 개발/스테이징
INFO 정상적인 주요 이벤트 앱 시작, 사용자 로그인, 주문 완료 운영 (기본값)
WARN 잠재적 문제 경고 Deprecated API, 재시도 발생, 임계치 근접 운영
ERROR 오류 발생 (앱은 계속 실행) 외부 API 실패, DB 저장 실패 운영 (모니터링 필수)
FATAL 치명적 오류 (앱 중단 수준) 커넥션 풀 고갈, 메모리 부족 운영 (즉시 알림)

⚠️ 주의: FATAL은 Log4j/Log4j2 전용입니다.
SLF4J와 Logback에는 FATAL이 없으며, ERROR로 통합되어 있습니다.

🔹 레벨별 코드 예시

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class PaymentService {
    private static final Logger logger = LoggerFactory.getLogger(PaymentService.class);

    public void pay(String userId, int amount) {
        logger.trace("pay() 메서드 진입 - userId={}, amount={}", userId, amount);
        logger.debug("결제 요청 데이터 검증 중: userId={}", userId);

        if (amount > 1_000_000) {
            logger.warn("고액 결제 감지: userId={}, amount={}", userId, amount);
        }

        try {
            // 결제 처리 로직
            logger.info("결제 성공: userId={}, amount={}", userId, amount);
        } catch (PaymentException e) {
            logger.error("결제 실패: userId={}", userId, e);
        } catch (OutOfMemoryError e) {
            // Log4j2에서는 logger.fatal(...) 사용 가능
            logger.error("[FATAL] 시스템 자원 고갈, 즉시 조치 필요", e);
        }
    }
}

🔹 레벨 선택 가이드라인

🟢 INFO를 써야 할 때

  • 사용자가 사후에 봤을 때 의미 있는 비즈니스 이벤트
  • 예: "사용자 가입 완료", "배치 작업 시작/종료"

🟡 WARN을 써야 할 때

  • 당장 문제는 없지만 방치하면 ERROR로 발전할 수 있는 상황
  • 예: "캐시 미스율 80% 초과", "재시도 3회 중 1회 실패"

🔴 ERROR를 써야 할 때

  • 비즈니니스 로직이 정상 완료되지 못한 경우
  • 반드시 예외 객체를 함께 전달: logger.error("메시지", exception)
  • 운영 환경에서는 알림(Slack, PagerDuty 등) 과 연동 권장

🔵 DEBUG/TRACE 사용 시 주의

  • 운영에서 활성화하면 성능 저하 + 디스크 폭증 발생
  • 반드시 Parameterized 형태({})로 작성
  • 운영에서 임시로 켤 때는 특정 패키지만 한정해서 적용
<!-- 운영 중 특정 패키지만 DEBUG로 임시 변경 -->
<configuration>
    <logger name="com.example.payment" level="DEBUG"/>

    <root level="INFO">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

🔹 환경별 권장 레벨 설정

환경 권장 레벨 이유
로컬 개발 DEBUG 모든 동작 확인
개발 서버(DEV) DEBUG 통합 디버깅
스테이징(STG) INFO 운영과 동일 조건 검증
운영(PROD) INFO 또는 WARN 성능·디스크 보호

4. 핵심 구성 요소

구성 요소 설명
Logger 로그 생성 주체 (보통 클래스 단위)
Appender 출력 대상 (Console, File, DB, Kafka 등)
Layout / Encoder 출력 형식 정의 (시간, 스레드, 메시지 등)
Filter 조건부 필터링
MDC 요청 추적 컨텍스트 (예: requestId)

5. SLF4J가 다른 프레임워크와 다른 점

💡 SLF4J는 "구현체"가 아니라 "인터페이스(Facade)"입니다.

🔹 구조

[애플리케이션]
     ↓ SLF4J API 호출
  [ SLF4J ]   ← 추상화 계층 (Facade)
     ↓ 바인딩
[Logback / Log4j2 / JUL]   ← 실제 구현체

🔹 비유로 이해하기

  • SLF4J = JDBC API
  • Logback / Log4j2 = MySQL / Oracle 드라이버

🔹 SLF4J의 장점

  1. 구현체 교체 자유 — 의존성만 바꾸면 코드 수정 불필요
  2. Parameterized Logging — logger.info("user: {}", name) 형태로 성능·안전성 확보
  3. 라이브러리 호환성 — Spring, Hibernate, Kafka 등 표준 채택
  4. 레거시 브리지 지원 — log4j-over-slf4j, jcl-over-slf4j 등

🔹 의존성 예시 (Maven)

<!-- SLF4J API -->
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-api</artifactId>
    <version>2.0.13</version>
</dependency>

<!-- 구현체: Logback -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.5.6</version>
</dependency>
반응형