stderr와 stdout의 통합과 분리 - 기준 세우기

2025. 7. 8. 15:03·컴퓨터구조 & OS



stderr와 stdout을 통합, 분리하는 기준을 세워보자.
 

통합 케이스
► 출력 순서와 타이밍이 중요함
→ stdout과 stderr의 출력 순서를 유지하며 하나의 타임라인처럼 보고 싶을 때.
► 로그 스트리밍, 모니터링, 실시간 피드백 성격

분리 케이스
► 정상 출력 / 에러를 별도로 처리해야 함
→ 예외 상황을 별도로 감지·로깅하거나, 에러만 따로 사용자에게 알릴 필요가 있을 때.
→ stdout은 결과 파일에, stderr는 로그 파일에 기록해야 할 때.
► 커맨드 파이프라인에서 정상 데이터만 다음 단계로 넘길 때
→ 예: grep, cat, find 등의 결과 중 에러 메시지는 파이프라인에 섞이면 안 되는 경우.

 
.
.
 
이 결론을 세우기까지 많은 공부가 되었는데, 그런 내용들을 정리해보려 한다.
 
Java에서 ProcessBuilder로 스트림을 통합 및 분리하는 방법,
두 스트림을 통합한다는 것을 이하하기, 
각 스트림의 버퍼링 정책 이해하기,
stdout, stderr의 분리 기준 세우기,
그리고 개인적인 시행착오와,
어떤 이유로 분리를 하게 되었는지를 정리했다.
 


Java에서 시스템 명령 수행하기

Java에서 외부 시스템 명령어를 실행할 때는 ProcessBuilder를 사용한다.
ProcessBuilder는 명령어를 실행하고, 표준 출력(stdout) 과 표준 에러(stderr) 스트림을 각각 처리할 수 있는 구조를 제공한다.

List<String> cmd = List.of("/bin/sh", "-c", command);
ProcessBuilder processBuilder = new ProcessBuilder(cmd);
processBuilder.redirectErrorStream(true); // stderr를 stdout으로 출력

 
여기서 redirectErrorStream(true) 옵션은 stderr를 stdout으로 통합하여 하나의 스트림으로 처리하도록 한다.
쉘에서 사용하는 2>&1와 동일한 개념으로, 표준에러를 표준출력 스트림으로 리다이렉트한다는 것이다. 
 
 
 


스트림 통합 이해하기 (2>&1)

리눅스/유닉스의 프로세스는 입출력을 숫자 형태의 파일 디스크립터(File Descriptor) 로 구분한다.
2>&1에서 &1은 파일디스크립터 1을 참조한다는 의미인데, 파일 디스크립터가 뭔지 좀 더 보자.
 
리눅스/유닉스의 프로세스는 입출력을 숫자 형태의 파일 디스크립터(File Descriptor) 로 구분한다.
아래와 같이 각 숫자에 입출력에 대한 의미를 부여한 것이다.

* 파일 디스크립터 0은 "표준 입력(stdin)"을 의미함 (ex: 키보드 입력)
* 파일 디스크립터 1은 "표준 출력(stdout)"을 의미함 (ex: 일반 출력)
* 파일 디스크립터 2는 "표준 에러(stderr)"을 의미함 (ex: 오류 메세지 출력)

 
그래서 2>&1의 의미를 다시 보면
2를 1으로 리다이렉션한다는 것이다.

2 표준에러를
> 리다이렉션 (출력 방향 변경)
&1 표준출력으로 

 
&가 왜 냐오냐면...
&의 뒤에 오는 것이 파일명이 아니라 파일 디스크립터라는 것을 의미해준다.
예시를 보면 충분히 이해할 것이다.

2>1  표준 에러를 1이라는 파일에 씀
2>&1  표준 에러를 표준 출력으로 리다이렉트
1>&2  표준 출력을 표준 에러로 리다이렉트

 
 
 


stdout과 stderr의 버퍼링 정책 알기

분리/통합을 선택하기 앞서, stdout과 stderr의 버퍼링 방식이 다르다는 것도 이해하자.
- stdout은 Line-buffered, 줄 단위 버퍼링으로, \n이 있을 때 출력된다.
- stderr는 Unbuffered, 버퍼링 없이 즉시 출력된다.
 
예를 들어서 아래 코드를 실행해보면
표준오류가 먼저 콘솔에 찍힌다. 표준출력에 \n이 없어서 버퍼링되기 때문에 좀 뒤늦게 찍히는 것이다.
* https://sjh836.tistory.com/41 를 읽고 큰 도움이 되었고, 아래 예시의 출처이다.

