파이썬 logging.info가 안 나올 때 확인하는 순서: 레벨·핸들러·basicConfig

파이썬에서 logging.info()나 logging.debug()가 보이지 않을 때는 먼저 기본 로그 레벨, 이미 만들어진 root handler, logger·handler의 레벨, 설정 호출 순서를 확인하면 됩니다. 기본 설정에서는 root logger의 레벨이 WARNING이므로 별도 구성이 없으면 INFO와 DEBUG가 출력되지 않습니다. basicConfig()를 호출했는데도 바뀌지 않는다면 root logger에 handler가 이미 있는지부터 보셔야 합니다.

자동화 스크립트는 실행 자체보다 어디까지 진행됐는지를 알 수 있어야 운영하기 쉽습니다. 아래 순서는 설정을 무작정 바꾸기보다 현재 상태를 확인한 뒤 다음 조치를 고르는 흐름입니다.

기본 레벨 때문에 INFO와 DEBUG가 걸러지는지 확인하기

Python 3.13 공식 문서 기준으로 기본 로깅 수준은 WARNING입니다. 따라서 별도 설정이 없으면 다음 코드에서 warning만 보일 수 있습니다.

import logging

logging.debug("상세 처리 정보")
logging.info("처리 시작")
logging.warning("주의가 필요한 상태")

애플리케이션 진입점에서 필요한 최소 레벨을 명시합니다.

import logging

logging.basicConfig(level=logging.INFO)
logging.info("처리 시작")

DEBUG까지 필요하다면 level=logging.DEBUG로 낮출 수 있습니다. 운영 자동화에서는 상세 로그가 많아질 수 있으므로 문제를 좁히는 동안만 DEBUG를 사용하는 편이 관리하기 쉽습니다.

basicConfig가 적용됐는지 root handler 확인하기

basicConfig() 뒤에도 설정이 바뀌지 않는다면 root logger에 handler가 이미 있었을 가능성이 큽니다. 공식 문서에 따르면 root logger에 handler가 하나라도 있으면 force=True가 아닌 basicConfig()는 아무 작업도 하지 않습니다.

import logging

root = logging.getLogger()
print("root level:", root.level)
print("root handlers:", root.handlers)

root.handlers가 비어 있지 않다면 다른 라이브러리나 실행 환경이 먼저 로깅을 구성했는지 확인합니다. 기존 handler를 의도적으로 교체해야 하는 애플리케이션이라면 force=True를 고려할 수 있습니다.

import logging

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

force=True는 기존 root handler를 제거하고 종료한 뒤 새 설정을 적용합니다. 다른 코드가 만든 handler나 출력 형식을 함께 사용한다면 영향 범위를 먼저 확인해야 합니다. 공용 라이브러리 내부에서 강제 설정하기보다 애플리케이션 진입점에서 판단하는 것이 안전합니다.

logger와 handler의 레벨을 따로 확인하기

root logger의 레벨을 낮췄는데도 특정 logger의 메시지가 사라진다면 logger와 handler를 분리해서 보십시오. logger는 이벤트를 처리할 최소 심각도를 정하고, handler도 자신에게 설정된 레벨에 따라 메시지를 걸러낼 수 있습니다.

import logging

logger = logging.getLogger(__name__)
print("logger level:", logger.level)
print("effective level:", logger.getEffectiveLevel())
for handler in logger.handlers:
    print("handler level:", handler.level)

logger.level이 0이면 자체 레벨 대신 부모 logger의 유효 레벨을 물려받는 상태일 수 있습니다. logger.handlers는 해당 logger에 직접 연결된 handler 목록이므로 비어 있어도 이벤트가 상위 logger의 handler로 전달되는 구성일 수 있습니다.

명시적인 구성이 필요하다면 logger와 handler의 책임을 나누어 설정합니다.

import logging

logger = logging.getLogger("worker")
logger.setLevel(logging.DEBUG)

handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
logger.addHandler(handler)

logger.debug("handler 레벨에서 걸러질 수 있는 메시지")
logger.info("handler가 허용하는 메시지")

logger가 DEBUG 이벤트를 받을 수 있어도 handler가 INFO보다 낮은 레벨을 허용하지 않으면 DEBUG는 출력되지 않습니다. 두 레벨을 함께 확인해야 하는 이유입니다.

설정 호출 순서를 진입점에서 정리하기

설정이 여러 모듈에 흩어져 있으면 import 과정에서 handler가 먼저 만들어질 수 있습니다. 그러면 나중에 호출한 basicConfig()가 기대대로 동작하지 않습니다. 운영 코드에서는 애플리케이션 진입점에서 구성을 한 번 정하고 각 모듈은 이름 있는 logger를 가져오는 구조가 판단하기 쉽습니다.

# main.py
import logging
from worker import run


def main():
    logging.basicConfig(level=logging.INFO)
    run()


if __name__ == "__main__":
    main()
# worker.py
import logging

logger = logging.getLogger(__name__)


def run():
    logger.info("작업을 시작합니다")

확인 순서는 다음과 같습니다.

  1. 진입점의 basicConfig()가 로그를 쓰는 코드보다 먼저 실행되는지 확인합니다.
  2. root logger에 handler가 이미 있다면 만든 위치를 찾습니다.
  3. 로그를 쓰는 logger의 유효 레벨과 연결된 handler의 레벨을 각각 확인합니다.
  4. 전체 애플리케이션의 기존 구성을 교체해야 할 때만 force=True를 검토합니다.

상황별로 다음 조치 고르기

  • WARNING만 보인다: root logger 또는 유효 레벨이 INFO보다 높은지 확인합니다.
  • basicConfig() 뒤에도 변화가 없다: root handler가 있는지 확인하고 기존 구성을 유지할지 교체할지 결정합니다.
  • 특정 모듈만 안 보인다: logger의 유효 레벨, handler 레벨, 상위 logger로의 전달 구조를 확인합니다.
  • import 뒤에 설정한다: 설정을 애플리케이션 진입점으로 옮겨 호출 순서를 고정합니다.

정리하면 logging.info()가 안 나올 때는 호출문보다 기본 WARNING 필터인지, root handler가 이미 있는지, logger와 handler 중 어느 쪽에서 걸러지는지, 설정이 언제 실행됐는지를 순서대로 좁히는 것이 좋습니다. 이 네 가지가 확인되면 단순한 레벨 조정인지, 명시적 handler 구성이나 force=True가 필요한 상황인지 구분할 수 있습니다.

더 살펴볼 질문

로그가 출력되기 시작한 뒤 중복으로 보인다면 파이썬 로그에 traceback이 안 남을 때 logging.exception과 exc_info 선택법을 이어서 확인할 수 있습니다. 콘솔과 파일의 레벨을 다르게 운영해야 한다면 파이썬 로그 콘솔·파일 레벨 분리: DEBUG는 파일에, INFO는 콘솔에를 참고할 수 있습니다.

출처

Leave a Comment