Usando o login em vários módulos


257

Eu tenho um pequeno projeto python que tem a seguinte estrutura -

Project 
 -- pkg01
   -- test01.py
 -- pkg02
   -- test02.py
 -- logging.conf

Planejo usar o módulo de log padrão para imprimir mensagens no stdout e em um arquivo de log. Para usar o módulo de log, é necessária alguma inicialização -

import logging.config

logging.config.fileConfig('logging.conf')
logger = logging.getLogger('pyApp')

logger.info('testing')

No momento, eu executo essa inicialização em todos os módulos antes de começar a registrar mensagens. É possível executar essa inicialização apenas uma vez em um local, para que as mesmas configurações sejam reutilizadas registrando-se em todo o projeto?


3
Em resposta ao seu comentário sobre a minha resposta: você não precisa chamar fileConfigtodos os módulos que efetuam log, a menos que tenha if __name__ == '__main__'lógica em todos eles. A resposta de prost não é uma boa prática se o pacote for uma biblioteca, embora possa funcionar para você - não se deve configurar o logon em pacotes da biblioteca, exceto adicionar a NullHandler.
Vinay Sajip

1
O prost implicava que precisamos chamar os stmts de importação e logger em todos os módulos e apenas chamar o stmt de arquivo de configuração no módulo principal. não é semelhante ao que você está dizendo?
Quest Monger

6
prost está dizendo que você deve inserir o código de configuração de log package/__init__.py. Normalmente, esse não é o lugar onde você coloca o if __name__ == '__main__'código. Além disso, o exemplo de prost parece chamar o código de configuração incondicionalmente na importação, o que não parece correto para mim. Geralmente, o código de configuração de log deve ser feito em um único local e não deve ocorrer como efeito colateral da importação, exceto quando você estiver importando __main__.
Vinay Sajip

você está certo, eu perdi totalmente a linha '# package / __ init__.py' em seu exemplo de código. obrigado por salientar isso e sua paciência.
Quest Monger

1
Então, o que acontece se você tiver vários if __name__ == '__main__'? (não é mencionada explicitamente em questão, se este for o caso)
kon Psych

Respostas:


293

A melhor prática é, em cada módulo, ter um criador de logs definido assim:

import logging
logger = logging.getLogger(__name__)

próximo à parte superior do módulo e, em seguida, em outro código do módulo, por exemplo,

logger.debug('My message with %s', 'variable data')

Se você precisar subdividir a atividade de log dentro de um módulo, use, por exemplo,

loggerA = logging.getLogger(__name__ + '.A')
loggerB = logging.getLogger(__name__ + '.B')

e faça logon loggerAe loggerBconforme apropriado.

No seu programa ou programas principais, faça, por exemplo:

def main():
    "your program code"

if __name__ == '__main__':
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    main()

ou

def main():
    import logging.config
    logging.config.fileConfig('/path/to/logging.conf')
    # your program code

if __name__ == '__main__':
    main()

Veja aqui o log de vários módulos e aqui a configuração do log de código que será usado como um módulo de biblioteca por outro código.

Atualização: ao ligar fileConfig(), convém especificar disable_existing_loggers=Falsese você está usando o Python 2.6 ou posterior (consulte os documentos para obter mais informações). O valor padrão é Truepara compatibilidade com versões anteriores, que faz com que todos os criadores de logs existentes sejam desativados, a fileConfig()menos que eles ou seus ancestrais sejam explicitamente nomeados na configuração. Com o valor definido como False, os registradores existentes são deixados em paz. Se você estiver usando o Python 2.7 / Python 3.2 ou posterior, considere a dictConfig()API que é melhor do fileConfig()que ela oferece mais controle sobre a configuração.


21
se você olhar para o meu exemplo, eu já estou fazendo o que você sugere acima. minha pergunta era como centralizar essa inicialização de log, de modo que não precise repetir essas três instruções. Além disso, no seu exemplo, você perdeu o stmt 'logging.config.fileConfig (' logging.conf ')'. este stmt é realmente a causa raiz da minha preocupação. você vê, se eu tiver iniciado o logger em cada módulo, eu teria que digitar este stmt em cada módulo. isso significaria rastrear o caminho do arquivo conf em todos os módulos, o que não me parece uma prática recomendada (imagine o caos ao alterar a localização dos módulos / pacotes).
Quest Monger

4
Se você chamar fileConfig após criar o logger, seja no mesmo ou em outro módulo (por exemplo, quando você cria o logger na parte superior do arquivo) não funciona. A configuração de registro se aplica apenas aos criadores criados depois. Portanto, essa abordagem não funciona ou não é uma opção viável para vários módulos. @Quest Monger: Você sempre pode criar outro arquivo que contém o local do arquivo de configuração ..;)
Vincent Ketelaars