fprint(stdout, "%s", message);
fprint(stderr, "%s", message);

 
 
그래서 스트림을 하나로 통합했을 때 stdout이 정확한 타이밍에 안 찍힐 수 있다. 예를들어 어떤 서버 로그에서, 어떤 stderr가 분명 찍혔는데 이후에 관련 stdout이 정상 출력되어 혼란스러울 수도 있다. 둘의 버퍼링 방법이 달라서 그럴 수 있다는 것을 알아두자.
 

버퍼링 종류

  • line buffered: 개행 문자(\n)를 만나면 출력이 이루어짐
  • full buffered: 버퍼가 가득 찼을 때 출력이 이루어짐
  • unbuffered: 버퍼링을 하지 않고 즉시 출력 (1바이트 단위로 처리)

 

입출력 스트림별 버퍼링 정책

stdin, stdout, stderr에서 각각 어떤 버퍼링 정책을 사용할까? 
 

◼︎ stdin

  • 터미널 입력시에는 line buffered 또는 unbuffered
  • 그 외 (파일 등에서 입력받았을 때) full buffered

* 터미널 입력은 일반적으로 line buffered 라는 것은, 엔터를 눌러야 프로그램이 입력 받을 수 있는걸 생각하면 쉽다. 그런데 unbuffered 입력이 가능한 경우는 터미널을 raw mode로 설정하거나, 한글자 단위로 즉시 읽어야 하는 곳에서 사용할 수 있다 (게임, 실시간UI, 비밀번호 입력시 실시간 체크 등)
* 예를들면 이 c코드는 한글자 단위로 즉시 읽게 한다: read(STDIN_FILENO, &c, 1);


◼︎ stdout

  • 터미널 출력시 line buffered
  • 그 외 (파일로 리다이렉트된 경우 등) full buffered 이다.

◼︎ stderr

  • 모든 경우 unbuffered (항상 즉시 출력)

 

터미널 모드별 버퍼링 정책 (참고)

터미널 모드에 따라서 버퍼링 정책이 다르며, raw mode에서는 stdin에서 unbuffered 를 채택한다.
  위에서 stdin 설명할 때, 터미널을 raw mode로 설정할 경우 unbuffered라고 했는데, 터미널 입력 모드에 따라 버퍼링 정책이 다르다는 걸 알았다. 위키 보면 간략히 설명이 되어있다. 특히 흥미로웠던 건 cbreak mode에서는 line buffered와 unbuffered의 중간 쯤의 버퍼링 정책을 사용한다는 것이다. 특수 키 (Ctrl + C 같은 거) 생각해보면 unbuffered에서는 무시되지만 line buffered에서는 처리된다. 근데 cbreak 입력 모드에서는 한 글자씩 프로그램에 전달하는 unbuffered 기반이지만 Ctrl + C 등은 처리된다고 한다.
이런게 있구나... 정도로 알아두려고 한다!

모드입력 처리단위버퍼링특수 키
(Ctrl+C 등)
특징
Cooked (Canonical)한 줄 단위line buffered처리됨사용자가 Enter를 누를 때까지 입력이 버퍼에 쌓임.
백스페이스, Ctrl-C 등 자동 처리
Cbreak한 글자 단위unbuffered-like처리됨한 글자씩 프로그램에 전달,
Ctrl-C 등은 터미널에서 처리
Raw (Character)한 글자 단위unbuffered무시모든 입력이 즉시 프로그램에 전달,
Ctrl-C 같은 특수키도 프로그램에서 처리해야 함

출처: https://en.wikipedia.org/wiki/Terminal_mode?utm_source=chatgpt.com 를 타고가며.. 
 
(터미널 말고 디스크 I/O도 버퍼링 특성이 있더라! 이 부분은 잘 모름)
 
 
 


stdout, stderr 분리 기준 세우기 🎶

  이번에 개발한 기능 중, 시스템 명령어 grep을 날려서 로그파일을 읽어오는 부분이 있다. 이때 stdout과 stderr를 분리할지 말지를 처음으로 고민해볼 수 있었다. 나름대로 통합, 분리의 기준을 세울 수 있게 된 것 같다...
(초반에는 통합, 분리 기준이 전혀 정리가 안되어있어서 생각이 왔다갔다했음...)
 

통합 케이스

► 출력 순서와 타이밍이 중요함
→ stdout과 stderr의 출력 순서를 유지하며 하나의 타임라인처럼 보고 싶을 때.
► 로그 스트리밍, 모니터링, 실시간 피드백 성격
 

