Python logging
Todo mundo que programa já espalhou print("chegou aqui") pelo código para descobrir o que estava acontecendo. Funciona, mas depois é preciso apagar tudo, e no dia em que o problema volta, é preciso escrever tudo de novo. O módulo logging ("registro", como no "diário de bordo" de um navio) resolve isso: o programa anota o que está fazendo, cada anotação com um nível de importância, e você decide, numa linha só, quais anotações aparecem e para onde elas vão: a tela ou um arquivo.
- Por que usar
loggingem vez deprintpara as mensagens "de bastidor". - Os cinco níveis:
DEBUG,INFO,WARNING,ERROReCRITICAL, e por que só três aparecem no começo. - A configurar com
logging.basicConfig: o nível (level) e o formato (format), com data e hora. - A gravar as mensagens num arquivo de log (
filename,filemode). - A dar nome às mensagens com
logging.getLoggere a registrar um erro com o traceback (logging.exception). - Os erros reais: o
basicConfigque não faz nada, a vírgula doprint, o nível com nome errado.
A ideia: o caderno de ocorrências da padaria
A padaria da Ana tem um caderno no balcão. Quem trabalha lá anota o que acontece, e cada anotação tem uma etiqueta colorida:
| Etiqueta no caderno | Nível do logging | Número | Para quê |
|---|---|---|---|
| Cinza: detalhe miúdo | DEBUG ("depuração") | 10 | Detalhes para quem está caçando um problema: "abrindo o arquivo", "a variável vale 3" |
| Azul: aconteceu algo normal | INFO ("informação") | 20 | O programa funcionando como deveria: "encomenda anotada", "padaria aberta" |
| Amarelo: atenção | WARNING ("aviso") | 30 | Ainda funciona, mas algo merece atenção: "estoque de ovos baixo" |
| Vermelho: deu errado | ERROR ("erro") | 40 | Uma tarefa falhou: "não deu para salvar a encomenda" |
| Preto: emergência | CRITICAL ("crítico") | 50 | O programa (ou a padaria) não tem como continuar: "o forno parou!" |
No fim do dia, a Ana não lê o caderno inteiro: ela lê só as amarelas para cima. Quando alguma coisa está muito estranha, aí sim ela lê tudo, até as cinzas. O logging funciona assim: você escolhe um nível mínimo, e só aparecem as mensagens daquele nível para cima. O número de cada nível é o que o Python compara.
1. As cinco funções, sem configurar nada
import logging
logging.debug("Detalhe: abrindo o arquivo de encomendas")
logging.info("Encomenda da Bia anotada")
logging.warning("Estoque de ovos baixo: 3")
logging.error("Não deu para salvar a encomenda")
logging.critical("O forno parou!")
print("Fim do programa")
$ python3 niveis.py WARNING:root:Estoque de ovos baixo: 3 ERROR:root:Não deu para salvar a encomenda CRITICAL:root:O forno parou! Fim do programa
Cinco mensagens no código, três na tela. O debug e o info sumiram. Não é defeito: sem configuração, o nível mínimo é WARNING. A ideia é que um programa "quieto" só fale quando há algo a avisar.
- WARNING
- O nível da mensagem.
- root
- O nome de quem registrou.
root("raiz") é o registrador principal, o que você usa quando chamalogging.warning(...)direto. Na seção 5 você vai criar outros, com nome. - Estoque de ovos baixo: 3
- A sua mensagem.
As mensagens vão para a saída de erros
Outra diferença para o print: o logging escreve na saída de erros (stderr), não na saída normal. Na tela, as duas aparecem juntas. Mas, jogando fora a saída de erros com 2>/dev/null, só sobra o print:
$ python3 niveis.py 2>/dev/null Fim do programa
Isso é ótimo: o resultado do programa (o que ele imprime com print) fica separado das anotações sobre o que ele fez. Quem usa o programa pode guardar um e jogar fora o outro (a aula os e sys fala dos dois canais).
print | logging | |
|---|---|---|
| Para quê | O resultado do programa, o que a pessoa veio ver | Anotações sobre o que o programa está fazendo |
| Para onde vai | Saída normal (stdout) | Saída de erros (stderr), ou um arquivo |
| Ligar e desligar | Só apagando a linha | Mudando o nível, numa linha só |
| Hora, nível, quem registrou | Você escreve à mão | Automático, com o format |
2. basicConfig(level=…): escolhendo o nível mínimo
A configuração fica numa chamada só, logging.basicConfig ("configuração básica"), feita uma vez, no começo do programa, antes de qualquer mensagem:
import logging
logging.basicConfig(level=logging.DEBUG)
logging.debug("Detalhe: abrindo o arquivo de encomendas")
logging.info("Encomenda da Bia anotada")
logging.warning("Estoque de ovos baixo: 3")
$ python3 config.py DEBUG:root:Detalhe: abrindo o arquivo de encomendas INFO:root:Encomenda da Bia anotada WARNING:root:Estoque de ovos baixo: 3
- logging.basicConfig(…)
- Configura o registrador principal.
- level=logging.DEBUG
- O nível mínimo.
logging.DEBUGé a constante que vale 10; com ela, tudo aparece. As outras:logging.INFO,logging.WARNING,logging.ERROR,logging.CRITICAL. O nome como texto também vale:level="DEBUG".
Esse é o grande truque: enquanto você está consertando o programa, level=logging.DEBUG; quando está tudo certo, troque para logging.WARNING, e os detalhes somem, sem apagar uma linha sequer.
Missão: rode e repare que a mensagem não aparece. Acrescente um logging.basicConfig com o nível INFO, antes da mensagem, para ela aparecer.
import logging
logging.info("Encomenda da Bia anotada")
print("Pronto.")
import logging
logging.basicConfig(level=logging.INFO)
logging.info("Encomenda da Bia anotada")
print("Pronto.")
3. format: o jeito de cada linha
WARNING:root:… é o formato padrão, e é feio. Com format, você monta a linha como quiser, com "lacunas" que o logging preenche:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%d/%m/%Y %H:%M:%S",
)
logging.info("Padaria aberta")
ovos = 3
logging.warning("Estoque de ovos baixo: %d", ovos)
$ python3 formato.py 18/10/2026 12:04:37 [INFO] Padaria aberta 18/10/2026 12:04:37 [WARNING] Estoque de ovos baixo: 3
- %(asctime)s
- A data e a hora da mensagem (ASCII time, "hora em texto"). Cada vez que você rodar, vai sair a sua hora.
- [%(levelname)s]
- O nome do nível, entre colchetes (os colchetes são texto seu, como o espaço).
- %(message)s
- A mensagem.
- datefmt="%d/%m/%Y %H:%M:%S"
- O formato da data: dia/mês/ano e hora:minuto:segundo, com os mesmos códigos do
strftime(aula Datas e Horas). - "…: %d", ovos
- A mensagem pode ter lacunas próprias, preenchidas pelos argumentos seguintes:
%dpara número inteiro,%spara qualquer coisa.
Cada lacuna tem o formato %(nome)s: um %, o nome entre parênteses e um s no fim (de string). Não confunda com as f-strings: aqui são parênteses, não chaves, e o texto não leva f na frente.
| Lacuna | O que entra |
|---|---|
%(message)s | A mensagem |
%(levelname)s | O nível: INFO, WARNING… |
%(levelname)-8s | O nível, completado com espaços até 8 letras (as colunas ficam alinhadas) |
%(asctime)s | Data e hora (padrão: 2026-10-18 12:04:37,694; os milésimos de segundo vêm depois da vírgula) |
%(name)s | O nome do registrador (root, ou o que você der) |
%(filename)s, %(lineno)d | O arquivo e a linha de onde a mensagem saiu |
%(funcName)s | A função de onde a mensagem saiu |
Mensagem com %s ou com f-string?
import logging
ovos = 3
logging.warning(f"Estoque de ovos: {ovos}")
logging.warning("Estoque de ovos: %s", ovos)
$ python3 fstring.py WARNING:root:Estoque de ovos: 3 WARNING:root:Estoque de ovos: 3
O resultado é o mesmo. A diferença é que, na segunda forma, o logging só monta o texto se a mensagem for aparecer; num debug desligado, ele nem perde tempo. A documentação oficial usa a forma com %s, e você vai encontrá-la em todo lugar. Para um programa pequeno, as duas servem; o que não serve é a vírgula do print, sem lacuna (veja Quando dá errado).
Missão: a linha sai como WARNING:root:Estoque de ovos baixo: 3. Acrescente um format ao basicConfig para ela sair WARNING: Estoque de ovos baixo: 3 (o nível, dois-pontos, espaço e a mensagem).
import logging
logging.basicConfig(level=logging.INFO)
ovos = 3
logging.warning("Estoque de ovos baixo: %d", ovos)
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
ovos = 3
logging.warning("Estoque de ovos baixo: %d", ovos)
4. O arquivo de log: filename
Um programa que roda sozinho (de madrugada, num servidor, todo dia às 6h) não tem ninguém olhando a tela. Aí as mensagens vão para um arquivo de log, que alguém lê depois. Basta dar o nome do arquivo:
import logging
logging.basicConfig(
filename="padaria.log",
level=logging.INFO,
format="%(asctime)s %(levelname)-8s %(message)s",
encoding="utf-8",
)
logging.info("Programa iniciado")
logging.warning("Faltou fermento")
print("Pronto. Veja o padaria.log")
$ python3 arquivo.py Pronto. Veja o padaria.log $ python3 arquivo.py Pronto. Veja o padaria.log $ cat padaria.log 2026-10-18 12:04:37,694 INFO Programa iniciado 2026-10-18 12:04:37,694 WARNING Faltou fermento 2026-10-18 12:04:37,742 INFO Programa iniciado 2026-10-18 12:04:37,742 WARNING Faltou fermento
- filename="padaria.log"
- As mensagens vão para esse arquivo, em vez da tela. Por isso, ao rodar, só apareceu o
print. - encoding="utf-8"
- A codificação do arquivo, para os acentos ficarem certos em qualquer computador (o mesmo cuidado do
open, na aula Abrindo Arquivos). - %(levelname)-8s
- O nível ocupando sempre 8 colunas:
INFOganha 4 espaços e as mensagens ficam alinhadas. - (as quatro linhas)
- A Ana rodou duas vezes, e o arquivo tem as duas. Por padrão, o
loggingacrescenta no fim do arquivo: o log é um histórico.
Para começar o arquivo do zero a cada execução, use filemode="w" (o mesmo "w" do open, de write, "escrever"):
import logging
logging.basicConfig(filename="hoje.log", filemode="w", level=logging.INFO, format="%(levelname)s %(message)s", encoding="utf-8")
logging.info("Só a execução de agora fica no arquivo")
$ python3 novo.py $ python3 novo.py $ cat hoje.log INFO Só a execução de agora fica no arquivo
Duas execuções, uma linha só. O padrão (acrescentar) é o filemode="a", de append.
Missão: as mensagens estão saindo na tela. Faça elas irem para o arquivo padaria.log (acrescente filename="padaria.log" e encoding="utf-8" ao basicConfig). Depois de rodar, o próprio programa mostra o que ficou no arquivo.
import logging
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
logging.info("Programa iniciado")
logging.warning("Faltou fermento")
try:
with open("padaria.log", encoding="utf-8") as arquivo:
print("No padaria.log:")
print(arquivo.read())
except FileNotFoundError:
print("O padaria.log ainda não existe.")
import logging
logging.basicConfig(filename="padaria.log", encoding="utf-8", level=logging.INFO, format="%(levelname)s %(message)s")
logging.info("Programa iniciado")
logging.warning("Faltou fermento")
try:
with open("padaria.log", encoding="utf-8") as arquivo:
print("No padaria.log:")
print(arquivo.read())
except FileNotFoundError:
print("O padaria.log ainda não existe.")
5. Registradores com nome: getLogger
Num programa maior, com vários arquivos, ajuda saber de que parte veio cada mensagem. logging.getLogger("nome") ("pegue o registrador") cria um registrador com nome, que você usa no lugar do logging.:
import logging
logging.basicConfig(level=logging.INFO, format="%(name)s - %(levelname)s - %(message)s")
log = logging.getLogger("estoque")
log.info("Contando os ingredientes")
log.warning("Açúcar acabando")
logging.info("Esta veio do root")
$ python3 nomes.py estoque - INFO - Contando os ingredientes estoque - WARNING - Açúcar acabando root - INFO - Esta veio do root
O %(name)s mostra quem registrou. As mensagens do log saem com estoque; a do logging.info, com root. O registrador com nome segue a configuração do basicConfig (nível e formato), porque, no fim, as mensagens dele sobem para o root.
getLogger(__name__)
Em programas com vários módulos, cada arquivo começa com log = logging.getLogger(__name__). O __name__ é o nome do módulo (estoque, para o estoque.py; aula Criando seu Módulo), e assim cada mensagem já diz de qual arquivo veio. O basicConfig fica só no programa principal.
6. Registrando um erro com o traceback: logging.exception
Dentro de um except, logging.exception registra uma mensagem de nível ERROR e o traceback do erro que foi pego, e o programa continua:
import logging
logging.basicConfig(format="%(levelname)s: %(message)s")
precos = {"bolo": 35}
try:
print(precos["torta"])
except KeyError:
logging.exception("Produto sem preço")
print("O programa continua.")
$ python3 excecao.py
ERROR: Produto sem preço
Traceback (most recent call last):
File "/home/ana/padaria/excecao.py", line 7, in <module>
print(precos["torta"])
~~~~~~^^^^^^^^^
KeyError: 'torta'
O programa continua.
É o melhor dos dois mundos: o programa não quebrou (o try segurou o erro, aula try e except), mas o traceback completo ficou registrado, com a linha exata, para alguém consertar depois. Num log em arquivo, isso vale ouro. Use logging.exception só dentro de um except.
Formas diferentes de mostrar uma mensagem
| Jeito | Quando usar |
|---|---|
print("…") | O resultado do programa, o que a pessoa veio ver |
print("…", file=sys.stderr) | Uma mensagem de erro rápida num script pequeno |
logging.debug/info/warning("…") | Anotações sobre o funcionamento, que você quer poder ligar, desligar ou mandar para um arquivo |
logging.getLogger(__name__) | Programas com vários arquivos (módulos) |
logging.exception("…") | Dentro de um except: registrar o erro com o traceback e seguir em frente |
raise / sys.exit | Quando o programa não deve seguir em frente (aulas raise e os e sys) |
Quando dá errado
basicConfig que não faz nada
import logging
logging.info("Começando")
logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logging.info("Encomenda da Bia anotada")
logging.warning("Estoque de ovos baixo")
$ python3 tarde.py WARNING:root:Estoque de ovos baixo
Nem o nível INFO nem o formato valeram: a linha saiu no formato padrão, e o info sumiu. Nenhuma mensagem de erro avisa. O motivo: o primeiro logging.info("Começando") chegou antes da configuração, e o logging, para não perder a mensagem, se configurou sozinho, com o padrão. A partir daí, o basicConfig encontra o registrador já configurado e não faz nada (está assim na documentação: ele só age se nada foi configurado ainda).
Como consertar: ponha o basicConfig logo depois dos import, antes de qualquer mensagem. Se não der, force=True ("forçar") apaga a configuração anterior:
$ python3 tarde2.py « o mesmo tarde.py, com force=True no basicConfig INFO: Encomenda da Bia anotada WARNING: Estoque de ovos baixo
print: --- Logging error ---
import logging
ovos = 3
logging.warning("Estoque de ovos:", ovos)
print("O programa continua.")
$ python3 virgula.py
--- Logging error ---
Traceback (most recent call last):
File "/usr/lib/python3.14/logging/__init__.py", line 1151, in emit
msg = self.format(record)
File "/usr/lib/python3.14/logging/__init__.py", line 999, in format
return fmt.format(record)
~~~~~~~~~~^^^^^^^^
File "/usr/lib/python3.14/logging/__init__.py", line 712, in format
record.message = record.getMessage()
~~~~~~~~~~~~~~~~~^^
File "/usr/lib/python3.14/logging/__init__.py", line 400, in getMessage
msg = msg % self.args
~~~~^~~~~~~~~~~
TypeError: not all arguments converted during string formatting
Call stack:
File "/home/ana/padaria/virgula.py", line 4, in <module>
logging.warning("Estoque de ovos:", ovos)
Message: 'Estoque de ovos:'
Arguments: (3,)
O programa continua.
$ echo $?
0
Um susto comprido, mas leia com calma:
- --- Logging error ---
- "Erro de registro": quem falhou foi o
loggingao montar a mensagem, não o seu programa. Por isso ele não quebra: oprintdo fim rodou, e o código de saída foi 0. - Traceback … logging/__init__.py
- As linhas de dentro do módulo
logging. Não é ali que está o problema. - TypeError: not all arguments converted during string formatting
- "Nem todos os argumentos foram usados na formatação": sobrou um argumento (o 3) e não havia lacuna (
%s) para ele. - Call stack: … virgula.py, line 4
- "Pilha de chamadas": aqui está a sua linha, a 4.
- Message / Arguments
- A mensagem e os argumentos que o
loggingrecebeu.
No print, a vírgula separa coisas para mostrar lado a lado. No logging, os argumentos depois da mensagem servem para preencher lacunas %s, e a mensagem não tinha nenhuma. E a anotação se perdeu: o aviso do estoque não foi registrado. Como consertar: logging.warning("Estoque de ovos: %s", ovos) ou logging.warning(f"Estoque de ovos: {ovos}").
format
import logging
logging.basicConfig(format="%(levelnome)s: %(message)s")
logging.warning("Teste")
$ python3 chave.py
--- Logging error ---
Traceback (most recent call last):
File "/usr/lib/python3.14/logging/__init__.py", line 472, in format
return self._format(record)
~~~~~~~~~~~~^^^^^^^^
File "/usr/lib/python3.14/logging/__init__.py", line 468, in _format
return self._fmt % values
~~~~~~~~~~^~~~~~~~
KeyError: 'levelnome'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/lib/python3.14/logging/__init__.py", line 1151, in emit
msg = self.format(record)
File "/usr/lib/python3.14/logging/__init__.py", line 999, in format
return fmt.format(record)
~~~~~~~~~~^^^^^^^^
File "/usr/lib/python3.14/logging/__init__.py", line 715, in format
s = self.formatMessage(record)
File "/usr/lib/python3.14/logging/__init__.py", line 684, in formatMessage
return self._style.format(record)
~~~~~~~~~~~~~~~~~~^^^^^^^^
File "/usr/lib/python3.14/logging/__init__.py", line 474, in format
raise ValueError('Formatting field not found in record: %s' % e)
ValueError: Formatting field not found in record: 'levelnome'
Call stack:
File "/home/ana/padaria/chave.py", line 4, in <module>
logging.warning("Teste")
Message: 'Teste'
Arguments: ()
Dois tracebacks, ligados por "durante o tratamento da exceção acima, aconteceu outra". Vá direto às últimas linhas de cada um: KeyError: 'levelnome' e Formatting field not found in record: 'levelnome' ("campo de formatação não encontrado no registro"). O nome certo é levelname, em inglês. O erro aparece em toda mensagem, não só na primeira. Como consertar: confira os nomes das lacunas na tabela da seção 3.
import logging logging.basicConfig(level="DEBUGG")
$ python3 nivel.py
Traceback (most recent call last):
File "/home/ana/padaria/nivel.py", line 3, in <module>
logging.basicConfig(level="DEBUGG")
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/logging/__init__.py", line 2120, in basicConfig
root.setLevel(level)
~~~~~~~~~~~~~^^^^^^^
File "/usr/lib/python3.14/logging/__init__.py", line 1495, in setLevel
self.level = _checkLevel(level)
~~~~~~~~~~~^^^^^^^
File "/usr/lib/python3.14/logging/__init__.py", line 210, in _checkLevel
raise ValueError("Unknown level: %r" % level)
ValueError: Unknown level: 'DEBUGG'
Desta vez é um erro de verdade, que para o programa: ValueError: Unknown level: 'DEBUGG', "nível desconhecido". O nível como texto tem de ser um dos cinco nomes, em maiúsculas. Como evitar: prefira as constantes (logging.DEBUG): um nome errado nelas dá AttributeError na hora, com a sugestão do Python:
$ python3 -c "import logging; logging.basicConfig(level=logging.DEBUGG)"
Traceback (most recent call last):
File "<string>", line 1, in <module>
import logging; logging.basicConfig(level=logging.DEBUGG)
^^^^^^^^^^^^^^
AttributeError: module 'logging' has no attribute 'DEBUGG'. Did you mean: 'DEBUG'?
debug não aparece"
É o caso da seção 1: sem basicConfig, o nível mínimo é WARNING, e debug e info somem em silêncio. Como consertar: logging.basicConfig(level=logging.DEBUG) no começo do programa.
Referência rápida
| Código | O que faz |
|---|---|
logging.debug/info/warning/error/critical("msg") | Registra uma mensagem com aquele nível |
logging.warning("x = %s", x) | Mensagem com lacuna preenchida pelo argumento |
logging.exception("msg") | Dentro de um except: nível ERROR + o traceback |
logging.basicConfig(level=logging.INFO) | O nível mínimo (padrão: WARNING) |
format="%(asctime)s %(levelname)s %(message)s" | O formato de cada linha |
datefmt="%d/%m/%Y %H:%M:%S" | O formato da data e hora |
filename="x.log", encoding="utf-8" | Grava no arquivo (acrescentando no fim) |
filemode="w" | Começa o arquivo do zero a cada execução |
force=True | Refaz a configuração, mesmo que já exista uma |
log = logging.getLogger(__name__) | Um registrador com nome (use log.info(…)) |
| Sintoma | Causa provável |
|---|---|
debug e info não aparecem | Falta basicConfig(level=…): o padrão é WARNING |
O basicConfig não muda nada | Já houve uma mensagem antes dele: mova para o começo ou use force=True |
--- Logging error --- com not all arguments converted | Argumento sem lacuna (a vírgula do print) |
Formatting field not found in record: '…' | Lacuna do format com nome errado |
ValueError: Unknown level: '…' | Nível como texto com nome errado |
| Nada na tela, mas o programa rodou | Tem filename=: as mensagens foram para o arquivo |
Exercícios
1. O nível de aviso
Complete para registrar um aviso (o nível que aparece mesmo sem configurar nada):
2. Mostrar tudo
Complete para que até as mensagens de depuração apareçam:
3. A lacuna da mensagem
Complete o formato para mostrar o nível e a mensagem:
4. Sem nenhuma configuração, quais destas mensagens aparecem?
5. A Ana rodou três vezes um programa com basicConfig(filename="padaria.log"). O que tem no arquivo?
6. Qual linha registra o estoque do jeito certo?
Missão final: o erro registrado, o programa de pé
Missão: o caixa da padaria soma os preços, mas a torta não tem preço e o programa quebra. Coloque o total += precos[item] dentro de um try e, no except KeyError:, registre com logging.exception("Produto sem preço: %s", item). O programa deve seguir até o fim e mostrar Total: R$ 35.
import logging
logging.basicConfig(format="%(levelname)s: %(message)s")
precos = {"bolo": 35}
pedido = ["bolo", "torta"]
total = 0
for item in pedido:
total += precos[item]
print(f"Total: R$ {total}")
import logging
logging.basicConfig(format="%(levelname)s: %(message)s")
precos = {"bolo": 35}
pedido = ["bolo", "torta"]
total = 0
for item in pedido:
try:
total += precos[item]
except KeyError:
logging.exception("Produto sem preço: %s", item)
print(f"Total: R$ {total}")
Resumo
loggingé o diário de bordo do programa: anotações com nível, que você liga, desliga e redireciona sem apagar código.- Cinco níveis, do menor ao maior:
DEBUG,INFO,WARNING,ERROR,CRITICAL. Sem configurar, só aparecem deWARNINGpara cima. - As mensagens vão para a saída de erros (
stderr), separadas do resultado doprint. logging.basicConfig(level=…, format=…, datefmt=…), uma vez, no começo, antes de qualquer mensagem (ou comforce=True).filename="x.log"grava num arquivo, acrescentando;filemode="w"começa do zero.- Argumentos da mensagem preenchem lacunas
%s:logging.warning("x = %s", x). A vírgula doprint, sem lacuna, dá--- Logging error ---. logging.getLogger(__name__)dá nome às mensagens;logging.exception, dentro de umexcept, registra o traceback e deixa o programa seguir.