Skip to main content

Python Logging Facility - Sistema de Log do Python

Carlo leite

April 14, 2022

O que é e porque?

Acompanhar a execução de um programa pode ser um tanto desafiador.

Seja por causa da complexidade da lógica e quantidade de variáveis, ou talvez pela integração com outros sistemas, pode ficar difícil entender o que de fato está acontecendo em uma parte do sistemas.

Também quando em produção, colher métricas de execução, tempo de resposta, atividades de usuários tem uma variedade de coisas acontecendo no sistema e muitas vezes sofrendo interferência de fora do sistema. Para isso temos o famoso "log" ou sistemas de "logging" ou em bom português, o registro.

A linguagem Python tem uma classe de objetos específica para auxiliar no trabalho de registro de execução de um software, e esta classe se chama `Logger`, ou pode procurar por Python Logging Facility"

Pare de usar `print()`s , você merece, e está pronto!

Note

Palavras em inglês estarão entre aspas duplas (ex.: "table")

Classes, funções e comandos da linguagem Python estarão no texto em itálico (ex.:fuction).

Quando houver necessidade de mostrar um código Python (mais de 1 comando) usaremos um bloco de código como abaixo

from foo import bar

def minha_funcao(param):
    print(param)  # comentário

Componentes do sistema de registro do Python

  1. Logger
  2. Handler
  3. Formatter
  4. Filter
Logger:
a classe Python que gerencia todo o sistema de registro.
`Handler`s:
classe de objetos que gerenciam as mensagens de registro, ou ao pé da letras - Manuzeadores.
`Formatter`s:
classe de formatadores, para gerenciar a forma da mensagem qanto ao seu conteúdo - ex: adicionar data e hora em todas as mensagens de registro.
`Filter`s:
Os filtros determinam se uma mensagem do registro será passada a diante ou não.

Porque complicar tanto ?

Em casos mais simples, se quisermos monitorar um passo-a-passo da execução podemos simplesmente usar um monte de prints() dentro do código certo? - certo!

O registro de atividades do sistema - o "log", pode ficar bastante complexo, desde um simples registro para "debugar" a aplicação, até registrar falhas no sistema e métricas de execução quando em produção. É justamente aí que entra o "logging"do Pyhton, para que o programador não tenha que definir previamente como será este controle, e nem ecrever seu próprio código para gerenciar as mensagens, independente do contexto - desenvolvimento e/ou produção.

Utilizando o "logging" do Python, simplesmente usamos o objeto logger para emitir as mensagens, e posso definir depois se serão impressas na tela, em um arquivo, ou mesmo quais mensagens ou de qual tipo envio para um arquivo, terminal ou endereço de rede. E não preciso alterar código. ... se fizer com cuidado, claro!

"O uso de do print() pode inclusive levar a erros de execução - Por exemplo, se a aplicação for instalada em um servidor sem um terminal"

—sim, experiência própria!

Porque o sistema de registro através do Logger?

Porque é simples, bom e barato!

para ter um log funcionando basta ...

# exemplo de código Python para preparar o sistema de "log"
import logging
import sys

logger = logging.getLogger(__name__)  # o parâm. `__name__` pega o nome do módulo Python.

# e pode usar a vontade ... "emitindo" mensagens.
logging.debug('Mensagem de debug')

# o  `Handler`,`Filter` e o `Formatter` não precisam necessariamente serem definidos ou usados!

Pronto, o código acima é tudo que você precisa para não usar print() para analisar seu código, e ganhamos uma série de controles que iremos explorar no restante do texto.

Tip

Se acha que não precisa de mais controle, é porque não está fazendo direito. Acredite

Níveis de mensagens

cada vez que for necessário imprimir um "log", basta utilizar logger.<level>(<msg>) ao invés de um print(). Como abaixo.

logger.warning('This is a warning message to stdout') # no lugar do Print!!

E é possível identificar níveis diferentes de mensagens.

logging.debug('Mensagem de debug')       #logging.DEBUG    = 10
logging.info('Mensagem de info')         #logging.INFO     = 20
logging.warning('Mensagem de warning')   #logging.WARNING  = 30
logging.critical('Mensagem de critical') #logging.CRITICAL = 50
logging.error('Mensagem de error')       #logging.ERROR    = 40

Identificando as mensagens propriamente fica fácil aumentar ou diminuir a sensibilidade do sistema de "log".

Por exemplo, se estou construindo o sistema, preciso de todas as mensagens (<=10), mas se estou enviando o sistema para produção, só preciso registrar os erros, então posso configurar o sistema para emitir mensagens somente a partir de logging.warning ou ">=30".

