Registro de dados variáveis ​​com novo formato de string


85

Eu uso o recurso de registro para python 2.7.3. A documentação para esta versão Python diz :

o pacote de registro é anterior às opções de formatação mais recentes, como str.format () e string.Template. Essas opções de formatação mais recentes são suportadas ...

Eu gosto do 'novo' formato com chaves. Então, estou tentando fazer algo como:

 log = logging.getLogger("some.logger")
 log.debug("format this message {0}", 1)

E obter o erro:

TypeError: nem todos os argumentos convertidos durante a formatação da string

O que eu sinto falta aqui?

PS eu não quero usar

log.debug("format this message {0}".format(1))

porque neste caso a mensagem está sempre sendo formatada, independentemente do nível do logger.


1
Você pode fazer isso: log.debug("format this message%d" % 1)
ronak

1
você precisa configurar o Formatterpara usar '{' como estilo
mata

2
@ronak Obrigado pelo conselho, mas não. Por favor, consulte a seção "ps" porque. BTW log.debug ("formatar esta mensagem% d", 1) - funciona bem.
MajesticRa

@mata Como configurá-lo? Existe documentação direta de como fazer isso?
MajesticRa

@mata eu encontrei. Por favor, dê uma resposta para que eu possa definir como "resposta certa. Obrigado mais uma vez.
MajesticRa

Respostas:


38

EDIT: dê uma olhada na StyleAdapterabordagem na resposta do @Dunes ao contrário desta resposta; permite usar estilos de formatação alternativos sem o clichê ao chamar os métodos do logger (debug (), info (), error (), etc).


Dos documentos - Uso de estilos de formatação alternativos :

As chamadas de registro (logger.debug (), logger.info () etc.) só aceitam parâmetros posicionais para a própria mensagem de registro, com parâmetros de palavra-chave usados ​​apenas para determinar opções de como lidar com a chamada de registro real (por exemplo, o parâmetro de palavra-chave exc_info para indicar que as informações de rastreamento devem ser registradas ou o parâmetro de palavra-chave extra para indicar informações contextuais adicionais a serem adicionadas ao log). Portanto, você não pode fazer chamadas de registro diretamente usando str.format () ou string.Template sintaxe, porque internamente o pacote de registro usa% -formatting para mesclar a string de formato e os argumentos variáveis. Não haveria nenhuma mudança enquanto preservava a compatibilidade com versões anteriores, uma vez que todas as chamadas de log que estão lá no código existente estarão usando strings de formato%.

E:

No entanto, existe uma maneira de usar a formatação {} - e $ - para construir suas mensagens de log individuais. Lembre-se de que, para uma mensagem, você pode usar um objeto arbitrário como uma string de formato de mensagem e que o pacote de registro irá chamar str () nesse objeto para obter a string de formato real.

Copie e cole no wherevermódulo:

