编写 Python 程序,经常需要在控制台打印输出,来了解程序的实际执行情况。
常用的打印输出方法 print(),在项目内使用,有时就不那么方便:
1. 调试阶段使用 print,发布时需要注释或删除不必要的打印信息,无法过滤;
2. 部分应用程序后台执行,没有控制台实时显示,就需要保存日志到文件;
3. 如果需要格式化输入日志,则需要重复编写格式代码。
所以 Python 标准库出现了更加灵活、功能丰富的 logging 模块,该模块定义函数和类,为应用程序和库实现了灵活的事件日志记录系统。
logging 模块化设计,主要包含四种组件:
- Loggers:记录器,提供应用程序代码能直接访问使用的接口;
- Handlers:处理器,将记录器产生的日志发送至目的地;
- Filters:过滤器,提供更好的粒度控制,决定哪些日志将会被输出;
- Formatters:格式化器,设置日志内容的组成结构和消息字段。
工作原理
创建 logger 就相当于创建了一支虚拟的笔,可以创建多个,创建时需要设置一个默认输出等级,仅输出 ≥ 默认日志等级的信息; 指定 Handler 控制 这个笔的输出目的地,可以只写入控制台,也可以只写入文件内,还可以同时在控制台与文件内都写,等等,根据业务自己定义,可以分别设置日志等级; 创建 Formatter 设置日志输出的结构、格式,供处理器使用;处理器的 Handler 日志经过 Formatter 渲染后,绑定到记录器 logger 内; 程序调用 logger 此时程序就可以根据需求去调用不同的 logger,比如仅输出控制台、写入日志等等。 logger 记录器 logger = logging.getLogger(name) 通过 logging.getLogger(name) 函数实例化,用来创建记录器;多次调用 getLogger() 相同名称将始终返回对同一 Logger 对象的引用,也就是说 logger 是单例的。 logger.setLevel(level) 设置日志默认级别,如果不设置,默认级别为 warning logger.addHandler() | logger.removeHandler() 记录器可以绑定或移除不同的处理器,从而实现各种功能。比如:logger_a 仅输出到控制台;logger_b 仅把日志写入文件;logger_c 输入到控制台的同时写入文件;logger_d 发送邮件给指定用户等等。 Handlers 处理器 将日志分发到不同的目的地,可以是文件、标准输出、邮件或者通过 socke、http 等协议发送到任何地方。 sh = logging.StreamHandler(stream=None) 标准输出 stdout fh = logging.FileHandler( filename, mode='a', encoding=None, delay=False ) 将日志保存到磁盘文件,mode 代表写入模式,‘a’ 为追加 sh.setFormatter(color_formatter) fh.setFormatter(formatter) 设置 formatter,定义日志输出结构、格式,一个处理器仅能绑定一个 formattor。这里列出的只是常用的两个处理器,还有其他一些 ,比如:RotatingFileHandler:按照文件大小,生成多个日志文件;
TimeRotatingFileHandler:按照时间,每天一个新文件等;
SMTPHandler:日志发送邮箱;
...
Filters 过滤器 filter = logging.Filter("test") logger.addFilter(filter) 上面代码设置过滤器内的记录器为 “test”,则仅输出 “test” 记录器的日志; 如果没有创建过这个记录器,就不会输出任何日志;也可以对处理器设置过滤器,通过 addFilter 关联生效。 日志级别 系统默认提供了 6 个日志级别,对应不同使用场景,如下:
# logging.py __init__ line:89
CRITICAL = 50
FATAL = CRITICAL # 致命错误,程序崩溃
ERROR = 40 # 程序错误,部分功能不能使用
WARNING = 30 # 警告信息,可能出现一些错误
WARN = WARNING
INFO = 20 # 程序正常运行时输出信息
DEBUG = 10 # 程序实现过程中调试信息
NOTSET = 0
另外,记录器和处理器可以分别选择设置默认日志级别,逻辑如下:
如果没有给处理器指定日志级别,将使用记录器的日志级别;
如果没有给记录器指定日志级别,将使用默认 warning 级别;
如果记录器指定日志级别大于处理器指定日志级别,将以记录器日志级别为准;
如果记录器指定日志级别小于处理器指定日志级别,将以处理器日志级别为准。
import os
import logging
import colorlog
class Logger(object):
"""初始化日志记录器
usage:
# 默认记录器是 root,会输出到控制台且写入到日志文件 output.log
>>> logger1 = Logger().logger
>>> logger1.debug("this is debug message")
>>> import logging
>>> logging.warning("this is warning message")
# 创建记录器 test,会输出到控制台且写入到日志文件 test.log
# 可以设置 level 级别
>>> logger2 = Logger("test", level=logging.INFO).logger
>>> logger2.error("this is error message")
# 仅在控制台输出
>>> logger3 = Logger(Logger.CONSOLE).logger
>>> logger3.info("this is info message")
"""
CONSOLE = "console"
__colors_config = {
'DEBUG': 'white', # cyan white
'INFO': 'bold_blue',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'bold_red',
}
__date_fmt = "%y%m%d %H:%M:%S"
__fmt = "[ %(levelname)1.1s %(asctime)s %(module)s:%(lineno)d ] %(message)s"
__color_fmt = '%(log_color)s[ %(levelname)1.1s %(asctime)s %(module)s:%(lineno)d ]%(black)s %(message)s'
__filepath = os.path.dirname(os.path.dirname(__file__)) + "/"
def __init__(self, filename=None, level=logging.DEBUG):
# 1. 创建记录器
self.logger = logging.getLogger(filename) # 不填则为 root
self.logger.setLevel(level)
# 定义输出格式
formatter = logging.Formatter(self.__fmt, datefmt=self.__date_fmt)
color_formatter = colorlog.ColoredFormatter(
fmt=self.__color_fmt,
datefmt=self.__date_fmt,
log_colors=self.__colors_config
)
# 2. 创建处理器并关联输出格式
# 如果没有给处理器指定日志级别,将使用记录器的日志级别
# 如果没有给记录器指定日志级别,那么会使用默认「warning」级别,处理器此时设置「debug」或「info」都不会输出
# 2.1 创建 输出到控制台 处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(color_formatter)
# 2.2 创建 输出到文件 处理器
file_handler = logging.FileHandler(
filename=self.__filepath + "output.log"
if filename is None or Logger.CONSOLE else self.__filepath + filename + ".log"
)
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(formatter)
# 3. 记录器关联处理器
if filename == "console":
self.logger.addHandler(console_handler)
else:
self.logger.addHandler(console_handler)
self.logger.addHandler(file_handler)
# 4. 过滤器 -- 仅输出设置的记录器下的日志,也可以对处理器进行设置过滤器
# 这里设置为 test,就不会再输出日志了,因为只输出名为 "test" 记录器的日志
# filter_app = logging.Filter("test")
# self.logger.addFil(filter_app) # 关联过滤器
if __name__ == '__main__':
# logging(默认记录器 root) 默认输出
logging.info("this is default logging warning message")
# 修改默认记录器 root,格式化输出
logger1 = Logger().logger
logger1.info("this is logger(root) info message")
# 创建 console 记录器,仅输出到控制台
logger2 = Logger(Logger.CONSOLE).logger
logger2.error("this is logger(console) error message")
# 创建 console 记录器,仅输出 ERROR 级别之上的日志
logger4 = Logger(Logger.CONSOLE, level=logging.ERROR).logger
logger4.info("logger4 > test info,You can't see me")
logger4.error("logger4 > test error")
注意,我的测试代码,创建了多个记录器,当同时运行时,会发现有的日志多次打印,这是由于 logger 对象是有父子关系的。
以 logger1 和 logger2 为例:如果创建 console 记录器,则它的父对象就是 root,上面由于也创建了 root 记录器,所以父对象会同时收到日志,造成 console 的 logger 对象打印两遍日志。
为了控制台的日志更美观、易于分辨,我加入了定义日志颜色的 formattor,用到了 colorlog 模块,效果如图:
以上就是关于 logging 模块,我的应用与理解,感谢阅读。