CodeGym /행동 /JAVA 25 SELF /로깅 기초: java.util.logging, Log4j, SLF4J

로깅 기초: java.util.logging, Log4j, SLF4J

JAVA 25 SELF
레벨 63 , 레슨 0
사용 가능

1. 왜 로깅이 필요한가

로깅 ≠ 콘솔 출력

프로그래밍을 막 시작했을 때는 문제를 찾기 위해 여기저기에 System.out.println("여기 있었다!")를 넣기만 해도 충분해 보입니다. 이 방법은 프로그램이 작고 내 컴퓨터에서만 실행될 때는 통합니다. 하지만 대규모 프로젝트, 24/7로 동작하는 서버, 수천 명이 사용하는 애플리케이션을 상상해 보세요 — 모든 사용자의 콘솔을 옆에서 지켜볼 수는 없겠죠?

로깅은 단순히 메시지를 출력하는 것이 아닙니다. 애플리케이션 동작에 대한 정보(오류, 경고, 비즈니스 이벤트, 기술적 세부사항)를 체계적으로 기록하는 것입니다. 로그는 파일이나 데이터베이스에 저장되거나 네트워크로 전송될 수 있으며 — 실행이 끝난 뒤는 물론 실행 중에도 프로그램의 동작을 분석할 수 있게 해 줍니다.

로그는 왜 필요한가

  • 문제 진단: 무언가 잘못되었을 때 로그는 가장 큰 도우미입니다. 어디서 왜 오류가 발생했는지 파악할 수 있습니다.
  • 감사와 보안: 로그는 사용자의 중요한 행동을 기록하여 사고를 조사하는 데 도움이 됩니다.
  • 디버깅: 어떤 버그는 특정 조건에서만 나타나며, 로그 없이는 잡기 어렵습니다.
  • 모니터링: 로그로 애플리케이션의 상태, 성능, ‘건강’을 추적할 수 있습니다.

현실의 예

비행기에 “블랙박스”(비행 로그)가 없다면 사고 원인을 조사하기가 거의 불가능할 것입니다. 프로그래밍에서 로그는 여러분의 블랙박스입니다.

2. 로깅의 기본 레벨

로깅에서는 레벨(levels)을 사용합니다. 신호등과 같습니다: 빨강 — 위험, 노랑 — 주의, 초록 — 이상 없음.

주요 레벨(가장 ‘시끄러운’ 것부터 가장 ‘조용한’ 것까지):

레벨 설명
ERROR
치명적 오류, 애플리케이션을 계속 실행할 수 없음
WARN
경고: 무언가 잘못되었지만 프로그램은 계속 동작
INFO
정상 동작에 대한 정보 메시지
DEBUG
디버깅을 위한 자세한 정보(주로 개발자에게만 표시)
TRACE
가장 상세한 정보 — 심층 진단용

예:

  • 사용자가 비밀번호가 틀려 로그인하지 못함 — WARN.
  • 데이터베이스가 고장남 — ERROR.
  • 애플리케이션 시작 — INFO.
  • 루프에서 변수 값을 출력 — DEBUG 또는 TRACE.

레벨 선택 방법

모든 것을 ERROR로 기록해서는 안 됩니다 — 그러면 진짜 오류를 놓치게 됩니다. 레벨을 의도적으로 사용하세요: 정말로 치명적인 장애만 오류로 표시하세요.

3. Java의 표준 로깅 (java.util.logging)

Java는 내장 로깅 시스템 — 패키지 java.util.logging(약칭 JUL) — 을 제공합니다. 별도 라이브러리를 추가하지 않아도 항상 사용할 수 있는 ‘기본’ 도구입니다.

주요 클래스:

  • Logger — 로그를 기록하는 핵심 클래스.
  • Level — 로깅 레벨 열거형(SEVERE, WARNING, INFO, CONFIG, FINE, FINER, FINEST).
  • Handler — 로그를 어디에 쓸지 지정(파일, 콘솔 등).
  • Formatter — 메시지 형식을 담당.

JUL 사용 예

import java.util.logging.Logger;
import java.util.logging.Level;

public class LoggingExample {
    // 클래스 이름으로 로거 가져오기
    private static final Logger logger = Logger.getLogger(LoggingExample.class.getName());

