파이썬 로그가 두 번씩 출력될 때 handler와 propagate를 구분하는 법

파이썬 로그가 두 번씩 출력될 때는 먼저 같은 logger에 handler가 반복 등록됐는지와 자식 logger의 기록이 부모 또는 root logger로 전파되는지를 나눠 확인해야 합니다. 두 원인은 겉으로 같은 증상을 만들지만, handler 목록과 logger 계층을 확인하면 어느 설정을 고칠지 비교적 빠르게 결정할 수 있습니다. 무작정 handlers.clear()나 propagate=False를 적용하면 다른 로그까지 사라질 수 있으므로 설정의 소유권부터 살펴보는 편이 안전합니다.

먼저 출력 경로를 확인합니다

로거 이름은 점(.)으로 이어지는 계층을 만듭니다. 예를 들어 app.worker는 app의 자식 logger이고, 기록은 전파가 끊기지 않는 한 조상 logger의 handler에서도 처리될 수 있습니다. 같은 LogRecord가 자식과 조상 logger의 handler에서 각각 처리되면 화면이나 파일에 같은 메시지가 반복됩니다. 이 동작과 handler 배치 원칙은 Python logging 공식 문서와 Logging HOWTO에서 확인할 수 있습니다.

우선 문제가 나는 지점에 다음과 같이 실제 연결 상태를 출력해 보십시오.

import logging

logger = logging.getLogger("app.worker")
root = logging.getLogger()

print("logger:", logger.name)
print("logger handlers:", logger.handlers)
print("logger propagate:", logger.propagate)
print("root handlers:", root.handlers)

여기서 logger.handlers는 해당 logger에 직접 연결된 handler만 보여 줍니다. 부모의 handler까지 자동으로 복사해 보여 주는 목록은 아닙니다. 따라서 자식 logger에 handler가 하나 있고 root에도 handler가 하나 있다면, propagate=True인 상태에서 두 경로가 모두 실행되는지 확인해야 합니다.

첫 번째 원인: 설정 함수가 handler를 반복해서 붙이는 경우

다음과 같은 설정 함수는 호출될 때마다 새로운 StreamHandler를 추가합니다.

import logging

def configure_logging():
    logger = logging.getLogger("app")
    logger.setLevel(logging.INFO)

    handler = logging.StreamHandler()
    handler.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
    logger.addHandler(handler)
    return logger

logger = configure_logging()
configure_logging()
logger.warning("작업을 확인합니다")

이 경우 문제의 핵심은 propagate가 아니라 설정 함수의 반복 호출입니다. 모듈 import, 애플리케이션 초기화, 테스트 fixture처럼 설정 함수가 여러 경로에서 실행되는지 먼저 추적하십시오. 같은 logger 객체를 다시 얻더라도 기존 handler가 자동으로 교체되지는 않으므로, 설정 함수가 handler를 소유한다면 중복을 방지하는 기준을 코드에 명시해야 합니다.

간단한 애플리케이션이라면 handler가 이미 있는지 확인한 뒤 한 번만 연결하는 방식으로 시작할 수 있습니다.

def configure_logging():
    logger = logging.getLogger("app")
    logger.setLevel(logging.INFO)

    if not logger.handlers:
        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter("%(levelname)s %(message)s"))
        logger.addHandler(handler)

    return logger

다만 이 검사는 해당 logger의 handler만 대상으로 합니다. 다른 코드가 root logger에 handler를 연결하고 있다면, 다음 단계에서 전파 경로를 별도로 확인해야 합니다.

두 번째 원인: 자식 logger와 root handler가 함께 처리하는 경우

애플리케이션 전체의 출력 정책을 root 또는 상위 logger 한 곳에서 관리한다면, 하위 logger는 기록만 만들고 전파를 유지하는 구성이 단순합니다.

import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(name)s %(levelname)s %(message)s",
)

logger = logging.getLogger("app.worker")
logger.info("작업을 시작합니다")

반대로 app.worker에 별도 handler를 붙이면서 root에도 handler가 남아 있으면, 자식의 기록이 두 handler를 지나갈 수 있습니다. 이때 선택지는 두 가지입니다.

  • 전체 애플리케이션이 같은 형식과 출력 대상으로 기록해야 한다면, 자식 logger의 handler를 제거하고 상위 logger 한 곳에서 처리합니다.
  • 자식 logger가 독립된 파일이나 형식을 반드시 가져야 한다면, 그 logger가 root로 전파되지 않도록 propagate = False를 검토합니다.

두 번째 선택은 중복만 없애는 스위치가 아닙니다. 해당 logger의 기록이 상위 handler로도 가야 하는 구조라면 필요한 출력까지 끊을 수 있습니다. 그래서 propagate=False는 “중복이니까 일단 적용”하는 값이 아니라, 이 logger의 출력 책임을 자식 쪽이 단독으로 맡는다는 설계 결정으로 다루는 것이 좋습니다.

basicConfig를 썼는데도 중복될 때

basicConfig()는 root logger에 기본 handler와 형식을 설정하는 간편한 방법입니다. root에 이미 handler가 있으면 기본적으로 기존 구성을 그대로 두므로, 호출 자체가 모든 handler를 정리해 주지는 않습니다. 사용자 handler를 직접 추가한 뒤 basicConfig()도 호출했다면 두 설정이 각각 어디에 붙었는지 확인해야 합니다. 자세한 동작은 basicConfig() 공식 reference에서 확인할 수 있습니다.

운영 중인 코드에서 force=True로 root handler를 재설정하는 방법도 있지만, 다른 모듈이 이미 사용 중인 출력 설정을 바꿀 수 있습니다. 애플리케이션 시작 시점처럼 소유권이 분명한 곳에서만 사용 여부를 판단하고, 라이브러리 코드가 전역 logging 설정을 임의로 덮어쓰는 방식은 피하는 편이 낫습니다.

제가 점검하는 순서

저라면 다음 순서로 범위를 좁힙니다.

  1. 중복되는 한 줄이 같은 실행 안에서 반복되는지 확인합니다.
  2. 해당 logger의 handlers와 root logger의 handlers를 각각 출력합니다.
  3. 설정 함수가 몇 번 호출되는지, handler를 매번 새로 만드는지 확인합니다.
  4. logger 이름의 부모 관계와 propagate 값을 확인합니다.
  5. handler를 상위 한 곳에 둘지, 자식 logger가 독립적으로 맡을지 결정합니다.
  6. 결정한 소유권에 맞춰 중복 handler를 제거하거나 propagate=False를 적용하고 다시 확인합니다.

이 순서를 따르면 증상만 보고 handlers.clear()를 실행하는 일을 줄일 수 있습니다. 특히 공용 라이브러리나 다른 모듈이 만든 handler까지 지우면 원래 필요했던 파일·콘솔 출력이 사라질 수 있으므로, 누가 handler를 만들고 관리하는지 모를 때는 삭제보다 구조 확인이 먼저입니다.

정리

파이썬 로그가 두 번씩 출력되는 문제는 보통 handler의 반복 등록과 logger 계층의 전파를 분리해서 보면 해결 방향이 선명해집니다. 같은 logger에 handler가 쌓였으면 설정 함수의 호출과 소유권을 고치고, 자식과 root가 함께 처리하고 있으면 handler를 한 곳에 둘지 전파를 끌지 결정하십시오.

로그 파일 자체가 생기지 않는 문제까지 이어진다면 파이썬 로그 파일이 안 생기는 이유와 logging 설정 점검법에서 파일 handler와 기본 설정의 점검 항목을 이어서 확인할 수 있습니다.

Leave a Comment