기술 · 실용 가이드
Python 로그가 매번 두 번 나오나요? handler와 전파 그래프부터 감사하세요
한 record에 표식을 넣고 root까지 모든 handler를 추적한 뒤 반복 설정, framework 기본값과 다중 프로세스 수집을 구분해 중복 edge 하나만 제거합니다.

중복 줄만으로는 진단할 수 없습니다. 하나의 LogRecord가 child와 ancestor handler 모두로 갈 수 있고, 요청마다 같은 logger에 handler가 추가될 수 있으며, framework가 별도 stream을 소유하거나 여러 process와 collector가 비슷한 event를 만들 수 있습니다.
안전한 수정은 test record, PID, logger와 handler identity를 기록하고 emitter에서 모든 sink까지 그래프를 그리는 것입니다. ownership boundary 하나만 바꾸고 원래 command를 재실행합니다.
event와 line 중복을 분리
고유 token, PID, logger name, level과 정밀 timestamp를 기록하고 console, file, collector를 따로 캡처합니다. 같은 text가 별도 event일 수도 있고 다른 format이 같은 record일 수도 있습니다.
실제 command, reloader, worker launcher 또는 service unit을 통해 재생합니다. python app.py만으로 다른 실행 경로를 증명할 수 없습니다.
logger-handler 그래프 출력
emitter의 name, disabled, effective level, propagate와 모든 direct handler의 object id, class, level, stream/file, formatter를 기록하고 parent에서 root까지 반복합니다.
child와 ancestor에 같은 handler 또는 같은 sink가 있고 propagation이 켜져 있으면 두 delivery path입니다. 선택한 owner만 남기거나 명확한 boundary에서 전파를 중지합니다.
반복 setup과 propagation 구분
import 전, startup 후, request 후, reload 후 handler count를 비교합니다. 안정된 graph에 child/root handler가 있으면 propagation, count가 늘면 비 idempotent 설정입니다.
setup을 하나의 startup path로 옮기거나 알려진 handler를 재사용합니다. class만으로 중복을 판단하지 않습니다.
framework와 process 소유권 존중
basicConfig 전에 framework logger를 검사합니다. Uvicorn의 parent, error와 access branch는 각각 보존하고 live graph를 current docs와 비교합니다.
probe에 PID/process name을 포함합니다. worker는 QueueHandler를 통해 단일 listener나 external collector에 보내고 stdout/stderr의 이중 수집도 확인합니다.
edge 하나 제거 후 검증
sink마다 application root, framework branch, queue listener 또는 external collector 한 owner를 선택해 증명된 edge만 제거하고 rollback을 유지합니다.
startup, request, application warning, framework error, access event를 재생합니다. sink당 probe 하나, reload 뒤 안정된 count와 필요한 stream 보존을 확인합니다.
