python中的日志管理模块logging

logging模块是python中自带的日志处理模块,可用于记录程序异常的位置、时间和具体错误信息等,从而方便开发人员检测程序运行过程和捕获、分析程序异常。

按照输出类型来分,logging可选择控制台直接输出日志信息,也可选择将日志信息写入日志文件。

按照日志级别来分,logging中的日志等级从低到高依次为:

日志级别数值说明
NOTSET0不做设置,自动集成父级Logger的等级。
DEBUG10详细信息,一般只在调试问题时使用。
INFO20证明事情按预期工作。
WARNING30某些没有预料到的事件的提示,或者在将来可能会出现的问题提示。
ERROR40由于更严重的问题,软件已经不能执行一些功能。
CRITIAL50严重错误,表明软件已不能继续运行。

利用logging进行日志输出配置主要有四种方式
方式一:logging模块级函数
方式二:实例化Logger类
方式三:通过fileConfig读入config文件
方式四:通过dictConfig加载json、yaml等配置文件

下文将分别进行介绍。

一、logging模块级函数

利用logging模块级函数进行配置的方式非常简单:

第一步:利用logging.basicConfig进行logging参数的设置

import logging
logging.basicConfig(**kwargs)
"""
参数包括:
filename:日志输出文件的文件名
filemode:写入文件时的模式,默认为追加模式'a'
format: 输出文本信息的格式
datefmt: 输出日期信息的格式
style:文本信息的填充位符号,默认%
level:日志等级,包括'DEBUG'、'INFO'、'WARNING'、'ERROR'、'CRITICAL',默认为'WARNING'级别
stream:文件流,注意该参数和filename参数是不相容的,若设置了filename,则忽略该参数
"""

第二步:利用函数级的日志输出方法进行日志的输出

import logging
logging.debug("调试日志")   # DEBUG级别
logging.info('消息日志')  # INFO级别
logging.warning("警告日志")  # WARNING级别
logging.error('错误日志')  # ERROR级别
logging.critical('致命错误日志')  # CRITICAL级别
1.1 终端输出日志
import logging
logging.basicConfig(level='DEBUG')   # DEBUG级别
logging.debug("调试日志")   
1.2 文件输出日志

如果在logging.basicConfig中直接设置filename参数,虽然其随着对应的文件句柄handler关闭而关闭,但因为编码问题无法直接写入中文;此时必须通过设置stream参数。但这种方法的缺点也非常明显:文件流必须一直打开!

import logging
f = open('logging.log', encoding='utf-8', mode='a')  # 配置文件流
logging.basicConfig(level='DEBUG', stream=f) # 设置stream参数
logging.debug("调试日志")   # 写入logging.log文件
二、实例化Logger类

在介绍通过实例化Logger类进行日志文件配置前,先介绍logging模块中的四大组件类:

2.1 Logger类

日志器,提供了一个可一直使用的用于配置和记录的日志对象,可视为整个日志记录过程的主入口。

2.1.1 Logger类常见的方法

Logger类常见的方法如下:

import logging
# 1. 定义Logger实例化对象
logger1 = logging.Logger()
logger = logging.getLogger(name='A')   # 更常用,默认name为root

# 2. 设置消息等级
logger.setLevel()    # 默认Warning

# 3. 添加/移除处理器handler
logger.addHandler()
logger.removeHandler()

# 4. 添加/移除过滤器Filter
logger.addFilter()
logger.removeFilter()

# 5. 配置完后,创建日志记录
logger.debug()   # 此外还有.info(),.warning(),.error(),.critical()

logger.exception()   # 记录异常记录,效果类似于logger.error(),具体使用见第五部分

logger.log()   # 必须显示定义明确的level来生成log
2.1.2 Logger对象的继承和反馈机制

Logger对象有两个非常重要的内部机制,继承反馈

(1) 继承机制

通过logging.getLogger生成的Logger对象的默认nameroot,这是所有Logger对象的最上一级父类。

而在为logging.getLogger设置name时,其命名应遵循以.分隔的分级原则。如分别定义nameAA.BA.C的Logger对象,则AA.BA.C的上一级Logger。

Logger对象会按照命名方式,在不重新定义其level的情况下,会继承上一级父Logger的消息等级,并一直上溯到root级Logger(其默认level为Waring)。

