Configuração elegante de registro Python no Django

101

Ainda estou para encontrar uma maneira de configurar o log Python com Django que me agrade. Meus requisitos são bastante simples:

  • Manipuladores de registro diferentes para eventos diferentes - ou seja, quero poder registrar em arquivos diferentes
  • Fácil acesso aos loggers em meus módulos. O módulo deve ser capaz de localizar seu registrador com pouco esforço.
  • Deve ser facilmente aplicável a módulos de linha de comando. Partes do sistema são processos de linha de comando ou daemon independentes. O registro deve ser facilmente utilizável com esses módulos.

Minha configuração atual é usar um logging.confarquivo e log de configuração em cada módulo de onde eu logar. Não parece certo.

Você tem uma configuração de registro de que gosta? Por favor, detalhe: como você logging.confdefine a configuração (você usa ou configura em código), onde / quando você inicia os loggers e como você obtém acesso a eles em seus módulos, etc.

Parand
fonte
1
Você pode achar o seguinte screencast útil - ericholscher.com/blog/2008/aug/29/… . Além disso, um melhor suporte para registro no Django foi proposto por Simon Willison (consulte simonwillison.net/2009/Sep/28/ponies ).
Dominic Rodger,
@Dominic Rodger - Você já pode fazer log flexível de aplicativos no Django, a proposta de Simon principalmente para facilitar o log em internos do Django. Há um trabalho em andamento no Python para adicionar configuração baseada em dicionário ao registro do Python, do qual o Django pode se beneficiar.
Vinay Sajip

Respostas:

57

A melhor maneira que encontrei até agora é inicializar a configuração de registro em settings.py - em nenhum outro lugar. Você pode usar um arquivo de configuração ou fazê-lo passo a passo programaticamente - depende apenas de seus requisitos. O principal é que geralmente adiciono os manipuladores que desejo ao logger root, usando níveis e, às vezes, registrando. Filtros para obter os eventos que desejo nos arquivos apropriados, console, syslogs etc. Você pode, é claro, adicionar manipuladores a qualquer outro logger também, mas geralmente não há necessidade disso na minha experiência.

Em cada módulo, eu defino um logger usando

logger = logging.getLogger(__name__)

e use isso para registrar eventos no módulo (e, se eu quiser diferenciar ainda mais), use um logger que é filho do logger criado acima.

Se meu aplicativo for potencialmente usado em um site que não configura o login em settings.py, defino um NullHandler em algum lugar da seguinte maneira:

#someutils.py

class NullHandler(logging.Handler):
    def emit(self, record):
        pass

null_handler = NullHandler()

e garantir que uma instância dele seja adicionada a todos os registradores criados nos módulos em meus aplicativos que usam registro. (Observação: NullHandler já está no pacote de registro para Python 3.1 e estará no Python 2.7.) Então:

logger = logging.getLogger(__name__)
logger.addHandler(someutils.null_handler)

Isso é feito para garantir que seus módulos funcionem bem em um site que não configura o login em settings.py, e que você não receba nenhuma mensagem chata "Nenhum manipulador foi encontrado para o logger XYZ" (que são avisos sobre potencialmente registro mal configurado).

Fazer dessa maneira atende aos seus requisitos declarados:

  • Você pode configurar diferentes manipuladores de log para eventos diferentes, como faz atualmente.
  • Fácil acesso aos registradores em seus módulos - use getLogger(__name__).
  • Facilmente aplicável a módulos de linha de comando - eles também importam settings.py.

Atualização: Observe que a partir da versão 1.3, o Django agora incorpora suporte para registro .

