كيفية إضافة Loglevel مخصص إلى منشأة تسجيل Python
-
24-09-2019 - |
سؤال
أرغب في الحصول على Trace (5) لتطبيقي ، حيث لا أعتقد ذلك debug()
كافي. بالإضافة إلى ذلك log(5, msg)
ليس ما أريد. كيف يمكنني إضافة Loglevel مخصص إلى مسجل 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")
شكرا مقدما لمساعدتكم.
تعديل (8 ديسمبر 2016): لقد غيرت الإجابة المقبولة على PFA وهو ، IMHO ، وهو حل ممتاز يعتمد على الاقتراح الجيد جدًا من إريك س.
المحلول
@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 ، هذا ما تفعله جميع وظائف السجل القياسية (.
يبدو أنني لا أستطيع نشر ردود على إجابات الآخرين لعدم وجود سمعة ... آمل أن يقوم إريك بتحديث منشوره إذا رأى ذلك. =)
نصائح أخرى
أخذت إجابة "تجنب رؤية Lambda" واضطررت إلى تعديل المكان الذي تم فيه إضافة log_at_my_log_level. لقد رأيت أيضًا المشكلة التي فعلها بولس "لا أعتقد أن هذا يعمل. ألا تحتاج إلى سجل كأول ARG في 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
مع الجمع بين جميع الإجابات الحالية مع مجموعة من تجربة الاستخدام ، أعتقد أنني توصلت إلى قائمة بجميع الأشياء التي يجب القيام بها لضمان الاستخدام السلس تمامًا للمستوى الجديد. تفترض الخطوات أدناه أنك تضيف مستوى جديد TRACE
مع القيمة logging.DEBUG - 5 == 5
:
logging.addLevelName(logging.DEBUG - 5, 'TRACE')
يحتاج إلى التذرع للحصول على المستوى الجديد المسجل داخليًا حتى يمكن الرجوع إليه بالاسم.- يحتاج المستوى الجديد إلى إضافة سمة إلى
logging
نفسها من أجل الاتساق:logging.TRACE = logging.DEBUG - 5
. - طريقة تسمى
trace
يجب إضافتها إلىlogging
وحدة. يجب أن يتصرف مثلdebug
,info
, ، إلخ. - طريقة تسمى
trace
يجب إضافتها إلى فئة المسجل التي تم تكوينها حاليًا. بما أن هذا ليس مضمونًا بنسبة 100 ٪logging.Logger
, ، استعمال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
) ولماذا تعتمد كل إجابة على ذلك؟! سيكون الحل البيثوني للاستخدام 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
, ، و setLevel(TRACE)
على ال handler
وعلى log
نفسها في الواقع SE هذا التتبع منخفض المستوى
هذا عمل بالنسبة لي:
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 باستخدام logger._log كما أشار Marqueed. أعتقد أن استخدام Lambda يبدو أكثر نظافة ، لكن العيب هو أنه لا يمكن أن يأخذ وسيطات الكلمات الرئيسية. لم أستخدم ذلك بنفسي أبدًا ، لذلك لا يوجد biggie.
NOTE setup: school's out for summer! dude FATAL setup: file not found.
في تجربتي ، هذا هو الحل الكامل لمشكلة OP ... لتجنب رؤية "Lambda" كوظيفة تنبع منها الرسالة ، تعمق:
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).
بالإضافة إلى مثال الفيزيائيين المجنون للحصول على اسم الملف ورقم السطر صحيح:
def logToRoot(message, *args, **kwargs):
if logging.root.isEnabledFor(levelNum):
logging.root._log(levelNum, message, args, **kwargs)
على الرغم من أن لدينا بالفعل الكثير من الإجابات الصحيحة ، فإن ما يلي في رأيي أكثر بيثونيا:
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
لقمع التحذيرات من إضافة السمة.
كبديل لإضافة طريقة إضافية إلى فئة المسجلات ، أوصي باستخدام ملف 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')
انا مرتبك؛ مع 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")
انتاج:
تصحيح: الجذر: Y1
تتبع: الجذر: Y2
استنادًا إلى الإجابة المثبتة ، كتبت طريقة صغيرة تقوم بها شركة Officatialy على إنشاء مستويات تسجيل جديدة
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.__name__ = 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))
قد يتكوين مثل هذا:
new_log_levels = {
# level_num is in logging.INFO section, that's why it 21, 22, etc..
"FOO": 21,
"BAR": 22,
}
في حالة رغبة أي شخص في طريقة تلقائية لإضافة مستوى تسجيل جديد إلى وحدة التسجيل (أو نسخة منه) بشكل ديناميكي ، قمت بإنشاء هذه الوظيفة ، وتوسيع إجابة @PFA:
def add_level(log_name,custom_log_module=None,log_num=None,
log_call=None,
lower_than=None, higher_than=None, same_as=None,
verbose=True):
'''
Function to dynamically add a new log level to a given custom logging module.
<custom_log_module>: the logging module. If not provided, then a copy of
<logging> module is used
<log_name>: the logging level name
<log_num>: the logging level num. If not provided, then function checks
<lower_than>,<higher_than> and <same_as>, at the order mentioned.
One of those three parameters must hold a string of an already existent
logging level name.
In case a level is overwritten and <verbose> is True, then a message in WARNING
level of the custom logging module is established.
'''
if custom_log_module is None:
import imp
custom_log_module = imp.load_module('custom_log_module',
*imp.find_module('logging'))
log_name = log_name.upper()
def cust_log(par, message, *args, **kws):
# Yes, logger takes its '*args' as 'args'.
if par.isEnabledFor(log_num):
par._log(log_num, message, args, **kws)
available_level_nums = [key for key in custom_log_module._levelNames
if isinstance(key,int)]
available_levels = {key:custom_log_module._levelNames[key]
for key in custom_log_module._levelNames
if isinstance(key,str)}
if log_num is None:
try:
if lower_than is not None:
log_num = available_levels[lower_than]-1
elif higher_than is not None:
log_num = available_levels[higher_than]+1
elif same_as is not None:
log_num = available_levels[higher_than]
else:
raise Exception('Infomation about the '+
'log_num should be provided')
except KeyError:
raise Exception('Non existent logging level name')
if log_num in available_level_nums and verbose:
custom_log_module.warn('Changing ' +
custom_log_module._levelNames[log_num] +
' to '+log_name)
custom_log_module.addLevelName(log_num, log_name)
if log_call is None:
log_call = log_name.lower()
setattr(custom_log_module.Logger, log_call, cust_log)
return custom_log_module