logging 的主要作用

提供日志记录的接口和众多处理模块,供用户存储各种格式的日志,帮助调试程序或者记录程序运行过程中的输出信息。

logging 日志等级

logging 日志等级分为五个等级,优先级从高到低依次是 :

**CRITICAL; ** 程序严重错误

**ERROR; **程序错误/部分功能错误

**WARNING; **程序有发生错误的可能

**INFO; **程序正常运行时的信息

DEBUG程序调试信息

默认的日志的记录等级为 WARNING, 即当日志的等级大于获等于 WARNING 时才会被记录。

一般常用的记录等级为 INFO,其用于记录程序的正常运行的一些信息(类似于print)。

当日志的等级达到 WARNING 以上时,表明此时程序不能正常运行;

logging 的基础函数

logging.basicConfig(**kwargs)

在没有显式的进行创建记录器(logger)时,会默认创建一个root logger,而logging.basicConfig(**kwargs) 可以创建带有默认的Formatter的streamHandle并将其添加到根日志记录器中来初始化基本配置。

比如

import logging

logging.debug('Debug code!')
logging.info('Run code!')
logging.warning('Watch out!')
logging.error('This is an error')
logging.critical('This is a ciritical')

上面代码中 logging 并没有显式的创建logger( logging.getLogger ), 其在直接使用debug(), info(), warning(), error(), critical() 时会使用默认的 root logger,并会自动调用 自定义的或者默认的logging.basicConfig(**kwargs) 初始化 root logger。

自定义的 logging.basicConfig(**kwargs) 中的参数 有以下的主要的选项:

例如下面通过自定义 logging.basicConfig(**kwargs) 来初始化 root logger 来获得DEBUG级别及以上的日志记录并保存到 log.txt 文件中。

import logging

logging.basicConfig(filename='./log.txt',
                        format='%(asctime)s-%(name)s-%(levelname)s-%(message)s-%(funcName)s:%(lineno)d',
                        level=logging.DEBUG)

logging.debug('Debug code!')
logging.info('Run code!')
logging.warning('Watch out!')
logging.error('This is an error')
logging.critical('This is a ciritical')

logging 的四大组件(类)

Logger

除了根记录器(root logger)外,最主要的是可以自己创建日志记录器。

通过模块级别的函数 logging.getLogger(name) 实例化记录器

默认情况下,记录器采用层级结构,通过 . 来区分不同的层级。比如 有个名叫 foo 的记录器 则 foo.afoo.b 都是 foo 的子级记录器。当然,最开始的或者说最上层的记录器就是 root logger。如果 name=None,构建的是root logger。

可以直接用当前模块的名称当作记录器的名字 logging.getLogger(__name__)

子级记录器通常不需要单独设置日志级别以及 Handler,如果子级记录器没有单独设置,则它的行为会委托给父级。比如说,记录器foo的级别为INFO,而foo.afoo.b 都不设置日志级别。此时foo.afoo.b 会遵循foo 的级别设置,即只记录大于等于INFO级别的日志;而如果foo也没设置的话,就会找到根记录器root logger,root默认的级别为WARGING。

logger类的一些常用的方法

logger 结合 后面要介绍的其他的三个组件可以实现以下的功能:

  • Logger需要通过handler将日志信息输出到目标位置,目标位置可以是sys.stdout和文件等(这与logging.basicConfig(**kwargs) 设置中不太一致)。
  • 一个Logger可以设置不同的Handler,而不同的Handler可以将日志输出到不同的位置(不同的日志文件),并且每个Handler都可以设置自己的filter从而实现日志过滤,保留实际项目中需要的日志。同时每个Handler也可以设置不同的Formatter,在每个Formatter实现同一条日志以不同的格式输出到不同的地方。

Handle

处理器;其可以控制记录的日志输出到什么地方(标准输出/文件/...),同时处理器也可以添加 过滤器(filter)和格式控制器(formatter)来控制输出的内容和输出的格式。

其具有几种常见的处理器:

  • logging.StreamHandler 标准流处理器,将消息发送到标准输出流、错误流 --> logging.StreamHandler(sys.stdout) # sys.stdout 表示的是指向控制台即标准输出;当我们在 Python 中打印对象调用 print obj 时候,事实上是调用了 sys.stdout.write(obj+'\n')。

    print 将你需要的内容打印到了控制台,然后追加了一个换行符

  • logging.FileHandler 文件处理器,将消息发送到文件 --> logging.FileHandler(log_path)

  • logging.RotatingFileHandler 文件处理器,文件达到指定大小后,启用新文件存储日志

  • logging.TimedRotatingFileHandler 文件处理器,日志以特定的时间间隔轮换日志文件

handle 类的一些常用的方法

Filter

filter组件用来过滤 logger 对象,一个 filter 可以直接添加到 logger对象上,也可以添加到 handler 对象上,而如果在logger和handler中都设置了filter,则日志是先通过logger的filter,再通过handler的filter。由于所有的信息都可以经过filter,所以filter不仅可以过滤信息,还可以增加信息。

Filter 类的实例化对象可以通过 logging.Filter(name) 来创建,其中name 为 记录器的名字,如果没有创建过该名字的记录器,就不会输出任何日志:

filter = logging.Filter("foo.a")

基本过滤器类只允许低于指定的日志记录器层级结构中低于特定层级的事件,例如 这个用 foo.a 初始化的过滤器,则foo.a.b;foo.a.c 等日志记录器记录的日志都可以通过过滤器,而foo.c; a.foo 等就不能通过。如果name为空字符串,则所有的日志都能通过。

Filter 类 有 三个方法 :

  • addFilter(filter) : 为 logger(logger..addFilter(filter)) 或者 handler(handler..addFilter(filter)) 增加过滤器
  • removeFilter(filter) : 为 logger 或者 handler 删除一个过滤器
  • filter(record) : 表示是否要记录指定的记录?返回零表示否,非零表示是。一般自定义Filter需要继承Filter基类,并重写filter方法

Formatter

格式化日志的输出;实例化:formatter = logging.Formatter(fmt=None,datefmt=None); 如果不指明 fmt,将默认使用 ‘%(message)s’ ,如果不指明 datefmt,将默认使用 ISO8601 日期格式。

其中 fmt 参数 有以下选项:

例如:

formatter = logging.Formatter('%(asctime)s %(levelname)-8s: %(message)s')		# -表示右对齐 8表示取8位
handler.formatter = formatter

datefmt 参数 有以下选项:

例子:

formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s","%Y%m%d-%H:%M:%S")
handler.formatter = formatter

logging 的配置

  • conf 形式的配置

    在 loguser.conf 中 写入相关的信息

    [loggers]
    keys=root,fileLogger,rotatingFileLogger
    
    [handlers]
    keys=consoleHandler,fileHandler,rotatingFileHandler
    
    [formatters]
    keys=simpleFormatter
    
    [logger_root]
    level=INFO
    handlers=consoleHandler
    
    [logger_fileLogger]
    level=INFO
    handlers=fileHandler
    qualname=fileLogger
    propagate=0
    
    [logger_rotatingFileLogger]
    level=INFO
    handlers=consoleHandler,rotatingFileHandler
    qualname=rotatingFileLogger
    propagate=0
    
    [handler_consoleHandler]
    class=StreamHandler
    level=INFO
    formatter=simpleFormatter
    args=(sys.stdout,)
    
    [handler_fileHandler]
    class=FileHandler
    level=INFO
    formatter=simpleFormatter
    args=("logs/fileHandler_test.log", "a")
    
    [handler_rotatingFileHandler]
    class=handlers.RotatingFileHandler
    level=WARNING
    formatter=simpleFormatter
    args=("logs/rotatingFileHandler.log", "a", 10*1024*1024, 50)
    
    [formatter_simpleFormatter]
    format=%(asctime)s - %(module)s - %(levelname)s -%(thread)d : %(message)s
    datefmt=%Y-%m-%d %H:%M:%S
    

    在使用logger时,直接导入配置文件即可

    from logging import config
    
    with open('./loguser.conf', 'r', encoding='utf-8') as f:
    	## 加载配置
        config.fileConfig(f)
        ## 创建同名Logger,其按照配置文件的handle,formatter,filter方法初始化
        logger = logging.getLogger(name="fileLogger")
    
  • yaml 形式配置文件

    在 loguser.yaml文件 中 配置相关信息

    version: 1
    disable_existing_loggers: False
    # formatters配置了日志输出时的样式
    # formatters定义了一组formatID,有不同的格式;
    formatters:
      brief:
          format: "%(asctime)s - %(message)s"
      simple:
          format: "%(asctime)s - [%(name)s] - [%(levelname)s] :%(levelno)s: %(message)s"
          datefmt: '%F %T'
    # handlers配置了需要处理的日志信息,logging模块的handler只有streamhandler和filehandler
    handlers:
      console:
          class : logging.StreamHandler
          formatter: brief
          level   : DEBUG
          stream  : ext://sys.stdout
      info_file_handler:
          class : logging.FileHandler
          formatter: simple
          level: ERROR
          filename: ./logs/debug_test.log
      error_file_handler:
        class: logging.handlers.RotatingFileHandler
        level: ERROR
        formatter: simple
        filename: ./logs/errors.log
        maxBytes: 10485760 # 10MB #1024*1024*10
        backupCount: 50
        encoding: utf8
    
    loggers:
    #fileLogger, 就是在代码中通过logger = logging.getLogger("fileLogger")来获得该类型的logger
      my_testyaml:
          level: DEBUG
          handlers: [console, info_file_handler,error_file_handler]
    # root为默认情况下的输出配置, 当logging.getLogger("fileLoggername")里面的fileLoggername没有传值的时候,
    # 就是用的这个默认的root,如logging.getLogger(__name__)或logging.getLogger()
    root:
        level: DEBUG
        handlers: [console]
    

    同样的可以通过导入 yaml 文件加载配置

    with open('./loguser.yaml', 'r', encoding='utf-8') as f:
            yaml_config = yaml.load(stream=f, Loader=yaml.FullLoader)
            config.dictConfig(config=yaml_config)
    
        root = logging.getLogger()
        # 子记录器的名字与配置文件中loggers字段内的保持一致
        # loggers:
        #   my_testyaml:
        #       level: DEBUG
        #       handlers: [console, info_file_handler,error_file_handler]
        my_testyaml = logging.getLogger("my_testyaml")
    

logging 和 print 的区别

看起来logging要比print复杂多了,那么为什么推荐在项目中使用 logging 记录日志而不是使用print 输出程序信息呢。

相比与print logging 具有以下优点:

  • 可以通过设置不同的日志等级,在 release 版本中只输出重要信息,而不必显示大量的调试信息;
  • print 将所有信息都输出到标准输出中,严重影响开发者从标准输出中查看其它数据;logging 则可以由开发者决定将信息输出到什么地方,以及怎么输出;
  • 和 print 相比,logging 是线程安全的。(python 3中 print 也是线程安全的了,而python 2中的print不是)(线程安全是指在多线程时程序不会运行混乱;而python 2 中的print 分两步打印信息,第一打印字符串,第二打印换行符,如果在这中间发生线程切换就会产生输出混乱。这就是为什么python2的print不是原子操作,也就是说其不是线程安全的)

主要参考资料

https://blog.csdn.net/weixin_41010198/article/details/89356417

https://www.cnblogs.com/chenyibai/p/10676574.html

07-11 11:38