mensagens de log que aparecem duas vezes com o Python Logging

100

Estou usando o log Python e, por algum motivo, todas as minhas mensagens estão aparecendo duas vezes.

Eu tenho um módulo para configurar o registro:

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

Mais tarde, chamo este método para configurar o registro:

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

E então, digamos, o módulo buy_ham, eu chamaria:

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

E por algum motivo, todas as mensagens estão aparecendo duas vezes. Comentei um dos manipuladores de fluxo, ainda é a mesma coisa. Um pouco estranho, não sei por que isso está acontecendo ... lol. Supondo que eu tenha esquecido algo óbvio.

Saúde, Victor

victorhooi
fonte
1
Tem certeza de que configure_logging()não é chamado duas vezes (por exemplo, do construtor também)? Apenas uma instância de Boy () foi criada?
Jacek Konieczny
1
Usar, em self.logger.handlers = [ch]vez disso, resolveria esse problema, embora seja melhor apenas garantir que você não execute este código duas vezes, por exemplo, usando if not self.loggerno início.
Ninjakannon de

Respostas:

133

Você está chamando configure_loggingduas vezes (talvez no __init__método de Boy): getLoggerretornará o mesmo objeto, mas addHandlernão verifica se um manipulador semelhante já foi adicionado ao logger.

Tente rastrear chamadas para esse método e eliminar um deles. Ou configure um sinalizador logging_initializedinicializado com Falseno __init__método de Boye mude configure_loggingpara não fazer nada se logging_initializedfor True, e configure-o para Truedepois de inicializar o registrador.

Se o seu programa criar várias Boyinstâncias, você terá que mudar a maneira como faz as coisas com uma configure_loggingfunção global adicionando os manipuladores e o Boy.configure_loggingmétodo apenas inicializando o self.loggeratributo.

Outra maneira de resolver isso é verificar o atributo handlers do seu logger:

logger = logging.getLogger('my_logger')
if not logger.handlers:
    # create the handlers and call logger.addHandler(logging_handler)
maca alex
fonte
1
Sim, você estava certo - sou bobo. Eu chamei no init , bem como explicitamente em outro lugar. Ri muito. Obrigado =).
victorhooi
Obrigado. Sua solução me salvou hoje.
ForeverLearner
1
No meu caso, eles apareceram 6 vezes. Suspeitei disso porque declarei o mesmo tipo de registrador em 6 classes
opa
5
Gostaria de compartilhar aqui minha experiência: para um aplicativo Flask que desenvolvi, as mensagens de log estavam aparecendo MAIS DE DUAS VEZES. Eu diria que eles estavam incrementando no arquivo de log, devido ao fato de que, quando a aplicação e os módulos foram carregados, a loggervariável utilizada, não foi a instanciada de uma das minhas classes, mas a loggervariável presente no cache do Python3 , e o manipulador foi adicionado a cada 60 segundos por um AppScheduler que eu configurei. Então, essa if not logger.handlersé uma maneira muito inteligente de evitar esse tipo de fenômeno. Obrigado pela solução, camarada :)!
ivanleoncz
2
Estou vendo esse problema no meu aplicativo Flask. Esta solução corrigiu o problema das mensagens de log geradas no aplicativo flask principal, mas meu aplicativo cals funções em um módulo de biblioteca e essas mensagens dessa biblioteca ainda são registradas várias vezes. Eu não sei como consertar isso.
Cas
24

Se você está vendo este problema e não está adicionando o manipulador duas vezes, veja a resposta de abarnert aqui

Dos documentos :

Nota: Se você anexar um manipulador a um logger e um ou mais de seus ancestrais, ele pode emitir o mesmo registro várias vezes. Em geral, você não deve precisar anexar um manipulador a mais de um registrador - se você apenas anexá-lo ao registrador apropriado, que é o mais alto na hierarquia do registrador, ele verá todos os eventos registrados por todos os registradores descendentes, desde que se propaguem configuração é deixada definida como True. Um cenário comum é anexar manipuladores apenas ao logger raiz e deixar a propagação cuidar do resto.

Portanto, se você deseja um manipulador personalizado em "teste" e não quer que suas mensagens também vão para o manipulador raiz, a resposta é simples: desative seu sinalizador de propagação:

logger.propagate = False

ouriço demente
fonte
1
Essa é a melhor resposta. Não se adequava ao propósito do pôster (erro lógico na codificação), mas na maioria das vezes, deveria ser o caso.
Artem
Bravo. ESTA é a causa real das duplicatas (para os casos mais gerais).
Sr. Duhart
Esse era o problema com meu código também. Muito obrigado.
severo
Melhor resposta de todas. Obrigado!
Foivos Ts
8

O manipulador é adicionado cada vez que você liga de fora. Tente remover o manipulador depois de terminar seu trabalho:

self.logger.removeHandler(ch)
Mayukh Roy
fonte
1
Eu usei logger.handlers.pop() em python 2.7, faz o truque
radtek
6

Eu sou um novato em Python, mas isso pareceu funcionar para mim (Python 2.7)

while logger.handlers:
     logger.handlers.pop()
Gregory Ponto
fonte
4

No meu caso, devo definir logger.propagate = Falsepara evitar impressão dupla.

No código abaixo, se você remover logger.propagate = False, verá uma impressão dupla.

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')
Shital Shah
fonte
Este é o problema que tenho. Obrigado
q0987
Ótima resposta; adicionar logger.propagate = Falsefoi a solução para evitar o registro duplo em um aplicativo Flask hospedado por Waitress, ao fazer login na app.loggerinstância do Flask .
bluebinary de
1

Uma chamada para logging.debug()chamadas logging.basicConfig()se não houver manipuladores raiz instalados. Isso estava acontecendo comigo em uma estrutura de teste onde eu não conseguia controlar a ordem de disparo dos casos de teste. Meu código de inicialização estava instalando o segundo. O padrão usa o log.BASIC_FORMAT que eu não queria.

JimB
fonte
Acho que é isso que está acontecendo comigo. Como você evita a criação automática de registradores de console?
Robert
@Robert trata de certificar-se de que você foi inicializado com o logger desejado, antes da primeira chamada de registro. Os frameworks de teste podem obscurecer isso, mas deve haver uma maneira de fazer isso. Além disso, se você estiver multiprocessando, terá que fazer o mesmo com cada processo.
JimB
1

Parece que se você enviar algo para o logger (acidentalmente) e depois configurá-lo, é tarde demais. Por exemplo, em meu código eu tinha

logging.warning("look out)"

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

root.info("hello")

Eu obteria algo como (ignorando as opções de formato)

look out
hello
hello

e tudo foi escrito para stdout duas vezes. Acredito que isso seja porque a primeira chamada para logging.warningcria um novo manipulador automaticamente e, em seguida, adicionei explicitamente outro manipulador. O problema foi embora quando removi a primeira logging.warningchamada acidental .

Mark Lakata
fonte
0

Eu estava tendo uma situação estranha onde os logs do console foram duplicados, mas os logs dos meus arquivos não. Depois de uma tonelada de escavações, descobri.

Esteja ciente de que pacotes de terceiros podem registrar loggers. Isso é algo que deve ser observado (e em alguns casos não pode ser evitado). Em muitos casos, o código de terceiros verifica se há algum gerenciador de log de raiz existente ; e se não houver - eles registram um novo manipulador de console.

Minha solução para isso foi registrar meu logger de console no nível raiz:

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)
Robert
fonte