Detalhando os componentes

O obj Logger

import logging

# Cria um "logger" chamado "module_1"
logger = logging.getLogger('module_1')

Você pode usar o objeto Logger de maneira simples como nos exemplos acima, não há pecado nisso.

Mas é bom saber que...

uma instância de Logger, pode conter uma ou mais instâncias do próprio objeto Loggger. É um objeto hierárquico, e portanto tem um conceito de árvore, onde o Logger que for criado sem o parâmentro nome, é na verdade o objeto raiz ou "root".

4 maneiras de se definir um Logger

import logging

# 1º ou
# Cria o logger raiz
logger = logging.getLogger()

# 2º ou
# Cria um "logger" chamado "module_1"
logger_module_1 = logging.getLogger('module_1')  # este está hierarquicamente abaixo de logger automaticamente!

...
# 3º ou
# podemos indicar a relação pai x filho usando a notação de ponto do Python ("dot notation")
logger_module_1.klass_1 = logging.getLogger('module_1.klass_1')

# 4º ou
# ou ainda podemos explicitamente criar um filho
logger_module_2 = logging.getLogger('module_2')
logger_module_2_klass = logger_module_2.getChild('klass')

# tá na dúvida? Use a 2º opção, lembre-se, "feito é melhor que não feito!"

Apesar de podermos utilizar o Logger de maneira simples, sem especificar um nome ou hirarquia, alguns softwares podem demandar tal complexidade, particularmente ao criarmos uma "lib" Python que será incorporada em outros softwares.

Note

se sua instância de "logger" não tiver um handler definido, o logger raiz, irá automaticamente chamar o logging.basicConfig().

O obj Handler

O que é, e porque seria necessário ?

Imagine que você terminou de construir seu sistema e irá fazer sua instalação para ser usado diariamente, mas ainda gostaria de obter alguma informação do sistema em execução. Acredito que neste caso você não queira ficar literalmente assistindo seu sistema imprimir coisas na tela. Também não devemos contar com a perspicácia do usuário em relatar erros de maneira eficiente - acredite, não é este o papel do usuário.

Que tal coletar os dados em um arquivo que possa ser coletado quando o usuário disser: "O sistema deu 'pau'!".

Para isso temos o `Handler`.

O `Handler` define para onde a mensagem emitida será enviada, se para um arquivo, para o terminal, para um endereço de rede, ou para todos ao mesmo tempo.

Existem uma série de `Handler`s prontos para uso, pelo menos 15 deles,
e você ainda pode fazer o seu próprio.

O que tem pronto?

  1. StreamHandler` transmissão contínua ("stream") para objetos do tipo "file-like" (ex.: seu terminal)
  2. FileHandler` mensagens para arquivos em disco
  3. RotatingFileHandler` mensagens são gravadas em arquivos em disco, rotacionando o arquivo baseado no tamanho do arquivo.
  4. TimedRotatingFileHandler` mensagens são gravadas em arquivos em disco, rotacionando o arquivo baseado no tempo (data e hora).
  5. SMTPHandler` mensagens são enviadas para um email.
  6. SysLogHandler` mensagens são enviadas para um "Unix syslog daemon", possivelmente remoto.
  7. NullHandler` um caso especial usado muito para uso em bibliotecas Python, que podem ser usadas em um sistema que não tenha definido um Handler na aplicação.

veja neste link outros handlers

Uma instância de Handler é definida como abaixo.

A definição abaixo, utilizando o StreamHandler, diz que toda mensagem emitida deve ser enviada para a saída padrão do sistema ou "standart output"

Note

sys.stdout para o Python, significa "standart Output", ou vulgo "sua tela", na maioria dos casos ;)

handler = logging.StreamHandler(stream=sys.stdout)  # define o Handler
logger.addHandler(handler)

Se você está executando o código no seu computador, nada de diferente como resultado, porque se um Handler não for definido (como no exemplo inicial), provavelmente teria o mesmo efeito - mas cuidado com o "provavelmente", "explícito é melhor que implícito". Então mesmo que só queira algo na tela, acostume-se a definir um `handler` para o `std.out`. ;)

E explicitando o desta maneira, evitamos ter surpresas como a do print() citada anteriormente.

O código para funcionar ficaria como abaixo.

import logging

logger = logging.getLogger()
handler = logging.StreamHandler(stream=sys.stdout)
logger.addHandler(handler)

Cada instância de Logger pode ter 1 ou mais handlers, cada um deles cuidando de um tipo de saída.

this

Please sign in

Signin to manage your account.

Do not have an account? Signup

OR