class BraceMessage(object):
    def __init__(self, fmt, *args, **kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return self.fmt.format(*self.args, **self.kwargs)

Então:

from wherever import BraceMessage as __

log.debug(__('Message with {0} {name}', 2, name='placeholders'))

Nota: a formatação real é atrasada até que seja necessária, por exemplo, se as mensagens DEBUG não forem registradas, a formatação não será executada.


4
A partir do Python 3.6, você pode usar strings f como:num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose

12
@ P1h3r1e3d13 ao contrário do código de registro na resposta, f '' - strings executam a formatação imediatamente.
jfs

1
Direito. Eles funcionam aqui porque formatam e retornam uma string regular antes de chamar o método de log. Isso pode ou não ser relevante para alguém, então acho que vale a pena mencionar como uma opção.
Jacktose

6
@Jacktose Eu acho que os usuários não devem fazer login usando f-strings, isso anula os serviços de agregação de registros (por exemplo, sentinela). Há uma boa razão para que o log stdlib adie a modelagem de string.
wim

30

Aqui está outra opção que não possui os problemas de palavra-chave mencionados na resposta de Dunes. Ele pode lidar apenas com {0}argumentos posicionais ( ) e não com argumentos de palavra-chave ( {foo}). Também não requer duas chamadas para formatar (usando o sublinhado). Ele tem o fator ick de subclasse str:

class BraceString(str):
    def __mod__(self, other):
        return self.format(*other)
    def __str__(self):
        return self


class StyleAdapter(logging.LoggerAdapter):

    def __init__(self, logger, extra=None):
        super(StyleAdapter, self).__init__(logger, extra)

    def process(self, msg, kwargs):
        if kwargs.pop('style', "%") == "{":  # optional
            msg = BraceString(msg)
        return msg, kwargs

Você o usa assim:

logger = StyleAdapter(logging.getLogger(__name__))
logger.info("knights:{0}", "ni", style="{")
logger.info("knights:{}", "shrubbery", style="{")

Obviamente, você pode remover a marca de verificação com # optionalpara forçar todas as mensagens através do adaptador a usar a formatação de novo estilo.


Observação para quem ler esta resposta anos depois : a partir do Python 3.2 , você pode usar o parâmetro style com Formatterobjetos:

O registro (a partir de 3.2) fornece suporte aprimorado para esses dois estilos de formatação adicionais. A classe Formatter foi aprimorada para receber um parâmetro de palavra-chave opcional adicional chamado style. O padrão é '%', mas outros valores possíveis são '{'e '$', que correspondem aos outros dois estilos de formatação. A compatibilidade com versões anteriores é mantida por padrão (como você esperaria), mas ao especificar explicitamente um parâmetro de estilo, você obtém a capacidade de especificar strings de formato que funcionam com str.format()ou string.Template.

Os documentos fornecem o exemplo logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Observe que, neste caso, você ainda não pode chamar o loggercom o novo formato. Ou seja, o seguinte ainda não funcionará:

logger.info("knights:{say}", say="ni")  # Doesn't work!
logger.info("knights:{0}", "ni")  # Doesn't work either

6
Sua declaração sobre Python 3 está incorreta. O parâmetro de estilo se aplica apenas à string de formato do Formatador, não às mensagens de log individuais. A página que você vinculou diz explicitamente: "Não haveria como alterar isso, preservando a compatibilidade com versões anteriores".
mhsmith

1
Obrigado por me manter honesto. A primeira parte é menos útil agora, mas eu a reformulei em termos de Formatter, o que está correto agora (eu acho). O StyleAdapter ainda funciona,
Felipe

@falstro - obrigado por apontar isso. A versão atualizada agora deve funcionar. Como BraceStringé uma subclasse de string, é seguro retornar de__str__
Felipe

1
responda apenas que menciona style = "{", +1
Tom S.

24

A solução mais fácil seria usar o módulo excelentelogbook

import logbook
import sys

logbook.StreamHandler(sys.stdout).push_application()
logbook.debug('Format this message {k}', k=1)

Ou o mais completo:

>>> import logbook
>>> import sys
>>> logbook.StreamHandler(sys.stdout).push_application()
>>> log = logbook.Logger('MyLog')
>>> log.debug('Format this message {k}', k=1)
[2017-05-06 21:46:52.578329] DEBUG: MyLog: Format this message 1

Isso parece ótimo, mas há uma maneira de ter milissegundos em vez de apenas segundos?
Jeff,

@Jeff, o livro de registro permite definir manipuladores personalizados e usar formatos de string personalizados.
Thomas Orozco,

5
@Jeff Alguns anos depois - a precisão de tempo padrão é milissegundos.
Jan Vlcinsky

24

Essa foi a minha solução para o problema quando descobri que o log só usa a formatação no estilo printf. Ele permite que as chamadas de registro permaneçam as mesmas - sem sintaxe especial como log.info(__("val is {}", "x")). A mudança necessária para codificar é envolver o logger em a StyleAdapter.

from inspect import getargspec

class BraceMessage(object):
    def __init__(self, fmt, args, kwargs):
        self.fmt = fmt
        self.args = args
        self.kwargs = kwargs

    def __str__(self):
        return str(self.fmt).format(*self.args, **self.kwargs)

class StyleAdapter(logging.LoggerAdapter):
    def __init__(self, logger):
        self.logger = logger

    def log(self, level, msg, *args, **kwargs):
        if self.isEnabledFor(level):
            msg, log_kwargs = self.process(msg, kwargs)
            self.logger._log(level, BraceMessage(msg, args, kwargs), (), 
                    **log_kwargs)

    def process(self, msg, kwargs):
        return msg, {key: kwargs[key] 
                for key in getargspec(self.logger._log).args[1:] if key in kwargs}

O uso é:

log = StyleAdapter(logging.getLogger(__name__))
log.info("a log message using {type} substitution", type="brace")

É importante notar que esta implementação tem problemas se as palavras-chave utilizadas para substituição cinta incluem level, msg, args, exc_info, extraou stack_info. Esses são nomes de argumentos usados ​​pelo logmétodo de Logger. Se precisar de um desses nomes, modifique processpara excluí-los ou apenas removê-los log_kwargsda _logchamada. Em uma nota adicional, esta implementação também ignora silenciosamente palavras-chave com erros ortográficos destinadas ao Logger (por exemplo. ectra).


4
Este caminho é recomendado por python doc, docs.python.org/3/howto/…
eshizhan

12

Como outras respostas mencionam, a formatação estilo chave introduzida no Python 3.2 é usada apenas na string de formato, não nas mensagens de log reais.

Para habilitar a formatação do tipo chave na mensagem de log real, podemos corrigir um pouco do código do logger.

O seguinte corrige o loggingmódulo para criar uma get_loggerfunção que retornará um logger que usa a formatação de novo estilo para cada registro de log que ele controla.

import functools
import logging
import types

def _get_message(record):
    """Replacement for logging.LogRecord.getMessage
    that uses the new-style string formatting for
    its messages"""
    msg = str(record.msg)
    args = record.args
    if args:
        if not isinstance(args, tuple):
            args = (args,)
        msg = msg.format(*args)
    return msg

def _handle_wrap(fcn):
    """Wrap the handle function to replace the passed in
    record's getMessage function before calling handle"""
    @functools.wraps(fcn)
    def handle(record):
        record.getMessage = types.MethodType(_get_message, record)
        return fcn(record)
    return handle

def get_logger(name=None):
    """Get a logger instance that uses new-style string formatting"""
    log = logging.getLogger(name)
    if not hasattr(log, "_newstyle"):
        log.handle = _handle_wrap(log.handle)
    log._newstyle = True
    return log

Uso:

>>> log = get_logger()
>>> log.warning("{!r}", log)
<logging.RootLogger object at 0x4985a4d3987b>

Notas:

  • Totalmente compatível com métodos de registro normais (basta substituir logging.getLoggerpor get_logger)
  • Afetará apenas registradores específicos criados pela get_loggerfunção (não quebra pacotes de terceiros).
  • Se o logger for acessado novamente de uma logging.getLogger()chamada normal , a formatação do novo estilo ainda será aplicada.
  • kwargs não são suportados (torna impossível conflito com o built-in exc_info, stack_info, stacklevele extra).
  • O impacto no desempenho deve ser mínimo (reescrever um único ponteiro de função para cada mensagem de log).
  • A formatação da mensagem é atrasada até que seja emitida (ou não se a mensagem de log for filtrada).
  • Args são armazenados em logging.LogRecordobjetos como de costume (útil em alguns casos com manipuladores de log personalizados).
  • Olhando para o loggingcódigo - fonte do módulo , parece que ele deve funcionar desde o Python 2.6 quando str.formatfoi introduzido (mas eu só testei no 3.5 e superior)

2
A única resposta que considera que a string de depuração só deve ser computada se a mensagem do depurador for impressa. Obrigado!
Fafaman

2

Experimente logging.setLogRecordFactoryno Python 3.2+:

import collections
import logging


class _LogRecord(logging.LogRecord):

    def getMessage(self):
        msg = str(self.msg)
        if self.args:
            if isinstance(self.args, collections.Mapping):
                msg = msg.format(**self.args)
            else:
                msg = msg.format(*self.args)
        return msg


logging.setLogRecordFactory(_LogRecord)

Funciona, mas o problema é que você quebra módulos de terceiros que estão usando %formatação, pois a fábrica de registros é global para o módulo de registro.
jtaylor

1

Criei um formatador personalizado, chamado ColorFormatter, que lida com o problema desta forma:

class ColorFormatter(logging.Formatter):

    def format(self, record):
        # previous stuff, copy from logging.py…

        try:  # Allow {} style
            message = record.getMessage()  # printf
        except TypeError:
            message = record.msg.format(*record.args)

        # later stuff…

Isso o mantém compatível com várias bibliotecas. A desvantagem é que provavelmente não tem desempenho devido à possível tentativa de formatação da string duas vezes.


0

Solução semelhante à pR0Ps', envolvimento getMessageem LogRecordpelo embrulho makeRecord(em vez de handleem sua resposta) em casos de Loggerque deve ser nova formatação habilitado:

def getLogger(name):
    log = logging.getLogger(name)
    def Logger_makeRecordWrapper(name, level, fn, lno, msg, args, exc_info, func=None, extra=None, sinfo=None):
        self = log
        record = logging.Logger.makeRecord(self, name, level, fn, lno, msg, args, exc_info, func, sinfo)
        def LogRecord_getMessageNewStyleFormatting():
            self = record
            msg = str(self.msg)
            if self.args:
                msg = msg.format(*self.args)
            return msg
        record.getMessage = LogRecord_getMessageNewStyleFormatting
        return record
    log.makeRecord = Logger_makeRecordWrapper
    return log

Eu testei isso com Python 3.5.3.


Isso determina para onde vai a carga de realmente interpolar a string. Você o carrega antecipadamente no momento da criação do registro, garantindo que uma string estática é o que escapa do back-end, ou você apenas executa a formatação se a mensagem for finalmente exibida. Caso simples: a mensagem está realmente abaixo do nível aceitável para exibição. Além disso: essa não é uma boa maneira de "consertar" as coisas. Na verdade, construa uma subclasse Logger e use-a, cara.
amcgregor

-1

Aqui está algo bem simples que funciona:

debug_logger: logging.Logger = logging.getLogger("app.debug")

def mydebuglog(msg: str, *args, **kwargs):
    if debug_logger.isEnabledFor(logging.DEBUG):
        debug_logger.debug(msg.format(*args, **kwargs))

Então:

mydebuglog("hello {} {val}", "Python", val="World")
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.