2
@ Oxidator: Não necessariamente - veja a disable_existing_loggersbandeira que está Truepor padrão, mas pode ser configurada como False.
Vinay Sajip 12/09

1
@ Vinay Sajip, obrigado. Você tem recomendações para registradores que trabalham em módulos, mas também fora das classes? Como as importações são feitas antes da função principal ser chamada, esses logs já foram registrados. Eu acho que configurar o seu logger antes que todas as importações no módulo principal sejam a única maneira? Esse criador de logs pode ser substituído, em geral, se você preferir.
Vincent Ketelaars

1
Se eu quiser que todos os registradores específicos do meu módulo tenham um nível de registro diferente do padrão WARNING, terei que fazer essa configuração em cada módulo? Digamos, quero ter todos os meus módulos registrados em INFO.
Raj

128

Na verdade, cada criador de logs é filho do criador de pacotes dos pais (isto é, package.subpackage.moduleherda a configuração do package.subpackage), portanto, tudo o que você precisa fazer é apenas configurar o criador de logs raiz. Isso pode ser alcançado por logging.config.fileConfig(sua própria configuração para criadores de logging.basicConfiglogs) ou (define o criador de logs raiz) O log de instalação no seu módulo de entrada ( __main__.pyou o que você deseja executar, por exemplo main_script.py. __init__.pyTambém funciona)

usando basicConfig:

# package/__main__.py
import logging
import sys

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

usando fileConfig:

# package/__main__.py
import logging
import logging.config

logging.config.fileConfig('logging.conf')

e crie cada registrador usando:

# package/submodule.py
# or
# package/subpackage/submodule.py
import logging
log = logging.getLogger(__name__)

log.info("Hello logging!")

Para mais informações, consulte Tutorial Avançado de Log .


15
essa é, de longe, a solução mais simples para o problema, sem mencionar que expõe e potencializa a relação pai-filho entre os módulos, algo que eu, como noob, desconhecia. Danke.
Quest Monger

você está certo. e, como vinay apontou em seu post, sua solução está correta desde que não esteja no módulo init .py. sua solução funcionou quando eu a apliquei no módulo principal (ponto de entrada).
Quest Monger

2
resposta realmente muito mais relevante, pois a pergunta está relacionada a módulos separados.
Jan Sila

1
Pergunta idiota, talvez: se não houver um logger __main__.py(por exemplo, se eu quiser usar o módulo em um script que não possua logger) logging.getLogger(__name__)ainda fará algum tipo de logon no módulo ou isso criará uma exceção?
Bill

1
Finalmente. Eu tinha um logger ativo, mas ele falhou no Windows for Parallel runs com joblib. Eu acho que isso é um ajuste manual no sistema - algo mais está errado com o Parallel. Mas, certamente funciona! Graças
B Furtado

17

Eu sempre faço isso como abaixo.

Use um único arquivo python para configurar meu log como padrão singleton chamado ' log_conf.py'

#-*-coding:utf-8-*-

import logging.config

def singleton(cls):
    instances = {}
    def get_instance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return get_instance()

@singleton
class Logger():
    def __init__(self):
        logging.config.fileConfig('logging.conf')
        self.logr = logging.getLogger('root')

Em outro módulo, basta importar a configuração.

from log_conf import Logger

Logger.logr.info("Hello World")

Este é um padrão único para registrar, de maneira simples e eficiente.


1
obrigado por detalhar o padrão singleton. Eu estava planejando implementar isso, mas a solução @prost é muito mais simples e atende perfeitamente às minhas necessidades. No entanto, vejo que sua solução é útil em projetos maiores que têm vários pontos de entrada (que não sejam principais). Danke.
Quest Monger

46
Isso é inútil. O registrador raiz já é um singleton. Basta usar logging.info em vez de Logger.logr.info.
vagem

9

Várias dessas respostas sugerem que, no topo de um módulo, você faz

import logging
logger = logging.getLogger(__name__)

Entendo que isso seja considerado uma prática muito ruim . O motivo é que a configuração do arquivo desativará todos os registradores existentes por padrão. Por exemplo

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logger.info('Hi, foo')

class Bar(object):
    def bar(self):
        logger.info('Hi, bar')

E no seu módulo principal:

#main
import logging

# load my module - this now configures the logger
import my_module

# This will now disable the logger in my module by default, [see the docs][1] 
logging.config.fileConfig('logging.ini')

my_module.foo()
bar = my_module.Bar()
bar.bar()

Agora, o log especificado em logging.ini estará vazio, pois o criador de logs existente foi desativado pela chamada fileconfig.