Vinay Sajip
fonte
Isso não exigirá que cada módulo tenha um manipulador definido na configuração (você não pode usar um manipulador para foo para manipular foo.bar)? Veja a conversa que tivemos anos atrás em groups.google.com/group/comp.lang.python/browse_thread/thread/…
andrew cooke
1
@andrew cooke: Você pode usar um manipulador de foopara manipular eventos registrados foo.bar. Ré. esse thread - ambos fileConfig e dictConfig agora têm opções para evitar a desativação de registradores antigos. Veja este problema: bugs.python.org/issue3136 , que surgiu alguns meses após seu problema bugs.python.org/issue2697 - de qualquer forma, está resolvido desde junho de 2008.
Vinay Sajip
não seria melhor fazer de logger = someutils.getLogger(__name__)onde someutils.getLoggerretorna o logger logging.getLoggercom um null_handler já adicionado?
7yl4r
1
@ 7yl4r Você não precisa que cada logger tenha um NullHandleradicionado - normalmente apenas o logger de nível superior para sua hierarquia de pacotes. Então isso seria um exagero, IMO.
Vinay Sajip
122

Eu sei que esta é uma resposta já resolvida, mas de acordo com django> = 1.3 há uma nova configuração de registro.

Mudar do antigo para o novo não é automático, então pensei em escrever aqui.

E, claro, verifique o doc django para mais alguns.

Esta é a configuração básica, criada por padrão com django-admin createproject v1.3 - a milhagem pode mudar com as últimas versões do django:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'mail_admins': {
            'level': 'ERROR',
            'class': 'django.utils.log.AdminEmailHandler',
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        }
    }
}

Essa estrutura é baseada no dictConfig de registro padrão do Python , que dita os seguintes blocos:

  • formatters - o valor correspondente será um dict em que cada chave é um id do formatador e cada valor é um dict que descreve como configurar a instância do Formatter correspondente.
  • filters - o valor correspondente será um dict em que cada chave é um id de filtro e cada valor é um dict que descreve como configurar a instância de Filter correspondente.
  • handlers- o valor correspondente será um dict em que cada chave é um id do manipulador e cada valor é um dict que descreve como configurar a instância do Handler correspondente. Cada manipulador possui as seguintes chaves:

    • class(obrigatório). Este é o nome totalmente qualificado da classe do manipulador.
    • level(opcional). O nível do manipulador.
    • formatter(opcional). A id do formatador para este manipulador.
    • filters(opcional). Uma lista de ids dos filtros para este manipulador.

Normalmente faço pelo menos isto:

  • adicionar um arquivo .log
  • configurar meus aplicativos para escrever neste log

O que se traduz em:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(message)s'
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'null': {
            'level':'DEBUG',
            'class':'django.utils.log.NullHandler',
        },
        'console':{
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        # I always add this handler to facilitate separating loggings
        'log_file':{
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(VAR_ROOT, 'logs/django.log'),
            'maxBytes': '16777216', # 16megabytes
            'formatter': 'verbose'
        },
        'mail_admins': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'django.utils.log.AdminEmailHandler',
            'include_html': True,
        }
    },
    'loggers': {
        'django.request': {
            'handlers': ['mail_admins'],
            'level': 'ERROR',
            'propagate': True,
        },
        'apps': { # I keep all my of apps under 'apps' folder, but you can also add them one by one, and this depends on how your virtualenv/paths are set
            'handlers': ['log_file'],
            'level': 'INFO',
            'propagate': True,
        },
    },
    # you can also shortcut 'loggers' and just configure logging for EVERYTHING at once
    'root': {
        'handlers': ['console', 'mail_admins'],
        'level': 'INFO'
    },
}

editar

Veja as exceções de solicitação agora sempre registradas e o tíquete nº 16288 :

Eu atualizei o exemplo de configuração acima para incluir explicitamente o filtro correto para mail_admins para que, por padrão, os e-mails não sejam enviados quando a depuração for True.

Você deve adicionar um filtro:

'filters': {
    'require_debug_false': {
        '()': 'django.utils.log.RequireDebugFalse'
    }
},

e aplique-o ao manipulador mail_admins:

    'mail_admins': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'django.utils.log.AdminEmailHandler',
        'include_html': True,
    }

