Error Handling

77. [Error Handling] [JAVA] 디버깅 방법: System.out.println() 활용하기

천재단미 2025. 1. 15. 14:19
728x90
반응형

 

 

 

 

 

 

디버깅은 개발 과정에서 필수적으로 수행되는 중요한 작업입니다. 발생한 오류를 찾아내고 수정하는 과정에서 적절한 디버깅 기술을 사용하는 것이 중요합니다. 오늘은 초보자부터 숙련자까지 자주 사용하는 디버깅 도구인 **System.out.println()**에 대해 알아보겠습니다. 이 간단한 메서드는 프로그램의 실행 상태를 확인하고 문제를 파악하는 데 큰 도움을 줍니다.


 

 System.out.println()란?

 

System.out.println()은 Java에서 데이터를 콘솔에 출력하는 명령어입니다. 프로그램의 실행 중 특정 변수의 값이나 코드 흐름을 확인할 수 있습니다.

public class DebugExample {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        System.out.println("Before addition: a = " + a + ", b = " + b); // 디버깅 메시지
        int sum = a + b;
        System.out.println("After addition: sum = " + sum); // 디버깅 메시지
    }
}

 


1. System.out.println() 활용 방법

 

1-1. 변수 값 확인

코드 중간에 변수 값을 출력하면 특정 시점에서 변수에 저장된 값을 확인할 수 있습니다. 이는 의도하지 않은 값이 저장되는 문제를 쉽게 찾아내는 데 유용합니다.

public class DebugExample {
    public static void main(String[] args) {
        int x = 5;
        int y = 0;

        System.out.println("x = " + x + ", y = " + y); // 변수 초기 값 확인

        y = x * 2;
        System.out.println("Updated y = " + y); // 값 변경 확인
    }
}

 

1-2. 조건문 확인

조건문이 올바르게 동작하는지 확인하려면 조건이 실행되는 시점에 로그를 출력하면 됩니다.

public class DebugCondition {
    public static void main(String[] args) {
        int score = 85;

        if (score >= 90) {
            System.out.println("Grade: A");
        } else if (score >= 80) {
            System.out.println("Grade: B"); // 실행될 경우 확인
        } else {
            System.out.println("Grade: C or below");
        }
    }
}

 

1-3. 코드 흐름 추적

System.out.println()을 사용해 함수 호출, 반복문 동작 등을 추적할 수 있습니다.

public class DebugFlow {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            System.out.println("Loop iteration: " + i); // 반복문 상태 확인
        }
    }
}

 

 

 


2.  System.out.println() 사용 시 주의사항

 

2-1. 너무 많은 로그 출력 자제

지나치게 많은 로그를 출력하면 로그의 가독성이 떨어지고 디버깅에 혼란을 줄 수 있습니다. 적절한 위치에 필요한 로그만 추가하세요.

2-2. 개발 후 로그 제거

System.out.println()은 주로 개발 단계에서 사용되며, 배포 전에 모두 제거하는 것이 일반적입니다. 배포된 코드에 남겨두면 성능 저하나 보안 문제가 발생할 수 있습니다.

2-3. 로깅 라이브러리 고려

복잡한 프로젝트에서는 System.out.println() 대신 Log4j, SLF4J와 같은 전문 로깅 라이브러리를 사용하는 것이 더 효율적입니다.

 

 

로깅(Logging)이란?

로깅은 프로그램 실행 동작을 일련의 기록인 로그(Log)의 생성을 통해 남겨놓는 일을 말합니다. 로그는 재현하기 힘든 버그나 성능에 대한 통계 등, 프로그램 동작에 있어서 유용한 정보를 제공하기 때문에 로깅 작업은 실제 서비스 개발에 필수적인 부분입니다.

 

 


 

3.  문제점 

 

3-1. 성능 저하의 원인이 됩니다.

/**
 * Terminates the current line by writing the line separator string.  The
 * line separator string is defined by the system property
 *<code>line.separator</code>, and is not necessarily a single newline
 * character (<code>'\\n'</code>).
 */public void println() {
    newLine();
}

 

System.out.println()에서 println() 메서드를 보면 내부적으로 newLine() 메서드를 호출하는 것을 볼 수 있는데요.

 

private void newLine() {
    try {
        synchronized (this) {
            ensureOpen();
            textOut.newLine();
						...
        }
    }
		...
}

 

newLine() 메서드의 try 부분을 보면 synchronized 키워드가 사용된 것을 볼 수 있습니다.

synchronized는 메서드나 블록 코드에 동기화 영역을 표시하는 것으로, 동기화된 불록은 한 시점에 1개의 스레드만 접근이 가능하게 되는데요.