분리 케이스

► 정상 출력 / 에러를 별도로 처리해야 함
→ 예외 상황을 별도로 감지·로깅하거나, 에러만 따로 사용자에게 알릴 필요가 있을 때.
→ stdout은 결과 파일에, stderr는 로그 파일에 기록해야 할 때.
► 커맨드 파이프라인에서 정상 데이터만 다음 단계로 넘길 때
→ 예: grep, cat, find 등의 결과 중 에러 메시지는 파이프라인에 섞이면 안 되는 경우.
 
 
 


[시행착오] 처음에 잘못 생각한 부분

나는... 맨 처음에는 stderr + stdout 통합을 택했다.
어차피 출력 순서도 상관없고, 하나의 스트림 쓰면 깔끔할 것 같으니까... 
 
좀 더 자세히 말하자면, 시스템 명령 grep 결과를 하나의 스트림으로만 쭉 출력하다가 cmd 수행 중의 에러가 감지되면 아예 중단할 계획이었다. 근데 그렇게되면 결국 stderr 감지를 위한 장치가 또 필요한데, 큰 문제점 두가지가 있다.
 
1. 발생가능한 모든 에러 케이스를 알아야 한다는 큰 문제점. 
2. stdout의 내용물에 해당 문구가 포함될 가능성.

// 문제의 장치!
private boolean isSystemErrorLine(String line) {
    if (line == null) return false;
    String lowerLine = line.toLowerCase();
    // 시스템 명령어 에러만 감지 (grep, find, cat 등의 명령어 에러)
    return lowerLine.startsWith("grep:") || 
           lowerLine.startsWith("find:") ||
           lowerLine.startsWith("zcat:") ||
           lowerLine.startsWith("tar:"); // 등..
}

 
  처음 생각으로는 스트림을 하나만 둔다는 단순함 때문에 좋다고 생각했는데, 이런 식으로 에러 핸들링을 하게 되면 stderr에 제대로 대응하기 힘들다는 것을 깨달았다. 수동으로 케이스 추가가 필요한데다가, 모든 케이스를 다루기도 힘들다. 결국 스트림을 분리하는게 오히려 더 깔끔 단순해진다. 애초에 에러를 인식해서 무언가를 하려는 계획이라면 stderr를 분리하는게 낫겠다는 판단이 섰다.
 
[반성]
초기에 요구사항 이해가 얕았던 것도 이런 시행착오를 만들었다.
사실은 cmd 수행 중 stderr가 생겨도 모든 stdout을 버릴 필요가 없었다는 것...
성공한 stdout만 잘 리턴해주는게 올바른 생각이었다!
 
 
 


[결정] 두 스트림을 분리하기로 함

결국 두 기능에서 모두 분리를 선택했다.
 
이번에 제작한 요구사항에서, 커맨드를 수행하는 부분이 두 곳 있다.
1. 로그 파일 리스트 조회 
2. 로그 검색 결과 조회
 
각 커맨드 수행에서의 에러를 어떻게 처리할지 고민을 각각 했는데, 
stdout과 stderr를 분리하고, 에러 처리는 각자 성격에 맞게 하기로 결정했다. 

💡 stderr가 stdout을 중단시키지 않으므로, 에러 발생 후에도 읽을 수 있는 부분이 있다면 계속 stdout으로 출력된다. 단 stderr가 출력되었다면 완전무결한 cmd 수행이 이뤄지지 않았다는 것인데 서비스 성격에 따라 처리하면 될듯. 

 
 

◼︎ 기능 1: 로그 파일 리스트 조회

- 특정 경로 이하의 로그파일을 가져오는 기능이다.
- 일부 파일에 대해서 Permission Denied 등 생겨도 사용자에게 안 알려줘도 됨.
- 읽을 수 있는 파일 리스트만 잘 가져오면 됐다.
 
위 목적을 달성하기 위해서, 스트림을 통합할 수도 있고 분리할 수도 있다.

스트림 통합
애초에 버리려면 cmd 차원에서 err를 버릴 수도 있다. 명령어 레벨에서 2>/dev/null를 사용해 stderr를 /dev/null로 리다이렉트해 stderr를 버리는 방식 사용하는 것이다. 그러면 Java 프로세스는 이미 비어있는 stderr 스트림을 받게 된다.

