Django Logging, The Right Way

Good logging é crítico para depuração e resolução de problemas. Não só é útil no desenvolvimento local, mas na produção é indispensável. Ao rever os logs para um problema, é raro ouvir alguém dizer: “Nós temos muitos logs em nossa aplicação”, mas é comum ouvir o inverso. Então, com isso em mente, vamos começar.

A Crash Course in Python Loggers

No topo de cada arquivo, você deve ter algo como isto:

import logginglogger = logging.getLogger(__name__)

__name__ avaliará o caminho pontilhado Python do módulo, por exemplo myproject/myapp/views.py usará myproject.myapp.views. Agora podemos usar esse logger em todo o arquivo assim:

# A simple string logged at the "warning" levellogger.warning("Your log message is here")# A string with a variable at the "info" levellogger.info("The value of var is %s", var)# Logging the traceback for a caught exceptiontry: function_that_might_raise_index_error()except IndexError: # equivalent to logger.error(msg, exc_info=True) logger.exception("Something bad happened")

Note: os loggers Python irão lidar com a inserção de variáveis na sua mensagem de log se você passar como argumentos na função de log. Se o log não precisa de ser gerado, a substituição de variáveis nunca irá ocorrer, ajudando a evitar um pequeno golpe de performance para um log que nunca será usado.

Pimenta o teu código liberalmente com estas instruções de log. Aqui está a regra geral que uso para níveis de log:

  • debug: Informação não necessária para operação regular, mas útil em desenvolvimento.
  • info: Informação que é útil durante operação regular.
  • warning: Informação que pode ser problemática, mas que não éurgente.
  • error: Informação que é importante e que provavelmente requer atenção imediata.
  • critical: Eu não me encontro usando isto na prática, mas se você precisar de um superior a error, aqui está

Onde Log

Sua aplicação não deve se preocupar com para onde seus logs vão. Ao invés disso, ela deve registrar tudo no console (stdout/stderr) e deixar o servidor decidir o que fazer com ela a partir daí. Normalmente isto é colocado em um arquivo dedicado (e logrotated), capturado pelo Systemd journal ou Docker, enviado para um serviço separado como o ElasticSearch, ou alguma combinação destes. O armazenamento de logs é uma preocupação de implantação, não uma preocupação de aplicação.

A única coisa com que sua aplicação precisa se preocupar é com o formato dos logs. Normalmente isto é apenas uma string com os dados relevantes, mas se o seu servidor já adiciona um timestamp ao log, você provavelmente quer excluí-lo do seu próprio formatter. Da mesma forma, se o seu agregador de logs pode aceitar JSON, um formatador como python-json-logger pode ser mais apropriado.

Configurar o Logger

O escrever para os loggers em Python é fácil. Configurá-los para ir para o lugar certo é mais desafiador do que você esperaria. Vou começar contornando o log por defeito do Django como descrito no meu post anterior. Isso nos dará uma tabela em branco para trabalhar com.

Configurando o Sentry

Os serviços de relatórios de erros são críticos para qualquer site não-trivial. Por padrão, essas exceções não-triviais pegam, notificam você sobre o problema (apenas uma vez por incidente), e fornecem uma boa interface para ver o estado do aplicativo quando a exceção ocorreu. Meu serviço favorito para isso é Sentry.

Podemos levar Sentry um passo adiante, enviando quaisquer mensagens de log que sejam warning ou superiores para o serviço também. Estas seriam perdidas em um mar de arquivos de log que, na prática, raramente são revistas. Para fazer isso, vamos adicionar um logger “root” que serve como um catch-all para quaisquer logs que são enviados a partir de qualquer módulo Python. Isso se parece algo assim nas configurações do Django,

import logging.configLOGGING_CONFIG = Nonelogging.config.dictConfig({ 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'console': { # exact format is not important, this is the minimum information 'format': '%(asctime)s %(name)-12s %(levelname)-8s %(message)s', }, }, 'handlers': { 'console': { 'class': 'logging.StreamHandler', 'formatter': 'console', }, # Add Handler for Sentry for `warning` and above 'sentry': { 'level': 'WARNING', 'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler', }, }, 'loggers': { # root logger '': { 'level': 'WARNING', 'handlers': , }, },})

Logging From Your Application

Embora você só queira saber sobre avisos e erros de suas dependências de terceiros, você normalmente quer uma visão muito mais profunda do código de sua aplicação. Idealmente, seu código vive todo sob um único espaço de nome para que possa ser capturado com uma única adição ao loggers. Vamos assumir que seu projeto usa o espaço de nomes myproject, construindo sobre o código de cima você adicionaria isto:

logging.config.dictConfig({ # ... 'loggers': { '': { 'level': 'WARNING', 'handlers': , }, 'myproject': { 'level': 'INFO', 'handlers': , # required to avoid double logging with root logger 'propagate': False, }, },})

Mas e se você quiser investigar algo em sua aplicação mais profundamente com debug registro? Ter que cometer um novo código e implementá-lo parece um exagero. Este é um ótimo caso de uso para variáveis de ambiente. Podemos modificar a estrofe anterior para ficar assim:

import osLOGLEVEL = os.environ.get('LOGLEVEL', 'info').upper()logging.config.dictConfig({ # ... 'loggers': { '': { 'level': 'WARNING', 'handlers': , }, 'myproject': { 'level': LOGLEVEL, 'handlers': , # required to avoid double logging with root logger 'propagate': False, }, },})

Agora o log da nossa aplicação será padrão para info, mas pode ser facilmente aumentado temporariamente definindo a variável de ambiente LOGLEVEL=debug. Alternativamente, se o armazenamento de logs não for um problema, considere sempre o log no nível debug. Eles são suficientemente fáceis de filtrar com um simples grep ou através da sua ferramenta de visualização de logs, por exemplo Kibana.

Cortando o Ruído

A partir do momento em que você tenha a sua configuração e execução de logs, você pode encontrar alguns módulos de informação de logs que você realmente não se importa e apenas servem para criar ruído extra (estou olhando para você newrelic). Para estes módulos, nós podemos adicionar outro logger para afiná-los. A primeira opção é registrá-los no console, mas evite propagá-los para o logger raiz que os enviaria para Sentry:

logging.config.dictConfig({ # ... 'loggers': { '': { 'level': 'WARNING', 'handlers': , }, 'myproject': { 'level': LOGLEVEL, 'handlers': , # required to avoid double logging with root logger 'propagate': False, }, # Don't send this module's logs to Sentry 'noisy_module': { 'level':'ERROR', 'handlers': , 'propagate': False, }, },})

Se você achar que eles são muito barulhentos, mesmo para o console, podemos soltá-los completamente:

logging.config.dictConfig({ # ... 'loggers': { # ... # Don't log this module at all 'noisy_module': { 'level': 'NOTSET', 'propagate': False, }, },})

Local Request Logging

Uma boa opção na configuração padrão do Django é o request logging com o servidor run. Ao sobrescrever a configuração do Django, nós a perdemos, mas é fácil o suficiente para adicionar de volta em:

from django.utils.log import DEFAULT_LOGGINGlogging.config.dictConfig({ # ... 'formatters': { # ... 'django.server': DEFAULT_LOGGING, }, 'handlers': { # ... 'django.server': DEFAULT_LOGGING, }, 'loggers': { # ... 'django.server': DEFAULT_LOGGING, },})

Leave a Reply