source

Python Logging을 사용하여 두 번 나타나는 로그 메시지

lovecheck 2023. 7. 16. 13:40
반응형

Python Logging을 사용하여 두 번 나타나는 로그 메시지

저는 파이썬 로깅을 사용하고 있는데, 어떤 이유에서인지 제 모든 메시지가 두 번 나타납니다.

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

# 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)

그리고 어떤 이유에서인지, 모든 메시지가 두 번 나타납니다.스트림 핸들러 중 한 명에 대해 의견을 제시했지만 여전히 동일합니다.좀 이상한 것 같은데, 왜 이런 일이 일어나는지 모르겠어요.제가 뭔가 명백한 것을 놓친 것 같습니다.

전화를 걸겠습니다.configure_logging 번 두번아 (도안에마아안)▁the에▁in▁()__init__Boy) :getLogger동일한 개체를 반환합니다.addHandler에서는 유사한 처리기가 이미 로거에 추가되었는지 확인하지 않습니다.

해당 메소드에 대한 호출을 추적하여 이 중 하나를 제거해 보십시오.아니면 깃발을 세우세요.logging_initialized로초화된기로 되었습니다.False에 시대에__init__Boy 변경 그고변화리configure_logginglogging_initialized이라True그리고 그것을 설정합니다.True로거를 초기화한 후.

에서 여러 개의 로램에서개경우는하성생를러를 생성하는 Boy예를 들어, 당신은 글로벌로 일을 하는 방식을 바꿔야 할 것입니다.configure_logging하는 것, 「 」는Boy.configure_logging only 메드합다니기화초.self.logger기여하다.

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

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

이 문제가 발생하고 핸들러를 두 번 추가하지 않으면 여기에서 바너트의 답변을 확인하십시오.

문서에서:

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

에 사용자 핸들러에도 할 수 . ""에 대한 사용자 지정 핸들러를 .propagate플래그:

logger.propagate = False

저는 파이썬 초보이지만, 이것은 저에게 효과가 있는 것 같았습니다(Python 2.7).

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

저 같은 경우에는.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')

외부에서 전화를 걸 때마다 처리기가 추가됩니다.작업을 마친 후 처리기 제거:

self.logger.removeHandler(ch)

이 문제는 상위 파일에서 로깅 개체를 생성하려는 경우에도 발생할 수 있습니다.예를 들어.이것은 기본 응용프로그램 파일입니다.test.py

import logging

# create logger
logger = logging.getLogger('simple_example')
logger.setLevel(logging.DEBUG)

# create console handler and set level to debug
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

# create formatter
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')

# add formatter to ch
ch.setFormatter(formatter)

# add ch to logger
logger.addHandler(ch)

def my_code():
# 'application' code
    logger.debug('debug message')
    logger.info('info message')
    logger.warning('warn message')
    logger.error('error message')
    logger.critical('critical message')

파일입니다.main.py

import test

test.my_code()

이 출력은 한 번만 인쇄됩니다.

2021-09-26 11:10:20,514 - simple_example - DEBUG - debug message
2021-09-26 11:10:20,514 - simple_example - INFO - info message
2021-09-26 11:10:20,514 - simple_example - WARNING - warn message
2021-09-26 11:10:20,514 - simple_example - ERROR - error message
2021-09-26 11:10:20,514 - simple_example - CRITICAL - critical message

하지만 상위 로깅 개체가 있으면 두 번 인쇄됩니다.예: 이 파일이 상위 파일인 경우

