Django项目日志概述
发布日期:2021-07-25 13:04:49 浏览次数:18 分类:技术文章

本文共 36999 字,大约阅读时间需要 123 分钟。

Django项目日志概述

本文环境python3.5.2,Django版本1.10.2

Django项目中日志的实现

Django项目中使用的日志,使用了Python标准库中的logging模块进行实现的,先看下实例代码:

在settings.py文件中添加如下配置LOGGING = {    'version': 1,    'disable_existing_loggers': False,    'formatters': {        'standard': {            'format': '%(levelname)s [%(asctime)s] [%(name)s:%(module)s:%(funcName)s:%(lineno)s] [%(exc_info)s] %(message)s',            'datefmt': "%d/%b/%Y %H:%M:%S"        },    },    'filters': {        'require_debug_false': {            '()': 'django.utils.log.RequireDebugFalse',        },        'require_debug_true': {            '()': 'django.utils.log.RequireDebugTrue',        },    },    'handlers': {        'console': {            'level': 'DEBUG',            'filters': ['require_debug_true'],            'class': 'logging.StreamHandler',            'formatter': 'standard'        },        'file': {            'level': 'INFO',            'class': 'logging.FileHandler',  # 'logging.handlers.TimedRotatingFileHandler',            #'filters': ['require_debug_false'],            'filename': os.path.join(BASE_DIR + '/logs/', 'access.log'),            'formatter': 'standard',            # 'when': 'midnight',            # 'backupCount':30        },        'sentry': {            'level': 'ERROR',            'filters': ['require_debug_false'],            'class': 'raven.contrib.django.raven_compat.handlers.SentryHandler'        },    },    'loggers': {        'django': {            'handlers': ['console', 'file'],            'level': 'INFO',            'propagate': False,        },        'django.request': {            'handlers': ['file', 'sentry'],            'level': 'INFO',            'propagate': False,        },        'apis': {            'handlers': ['console', 'file', 'sentry'],            'level': 'INFO',            'propagate': False,        },        'django.db': {            'handlers': ['console'],            'level': 'DEBUG',            'propagate': False,        },        'user': {            'handlers': ['console', 'file', 'sentry'],            'level': 'INFO',            'propagate': False,        },    }}在业务代码中使用方式如下:import logginglogger = logging.getLogger('user') logger.error("level error test")logger.info("level info test")

由于Django中的日志最终依赖于logging提供的功能来实现日志收集,接下来我们继续分析如果进行配置与初始化。

Django项目日志配置的初始化过程

在前文的博文中已经介绍过,Django项目启动的时候,执行如下函数:

def setup(set_prefix=True):    """    Configure the settings (this happens as a side effect of accessing the    first setting), configure logging and populate the app registry.    Set the thread-local urlresolvers script prefix if `set_prefix` is True.    """    from django.apps import apps    from django.conf import settings    from django.urls import set_script_prefix    from django.utils.encoding import force_text    from django.utils.log import configure_logging    configure_logging(settings.LOGGING_CONFIG, settings.LOGGING)            # 初始化Django中配置的日志信息    if set_prefix:        set_script_prefix(            '/' if settings.FORCE_SCRIPT_NAME is None else force_text(settings.FORCE_SCRIPT_NAME)        )    apps.populate(settings.INSTALLED_APPS)

Django项目中的初始化过程就是从configure_logging函数开始进行初始化的,其中settings.LOGGING_CONFIG对应的就是’logging.config.dictConfig’,LOGGING就是在setting.py文件中配置的信息,

DEFAULT_LOGGING = {    'version': 1,    'disable_existing_loggers': False,    'filters': {        'require_debug_false': {            '()': 'django.utils.log.RequireDebugFalse',        },        'require_debug_true': {            '()': 'django.utils.log.RequireDebugTrue',        },    },    'formatters': {        'django.server': {            '()': 'django.utils.log.ServerFormatter',            'format': '[%(server_time)s] %(message)s',        }    },    'handlers': {        'console': {            'level': 'INFO',            'filters': ['require_debug_true'],            'class': 'logging.StreamHandler',        },        'django.server': {            'level': 'INFO',            'class': 'logging.StreamHandler',            'formatter': 'django.server',        },        'mail_admins': {            'level': 'ERROR',            'filters': ['require_debug_false'],            'class': 'django.utils.log.AdminEmailHandler'        }    },    'loggers': {        'django': {            'handlers': ['console', 'mail_admins'],            'level': 'INFO',        },        'django.server': {            'handlers': ['django.server'],            'level': 'INFO',            'propagate': False,        },    }}def configure_logging(logging_config, logging_settings):    if logging_config:                                          # 配置文件中是否设置了LOGGING_CONFIG配置        # First find the logging configuration function ...        logging_config_func = import_string(logging_config)     # 导入logging_config类        logging.config.dictConfig(DEFAULT_LOGGING)              # 加载默认配置的日志配置信息        # ... then invoke it with the logging settings        if logging_settings:                                    # 判断是否在setting.py中配置了LOGGING            logging_config_func(logging_settings)               # 初始化日志配置

其中logging_config其实就是logging.config.dictConfig函数,在导入后,先将系统默认的日志配置DEFAULT_LOGGING导入其中,然后再初始化,

dictConfigClass = DictConfiguratordef dictConfig(config):    """Configure logging using a dictionary."""    dictConfigClass(config).configure()         # 初始化DictConfigurator类,然后调用实例的configure方法

此时就是初始化了DictConfigurator类,继承自BaseConfigurator类,然后调用该实例的configure方法,其中DictConfigurator类初始化的时候通过初始化一个扩展的方法去初始化配置的参数,该类的configure函数分析如下;

class BaseConfigurator(object):    """    The configurator base class which defines some useful defaults.    """    CONVERT_PATTERN = re.compile(r'^(?P
[a-z]+)://(?P
.*)$') WORD_PATTERN = re.compile(r'^\s*(\w+)\s*') DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*') INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*') DIGIT_PATTERN = re.compile(r'^\d+$') value_converters = { 'ext' : 'ext_convert', 'cfg' : 'cfg_convert', } # We might want to use a different one, e.g. importlib importer = staticmethod(__import__) def __init__(self, config): self.config = ConvertingDict(config) # 扩展了内建方法dict 可以通过转换获取对应的dict self.config.configurator = self # 设置configurator属性 ...Class DictConfigurator(BaseConfigurator): def configure(self): """Do the configuration.""" config = self.config if 'version' not in config: # 检查version key是否在配置中 raise ValueError("dictionary doesn't specify a version") if config['version'] != 1: # 检查版本号是否为1 raise ValueError("Unsupported version: %s" % config['version']) incremental = config.pop('incremental', False) # 从配置中获取incremental参数值 是否增量 EMPTY_DICT = {} logging._acquireLock() # 获取线程锁 try: if incremental: # 如果是增量的则保存以前已经存在的handlers和filters handlers = config.get('handlers', EMPTY_DICT) # 获取配置文件中的handlers for name in handlers: # 遍历 if name not in logging._handlers: # 如果不在handlers则报错 raise ValueError('No handler found with ' 'name %r' % name) else: try: # 获取已经存在的handler handler = logging._handlers[name] handler_config = handlers[name] # 获取对应handler的配置信息 level = handler_config.get('level', None) # 获取日志等级 if level: handler.setLevel(logging._checkLevel(level)) # 如果设置了等级则重新设置日志等级 except Exception as e: raise ValueError('Unable to configure handler ' '%r: %s' % (name, e)) loggers = config.get('loggers', EMPTY_DICT) # 获取loggers for name in loggers: # 遍历loggers try: self.configure_logger(name, loggers[name], True) # 依次调用configure_logger方法 except Exception as e: raise ValueError('Unable to configure logger ' '%r: %s' % (name, e)) root = config.get('root', None) # 获取root if root: try: self.configure_root(root, True) # 设置root except Exception as e: raise ValueError('Unable to configure root ' 'logger: %s' % e) else: disable_existing = config.pop('disable_existing_loggers', True) # 是否禁止已经启动的默认的日志loggers logging._handlers.clear() # 清除已经存在的handlers del logging._handlerList[:] # Do formatters first - they don't refer to anything else formatters = config.get('formatters', EMPTY_DICT) # 获取formatters 配置信息 for name in formatters: # 遍历formatters try: formatters[name] = self.configure_formatter( formatters[name]) # 配置formatter并返回配置的实例 except Exception as e: raise ValueError('Unable to configure ' 'formatter %r: %s' % (name, e)) # Next, do filters - they don't refer to anything else, either filters = config.get('filters', EMPTY_DICT) # 获取filters for name in filters: # 遍历filters try: filters[name] = self.configure_filter(filters[name]) # 配置filter except Exception as e: raise ValueError('Unable to configure ' 'filter %r: %s' % (name, e)) # Next, do handlers - they refer to formatters and filters # As handlers can refer to other handlers, sort the keys # to allow a deterministic order of configuration handlers = config.get('handlers', EMPTY_DICT) # 获取handlers deferred = [] for name in sorted(handlers): # 遍历排序后的handlers try: handler = self.configure_handler(handlers[name]) # 依次生成handler实例并返回 handler.name = name # 设置handler的名称 handlers[name] = handler # 保存到handlers中 except Exception as e: if 'target not configured yet' in str(e): deferred.append(name) else: raise ValueError('Unable to configure handler ' '%r: %s' % (name, e)) # Now do any that were deferred for name in deferred: # 保存到处问题的 handler try: handler = self.configure_handler(handlers[name]) handler.name = name handlers[name] = handler except Exception as e: raise ValueError('Unable to configure handler ' '%r: %s' % (name, e)) # Next, do loggers - they refer to handlers and filters #we don't want to lose the existing loggers, #since other threads may have pointers to them. #existing is set to contain all existing loggers, #and as we go through the new configuration we #remove any which are configured. At the end, #what's left in existing is the set of loggers #which were in the previous configuration but #which are not in the new configuration. root = logging.root existing = list(root.manager.loggerDict.keys()) #The list needs to be sorted so that we can #avoid disabling child loggers of explicitly #named loggers. With a sorted list it is easier #to find the child loggers. existing.sort() #We'll keep the list of existing loggers #which are children of named loggers here... child_loggers = [] #now set up the new ones... loggers = config.get('loggers', EMPTY_DICT) # 获取loggers for name in loggers: # 遍历所有的loggers if name in existing: # 检查是否在已存在的列表中 i = existing.index(name) + 1 # look after name # 获取下标 prefixed = name + "." # 获取前缀名称 pflen = len(prefixed) # 获取长度 num_existing = len(existing) # 获取已存在列表的长度 while i < num_existing: # 如果小于已存在的列表长度 if existing[i][:pflen] == prefixed: # 检查是否有对应的前缀 child_loggers.append(existing[i]) i += 1 existing.remove(name) try: self.configure_logger(name, loggers[name]) except Exception as e: raise ValueError('Unable to configure logger ' '%r: %s' % (name, e)) #Disable any old loggers. There's no point deleting #them as other threads may continue to hold references #and by disabling them, you stop them doing any logging. #However, don't disable children of named loggers, as that's #probably not what was intended by the user. #for log in existing: # logger = root.manager.loggerDict[log] # if log in child_loggers: # logger.level = logging.NOTSET # logger.handlers = [] # logger.propagate = True # elif disable_existing: # logger.disabled = True _handle_existing_loggers(existing, child_loggers, disable_existing) # 检查已存在的loggers,使存在的子logger是否设置为不能使用 # And finally, do the root logger root = config.get('root', None) # 如果配置了root if root: try: self.configure_root(root) # 设置配置的root except Exception as e: raise ValueError('Unable to configure root ' 'logger: %s' % e) finally: logging._releaseLock() # 释放线程锁

在该函数中,有三个比较主要的方法就是configure_formatter、configure_filter和configure_handler三个方法,三个方法的执行过程如下;

def configure_formatter(self, config):    """Configure a formatter from a dictionary."""    if '()' in config:                                                      # 检查 ()是否在key中        factory = config['()'] # for use in exception handler        try:            result = self.configure_custom(config)                          # 导入获取的formatter类并返回实例        except TypeError as te:            if "'format'" not in str(te):                raise            #Name of parameter changed from fmt to format.            #Retry with old name.            #This is so that code can be used with older Python versions            #(e.g. by Django)            config['fmt'] = config.pop('format')                            # 如果导入出错则使用默认的factory生成实例            config['()'] = factory            result = self.configure_custom(config)                          # 导入并初始化实例返回    else:        fmt = config.get('format', None)                                    # 获取文本format配置        dfmt = config.get('datefmt', None)                                  # 获取时间format格式        style = config.get('style', '%')                                    # 获取样式        cname = config.get('class', None)                                   # 获取导入类        if not cname:                                                       # 如果配置文件中没有传入则使用默认的            c = logging.Formatter        else:            c = _resolve(cname)                                             # 导入该类        result = c(fmt, dfmt, style)                                        # 初始化并返回实例    return resultdef configure_filter(self, config):    """Configure a filter from a dictionary."""    if '()' in config:                                                      # 检查配置文件中是否有 () 配置        result = self.configure_custom(config)                              # 导入配置文件中的类并初始化实例返回    else:        name = config.get('name', '')                                       # 获取配置文件中的name        result = logging.Filter(name)                                       # 初始化并返回结果    return resultdef configure_handler(self, config):    """Configure a handler from a dictionary."""    config_copy = dict(config)  # for restoring in case of error    formatter = config.pop('formatter', None)                               # 获取初始化的格式    if formatter:                                                           # 如果不为空        try:            formatter = self.config['formatters'][formatter]                # 获取formatter实例        except Exception as e:            raise ValueError('Unable to set formatter '                             '%r: %s' % (formatter, e))    level = config.pop('level', None)                                       # 获取配置的日志等级    filters = config.pop('filters', None)                                   # 获取配置的filters    if '()' in config:                                                      # 检查是否配置了 ()        c = config.pop('()')                                                # 获取 ()         if not callable(c):                                                 # 检查是否是可调用的            c = self.resolve(c)                                             # 导入该类        factory = c                                                         # 赋值    else:         cname = config.pop('class')                                         # 获取配置文件中的 class        klass = self.resolve(cname)                                         # 导入该类        #Special case for handler which refers to another handler        if issubclass(klass, logging.handlers.MemoryHandler) and\             'target' in config:                                             # 检查该类是否是MemoryHandler的子类并且配置文件中有target            try:                th = self.config['handlers'][config['target']]              # 获取target                if not isinstance(th, logging.Handler):                     # 检查target是否是Handler实例                    config.update(config_copy)  # restore for deferred cfg                    raise TypeError('target not configured yet')                config['target'] = th                                                   except Exception as e:                raise ValueError('Unable to set target handler '                                 '%r: %s' % (config['target'], e))        elif issubclass(klass, logging.handlers.SMTPHandler) and\            'mailhost' in config:                                           # 检查是否是邮件类            config['mailhost'] = self.as_tuple(config['mailhost'])        elif issubclass(klass, logging.handlers.SysLogHandler) and\            'address' in config:                                            # 检查是否是SysLogHandler处理实例            config['address'] = self.as_tuple(config['address'])        factory = klass                                                     # 设置factory类值    props = config.pop('.', None)                                           # 是否配置了属性    kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])    try:        result = factory(**kwargs)                                          # 传入初始化的参数值    except TypeError as te:        if "'stream'" not in str(te):            raise        #The argument name changed from strm to stream        #Retry with old name.        #This is so that code can be used with older Python versions        #(e.g. by Django)        kwargs['strm'] = kwargs.pop('stream')        result = factory(**kwargs)                                          # 如果报错则使用strm 作为传入值    if formatter:        result.setFormatter(formatter)                                      # 设置序列化实例    if level is not None:        result.setLevel(logging._checkLevel(level))                         # 设置日志等级    if filters:        self.add_filters(result, filters)                                   # 添加过滤    if props:        for name, value in props.items():            setattr(result, name, value)                                    # 设置返回值    return result                                                           # 返回初始化实例def configure_custom(self, config):    """Configure an object with a user-supplied factory."""    c = config.pop('()')                                                    # 导出 ()    if not callable(c):                                                     # 如果配置的是不可调用的        c = self.resolve(c)                                                 # 导入该类    props = config.pop('.', None)                                           # 是否有 . 配置    # Check for valid identifiers    kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])       # 解析初始化参数    result = c(**kwargs)                                                    # 初始化实例    if props:        for name, value in props.items():                                   # 如果有则设置到实例属性上            setattr(result, name, value)    return result                                                           # 返回实例

其中相对重要的是logger的初始化,configure_logger的函数的执行过程如下;

def configure_logger(self, name, config, incremental=False):    """Configure a non-root logger from a dictionary."""    logger = logging.getLogger(name)                                        # 获取标准库logging中的日志logger    self.common_logger_config(logger, config, incremental)                  # 配置该logger    propagate = config.get('propagate', None)                               # 获取proagate并配置该参数值    if propagate is not None:        logger.propagate = propagatedef common_logger_config(self, logger, config, incremental=False):    """    Perform configuration which is common to root and non-root loggers.    """    level = config.get('level', None)                                       # 获取日志等级    if level is not None:        logger.setLevel(logging._checkLevel(level))                         # 先检查日志等级是否合理然后设计日志等级    if not incremental:                                                     # 是否保留先前的处理handler        #Remove any existing handlers        for h in logger.handlers[:]:                                        # 遍历删除该handler            logger.removeHandler(h)        handlers = config.get('handlers', None)                             # 获取配置中的handlers             if handlers:            self.add_handlers(logger, handlers)                             # 有值就添加到该logger种        filters = config.get('filters', None)                               # 获取filters        if filters:            self.add_filters(logger, filters)                               # 添加到logger filter#  位于logging.__init__中 获取def getLogger(name=None):    """    Return a logger with the specified name, creating it if necessary.    If no name is specified, return the root logger.    """    if name:        return Logger.manager.getLogger(name)                               # 获取对应name的Logger,通过manager实例来获取    else:         return root                                                         # 如果name为空则返回rootdef getLogger(self, name):    """    Get a logger with the specified name (channel name), creating it    if it doesn't yet exist. This name is a dot-separated hierarchical    name, such as "a", "a.b", "a.b.c" or similar.    If a PlaceHolder existed for the specified name [i.e. the logger    didn't exist but a child of it did], replace it with the created    logger and fix up the parent/child references which pointed to the    placeholder to now point to the logger.    """    rv = None    if not isinstance(name, str):                                           # 判断是否为字符串        raise TypeError('A logger name must be a string')    _acquireLock()                                                          # 获取线程锁    try:        if name in self.loggerDict:                                         # 如果该name对应的logger存在            rv = self.loggerDict[name]                                      # 获取该实例            if isinstance(rv, PlaceHolder):                                 # 判断是否是实例 PlaceHolder                ph = rv                rv = (self.loggerClass or _loggerClass)(name)                rv.manager = self                self.loggerDict[name] = rv                self._fixupChildren(ph, rv)                self._fixupParents(rv)        else:            rv = (self.loggerClass or _loggerClass)(name)                   # 否则就初始化一个该name的实例,默认为Logger类            rv.manager = self                                               # 设置该实例的manager            self.loggerDict[name] = rv                                      # 保存到logger的全局字典中            self._fixupParents(rv)                                          # 设置父类    finally:        _releaseLock()                                                      # 释放该锁    return rv                                                               # 返回该实例

至此,日志在Django项目中的初始化过程中的主要步骤就执行完成,接下来就分析该日志是如何在Django项目中使用的,在本例示例中使用的是如下;

import logginglogger = logging.getLogger('user') logger.error("level error test")logger.info("level info test")

此时,先调用了logging的getLogger方法,此时获取的’user’就是返回的是logger实例,logger实例如下;

class Logger(Filterer):    """    Instances of the Logger class represent a single logging channel. A    "logging channel" indicates an area of an application. Exactly how an    "area" is defined is up to the application developer. Since an    application can have any number of areas, logging channels are identified    by a unique string. Application areas can be nested (e.g. an area    of "input processing" might include sub-areas "read CSV files", "read    XLS files" and "read Gnumeric files"). To cater for this natural nesting,    channel names are organized into a namespace hierarchy where levels are    separated by periods, much like the Java or Python package namespace. So    in the instance given above, channel names might be "input" for the upper    level, and "input.csv", "input.xls" and "input.gnu" for the sub-levels.    There is no arbitrary limit to the depth of nesting.    """    def __init__(self, name, level=NOTSET):        """        Initialize the logger with a name and an optional level.        """        Filterer.__init__(self)        self.name = name        self.level = _checkLevel(level)        self.parent = None        self.propagate = True        self.handlers = []        self.disabled = False    ...

可以看出Logger继承自Filterer,该类主要就是调用过滤条件,即配置文件中的filters,(“level info test”)时,就是调用了Logger的info方法,

def info(self, msg, *args, **kwargs):        """        Log 'msg % args' with severity 'INFO'.        To pass exception information, use the keyword argument exc_info with        a true value, e.g.        logger.info("Houston, we have a %s", "interesting problem", exc_info=1)        """        if self.isEnabledFor(INFO):                                                 # 检查是否能够info输出            self._log(INFO, msg, args, **kwargs)                                    # 调用打印方法

此时会首先检查是否能够打印info等级的日志,

def isEnabledFor(self, level):    """    Is this logger enabled for level 'level'?    """    if self.manager.disable >= level:                                           # 检查manager配置的等级是否大于当前的等级,如果大于等于则返回false        return False    return level >= self.getEffectiveLevel()                                    # 判断当前的等级是否大于有效日志等级def getEffectiveLevel(self):    """    Get the effective level for this logger.    Loop through this logger and its parents in the logger hierarchy,    looking for a non-zero logging level. Return the first one found.    """    logger = self    while logger:                                                               # 循环遍历        if logger.level:                                                        # 如果当前logger设置了日志等级则直接返回            return logger.level        logger = logger.parent                                                  # 寻找parent的日志等级    return NOTSET                                                               # 如果都没哟适则返回NOTSET

如果当前日志等级能够打印则继续调用self._log函数,该函数所有调用的大致流程如下;

def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):    """    Low-level logging routine which creates a LogRecord and then calls    all the handlers of this logger to handle the record.    """    sinfo = None    if _srcfile:                                                            #IronPython doesn't track Python frames, so findCaller raises an        #exception on some versions of IronPython. We trap it here so that        #IronPython can use logging.        try:            fn, lno, func, sinfo = self.findCaller(stack_info)                  # 获取当前栈信息        except ValueError: # pragma: no cover            fn, lno, func = "(unknown file)", 0, "(unknown function)"    else: # pragma: no cover        fn, lno, func = "(unknown file)", 0, "(unknown function)"    if exc_info:                                                                # 如果传入exec_info有值        if isinstance(exc_info, BaseException):                                 # 判断是否是BaseException类型            exc_info = (type(exc_info), exc_info, exc_info.__traceback__)       # 获取相关栈信息        elif not isinstance(exc_info, tuple):                                   # 如果不是元组            exc_info = sys.exc_info()                                           # 获取系统相关信息    record = self.makeRecord(self.name, level, fn, lno, msg, args,                             exc_info, func, extra, sinfo)                      # 包装信息    self.handle(record)                                                         # 过滤并打印def makeRecord(self, name, level, fn, lno, msg, args, exc_info,               func=None, extra=None, sinfo=None):    """    A factory method which can be overridden in subclasses to create    specialized LogRecords.    """    rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func,                         sinfo)                                                 # 使用默认的LogRecord类来包装信息    if extra is not None:                                                       # 如果不为空        for key in extra:                                                       # 遍历并添加到rv实例中            if (key in ["message", "asctime"]) or (key in rv.__dict__):                raise KeyError("Attempt to overwrite %r in LogRecord" % key)            rv.__dict__[key] = extra[key]    return rvdef handle(self, record):    """    Call the handlers for the specified record.    This method is used for unpickled records received from a socket, as    well as those created locally. Logger-level filtering is applied.    """    if (not self.disabled) and self.filter(record):                             # 检查是否可用并过滤该record        self.callHandlers(record)                                               # 检查通过后则调用callHandlersdef callHandlers(self, record):    """    Pass a record to all relevant handlers.    Loop through all handlers for this logger and its parents in the    logger hierarchy. If no handler was found, output a one-off error    message to sys.stderr. Stop searching up the hierarchy whenever a    logger with the "propagate" attribute set to zero is found - that    will be the last logger whose handlers are called.    """    c = self    found = 0    while c:        for hdlr in c.handlers:                                                 # 遍历所有的handlers            found = found + 1                                                   # 计数加1            if record.levelno >= hdlr.level:                                    # 检查record的日志等级和当前handler的日志等级 如果大于等于则调用handler处理                hdlr.handle(record)                                             # 处理当前日志        if not c.propagate:                                                     # 如果不需要往上查找            c = None    #break out                                              # 则停止循环        else:            c = c.parent                                                        # 否则查找父类并继续查找    if (found == 0):        if lastResort:            if record.levelno >= lastResort.level:                lastResort.handle(record)        elif raiseExceptions and not self.manager.emittedNoHandlerWarning:            sys.stderr.write("No handlers could be found for logger"                             " \"%s\"\n" % self.name)            self.manager.emittedNoHandlerWarning = True