스트림 분리
결과물로 stdout만 필요하더라도, stderr를 로깅하려면 스트림을 분리해야 한다.

 
‼️ 결론
  사용자님께서 "왜 로그 리스트가 이것밖에 안나와요? 분명 더 있을 텐데!"라고 궁금해하실 수 있으니까, stderr 발생 로깅은 꼭 남겨야할 것 같았다. 그래서 스트림 분리를 최종으로 선택했다.
 

◼︎ 기능2: 로그 검색 결과 조회

- 대상 로그파일을 read해서, 로그 라인을 보기 좋게 후처리하는 기능이다.
- 단일 파일만 read하는게 아니라 파일1, 파일2, 파일3, .. 여러 파일을 read해서 하나의 출력물을 만들어야 하는 케이스도 있다.
- read 대상 파일이 10개라고 생각해보자. 파일4의 읽기에 실패했다고 해서 나머지 9개 파일을 버리는건 좀 아깝다. 파일1, 2, 3을 read한 결과도 버려야 하는데다가, read가능한 5, 6, ... 10 또한 포기해 버리는 셈이다.
- 사용자에게 9개의 성공 결과는 보여주고, 실패 건은 따로 출력해서 사유를 알리려는 목적으로 분리하는게 좋을 것 같다.
 
‼️ 결론
  사용자에게 알려줄 결과물의 맨 앞부분에 조회 중 생긴 문제에 대해 안내하는게 좋겠다고 생각했다. 그러려면 에러 핸들링이 필요하므로 stderr를 분리해야 했다.
 
 
 


 마치며

stdout과 stderr를 통합/분리하는 과정, 각 스트림의 버퍼링 특성, 실제 개발 과정에서의 시행착오를 겪으면서 언제 통합하고 언제 분리할지 기준을 세웠고, 각 기능 특성별 에러 처리 기준도 세워보았다.
 
앞으로 유사한 상황에서 빠르게 도움이 될 것 같다.
 
 
 
+ 메모 +
사실 stdout, stderr 에 대해 배운 것 외에도,
로그 전문을 다루는 기능을 만들어보면서 여러 것들을 고민해볼 수 있었다.
 
- cmd로 처리할까 vs Java I/O로 처리할까
- cmd는 어느 정도로 구성할 것인가 (후처리를 할지 cmd에 넣을지에 대한 범위)
- 멀티라인 로그를 어떻게 다룰 것인가
- 검색 키워드 필터링은 어느 시점에 할 것인가
- Linux 로그파일의 여러가지 압축 형태
- 비동기로 읽을까 말까
- 대용량 로그파일 읽을 때 OOM 방지 및 메모리 관리
 
모두 고민이 컸던 부분이다.
관리자 페이지 내 기능이며 대고객 대상이 아니기 때문에 어느정도의 성능은 타협할 수 있었는데,
멀티라인 로그랑 cmd를 어느정도로 사용할지, 그 구성에서 고민거리가 많았다.

끝!

'컴퓨터구조 & OS' 카테고리의 다른 글

[가상메모리:SWAP] 건드리면 터지는 2GB 서버에 서비스올리기  (0) 2024.04.27
[OS] 터미널을 종료하면 프로세스도 종료된다 -- background로 실행하여 SIGHUP 시그널 피하기  (0) 2024.02.26
[운영체제] Process Control Block(PCB)  (0) 2023.10.06
[운영체제] 프로세스 상태도  (2) 2023.09.28
[운영체제] 프로세스 스케줄링: 멀티프로그래밍 vs 타임쉐어링  (0) 2023.09.27
'컴퓨터구조 & OS' 카테고리의 다른 글
  • [가상메모리:SWAP] 건드리면 터지는 2GB 서버에 서비스올리기
  • [OS] 터미널을 종료하면 프로세스도 종료된다 -- background로 실행하여 SIGHUP 시그널 피하기
  • [운영체제] Process Control Block(PCB)
  • [운영체제] 프로세스 상태도
히어로맛쿠키
히어로맛쿠키
  • 히어로맛쿠키
    yeny_lab
    히어로맛쿠키
  • 전체
    오늘
    어제
    • 분류 전체보기 (388)
      • 미분류글 (30)
        • ㅇ (2)
      • JAVA (84)
        • Effective Java (1)
        • Application (21)
      • 컴퓨터구조 & OS (29)
      • 자료구조 + 알고리즘 (43)
      • Database (12)
      • 컴파일러 (10)
      • 수학 (33)
        • 미분방정식 (12)
      • 데이터분석과 머신러닝 (38)
      • 기타 (58)
      • yyeeennyy (25)
  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
히어로맛쿠키
stderr와 stdout의 통합과 분리 - 기준 세우기
상단으로

티스토리툴바