    public static void main(String[] args) {
        logger.info("애플리케이션이 시작되었습니다");
        logger.warning("경고입니다!");
        logger.severe("이건 오류입니다!");

        int x = 42;
        logger.fine("디버그 변수 x=" + x); // 기본 설정에서는 출력되지 않음
    }
}

중요: 기본적으로 JUL은 INFO 이상의 레벨만 출력합니다. fine 및 기타 상세 메시지를 보려면 로깅 레벨을 설정해야 합니다.

파일을 통한 구성

JUL은 logging.properties 파일(보통 JRE 폴더에 있음)로 설정할 수 있습니다. 여기에서 다음을 지정할 수 있습니다:

  • 로거의 최소 레벨
  • 로그를 기록할 대상(파일, 콘솔)
  • 메시지 포매팅

예시 설정 줄( logging.properties):

.level=INFO

4. 외부 로깅 라이브러리

Log4j (Apache)

Log4j는 Java에서 가장 잘 알려진 로깅 라이브러리 중 하나입니다. 유연하고 강력하며, 다양한 포맷, 비동기 기록, 파일 로테이션 등 많은 기능을 지원합니다.

Log4j 2의 가장 간단한 구성(XML) 예:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="info">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

코드에서 Log4j 2 사용 예:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Log4jExample {
    private static final Logger logger = LogManager.getLogger(Log4jExample.class);

    public static void main(String[] args) {
        logger.info("Hello from Log4j!");
        logger.error("Log4j를 통한 오류입니다");
    }
}

작동하려면 Log4j 의존성을 프로젝트에 추가해야 합니다(예: Maven 또는 Gradle).

SLF4J: 로깅 파사드

SLF4J(Simple Logging Facade for Java)는 독립적인 로깅 시스템이 아니라, 코드와 구체적 구현(Log4j, Logback, JUL 등) 사이의 ‘얇은 레이어’입니다.

파사드를 사용하면 코드를 한 번만 작성하고, 서로 다른 라이브러리가 다른 로거를 사용하더라도 코드를 고치지 않고 실제 로깅 시스템을 교체할 수 있습니다.

SLF4J 사용 예:

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

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

    public static void main(String[] args) {
        logger.info("안녕하세요, SLF4J로 로깅합니다!");
        logger.warn("주의: 문제가 생길 수 있습니다");
        logger.error("SLF4J를 통한 오류");
    }
}

동작 방식은?

  • 코드는 SLF4J API로 작성합니다.
  • 클래스패스에 원하는 구현체(예: Logback 또는 Log4j)를 추가합니다.
  • SLF4J가 호출을 선택한 구현체로 전달합니다.

SLF4J와 다른 로거의 연동: SLF4J는 JUL, Log4j, Logback 등 위에서 동작할 수 있어 코드를 다시 쓰지 않고도 손쉽게 마이그레이션할 수 있습니다.

5. 실습: System.out.println과 로깅 비교

예제 1: System.out.println

public class PrintlnExample {
    public static void main(String[] args) {
        System.out.println("애플리케이션이 시작되었습니다");
        System.out.println("오류 발생: 무언가 잘못되었습니다");
    }
}

문제점:

  • 메시지 레벨이 없음(오류, 정보, 경고 — 모두 동일).
  • 시간, 클래스 이름, 스레드 정보가 없음.
  • 모든 메시지가 한데 섞임.
  • 유연한 출력 제어 불가(예: 오류만 출력).

예제 2: SLF4J + Logback 로깅

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

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

    public static void main(String[] args) {
        logger.info("애플리케이션이 시작되었습니다");
        logger.error("오류 발생: 무언가 잘못되었습니다");
    }
}

장점:

  • 각 메시지에 시간, 레벨, 클래스 이름, 스레드가 함께 기록됩니다.
  • 레벨별로 메시지를 필터링할 수 있습니다.
  • 파일 기록, 네트워크 전송, 포맷팅, 아카이빙 등을 할 수 있습니다.
  • 로깅은 스레드 안전합니다(멀티스레드 애플리케이션에 중요).

차이 시연