Embora seja certamente possível contornar isso (disable_existing_Loggers = False), realisticamente muitos clientes da sua biblioteca não saberão sobre esse comportamento e não receberão seus logs. Facilite para seus clientes sempre chamando logging.getLogger localmente. Dica: eu aprendi sobre esse comportamento no site de Victor Lin .

Portanto, é uma boa prática sempre chamar logging.getLogger localmente. Por exemplo

#my_module
import logging

logger = logging.getLogger(__name__)

def foo():
    logging.getLogger(__name__).info('Hi, foo')

class Bar(object):
    def bar(self):
        logging.getLogger(__name__).info('Hi, bar')    

Além disso, se você usar fileconfig em seu diretório principal, defina disable_existing_loggers = False, caso seus designers de bibliotecas usem instâncias de registrador no nível do módulo.


Você não pode correr logging.config.fileConfig('logging.ini')antes import my_module? Como sugerido nesta resposta .
Lucid_dreamer 4/09/17

Não tenho certeza - mas definitivamente também seria considerado uma má prática misturar importações e código executável dessa maneira. Você também não deseja que seus clientes tenham que verificar se eles precisam configurar o log antes de serem importados, especialmente quando há uma alternativa trivial! Imagine se uma biblioteca amplamente usada como solicitações tivesse feito isso ....!
phil_20686

"Não tenho certeza - mas definitivamente também seria considerado uma má prática misturar importações e código executável dessa maneira". - porque?
Lucid_dreamer 04/04

Não estou muito claro por que isso é ruim. E eu não entendo completamente o seu exemplo. Você pode postar sua configuração para este exemplo e mostrar algum uso?
Lucid_dreamer 04/04

1
Você parece contradizer os documentos oficiais : 'Uma boa convenção a ser usada ao nomear registradores é usar um registrador em nível de módulo, em cada módulo que usa log, com o seguinte nome: logger = logging.getLogger(__name__)'
iron9

9

Uma maneira simples de usar uma instância da biblioteca de log em vários módulos para mim era a seguinte solução:

base_logger.py

import logging

logger = logging
logger.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)

Outros arquivos

from base_logger import logger

if __name__ == '__main__':
    logger.info("This is an info message")

7

Jogando em outra solução.

No init .py do meu módulo, tenho algo como:

# mymodule/__init__.py
import logging

