Python 로깅 모듈을 사용할 때 중복 로그 출력


105

파이썬 로거를 사용하고 있습니다. 다음은 내 코드입니다.

import os
import time
import datetime
import logging
class Logger :
   def myLogger(self):
      logger = logging.getLogger('ProvisioningPython')
      logger.setLevel(logging.DEBUG)
      now = datetime.datetime.now()
      handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
      formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
      handler.setFormatter(formatter)
      logger.addHandler(handler)
      return logger

내가 가진 문제는 각 logger.info호출 에 대해 로그 파일에 여러 항목이 있다는 것 입니다. 어떻게 해결할 수 있습니까?


나를 위해 작동합니다. Python 3.2 및 Windows XP.
Zuljin

2
로거 인스턴스를 여러 개 생성하지 않습니까?
Gandi

예. 다른 파일에서 Java 프로젝트에서했던 것처럼 새 인스턴스를 사용하고 있습니다. 그것이 문제를 일으키는 지 여부를 지정하십시오.
user865438

답변:


94

logging.getLogger()이미 싱글이다. ( 문서 )

문제는를 호출 할 때마다 myLogger()인스턴스에 다른 핸들러를 추가하여 로그가 중복된다는 것입니다.

아마도 이런 건가요?

import os
import time
import datetime
import logging

loggers = {}

def myLogger(name):
    global loggers

    if loggers.get(name):
        return loggers.get(name)
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            '/root/credentials/Logs/ProvisioningPython' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers[name] = logger

        return logger

3
대신 loggers.update (dict ((name, logger))) 있어야한다고 생각합니다.
고소 공포증

loggers.update(dict(name=logger))? loggers[name] = logger더 간단 하지 않습니까?
Ryan J McCall

@RyanJMcCall 당시 내가 사용한 코딩 규칙이었습니다. 그러나 지금의 코드를 검토해 보면 깨 졌음을 알 수 있습니다. loggers.update(dict(name=logger))라는 단일 키로 사전을 만들고 name동일한 키를 지속적으로 업데이트합니다. 이 코드가 상당히 손상 되었기 때문에 아무도 이전에 이것을 언급하지 않은 것에 놀랐습니다. :) 필요한 수정을 할 것입니다.
Werner Smit

나는 @acrophobia가이 시대 전에 피할 수 있었던 것을 봅니다. 감사.
Werner Smit

전역 loggers사전이 중복 되지 logging.getLogger않습니까? 핸들러를 추가하는 것을 정말로 피하고 싶기 때문에 핸들러를 직접 확인하는 아래 답변을 선호하는 것 같습니다
mway

60

Python 3.2부터는 핸들러가 이미 있는지 확인하고 있다면 새 핸들러를 추가하기 전에 지우면됩니다. 이것은 디버깅 할 때 매우 편리하며 코드에는 로거 초기화가 포함됩니다.

if (logger.hasHandlers()):
    logger.handlers.clear()

logger.addHandler(handler)

좋은 답변, 들으 :))
가브리엘 코헨

2
hasHandlers ()는 로컬 / 커스텀 핸들러가 아직 추가되지 않은 경우에도 핸들러가 루트 로거에 추가 된 pytest에서 true를 반환합니다. 이 경우 len (logger.handlers) (Guillaume의 답변에 따라)는 0을 반환하므로 더 나은 옵션이 될 수 있습니다.
Grant

이것은 내가 찾던 진정한 해결책입니다.
XCanG

