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.

Você vai aprender
  • Por que usar logging em vez de print para as mensagens "de bastidor".
  • Os cinco níveis: DEBUG, INFO, WARNING, ERROR e CRITICAL, 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.getLogger e a registrar um erro com o traceback (logging.exception).
  • Os erros reais: o basicConfig que não faz nada, a vírgula do print, 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 cadernoNível do loggingNúmeroPara quê
Cinza: detalhe miúdoDEBUG ("depuração")10Detalhes para quem está caçando um problema: "abrindo o arquivo", "a variável vale 3"
Azul: aconteceu algo normalINFO ("informação")20O programa funcionando como deveria: "encomenda anotada", "padaria aberta"
Amarelo: atençãoWARNING ("aviso")30Ainda funciona, mas algo merece atenção: "estoque de ovos baixo"
Vermelho: deu erradoERROR ("erro")40Uma tarefa falhou: "não deu para salvar a encomenda"
Preto: emergênciaCRITICAL ("crítico")50O 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 chama logging.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).

printlogging
Para quêO resultado do programa, o que a pessoa veio verAnotações sobre o que o programa está fazendo
Para onde vaiSaída normal (stdout)Saída de erros (stderr), ou um arquivo
Ligar e desligarSó apagando a linhaMudando o nível, numa linha só
Hora, nível, quem registrouVocê escreve à mãoAutomá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: %d para número inteiro, %s para 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.

LacunaO que entra
%(message)sA mensagem
%(levelname)sO nível: INFO, WARNING…
%(levelname)-8sO nível, completado com espaços até 8 letras (as colunas ficam alinhadas)
%(asctime)sData e hora (padrão: 2026-10-18 12:04:37,694; os milésimos de segundo vêm depois da vírgula)
%(name)sO nome do registrador (root, ou o que você der)
%(filename)s, %(lineno)dO arquivo e a linha de onde a mensagem saiu
%(funcName)sA 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: INFO ganha 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 logging acrescenta 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.

O costume: 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

JeitoQuando 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.exitQuando o programa não deve seguir em frente (aulas raise e os e sys)

Quando dá errado

O 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
A vírgula do 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 logging ao montar a mensagem, não o seu programa. Por isso ele não quebra: o print do 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 logging recebeu.

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}").

Uma lacuna com o nome errado no 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.

Um nível que não existe
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'?
"Meu 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ódigoO 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=TrueRefaz a configuração, mesmo que já exista uma
log = logging.getLogger(__name__)Um registrador com nome (use log.info(…))
SintomaCausa provável
debug e info não aparecemFalta basicConfig(level=…): o padrão é WARNING
O basicConfig não muda nadaJá houve uma mensagem antes dele: mova para o começo ou use force=True
--- Logging error --- com not all arguments convertedArgumento 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 rodouTem 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):

logging.("Estoque de ovos baixo")

2. Mostrar tudo

Complete para que até as mensagens de depuração apareçam:

logging.basicConfig(level=logging.)

3. A lacuna da mensagem

Complete o formato para mostrar o nível e a mensagem:

format="%(levelname)s: %()s"

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 de WARNING para cima.
  • As mensagens vão para a saída de erros (stderr), separadas do resultado do print.
  • logging.basicConfig(level=…, format=…, datefmt=…), uma vez, no começo, antes de qualquer mensagem (ou com force=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 do print, sem lacuna, dá --- Logging error ---.
  • logging.getLogger(__name__) dá nome às mensagens; logging.exception, dentro de um except, registra o traceback e deixa o programa seguir.