(2) 反馈机制

下一级Logger的消息会自动反馈给上一级Logger,并根据上一级Logger的level做出相应的输出,这种反馈机制会逐层作用一直到root级Logger。

关闭这种反馈机制的方法为设置Logger对象的propagate属性为0(默认值为1)。

logger.propagate = 0
2.2 Handler类

处理器,将日志消息分发到的各类处理对象,可视为日志记录的核心处理单元。

2.2.1 Handler类常见的方法
import logging 
# 1. 定义handler
handler = logging.StreamHandler()

# 2. 设置消息等级
handler.setLevel()    # 默认Warning

# 3. 设置格式器
handler.setFormatter()

# 4. 添加/移除过滤器Filter
handler.addFilter()
handler.removeFilter()
2.2.2 常见的Handler类
Handler说明
StreamHandler流文件,可将日志消息进行屏幕标准输出或文件输出。
FileHandler将日志消息发送到磁盘文件,默认情况下文件大小会无限增长。
RotatingFileHandler将日志发送到磁盘文件,支持日志文件按大小切分。
TimedRotatingFileHandler将日志发送到磁盘文件,支持日志文件按时间切分。
HTTPHandler将日志消息以GET或POST的方式发送给HTTP服务器。
SMTPHandler将日志消息发送给指定的email地址
2.3 Filter类

过滤器,可被Logger对象或Handler使用,用于对日志的输出的等级或内容进行更颗粒度的控制,可视为日志记录的前置过滤条件。

Filter类的主要工作原理为:通过设置日志消息record需要满足的条件,来对日记进行过滤操作。

该类的定义方式非常简单:

class logging.Filter(name='')
    filter(record)

其中name=A参数表示只有名字为A的Logger,以及其子Logger(如A.B,A.B.C)可以通过该Filter;而默认值name=''表示所有日志信息均可通过。而实际使用中,过滤的原则有很多种,更细致的定义方式需要通过设置其中的record对象。

2.3.1 LogRecord类

日志消息record其实是logging模块中LogRecord类,其常用的属性包括:

name:所属Logger的name
level:消息等级
pathname:文件路径
lineno: 代码所处行数
msg:消息内容
exc_info:执行的反馈信息
# 此外,还可以自定义某个关注的key,具体见下面的例子
2.3.2 自定义Filter类的一个案例(捕获自定义的key)
import logging
import sys

class ContextFilter(logging.Filter):
    """
    这是一个控制日志记录的过滤器。
    仅记录Task=logToConsole的消息,也可以通过record自带的name、line等进行相关设置。
    """
    def filter(self, record):
        try:
            filter_key = record.TASK
        except AttributeError:
            return False
	
        if filter_key == "logToConsole":   
            return True
        else:
            return False

logger = logging.getLogger(__name__)
console_handler = logging.StreamHandler(sys.stdout)
console_handler.setLevel(logging.WARN)

console_fmt = '%(asctime)-15s [%(TASK)s] %(message)s'
console_formatter = logging.Formatter(console_fmt)
console_handler.setFormatter(console_formatter)

console_filter = ContextFilter()
console_handler.addFilter(console_filter)
logger.addHandler(console_handler)

filter_dict = {'TASK': 'logToConsole'}

# 记录日志
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message1', extra=filter_dict)
logger.error('error message2')
2.4 Formatter类

格式器,可作为模块级函数直接使用,或被Handler使用,用于定义日志输出的文本或日期等信息的格式,可视为日志记录的格式控制。

2.4.1 两种构造方法

方法一:直接通过模块级方法进行构造

logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
"""
参数说明:
fmt:指定消息格式化字符串,如果不指定该参数则默认使用message的原始值
datefmt: 指定日期格式字符串,如果不指定该参数则默认使用"%Y-%m-%d %H:%M:%S"
styple: 可取值为 '%', '{'和 '$',如果不指定该参数则默认使用'%'
"""

方法二:定义Formatter类,并绑定于Handler

