Registrazione di dati variabili con una nuova stringa di formato


85

Uso la funzione di registrazione per Python 2.7.3. La documentazione per questa versione di Python dice :

il pacchetto di registrazione precede le opzioni di formattazione più recenti come str.format () e string.Template. Queste nuove opzioni di formattazione sono supportate ...

Mi piace il formato "nuovo" con le parentesi graffe. Quindi sto cercando di fare qualcosa come:

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

E ottieni errore:

TypeError: non tutti gli argomenti vengono convertiti durante la formattazione della stringa

Cosa mi manca qui?

PS non voglio usare

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

perché in questo caso il messaggio viene sempre formattato indipendentemente dal livello di logger.


1
Puoi farlo: log.debug("format this message%d" % 1)
ronak

1
è necessario configurare l' Formatteruso di "{" come stile
mata

2
@ronak Grazie per il consiglio ma no. Per favore, vedere la sezione "ps" perché. BTW log.debug ("formatta questo messaggio% d", 1) - funziona bene.
MajesticRa

@mata Come configurarlo? Esiste una documentazione diretta per farlo?
MajesticRa

@mata l'ho trovato. Per favore, rendila una risposta in modo da poterla impostare come "risposta giusta. Grazie ancora.
MajesticRa

Risposte:


38

EDIT: dai un'occhiata StyleAdapterall'approccio nella risposta di @Dunes a differenza di questa risposta; permette di utilizzare stili di formattazione alternativi senza boilerplate durante la chiamata ai metodi del logger (debug (), info (), error (), ecc.).


Dai documenti - Uso di stili di formattazione alternativi :

Le chiamate di registrazione (logger.debug (), logger.info () ecc.) Accettano solo parametri posizionali per il messaggio di registrazione effettivo stesso, con i parametri delle parole chiave utilizzati solo per determinare le opzioni su come gestire la chiamata di registrazione effettiva (ad esempio il parametro della parola chiave exc_info per indicare che le informazioni di traceback devono essere registrate o il parametro della parola chiave extra per indicare ulteriori informazioni contestuali da aggiungere al log). Quindi non è possibile effettuare direttamente chiamate di registrazione utilizzando la sintassi str.format () o string.Template, perché internamente il pacchetto di registrazione utilizza% -formatting per unire la stringa di formato e gli argomenti della variabile. Non cambierebbe questa impostazione preservando la compatibilità con le versioni precedenti, dal momento che tutte le chiamate di registrazione presenti nel codice esistente useranno stringhe% -format.

E:

Esiste, tuttavia, un modo per utilizzare la formattazione {} e $ per costruire i propri messaggi di registro individuali. Ricorda che per un messaggio puoi usare un oggetto arbitrario come stringa di formato del messaggio e che il pacchetto di registrazione chiamerà str () su quell'oggetto per ottenere la stringa di formato effettiva.

Copia e incolla questo nel wherevermodulo:

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)

Poi:

from wherever import BraceMessage as __

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

Nota: la formattazione effettiva viene ritardata fino a quando non è necessario, ad esempio, se i messaggi DEBUG non vengono registrati, la formattazione non viene eseguita affatto.


4
A partire da Python 3.6, puoi usare le stringhe f in questo modo:num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose

12
@ P1h3r1e3d13 a differenza del codice di registrazione nella risposta, le stringhe f '' eseguono la formattazione immediatamente.
jfs

1
Destra. Funzionano qui perché formattano e restituiscono una stringa normale prima di chiamare il metodo di registro. Questo può o non può essere rilevante per qualcuno, quindi penso che valga la pena menzionarlo come opzione.
Jacktose

6
@Jacktose Penso che gli utenti non dovrebbero accedere usando le stringhe f, sconfigge i servizi di aggregazione dei log (ad esempio sentry). C'è una buona ragione per cui la registrazione stdlib differisce il templating della stringa.
wim

30

Ecco un'altra opzione che non presenta i problemi di parole chiave menzionati nella risposta di Dunes. Può gestire solo {0}argomenti positional ( ) e non argomenti keyword ( {foo}). Inoltre non richiede due chiamate per formattare (utilizzando il trattino basso). Ha il fattore ick della sottoclasse 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

Lo usi in questo modo:

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

Ovviamente, puoi rimuovere il segno di spunta con # optionalper forzare tutti i messaggi attraverso l'adattatore a utilizzare la formattazione del nuovo stile.


Nota per chiunque legga questa risposta anni dopo : a partire da Python 3.2 , puoi utilizzare il parametro style con gli Formatteroggetti:

