Programming/Java

로깅시 System.out.println() 사용을 지양해야 하는 이유

Jan92 2022. 11. 16. 01:17
반응형

System.out.println("Hello World");

Java 프로그래밍을 처음 접하게 되면 거의 대부분이 다음과 같이 "Hello World"를 콘솔에 출력하는 것에서부터 시작하게 됩니다.

이때 사용되는 출력 방법인 System.out.println() 메서드는 자바 표준 입력 클래스인 System을 사용하기 때문에 클래스 경로에 추가해야 될 라이브러리나 추가 구성이 필요 없기 때문에 간편하고 쉽게 사용할 수 있다는 장점이 있는데요.

하지만 프로덕션 코드(Production Code)에서는 System.out.println() 메서드를 통한 출력이 지양되는데, 그 이유는 무엇인지 살펴보겠습니다.

 

/*

로깅(Logging)이란?

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

*/

 

 

System.out.println() 메서드를 사용했을 때의 문제점

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) : 어떤 처리를 하기 위해 추가로 들어가는 처리 시간, 메모리 등의 컴퓨터 자원을 말합니다.

 

 

 

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

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

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

 

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

 

 

 

< 참고 자료 >

https://hudi.blog/do-not-use-system-out-println-for-logging/

https://www.baeldung.com/java-system-out-println-vs-loggers

반응형