Logging模块是Python标准库自带的日志模块,通过日志记录程序的运行情况是一个开发的好习惯,对于错误排查和系统运维都有很大帮助。内置模块提供了四大组件:
- 记录器(Logger):提供应用程序代码直接使用的接口。
- 处理器(Handler):将日志记录(由记录器创建)发送到适当的目的地。
- 筛选器(Filter):提供了更细粒度的功能,用于确定要输出的日志记录。
- 格式器(Formatter):程序在最终输出日志记录的内容格式。
logger = logging.getLogger()
需要定义多个日志器时,需要指定日志器名称,不然结果就演化成多份相同的日志器了(Handler叠加现象)
若没有指定记录器名称,默认为root
import logging
logger = logging.getLogger() # 记录器名称默认为root
logger1 = logging.getLogger() # 记录器名称默认为root
logger2 = logging.getLogger() # 记录器名称默认为root
# 设置三个处理器handler
console_handler = logging.StreamHandler()
console_handler1 = logging.StreamHandler()
console_handler2 = logging.StreamHandler()
# 给三个相同名称的logger添加上处理器;可以理解成给一个日志器加三个处理器,这一个日志器可以用logger、logger1、logger2任意一个表示
logger.addHandler(console_handler)
logger1.addHandler(console_handler1)
logger2.addHandler(console_handler2)
# 设置一下格式
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
console_handler1.setFormatter(formatter)
console_handler2.setFormatter(formatter)
# 输出日志记录
logger1.warning("输出一条日志记录")
"""
2023-06-22 09:28:50,102 - root - WARNING - 输出一条日志记录
2023-06-22 09:28:50,102 - root - WARNING - 输出一条日志记录
2023-06-22 09:28:50,102 - root - WARNING - 输出一条日志记录
"""
通常开发过程中,需要指定记录器名器,来规避handler叠加现象
logger = logging.getLogger(__name__) # 日志器名称为包名
logger1 = logging.getLogger("DIY") # 日志器名称为DIY
print(logger) #
print(logger1) #
处理器负责将日志消息发送到不同目标,例如:控制台,日志文件,电子邮件,等目标
处理器类型 | 描述 |
---|---|
StreamHandler |
将日志消息发送到流(默认是sys.stderr ),类似文件的对象 |
FileHandler |
将日志消息发送到磁盘文件 |
NullHandler |
禁止任何日志消息的处理 |
WatchedFileHandler |
将日志消息发送到文件,并监视文件的变化 |
SocketHandler |
将日志消息发送到网络套接字 |
DatagramHandler |
将日志消息作为UDP数据包发送到指定的主机和端口 |
SMTPHandler |
将日志消息通过电子邮件发送 |
SysLogHandler |
将日志消息发送到系统日志 |
NTEventLogHandler |
将日志消息发送到Windows NT/2000/XP的事件日志 |
MemoryHandler |
将日志消息缓存到内存中,并在达到指定容量时发送到其他处理器 |
RotatingFileHandler |
将日志消息发送到文件,并在文件大小达到一定限制时进行轮转 |
TimedRotatingFileHandler |
将日志消息发送到文件,并在时间间隔或文件大小达到一定限制时进行轮转 |
HTTPHandler |
将日志消息发送到HTTP服务器 |
QueueHandler |
将日志消息发送到队列中,供其他线程处理 |
QueueListener |
从队列中获取日志消息,并将其发送到其他处理器 |
日志记录发送到控制台中
用法
logging.StreamHandler(stream=None)
参数说明
日志记录发送到磁盘文件中
用法
logging.FileHandler(filename, mode='a', encoding=None, delay=False)
参数说明
不做任何事情的处理器,防止sys.stderr在没有日志记录配置的情况下将库的已记录事件输出
用法
logging.NullHandler()
该处理器旨在在Unix / Linux下使用,它监视文件以查看自上一次发出以来是否已更改。(如果文件的设备或索引节点已更改,则认为该文件已更改。)如果文件已更改,则关闭旧文件流,并打开该文件以获取新的流,不适合在Windows下使用
用法
logging.handlers.WatchedFileHandler(filename, mode='a', encoding=None, delay=False)
日志记录到文件中,且支持指定日志文件大小,备份文件数量
用法
logging.handlers.RotatingFileHandler(filename, mode='a', maxBytes=0,
backupCount=0, encoding=None, delay=False)
参数说明
filenam:要写入的日志文件名。
mode:文件打开模式,默认为追加模式a。
maxBytes:日志文件大小,单位为字节。当达到该大小限制时,将触发文件轮转。默认值为0,表示没有大小限制。
backupCount:备份文件数量。默认值为0,表示不保留备份文件。
假设backupCount设置为3,并且已经存在app.log、app.log.1、app.log.2、app.log.3四个文件。当app.log文件大小达到或超过maxBytes限制时,文件轮转会按照以下步骤进行:
app.log.3文件将被删除。
app.log.2将重命名为app.log.3。
app.log.1将重命名为app.log.2。
app.log将重命名为app.log.1。
创建一个新的空app.log文件用于继续记录日志消息。
现在,你将有app.log、app.log.1、app.log.2和app.log.3四个文件。旧备份文件的数字会递增,最新的日志总是记录在app.log文件中。
当app.log再次达到或超过maxBytes限制时,将重复这个轮转过程,删除最旧的备份文件(此时是app.log.3),重命名其他备份文件,并创建一个新的空app.log文件。
encoding:文件编码方式。默认为None,表示使用系统默认编码。
delay:是否在打开文件之前延迟。默认为False,表示立即打开文件。
日志记录到文件中,支持按时间间隔来更新日志
用法
logging.handlers.TimedRotatingFileHandler(filename, when='h', interval=1,
backupCount=0, encoding=None, delay=False, utc=False, atTime=None)
参数说明
filename
:要写入的日志文件名。when
:指定时间间隔的单位。默认为'h'
,表示按小时进行轮转。其他可选值有'S'
(秒)、'M'
(分钟)、'D'
(天)、'W0'
至'W6'
(每周的某天)、'midnight'
(每天凌晨)。interval
:时间间隔的数值。默认为1,表示每个时间间隔进行轮转。backupCount
:要保留的备份文件的数量。默认值为0,表示不保留备份文件。encoding
:文件编码方式。默认为None
,表示使用系统默认编码。delay
:是否在打开文件之前延迟。默认为False
,表示立即打开文件。utc
:是否使用UTC时间。默认为False
,表示使用本地时间。atTime
:指定在一天中的特定时间执行轮转。当when
设置为'midnight'
时,可以指定具体的时间,格式为'%H:%M:%S'
。用于对日志消息进行筛选和过滤,决定哪些日志消息应该被记录下来,哪些应该被忽略。过滤器可以被应用于日志记录器(
Logger
)或处理器(Handler
)。
官网给出的示例
import logging
from random import choice
class ContextFilter(logging.Filter):
"""
This is a filter which injects contextual information into the log.
Rather than use actual contextual information, we just use random
data in this demo.
"""
USERS = ['jim', 'fred', 'sheila']
IPS = ['123.231.231.123', '127.0.0.1', '192.168.0.1']
def filter(self, record):
record.ip = choice(ContextFilter.IPS)
record.user = choice(ContextFilter.USERS)
return True
if __name__ == '__main__':
levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL)
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)-15s %(name)-5s %(levelname)-8s IP: %(ip)-15s User: %(user)-8s %(message)s')
a1 = logging.getLogger('a.b.c')
a2 = logging.getLogger('d.e.f')
f = ContextFilter()
a1.addFilter(f)
a2.addFilter(f)
a1.debug('A debug message')
a1.info('An info message with %s', 'some parameters')
for x in range(10):
lvl = choice(levels)
lvlname = logging.getLevelName(lvl)
a2.log(lvl, 'A message at %s level with %d %s', lvlname, 2, 'parameters')
"""
2023-06-22 10:58:48,965 a.b.c DEBUG IP: 127.0.0.1 User: jim A debug message
2023-06-22 10:58:48,965 a.b.c INFO IP: 192.168.0.1 User: sheila An info message with some parameters
2023-06-22 10:58:48,965 d.e.f WARNING IP: 192.168.0.1 User: sheila A message at WARNING level with 2 parameters
2023-06-22 10:58:48,965 d.e.f DEBUG IP: 123.231.231.123 User: jim A message at DEBUG level with 2 parameters
2023-06-22 10:58:48,965 d.e.f DEBUG IP: 127.0.0.1 User: sheila A message at DEBUG level with 2 parameters
2023-06-22 10:58:48,965 d.e.f ERROR IP: 123.231.231.123 User: sheila A message at ERROR level with 2 parameters
2023-06-22 10:58:48,965 d.e.f INFO IP: 123.231.231.123 User: fred A message at INFO level with 2 parameters
2023-06-22 10:58:48,965 d.e.f INFO IP: 127.0.0.1 User: sheila A message at INFO level with 2 parameters
2023-06-22 10:58:48,965 d.e.f WARNING IP: 127.0.0.1 User: sheila A message at WARNING level with 2 parameters
2023-06-22 10:58:48,965 d.e.f DEBUG IP: 127.0.0.1 User: sheila A message at DEBUG level with 2 parameters
2023-06-22 10:58:48,965 d.e.f CRITICAL IP: 192.168.0.1 User: fred A message at CRITICAL level with 2 parameters
2023-06-22 10:58:48,965 d.e.f WARNING IP: 123.231.231.123 User: fred A message at WARNING level with 2 parameters
"""
格式器可以初始化日志记录的内容格式,结合LogRecord对象提供的属性,可以设置不同的日志格式
每个处理器只能有一个formatter,当给处理器多次设置formatter时,以最后一个为准
语法
logging.Formatter(fmt=None, datefmt=None, style='%')
参数说明
属性 | 描述 | 类型 | 格式化符号 |
---|---|---|---|
args | 用于格式化消息的参数 | 元组 | %(args)s |
asctime | 格式化的可读时间字符串 | 字符串 | %(asctime)s |
created | 记录的创建时间(自epoch开始的秒数) | 浮点数 | %(created)f |
exc_info | 异常信息(sys.exc_info的返回值) | 元组 | %(exc_info)s |
exc_text | 异常信息的字符串表示 | 字符串 | %(exc_text)s |
filename | 创建日志记录的源文件的文件名 | 字符串 | %(filename)s |
funcName | 创建日志记录的函数名 | 字符串 | %(funcName)s |
levelname | 日志级别名称 | 字符串 | %(levelname)s |
levelno | 日志级别的数值 | 整数 | %(levelno)s |
lineno | 创建日志记录的源文件的行号 | 整数 | %(lineno)d |
module | 创建日志记录的模块名 | 字符串 | %(module)s |
msg | 日志消息 | 字符串 | %(message)s |
msecs | 毫秒部分的时间戳 | 整数 | %(msecs)d |
name | 记录器名称 | 字符串 | %(name)s |
pathname | 创建日志记录的源文件的完整路径 | 字符串 | %(pathname)s |
process | 进程ID | 整数 | %(process)d |
processName | 进程名称 | 字符串 | %(processName)s |
relativeCreated | 记录相对于logging模块加载的时间(以毫秒为单位) | 浮点数 | %(relativeCreated)f |
stack_info | 栈信息字符串 | 字符串 | %(stack_info)s |
thread | 线程ID | 整数 | %(thread)d |
threadName | 线程名称 | 字符串 | %(threadName)s |
指定了日志等级后,只会显示大于等于所指定日志等级的日志信息!
logging中级别大小:DEBUG
日志等级(level) | 描述 | 数值 |
---|---|---|
0 | ||
DEBUG | 调试信息,通常在诊断问题的时候用 | 10 |
INFO | 普通信息,确认程序按照预期运行 | 20 |
WARNING(默认级别) | 警告信息,表示发生意想不到的事,或者指示接下来可能会出现一些问题,但是程序还是继续运行 | 30 |
ERROR | 错误信息,程序运行中出现了一些问题,程序某些功能不能执行 | 40 |
CRITICAL | 危险信息,一个严重的错误,导致程序无法继续运行 | 50 |
记录器设置的日志级别会作用于该记录器下所有的处理器
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
如果同时设置了记录器、处理器的日志级别,那么以最高的那个级别作为标准输出
import logging
logger = logging.getLogger(__name__)
# 设置两个处理器
console_handler = logging.StreamHandler()
console_handler1 = logging.StreamHandler()
# 设置处理器日志级别分别INFO,WARNING
console_handler.setLevel(logging.INFO)
console_handler1.setLevel(logging.WARNING)
logging模块提供5个日志级别的输出方法(debug、info、warning、erro、critical)
直接通过logging调用
import logging
logging.error("aaaaa")
通过创建记录器对象
import logging
logger = logging.getLogger('logger1') # 获取配置中的logger对象
# 输出logger日志记录
logger.error("====================【开始测试】====================")
语法
import logging
logger = logging.getLogger('logger1')
logger.debug(msg, *args, **kwargs)
参数
import logging
logger = logging.getLogger('logger1') # 获取配置中的logger对象
logger.setLevel(logging.INFO)
console_handler = logging.StreamHandler()
# console_handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
try:
open("notreal.txt", "rb")
except Exception:
logger.info("Faild to open notreal.txt from logger.debug", exc_info=True)
"""
2023-06-22 11:42:14,166 - logger1 - INFO - Faild to open notreal.txt from logger.debug
Traceback (most recent call last):
File "/Users/meta/GitHub/flaskProject/conf/log_conf.py", line 14, in
open("notreal.txt", "rb")
FileNotFoundError: [Errno 2] No such file or directory: 'notreal.txt'
"""
logger.info("====================【开始测试】====================", stack_info=True)
"""
2023-06-22 11:46:23,240 - logger1 - INFO - ====================【开始测试】====================
Stack (most recent call last):
File "/Users/meta/GitHub/flaskProject/conf/log_conf.py", line 19, in
logger.info("====================【开始测试】====================", stack_info=True)
"""
FORMAT = '%(asctime)-15s %(clientip)s %(user)-8s %(message)s'
logging.basicConfig(format=FORMAT)
d = {'clientip': '192.168.0.1', 'user': 'fbloggs'}
logger = logging.getLogger('tcpserver')
logger.warning('Protocol problem: %s', 'connection reset', extra=d)
"""
2023-06-22 11:48:19,350 192.168.0.1 fbloggs Protocol problem: connection reset
"""
logging模块提供一个配置方法,以字典为容器,设置日志器、处理器、格式器等参数,使用
logging.config.dictConfig(配置字典)
方法生成对应的元器件
方式一:
import logging.handlers
import logging.config
config = {
'version': 1, # 必填项,值只能为1
'disable_existing_loggers': True, # 选填,默认为True,将以向后兼容的方式启用旧行为,此行为是禁用任何现有的非根日志记录器,除非它们或它们的祖先在日志配置中显式命名。如果指定为False,则在进行此调用时存在的记录器将保持启用状态
'incremental': False, # 选填,默认为False,作用,为True时,logging完全忽略任何formatters和filters,仅处理handlers的level
'formatters': # 格式器配置专用key,在这里配置formatter,可配置复数formatter
{
'myformatter1': {
'class': 'logging.Formatter', # 必填,格式器对应的类
'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s', # fmt格式
'datefmt': '%Y-%m-%d %H:%M:%S' # 日期时间格式
},
# 'myformatter2': {
# '()': 'my_diy_formatter', # 将class改为(),代表不使用logging的类,使用我们重新定义的类
# 'format': '%(asctime)s - %(levelname)s - %(message)s', # fmt格式
# 'datefmt': '%Y-%m-%d %H:%M:%S' # 日期时间格式
# }
},
'handlers': # 处理器配置专用key,在这里配置handler,可配置复数handler
{
'console_handler': {
'class': 'logging.StreamHandler', # 必填,处理器对应的类
'level': logging.DEBUG, # 选填,处理器的日志级别,可填字符串'info',或者logging.INFO
'formatter': 'myformatter1', # 选填,这里要填写formatters字典中的键
},
'file_handler': {
'class': 'logging.handlers.RotatingFileHandler', # 必填,处理器对应的类
'level': logging.INFO, # 选填,处理器的日志级别,可填字符串'info',或者logging.INFO
'formatter': 'myformatter1', # 选填,这里要填写formatters字典中的键
'filename': './mylog.log', # filehandler特有参数,文件名
'maxBytes': 1024*1024, # 文件大小
'backupCount': 3, # 备份数量
'encoding': 'UTF-8', # 编码格式
}
},
'loggers': # 记录器配置专用key,在这里配置logger,可配置复数logger
{
'logger1': {
'handlers': ['console_handler', 'file_handler'], # 列表形式,元素填handlers字典中的handler
'level': logging.DEBUG, # 选填,记录器的日志级别,不填则默认Warning级别
'propagate': False, # 选填,为False时,禁止将日志消息传递给父级记录器
}
},
'root': # 根记录器专用key
{
'handlers': ['console_handler', 'file_handler'], # 列表形式,元素填handlers字典中的handler
'level': logging.DEBUG, # 选填,记录器的日志级别,不填则默认Warning级别
}
}
# 根据配置字典,配置对应元器件
logging.config.dictConfig(config)
if name == 'main':
logger = logging.getLogger('logger1') # 获取配置中的logger对象
# 输出logger日志记录
logger.debug("====================【开始测试】====================")
logger.info("====================【开始测试】====================")
logger.warning("====================【开始测试】====================")
logger.error("====================【开始测试】====================")
logger.critical("====================【开始测试】====================")
方式二
# json文件
{
"version": 1,
"disable_existing_loggers": true,
"incremental": false,
"formatters": {
"formatter1": {
"class": "logging.Formatter",
"format": "%(asctime)s - %(name)s - %(process)d - %(thread)d - %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S"
}
},
"handlers": {
"console_handler": {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "formatter1"
},
"info_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "INFO",
"formatter": "formatter1",
"filename": "{{ROOT_PATH}}/logs/info.log",
"maxBytes": 1048576,
"backupCount": 3,
"encoding": "UTF-8"
},
"warning_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "WARNING",
"formatter": "formatter1",
"filename": "{{ROOT_PATH}}/logs/warning.log",
"maxBytes": 1048576,
"backupCount": 3,
"encoding": "UTF-8"
},
"error_handler": {
"class": "logging.handlers.RotatingFileHandler",
"level": "ERROR",
"formatter": "formatter1",
"filename": "{{ROOT_PATH}}/logs/error.log",
"maxBytes": 1048576,
"backupCount": 3,
"encoding": "UTF-8"
}
},
"loggers": {
"log.debug": {
"handlers": [
"console_handler"
],
"level": "DEBUG",
"propagate": false
},
"log.info": {
"handlers": [
"info_handler"
],
"level": "INFO",
"propagate": false
},
"log.warning": {
"handlers": [
"warning_handler"
],
"level": "WARNING",
"propagate": false
},
"log.error": {
"handlers": [
"error_handler"
],
"level": "ERROR",
"propagate": false
}
},
"root": {
"handlers": [
"console_handler"
],
"level": "DEBUG"
}
}
# 自定义封装一个类
import json
import logging.config
from apps.src.utils.const import ROOT_PATH
with open(f"{ROOT_PATH}/conf/log.json", "r") as f:
log_json_str = f.read().replace("{{ROOT_PATH}}", ROOT_PATH)
log_json = json.loads(log_json_str) if log_json_str else {}
# 根据配置字典,配置对应元器件
logging.config.dictConfig(log_json)
_info_log = logging.getLogger("log.info")
_warning_log = logging.getLogger("log.warning")
_error_log = logging.getLogger("log.error")
_debug_log = logging.getLogger("log.debug")
class ilog(object):
info = _info_log.info
debug = _debug_log.debug
warning = _warning_log.warning
error = _error_log.error