Python日志logging模块详解

1. 简述

把我自己理解的概念用通俗易懂的话讲出来大概就是

查看日志是开发人员日常获取信息、排查异常、发现问题的最好途径,日志记录中通常会标记有异常产生的原因、发生时间、具体错误行数等信息,这极大的节省了我们的排查时间,无形中提高了编码效率。

2. 级别分类

下表是日志按照级别分类,指的是 Debug、Info、WARNING、ERROR 、CRITICAL 等严重等级进行划分。

级别 数值 说明
CRITICAL 50 十分严重的问题导致程序已经不能运行
ERROR 40 有错误导致程序的某些功能不能按照预期工作
WARNING 30 可以是一些意料之外的问题,也可以是一些预期将要发生的问题的警告
INFO 20 表明程序正常运行
DEBUG 10 比较详细的信息,一般在调试问题的时候使用
NOTSET 0 没有日志级别,任何信息都会输出

上述级别分类是在日志系统里通用的。在 python 中对应的模块函数分别为 debug()、info()、warning()、error()、critical() 
Python 中日志的默认等级是 WARNING , DEBUG  和 INFO  级别的日志将不会得到显示,在 logging 中更改设置。

3. 日志的输出

先来简单使用下 python 的 logging 模块输出日志的功能

import logging

logging.debug('this is a debug msg')
logging.info('this is a info msg')
logging.warning('this is a warning msg')
logging.error('this is a error msg')

输出如下

WARNING:root:this is a warning msg
ERROR:root:this is a error msg

这样的输出证实了上面所说

“Python 中日志的默认等级是 WARNINGDEBUG  和 INFO  级别的日志将不会得到显示”

我们来修改下 Python 默认的日志等级为 DEBUG

import logging

logging.basicConfig(level=logging.DEBUG)
logging.debug('this is a debug msg')
logging.info('this is a info msg')
logging.warning('this is a warning msg')
logging.error('this is a error msg')

再来看看输出结果,正如我们所预期的那样

DEBUG:root:this is a debug msg
INFO:root:this is a info msg
WARNING:root:this is a warning msg
ERROR:root:this is a error msg

4. 模块函数及四大组件

上面的代码只是将我们想要的日志信息输出到了控制台,如果我们要记录日志到文件中,就要用到强大的logging模块级别的函数,或者是 logging 的四大组件了。

上面设置日志级别的 logging.DEBUG 就是所说的级别函数,而四大组件分别是 loggers、handlers、filters、formatters ,从名字上也不难看出这些组件的作用

组件名称 logging中对应的类 功能
日志器 Logger 提供日志系统使用的接口
处理器 Handler logger 收集到的日志发送到指定的地方
过滤器 Filter logger 收集日志的时候提供过滤规则
格式器 Formatter 设定日志的输出格式

工作原理

日志器(logger)是入口,真正工作的是处理器(handler),处理器(handler)还可以通过过滤器(filter)和格式器(formatter)对要输出的日志内容做过滤和格式化等处理操作。

Logger

Logger 的三个功能点

  • 提供记录日志的接口,如 logging.debug
  • 基于日志的严重级别或 filter 对象来对日志进行处理,过滤
  • 将处理过的日志信息发送到设定的 handlers

Logger 对象常用方法如下

方法 功能描述
Logger.setLevel() 设置日志处理的最低严重级别
Logger.addHandler() 将 handler 对象添加到 logger 对象中
Logger.removeHandler() 将 hanlder 对象从 logger 对象中移除
Logger.addFilter() 将 filter 对象添加到 logger 对象中
Logger.removeFilter() 将 filter 对象从 logger 对象中移除

获取 logger 对象有两种方式,我们通常使用第二种方式

  • 通过 Logger 类的实例化方法创建一个 Logger 类的实例,, 例如 logger = Logger()
  • 通过logging.getLogger()  方法。 logging.getLogger() 方法有一个可选参数 name ,该参数表示将要返回的日志器的名称标识,如果不提供该参数,则其值为 ‘root’。若以相同的 name 参数值多次调用 getLogger()  方法,将会返回指向同一个 logger 对象的引用。

对于 logger 层级的补充

  • logger 的名称是一个以 . 分割的层级结构,每个 . 后面的 logger 都是 . 前面的 logger 的 child。例如,有一个名称为 A 的 logger,其它 logger 名称分别为 A.B, A.B.C 都是 A 的后代。
  • logger 有一个"有效等级(effective level)"的概念。如果一个 logger 上没有被明确设置一个 level,那么该logger将会向上查找已经明确定义了 level 的 logger 。需要说明的是,root logger 总是会有一个明确的level设置(默认为 WARNING)。当决定是否去处理一个已发生的事件时,logger 的有效等级将会被用来决定是否将该事件传递给该 logger 的 handlers 进行处理。
  • child loggers 在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先 loggers 相关的 handlers。因此,我们不必为一个应用程序中所使用的所有 loggers 定义和配置 handlers,只需要为一个顶层的 logger 配置 handlers,然后按照需要创建 child loggers 就可足够了。我们也可以通过将一个 logger 的 propagate 属性设置为False来关闭这种传递机制。