formatter = logging.Formatter(fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
handler = logging.Handler()
handler.setFormatter(formatter)
2.5 各组件间的逻辑关系

上述四大组件间的相互逻辑关系见下图:
在这里插入图片描述

2.6 一个简单的示例
def my_logger():
    """
    共配置了两个Logger对象,父Logger A和子Logger A.B
    父Logger A配置了一个StreamHandler,用于将日志输出文件
    子Logger A.B配置了两个FileHandler,用于分别对日志的level进行控制
    :return:
    """

    logger_parent = logging.getLogger(name='A')    # 父logger
    logger_child = logging.getLogger(name='A.B')    # 子logger
    # logger_child.propagate = 0     # 防止反传消息

    logger_parent.setLevel('DEBUG')     # 必须先设置logger级的level,再设置handler级的,否则会出现错误

    stream_handler = logging.StreamHandler()    # 流文件处理器,此处用于控制写入A.B logger
    stream_handler.setLevel(logging.WARNING)   # handler的level设置
    stream_handler.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))   # handler的format设置

    file_handler1 = logging.FileHandler('logging_Logger_sample_logger1.log', encoding='utf-8', mode='a+')
    file_handler1.setLevel(logging.INFO)
    file_handler1.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))

    file_handler2 = logging.FileHandler('logging_Logger_sample_logger2.log', encoding='utf-8', mode='a+')
    file_handler2.setLevel(logging.WARNING)
    file_handler2.setFormatter(logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))

    logger_parent.addHandler(stream_handler)
    logger_child.addHandler(file_handler1)
    logger_child.addHandler(file_handler2)

    logger_parent.warning("parent_logger警告!")
    logger_parent.info('parent_logger消息!')
    logger_parent.debug('parent_logger调试!')

    logger_child.warning("child_logger警告!")
    logger_child.info('child_logger消息!')
    logger_child.debug('child_logger调试!')
    logger_child.error('child_logger错误!')
2.7 另一个封装的示例(按UUID进行日志记录归档)
import os
import logging
import uuid
from logging import Handler, FileHandler, StreamHandler


class PathFileHandler(FileHandler):
    def __init__(self, path, filename, mode='a', encoding='utf-8', delay=False):

        filename = os.fspath(filename)
        if not os.path.exists(path):
            os.mkdir(path)
        self.baseFilename = os.path.join(path, filename)
        self.mode = mode
        self.encoding = encoding
        self.delay = delay
        if delay:
            Handler.__init__(self)
            self.stream = None
        else:
            StreamHandler.__init__(self, self._open())


class Loggers(object):
    # 日志级别关系映射
    level_relations = {
        'debug': logging.DEBUG, 'info': logging.INFO, 'warning': logging.WARNING,
        'error': logging.ERROR, 'critical': logging.CRITICAL
    }

    def __init__(self, filename='{uid}.log'.format(uid=uuid.uuid4()), level='info', log_dir='log',
                 fmt='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s'):
        self.logger = logging.getLogger(filename)
        abspath = os.path.dirname(os.path.abspath(__file__))
        self.directory = os.path.join(abspath, log_dir)
        format_str = logging.Formatter(fmt)  # 设置日志格式
        self.logger.setLevel(self.level_relations.get(level))  # 设置日志级别
        stream_handler = logging.StreamHandler()  # 往屏幕上输出
        stream_handler.setFormatter(format_str)
        file_handler = PathFileHandler(path=self.directory, filename=filename, mode='a')
        file_handler.setFormatter(format_str)
        self.logger.addHandler(stream_handler)
        self.logger.addHandler(file_handler)


if __name__ == "__main__":
    txt = "用于测试日志的文本数据"
    log = Loggers(level='debug')
    log.logger.info(1)
    log.logger.error("出现error")
    log.logger.info(txt)
三、通过fileConfig读入config文件
3.1 相关文件的配置

下面检出一份简单的logging.conf配置文件和调用的py文件案例。

  • logging.conf配置文件
[loggers]
keys=root,simpleExample

[handlers]
keys=fileHandler,consoleHandler

[formatters]
keys=simpleFormatter

[logger_root]
level=DEBUG
handlers=fileHandler

