web-dev-qa-db-ja.com

Pythonのログ機能にカスタムログレベルを追加する方法

debug()で十分だとは思わないので、アプリケーションのログレベルTRACE(5)が必要です。さらに、log(5, msg)は私が望むものではありません。 Pythonロガーにカスタムログレベルを追加するにはどうすればよいですか?

私は_mylogger.py_を次の内容で持っています:

_import logging

@property
def log(obj):
    myLogger = logging.getLogger(obj.__class__.__name__)
    return myLogger
_

私のコードでは、次のように使用しています。

_class ExampleClass(object):
    from mylogger import log

    def __init__(self):
        '''The constructor with the logger'''
        self.log.debug("Init runs")
_

今、私はself.log.trace("foo bar")を呼び出したいです

よろしくお願いします。

Edit(2016年12月8日):受け入れられた答えを pfa's に変更しました。エリック・Sからの非常に良い提案.

98
tuergeist

@エリック・S.

Eric S.の答えは優れていますが、実験により、ログレベルの設定に関係なく、常に新しいデバッグレベルで記録されたメッセージが出力されることがわかりました。したがって、_9_の新しいレベル番号を作成した場合、setLevel(50)を呼び出すと、下位レベルメッセージが誤って出力されます。

これを防ぐには、「debugv」関数内に別の行を追加して、問題のログレベルが実際に有効になっているかどうかを確認する必要があります。

ログレベルが有効かどうかを確認する例を修正しました。

_import logging
DEBUG_LEVELV_NUM = 9 
logging.addLevelName(DEBUG_LEVELV_NUM, "DEBUGV")
def debugv(self, message, *args, **kws):
    if self.isEnabledFor(DEBUG_LEVELV_NUM):
        # Yes, logger takes its '*args' as 'args'.
        self._log(DEBUG_LEVELV_NUM, message, args, **kws) 
logging.Logger.debugv = debugv
_

_class Logger_の_logging.__init__.py_のコードをPython 2.7の場合)で見ると、これはすべての標準ログ関数が行うことです(.critical、.debugなど) 。

他の人の回答に対する評判の欠如に対する回答を投稿することはできないようです...エリックがこれを見たら投稿を更新することを願っています。 =)

144
pfa

「ラムダが表示されないようにする」という回答を受け取り、log_at_my_log_levelを追加する場所を変更する必要がありました。私も、ポールが「これが機能するとは思わない。log_at_my_log_levelの最初の引数としてロガーを必要としないのか」という問題を見ました。これは私のために働いた

import logging
DEBUG_LEVELV_NUM = 9 
logging.addLevelName(DEBUG_LEVELV_NUM, "DEBUGV")
def debugv(self, message, *args, **kws):
    # Yes, logger takes its '*args' as 'args'.
    self._log(DEBUG_LEVELV_NUM, message, args, **kws) 
logging.Logger.debugv = debugv
58
Eric S.

既存のすべての回答と一連の使用経験を組み合わせて、新しいレベルを完全にシームレスに使用するために必要なすべてのことのリストを思いついたと思います。以下の手順では、値_logging.DEBUG - 5 == 5_で新しいレベルTRACEを追加することを想定しています。

  1. logging.addLevelName(logging.DEBUG - 5, 'TRACE')は、名前で参照できるように内部で登録された新しいレベルを取得するために呼び出す必要があります。
  2. 一貫性を保つために、新しいレベルを属性としてlogging自体に追加する必要があります:_logging.TRACE = logging.DEBUG - 5_。
  3. traceというメソッドをloggingモジュールに追加する必要があります。 debuginfoなどのように動作する必要があります。
  4. traceというメソッドを、現在構成されているロガークラスに追加する必要があります。これは_logging.Logger_であることが100%保証されていないため、代わりにlogging.getLoggerClass()を使用してください。

すべての手順は、以下の方法で説明されています。

