新しいフォーマット文字列で変数データをログに記録する


85

Python2.7.3のログ機能を使用しています。このPythonバージョンのドキュメントには次のように書かれています

ロギングパッケージは、str.format()やstring.Templateなどの新しいフォーマットオプションよりも前のものです。これらの新しいフォーマットオプションがサポートされています...

中括弧付きの「新しい」形式が好きです。だから私は次のようなことをしようとしています:

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

そしてエラーが発生します:

TypeError:文字列のフォーマット中にすべての引数が変換されるわけではありません

ここで何が恋しいですか?

PS使いたくない

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

この場合、メッセージはロガーレベルに関係なく常にフォーマットされているためです。


1
これを行うことができます: log.debug("format this message%d" % 1)
ronak 2012年

1
Formatter'{'をスタイルとして使用するように構成する必要があります
mata

2
@ronakアドバイスをありがとうございますが、違います。理由は「ps」セクションを参照してください。ところでlog.debug( "format this message%d"、1)-正常に動作します。
majesticRa

@mata設定方法は?それを行うための直接の文書はありますか?
majesticRa

@mata見つけました。私は正しい答え」として、それを設定することができますので答えてくださいもう一度ありがとう。。
MajesticRa

回答:


38

編集:この回答とは異なりStyleAdapter、@ Dunesの回答のアプローチを見てください。これにより、ロガーのメソッド(debug()、info()、error()など)を呼び出すときに、ボイラープレートなしで代替のフォーマットスタイルを使用できます。


ドキュメントから—代替フォーマットスタイルの使用

ロギング呼び出し(logger.debug()、logger.info()など)は、実際のロギングメッセージ自体の位置パラメーターのみを取り、キーワードパラメーターは、実際のロギング呼び出しを処理する方法のオプションを決定するためにのみ使用されます(例:exc_infoキーワードパラメーター)。トレースバック情報をログに記録する必要があることを示すため、またはログに追加する追加のコンテキスト情報を示すための追加のキーワードパラメーター)。したがって、内部的にロギングパッケージは%-formattingを使用してフォーマット文字列と変数引数をマージするため、str.format()またはstring.Template構文を使用してロギング呼び出しを直接行うことはできません。既存のコードにあるすべてのロギング呼び出しは%形式の文字列を使用するため、下位互換性を維持しながらこれを変更することはありません。

そして:

ただし、{}-および$-フォーマットを使用して個々のログメッセージを作成する方法があります。メッセージの場合、任意のオブジェクトをメッセージ形式の文字列として使用でき、ロギングパッケージはそのオブジェクトに対してstr()を呼び出して、実際の形式の文字列を取得することを思い出してください。

これをコピーしてwhereverモジュールに貼り付けます。

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)

次に:

from wherever import BraceMessage as __

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

注:実際のフォーマットは、必要になるまで遅延されます。たとえば、DEBUGメッセージがログに記録されない場合、フォーマットはまったく実行されません。


4
:Pythonの3.6の時点で、あなたはとてもように、F-文字列を使用することができますnum = 2; name = 'placeholders'; log.debug(f'Message with {num} {name}')
Jacktose

11
@ P1h3r1e3d13回答のロギングコードとは異なり、f ''-文字列はすぐにフォーマットを実行します。
jfs 2018年

1
正しい。logメソッドを呼び出す前に通常の文字列をフォーマットして返すため、ここで機能します。それは誰かに関係があるかもしれないし、関係がないかもしれないので、オプションとして言及する価値があると思います。
Jacktose 2018年

3
@Jacktoseユーザーはf文字列を使用してログを記録するべきではないと思います。ログ集約サービス(歩哨など)が無効になります。stdlibロギングが文字列テンプレートを延期するのには十分な理由があります。
WIM

31

Dunesの回答に記載されているキーワードの問題がない別のオプションがあります。位置({0})引数のみを処理でき、キーワード({foo})引数は処理できません。また、フォーマットするために2回呼び出す必要はありません(アンダースコアを使用)。それはサブクラス化のick-factorを持っています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

あなたはそれをこのように使います:

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

もちろん、で示されたチェックを削除し# optionalて、アダプタを介したすべてのメッセージに新しいスタイルのフォーマットを使用させることができます。


数年後にこの回答を読んだ人への注意Python 3.2以降、オブジェクトでstyleパラメーター使用できFormatterます。

ロギング(3.2以降)は、これら2つの追加のフォーマットスタイルのサポートを改善します。Formatterクラスが拡張され、style。という名前の追加のオプションのキーワードパラメーターを受け取るようになりました。このデフォルトは'%'、他の可能な値である'{''$'他の二つのフォーマットのスタイルに対応します。下位互換性はデフォルトで維持されますが(予想どおり)、スタイルパラメータを明示的に指定することで、str.format()または で機能するフォーマット文字列を指定できますstring.Template