def get_module_logger(mod_name):
  logger = logging.getLogger(mod_name)
  handler = logging.StreamHandler()
  formatter = logging.Formatter(
        '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
  handler.setFormatter(formatter)
  logger.addHandler(handler)
  logger.setLevel(logging.DEBUG)
  return logger

Então, em cada módulo, preciso de um logger:

# mymodule/foo.py
from [modname] import get_module_logger
logger = get_module_logger(__name__)

Quando os logs são perdidos, você pode diferenciar sua origem pelo módulo de origem.


O que significa "init principal do meu módulo"? E "Então em cada classe eu preciso de um logger, preciso:"? Você pode fornecer uma amostra chamada_module.py e um exemplo de uso como uma importação do módulo caller_module.py? Veja esta resposta para obter uma inspiração do formato que estou perguntando. Não tentando ser condescendente. Estou tentando entender sua resposta e sei que entenderia se você a escrevesse dessa maneira.
Lucid_dreamer 04/04

1
@lucid_dreamer eu esclareci.
Tommy

4

Você também pode criar algo assim!

def get_logger(name=None):
    default = "__app__"
    formatter = logging.Formatter('%(levelname)s: %(asctime)s %(funcName)s(%(lineno)d) -- %(message)s',
                              datefmt='%Y-%m-%d %H:%M:%S')
    log_map = {"__app__": "app.log", "__basic_log__": "file1.log", "__advance_log__": "file2.log"}
    if name:
        logger = logging.getLogger(name)
    else:
        logger = logging.getLogger(default)
    fh = logging.FileHandler(log_map[name])
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.setLevel(logging.DEBUG)
    return logger

Agora você pode usar vários registradores no mesmo módulo e em todo o projeto, se o acima for definido em um módulo separado e importado em outros módulos onde o registro for necessário.

a=get_logger("__app___")
b=get_logger("__basic_log__")
a.info("Starting logging!")
b.debug("Debug Mode")

4

A solução da @ Yarkee parecia melhor. Gostaria de acrescentar algo a ele -

class Singleton(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances.keys():
            cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]


class LoggerManager(object):
    __metaclass__ = Singleton

    _loggers = {}

    def __init__(self, *args, **kwargs):
        pass

    @staticmethod
    def getLogger(name=None):
        if not name:
            logging.basicConfig()
            return logging.getLogger()
        elif name not in LoggerManager._loggers.keys():
            logging.basicConfig()
            LoggerManager._loggers[name] = logging.getLogger(str(name))
        return LoggerManager._loggers[name]    


log=LoggerManager().getLogger("Hello")
log.setLevel(level=logging.DEBUG)

Portanto, o LoggerManager pode ser plugável em todo o aplicativo. Espero que faça sentido e valor.


11
O módulo de registro já lida com singletons. logging.getLogger ("Hello") obterá o mesmo logger em todos os seus módulos.
vagem

2

Existem várias respostas. acabei com uma solução semelhante, mas diferente, que faz sentido para mim, talvez faça sentido para você também. Meu objetivo principal era poder passar logs para manipuladores por nível (logs de nível de depuração para o console, avisos e acima para arquivos):

from flask import Flask
import logging
from logging.handlers import RotatingFileHandler

app = Flask(__name__)

# make default logger output everything to the console
logging.basicConfig(level=logging.DEBUG)

rotating_file_handler = RotatingFileHandler(filename="logs.log")
rotating_file_handler.setLevel(logging.INFO)

app.logger.addHandler(rotating_file_handler)

criou um bom arquivo utilitário chamado logger.py:

import logging

def get_logger(name):
    return logging.getLogger("flask.app." + name)

o flask.app é um valor codificado no balão. o criador de aplicativos está sempre começando com flask.app como o nome do módulo.

Agora, em cada módulo, eu posso usá-lo no seguinte modo:

from logger import get_logger
logger = get_logger(__name__)

logger.info("new log")

Isso criará um novo log para "app.flask.MODULE_NAME" com o mínimo de esforço.


2

A melhor prática seria criar um módulo separadamente, que possui apenas um método cuja tarefa é atribuir um manipulador de logger ao método de chamada. Salve este arquivo como m_logger.py

import logger, logging

def getlogger():
    # logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    # create console handler and set level to debug
    #ch = logging.StreamHandler()
    ch = logging.FileHandler(r'log.txt')
    ch.setLevel(logging.DEBUG)
    # create formatter
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    # add formatter to ch
    ch.setFormatter(formatter)
    # add ch to logger
    logger.addHandler(ch)
    return logger

Agora chame o método getlogger () sempre que necessário.

from m_logger import getlogger
logger = getlogger()
logger.info('My mssg')

1
Isso é bom se você não tiver nenhum parâmetro adicional. Mas se, digamos, você tem --debuga opção no aplicativo e quer nível de registo conjunto em todos os madeireiros em seu aplicativo com base neste parâmetro ...
The Godfather

@ TheGodfather Sim, isso é difícil de conseguir com esta metodologia. O que podemos fazer nessa situação é criar uma classe para a qual levaria o formatador como parâmetro no momento da criação do objeto e teria a função semelhante de retornar o manipulador do criador de logs. Quais são seus pontos de vista sobre isso?
Mousam Singh

Sim, fiz algo semelhante, feito get_logger(level=logging.INFO)para retornar algum tipo de singleton; portanto, quando ele ligou pela primeira vez no aplicativo principal, inicializa o criador de logs e os manipuladores com o nível adequado e depois retorna o mesmo loggerobjeto para todos os outros métodos.
The Godfather

0

Novo no python, então não sei se isso é aconselhável, mas funciona muito bem para não reescrever o clichê.

Seu projeto deve ter um init .py para que possa ser carregado como um módulo

# Put this in your module's __init__.py
import logging.config
import sys

# I used this dictionary test, you would put:
# logging.config.fileConfig('logging.conf')
# The "" entry in loggers is the root logger, tutorials always 
# use "root" but I can't get that to work
logging.config.dictConfig({
    "version": 1,
    "formatters": {
        "default": {
            "format": "%(asctime)s %(levelname)s %(name)s %(message)s"
        },
    },
    "handlers": {
        "console": {
            "level": 'DEBUG',
            "class": "logging.StreamHandler",
            "stream": "ext://sys.stdout"
        }
    },
    "loggers": {
        "": {
            "level": "DEBUG",
            "handlers": ["console"]
        }
    }
})

def logger():
    # Get the name from the caller of this function
    return logging.getLogger(sys._getframe(1).f_globals['__name__'])

sys._getframe(1)sugestão vem daqui

Em seguida, use seu logger em qualquer outro arquivo:

from [your module name here] import logger

logger().debug("FOOOOOOOOO!!!")

Ressalvas:

  1. Você deve executar seus arquivos como módulos, caso contrário import [your module]não funcionará:
    • python -m [your module name].[your filename without .py]
  2. O nome do criador de logs para o ponto de entrada do seu programa será __main__, mas qualquer solução usando __name__terá esse problema.
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.