_def addLoggingLevel(levelName, levelNum, methodName=None):
    """
    Comprehensively adds a new logging level to the `logging` module and the
    currently configured logging class.

    `levelName` becomes an attribute of the `logging` module with the value
    `levelNum`. `methodName` becomes a convenience method for both `logging`
    itself and the class returned by `logging.getLoggerClass()` (usually just
    `logging.Logger`). If `methodName` is not specified, `levelName.lower()` is
    used.

    To avoid accidental clobberings of existing attributes, this method will
    raise an `AttributeError` if the level name is already an attribute of the
    `logging` module or if the method name is already present 

    Example
    -------
    >>> addLoggingLevel('TRACE', logging.DEBUG - 5)
    >>> logging.getLogger(__name__).setLevel("TRACE")
    >>> logging.getLogger(__name__).trace('that worked')
    >>> logging.trace('so did this')
    >>> logging.TRACE
    5

    """
    if not methodName:
        methodName = levelName.lower()

    if hasattr(logging, levelName):
       raise AttributeError('{} already defined in logging module'.format(levelName))
    if hasattr(logging, methodName):
       raise AttributeError('{} already defined in logging module'.format(methodName))
    if hasattr(logging.getLoggerClass(), methodName):
       raise AttributeError('{} already defined in logger class'.format(methodName))

    # This method was inspired by the answers to Stack Overflow post
    # http://stackoverflow.com/q/2183233/2988730, especially
    # http://stackoverflow.com/a/13638084/2988730
    def logForLevel(self, message, *args, **kwargs):
        if self.isEnabledFor(levelNum):
            self._log(levelNum, message, *args, **kwargs)
    def logToRoot(message, *args, **kwargs):
        logging.log(levelNum, message, *args, **kwargs)

    logging.addLevelName(levelNum, levelName)
    setattr(logging, levelName, levelNum)
    setattr(logging.getLoggerClass(), methodName, logForLevel)
    setattr(logging, methodName, logToRoot)
_
40
Mad Physicist

この質問はかなり古いですが、同じトピックを扱っただけで、すでに述べたものと似た方法を見つけました。これは3.4でテストされたため、使用されているメソッドが古いバージョンに存在するかどうかはわかりません。

from logging import getLoggerClass, addLevelName, setLoggerClass, NOTSET

VERBOSE = 5

class MyLogger(getLoggerClass()):
    def __init__(self, name, level=NOTSET):
        super().__init__(name, level)

        addLevelName(VERBOSE, "VERBOSE")

    def verbose(self, msg, *args, **kwargs):
        if self.isEnabledFor(VERBOSE):
            self._log(VERBOSE, msg, args, **kwargs)

setLoggerClass(MyLogger)
36
Wisperwind

誰が内部メソッド(self._log)を使用するという悪い習慣を始めたのか、そしてなぜそれぞれの答えはそれに基づいているのか?! Pythonicの解決策は、代わりにself.logを使用することで、内部のものをいじる必要はありません。

import logging

SUBDEBUG = 5
logging.addLevelName(SUBDEBUG, 'SUBDEBUG')

def subdebug(self, message, *args, **kws):
    self.log(SUBDEBUG, message, *args, **kws) 
logging.Logger.subdebug = subdebug

logging.basicConfig()
l = logging.getLogger()
l.setLevel(SUBDEBUG)
l.subdebug('test')
l.setLevel(logging.DEBUG)
l.subdebug('test')
19
schlamar

Log()関数を渡すロガーオブジェクトの新しい属性を作成する方が簡単です。この理由から、ロガーモジュールはaddLevelName()とlog()を提供すると思います。したがって、サブクラスや新しいメソッドは必要ありません。

import logging

@property
def log(obj):
    logging.addLevelName(5, 'TRACE')
    myLogger = logging.getLogger(obj.__class__.__name__)
    setattr(myLogger, 'trace', lambda *args: myLogger.log(5, *args))
    return myLogger

mylogger.trace('This is a trace message')

期待どおりに動作するはずです。

9
LtPinback

Loggerクラスをサブクラス化し、traceよりも低いレベルでLogger.logを基本的に呼び出すDEBUGというメソッドを追加する必要があると思います。私はこれを試していませんが、これは ドキュメントが示す です。

8
Noufal Ibrahim

カスタムロガーを作成するためのヒント:

  1. __log_を使用せず、logを使用します(isEnabledForを確認する必要はありません)
  2. ロギングモジュールは、getLoggerで何らかの魔法を行うため、カスタムロガーのインスタンスを作成するものでなければなりません。したがって、setLoggerClassを介してクラスを設定する必要があります。
  3. ロガー、クラスに何も格納しない場合は、___init___を定義する必要はありません。
_# Lower than debug which is 10
TRACE = 5
class MyLogger(logging.Logger):
    def trace(self, msg, *args, **kwargs):
        self.log(TRACE, msg, *args, **kwargs)
_

このロガーを呼び出すときは、setLoggerClass(MyLogger)を使用して、これをgetLoggerからのデフォルトのロガーにします

_logging.setLoggerClass(MyLogger)
log = logging.getLogger(__name__)
# ...
log.trace("something specific")
_

この低レベルを実際に確認するには、setFormatterおよびsetHandler自体でhandlerlog、およびsetLevel(TRACE)が必要です。トレース

5
Bryce Guinta

これは私のために働いた:

