سؤال

أرغب في الحصول على 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:

  1. logging.addLevelName(logging.DEBUG - 5, 'TRACE') يحتاج إلى التذرع للحصول على المستوى الجديد المسجل داخليًا حتى يمكن الرجوع إليه بالاسم.
  2. يحتاج المستوى الجديد إلى إضافة سمة إلى logging نفسها من أجل الاتساق: logging.TRACE = logging.DEBUG - 5.
  3. طريقة تسمى trace يجب إضافتها إلى logging وحدة. يجب أن يتصرف مثل debug, info, ، إلخ.
  4. طريقة تسمى 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. لم أحاول هذا ولكن هذا ما تشير المستندات.

نصائح لإنشاء مسجل مخصص:

  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, ، و 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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top