日志管理模块——logging模块,是Python的内置模块。
1、日志级别
import logging
logging.debug('调试日志')
logging.info('消息日志')
logging.warning('警告日志')
logging.error('错误日志')
logging.critical('严重错误日志')
运行结果:
默认的日志输出级别是WARNING,只打印WARNING级别以上的日志,WARNING级别以下的INFO和DEBUG日志未输出。
root是日志的名字,可更改。
2、日志基本配置
import logging
# 日志基本配置
logging.basicConfig(
# 1、日志级别
level=10,
# DEBUG 10
# INFO 20
# WARNING 30
# ERROR 40
# CRITICAL 50
# 2、日志输出格式
# asctime 获取当前时间;name 当前日志名字;pathname 指的是哪一个文件产生的日志;lineno 指的是这个文件的哪一行产生的这个日志;
# levelname 日志等级;message 日志的具体内容
# 输出格式:2022-12-14 14:05:22,997 root [E:/practice/日志管理.py line:26 DEBUG 调试日志]
# 997是毫秒,若不想要,可通过设置asctime的时间格式来更改
format='%(asctime)s %(name)s [%(pathname)s line:%(lineno)d %(levelname)s %(message)s]',
# 3、asctime的时间格式
datefmt='%Y-%m-%d %H:%M:%S',
# 4、日志输出位置:终端/文件
filename='user.log', # 不指定此配置,默认打印到终端;指定后会在当前路径下产生一个user.log文件,日志输出到该文件中,不会打印到终端
)
'''
%(name)s Logger的名字(getlogger时指定的名字)
%(levelno)s 数字形式的日志级别
%(levelname)s 文本形式的日志级别
%(pathname)s 调用日志输出日志的完整路径名
%(filename)s 调用日志输出日志的文件名
%(module)s 调用日志输出日志的模块名
%(funcName)s 调用日志输出日志的函数名
%(lineno)d 调用日志输出函数的语句所在的代码行
%(created)f 当前时间,用UNIX标准的表示时间的浮点数表示
%(relativeCreated)d 输出日志信息时的自Logger创建以来的毫秒数
%(asctime)s 字符串形式的当前时间,默认格式是”2022-12-24 15:37:53,394“
%(thread)d 线程ID,可能没有
%(threadName)s 线程名,可能没有
%(process)d 进程ID,可能没有
%(message)s 用户输出的信息
'''
# 日志级别
logging.debug('调试日志')
logging.info('消息日志')
logging.warning('警告日志')
logging.error('错误日志')
logging.critical('严重错误日志')
在PyCharm中打开user.log文件,出现乱码。–写入日志时没有指定字符编码,日志基本配置logging.basicConfig()无法设置字符编码。未指定字符编码,默认使用Windows系统的编码方式gbk,而PyCharm默认是用utf-8打开的,所以会乱码。
3、日志配置字典
日志配置字典可解决:
1、字符编码问题
2、把日志信息既往文件里面写,同时还在终端输出一份
把logging模块所需要的各方面配置都写在配置字典里,通过加载这个字典,让logging模块使用这里面的配置项。
'''
logging模块有三个比较重要的功能组件:
1、loggers 配置文件可定义一些输出日志的appname
2、handler 配置日志的分隔大小、输出位置、日志文件创建等
3、formatters 配置日志输出的格式
'''
LOGGING_DIC ={
'version': 1.0,
'disable_existing_loggers': False,
# 日志格式
'formatters': {
'standard': {
'format': '%(asctime)s %(threadName)s:%(thread)d [%(name)s] %(levelname)s [%(pathname)s:line:%(lineno)d] %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
},
'simple': {
'format': '%(asctime)s [%(name)s] %(levelname)s %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S',
},
'test': {
'format': '%(asctime)s %(message)s',
},
},
'filters': {},
# 日志处理器
# logger负责生产不同级别的日志,然后把日志丢给handler。而handler就是把logger生产的日志进行处理,是输出到控制台还是写入到文件,是写入到文件a还是写入到文件b,都是由handler控制。
# 可设置多个handler,不同的handler做不同的处理。
'handlers': {
# 把日志输出到终端,同时日志等级是debug
'console_debug_handler': {
'level': 'WARNING', # 日志处理的级别限制
# 'level': 10,
'class': 'logging.StreamHandler', # 输出到终端
'formatter': 'simple' # 日志格式
},
# 'file_info_handler': {},
'file_debug_handler': {
'level': 'DEBUG',
'class': 'logging.FileHandler', # 保存到文件
'filename': 'text.log', # 日志存放的路径
'encoding': 'utf-8', # 日志文件的编码
'formatter': 'test',
},
'file_deal_handler': {
'level': 'INFO',
'class': 'logging.FileHandler', # 保存到文件
'filename': 'deal.log', # 日志存放的路径
'encoding': 'utf-8', # 日志文件的编码
'formatter': 'standard',
},
'file_operate_handler': {
'level': 'INFO',
'class': 'logging.FileHandler', # 保存到文件
'filename': 'operate.log', # 日志存放的路径
'encoding': 'utf-8', # 日志文件的编码
'formatter': 'standard',
},
},
# 日志记录器
'loggers': {
'logger1': {
'handlers': ['console_debug_handler'], # 日志分配到哪个handelrs中
'level': 'INFO', # 日志记录的级别限制
'propagate': False # 默认为True,向上(更高级别的logger)传递,设置为False即可,否则会一份日志向上层层传递
},
# 'logger2': {
# 'handlers': ['console_debug_handler', 'file_debug_handler'],
# 'level': 'INFO',
# 'propagate': False,
# },
'logger3': {
'handlers': ['console_debug_handler', 'file_deal_handler'],
'level': 'INFO',
'propagate': False,
},
'logger4': {
'handlers': ['console_debug_handler', 'file_operate_handler'],
'level': 'INFO',
'propagate': False,
},
},
}
4、日志使用
logging本身是一个包,而logging下面有一个子包叫config,不能直接logging.config
。
上图原因是:logging模块的设计者没有把config这个名字导入到logging的__init__.py里面。因为导logging导的就是logging下面的__init__.py。
可通过from logging import config
,不但可以找logging的__init__里面的名字,还可以找logging文件夹里的名字。
# import logging
# from logging import config
import logging.config
import setting
logging.config.dictConfig(setting.LOGGING_DIC)
logger1 = logging.getLogger('logger3')
logger1.info('xxx充值了5毛钱')
logger2 = logging.getLogger('logger4')
logger2.info('xxx登录了')
'''
注释掉import logging,将无法使用logging下面的功能。from logging import config导入的是config,logging根本没导入。
from logging import config,虽然logging没导入,但是logging下面的init文件也是运行过的。
因为from logging这种方式,会先检索logging的init里面有没有config,要检索init就一定会执行一遍init。如果init里面没有config,才会在logging文件夹里找。
虽然执行了logging下面的init,但是用不了logging下面的功能。因为只是执行了init,并没有导入logging,也就是并没有导入logging下的init。
import logging.config和from logging import config一样,同样也会先执行logging下的init,看看有没有config这个名字,如果没有再找logging这个文件夹下面的模块名。但和from logging import config不同的是,import logging.config会导入logging。
'''
5、日志轮转
由于程序产生的日志,记录的是程序运行过程中的一些关键信息,这些信息是不能够轻易删除的,有可能这个日志很多年都没有用过,也不能去删除它,因为不知道哪天哪个用户会出问题,有可能前脚刚把日志删了,后脚用户就来找麻烦了。所以日志一定不能随便删。
但是随着软件生命周期的不断拉长,日志文件可能会非常大。如果日志文件过大,打开文件查看时就会非常卡,所以不应该一直把日志往同一个文件里写。
当日志文件内容增长到一定量时,就把它里面原有的内容给剪切到另外一个文件里存着。
日志轮转:当某个日志文件deal.log增长到一定量时,就把这个文件重命名一下,下次再产生新日志时,就又会重新创建一个deal.log。
# 日志轮转
'file_info_handler': {
'level': 'INFO',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件
'filename': 'deal.log',
# 'maxBytes': 1024*1024*10, # 日志文件大小 10M 当日志文件达到多大时,就进行日志轮转 默认单位是字节
'maxBytes': 800,
'backupCount': 3, # 日志文件保存数量限制
'encoding': 'utf-8',
'formatter': 'standard',
},
报错:PermissionError: [WinError 32] 另一个程序正在使用此文件,进程无法访问。: ‘E:\practice\deal.log’ -> ‘E:\practice\deal.log.1’
程序在把deal.log重命名为deal.log.1时,deal.log被另一个程序占用着。
解决办法:
在日志轮转fiel_into_hanlder中处理的文件是deal.log,在file_deal_handler中处理的文件也是deal.log,即便没有logger在调用file_deal_handler,也会把这个文件给占用着。
deal.log被占用的情况下不能够重命名。进行日志轮转的文件必须是独一无二的,不能够和其他handler共用。