Handler

Hander 常用的方法也不多

方法 功能描述
Handler.setLevel() 设置日志处理的最低严重级别
Handler.setFormatter() 为 handler 对象设置一个格式器对象
Handler.addFilter() 将 filter 对象添加到 handler 对象中
Handler.removeFilter() 将 filter 对象从 handler 对象中移除

但是和 Logger 类似,我们在获取 handler 对象的时候最好不要直接实例化 Handler 这个基类。logging 模块已经为我们定义好了一些常用的 handler

  • logging.StreamHandler 将日志消息发送到 Stream, 如 std.out, std.err
  • logging.FileHandler 将日志消息发送到文件中,默认是 a 模式追加写入
  • logging.handlers.SMTPHandler 将日志消息发送到指定邮箱
  • logging.handlers.HTTPHandler 将日志消息以 GET 或是 POST 的方式发送到一个 HTTP服务器
  • logging.handlers.RotatingFileHandler 日志文件支持按照大小切割并发送到文件
  • logging.handlers.TimedRotatingFileHandler 日志文件支持按照时间切割并发送到文件

Formatter

Formater 对象用于配置日志信息的最终顺序、结构和内容。与 logging.Handler 基类不同的是,可以直接实例化 Formatter 类。另外,如果你的应用程序需要一些特殊的处理行为,也可以实现一个 Formatter 的子类来完成。 Formatter 类的构造方法定义如下
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')

该构造方法接收 3 个可选参数:

  • fmt :指定消息格式化字符串,如果不指定该参数则默认使用 message 的原始值
  • datefmt :指定日期格式字符串,如果不指定该参数则默认使用 %Y-%m-%d %H:%M:%S
  • style :可取值为 %, {, $ ,如果不指定该参数则默认使用 “%”

fmt 中允许使用的变量可以参考下表。

  • **%(name)s**  Logger的名字
  • **%(levelno)s**  数字形式的日志级别
  • **%(levelname)s**  文本形式的日志级别
  • **%(pathname)s**  调用日志输出函数的模块的完整路径名,可能没有
  • **%(filename)s**  调用日志输出函数的模块的文件名
  • **%(module)s**  调用日志输出函数的模块名
  • **%(funcName)s**  调用日志输出函数的函数名
  • **%(lineno)d**  调用日志输出函数的语句所在的代码行
  • **%(created)f**  当前时间,用UNIX标准的表示时间的浮点数表示
  • **%(relativeCreated)d**  输出日志信息时的,自Logger创建以来的毫秒数|
  • **%(asctime)s**  字符串形式的当前时间。默认格式是 “yyyy-mm-dd HH:MM:SS,SSS”
  • **%(thread)d**  线程ID
  • **%(threadName)s**  线程名
  • **%(process)d**  进程ID
  • **%(message)s**  用户输出的消息

Filter

Filter 可以被 Handler 和 Logger 用来做比 level 更细粒度的、更复杂的过滤功能。Filter 是一个过滤器基类,它只允许某个 logger 层级下的日志事件通过过滤。

比如,一个 filter 实例化时传递的 name 参数值为 A.B ,那么该 filter 实例将只允许名称为类似如下规则的 loggers 产生的日志记录通过过滤: A.B, A.B.C, A.B.C.D, A.B.D ,而名称为 A.BB, B.A.B 的 loggers 产生的日志则会被过滤掉。如果 name 的值为空字符串,则允许所有的日志事件通过过滤。

5. 实战应用

import logging
import os
import sys


BASE_PATH = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(BASE_PATH)


def my_logger(log_name='NANCY_LOG',
              log_file=f'{BASE_PATH}/log_demo/log/nancy.log',
              level=logging.DEBUG):
    # 创建logger对象
    logger = logging.getLogger(log_name)
    logger.setLevel(level)       # 添加等级

    # 创建控制台 console handler
    ch = logging.StreamHandler()
    ch.setLevel(level)

    # 创建文件   handler
    fh = logging.FileHandler(log_file)
    fh.setLevel(level)

    # 创建   formatter
    formatter = logging.Formatter('%(asctime)s '
                                  '%(filename)s '
                                  '[line:%(lineno)s] '
                                  '%(name)s '
                                  '%(levelname)s: '
                                  '%(message)s')

    # 添加日志格式  formatter
    # ch.setFormatter(formatter)
    fh.setFormatter(formatter)

    # 把ch fh 添加到  logger中
    logger.addHandler(ch)
    logger.addHandler(fh)

    return logger


def main():
    # 测试
    logger = my_logger(log_name='nancy test',
                       level=logging.DEBUG)

    logger.info('test logging info'.center(30, '*'))
    logger.debug('test logging debug'.center(30, '*'))
    logger.error('test logging error'.center(30, '*'))
    logger.warning('test logging warning'.center(30, '*'))
    logger.critical('test logging critical'.center(30, '*'))


if __name__ == '__main__':
    main()

你可能感兴趣的:(python,python)