Python Logging에서 두 번 나타나는 로그 메시지


100

Python 로깅을 사용하고 있으며 어떤 이유로 모든 메시지가 두 번 나타납니다.

로깅을 구성하는 모듈이 있습니다.

# BUG: It's outputting logging messages twice - not sure why - it's not the propagate setting.
def configure_logging(self, logging_file):
    self.logger = logging.getLogger("my_logger")
    self.logger.setLevel(logging.DEBUG)
    self.logger.propagate = 0
    # Format for our loglines
    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
    # Setup console logging
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    ch.setFormatter(formatter)
    self.logger.addHandler(ch)
    # Setup file logging as well
    fh = logging.FileHandler(LOG_FILENAME)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    self.logger.addHandler(fh)

나중에이 메서드를 호출하여 로깅을 구성합니다.

if __name__ == '__main__':
    tom = Boy()
    tom.configure_logging(LOG_FILENAME)
    tom.buy_ham()

그런 다음 buy_ham 모듈에서 다음을 호출합니다.

self.logger.info('Successfully able to write to %s' % path)

그리고 어떤 이유로 모든 메시지가 두 번 나타납니다. 나는 여전히 똑같은 스트림 핸들러 중 하나를 주석 처리했습니다. 이상한 일이지만 왜 이런 일이 발생하는지 모르겠습니다. 내가 명백한 것을 놓쳤다 고 가정합니다.

건배, 빅터


1
당신은 확인된다 configure_logging()(예를 들어 생성자에서 너무) 두 번 호출되지 않는 이유는 무엇입니까? Boy () 인스턴스가 하나만 생성됩니까?
제이 섹 Konieczny

1
self.logger.handlers = [ch]대신 사용하면 이 문제가 해결되지만, 예를 들어 if not self.logger처음에 사용하여이 코드를 두 번 실행하지 않도록하는 것이 가장 좋습니다 .
Ninjakannon

답변:


133

configure_logging두 번 호출합니다 (의 __init__메서드에서 가능 Boy) : getLogger동일한 객체를 반환하지만 addHandler유사한 핸들러가 이미 로거에 추가되었는지 확인하지 않습니다.

해당 메서드에 대한 호출을 추적하고 이들 중 하나를 제거하십시오. 아니면 플래그 설정 logging_initialized으로 초기화 False에서 __init__의 방법 Boy과 변화 configure_logging하면 아무것도 할 수을 logging_initialized것입니다 True, 그리고 그것을 설정 True하면 로거를 초기화 한 후.

프로그램이 여러 Boy인스턴스를 생성 configure_logging하는 경우 처리기를 추가 하는 전역 함수와 속성을 Boy.configure_logging초기화하는 메서드 만 사용하여 작업을 수행하는 방식을 변경해야 self.logger합니다.

이를 해결하는 또 다른 방법은 로거의 handlers 속성을 확인하는 것입니다.

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)

1
예, 당신이 옳았습니다. 나는 그것을 init 뿐만 아니라 명시 적으로 다른 곳 에서 불렀다 . Lol. 감사합니다 =).
victorhooi

감사. 귀하의 솔루션은 오늘 저를 구했습니다.
ForeverLearner

1
제 경우에는 6 번 등장했습니다. 나는 의심했던 것을 나는 6 개 OOP 클래스에서 로거의 동일한 유형을 선언했기 때문에
answerSeeker

5
여기에 내 경험을 공유하고 싶습니다. 제가 개발 한 Flask 애플리케이션의 경우 로그 메시지가 두 배 이상 표시되었습니다. 응용 프로그램과 모듈이로드 될 때 logger사용 된 변수가 내 클래스 중 하나에서 인스턴스화 된 logger변수가 아니라 Python3 캐시에 있는 변수 라는 사실 때문에 로그 파일에서 증가한다고 말하고 싶습니다. , 핸들러는 내가 구성한 AppScheduler에 의해 60 초마다 추가되었습니다. 따라서 if not logger.handlers이것은 이러한 유형의 현상을 피하는 매우 현명한 방법입니다. 해결책을 주셔서 감사합니다, 동지 :)!
ivanleoncz

2
내 Flask 앱에서이 문제가 보입니다. 이 솔루션은 메인 플라스크 앱에서 생성 된 로그 메시지에 대한 문제를 해결했지만 내 앱 cals는 라이브러리 모듈에서 작동하며 해당 라이브러리의 메시지는 여전히 여러 번 로깅됩니다. 이 문제를 해결하는 방법을 모르겠습니다.
Cas

24

이 문제가 표시되고 핸들러를 두 번 추가하지 않으면 여기에서 abarnert의 답변을 참조 하십시오.

로부터 문서 :

참고 : 핸들러를 로거와 하나 이상의 상위 항목에 연결하면 동일한 레코드를 여러 번 내보낼 수 있습니다. 일반적으로 하나 이상의 로거에 핸들러를 연결할 필요가 없습니다. 로거 계층 구조에서 가장 높은 적절한 로거에 핸들러를 첨부하면 전파되는 경우 모든 하위 로거가 기록한 모든 이벤트를 볼 수 있습니다. 설정은 True로 설정되어 있습니다. 일반적인 시나리오는 처리기를 루트 로거에만 연결하고 전파가 나머지를 처리하도록하는 것입니다.

