O Loguru é uma biblioteca que visa trazer uma loging agradável no Python.
Você já se sentiu preguiçoso por configurar um madeireiro e usou print()
? ... Eu fiz, mas o registro é fundamental para cada aplicação e facilita o processo de depuração. Usando o Loguru, você não tem desculpa para não usar o log do início, isso é tão simples quanto from loguru import logger
.
Além disso, esta biblioteca visa tornar o registro do Python menos doloroso, adicionando um monte de funcionalidades úteis que resolvem advertências dos madeiras padrão. Usando logs em seu aplicativo deve ser um automatismo, o Loguru tenta torná -lo agradável e poderoso.
pip install loguru
O principal conceito de Loguru é que existe um e apenas um logger
.
Por conveniência, é pré-configurado e saídas para stderr
para começar (mas isso é totalmente configurável).
from loguru import logger
logger . debug ( "That's it, beautiful and simple logging!" )
O logger
é apenas uma interface que envia mensagens de log para manipuladores configurados. Simples, certo?
Como adicionar um manipulador? Como configurar a formatação de logs? Como filtrar mensagens? Como definir o nível?
Uma resposta: a função add()
.
logger . add ( sys . stderr , format = "{time} {level} {message}" , filter = "my_module" , level = "INFO" )
Esta função deve ser usada para registrar pias responsáveis pelo gerenciamento de mensagens de log contextualizadas com um ditado de registro. Um coletor pode assumir muitas formas: uma função simples, um caminho de string, um objeto semelhante a um arquivo, uma função de coroutina ou um manipulador embutido.
Observe que você também pode remove()
um manipulador adicionado anteriormente usando o identificador retornado ao adicioná -lo. Isso é particularmente útil se você deseja substituir o manipulador padrão stderr
: basta ligar para logger.remove()
para começar.
Se você deseja enviar mensagens registradas para um arquivo, basta usar um caminho de string como pia. Também pode ser cronometrado automaticamente por conveniência:
logger . add ( "file_{time}.log" )
Também é facilmente configurável se você precisar girar o registrador, se desejar remover logs mais antigos ou se desejar comprimir seus arquivos no fechamento.
logger . add ( "file_1.log" , rotation = "500 MB" ) # Automatically rotate too big file
logger . add ( "file_2.log" , rotation = "12:00" ) # New file is created each day at noon
logger . add ( "file_3.log" , rotation = "1 week" ) # Once the file is too old, it's rotated
logger . add ( "file_X.log" , retention = "10 days" ) # Cleanup after some time
logger . add ( "file_Y.log" , compression = "zip" ) # Save some loved space
O Loguru favorece a formatação {}
muito mais elegante e poderosa sobre %
, as funções de registro são realmente equivalentes a str.format()
.
logger . info ( "If you're using Python {}, prefer {feature} of course!" , 3.6 , feature = "f-strings" )
Você já viu seu programa travando inesperadamente sem ver nada no arquivo de log? Você já notou que as exceções que ocorrem em threads não foram registradas? Isso pode ser resolvido usando o Decorador / Gerenciador de catch()
que garante que qualquer erro seja propagado corretamente ao logger
.
@ logger . catch
def my_function ( x , y , z ):
# An error? It's caught anyway!
return 1 / ( x + y + z )
O Loguru adiciona automaticamente as cores aos seus logs se o seu terminal for compatível. Você pode definir seu estilo favorito usando tags de marcação no formato da pia.
logger . add ( sys . stdout , colorize = True , format = "<green>{time}</green> <level>{message}</level>" )
Todas as pias adicionadas ao logger
são seguras por threads por padrão. Eles não são seguros de multiprocesso, mas você pode enqueue
as mensagens para garantir a integridade dos logs. Esse mesmo argumento também pode ser usado se você deseja o log de assíncrono.
logger . add ( "somefile.log" , enqueue = True )
As funções de coroutine usadas como pias também são suportadas e devem ser aguardadas com complete()
.
As exceções de registro que ocorrem no seu código são importantes para rastrear bugs, mas é bastante inútil se você não souber por que falhou. O Loguru ajuda a identificar problemas, permitindo que todo o rastreamento da pilha seja exibido, incluindo valores de variáveis (obrigado better_exceptions
por isso!).
O código:
# Caution, "diagnose=True" is the default and may leak sensitive data in prod
logger . add ( "out.log" , backtrace = True , diagnose = True )
def func ( a , b ):
return a / b
def nested ( c ):
try :
func ( 5 , c )
except ZeroDivisionError :
logger . exception ( "What?!" )
nested ( 0 )
Resultaria em:
2018-07-17 01: 38: 43.975 | Erro | __Main __: aninhado: 10 - O quê?!
Traceback (chamada mais recente):
Arquivo "test.py", linha 12, em <Dodule>
aninhado (0)
└ <função aninhada em 0x7F5C755322F0>
> Arquivo "test.py", linha 8, em aninhado
func (5, c)
│ └ 0
└ <função funct em 0x7f5c79fc2e18>
Arquivo "test.py", linha 4, em func
Retornar A / B
│ └ 0
└ 5
ZerodivisisionError: divisão por zero
Observe que esse recurso não funcionará no Python Repl padrão devido a dados de quadros indisponíveis.
Veja também: considerações de segurança ao usar o Loguru.
Quer que seus troncos sejam serializados para facilitar a análise ou passá -los? Usando o argumento serialize
, cada mensagem de log será convertida em uma string json antes de ser enviada para o coletor configurado.
logger . add ( custom_sink_function , serialize = True )
Usando bind()
você pode contextualizar suas mensagens de logger modificando o atributo de registro extra.
logger . add ( "file.log" , format = "{extra[ip]} {extra[user]} {message}" )
context_logger = logger . bind ( ip = "192.168.0.1" , user = "someone" )
context_logger . info ( "Contextualize your logger easily" )
context_logger . bind ( user = "someone_else" ). info ( "Inline binding of extra attribute" )
context_logger . info ( "Use kwargs to add context during formatting: {user}" , user = "anybody" )
É possível modificar um estado local de contexto temporariamente com contextualize()
:
with logger . contextualize ( task = task_id ):
do_something ()
logger . info ( "End of task" )
Você também pode ter mais controle de grão fino sobre seus logs combinando bind()
e filter
:
logger . add ( "special.log" , filter = lambda record : "special" in record [ "extra" ])
logger . debug ( "This message is not logged to the file" )
logger . bind ( special = True ). info ( "This message, though, is logged to the file!" )
Finalmente, o método patch()
permite que valores dinâmicos sejam anexados ao ditado de registro de cada nova mensagem:
logger . add ( sys . stderr , format = "{extra[utc]} {message}" )
logger = logger . patch ( lambda record : record [ "extra" ]. update ( utc = datetime . utcnow ()))
Em algum momento, você gostaria de registrar informações detalhadas sem a penalidade de desempenho na produção, você pode usar o método opt()
para conseguir isso.
logger . opt ( lazy = True ). debug ( "If sink level <= DEBUG: {x}" , x = lambda : expensive_function ( 2 ** 64 ))
# By the way, "opt()" serves many usages
logger . opt ( exception = True ). info ( "Error stacktrace added to the log message (tuple accepted too)" )
logger . opt ( colors = True ). info ( "Per message <blue>colors</blue>" )
logger . opt ( record = True ). info ( "Display values from the record (eg. {record[thread]})" )
logger . opt ( raw = True ). info ( "Bypass sink formatting n " )
logger . opt ( depth = 1 ). info ( "Use parent stack context (useful within wrapped functions)" )
logger . opt ( capture = False ). info ( "Keyword arguments not added to {dest} dict" , dest = "extra" )
O LogURU vem com todos os níveis de registro padrão aos quais trace()
e success()
são adicionados. Você precisa de mais? Em seguida, basta criá -lo usando a função level()
.
new_level = logger . level ( "SNAKY" , no = 38 , color = "<yellow>" , icon = "?" )
logger . log ( "SNAKY" , "Here we go!" )
O registro padrão é inchado com argumentos como datefmt
ou msecs
, %(asctime)s
%(created)s
, ingênuos datetimes sem informações do fuso horário, não formatação intuitiva, etc. Loguru corrige:
logger . add ( "file.log" , format = "{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}" )
Usar o logger em seus scripts é fácil e você pode configure()
no início. Para usar o LogURu de dentro de uma biblioteca, lembre-se de nunca ligar para add()
, mas use disable()
para que as funções de log se tornem nop. Se um desenvolvedor deseja ver os logs da sua biblioteca, ele pode enable()
novamente.
# For scripts
config = {
"handlers" : [
{ "sink" : sys . stdout , "format" : "{time} - {message}" },
{ "sink" : "file.log" , "serialize" : True },
],
"extra" : { "user" : "someone" }
}
logger . configure ( ** config )
# For libraries, should be your library's `__name__`
logger . disable ( "my_library" )
logger . info ( "No matter added sinks, this message is not displayed" )
# In your application, enable the logger in the library
logger . enable ( "my_library" )
logger . info ( "This message however is propagated to the sinks" )
Para conveniência adicional, você também pode usar a biblioteca loguru-config
para configurar o logger
diretamente de um arquivo de configuração.
Deseja usar Handler
de madeira integrado como uma pia Loguru?
handler = logging . handlers . SysLogHandler ( address = ( 'localhost' , 514 ))
logger . add ( handler )
Precisa propagar mensagens Loguru para o log padrão?
class PropagateHandler ( logging . Handler ):
def emit ( self , record : logging . LogRecord ) -> None :
logging . getLogger ( record . name ). handle ( record )
logger . add ( PropagateHandler (), format = "{message}" )
Deseja interceptar mensagens de registro padrão em direção aos seus afundos de Loguru?
class InterceptHandler ( logging . Handler ):
def emit ( self , record : logging . LogRecord ) -> None :
# Get corresponding Loguru level if it exists.
level : str | int
try :
level = logger . level ( record . levelname ). name
except ValueError :
level = record . levelno
# Find caller from where originated the logged message.
frame , depth = inspect . currentframe (), 0
while frame and ( depth == 0 or frame . f_code . co_filename == logging . __file__ ):
frame = frame . f_back
depth += 1
logger . opt ( depth = depth , exception = record . exc_info ). log ( level , record . getMessage ())
logging . basicConfig ( handlers = [ InterceptHandler ()], level = 0 , force = True )
Não gosta da formatação padrão do registro? Preferiria outra cor DEBUG
? Sem problemas:
# Linux / OSX
export LOGURU_FORMAT = "{time} | <lvl>{message}</lvl>"
# Windows
setx LOGURU_DEBUG_COLOR "<green>"
Muitas vezes, é útil extrair informações específicas de logs gerados, é por isso que o Loguru fornece um método parse()
que ajuda a lidar com logs e regexes.
pattern = r"(?P<time>.*) - (?P<level>[0-9]+) - (?P<message>.*)" # Regex with named groups
caster_dict = dict ( time = dateutil . parser . parse , level = int ) # Transform matching groups
for groups in logger . parse ( "file.log" , pattern , cast = caster_dict ):
print ( "Parsed:" , groups )
# {"level": 30, "message": "Log example", "time": datetime(2018, 12, 09, 11, 23, 55)}
A Loguru pode ser facilmente combinada com a Biblioteca Great notifiers
(deve ser instalada separadamente) para receber um e-mail quando seu programa falhar inesperadamente ou enviar muitos outros tipos de notificações.
import notifiers
params = {
"username" : "[email protected]" ,
"password" : "abc123" ,
"to" : "[email protected]"
}
# Send a single notification
notifier = notifiers . get_notifier ( "gmail" )
notifier . notify ( message = "The application is running!" , ** params )
# Be alerted on each error message
from notifiers . logging import NotificationHandler
handler = NotificationHandler ( "gmail" , defaults = params )
logger . add ( handler , level = "ERROR" )
Embora o impacto da exploração de registro nas performances seja insignificante, um madeireiro de custo zero permitiria usá-lo em qualquer lugar sem muita preocupação. Em um próximo lançamento, as funções críticas do Loguru serão implementadas em C para a velocidade máxima.