Değişken verileri yeni biçim dizesiyle günlüğe kaydetme


85

Python 2.7.3 için günlük kaydı özelliğini kullanıyorum. Bu Python sürümü için belgeler şunları söylüyor :

günlük paketi str.format () ve string.Template gibi daha yeni biçimlendirme seçeneklerini önceden tarihlendirir. Bu yeni biçimlendirme seçenekleri desteklenmektedir ...

Küme parantezli 'yeni' biçimi seviyorum. Bu yüzden şöyle bir şey yapmaya çalışıyorum:

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

Ve hata al:

TypeError: dize biçimlendirme sırasında tüm bağımsız değişkenler dönüştürülmez

Burada neyi özledim?

PS kullanmak istemiyorum

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

çünkü bu durumda mesaj, kaydedici seviyesinden bağımsız olarak her zaman biçimlendirilir.


1
Bunu yapabilirsiniz: log.debug("format this message%d" % 1)
ronak

1
Formatter'{' öğesini stil olarak kullanacak şekilde yapılandırmanız gerekir
mata

2
@ronak Tavsiye için teşekkürler ama hayır. Lütfen neden "ps" bölümüne bakın. BTW log.debug ("bu iletiyi biçimlendir% d", 1) - sorunsuz çalışıyor.
MajesticRa

@mata Nasıl yapılandırılır? Bunu yapmanın doğrudan bir dokümantasyonu var mı?
MajesticRa

@mata buldum. Lütfen bir cevap verin ki "doğru cevap" olarak ayarlayabileyim. Bir kez daha teşekkür ederim.
MajesticRa

Yanıtlar:


38

DÜZENLEME: Bu cevabın aksine StyleAdapter@Dunes'un cevabındaki yaklaşıma bir göz atın ; logger'ın yöntemlerini (debug (), info (), error (), vb.) çağırırken ortak format olmadan alternatif biçimlendirme stillerinin kullanılmasına izin verir.


Dokümanlardan - Alternatif biçimlendirme stillerinin kullanımı :

Günlüğe kaydetme çağrıları (logger.debug (), logger.info () vb.), Yalnızca gerçek günlük kaydı mesajının kendisi için konumsal parametreleri alır, anahtar kelime parametreleri yalnızca gerçek günlük çağrısının nasıl ele alınacağına ilişkin seçenekleri belirlemek için kullanılır (ör. Exc_info anahtar kelime parametresi geri izleme bilgilerinin günlüğe kaydedilmesi gerektiğini veya günlüğe eklenecek ek bağlamsal bilgileri belirtmek için ekstra anahtar sözcük parametresi). Bu nedenle, str.format () veya string.Template sözdizimini kullanarak doğrudan günlük çağrıları yapamazsınız, çünkü dahili olarak günlük paketi, biçim dizesini ve değişken bağımsız değişkenlerini birleştirmek için% -formatting kullanır. Geriye dönük uyumluluğu korurken bunu değiştirmek gerekmez, çünkü mevcut kodda bulunan tüm günlük çağrıları% -format dizeleri kullanacaktır.

Ve:

Bununla birlikte, bireysel günlük mesajlarınızı oluşturmak için {} - ve $ - biçimlendirmesini kullanmanın bir yolu vardır. Bir mesaj için rastgele bir nesneyi bir ileti biçimi dizesi olarak kullanabileceğinizi ve günlük paketinin gerçek biçim dizesini elde etmek için bu nesne üzerinde str () öğesini çağıracağını hatırlayın.

Bunu wherevermodüle kopyalayıp yapıştırın :

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)

Sonra:

from wherever import BraceMessage as __

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

Not: gerçek biçimlendirme gerekli olana kadar ertelenir, örneğin, DEBUG mesajları günlüğe kaydedilmezse, biçimlendirme hiç yapılmaz.


4
Python 3.6'dan itibaren, f dizelerini şu şekilde kullanabilirsiniz:num = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose

12
@ P1h3r1e3d13 yanıttaki günlük kodunun aksine, f '' - dizeler hemen biçimlendirmeyi gerçekleştirir.
jfs

1
Sağ. Burada çalışırlar çünkü günlük yöntemini çağırmadan önce normal bir dizeyi biçimlendirir ve döndürürler. Bu biriyle alakalı olabilir veya olmayabilir, bu yüzden bir seçenek olarak bahsetmeye değer olduğunu düşünüyorum.
Jacktose

