web-dev-qa-db-ja.com

Pythonのログ形式はメッセージログレベルに応じて変更できますか?

出力を画面に出力するためにPythonのloggingメカニズムを使用しています。これを印刷ステートメントで行うこともできますが、ユーザーが特定のタイプの出力を無効にできるように細かく調整できるようにしたいと考えています。エラー用に出力されるフォーマットが好きですが、出力レベルが「info」の場合は、より単純なフォーマットを使用します。

例えば:

  logger.error("Running cmd failed")
  logger.info("Running cmd passed")

この例では、エラーのフォーマットを別の方法で出力したいと思います。

# error
Aug 27, 2009 - ERROR: Running cmd failed
# info
Running cmd passed

複数のログオブジェクトがなくても、ログレベルごとに異なるフォーマットを使用できますか?出力のログ方法を決定するためのif/elseステートメントが多数あるため、ロガーが作成されたら、ロガーを変更せずにこれを行うことをお勧めします。

60
bedwyr

はい、カスタムFormatterクラスを持つことでこれを行うことができます:

class MyFormatter(logging.Formatter):
    def format(self, record):
        #compute s according to record.levelno
        #for example, by setting self._fmt
        #according to the levelno, then calling
        #the superclass to do the actual formatting
        return s

次に、MyFormatterインスタンスをハンドラーにアタッチします。

31
Vinay Sajip

私はこの問題に遭遇し、上記の例で残った「穴」を埋めるのに苦労しました。これが、私が使用した、より完全で機能するバージョンです。うまくいけば、これは誰かを助ける:

# Custom formatter
class MyFormatter(logging.Formatter):

    err_fmt  = "ERROR: %(msg)s"
    dbg_fmt  = "DBG: %(module)s: %(lineno)d: %(msg)s"
    info_fmt = "%(msg)s"


    def __init__(self, fmt="%(levelno)s: %(msg)s"):
        logging.Formatter.__init__(self, fmt)


    def format(self, record):

        # Save the original format configured by the user
        # when the logger formatter was instantiated
        format_orig = self._fmt

        # Replace the original format with one customized by logging level
        if record.levelno == logging.DEBUG:
            self._fmt = MyFormatter.dbg_fmt

        Elif record.levelno == logging.INFO:
            self._fmt = MyFormatter.info_fmt

        Elif record.levelno == logging.ERROR:
            self._fmt = MyFormatter.err_fmt

        # Call the original formatter class to do the grunt work
        result = logging.Formatter.format(self, record)

        # Restore the original format configured by the user
        self._fmt = format_orig

        return result

編集:

Halloleoの賛辞、これはスクリプトで上記を使用する方法の例です。

fmt = MyFormatter()
hdlr = logging.StreamHandler(sys.stdout)

hdlr.setFormatter(fmt)
logging.root.addHandler(hdlr)
logging.root.setLevel(DEBUG)

編集2:

Python3のロギングが少し変更されました。 Python3のアプローチについては here を参照してください。

60
JS.

そして再びJSの答えのように、よりコンパクトに。

class SpecialFormatter(logging.Formatter):
    FORMATS = {logging.DEBUG :"DBG: %(module)s: %(lineno)d: %(message)s",
               logging.ERROR : "ERROR: %(message)s",
               logging.INFO : "%(message)s",
               'DEFAULT' : "%(levelname)s: %(message)s"}

    def format(self, record):
        self._fmt = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
        return logging.Formatter.format(self, record)

hdlr = logging.StreamHandler(sys.stderr)
hdlr.setFormatter(SpecialFormatter())
logging.root.addHandler(hdlr)
logging.root.setLevel(logging.INFO)
16
estani

これは、フォーマットスタイルに依存するlogging.Formatterの新しい実装への estaniの回答 の改作です。鉱山は'{'スタイルのフォーマットに依存していますが、それを適応させることができます。より一般的になるように改良し、__init__の引数としてフォーマットスタイルとカスタムメッセージを選択できるようにすることもできます。

class SpecialFormatter(logging.Formatter):
    FORMATS = {logging.DEBUG : logging._STYLES['{']("{module} DEBUG: {lineno}: {message}"),
           logging.ERROR : logging._STYLES['{']("{module} ERROR: {message}"),
           logging.INFO : logging._STYLES['{']("{module}: {message}"),
           'DEFAULT' : logging._STYLES['{']("{module}: {message}")}

    def format(self, record):
        # Ugly. Should be better
        self._style = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
        return logging.Formatter.format(self, record)

hdlr = logging.StreamHandler(sys.stderr)
hdlr.setFormatter(SpecialFormatter())
logging.root.addHandler(hdlr)
logging.root.setLevel(logging.INFO)
8
Evpok