在本例中以logging.StreamHandler为例进行hdr.handle进行说明;

class StreamHandler(Handler):    """    A handler class which writes logging records, appropriately formatted,    to a stream. Note that this class does not close the stream, as    sys.stdout or sys.stderr may be used.    """    terminator = '\n'    def __init__(self, stream=None):        """        Initialize the handler.        If stream is not specified, sys.stderr is used.        """        Handler.__init__(self)        if stream is None:            stream = sys.stderr                                         # 默认使用控制台终端输出        self.stream = stream    def flush(self):        """        Flushes the stream.        """        self.acquire()                                                  # 获取线程锁        try:            if self.stream and hasattr(self.stream, "flush"):           # 判断stream是否有flush方法                self.stream.flush()                                     # 有就调用        finally:            self.release()                                              # 释放线程锁    def emit(self, record):        """        Emit a record.        If a formatter is specified, it is used to format the record.        The record is then written to the stream with a trailing newline.  If        exception information is present, it is formatted using        traceback.print_exception and appended to the stream.  If the stream        has an 'encoding' attribute, it is used to determine how to do the        output to the stream.        """        try:            msg = self.format(record)                                   # 先调用formatter实例初始化该信息            stream = self.stream            stream.write(msg)                                           # 写入该msg            stream.write(self.terminator)                               # 写入换行符            self.flush()                                                # 刷新缓冲区        except Exception:            self.handleError(record)

