Python 크래시 로그 남기기 — 원격 서버에서 stderr 리다이렉션

1. 문제 정의

라즈베리 파이처럼 원격 머신에서 실행되는 Python 스크립트가 있습니다. 이 코드는 원래 영원히 계속 실행되어야 하는데, 몇 시간이 지나면 크래시됩니다.

문제는 스크립트가 원격 머신에서 돌기 때문에 크래시 순간의 오류 메시지를 직접 볼 수 없다는 점입니다.

질문 원문 (StackOverflow 15111249 요약)

“I am running a python code in a raspberry Pi. The code is supposed to last forever. However, after a few hours it crashes. Since it is running on a remote machine, I cannot see the message it gives during the crash. How can I store this message on a file so I can see what was the problem?”

즉, 해결해야 할 문제는 **“크래시 시점의 오류 메시지를 파일로 저장해 나중에 원인을 확인할 수 있게 하는 것”**입니다. 리눅스에서 이런 일이 자동으로 일어나는지, 아니면 오류를 파일로 내보내는 함수를 직접 작성해야 하는지가 핵심 질문입니다.

2. 원인 탐구

왜 크래시 메시지가 사라지는가?

Python 스크립트가 크래시를 일으키면 인터프리터는 traceback(역추적)을 stderr(표준 에러) 로 출력합니다. 이때 기본적으로 오류 메시지는:

  • 터미널(콘솔)에만 출력되고,
  • 별도 파일로 저장되지 않습니다.

원격 머신에서는 셸 세션을 직접 보고 있지 않거나, 프로세스가 백그라운드로 돌고 있기 때문에 이 출력을 놓치게 됩니다. 따라서 “리눅스가 자동으로 오류를 파일에 남겨주는가?“라는 질문에 대한 답은 일반적으로 아니오입니다. 원하는 형태로 오류를 파일에 남기려면 명시적으로 조치를 취해야 합니다.

두 가지 접근 방식

크래시 로그를 남기는 방법은 크게 두 갈래로 나뉩니다.

접근방식장점단점
셸 리다이렉션실행 시 >> file 2>&1 로 출력 방향 전환코드 수정 불필요, 초보자도 쉬움로그 형식/시간 기록 제어는 별도
코드 내 로깅logging.exception() / sys.exc_info()타임스탬프·형식·파일 관리 가능소스 수정 필요

3. 근본 원인 분석

크래시 메시지를 볼 수 없는 근본 원인은 오류 출력(stderr)이 저장되지 않는 기본 동작 때문입니다. Python이 예외를 던지고 종료되면 traceback은 프로세스의 표준 에러 스트림으로만 흘러갑니다. 그 스트림이 파일로 연결되어 있지 않다면 메시지는 그냥 사라집니다.

따라서 근본 해결책은 “오류 출력이 파일로 흘러가도록 스트림을 연결” 하는 것입니다. 이는 두 층위에서 해결할 수 있습니다.

  1. 운영(셸) 층위: 프로세스를 띄울 때 파일로 리다이렉션.
  2. 코드 층위: Python 내부에서 예외를 잡아 파일에 직접 기록.

4. 코드 해결책

방법 A — 셸 리다이렉션 (가장 간단, 채택 답변)

소스를 전혀 건드리지 않고, 프로세스를 시작할 때 표준 출력과 표준 에러를 모두 로그 파일로 보냅니다.

1
python script.py >> /logdir/script.py.log 2>&1
  • >> : 출력을 파일 끝에 이어붙이기(append). >는 파일을 덮어씁니다.
  • 2>&1 : stderr(2)를 stdout(1)과 같은 곳으로 보냅니다. 이 한 줄 덕분에 크래시 traceback까지 파일에 남습니다.

>>를 쓰는 이유는 오래 실행되는 스크립트에서 이전 로그가 유지되도록 하기 위함입니다. 파일을 덮어쓰는 >는 재시작할 때마다 이전 로그를 지워버립니다.

방법 B — logging 모듈로 메인 함수 크래시 기록

로그 집계(예: Logstash 같은 도구)를 사용하고 있다면, 메인 함수를 try/except로 감싸 크래시 시점을 logging.exception()으로 남기는 방식이 더 유리합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import logging

logger = logging.getLogger(__name__)

def main():
    # ... 오래 실행되는 작업 ...
    raise ValueError("Crashed because I'm a bad exception")

if __name__ == "__main__":
    try:
        main()
    except Exception as e:
        logger.exception("main crashed. Error: %s", e)

logger.exception()는 예외 메시지뿐 아니라 전체 traceback과 타임스탬프까지 함께 로그로 남기므로, “언제 크래시했는지"를 포함해 원인을 추적하기 쉽습니다. 파일/형식/시간 관리는 logging 설정으로 제어합니다.

방법 C — sys.exc_info()로 크래시 로그 파일 직접 작성

별도 로그 파일(예: CRASH-<timestamp>.txt)에 오류 위치와 메시지를 기록하고 싶다면 sys.exc_info()를 활용할 수 있습니다. 아래는 Python 3 코드입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import sys
import time

try:
    # ... 실행할 프로그램 코드 ...
    pass
except Exception as e:
    crash = [
        "Error on line {}".format(sys.exc_info()[-1].tb_lineno),
        "\n",
        str(e),
    ]
    print(crash)

    time_x = str(time.time())
    with open("crashlogs/CRASH-" + time_x + ".txt", "w") as crash_log:
        for line in crash:
            crash_log.write(line)
  • sys.exc_info()[-1].tb_lineno : 오류가 발생한 라인 번호
  • time.time() : 크래시 시점의 유닉스 타임스탬프 (파일명에 포함 → 크래시 마다 개별 로그 생성)

방법 비교 요약

구분방법 A (리다이렉션)방법 B (logging)방법 C (sys.exc_info)
코드 수정불필요필요필요
타임스탬프 기록리다이렉션만으론 없음자동 포함수동 추가
traceback 저장stderr 그대로 저장exception()이 포함라인 번호만 직접 기록
적합한 상황빠른 디버깅, 초보자로그 집계 도구 사용 시크래시별 개별 파일 필요 시

5. 향후 예방 조치

  1. 항상 리다이렉션을 습관화 — 원격에서 장시간 실행하는 스크립트는 시작 명령부터 >> 로그파일 2>&1을 붙여 오류 스트림이 파일로 가도록 하세요.
  2. > 대신 >> — 재시작해도 이전 로그를 지우지 않도록 append를 사용하세요.
  3. 구조적인 로깅 도입 — 장기 실행 서비스라면 logging 모듈로 파일 핸들러를 설정하고 메인 함수를 try/except로 감싸 logger.exception()을 남기세요. 타임스탬프와 traceback이 함께 남아 원인 추적이 쉽습니다.
  4. 크래시가 잦은 경우 근본 원인 확인 — 로그가 남으면 “몇 시간 뒤 크래시"의 원인(메모리 누수, 리소스 고갈, 예외 미처리 등)을 traceback으로 분석해 코드를 고치세요.

출처