즉, 이때 블록에 접근을 시도하는 다른 스레드들은 블록 안의 스레드가 실행을 마치고 블록을 벗어날 때까지 블록(blocked) 상태(멀티 스레드의 동시 접근이 방지)가 됩니다.

다시 말해서 newLine() 메서드의 synchronized 키워드로 인해서 만약 System.out.println() 메서드를 여러 스레드가 사용하게 된다면 오버헤드가 발생하여 프로세스 처리가 늦어지게 되는 것입니다.

  • 오버헤드(Overhead) : 어떤 처리를 하기 위해 추가로 들어가는 처리 시간, 메모리 등의 컴퓨터 자원을 말합니다.

 

3-2.  로그 출력 레벨을 사용할 수 없습니다.

프로젝트 개발 단계에서는 디버깅을 위한 상세한 로그들을 출력하고 활용하는 경우가 많지만, 실제 프로덕션 환경에서 동작하는 코드의 경우에는 리소스의 낭비를 줄이기 위해 에러 및 장애가 발생할 때의 문제를 진단할 수 있는 로그만 남기는데요.

(모든 로그가 쌓였을 때, 문제 해결을 위해 정작 필요한 정보는 찾기 힘들고 의미 없는 로그가 쌓여 서버의 용량을 차지할 수 있습니다.)

때문에 여러 로깅 프레임워크는 프로그램 동작 환경(로컬 개발 환경, 개발 서버, 프로덕션 서버)에 맞는 로그가 출력될 수 있도록 로그 출력 레벨이라는 기능을 제공합니다.

log.trace("Trace Log Message");
log.debug("Debug Log Message");
log.info("Info Log Message");
log.warn("Warn Log Message");
log.error("Error Log Message");

 

로그 레벨은 TRACE > DEBUG > INFO > WARN > ERROR > FATAL이 있습니다.

(slf4j 같은 경우는 FATAL 레벨이 없습니다.)

하지만 System.out.println()의 경우는 **System.out.println()**을 사용한 인포메이션 로그와 **System.err.println()**을 사용한 에러 로그 두 가지로만 분류가 가능하기 때문에 로깅 프레임워크와 같은 레벨별 출력이 불가능한데요.

때문에 프로덕션 환경이 되었을 때 System.out.println()을 일일이 주석처리를 하거나 제거하는 작업이 필요하며, 그 과정에서 작업이 누락되는 등의 실수가 발생할 수 있습니다.

 

3-3. 에러 발생 시 추적할 수 있는 최소한의 정보가 남지 않습니다. (날짜, 시간, 문제 수준 등)

로깅 프레임워크를 사용했을 때 출력되는 로그(slf4j)

로깅 프레임워크를 사용했을 때는 기본적으로 에러 발생 날짜와 시간, 문제 수준, 발생 경로 등의 정보를 얻을 수 있지만 System.out.println() 메서드를 사용했을 때는 에러 발생 시 추적할 수 있는 최소한의 정보가 남지 않는다는 문제점이 있는데요.

남겨지는 정보 외에도 로그 메시지의 경우 개발자가 확인하기 쉽도록 공통적인 형태를 가지고 있는 것이 좋기 때문에 로깅 프레임워크를 사용했을 때는 지정되는 포맷이 있기 때문에 다른 설정 과정이 필요하지 않다는 장점도 있습니다.

 

 

4. 대안

로깅 프레임워크를 사용하는 것이 가장 효과적인 대안입니다. 대표적으로 SLF4J, Log4j, Logback 등이 있습니다. 이러한 로깅 프레임워크를 사용하면 다음과 같은 이점을 얻을 수 있습니다.

 

4-1. 로그 영구 저장

 로그를 파일로 저장하여 나중에 참조할 수 있습니다.

4-2. 상세한 메타데이터

시간, 로그 발생 위치 등의 정보를 자동으로 추가합니다.

4-3. 로그 레벨 설정

TRACE, DEBUG, INFO, WARN, ERROR, FATAL 등의 레벨을 사용하여 환경에 따라 로그 출력을 제어할 수 있습니다.

성능 최적화: 비동기 로깅 등의 기능을 통해 성능을 개선할 수 있습니다.

 

예를 들어,

SLF4J와 Logback을 사용한 로깅 코드는 다음과 같습니다.

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

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

    public void someMethod() {
        logger.info("This is an info message");
        logger.error("This is an error message");
    }
}

 

이러한 방식으로 로깅을 구현하면, System.out.println()의 문제점들을 해결하고 더 효과적인 로깅 시스템을 구축할 수 있습니다.

 

728x90
반응형
home top bottom
}