La registrazione (a partire dalla versione 3.2) fornisce un supporto migliorato per questi due stili di formattazione aggiuntivi. La classe Formatter è stata migliorata per accettare un parametro di parola chiave opzionale aggiuntivo denominato style. L'impostazione predefinita è '%', ma altri valori possibili sono '{'e '$', che corrispondono agli altri due stili di formattazione. La compatibilità con le versioni precedenti viene mantenuta per impostazione predefinita (come ci si aspetterebbe), ma specificando esplicitamente un parametro di stile, si ha la possibilità di specificare stringhe di formato che funzionano con str.format()o string.Template.

I documenti forniscono l'esempio logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Nota che in questo caso non puoi ancora chiamare il loggercon il nuovo formato. Cioè, quanto segue ancora non funzionerà:

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

6
La tua dichiarazione su Python 3 non è corretta. Il parametro style si applica solo alla stringa del formato del formattatore, non ai singoli messaggi di registro. La pagina a cui ti sei collegato dice esplicitamente: "Non cambierebbe questa impostazione preservando la retrocompatibilità".
mhsmith

1
Grazie per avermi mantenuto onesto. La prima parte ora è meno utile, ma l'ho riformulata in termini di Formatter, che ora è corretta (credo). I StyleAdapter funziona ancora,
Felipe

@falstro - grazie per averlo sottolineato. La versione aggiornata dovrebbe ora funzionare. Poiché BraceStringè una sottoclasse di stringhe, è sicuro tornare da__str__
Felipe

1
unica risposta che menziona lo stile = "{", +1
Tom S.

24

La soluzione più semplice sarebbe utilizzare il modulo eccellentelogbook

import logbook
import sys

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

O il più 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

Sembra fantastico, ma c'è un modo per avere millisecondi anziché solo secondi?
Jeff

@ Jeff certo, il registro ti consente di definire gestori personalizzati con e utilizzare formati di stringa personalizzati.
Thomas Orozco

5
@Jeff Un paio di anni dopo - la precisione predefinita dell'ora è millisecondi.
Jan Vlcinsky,

24

Questa è stata la mia soluzione al problema quando ho scoperto che la registrazione utilizza solo la formattazione in stile printf. Consente alle chiamate di registrazione di rimanere invariate - nessuna sintassi speciale come log.info(__("val is {}", "x")). La modifica richiesta al codice consiste nell'incapsulare il logger in un file 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}

L'utilizzo è:

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

Vale la pena di notare che questa implementazione ha problemi se le parole chiave utilizzate per la sostituzione tutore includono level, msg, args, exc_info, extrao stack_info. Questi sono i nomi degli argomenti usati dal logmetodo di Logger. Se è necessario uno di questi nomi, modificare processper escludere questi nomi o semplicemente rimuoverli log_kwargsdalla _logchiamata. In un'ulteriore nota, questa implementazione ignora silenziosamente anche le parole chiave con errori di ortografia destinate al Logger (ad es. ectra).


4
Questo metodo è consigliato da python doc, docs.python.org/3/howto/…
eshizhan

12

Come menzionato in altre risposte, la formattazione in stile parentesi graffa introdotta in Python 3.2 viene utilizzata solo sulla stringa di formato, non sui messaggi di registro effettivi.

Per abilitare la formattazione in stile parentesi graffa sul messaggio di log effettivo, possiamo applicare una patch di scimmia al codice del logger.

Di seguito viene applicato un patch al loggingmodulo per creare una get_loggerfunzione che restituirà un logger che utilizza il nuovo stile di formattazione per ogni record di log che gestisce.

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

Utilizzo:

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

Appunti:

  • Pienamente compatibile con i normali metodi di registrazione (basta sostituire logging.getLoggercon get_logger)
  • Interesserà solo logger specifici creati dalla get_loggerfunzione (non interrompe i pacchetti di terze parti).
  • Se si accede nuovamente al logger da una normale logging.getLogger()chiamata, la formattazione del nuovo stile verrà comunque applicata.
  • kwargs non sono supportati (rende impossibile conflitto con il built-in exc_info, stack_info, stacklevele extra).
  • Il calo delle prestazioni dovrebbe essere minimo (riscrittura di un singolo puntatore a funzione per ogni messaggio di registro).
  • La formattazione del messaggio viene ritardata fino a quando non viene emesso (o non viene visualizzata affatto se il messaggio di registro viene filtrato).
  • Gli argomenti vengono archiviati sugli logging.LogRecordoggetti come al solito (utile in alcuni casi con gestori di log personalizzati).
  • Guardando il loggingcodice sorgente del modulo sembra che dovrebbe funzionare fino a Python 2.6 quando è str.formatstato introdotto (ma l'ho testato solo in 3.5 e versioni successive)

2
L'unica risposta che considera che la stringa di debug deve essere calcolata solo se il messaggio del debugger deve essere stampato. Grazie!
Fafaman

2

Prova logging.setLogRecordFactoryin 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)

Funziona, ma il problema è che si rompono i moduli di terze parti che utilizzano la %formattazione poiché il record factory è globale per il modulo di registrazione.
jtaylor

1

Ho creato un formattatore personalizzato, chiamato ColorFormatter che gestisce il problema in questo modo:

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…

Ciò lo mantiene compatibile con varie librerie. Lo svantaggio è che probabilmente non è performante a causa del potenziale tentativo di formattazione della stringa due volte.


0

Soluzione simile a pR0Ps', avvolgimento getMessagein LogRecordavvolgendo makeRecord(invece che handlenella loro risposta) nei casi di Loggercui dovrebbe essere nuovo-formattazione abilitati:

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

Ho provato questo con Python 3.5.3.


Questo determina dove va il carico dell'interpolazione effettiva della stringa. Lo carichi frontalmente al momento della creazione del record, assicurandoti che una stringa statica sia ciò che sfugge al back-end, oppure esegui la formattazione solo se il messaggio viene infine visualizzato. Caso semplice: il messaggio è effettivamente al di sotto del livello accettabile per la visualizzazione. Inoltre: questo non è un buon modo per "riparare" le cose. Realmente costruisci una sottoclasse Logger e usala, amico.
amcgregor

-1

Ecco qualcosa di molto semplice che funziona:

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))

Poi:

mydebuglog("hello {} {val}", "Python", val="World")
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.