최근 콘솔에는 로그가 출력되나 파일에 기록되지 않는 문제를 발견했습니다. 이 문제를 해결하기 위해 Java의 ProcessBuilder를 사용한 로그 처리 방식을 분석했습니다. 관련 코드는 다음과 같습니다.
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.redirectInput(ProcessBuilder.Redirect.INHERIT);
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process process = processBuilder
.directory(nodeDir)
.command(command) // processImage.js 호출
.start();
// processImage.js
function processImage() {
console.log('process 시작');
process();
console.log('process 완료');
}
(ProcessBuilder는 자바에서 외부 프로세스를 실행하는 데 사용되는 클래스입니다. 여기서 ‘외부 프로세스’란 자바 애플리케이션 외부의 다른 프로그램이나 스크립트를 말합니다. Redirect.INHERIT 옵션을 사용하면, 이 외부 프로세스가 자바 애플리케이션과 같은 입출력 소스(콘솔 등)를 사용하게 됩니다.)
command에서 실행하는 process시작, process 완료 라는 로그 모두 콘솔에는 출력이 되었으나, 별도로 기록하는 로그 파일에는 기록되지 않았습니다. 로그파일도 팀 내에서 사용되기에 필요한 정보가 기록돼야만 했는데요.
ProcessBuilder의 기본 작동 원리
ProcessBuilder 클래스는 자바에서 외부 프로세스를 실행하는 데 사용됩니다. 이 클래스를 사용하여 커맨드라인 명령어를 실행하고, 실행 환경을 설정할 수 있습니다. 프로세스가 생성될 때, ProcessBuilder는 다양한 속성을 설정할 수 있으며, 이 중 유심히 볼 것은 입출력 스트림의 관리입니다.
ProcessBuilder에서 제공하는 스트림 리디렉션 옵션 중 Redirect.INHERIT는 하위 프로세스가 부모 프로세스의 입출력 스트림을 상속받게 합니다. 이 설정을 사용하면, 외부 프로세스(예: Node.js 프로세스)는 자바 애플리케이션을 실행 중인 콘솔의 표준 입출력 스트림을 직접 사용하게 됩니다.
이 경우, node script.js와 같은 명령을 실행할 때 console.log() 함수로 생성된 출력은 자바 애플리케이션의 표준 출력 스트림을 통해 콘솔에 출력됩니다. IntelliJ나 다른 개발 환경에서는 이 표준 출력을 캡처하여 IDE의 콘솔 창에 표시합니다.
그러나 Redirect.INHERIT 옵션을 사용할 때, 출력 스트림은 파일이 아닌 콘솔로 직접 연결됩니다. 이는 외부 프로세스의 출력이 파일로 리디렉션되지 않음을 의미합니다. 따라서, 로그 파일에는 자동으로 데이터가 기록되지 않습니다. 로그 파일에 데이터를 기록하기 위해서는 별도의 리디렉션 설정이 필요합니다.
외부 프로세스의 출력을 로그 파일에 기록하려면 Redirect.appendTo(File file)와 같은 메소드를 사용하여 출력 스트림을 로그 파일로 명시적으로 리디렉션해야 합니다. 이 설정을 통해 console.log()의 결과가 파일에 직접 쓰여질 수 있습니다.
이러한 분석을 통해 문제의 근본적인 원인이 ProcessBuilder의 스트림 리디렉션 방식에 있다는 것을 이해할 수 있습니다. Redirect.INHERIT 옵션에 따라 하위프로세스는 부모프로세스의 표준 출력(콘솔)을 사용합니다. 이 설정으로 인해 console.log()와같은 모든 출력은 자바 애플리케이션의 표준 출력 스트림을 통해 콘솔로 출력됩니다. 해당 출력스트림을 명시적으로 파일로 보내는 설정이 필요한데 그런 설정을 해주지 않았으니 로그 파일에는 자동으로 데이터가 기록되지 않습니다.
해결 방안
그렇다면 콘솔 출력을 파일로 리디렉션 하려면 어떻게 해야할까요?
아래와 같은 코드로 리디렉션을 할 수 있습니다.
File log = new File("log.txt"); // 파일 경로를 명시해주어야함
ProcessBuilder pb = new ProcessBuilder("node", "processImage.js");
pb.redirectOutput(ProcessBuilder.Redirect.appendTo(log));
pb.redirectError(ProcessBuilder.Redirect.appendTo(log));
Process p = pb.start();
하지만, 위의 설정은 로그 파일의 경로를 명시해주어야하는 문제가 있습니다. 로그파일 경로가 변경될때마다 코드를 변경하거나 환경변수 값이 들어있는 파일을 변경하는 등의 작업을 해주어야합니다. 따라서 저희팀은 ProcessBuilder의 출력을 파일로 리디렉션하지 않고, 대신 Java 프로그램 내에서 스트림을 직접 읽어 로그로 기록하였습니다.
이 방식의 장점은 외부 프로세스의 출력을 실시간으로 캡처하고, 이를 로깅 프레임워크를 통해 동적으로 처리할 수 있다는 점입니다. 또한, 로그 파일의 경로나 이름을 미리 설정할 필요가 없어, 유연성이 높아집니다.
private void executeJsFile(List<String> command) throws Exception {
ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.redirectInput(ProcessBuilder.Redirect.INHERIT);
processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE);
Process process = processBuilder
.directory(getDir())
.command(command)
.start();
logInfo(process.getInputStream());
}
private void logInfo(InputStream stream) {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
String line;
while ((line = reader.readLine()) != null) {
log.info(line);
}
}
}
ProcessBuilder.Redirect.PIPE
- PIPE는 프로세스의 표준 입출력(표준 출력, 표준 오류)을 파이프를 통해 Java 프로그램 내부로 리디렉션합니다. 이 옵션을 사용하면 Java 코드 내에서 Process 객체의 getInputStream()과 getErrorStream() 메소드를 통해 프로세스의 출력을 읽을 수 있습니다.
- 이 설정은 Java 애플리케이션에서 외부 프로세스의 출력을 직접 처리하고자 할 때 유용합니다. 예를 들어, 프로세스의 결과를 로깅하거나 결과에 기반한 추가 작업을 수행하고자 할 때 사용됩니다.
ProcessBuilder.Redirect.INHERIT
- INHERIT는 외부 프로세스가 부모 프로세스(Java 프로세스)의 표준 입출력 스트림을 상속받도록 설정합니다. 이 설정을 사용하면 외부 프로세스는 Java 애플리케이션을 실행하고 있는 콘솔의 표준 입출력을 직접 사용합니다.
위와 같이 처리하면 Node.js 프로세스가 console.log를 통해 생성한 출력을 Java 프로그램에서 캡처합니다. (Process.getInputStream() ) 그리고 Java 프로그램은 InputStream에서 데이터를 읽습니다. 이 스트림은 Node.js 프로세스의 표준 출력(stdout)에 연결되어 있습니다. 읽은 데이터를 Java의 로깅 프레임워크(Logback)를 사용하여 로그로 기록합니다. 예를 들어, log.info(line)을 호출하여 로그 메시지를 로깅 시스템에 전달합니다.
저희 팀은 ProcessBuilder의 스트림 리디렉션 방식에 대한 분석을 통해 콘솔에는 보이지만 로그 파일에 기록되지 않는 문제의 근본 원인을 파악할 수 있었습니다.
ProcessBuilder.Redirect.PIPE을 활용하여 외부 프로세스의 출력을 직접적으로 Java 프로그램 내로 리디렉션하고, 이를 통해 로그 데이터를 InputStream에서 직접 읽고, Java의 로깅 프레임워크를 사용하여 동적으로 로그 파일에 기록할 수 있게 되었습니다.
이 경험은 로그 관리 전략을 개선하고, 더 나은 시스템 모니터링과 오류 분석을 가능하게 하는 중요한 학습 기회였습니다.
'문제해결' 카테고리의 다른 글
| 트레이싱 어노테이션 140개를 지우며 들었던 생각 (0) | 2025.02.26 |
|---|---|
| 부팅 시 도커 데몬 자동 실행하게 하기 (0) | 2024.09.09 |
| JUnit 5 의존성 주입 과정에서의 문제 (0) | 2023.11.20 |
| Reddisson을 활용한 동시성 문제 해결 (1) | 2023.10.04 |
| 로드밸런싱을 통한 높은 CPU 점유율 해결하기 (0) | 2023.09.18 |