import test
import logging
logging.basicConfig(level=logging.DEBUG,format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')


test.my_code()

출력은 다음과 같습니다.

2021-09-26 11:16:28,679 - simple_example - DEBUG - debug message
2021-09-26 11:16:28,679 - simple_example - DEBUG - debug message
2021-09-26 11:16:28,679 - simple_example - INFO - info message
2021-09-26 11:16:28,679 - simple_example - INFO - info message
2021-09-26 11:16:28,679 - simple_example - WARNING - warn message
2021-09-26 11:16:28,679 - simple_example - WARNING - warn message
2021-09-26 11:16:28,679 - simple_example - ERROR - error message
2021-09-26 11:16:28,679 - simple_example - ERROR - error message
2021-09-26 11:16:28,679 - simple_example - CRITICAL - critical message
2021-09-26 11:16:28,679 - simple_example - CRITICAL - critical message

다른 사용자의 유용한 응답을 추가하는 중...

실수로 로거에 두 개 이상의 핸들러를 구성하지 않은 경우:

로거에 조상(루트 로거는 항상 하나)이 있고 고유한 핸들러가 있으면 로거가 출력할 때(기본적으로) 이러한 핸들러도 출력되므로 중복 항목이 생성됩니다.두 가지 옵션이 있습니다.

  • 다음을 설정하여 로그 이벤트를 조상에게 전파하지 마십시오.
my_logger.propagate = False
  • 하나의 상위(루트 로거)만 있는 경우 대신 해당 처리기를 직접 구성할 수 있습니다.예:
# directly change the formatting of root's handler
root_logger = logging.getLogger()
roots_handler = root_logger.handlers[0]
roots_handler.setFormatter(logging.Formatter(': %(message)s')) # change format

my_logger = logging.getLogger('my_logger') # my_logger will use new formatting

:logging.debug() 출들logging.basicConfig()루트 핸들러가 설치되어 있지 않은 경우.그것은 제가 테스트 케이스가 발생하는 순서를 제어할 수 없는 테스트 프레임워크에서 일어나고 있었습니다.제 초기화 코드는 두 번째 코드를 설치하는 것이었습니다.기본값은 내가 원하지 않는 logging.BASIC_FORMAT 형식입니다.

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

logging.warning("look out)"

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

root.info("hello")

(포맷 옵션 무시)와 같은 것을 얻을 수 있습니다.

look out
hello
hello

그리고 모든 것이 두 번이나 눈에 띄도록 쓰여 있었습니다.저는 이것이 첫 번째 전화이기 때문이라고 믿습니다.logging.warning새 핸들러를 자동으로 생성한 다음 다른 핸들러를 명시적으로 추가했습니다.사고가 난 것을 먼저 제거하자 문제가 사라졌습니다.logging.warning콜.콜.

저는 여러 프로세스의 맥락에서 동일한 문제와 씨름하고 있었습니다. (코드에 대해서는 제가 거의 말 그대로 따르던 문서를 참조하십시오.)즉, 하위 프로세스에서 발생한 모든 로그 메시지가 중복되었습니다.

내 실수는 전화를 한 것입니다.worker_configurer(),

def worker_configurer(logging_queue):
    queue_handler = logging.handlers.QueueHandler(logging_queue)
    root = logging.getLogger()
    root.addHandler(queue_handler)
    root.setLevel(level)

하위 프로세스와 주요 프로세스 모두에서 (주 프로세스도 로그 항목을 기록하기를 원했기 때문에).Linux 시스템에서 문제가 발생한 이유는 Linux에서 자식 프로세스가 포크를 통해 시작되었기 때문에 기본 프로세스에서 기존 로그 핸들러를 상속했기 때문입니다.즉, 리눅스에서QueueHandler두 번 등록되었습니다.

그럼 이제 막자, 하는것방.QueueHandler두 번 등록되는 것으로부터.worker_configurer()기능은 보이는 것만큼 사소한 것이 아닙니다.

  • 로거와 입니다.root다가 나다handlers문서화되지 않은 속성입니다.

  • 으로는, 제경상, 테는트의 여부를 입니다.any([handler is queue_handler for handler in root.handlers]) 또는 (으)ㄴany([handler == queue_handler for handler in root.handlers]) 후 합니다.root.handlers한 겉에는동내포니다합함을이 되어 있습니다.QueueHandler두은 (으)로 줄여 쓸 수 있습니다.queue_handler in root.handlers래 부터.in연산자는 목록의 경우 동일성과 동일성을 모두 확인합니다.)

  • 같은 되므로 루트로와는패수의정니다됩해에지같키은거▁thetest▁like.root.handlers그리고.root.hasHandlers()처음부터 신뢰할 수 없습니다. (결국 글로벌 상태입니다.)

따라서 이러한 종류의 다중 처리 버그를 처음부터 방지하기 위해 포킹을 산란으로 대체하는 것이 가장 좋은 해결책입니다(물론 추가 메모리 설치 공간을 사용할 수 있다면).또는 다른 대안을 사용하는 것.logging나는 있어요… :) :) ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅜㅠ

그 말을 듣고 저는 결국 다소 사소한 검사를 받게 되었습니다.

def worker_configurer(logging_queue):
    queue_handler = logging.handlers.QueueHandler(logging_queue)
    root = logging.getLogger()

    for handler in root.handlers:
        if isinstance(handler, logging.handlers.QueueHandler):
            return

    root.addHandler(queue_handler)
    root.setLevel(level)

분명히, 제가 다른 곳에 두 번째 대기열 처리기를 등록하기로 결정하는 순간 이것은 끔찍한 부작용을 일으킬 것입니다.

문서에서:

"로거에는 다음과 같은 속성과 방법이 있습니다.Logger는 직접 인스턴스화되지 않아야 하며 항상 모듈 수준 함수 logging.getLogger(이름)를 통해 인스턴스화되어야 합니다.같은 이름의 getLogger()를 여러 번 호출하면 항상 동일한 Logger 개체에 대한 참조가 반환됩니다."

. 는 로거를 하는 것이 . 로거를 초기화하는 것이 좋습니다.__name__ " " " (으)로 합니다.

import logging
logger = logging.getLogger(__name__)

참고: 동일한 이름의 다른 모듈에서 로거를 시작하더라도 동일한 로거가 계속 제공되므로 다음을 호출합니다.logger.info('somthing')로거 클래스를 시작한 횟수만큼 기록됩니다.