ドキュメントは例を提供します logging.Formatter('{asctime} {name} {levelname:8s} {message}', style='{')

この場合でもlogger、新しい形式でを呼び出すことはできません。つまり、次はまだ機能しません。

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

5
Python3に関するあなたの声明は正しくありません。styleパラメータはFormatterフォーマット文字列にのみ適用され、個々のログメッセージには適用されません。リンクしたページには、「下位互換性を維持しながらこれを変更することはありません」と明示的に記載されています。
mhsmith 2015年

1
正直に言ってくれてありがとう。最初の部分は今ではあまり役に立ちませんが、私はそれを、Formatter今は正しいという観点から言い換えました(私は思います)。StyleAdapter まだ作品、
フェリペ

@ falstro-それを指摘してくれてありがとう。更新されたバージョンが機能するはずです。BraceStringは文字列サブクラスなので、元に戻すのは安全です__str__
Felipe

1
style = "{"、+ 1
Tom S.

24

これは、ロギングがprintfスタイルのフォーマットのみを使用していることがわかったときの問題に対する私の解決策でした。これにより、ロギング呼び出しを同じままにすることができますlog.info(__("val is {}", "x"))。のような特別な構文はありません。コードに必要な変更は、ロガーをでラップすることです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}

使用法は次のとおりです。

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

それのブレース置換に使用するキーの単語が含まれている場合、この実装は問題を抱えていることは注目に値するlevelmsgargsexc_infoextraまたはstack_info。これらは、のlogメソッドで使用される引数名ですLogger。これらの名前のいずれかが必要な場合は、これらの名前processを除外するように変更するかlog_kwargs_log通話から削除してください。さらに、この実装は、ロガー向けのスペルミスのあるキーワードも黙って無視します(例ectra)。


4
この方法は、python doc、docs.python.org
/

23

より簡単な解決策は、優れたモジュールを使用することですlogbook

import logbook
import sys

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

またはより完全な:

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

これは見栄えがしますが、秒だけでなくミリ秒にする方法はありますか?
ジェフ

@Jeff確かに、logbookを使用すると、カスタムハンドラーを定義し、カスタム文字列形式を使用できます。
トーマスオロスコ2013年

5
@Jeff数年後-デフォルトの時間精度はミリ秒です。
Jan Vlcinsky 2016

12

他の回答が述べているように、Python 3.2で導入された中括弧スタイルのフォーマットは、実際のログメッセージではなく、フォーマット文字列でのみ使用されます。

実際のログメッセージで中括弧スタイルのフォーマットを有効にするために、ロガーコードの一部にモンキーパッチを適用できます。

以下は、loggingモジュールにパッチを適用して、get_logger処理するすべてのログレコードに新しいスタイルのフォーマットを使用するロガーを返す関数を作成します。

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

使用法:

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

ノート:

  • 通常の伐採方法(ちょうど置き換えると完全互換logging.getLoggerget_logger
  • get_logger関数によって作成された特定のロガーにのみ影響します(サードパーティのパッケージを壊しません)。
  • 通常のlogging.getLogger()呼び出しからロガーに再度アクセスした場合でも、新しいスタイルのフォーマットが適用されます。
  • kwargsからは(ビルトインと競合することが不可能にサポートされていないexc_infostack_infostacklevelおよびextra)。
  • パフォーマンスへの影響は最小限に抑える必要があります(ログメッセージごとに1つの関数ポインターを書き換えます)。
  • メッセージのフォーマットは、出力されるまで遅延されます(または、ログメッセージがフィルタリングされている場合はまったく遅延されません)。
  • 引数はlogging.LogRecord通常どおりオブジェクトに保存されます(カスタムログハンドラーを使用すると便利な場合があります)。
  • loggingモジュールのソースコードを見ると、str.format導入されたときはPython 2.6までさかのぼって動作するはずです(ただし、3.5以降でのみテストしました)

2
デバッガメッセージを出力する場合にのみデバッグ文字列を計算する必要があると考える唯一の答え。ありがとう!
ファファマン2016

2

logging.setLogRecordFactoryPython3.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)

それは機能しますが、問題は%、レコードファクトリがロギングモジュールに対してグローバルであるため、フォーマットを使用しているサードパーティモジュールを壊すことです。
jtaylor

1

このような問題を処理するColorFormatterと呼ばれるカスタムフォーマッターを作成しました。

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…

これにより、さまざまなライブラリとの互換性が保たれます。欠点は、文字列のフォーマットを2回試行する可能性があるため、おそらくパフォーマンスが低下することです。


0

これが機能する本当に簡単なものです:

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

次に:

mydebuglog("hello {} {val}", "Python", val="World")

0

pR0Psと同様のソリューションであり、そのインスタンスで(回答ではなく)ラップgetMessageするLogRecordことでラップインする場合は、新しいフォーマットを有効にする必要があります。makeRecordhandleLogger

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

私はこれをPython3.5.3でテストしました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.