[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
args=(sys.stdout,)
level=DEBUG
formatter=simpleFormatter

[handler_fileHandler]
class=FileHandler
args=('logging_fileconfig_sample_logging.log', 'a')
level=ERROR
formatter=simpleFormatter

[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
  • 调用的py文件
import logging.config

# 读取日志配置文件内容
logging.config.fileConfig('logging.conf')

# 创建一个日志器logger
logger = logging.getLogger('simpleExample')

# 日志输出
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
3.2 logging.conf配置文件的说明

通过logging.conf进行日志配置,其实就是对python中自带的configparser库的封装,所以logging.conf对于sectionoption的书写格式及其他要求均可参考该库。

读取配置文件的API为:

logging.config.fileConfig(fname, defaults=None, disable_existing_loggers=True)
"""
fname: 配置文件路径或名称
defaults:指定传给ConfigParser的默认值
disable_existing_loggers:布尔型值,默认值为True(为了向后兼容)表示禁用已经存在的logger;如果值为False则对已存在的loggers保持启动状态。
"""

在编写配置文件fname时候,需要注意如下事项:

(1)配置文件中一定要包含loggers、handlers、formatters这些section,它们通过keys这个option来指定该配置文件中已经定义好的loggers、handlers和formatters,多个值之间用逗号分隔;

(2)loggers这个section中的keys一定要包含root这个值;

(3)loggers、handlers、formatters中所指定的日志器、处理器和格式器都需要在下面以单独的section进行定义。seciton的命名规则为[logger_loggerName]、[formatter_formatterName]、[handler_handlerName]

(4)定义logger的section必须指定level和handlers这两个option。其中handlers的值是以逗号分隔的handler名字列表(若只有1个,也可单独给出),这里出现的handler必须出现在[handlers]这个section中,并且相应的handler必须在配置文件中有对应的section定义;

(5)对于非root logger来说,除了level和handlers这两个option之外,还需要一些额外的option,其中qualname是必须提供的option,它表示在logger层级中的名字,在应用代码中通过这个名字得到logger;控制反馈的参数propagate是可选项,其默认是为1;

(6)定义handler的section中必须指定class和args这两个option,level和formatter为可选option;class表示用于创建handler的类名,args表示传递给class所指定的handler类初始化方法参数,它必须是一个元组(tuple)的形式,即便只有一个参数值也需要是一个元组的形式;level与logger中的level一样,而formatter指定的是该处理器所使用的格式器,这里指定的格式器名称必须出现在formatters这个section中,且在配置文件中必须要有这个formatter的section定义;如果不指定formatter则该handler将会以消息本身作为日志消息进行记录,而不添加额外的时间、日志器名称等信息;

(7)定义formatter的sectioin中的option都是可选的,其中包括format用于指定格式字符串,默认为消息字符串本身;datefmt用于指定asctime的时间格式,默认为’%Y-%m-%d %H:%M:%S’;class用于指定格式器类名,默认为logging.Formatter;

(8)配置文件中的class指定类名时,该类名可以是相对于logging模块的相对值,如:FileHandler、handlers.TimeRotatingFileHandler;也可以是一个绝对路径值,通过普通的import机制来解析(注意路径一定要在sys.path包含的路径中)。

四、通过dictConfig加载json、yaml等配置文件
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"]
    }
}
  • 加载的python文件
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]
  • 加载的python文件
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()
五、利用logging模块记录程序异常

利用logging模块可以记录异常回溯信息 traceback.format_exc(),其可行的API接口为loging.exception(msg, args) 或者logging.error(msg,exec_info=true,args)(两者是等价的)。

  • 举个例子
import logging

def logger_traceback():
    logger = logging.getLogger('traceback')
    logger.setLevel(level=logging.INFO)
    handler = logging.FileHandler("trace_log.txt", encoding='utf-8')
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)

    console = logging.StreamHandler()
    console.setLevel(logging.INFO)

    logger.addHandler(handler)
    logger.addHandler(console)

    logger.info("开始记录日志信息!")
    logger.debug("调试程序!")
    logger.warning("出现某些异常!")
    try:
        open("destination.txt", "rb")
    except (SystemExit, KeyboardInterrupt):
        raise
    except Exception:
        logger.error("无法打开destination.txt", exc_info=True)

    logger.info("日记记录结束!")


if __name__ == '__main__':
    logger_traceback()

本文中的相关案例下载请戳这里

Reference:

  1. 一看就懂,Python 日志模块详解及应用
  2. 详解python之配置日志的几种方式
  3. logging模块中json和yaml文件的加载案例
  4. logging模块中自定义过滤器Filter
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值