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からの非常に良い提案.
@エリック・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など) 。
他の人の回答に対する評判の欠如に対する回答を投稿することはできないようです...エリックがこれを見たら投稿を更新することを願っています。 =)
「ラムダが表示されないようにする」という回答を受け取り、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
既存のすべての回答と一連の使用経験を組み合わせて、新しいレベルを完全にシームレスに使用するために必要なすべてのことのリストを思いついたと思います。以下の手順では、値_logging.DEBUG - 5 == 5
_で新しいレベルTRACE
を追加することを想定しています。
logging.addLevelName(logging.DEBUG - 5, 'TRACE')
は、名前で参照できるように内部で登録された新しいレベルを取得するために呼び出す必要があります。logging
自体に追加する必要があります:_logging.TRACE = logging.DEBUG - 5
_。trace
というメソッドをlogging
モジュールに追加する必要があります。 debug
、info
などのように動作する必要があります。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)
_
この質問はかなり古いですが、同じトピックを扱っただけで、すでに述べたものと似た方法を見つけました。これは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)
誰が内部メソッド(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')
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')
期待どおりに動作するはずです。
Logger
クラスをサブクラス化し、trace
よりも低いレベルでLogger.log
を基本的に呼び出すDEBUG
というメソッドを追加する必要があると思います。私はこれを試していませんが、これは ドキュメントが示す です。
カスタムロガーを作成するためのヒント:
_log
_を使用せず、log
を使用します(isEnabledFor
を確認する必要はありません)getLogger
で何らかの魔法を行うため、カスタムロガーのインスタンスを作成するものでなければなりません。したがって、setLoggerClass
を介してクラスを設定する必要があります。__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
自体でhandler
、log
、およびsetLevel(TRACE)
が必要です。トレース
これは私のために働いた:
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。
私の経験では、これは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を使用)。
ファイル名と行番号を正しく取得するためのMad Physicistsの例の追加:
def logToRoot(message, *args, **kwargs):
if logging.root.isEnabledFor(levelNum):
logging.root._log(levelNum, message, args, **kwargs)
私たちはすでに正しい答えをたくさん持っていますが、私の意見では次のほうがより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
を追加して、属性の追加による警告を抑制することをお勧めします。
固定された回答に基づいて、新しいログレベルを自動的に作成する小さなメソッドを作成しました
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,
}
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')
よくわかりません; 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