System.out.println
로깅(SLF4J/Logback)
애플리케이션이 시작되었습니다 12:34:56 [main] INFO LoggingVsPrintln - 애플리케이션이 시작되었습니다
오류 발생: 무언가 잘못되었습니다 12:34:56 [main] ERROR LoggingVsPrintln - 오류 발생: 무언가 잘못되었습니다

6. 프로젝트에 로깅 추가하기

표준 로거(JUL) 사용 시

JDK에 이미 포함되어 있으므로 바로 사용할 수 있습니다:

import java.util.logging.Logger;

public class MyApp {
    private static final Logger logger = Logger.getLogger(MyApp.class.getName());

    public static void main(String[] args) {
        logger.info("정보 메시지입니다");
    }
}

Log4j 또는 SLF4J 사용 시

  1. 의존성을 추가 (예: Maven).

    Log4j 2:

    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-core</artifactId>
        <version>2.20.0</version>
    </dependency>
    

    SLF4J + Logback:

    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>2.0.7</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.4.7</version>
    </dependency>
    
  2. 구성 파일을 생성 (예: Logbacklogback.xml, Log4jlog4j2.xml).
  3. 코드에서 로거를 사용 (위 예시 참고).

7. 미니 애플리케이션: 로깅 추가

간단한 커맨드라인 계산기 애플리케이션이 있다고 가정해 봅시다. 여기에 로깅을 추가해 보겠습니다.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Scanner;

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

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        logger.info("계산기가 시작되었습니다");

        try {
            System.out.print("첫 번째 숫자를 입력하세요: ");
            int a = Integer.parseInt(scanner.nextLine());
            logger.debug("입력된 첫 번째 숫자: {}", a);

            System.out.print("두 번째 숫자를 입력하세요: ");
            int b = Integer.parseInt(scanner.nextLine());
            logger.debug("입력된 두 번째 숫자: {}", b);

            int sum = a + b;
            logger.info("합계: {}", sum);
            System.out.println("결과: " + sum);
        } catch (NumberFormatException ex) {
            logger.error("숫자 입력 오류", ex);
            System.out.println("오류: 올바른 숫자를 입력하세요!");
        } catch (Exception ex) {
            logger.error("알 수 없는 오류", ex);
            System.out.println("오류가 발생했습니다!");
        }
        logger.info("계산기 종료");
    }
}

여기서 무엇을 했나:

  • 모든 핵심 이벤트를 로그에 기록합니다.
  • 오류는 ERROR 레벨과 전체 스택 트레이스로 기록합니다.
  • 프로그램 실행 후에도 분석할 수 있도록 모든 정보가 남습니다.

8. 로깅에서 흔한 실수

오류 №1: 디버깅과 진단에 System.out.println 만 사용하는 것.
초기에는 편하지만 실서비스 애플리케이션에는 맞지 않습니다. 메시지 레벨을 제어할 수 없고, 시간 정보도 없으며, 프로그램이 서버에서 동작할 때 로그를 분석하기도 어렵습니다.

오류 №2: 너무 많은 정보를 ERROR 레벨로 기록.
모든 것을 오류로 기록하면 정말 중요한 것과 단지 디버깅 정보가 무엇인지 구분하지 못하게 됩니다.

오류 №3: 민감한 데이터 기록 (비밀번호, 토큰, 카드 번호).
로그는 안전해야 합니다! 남이 보면 안 되는 것을 기록하지 마세요.

오류 №4: 로깅 구성 부재.
레벨, 포맷, 저장 위치를 설정하지 않으면 — 원치 않는 방대한 로그에 ‘파묻히거나’ 아예 오류를 놓칠 수 있습니다.

오류 №5: 대형 프로젝트에서 로깅 파사드 (SLF4J) 를 사용하지 않는 것.
프로젝트가 커지면서 한 라이브러리에서 다른 라이브러리로 옮길 때 — 파사드가 없으면 매우 고통스럽습니다. SLF4J는 코드를 고치지 않고도 로깅 ‘엔진’을 쉽게 바꿀 수 있게 해 줍니다.

1
과제
JAVA 25 SELF, 레벨 63, 레슨 0
잠금
공장 엔지니어
공장 엔지니어
1
과제
JAVA 25 SELF, 레벨 63, 레슨 0
잠금
새로운 개발자
새로운 개발자
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION