IT·테크/개발

Java System.out·System.err를 SLF4J 로거로 리다이렉트하기

레루루 2026. 7. 29. 23:03
반응형

이 글은 2023년 2월 기준 기록입니다.

 

System.out.println으로 찍은 출력은 콘솔에만 남습니다. 로깅 체계를 갖춘 애플리케이션이라면 이 출력들이 로그 파일 바깥으로 새어 나가는 셈입니다. 표준 출력 스트림 자체를 로거로 갈아 끼우면 이 구멍을 막을 수 있습니다.

OutputStream을 상속해 로거로 흘려보냅니다

핵심은 OutputStream을 상속한 클래스를 하나 만들고, 그것을 PrintStream으로 감싸 System.out·System.err 자리에 꽂는 것입니다.

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;

import org.slf4j.Logger;

public class LoggingOutputStream extends OutputStream {

  public static void redirectSysOutAndSysErr(Logger logger) {
    System.setOut(new PrintStream(new LoggingOutputStream(logger, LogLevel.INFO)));
    System.setErr(new PrintStream(new LoggingOutputStream(logger, LogLevel.ERROR)));
  }

  private final ByteArrayOutputStream baos = new ByteArrayOutputStream(1000);
  private final Logger logger;
  private final LogLevel level;

  public enum LogLevel {
    TRACE, DEBUG, INFO, WARN, ERROR,
  }

  public LoggingOutputStream(Logger logger, LogLevel level) {
    this.logger = logger;
    this.level = level;
  }

  @Override
  public void write(int b) {
    if (b == '\n') {
      String line = baos.toString();
      baos.reset();

      switch (level) {
        case TRACE:
          logger.trace(line);
          break;
        case DEBUG:
          logger.debug(line);
          break;
        case ERROR:
          logger.error(line);
          break;
        case INFO:
          logger.info(line);
          break;
        case WARN:
          logger.warn(line);
          break;
      }
    } else {
      baos.write(b);
    }
  }

}

줄바꿈을 만날 때까지 버퍼에 모읍니다

OutputStream이 요구하는 것은 바이트 하나를 쓰는 write(int b) 하나입니다. 다만 로거는 바이트가 아니라 줄 단위로 기록합니다. 그 간극을 ByteArrayOutputStream이 메웁니다.

 

들어온 바이트가 줄바꿈('\n')이 아니면 그대로 baos에 쌓아 둡니다. 줄바꿈을 만나는 순간 baos.toString()으로 한 줄을 완성해 꺼내고, baos.reset()으로 버퍼를 비워 다음 줄을 받을 준비를 합니다. println 한 번이 로그 한 줄에 대응되는 이유입니다.

 

완성된 줄은 인스턴스가 들고 있는 LogLevel에 따라 logger.trace·debug·error·info·warn 중 하나로 넘어갑니다. 레벨은 생성자에서 한 번 정해지고 그 뒤로 바뀌지 않으므로, 하나의 스트림은 하나의 레벨만 담당합니다.

표준 출력과 표준 에러를 각각 다른 레벨로 꽂습니다

교체는 redirectSysOutAndSysErr 한 번의 호출로 끝납니다.

System.setOut(new PrintStream(new LoggingOutputStream(logger, LogLevel.INFO)));
System.setErr(new PrintStream(new LoggingOutputStream(logger, LogLevel.ERROR)));

 

System.outINFO 레벨을 쓰는 인스턴스로, System.errERROR 레벨을 쓰는 인스턴스로 각각 교체합니다.

 

LoggingOutputStreamOutputStream일 뿐이라 그대로 System.setOut에 넣을 수 없고, PrintStream으로 한 번 감싸야 println 계열 메소드를 갖춘 표준 출력 자리에 들어갑니다.

 

이 호출 이후로 코드 어디에서 System.out.println을 부르든, 그 출력은 넘겨받은 Logger를 거쳐 나갑니다.

반응형