文档目录
- logging模块
- 一、logging模块函数
- 1.1、**kwargs 参数
- 1.2、basicConfig()参数
- 1.3、format格式参数
- 二、logging日志系统
- 2.1、Logger 类
- 2.2、Handler类
- 2.3、Formater类
- 2.4、Filter类
- 2.5、流处理流程
- 2.6、日志重复记录
- 三、logging日志配置
- 3.2、配置文件
- 四、添加上下文
- 五、项目引用
- 5.1、日志封装
- 5.2、项目引入
logging模块
参考资料:https://docs.python.org/zh-cn/3/howto/logging.html#logging-basic-tutorial
logging模块是Python内置的标准模块,主要用于输出运行日志,可以设置输出日志的等级、日志保存路径、日志文件回滚等。
- 日志级别
级别 | levelno | 场景 |
DEBUG | 10 | 程序调试详细信息 |
INFO | 20 | 按照正常预期执行 |
WARNING | 30 | 告警信息 |
ERROR | 40 | 程序部分模块运行错误 |
CRITICAL | 50 | 整体严重错误,无法继续执行 |
日志等级是从上到下依次升高的,即:DEBUG < INFO < WARNING < ERROR < CRITICAL,而日志的信息量是依次减少的;
- 日志要素
1)发生事件
2)发生位置
3)日志级别
4)事件内容
命令行方式设置日志级别:
----log=INFO
logging模块的2种日志方式
A:通过 logging提供的模块级别的函数
B:使用Logging日志系统的四大组件其实,logging所提供的模块级别的日志记录函数也是对logging日志系统相关类的封装。
一、logging模块函数
logging.debug(msg, *args, **kwargs)
logging.info(msg, *args, **kwargs)
logging.warning(msg, *args, **kwargs)
logging.error(msg, *args, **kwargs)
logging.critical(msg, *args, **kwargs)
logging.log(level, *args, **kwargs)
logging.basicConfig(**kwargs)
- 简单实现
# coding=utf-8
"""
@DevTool : PyCharm
@Author : xxx
@DateTime : 2022/3/29 9:56
@FileName : test_09.py
"""
import logging
logging.debug("there is debug message!")
logging.info("there is info message!")
logging.warning("there is warning message!")
logging.error("there is error message!")
logging.critical("there is critical message!")
输出结果
D:\SoftWare\Python\python.exe E:/PythonProject/FileOperTest/test_09.py
WARNING:root:there is warning message!
ERROR:root:there is error message!
CRITICAL:root:there is critical message!
Process finished with exit code 0
备注:默认情况下 Python的logging模块只输出日志级别 大于等于 warning
的信息;
在代码开始位置新增如下代码:
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s: %(message)s", datefmt = "%Y-%m-%d %H:%M:%S")
输出结果:
D:\SoftWare\Python\python.exe E:/PythonProject/FileOperTest/test_09.py
2022-04-08 16:01:41 DEBUG: there is debug message!
2022-04-08 16:01:41 INFO: there is info message!
2022-04-08 16:01:41 WARNING: there is warning message!
2022-04-08 16:01:41 ERROR: there is error message!
2022-04-08 16:01:41 CRITICAL: there is critical message!
Process finished with exit code 0
1.1、**kwargs 参数
logging.debug()、logging.info()等方法的定义中,除了msg和args参数外,还有一个**kwargs参数,其支持3个关键字参数: exc_info, stack_info, extra,具体用法如下:
- exc_info
其值为布尔值,如果该参数的值设置为True,则会将异常异常信息添加到日志消息中。如果没有异常信息则添加None到日志信息中。
- stack_info
其值也为布尔值,默认值为False。如果该参数的值设置为True,栈信息将会被添加到日志信息中。
- extra
这是一个字典(dict)参数,它可以用来自定义消息格式中所包含的字段,但是它的key不能与logging模块定义的字段冲突。
具体用法
# coding=utf-8
"""
@DevTool : PyCharm
@Author : xxx
@DateTime : 2022/3/29 9:56
@FileName : test_09.py
"""
import logging
dict_extra={"user":"robot","ip":"99.99.99.99"}
logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(levelname)s {%(user)s-%(ip)s}: %(message)s", datefmt = "%Y-%m-%d %H:%M:%S")
logging.info("there is info message!",exc_info=True,stack_info=True,extra=dict_extra)
logging.warning("there is warning message!",exc_info=True,stack_info=False,extra=dict_extra)
logging.error("there is error message!",exc_info=False,stack_info=False,extra=dict_extra)
结果输出
D:\SoftWare\Python\python.exe E:/PythonProject/FileOperTest/test_09.py
2022-05-02 21:15:53 INFO {robot-99.99.99.99}: there is info message!
NoneType: None
Stack (most recent call last):
File "E:/PythonProject/FileOperTest/test_09.py", line 14, in <module>
logging.info("there is info message!",exc_info=True,stack_info=True,extra=dict_extra)
2022-05-02 21:15:53 WARNING {robot-99.99.99.99}: there is warning message!
NoneType: None
2022-05-02 21:15:53 ERROR {robot-99.99.99.99}: there is error message!
Process finished with exit code 0
1.2、basicConfig()参数
参数 | 描述 |
filename | 指定日志输出到的目标文件的文件名称 |
filemode | 指定日志文件的打开模式,默认为’a’;该选项要在filename指定时才有效; |
format | 指定日志格式字符串,即指定日志输出时所包含的字段信息以及顺序; |
datefmt | 指定日期/时间格式,该选项要在format中包含时间字段%(asctime)s时才有效; |
level | 指定日志器的日志级别; |
stream | 指定日志输出目标stream,如sys.stdout、sys.stderr以及网络stream;stream和filename不能同时指定,否则会引发 ValueError异常; |
style | 指定format格式字符串的风格,可取值为’%‘、’{‘和’$‘,默认为’%'; |
handlers | 这些handler将会被添加到root logger;注意 filename、stream和handlers这三个配置项只能有一个存在,否则会引发ValueError异常; |
1.3、format格式参数
属性 | 引用格式 | 描述 |
asctime | %(asctime)s | 记录日志发生时间,可以精确到毫秒 |
name | %(name)s | 日志器名称,默认是’root’,因为默认使用的是 rootLogger |
filename | %(filename)s | 调用日志输出函数的模块的文件名; pathname的文件名部分,包含文件后缀 |
funcName | %(funcName)s | 调用日志输出函数的函数名 |
levelname | %(levelname)s | 日志的最终等级(被filter修改后的) |
message | %(message)s | 日志信息, 日志记录的文本内容 |
lineno | %(lineno)d | 当前日志的行号, 调用日志输出函数的语句所在的代码行 |
levelno | %(levelno)s | 该日志记录的数字形式的日志级别(10, 20, 30, 40, 50) |
pathname | %(pathname)s | 完整路径 ,调用日志输出函数的模块的完整路径名,可能没有 |
process | %(process)s | 当前进程, 进程ID,可能没有 |
processName | %(processName)s | 进程名称,Python 3.1新增 |
thread | %(thread)s | 当前线程, 线程ID,可能没有 |
threadName | %(thread)s | 线程名称 |
module | %(module)s | 调用日志输出函数的模块名, filename的名称部分,不包含后缀即不包含文件后缀的文件名 |
created | %(created)f | 当前时间,用UNIX标准的表示时间的浮点数表示; 日志事件发生的时间–时间戳,就是当时调用time.time()函数返回的值 |
relativeCreated | %(relativeCreated)d | 输出日志信息时的,自Logger创建以 来的毫秒数; 日志事件发生的时间相对于logging模块加载时间的相对毫秒数 |
msecs | %(msecs)d | 日志事件发生事件的毫秒部分。logging.basicConfig()中用了参数datefmt,将会去掉asctime中产生的毫秒部分,可以用这个加上 |
到此为止,Python中简单的日志记录已经可以实现,不过对于更加复杂的场景日志记录则需要如下的日志系统来完成;
二、logging日志系统
logging日志模块四大组件
组件名称 | 对应类名 | 功能描述 |
日志器 | Logger | 提供了应用程序可一直使用的接口 |
处理器 | Handler | 将logger创建的日志记录发送到合适的目的输出 |
过滤器 | Filter | 提供了更细粒度的控制工具来决定输出哪条日志记录,丢弃哪条日志记录 |
格式器 | Formatter | 决定日志记录的最终输出格式 |
组件关系
- 日志器(logger)需要通过处理器(handler)将日志信息输出到目标位置,如:文件、sys.stdout、网络等;
- 不同的处理器(handler)可以将日志输出到不同的位置;
- 日志器(logger)可以设置多个处理器(handler)将同一条日志记录输出到不同的位置;
- 每个处理器(handler)都可以设置自己的过滤器(filter)实现日志过滤,从而只保留感兴趣的日志;
- 每个处理器(handler)都可以设置自己的格式器(formatter)实现同一条日志以不同的格式输出到不同的地方。
2.1、Logger 类
logger 对象
logging.getLogger([name])
返回一个logger对象,如果没有指定名字,则 name 的值为 “root”;若以相同的name参数值多次调用getLogger()方法,将会返回指向同一个logger对象的引用。
Logger对象最常用的方法分为两类:配置方法 和 消息发送方法;
常用配置方法:
方法 | 描述 |
Logger.setLevel() | 设置日志器将会处理的日志消息的最低严重级别 |
Logger.addHandler() 和 Logger.removeHandler() | 为该logger对象添加 和 移除一个handler对象 |
Logger.addFilter() 和 Logger.removeFilter() | 为该logger对象添加 和 移除一个filter对象 |
日志记录方法:
方法 | 描述 |
Logger.debug(), Logger.info(), Logger.warning(), Logger.error(), Logger.critical() | 创建一个与它们的方法名对应等级的日志记录 |
Logger.exception() | 创建一个类似于Logger.error()的日志消息 |
Logger.log() | 需要获取一个明确的日志level参数来创建一个日志记录 |
logger的层级结构和有效等级
- logger的名称是一个以".“分割的层级结构,每个”.“后面的logger都是”.”前面的logger的children;
- logger有一个"有效等级(effective level)"的概念:如果一个logger上没有被明确设置一个level,那么该logger就是使用它parent的level;如果它的parent也没有明确设置level则继续向上查找parent的parent的有效level,依次类推;
- child loggers在完成对日志消息的处理后,默认会将日志消息传递给与它们的祖先loggers相关的handlers。因此,我们不必为一个应用程序中所使用的所有loggers定义和配置handlers,只需要为一个顶层的logger配置handlers,然后按照需要创建child loggers就可足够了。我们也可以通过将一个logger的propagate属性设置为False来关闭这种传递机制。
2.2、Handler类
Handler对象的作用是(基于日志消息的level)将消息分发到handler指定的位置(文件、网络、邮件等)。Logger对象可以通过addHandler()方法为自己添加零个或者多个handler对象。
因为Handler是一个基类,它只定义了所有handlers都应该有的接口,所以无法直接实例化和使用Handler实例。
常用Handler
:
Handler | 描述 |
logging.StreamHandler | 将日志消息输出到Stream,如std.out, std.err或任何file-like对象。 |
logging.FileHandler | 将日志消息发送到磁盘文件,默认情况下文件大小会无限增长 |
logging.handlers.RotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按大小切割 |
logging.hanlders.TimedRotatingFileHandler | 将日志消息发送到磁盘文件,并支持日志文件按时间切割 |
logging.handlers.HTTPHandler | 将日志消息以GET或POST的方式发送给一个HTTP服务器 |
logging.handlers.SMTPHandler | 将日志消息发送给一个指定的email地址 |
logging.NullHandler | 该Handler实例会忽略error messages,通常被想使用logging的library开发者使用来避免’No handlers could be found for logger XXX’信息的出现。 |
handler常用方法
方法 | 描述 |
Handler.setLevel() | 设置handler将会处理的日志消息的最低严重级别 |
Handler.setFormatter() | 为handler设置一个格式器对象 |
Handler.addFilter() 和 Handler.removeFilter() | 为handler添加 和 删除一个过滤器对象 |
- RotatingFileHandler
类定义
class logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False, errors=None)
当前被写入的文件总是 app.log。
如果 maxBytes 或 backupCount 两者之一的值为零,日志文件不会发生轮换;
当 backupCount 为 5 而基本文件名为 app.log 时,你将得到 app.log、app.log.1、app.log.2 直至 app.log.5。
- TimedRotatingFileHandler
类定义
class logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None, errors=None)
日志配置
[handler_timedRotatingFileHandler]
class=logging.handlers.TimedRotatingFileHandler
level=INFO
formatter=commonFormatter
args=("logTrack.log","D",1,180,"utf-8")
说明:此处因为报错TimedRotatingFileHandler 没有定义,所以使用全路径来描述类名称;
2.3、Formater类
Formater对象用于配置日志信息的最终顺序、结构和内容。与logging.Handler基类不同的是,应用代码可以直接实例化Formatter类。
类构造方法:
logging.Formatter.__init__(fmt=None, datefmt=None, style='%')
一般直接用 logging.Formatter(fmt, datefmt)
对象获取
2.4、Filter类
Filter可以被Handler和Logger用来做比level更细粒度的、更复杂的过滤功能。Filter是一个过滤器基类,它只允许某个logger层级下的日志事件通过过滤。
2.5、流处理流程
1、创建一个根 logger对象;
2、设置根 logger对象的日志等级;
3、创建需要的Handler(FileHandler要有路径);
4、设置下每个Handler对象的日志等级;
5、创建下日志的fmt格式和 datefmt格式;
6、设置每个 Handler对象的 formatter 的格式
7、将每个 Handler对象添加到logger对象中;
8、打印输出logger.debug\logger.info\logger.warning\logger.error\logger.critical日志信息;
代码实现
功能描述:按照不同的日志级别,将日志信息分别记录到不同的日志文件;
# coding=utf-8
"""
@DevTool : PyCharm
@Author : xxx
@DateTime : 2022/5/2 0:34
@FileName : test_14.py
"""
import logging
logger = logging.getLogger("Tracker")
logger.setLevel(logging.DEBUG)
allHandler = logging.FileHandler("all_log.txt",mode="a+",encoding="utf-8")
allHandler.setLevel(logging.DEBUG)
warnHandler = logging.FileHandler("warn_log.txt",mode="a+",encoding="utf-8")
warnHandler.setLevel(logging.WARNING)
errorHandler = logging.FileHandler("error_log.txt",mode="a+",encoding="utf-8")
errorHandler.setLevel(logging.ERROR)
criticalHandler = logging.FileHandler("critical_log.txt",mode="a+",encoding="utf-8")
criticalHandler.setLevel(logging.CRITICAL)
formatter = logging.Formatter(fmt="%(asctime)s {%(name)s}-[%(levelname)s] %(message)s",datefmt = "%Y-%m-%d %H:%M:%S")
# set formatter
allHandler.setFormatter(formatter)
warnHandler.setFormatter(formatter)
errorHandler.setFormatter(formatter)
criticalHandler.setFormatter(formatter)
# add handlers
logger.addHandler(allHandler)
logger.addHandler(warnHandler)
logger.addHandler(errorHandler)
logger.addHandler(criticalHandler)
# write log messages
logger.debug("there is debug message!")
logger.info("there is info message!")
logger.warning('{} is {} years old.'.format('Tom', 10))
logger.error("there is error message!")
logger.critical("there is critical message!")
all_log.txt
2022-05-02 00:59:13 {Tracker}-[DEBUG] there is debug message!
2022-05-02 00:59:13 {Tracker}-[INFO] there is info message!
2022-05-02 00:59:13 {Tracker}-[WARNING] Tom is 10 years old.
2022-05-02 00:59:13 {Tracker}-[ERROR] there is error message!
2022-05-02 00:59:13 {Tracker}-[CRITICAL] there is critical message!
warn_log.txt
2022-05-02 00:59:13 {Tracker}-[WARNING] Tom is 10 years old.
2022-05-02 00:59:13 {Tracker}-[ERROR] there is error message!
2022-05-02 00:59:13 {Tracker}-[CRITICAL] there is critical message!
error_log.txt
2022-05-02 00:59:13 {Tracker}-[ERROR] there is error message!
2022-05-02 00:59:13 {Tracker}-[CRITICAL] there is critical message!
critical_log.txt
2022-05-02 00:59:13 {Tracker}-[CRITICAL] there is critical message!
2.6、日志重复记录
当重复调用一个 同名的 logger 时,将重复 添加相同 Handler,导致日志的重复记录;
解决方式,即在添加之前 判断 logger 的 handlers 是否为空;
实现代码
# coding=utf-8
"""
@DevTool : PyCharm
@Author : xxx
@DateTime : 2022/5/2 0:34
@FileName : test_14.py
"""
import logging
def getConfigedLogger():
logger = logging.getLogger("Tracker")
logger.setLevel(logging.DEBUG)
warnHandler = logging.FileHandler("warn_log.txt", mode="a+", encoding="utf-8")
warnHandler.setLevel(logging.WARNING)
errorHandler = logging.FileHandler("error_log.txt", mode="a+", encoding="utf-8")
errorHandler.setLevel(logging.ERROR)
formatter = logging.Formatter(fmt="%(asctime)s {%(name)s}-[%(levelname)s] %(message)s", datefmt="%Y-%m-%d %H:%M:%S")
# set formatter
warnHandler.setFormatter(formatter)
errorHandler.setFormatter(formatter)
if not logger.handlers:
logger.addHandler(warnHandler)
logger.addHandler(errorHandler)
return logger
realLogger = getConfigedLogger()
# write log messages
realLogger.debug("there is debug message!")
realLogger.info("there is info message!")
realLogger.warning('{} is {} years old.'.format('Tom', 10))
realLogger.error("there is error message!")
realLogger.critical("there is critical message!")
三、logging日志配置
Python日志常用的3种配置方式如下:
1、使用Python代码显式的创建loggers, handlers和formatters并分别调用它们的配置函数;
2、创建一个日志配置文件,然后使用fileConfig()函数来读取该文件的内容;
3、创建一个包含配置信息的dict,然后把它传递个dictConfig()函数;
3.2、配置文件
通过配置文件实现日志记录,实现了配置和代码的分离,同时开发者可以轻松修改日志记录属性;
- 配置文件
[loggers]
keys=root,tracker
[handlers]
keys=consoleHandler,fileHandler
[formatters]
keys=commonFormatter
[logger_root]
level=INFO
handlers=consoleHandler
[logger_tracker]
level=INFO
handlers=consoleHandler,fileHandler
qualname=tracker
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=INFO
formatter=commonFormatter
args=(sys.stdout,)
[handler_fileHandler]
class=FileHandler
level=INFO
formatter=commonFormatter
args=("logTrack.log","a+","utf-8")
[formatter_commonFormatter]
format=%(asctime)s -[%(name)s-%(levelname)s]: %(message)s
datefmt=%Y-%m-%d %H:%M:%S
当使用配置文件进行日志的记录时,有些关键规则:
0)在配置文件中,首先包含了三大主要模块,loggers、handlers、formatters。对于三个主要模块其包含的内容都是通过keys进行指定,然后通过logger_key/handler_key/formatter_key对里面的key进行具体的设置。
1)配置logger信息,必须包含一个名字叫做root的logger,当使用无参函数logging.getLogger()时,默认返回root这个logger;其他自定义logger可以通过 logging.getLogger(“loggername”) 方式进行调用;
2)对loggers中声明的logger进行逐个配置,且必须指定level和handlers这两个选项;对于非roothandler,还需要添加一些额外的option,其中qualname表示它在logger层级中的名字;
handlers可以指定多个,中间用逗号隔开,比如handlers=fileHandler,consoleHandler,同时制定使用控制台和文件输出日志;3)在handler中,必须指定class和args这两个option,常用的class包括 StreamHandler、FileHandler、RotaRotatingFileHandler;
args表示传递给class所指定的handler类初始化方法参数,它必须是一个元组(tuple)的形式,即便只有一个参数值也需要是一个元组的形式;里面指定输出路径,比如输出的文件名称等。
level与logger中的level一样,而formatter指定的是该处理器所使用的格式器,这里指定的格式器名称必须出现在formatters这个section中,且在配置文件中必须要有这个formatter的section定义;如果不指定formatter则该handler将会以消息本身作为日志消息进行记录,而不添加额外的时间、日志器名称等信息;
import logging
import logging.config
logging.config.fileConfig('logging.conf')
# create logger
logger = logging.getLogger('Tracker')
四、添加上下文
除了传递给日志记录函数的参数外,有时候需要日志输出中包含一些额外的上下文信息,如客户端的IP地址和用户信息等,实现方式如下:
- 通过向日志记录函数传递一个extra参数引入上下文信息
- 使用LoggerAdapters引入上下文信息
- 使用Filters引入上下文信息
五、项目引用
5.1、日志封装
5.2、项目引入
- 项目结构
项目根目录tree /f
显示目录结构
E:.
│ appprog.py
│
├─comTool
│ comFun.py
│
├─conf
│ logging.conf
│
├─logs
│ logTrack.log
│
└─__pycache__
logging.conf 日志配置文件内容:
[loggers]
keys=root,tracker
# 和上面部分一样
...
[handler_fileHandler]
class=FileHandler
level=INFO
formatter=commonFormatter
args=("./logs/logTrack.log","a+","utf-8")
[formatter_commonFormatter]
format=[%(asctime)s]-[%(name)s-%(levelname)s]-->[%(filename)s]: %(message)s
datefmt=%Y-%m-%d %H:%M:%S
主程序 appprog.py 内容如下:
# -*- coding= utf-8 -*-
"""
@DevTool : PyCharm
@Author : gzh
@DateTime : 2023/6/15 13:41
@FileName : appprog.py
"""
import logging
import logging.config
from comTool import comFun
if __name__ == "__main__":
print("the main app is running!")
logging.config.fileConfig('./conf/logging.conf')
logger = logging.getLogger("tracker")
logger.info("the main app is running!")
comFun.recordTest()
公共程序 comFun.py 内容如下:
# -*- coding= utf-8 -*-
"""
@DevTool : PyCharm
@Author : gzh
@DateTime : 2023/6/15 14:12
@FileName : comFun.py
"""
import logging
def recordTest():
print("current step is in function recordTest!")
logger = logging.getLogger("tracker")
logger.info("current step is in function recordTest!")
在公共程序中,logger 可以直接使用在主程序配置的;
控制台输出内容:
the main app is running!
[2023-06-15 14:48:58]-[tracker-INFO]-->[appprog.py]: the main app is running!
current step is in function recordTest!
[2023-06-15 14:48:58]-[tracker-INFO]-->[comFun.py]: current step is in function recordTest!
Process finished with exit code 0
日志输出内容:
[2023-06-15 14:40:43]-[tracker-INFO]-->[appprog.py]: the main app is running!
[2023-06-15 14:40:43]-[tracker-INFO]-->[comTool.py]: current step is in function recordTest!
================================ over ========================================