import logging
logging.basicConfig(
    format='  %(levelname)-8.8s %(funcName)s: %(message)s',
)
logging.NOTE = 32  # positive yet important
logging.addLevelName(logging.NOTE, 'NOTE')      # new level
logging.addLevelName(logging.CRITICAL, 'FATAL') # rename existing

log = logging.getLogger(__name__)
log.note = lambda msg, *args: log._log(logging.NOTE, msg, args)
log.note('school\'s out for summer! %s', 'dude')
log.fatal('file not found.')

Lambda/funcNameの問題は、@ marqueedが指摘したようにlogger._logで修正されています。ラムダの使用は少しきれいに見えると思いますが、欠点はキーワード引数を取ることができないことです。私はそれを自分で使ったことがないので、大したことはありません。

メモのセットアップ:学校は夏休みです! dude 
 FATAL setup:file not found。
3
Gringo Suave

私の経験では、これはopの問題に対する完全な解決策です...「ラムダ」をメッセージが送信される関数として見ることを避けるために、より深くなります:

MY_LEVEL_NUM = 25
logging.addLevelName(MY_LEVEL_NUM, "MY_LEVEL_NAME")
def log_at_my_log_level(self, message, *args, **kws):
    # Yes, logger takes its '*args' as 'args'.
    self._log(MY_LEVEL_NUM, message, args, **kws)
logger.log_at_my_log_level = log_at_my_log_level

スタンドアロンのロガークラスで作業したことはありませんが、基本的な考え方は同じだと思います(_logを使用)。

2
marqueed

ファイル名と行番号を正しく取得するためのMad Physicistsの例の追加:

def logToRoot(message, *args, **kwargs):
    if logging.root.isEnabledFor(levelNum):
        logging.root._log(levelNum, message, args, **kwargs)
2

私たちはすでに正しい答えをたくさん持っていますが、私の意見では次のほうがよりPython的です:

import logging

from functools import partial, partialmethod

logging.TRACE = 5
logging.addLevelName(logging.TRACE, 'TRACE')
logging.Logger.trace = partialmethod(logging.Logger.log, logging.TRACE)
logging.trace = partial(logging.log, logging.TRACE)

コードでmypyを使用する場合は、# type: ignoreを追加して、属性の追加による警告を抑制することをお勧めします。

2
DerWeh

固定された回答に基づいて、新しいログレベルを自動的に作成する小さなメソッドを作成しました

def set_custom_logging_levels(config={}):
    """
        Assign custom levels for logging
            config: is a dict, like
            {
                'EVENT_NAME': EVENT_LEVEL_NUM,
            }
        EVENT_LEVEL_NUM can't be like already has logging module
        logging.DEBUG       = 10
        logging.INFO        = 20
        logging.WARNING     = 30
        logging.ERROR       = 40
        logging.CRITICAL    = 50
    """
    assert isinstance(config, dict), "Configuration must be a dict"

    def get_level_func(level_name, level_num):
        def _blank(self, message, *args, **kws):
            if self.isEnabledFor(level_num):
                # Yes, logger takes its '*args' as 'args'.
                self._log(level_num, message, args, **kws) 
        _blank.__= level_name.lower()
        return _blank

    for level_name, level_num in config.items():
        logging.addLevelName(level_num, level_name.upper())
        setattr(logging.Logger, level_name.lower(), get_level_func(level_name, level_num))

configは次のようになります:

new_log_levels = {
    # level_num is in logging.INFO section, that's why it 21, 22, etc..
    "FOO":      21,
    "BAR":      22,
}
0
groshevpavel

Loggerクラスに追加のメソッドを追加する代わりに、Logger.log(level, msg)メソッドを使用することをお勧めします。

import logging

TRACE = 5
logging.addLevelName(TRACE, 'TRACE')
FORMAT = '%(levelname)s:%(name)s:%(lineno)d:%(message)s'


logging.basicConfig(format=FORMAT)
l = logging.getLogger()
l.setLevel(TRACE)
l.log(TRACE, 'trace message')
l.setLevel(logging.DEBUG)
l.log(TRACE, 'disabled trace message')
0
schlamar

よくわかりません; with python 3.5、少なくとも、それはちょうど動作します:

import logging


TRACE = 5
"""more detail than debug"""

logging.basicConfig()
logging.addLevelName(TRACE,"TRACE")
logger = logging.getLogger('')
logger.debug("n")
logger.setLevel(logging.DEBUG)
logger.debug("y1")
logger.log(TRACE,"n")
logger.setLevel(TRACE)
logger.log(TRACE,"y2")

出力:

DEBUG:root:y1

TRACE:root:y2

0
gerardw