저는 많은 것을 시도했고 마침내 맞춤형 로거를 생각해냈습니다.당신은 그저 해야 합니다.copy그리고.paste코드로 입력하면 필요한 작업을 수행할 수 있습니다.

사용해야 하는 이유

  1. 반복 로그가 없습니다.
  2. 정보 로그에 대한 별도의 사용자 지정 - [경로 및 불필요한 세부 정보 포함 안 함]
  3. 다양한 색상에서 다양한 로그 레벨.
  4. [info, warning, error, critical, debug,...]과 같은 다양한 수준의 로그가 모두 표시됩니다.
import logging


class CustomFormatter(logging.Formatter):
    def __init__(self) -> None:
        super().__init__()

    def format(self, record):
        if record.levelno == logging.INFO:
            self._style._fmt = "%(levelname)-5s  %(asctime)-3s %(message)s"
        else:
            color = {
                logging.WARNING: 33,
                logging.ERROR: 31,
                logging.FATAL: 31,
                # logging.DEBUG: 36
            }.get(record.levelno, 0)
            self._style._fmt = f'\033[{color}m%(levelname)-5s %(asctime)s file/"%(pathname)10s", line %(lineno)d in "%(funcName)s" -> "%(message)s" \033[0m'
        return super().format(record)


logger = logging.getLogger("my-logger")
if not logger.handlers:
    handler = logging.StreamHandler()
    handler.setFormatter(CustomFormatter())
    logger.addHandler(handler)
    logger.setLevel(logging.DEBUG)
    logger.propagate = False
    # to disable any level of logs; eg - disabled debug level logs.
    logging.disable(logging.DEBUG)

제공되는 내용 [*콜솔/로그 파일에 다양한 색상이 포함됨]

logger.info (이것은 정보 로그입니다")

INFO 2023-03-24 20:11:51,230 인포로그입니다.

logger.warning(로거경고 로그입니다.")

WARNING 2023-03-24 20:11:51,230 file/"/home/xyz/abc", 25행 "my_function" -> "이것은 경고 로그입니다"

logger.error("이것은 오류 로그입니다.")

ERROR 2023-03-24 20:11:51,230 file/"/home/xyz/abc", "my_function" 26행 -> "이것은 오류 로그입니다"

logger.critical("이 로그는 중요 로그입니다.")

Critical 2023-03-24 20:11:51,231 file/"/home/xyz/abc", "my_function"의 27행 -> "This is critical log"

벌목꾼디버그 로그입니다.")

DEBUG 2023-03-24 20:11:51,231 file/"/home/xyz/abc", "my_function"의 28행 -> "디버깅 로그입니다"

참고 - 주석을 사용하거나 줄을 제거하기 위해 위 코드에서 디버그 수준 로그를 비활성화했습니다.

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

타사 패키지는 로거를 등록할 수 있습니다.이는 주의해야 할 사항이며, 경우에 따라 예방할 수 없습니다.대부분의 경우 타사 코드는 기존 루트 로거 처리기가 있는지 확인하고, 없으면 새 콘솔 처리기를 등록합니다.

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

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)

하는 경우 " " "는 " " 입니다.log.conf

에서 이 을 .conf 파일에 .[logger_myLogger] 섹션:propagate=0

[logger_myLogger]
level=DEBUG
handlers=validate,consoleHandler
qualname=VALIDATOR
propagate=0

표준 구조를 사용하는 경우logger = logging.getLogger('mymodule')로 실로잘못타이다한음에핑수ype다에▁and음▁then한▁accident핑ally.loggger~하듯이logging를 들어 예를 들어.

logger = logging.getLogger('mymodule')

# configure your handlers

logger.info("my info message")  # won't make duplicate 
logging.info("my info message")  # will make duplicate logs

그러면 다음으로 전화가 걸려오기 때문에 중복된 메시지가 표시됩니다.logging새 로거를 만듭니다.

저도 같은 문제가 있었습니다.저의 경우, 핸들러나 중복된 초기 구성 때문이 아니라 바보 같은 오타였습니다.main.py제가 사용한 것은logger이지만 에반는에 있습니다.my_tool.py저는 직접 전화를 걸고 있었습니다.logging실수로 했기 때문에 실로모듈, 따서함호에서 my_tool모듈은 모든 것이 엉망이 되었고 메시지가 중복된 것처럼 보였습니다.

다음은 코드였습니다.

main.py

import logging
import my_tool

logger_name = "cli"
logger = logging.getLogger(logger_name)

logger.info("potato")
logger.debug("potato)
my_tool.function()
logger.info("tomato")

my_tool.py

import logging
logger_name = "cli"
logger = logging.getLogger(logger_name)
# some code
logging.info("carrot")

그리고 그 결과

terminal

>> potato
>> potato
>> carrot
>> tomato
>> tomato

언급URL : https://stackoverflow.com/questions/6729268/log-messages-appearing-twice-with-python-logging

반응형