45
import datetime
import logging
class Logger :
    def myLogger(self):
       logger=logging.getLogger('ProvisioningPython')
       if not len(logger.handlers):
          logger.setLevel(logging.DEBUG)
          now = datetime.datetime.now()
          handler=logging.FileHandler('/root/credentials/Logs/ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
          formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
          handler.setFormatter(formatter)
          logger.addHandler(handler)
        return logger

나를 위해 속임수를 만들었다

파이썬 2.7 사용


1
이것은 모듈이 다시로드 된 경우에도 작동합니다 (다른 답변의 경우는 아님)
yco

3
팁을 주셔서 감사합니다. 목록이 비어 있는지 여부를 확인하는 BTW는 "len"연산자를 사용할 필요가 없습니다. my_list : ..
rkachach aug

26

난 이미 사용 loggerA와 싱글 및 확인 if not len(logger.handlers),하지만 여전히 중복을 가지고 : 그것은는 포맷 된 출력했다 포맷되지 않은 하였다.

제 경우에는 해결책 : logger.propagate = False

이 답변문서에 대한 크레딧 .


1
두 배의 로깅이 RootLogger와 StreamHandler에서 온다는 것을 알았지 만이 작업을 수행 할 때까지 (내 포맷터를 StreamHandler에 유지하면서) 문제를 해결할 수 없었습니다.
Xander YzWich

10

Logger.myLogger()두 번 이상 전화 하고 있습니다. 반환하는 로거 인스턴스를 어딘가에 저장하고 다시 사용 합니다 .

또한 핸들러를 추가하기 전에 로그인하면 기본값 StreamHandler(sys.stderr)이 생성됩니다.


실제로 Java에서 사용하는 로거 인스턴스에 액세스하려고하지만 전체 프로젝트에 대해 인스턴스를 한 번만 만들어야하는지 여부는 알 수 없습니다.
user865438

1
@ user865483 : 한 번만. 모든 표준 라이브러리 로거는 싱글 톤입니다.
Matt Joiner

5

이것은 @ rm957377의 답변에 추가 되었지만 왜 이런 일이 발생하는지에 대한 설명이 있습니다. AWS에서 람다 함수를 실행하면 여러 호출에 대해 살아있는 래핑 인스턴스 내에서 함수를 호출합니다. 즉, addHandler()함수 코드 내에서 호출 하면 함수가 실행될 때마다 로깅 싱글 톤에 중복 처리기가 계속 추가됩니다. 로깅 싱글 톤은 람다 함수의 여러 호출을 통해 유지됩니다.

이를 해결하기 위해 다음을 통해 설정하기 전에 핸들러를 지울 수 있습니다.

logging.getLogger().handlers.clear()
logging.getLogger().addHandler(...)

어떻게 든 내 경우에는 .info()내가 이해하지 못하는 호출시 이벤트에 로거 핸들러가 추가됩니다 .
Evgeny

4

로거는 싱글 톤으로 작동해야합니다. 두 번 이상 생성하면 안됩니다. 다음은 어떻게 보이는지 예입니다.

import os
import time
import datetime
import logging
class Logger :
    logger = None
    def myLogger(self):
        if None == self.logger:
            self.logger=logging.getLogger('ProvisioningPython')
            self.logger.setLevel(logging.DEBUG)
            now = datetime.datetime.now()
            handler=logging.FileHandler('ProvisioningPython'+ now.strftime("%Y-%m-%d") +'.log')
            formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
            handler.setFormatter(formatter)
            self.logger.addHandler(handler)
        return self.logger

s = Logger()
m = s.myLogger()
m2 = s.myLogger()
m.info("Info1")
m2.info("info2")

다시 다른 파일에서 다른 인스턴스를 가져 오려면 파일 1 s = Logger () m = s.myLogger () 및 파일 2 s = Logger () 작동 여부 m2 = s.myLogger ()
user865438

여전히 동일한 로그의 사본을 여러 번 받고 있습니다. 내부 스레드 로그가 하나 이상을 인쇄하는지 여부는 의심 스럽습니다. 이걸 도와주세요.
user865438 2011-08-24

1
@ user865438, 우리는 구현을 싱글 톤으로 만드는 것에 대해 걱정할 필요가 없습니다 (이미 그렇습니다). 하위 모듈에 로그인하려면 공식 Logging Cookbook 링크를 따르십시오 . 기본적으로 로거의 이름을 지정하는 동안 이름 지정 계층을 따라야하며 나머지는 처리됩니다.
narayan

4

로거의 구현은 이미 싱글 톤입니다.

logging.getLogger ( 'someLogger')를 여러 번 호출하면 동일한 로거 객체에 대한 참조가 반환됩니다. 이는 동일한 모듈 내 에서뿐만 아니라 동일한 Python 인터프리터 프로세스에있는 한 모듈 간에도 마찬가지입니다. 동일한 객체에 대한 참조에 해당됩니다. 또한 응용 프로그램 코드는 하나의 모듈에서 부모 로거를 정의 및 구성하고 별도의 모듈에서 자식 로거를 생성 (구성은 아님) 할 수 있으며 자식에 대한 모든 로거 호출은 부모에게 전달됩니다. 다음은 주요 모듈입니다.

소스- 여러 모듈에서 로깅 사용

그래서 이것을 활용하는 방법은-

메인 모듈에 'main_logger' 라는 로거를 만들고 구성했다고 가정 해 보겠습니다 (단순히 로거를 구성하고 아무것도 반환하지 않음).

# get the logger instance
logger = logging.getLogger("main_logger")
# configuration follows
...

이제 하위 모듈에서 'main_logger.sub_module_logger' 명명 계층 구조에 따라 하위 로거를 만들면 하위 모듈에서 구성 할 필요가 없습니다. 명명 계층 구조에 따라 로거를 생성하는 것만으로도 충분합니다.

# get the logger instance
logger = logging.getLogger("main_logger.sub_module_logger")
# no configuration needed
# it inherits the configuration from the parent logger
...

그리고 중복 핸들러도 추가하지 않습니다.

좀 더 자세한 답변 은 질문을 참조하십시오 .


1
getLogger가 나를 위해 작동하는 것 같으면 핸들러를 재정의합니다. logger = logging.getLogger('my_logger') ; logger.handlers = [logger.handlers[0], ]
radtek

2

더블 (또는 재로드 횟수에 따라 트리플 또는 ..-) 로거 출력은 importlib.reload(허용 된 답변에 설명 된 것과 같은 이유로)를 통해 모듈을 다시로드 할 때도 발생할 수 있습니다 . 내 출력이 왜 dupli (triple) cated되는지 알아내는 데 시간이 오래 걸렸기 때문에 향후 참조를 위해이 답변을 추가하고 있습니다.


1

한 가지 간단한 해결 방법은 다음과 같습니다.

logger.handlers[:] = [handler]

이렇게하면 기본 목록 "핸들러"에 새 처리기를 추가하지 않아도됩니다.


1

이러한 상황이 발생하는 대부분의 경우 결론은 모듈 당 한 번만 logger.getLogger ()를 호출하면됩니다. 내가 한 것처럼 여러 클래스가 있으면 다음과 같이 부를 수 있습니다.

LOGGER = logger.getLogger(__name__)

class MyClass1:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 1 initialized')

class MyClass2:
    log = LOGGER
    def __init__(self):
        self.log.debug('class 2 initialized')

그러면 둘 다 자신의 전체 패키지 이름과 기록 된 방법을 갖게됩니다.


0

특정 로거에 대한 모든 핸들러의 목록을 가져올 수 있으므로 다음과 같이 할 수 있습니다.

logger = logging.getLogger(logger_name)
handler_installed = False
for handler in logger:
    # Here your condition to check for handler presence
    if isinstance(handler, logging.FileHandler) and handler.baseFilename == log_filename:
        handler_installed = True
        break

if not handler_installed:
    logger.addHandler(your_handler)

위의 예에서 지정된 파일의 핸들러가 이미 로거에 연결되어 있는지 확인하지만 모든 핸들러 목록에 액세스하면 다른 핸들러를 추가해야하는 기준을 결정할 수 있습니다.


0

오늘이 문제가 있었다. 내 함수가 @staticmethod 였기 때문에 위의 제안은 random ()으로 해결되었습니다.

다음과 같이 보입니다.

import random

logger = logging.getLogger('ProvisioningPython.{}'.format(random.random()))

-1
from logging.handlers import RotatingFileHandler
import logging
import datetime

# stores all the existing loggers
loggers = {}

def get_logger(name):

    # if a logger exists, return that logger, else create a new one
    global loggers
    if name in loggers.keys():
        return loggers[name]
    else:
        logger = logging.getLogger(name)
        logger.setLevel(logging.DEBUG)
        now = datetime.datetime.now()
        handler = logging.FileHandler(
            'path_of_your_log_file' 
            + now.strftime("%Y-%m-%d") 
            + '.log')
        formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
        handler.setFormatter(formatter)
        logger.addHandler(handler)
        loggers.update(dict(name=logger))
        return logger

이 답변을 장기간 사용하는 데 더 유용하게 사용할 수 있도록 설명을 추가하십시오.
Aminah Nuraini
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.