6
@Jacktose Kullanıcıların f-dizeleri kullanarak oturum açmaması gerektiğini düşünüyorum, günlük toplama hizmetlerini (örneğin nöbetçi) bozuyor. Stdlib günlüğünün dize şablonunu ertelemesinin iyi bir nedeni var.
wim

30

İşte Dunes'un cevabında bahsedilen anahtar kelime problemlerine sahip olmayan başka bir seçenek. Yalnızca konumsal ( {0}) argümanları işleyebilir, keyword ( {foo}) argümanlarını işleyemez . Ayrıca, biçimlendirmek için iki çağrı gerektirmez (alt çizgi kullanılarak). Alt sınıflandırma ick faktörüne sahiptir 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

Bunu şu şekilde kullanırsın:

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

Elbette # optional, bağdaştırıcı aracılığıyla tüm iletileri yeni stil biçimlendirmeyi kullanmaya zorlamak için ile not edilen onay işaretini kaldırabilirsiniz .


Daha sonra bu cevabı yıllar okuyan herkes için Not : başlayarak Python 3.2 şunları yapabilirsiniz tarzı parametresini kullanın ile Formatternesneler:

Günlük kaydı (3.2'den itibaren), bu iki ek biçimlendirme stili için gelişmiş destek sağlar. Formatter sınıfı, adında ek, isteğe bağlı bir anahtar kelime parametresi alacak şekilde geliştirilmiştir style. Bu varsayılan değerdir '%', ancak diğer olası değerler '{'ve '$', diğer iki biçimlendirme stiline karşılık gelir. Geriye dönük uyumluluk varsayılan olarak korunur (beklediğiniz gibi), ancak bir stil parametresini açıkça belirterek, str.format()veya ile çalışan biçim dizelerini belirtme olanağına sahip olursunuz string.Template.

Dokümanlar örnek sağlar logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

Bu durumda logger, yeni formatla hala arayamayacağınızı unutmayın . Yani, aşağıdakiler hala işe yaramayacak:

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

6
Python 3 hakkındaki açıklamanız yanlış. Stil parametresi, tek tek günlük mesajları için değil, yalnızca Biçimlendirici biçim dizesi için geçerlidir. Bağlandığınız sayfa açıkça şunu söylüyor: "Geriye dönük uyumluluğu korurken bunu değiştirmek olmaz".
mhsmith

1
Beni dürüst tuttuğunuz için teşekkürler. İlk bölüm şimdi daha az kullanışlı, ancak Formatterşimdi doğru olan (sanırım) açısından onu yeniden ifade ettim . StyleAdapter Hala işleri,
Felipe

@falstro - bunu belirttiğiniz için teşekkürler. Güncellenen sürüm şimdi çalışmalıdır. Yana BraceStringbir dize alt sınıf, bu kendisini dönmek için güvenli__str__
Felipe

1
sadece stilden bahseden cevap = "{", +1
Tom S.

24

Daha kolay çözüm, mükemmel modülü kullanmak olacaktır.logbook

import logbook
import sys

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

Veya daha eksiksiz:

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

Bu harika görünüyor, ancak saniyeler yerine milisaniyeye sahip olmanın bir yolu var mı?
Jeff

@Jeff elbette, günlük defteri özel dize biçimleriyle özel işleyicileri tanımlamanıza ve kullanmanıza izin verir.
Thomas Orozco

5
@Jeff Birkaç yıl sonra - varsayılan zaman hassasiyeti milisaniyedir.
Jan Vlcinsky

24

Bu, günlüğün yalnızca printf stili biçimlendirmeyi kullandığını bulduğumda soruna çözümümdü. Günlüğe kaydetme çağrılarının aynı kalmasına izin verir - gibi özel bir sözdizimi yoktur log.info(__("val is {}", "x")). Kod için gerekli değişiklik, kaydediciyi bir 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}

Kullanım:

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

Ayracı ikamesi için kullanılan anahtar kelimeler eklerseniz bu uygulama sorunları vardır belirterek It değerinde level, msg, args, exc_info, extraveya stack_info. Bunlar, logyöntemi tarafından kullanılan bağımsız değişken isimleridir Logger. Eğer bu isimlerin biri gerekiyorsa o zaman değiştirmek processbu isimleri dışlamak ya da sadece kaldırmak için log_kwargsgelen _logçağrı. Diğer bir not olarak, bu uygulama aynı zamanda Logger için yazılmış yanlış yazılmış anahtar kelimeleri sessizce yok sayar (örn. ectra).


4
Bu yol python doc, docs.python.org/3/howto/…
eshizhan

12

Diğer yanıtların da belirttiği gibi, Python 3.2'de tanıtılan küme ayracı stili biçimlendirme , gerçek günlük mesajlarında değil, yalnızca biçim dizesinde kullanılır.

Gerçek günlük mesajında ​​ayraç tarzı biçimlendirmeyi etkinleştirmek için, günlükçü kodunun bir kısmını maymunla yamayabiliriz.

Aşağıdaki yama, işlediği her günlük kaydı için yeni stil biçimlendirmesini kullanan bir günlükçüyü döndürecek loggingbir get_loggerişlev oluşturmak için modülü yamalar .

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

Kullanım:

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

Notlar:

  • Normal günlük yöntemlerle (sadece yerini tam uyumlu logging.getLoggerolan get_logger)
  • Yalnızca get_loggerişlev tarafından oluşturulan belirli kaydedicileri etkiler (3. taraf paketlerini bozmaz).
  • Kaydediciye normal bir logging.getLogger()aramadan tekrar erişilirse , yeni stil biçimlendirme yine de geçerli olacaktır.
  • kwargs (markaları imkansız ile çatışma yerleşik desteklenmez exc_info, stack_info, stacklevelve extra).
  • Performans vuruşu minimum düzeyde olmalıdır (her günlük mesajı için tek bir işlev işaretçisini yeniden yazma).
  • Mesajın biçimlendirilmesi, çıktı alınana kadar ertelenir (veya günlük mesajı filtrelenirse hiç olmaz).
  • Bağımsız değişkenler logging.LogRecordnesnelerde her zamanki gibi depolanır (bazı durumlarda özel günlük işleyicileriyle kullanışlıdır).
  • Bakarak loggingmodül kaynak kodu o zaman tüm Python 2.6 yolu geri çalışmalıdır gibi görünüyor str.formattanıtıldı (ama sadece 3.5'de yaptıkları testler yukarı ettik)

2
Hata ayıklama dizesinin yalnızca hata ayıklayıcı mesajı yazdırılacaksa hesaplanması gerektiğini düşünen tek yanıt. Teşekkürler!
Fafaman

2

logging.setLogRecordFactoryPython 3.2+ ile deneyin :

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)

İşe yarıyor, ancak sorun, %kayıt fabrikası günlük kaydı modülüne genel olduğundan, biçimlendirmeyi kullanan üçüncü taraf modüllerini kırmanızdır .
jtaylor

1

ColorFormatter adında , sorunu şu şekilde ele alan özel bir Formatter oluşturdum :

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…

Bu, çeşitli kitaplıklarla uyumlu olmasını sağlar. Bunun dezavantajı, dizenin biçimlendirmeye iki kez girme potansiyeli nedeniyle muhtemelen yüksek performanslı olmamasıdır.


0

, Paketleme pR0Ps benzer çözüm getMessageiçinde LogRecordsarma tarafından makeRecord(yerine handleörneklerini Yanıtlarında) Loggeryeni-biçimlendirme etkin olmalıdır:

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

Bunu Python 3.5.3 ile test ettim.


Bu, dizgenin gerçekte enterpolasyonunun yükünün nereye gittiğini belirler. Kayıt oluşturma sırasında önden yükleyerek arka uçtan kaçan şeyin statik bir dizge olmasını mı sağlıyorsunuz, yoksa yalnızca mesaj en sonunda görüntülendiğinde biçimlendirme mi yapıyorsunuz? Basit durum: mesaj gerçekte görüntüleme için kabul edilebilir seviyenin altındadır. Ek olarak: bu, şeyleri "yamalamak" için iyi bir yol değildir. Aslında bir Logger alt sınıfı oluştur ve onu kullan, adamım.
amcgregor

-1

İşte işe yarayan gerçekten basit bir şey:

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

Sonra:

mydebuglog("hello {} {val}", "Python", val="World")
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.