前言
做项目的时候,将代码的输出结果以日志的方式保存下来,是很有必要的
在python中,内置了一个日志模块logging,用来输出日志信息,我们可以进行配置各种参数,从而满足我们大部分场景对日志的需求
简单实现
1.1 日志等级
首先日志输出的信息是分等级的,毕竟大家都希望在看日志的时候能快速的定位到自己要关注的信息吧
DEBUG:debug级别,一般用来打印一些调试信息,级别最低
INFO:info级别,一般用来打印一些正常的操作信息
WARNING:waring级别,一般用来打印警告信息
ERROR:error级别,一般用来打印一些错误信息
CRITICAL:critical级别,一般用来打印一些致命的错误信息,等级最高
使用方法,就像写print('xxxx')一样,只不过会有不一样的等级而已:
import logging
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG) #配置日志级别
logging.debug('debug级别,一般用来打印一些调试信息,级别最低')
logging.info('info级别,一般用来打印一些正常的操作信息')
logging.warning('waring级别,一般用来打印警告信息')
logging.error('error级别,一般用来打印一些错误信息')
logging.critical('critical级别,一般用来打印一些致命的错误信息,等级最高')
1.2输出格式
上面输出的内容挺简略的,难道这你就满足了?当你的程序日夜运行的时候,怎么去定位到那个时间点呢?运行到那个函数呢?(谁也不敢保证你写的函数里面就没有重复的操作,尤其是当数量多的时候)
尤其是当你进行多线程、多进程的时候,你又要如何定位到相应的进程或者线程呢,诶,所以它来了
level是设置日志的格式,那么format 可以指定日志输出的内容和格式,其内置的参数如下:
%(name)s:Logger的名字
%(levelno)s:打印日志级别的数值
%(levelname)s:打印日志级别的名称
%(pathname)s:打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s:打印当前执行程序名
%(funcName)s:打印日志的当前函数
%(lineno)d:打印日志的当前行号
%(asctime)s:打印日志的时间
%(thread)d:打印线程ID
%(threadName)s:打印线程名称
%(process)d:打印进程ID
%(message)s:打印日志信息
按自己的需要输入就好啦
例如我的
logging.basicConfig(filename=f'{floder_path}/{name}.log', level=logging.INFO,format='%(asctime)s %(funcName)s %(threadName)s %(message)s')
info_logger = logging.getLogger() # 获取Logger对象
输出了时间、函数名、线程名、以及自己print的内容
1.3 输出日志到文件
看了那么久,其实我们只是把日志输出到控制台而已,谁家好人看日志文件是放到控制台去看的啊,一般是看文件的啦,这样程序出现问题时,也可以方便我们根据日志信息进行定位。
最简单的方式是在basicConfig中配置filename和filemode:
其中参数 filemode表示文件打开模式,不设的话默认为’a’,即追加模式,可以不设;也可以设为’w’,每次写日志会覆盖之前的日志。
这样就可以把日志输出到你指定的文件夹指定的名字的文件中去了,如果没有这个文件的话会自动创建。
logging.basicConfig(filename=f'{floder_path}/output_{year}_{month}_{day}.log', level=logging.INFO,
format='%(asctime)s %(funcName)s %(thread)d %(threadName)s %(message)s')
info_logger = logging.getLogger() # 获取Logger对象
例如我在某个路径创建了一个output_某年_某月_某日的文件名字了
那么问题来了,这样子写,它是不会每天产生一个新文件的,你日志文件会越来越大的,自己写个程序去实现它?其实人家也帮我们弄好了
进阶
2.1模块化设计
logging 库采取了模块化的设计,提供了许多组件:记录器、处理器、过滤器和格式化器。
Logger 暴露了应用程序代码能直接使用的接口,用于记录日志。
Handler 将(记录器产生的)日志记录发送至合适的目的地(控制台、文件等)。
Filter 提供了更好的粒度控制,它可以决定输出哪些日志记录。
Formatter 指明了最终输出中日志记录的内容和格式。
简单地说,其中 Logger 是负责记录日志消息的,然后我们要把这些日志消息放到哪里,交给 Handler 处理,Filter 则帮我们过滤信息(不限于通过级别过滤),Formatter 就是跟上面的 format 一个意思,用来设置日志内容和格式。
有点懵?没关系,看代码
啊,对了
Handler的种类有很多,常用的有4种:
logging.StreamHandler -> 控制台输出
logging.FileHandler -> 文件输出
logging.handlers.RotatingFileHandler -> 按照大小自动分割日志文件,一旦达到指定的大小重新生成文件
logging.handlers.TimedRotatingFileHandler -> 按照时间自动分割日志文件
到时候我们通过RotatingFileHandler 实现每天产生新日志
代码来了
import logging
from logging.handlers import TimedRotatingFileHandler
logger = logging.getLogger() # 获取Logger对象
logger.setLevel(logging.INFO) #设置等级
handler = TimedRotatingFileHandler(f'{floder_path}/output', when="midnight", interval=1)
handler.suffix = "_%Y_%m_%d.log" #设置的后缀名 例如我的是output 那分割后就会输出output._年_月_日.log
formatter = logging.Formatter('%(asctime)s %(funcName)s %(threadName)s %(message)s') #设置输出格式
handler.setFormatter(formatter)
logger.addHandler(handler)
如果大家想输出到分秒 那么就是
handler.suffix = "_%Y-%m-%d_%H-%M.log"
忘了给大家介绍TimedRotatingFileHandler了
2.2 TimedRotatingFileHandler
输出名字就不介绍了,大家把目光看向 when参数
when是一个字符串用于描述滚动周期的基本单位,字符串的值及意义如下:
'S': Seconds 秒
'M': Minutes 分
'H': Hours 时
'D': Days 天
'W': Week day (0=Monday) 星期
'midnight': Roll over at midnight 午夜
猜猜为什么会有midnight?
那是因为其他的秒、分、小时、天、周都是根据程序启动的时间计算的
然后是interval、backupCount
interval: 滚动周期,单位有when指定,比如:when='D',interval=1,表示每天产生一个日志文件;
backupCount: 表示日志文件的保留个数 ,例如你选when='D',interval=1 每天产生一个文件,而你backupCount设置了时, 表示保留日志文件的个数,超过了就删
给大家写了个例子,拿去玩玩,按分钟产生一个文件,那去验证验证
def logger_init():
import logging
from logging.handlers import TimedRotatingFileHandler
txt_path = ''
logger = logging.getLogger() # 获取Logger对象
logger.setLevel(logging.INFO)
handler = TimedRotatingFileHandler(f'{txt_path}/output', when="M", interval=1)
handler.suffix = "_%Y-%m-%d_%H-%M.log"
formatter = logging.Formatter('%(asctime)s %(funcName)s %(threadName)s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger