zoukankan      html  css  js  c++  java
  • python的logging模块详解-filters,handlers,formatters,loggers | json配置(2)

    经常运行的程序,通常都有日志记录的需求,我们可以通过日志记录程序的日常访问,也可以把一些错误、警告等信息记录下来。如果你的编程语言是python,那日志模块的logging模块对你的程序开发一定很有用。

    通过 logging 模块存储各种格式的日志,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等,那这些能否用 print替代呢?

    print这种方式对于简单脚本型程序有用,但是如果是复杂的系统,最好不要用。

    • 1.首先,这些print是没用的输出,大量使用很有可能会被遗忘在代码里
    • 2.再者,print 输出的所有信息都到了标准输出中,这将严重影响到你从标准输出中查看其它输出数据

    再来看看使用logging优势

    • 1.你可以控制消息的级别,过滤掉那些并不重要的消息。
    • 2.你可决定输出到什么地方,以及怎么输出。有许多的重要性别级可供选择,debug、info、warning、error 以及 critical。通过赋予 logger 或者 handler 不同的级别,你就可以只输出错误消息到特定的记录文件中,或者在调试时只记录调试信息。

    接下来对logging模块做个详细讲解。

    1.logging的日志框架

    logging主要包含四大组成部分,每部分对应功能不同:

    1.Loggers: 可供程序直接调用的接口,app通过调用提供的api来记录日志
    
    2.Handlers: 决定将日志记录分配至正确的目的地
    
    3.Filters:对日志信息进行过滤, 提供更细粒度的日志是否输出的判断
    
    4.Formatters: 制定最终记录打印的格式布局
    

    1.1 loggers

    loggers 就是程序可以直接调用的一个日志接口,可以直接向logger写入日志信息。logger并不是直接实例化使用的,而是通过logging.getLogger(name)来获取对象,事实上logger对象是单例模式,logging是多线程安全的,也就是无论程序中哪里需要打日志获取到的logger对象都是同一个。但是不幸的是logger并不支持多进程,这个在后面的章节再解释,并给出一些解决方案。

    【注意】loggers对象是有父子关系的,当没有父logger对象时它的父对象是root,当拥有父对象时父子关系会被修正。举个例子,logging.getLogger("abc.xyz") 会创建两个logger对象,一个是abc父对象,一个是xyz子对象,同时abc没有父对象,所以它的父对象是root。但是实际上abc是一个占位对象(虚的日志对象),可以没有handler来处理日志。但是root不是占位对象,如果某一个日志对象打日志时,它的父对象会同时收到日志,所以有些使用者发现创建了一个logger对象时会打两遍日志,就是因为他创建的logger打了一遍日志,同时root对象也打了一遍日志。

    1.2 handlers

    Handlers 将logger发过来的信息进行准确地分配,送往正确的地方。举个栗子,送往控制台或者文件或者both或者其他地方(进程管道之类的)。它决定了每个日志的行为,是之后需要配置的重点区域。

    每个Handler同样有一个日志级别,一个logger可以拥有多个handler也就是说logger可以根据不同的日志级别将日志传递给不同的handler。当然也可以相同的级别传递给多个handlers这就根据需求来灵活的设置了。

    1.3 filters

    Filters 提供了更细粒度的判断,来决定日志是否需要打印。原则上handler获得一个日志就必定会根据级别被统一处理,但是如果handler拥有一个Filter可以对日志进行额外的处理和判断。例如Filter能够对来自特定源的日志进行拦截or修改甚至修改其日志级别(修改后再进行级别判断)。

    logger和handler都可以安装filter甚至可以安装多个filter串联起来。

    1.4 formatters

    Formatters 指定了最终某条记录打印的格式布局。Formatter会将传递来的信息拼接成一条具体的字符串,默认情况下Format只会将信息%(message)s直接打印出来。Format中有一些自带的LogRecord属性可以使用,如下表格:

    一个Handler只能拥有一个Formatter 因此如果要实现多种格式的输出只能用多个Handler来实现。

    2.日志级别

    在记录日志时, 日志消息都会关联一个级别。

    级别排序:CRITICAL > ERROR > WARNING > INFO > DEBUG

    debug : 打印全部的日志,详细的信息,通常只出现在诊断问题上
    
    info : 打印info,warning,error,critical级别的日志,确认一切按预期运行
    
    warning : 打印warning,error,critical级别的日志,一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间低”),这个软件还能按预期工作
    
    error : 打印error,critical级别的日志,更严重的问题,软件没能执行一些功能
    
    critical : 打印critical级别,一个严重的错误,这表明程序本身可能无法继续运行
    

    如果需要显示低于某一级别级别的内容,可以引入NOTSET级别来显示。

    日常使用中,注意以下:

    3.基本使用

    3.1 简单的将日志打印到屏幕

    import logging 
    
    logging.debug('this is debug message') 
    logging.info('this is info message')
    logging.warning('this is warning message')
    
    # 打印结果:WARNING:root:this is warning message  
    

    默认情况下,logging将日志打印到屏幕,日志级别为WARNING;
    日志级别大小关系为:CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET,当然也可以自己定义日志级别。

    3.2 通过logging.basicConfig函数对日志的输出格式及方式做相关配置

    import logging 
    
    logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(name)s - %(message)s')  
      
    logging.debug('this is debug message')  
    logging.info('this is info message')  
    logging.warning('this is warning message')  
    

    logging.basicConfig函数各参数:

    filename: 指定日志文件名
    filemode: 和file函数意义相同,指定日志文件的打开模式,'w'或'a'
    format: 指定输出的格式和内容,format可以输出很多有用信息,如上例所示:
     %(levelno)s: 打印日志级别的数值
     %(levelname)s: 打印日志级别名称
     %(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
     %(filename)s: 打印当前执行程序名
     %(funcName)s: 打印日志的当前函数
     %(lineno)d: 打印日志的当前行号
     %(asctime)s: 打印日志的时间
     %(thread)d: 打印线程ID
     %(threadName)s: 打印线程名称
     %(process)d: 打印进程ID
     %(message)s: 打印日志信息
    datefmt: 指定时间格式,同time.strftime()
    level: 设置日志级别,默认为logging.WARNING
    stream: 指定将日志的输出流,可以指定输出到sys.stderr,sys.stdout或者文件,默认输出到sys.stderr,当stream和filename同时指定时,stream被忽略
    

    3.3 日志回滚

    如果你用 FileHandler 写日志,文件的大小会随着时间推移而不断增大。最终有一天它会占满你所有的磁盘空间。为了避免这种情况出现,你可以在你的生成环境中使用 RotatingFileHandler 替代 FileHandler。

    import logging
    from logging.handlers import RotatingFileHandler
    logger = logging.getLogger(__name__)
    logger.setLevel(level = logging.INFO)
    # 定义一个RotatingFileHandler,最多备份3个日志文件,每个日志文件最大1K
    rHandler = RotatingFileHandler("log.txt",maxBytes = 1*1024,backupCount = 3)
    rHandler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    rHandler.setFormatter(formatter)
    
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    console.setFormatter(formatter)
    
    logger.addHandler(rHandler)
    logger.addHandler(console)
    
    logger.info("Start print log")
    logger.debug("Do something")
    logger.warning("Something maybe fail.")
    logger.info("Finish")
    

    4.使用logging配置

    4.1 通过JSON加载日志配置

    logging.json:

    {
        "version":1,
        "disable_existing_loggers":false,
        "formatters":{
            "simple":{
                "format":"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
            }
        },
        "handlers":{
            "console":{
                "class":"logging.StreamHandler",
                "level":"DEBUG",
                "formatter":"simple",
                "stream":"ext://sys.stdout"
            },
            "info_file_handler":{
                "class":"logging.handlers.RotatingFileHandler",
                "level":"INFO",
                "formatter":"simple",
                "filename":"info.log",
                "maxBytes":"10485760",
                "backupCount":20,
                "encoding":"utf8"
            },
            "error_file_handler":{
                "class":"logging.handlers.RotatingFileHandler",
                "level":"ERROR",
                "formatter":"simple",
                "filename":"errors.log",
                "maxBytes":10485760,
                "backupCount":20,
                "encoding":"utf8"
            }
        },
        "loggers":{
            "my_module":{
                "level":"ERROR",
                "handlers":["info_file_handler"],
                "propagate":"no"
            }
        },
        "root":{
            "level":"INFO",
            "handlers":["console","info_file_handler","error_file_handler"]
        }
    }
    

    通过JSON加载配置文件,然后通过logging.dictConfig配置logging,setup_logging.py

    import json
    import logging.config
    import os
    
    def setup_logging(default_path = "logging.json",default_level = logging.INFO,env_key = "LOG_CFG"):
        path = default_path
        value = os.getenv(env_key,None)
        if value:
            path = value
        if os.path.exists(path):
            with open(path,"r") as f:
                config = json.load(f)
                logging.config.dictConfig(config)
        else:
            logging.basicConfig(level = default_level)
    
    def func():
        logging.info("start func")
    
        logging.info("exec func")
    
        logging.info("end func")
    
    if __name__ == "__main__":
        setup_logging(default_path = "logging.json")
        func()
    

    4.2 通过YAML文件配置

    logging.yaml:

    version: 1
    disable_existing_loggers: False
    formatters:
            simple:
                format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
    handlers:
        console:
                class: logging.StreamHandler
                level: DEBUG
                formatter: simple
                stream: ext://sys.stdout
        info_file_handler:
                class: logging.handlers.RotatingFileHandler
                level: INFO
                formatter: simple
                filename: info.log
                maxBytes: 10485760
                backupCount: 20
                encoding: utf8
        error_file_handler:
                class: logging.handlers.RotatingFileHandler
                level: ERROR
                formatter: simple
                filename: errors.log
                maxBytes: 10485760
                backupCount: 20
                encoding: utf8
    loggers:
        my_module:
                level: ERROR
                handlers: [info_file_handler]
                propagate: no
    root:
        level: INFO
        handlers: [console,info_file_handler,error_file_handler]
    

    通过YAML加载配置文件,然后通过logging.dictConfig配置logging,setup_logging.py

    import yaml
    import logging.config
    import os
    
    def setup_logging(default_path = "logging.yaml",default_level = logging.INFO,env_key = "LOG_CFG"):
        path = default_path
        value = os.getenv(env_key,None)
        if value:
            path = value
        if os.path.exists(path):
            with open(path,"r") as f:
                config = yaml.load(f)
                logging.config.dictConfig(config)
        else:
            logging.basicConfig(level = default_level)
    
    def func():
        logging.info("start func")
    
        logging.info("exec func")
    
        logging.info("end func")
    
    if __name__ == "__main__":
        setup_logging(default_path = "logging.yaml")
        func()
    

    接下来,你就可以在运行程序的时候调用setup_logging来启动日志记录了。它默认会读取logging.json或logging.yaml文件。你也可以设置环境变量LOG_CFG从指定的路径加载日志配置,例如:

    LOG_CFG = my_logging.json
    python my_server.py

    如果你喜欢YAML:

    LOG_CFG = my_logging.yaml
    python my_server.py

    注意:配置文件中“disable_existing_loggers” 参数设置为 False;如果不设置为False,创建了 logger,然后你又在加载日志配置文件之前就导入了模块。logging.fileConfig 与 logging.dictConfig 默认情况下会使得已经存在的 logger 失效。那么,这些配置信息就不会应用到你的 Logger 上。“disable_existing_loggers” = False解决了这个问题。

    5.捕捉异常并使用traceback记录

    出问题时记录下来是个好习惯,python中的traceback模块用于记录异常信息,我们可以在logger中记录下traceback

    比如下面的例子:

    try:
        open('/path/to/does/not/exist', 'rb')
    except (SystemExit, KeyboardInterrupt):
        raise
    except Exception, e:
        logger.error('Failed to open file', exc_info=True) 
    # 也可以调用 logger.exception(msg, _args),它等价于 logger.error(msg, exc_info=True, _args)。
    

    结果为:

    ERROR:__main__:Failed to open file  
    Traceback (most recent call last):  
      File "example.py", line 6, in <module>  
        open('/path/to/does/not/exist', 'rb')  
    IOError: [Errno 2] No such file or directory: '/path/to/does/not/exist'  
    

    6.tips

    6.1 使用__name__作为logger的名称

    虽然不是非得将 logger 的名称设置为 name ,但是这样做会给我们带来诸多益处。在 python 中,变量 name 的名称就是当前模块的名称。比如,在模块 “foo.bar.my_module” 中调用 logger.getLogger(name) 等价于调用logger.getLogger(“foo.bar.my_module”) 。当你需要配置 logger 时,你可以配置到 “foo” 中,这样包 foo 中的所有模块都会使用相同的配置。当你在读日志文件的时候,你就能够明白消息到底来自于哪一个模块。

    参考:
    https://www.cnblogs.com/deeper/p/7404190.html

  • 相关阅读:
    Linux Shell中的特殊符号和含义简明总结(包含了绝大部份)
    Linux学习笔记:cat、tac、more、less、head、tail查看文件内容
    Linux学习笔记:pwd与dirs的区别
    Linux学习笔记:nohup & 后台任务
    Linux学习笔记:输入输出重定向 >>命令
    Linux学习笔记:vi常用命令
    Linux学习笔记:touch新建文件、修改访问、改动时间
    Linux学习笔记:mkdir创建文件夹
    Linux学习笔记:mv移动或文件重命名
    Shell学习笔记:#*、%*字符串掐头去尾方法
  • 原文地址:https://www.cnblogs.com/davis12/p/14504725.html
Copyright © 2011-2022 走看看