Caso contrário, o django.core.handers.base.handle_uncaught_exceptionnão passa erros para o registrador 'django.request' se settings.DEBUG for True.

Se você não fizer isso no Django 1.5, você obterá um

Aviso de descontinuação: Você não tem filtros definidos no gerenciador de registro 'mail_admins': adicionando filtro implícito debug-false-only

mas as coisas ainda funcionarão corretamente AMBOS no Django 1.4 e no Django 1.5.

** fim da edição **

Esse conf é fortemente inspirado pelo conf de amostra no doc django, mas adicionando a parte do arquivo de log.

Freqüentemente também faço o seguinte:

LOG_LEVEL = 'DEBUG' if DEBUG else 'INFO'

...
    'level': LOG_LEVEL
...

Então, em meu código python, sempre adiciono um NullHandler no caso de nenhuma configuração de registro ser definida. Isso evita avisos para nenhum Handler especificado. Especialmente útil para libs que não são necessariamente chamados apenas no Django ( ref )

import logging
# Get an instance of a logger
logger = logging.getLogger(__name__)
class NullHandler(logging.Handler): #exists in python 3.1
    def emit(self, record):
        pass
nullhandler = logger.addHandler(NullHandler())

# here you can also add some local logger should you want: to stdout with streamhandler, or to a local file...

[...]

logger.warning('etc.etc.')

Espero que isto ajude!

Stefano
fonte
Stefano, muito obrigado pela resposta detalhada, muito útil. Isso pode valer a pena atualizar para 1.3.
Paraná,
Parand, definitivamente vale a pena (IMHO!) Avançar para django 1.3, embora haja alguns pontos a serem considerados para uma transição suave - abra uma nova pergunta do SO se você tiver problemas ;-)
Stefano
a propósito: eu ainda uso este tipo de configurações e o log de arquivo, mas mudei para sentinela para a produção!
Stefano
@clime bem, tentei explicar na própria resposta: caso nenhuma configuração de log seja definida. Isso evita avisos para nenhum Handler especificado. Especialmente útil para libs que não são necessariamente chamados apenas no Django (ref)
Stefano
Não vejo como você usa esta definição: 'null': {'level': 'DEBUG', 'class': 'django.utils.log.NullHandler',}
clime
9

Inicializamos o registro no nível superior urls.pyusando um logging.iniarquivo.

A localização do logging.inié fornecida em settings.py, mas isso é tudo.

Cada módulo faz

logger = logging.getLogger(__name__)

Para distinguir as instâncias de teste, desenvolvimento e produção, temos diferentes arquivos logging.ini. Na maior parte, temos um "log de console" que vai para stderr apenas com erros. Temos um "log do aplicativo" que usa um arquivo de log regular que vai para um diretório de logs.

S.Lott
fonte
Acabei usando isso, exceto inicializar em settings.py em vez de urls.py
Parand,
Como você usa as configurações de settings.py em seu arquivo logging.ini? Por exemplo, preciso da configuração BASE_DIR, para que possa dizer onde armazenar meus arquivos de log.
slypete
@slypete: Não usamos configurações no logging.ini. Como o registro é amplamente independente, não usamos nenhuma das configurações do Django. Sim, existe a possibilidade de repetir algo. Não, não faz muita diferença prática.
S.Lott
Nesse caso, eu faria um arquivo logging.ini separado em cada instalação do meu aplicativo.
slypete
@slypete: Você tem um settings.py para cada instalação. Você também tem um logging.ini para cada instalação. Além disso, você provavelmente também tem um arquivo conf do Apache para cada instalação. Além de um arquivo de interface wsgi. Não tenho certeza de qual é o seu ponto.
S.Lott
6

Atualmente, estou usando um sistema de registro, que eu mesmo criei. Ele usa o formato CSV para registro.

django-csvlog

Este projeto ainda não tem documentação completa, mas estou trabalhando nisso.

Oduvan
fonte