スタイルや内部フィールドに依存する代わりに、record.levelno(または他の基準)に応じて他のフォーマッターに委譲するフォーマッターを作成することもできます。これは、私の控えめな意見では、少しすっきりとした解決策です。以下のコードはすべてのpython version> = 2.7で機能するはずです。

簡単な方法は次のようになります。

class MyFormatter(logging.Formatter):

    default_fmt = logging.Formatter('%(levelname)s in %(name)s: %(message)s')
    info_fmt = logging.Formatter('%(message)s')

    def format(self, record):
        if record.levelno == logging.INFO:
            return self.info_fmt.format(record)
        else:
            return self.default_fmt.format(record)

しかし、もっと一般的にすることもできます:

class VarFormatter(logging.Formatter):

    default_formatter = logging.Formatter('%(levelname)s in %(name)s: %(message)s')

    def __init__(self, formats):
        """ formats is a dict { loglevel : logformat } """
        self.formatters = {}
        for loglevel in formats:
            self.formatters[loglevel] = logging.Formatter(formats[loglevel])

    def format(self, record):
        formatter = self.formatters.get(record.levelno, self.default_formatter)
        return formatter.format(record)

ここでは入力としてdictを使用しましたが、明らかに、ボートをフロートさせるものであれば、タプル、** kwargsなども使用できます。これは次のように使用されます:

formatter = VarFormatter({logging.INFO: '[%(message)s]', 
                          logging.WARNING: 'warning: %(message)s'})
<... attach formatter to logger ...>
8
Joris

上記のソリューションは3.3.3リリースで動作します。ただし3.3.4では、次のエラーが発生します。

FORMATS = { logging.DEBUG : logging._STYLES['{']("{module} DEBUG: {lineno}: {message}"),

TypeError: 'Tuple'オブジェクトは呼び出し不可能です

ロギングクラスLib\logging__init __。pyをいくつか検索した後、問題の原因となるデータ構造が3.3.3から3.3.4に変更されていることがわかりました

3.3.3

_STYLES = {
    '%': PercentStyle,
    '{': StrFormatStyle,
    '$': StringTemplateStyle
}

3.3.4

_STYLES = {
   '%': (PercentStyle, BASIC_FORMAT),
   '{': (StrFormatStyle, '{levelname}:{name}:{message} AA'),
    '$': (StringTemplateStyle, '${levelname}:${name}:${message} BB'),
}

したがって、更新されたソリューションは

class SpecialFormatter(logging.Formatter):
     FORMATS = {logging.DEBUG : logging._STYLES['{'][0]("{module} DEBUG: {lineno}: {message}"),
       logging.ERROR : logging._STYLES['{'][0]("{module} ERROR: {message}"),
       logging.INFO : logging._STYLES['{'][0]("{module}: {message}"),
       'DEFAULT' : logging._STYLES['{'][0]("{module}: {message}")}

 def format(self, record):
    # Ugly. Should be better
    self._style = self.FORMATS.get(record.levelno, self.FORMATS['DEFAULT'])
    return logging.Formatter.format(self, record)
6
user1837990

特定のレベルのフォーマットをスキップするだけの場合は、次のような他の回答よりも簡単なことを行うことができます。

class FormatterNotFormattingInfo(logging.Formatter):
    def __init__(self, fmt = '%(levelname)s:%(message)s'):
        logging.Formatter.__init__(self, fmt)

    def format(self, record):
        if record.levelno == logging.INFO:
            return record.getMessage()
        return logging.Formatter.format(self, record)

これには、3.2リリースの前後にself._fmtやself._styleなどの内部変数を使用しないことで機能するという利点もあります。

3
JDiMatteo

これを行う1つの方法

クラスを定義します

import logging

class CustomFormatter(logging.Formatter):
    """Logging Formatter to add colors and count warning / errors"""

    grey = "\x1b[38;21m"
    yellow = "\x1b[33;21m"
    red = "\x1b[31;21m"
    bold_red = "\x1b[31;1m"
    reset = "\x1b[0m"
    format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s (%(filename)s:%(lineno)d)"

    FORMATS = {
        logging.DEBUG: grey + format + reset,
        logging.INFO: grey + format + reset,
        logging.WARNING: yellow + format + reset,
        logging.ERROR: red + format + reset,
        logging.CRITICAL: bold_red + format + reset
    }

    def format(self, record):
        log_fmt = self.FORMATS.get(record.levelno)
        formatter = logging.Formatter(log_fmt)
        return formatter.format(record)

ロガーのインスタンス化

# create logger with 'spam_application'
logger = logging.getLogger("My_app")
logger.setLevel(logging.DEBUG)

# create console handler with a higher log level
ch = logging.StreamHandler()
ch.setLevel(logging.DEBUG)

ch.setFormatter(CustomFormatter())

logger.addHandler(ch)

そして使用してください!

logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")

結果enter image description here

0