由于继承了handle类,此时继续查看该类的大致执行流程;

class Handler(Filterer):    def handle(self, record):        """        Conditionally emit the specified logging record.        Emission depends on filters which may have been added to the handler.        Wrap the actual emission of the record with acquisition/release of        the I/O thread lock. Returns whether the filter passed the record for        emission.        """        rv = self.filter(record)                                        # 过滤 查看是否可以打印该信息        if rv:                                                          # 如果可以打印输出            self.acquire()                                              # 获取线程锁            try:                self.emit(record)                                       # 打印输出            finally:                self.release()                                          # 释放线程锁        return rv

至此,Django项目中通过标准库logging打印输出日志的流程基本上分析完成,其中还有其他的一些第三方扩展的如sentry和分析过程中的更细致的流程信息,大家如有兴趣可自行阅读。

总结

本文简单的分析了Django项目是如何通过标准库logging来进行日志的打印,大致流程基本上是通过初始化相关配置文件通过DictConfigurator类来将相关的配置信息配置到标准库logging的全局的字典中去,然后通过获取字典中对应的配置logger进行日志的输出,相对而言日志的分析与配置过程没有很复杂的实现技巧,大部分都是细节的业务处理,鉴于本人才疏学浅,如有疏漏请批评指正。

转载地址:https://blog.csdn.net/qq_33339479/article/details/85044966 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Django项目配合sentry实现浅析
下一篇:Django项目test中的mock概述

发表评论

最新留言

不错!
[***.144.177.141]2024年04月19日 05时27分36秒