따라서 "test"에서 사용자 지정 처리기를 원하고 메시지가 루트 처리기로 전달되는 것을 원하지 않는 경우 대답은 간단합니다. 전파 플래그를 끕니다.

logger.propagate = 거짓


1
그게 최고의 답입니다. 포스터의 목적 (코딩의 논리적 오류)에는 맞지 않았지만 대부분의 경우에 해당됩니다.
Artem

브라보. 이것은 중복의 실제 원인입니다 (가장 일반적인 경우).
Mr. Duhart

이것은 또한 내 코드의 문제였습니다. 감사합니다.
harshit

최고의 답변입니다. 감사합니다!
Foivos Ts

8

핸들러는 외부에서 호출 할 때마다 추가됩니다. 작업을 마친 후 처리기를 제거해보십시오.

self.logger.removeHandler(ch)

1
나는 logger.handlers.pop() 파이썬 2.7에서 사용 했고, 트릭을한다
radtek

6

나는 파이썬 초보자이지만 이것은 나를 위해 일하는 것처럼 보였습니다 (Python 2.7)

while logger.handlers:
     logger.handlers.pop()

4

제 경우에는 logger.propagate = False이중 인쇄를 방지 하도록 설정하고 싶습니다 .

아래 코드에서 제거 logger.propagate = False하면 이중 인쇄가 표시됩니다.

import logging
from typing import Optional

_logger: Optional[logging.Logger] = None

def get_logger() -> logging.Logger:
    global _logger
    if _logger is None:
        raise RuntimeError('get_logger call made before logger was setup!')
    return _logger

def set_logger(name:str, level=logging.DEBUG) -> None:
    global _logger
    if _logger is not None:
        raise RuntimeError('_logger is already setup!')
    _logger = logging.getLogger(name)
    _logger.handlers.clear()
    _logger.setLevel(level)
    ch = logging.StreamHandler()
    ch.setLevel(level)
    # warnings.filterwarnings("ignore", "(Possibly )?corrupt EXIF data", UserWarning)
    ch.setFormatter(_get_formatter())
    _logger.addHandler(ch)
    _logger.propagate = False # otherwise root logger prints things again


def _get_formatter() -> logging.Formatter:
    return logging.Formatter(
        '[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')

이것이 내가 가진 문제입니다. 감사합니다
q0987

훌륭한 대답; 추가 logger.propagate = False는 Flask의 app.logger인스턴스에 로그인 할 때 Waitress가 호스팅하는 Flask 애플리케이션에서 이중 로그인을 방지하는 솔루션이었습니다 .
bluebinary

1

설치된 루트 핸들러가없는 경우 호출에 대한 logging.debug()호출 logging.basicConfig(). 그것은 테스트 케이스가 실행되는 순서를 제어 할 수없는 테스트 프레임 워크에서 저에게 일어났습니다. 내 초기화 코드는 두 번째 코드를 설치했습니다. 기본값은 내가 원하지 않는 logging.BASIC_FORMAT을 사용합니다.


나는 이것이 나를 위해 일어나는 일이라고 생각합니다. 콘솔 로거의 자동 생성을 어떻게 방지합니까?
Robert

@Robert는 첫 번째 로깅 호출 전에 원하는 로거로 초기화되었는지 확인하는 것입니다. 테스트 프레임 워크는이를 모호하게 만들 수 있지만이를 수행하는 방법이 있어야합니다. 또한 다중 처리를하는 경우 각 프로세스에 대해 동일한 작업을 수행해야합니다.
JimB

1

실수로 로거에 무언가를 출력하고 구성하면 너무 늦은 것 같습니다. 예를 들어, 내 코드에서

logging.warning("look out)"

...
ch = logging.StreamHandler(sys.stdout)
root = logging.getLogger()
root.addHandler(ch)

root.info("hello")

나는 (형식 옵션 무시)와 같은 것을 얻을 것입니다.

look out
hello
hello

모든 것이 stdout에 두 번 기록되었습니다. 첫 번째 호출이 logging.warning자동으로 새 처리기 를 만든 다음 명시 적으로 다른 처리기를 추가 했기 때문이라고 생각 합니다. 우연한 첫 번째 logging.warning전화를 제거했을 때 문제가 사라졌습니다 .


0

콘솔 로그가 두 배가되었지만 파일 로그는 그렇지 않은 이상한 상황이 발생했습니다. 많은 파고 끝에 나는 그것을 알아 냈습니다.

타사 패키지는 로거를 등록 할 수 있습니다. 이것은주의해야 할 사항입니다 (어떤 경우에는 예방할 수 없습니다). 대부분의 경우 타사 코드는 기존 루트 로거 핸들러 가 있는지 확인합니다 . 없는 경우 새 콘솔 핸들러를 등록합니다.

이에 대한 내 해결책은 루트 수준에서 콘솔 로거를 등록하는 것입니다.

rootLogger = logging.getLogger()  # note no text passed in--that's how we grab the root logger
if not rootLogger.handlers:
        ch = logging.StreamHandler()
        ch.setLevel(logging.INFO)
        ch.setFormatter(logging.Formatter('%(process)s|%(levelname)s] %(message)s'))
        rootLogger.addHandler(ch)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.