web-dev-qa-db-ja.com

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

python 2.7.3。 このためのドキュメントPython version say

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

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

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

エラーが発生します:

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

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

追伸使いたくない

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

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

73
MajesticRa

EDIT:@ -Dunesの回答の StyleAdapterアプローチ この回答とは異なり、ロガーのメソッド(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メッセージがログに記録されない場合、フォーマットはまったく実行されません。

38
jfs

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

_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からは、 Formatterオブジェクトを使用したスタイルパラメーター

ロギング(3.2以降)では、これら2つの追加のフォーマットスタイルのサポートが改善されています。 Formatterクラスは、styleという名前の追加のオプションのキーワードパラメータを取るように拡張されました。デフォルトは_'%'_ですが、他の2つのフォーマットスタイルに対応する他の可能な値は_'{'_および_'$'_です。下位互換性はデフォルトで維持されます(予想どおり)が、スタイルパラメーターを明示的に指定することにより、 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
_
24
Felipe

これは、ロギングが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} substiution", type="brace")

ブレース置換に使用されるキーワードにlevelmsgargsexc_infoextraが含まれる場合、この実装に問題があることに注意してくださいstack_info。これらは、logLoggerメソッドで使用される引数名です。これらの名前のいずれかが必要な場合は、processを変更してこれらの名前を除外するか、log_kwargs呼び出しから_logを削除してください。さらに注意すべき点として、この実装は、ロガー向けのスペルミスのキーワード(たとえば、ectra)も黙って無視します。

20
Dunes

より簡単な解決策は、 excellent logbook module を使用することです

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
19
Thomas Orozco

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

Python 3.5の時点で、中括弧スタイルのフォーマットを使用してメッセージを記録する良い方法はありません。

ただし、Pythonのほとんどのものと同様に、ナイスではない方法があります。

次の例では、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
    it's 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>
_

ノート:

  • _get_logger_関数によって作成された特定のロガーにのみ影響します。
  • ロガーが通常のlogging.getLogger()呼び出しから再度アクセスされた場合、新しいスタイルのフォーマットが引き続き適用されます
  • kwargsはサポートされていません
  • パフォーマンスへの影響は最小限に抑える必要があります(ログメッセージごとに1つの関数ポインターを書き換えます)
  • メッセージのフォーマットは、出力されるまで遅延されます
  • 引数が_logging.LogRecord_オブジェクトに保存されるのを止めません(特定の場合に便利です)
  • loggingモジュールのソースコード を見ると、Python 2.6 when _str.format_ が導入されました(ただし、Python 3.5)でのみテストされました。
10
pR0Ps

logging.setLogRecordFactory in 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)
1
nexcvon

以下に、実際に機能するシンプルなものを示します。

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")
1
Dutch Masters

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