「这是我参与2022首次更文挑战的第12天,活动详情查看:2022首次更文挑战」。
前言
接着上篇讲,开始logging模块的更多应用和封装。
渠道选择
开发在设计应用程序时,都会记录日志,不同的环境输出的级别也有不同,生产环境往往为了节约磁盘资源、提升读写性能,都不建议debug,一般都是info级别,有些甚至是error级别。还有写入文件的轮询,要知道服务是不停的在写日志,当一个日志文件大到一定级别,也不方便排查问题,所以一般选用两种策略:时间和容量,设置多久写下一个文件或日志容量到了就写下一个文件;至于控制台就没啥要求了,debug都可以,反正只是方便开发而已。
- 问题分析了,也测试了,方案也有选择了,接下来就走封装路线,当你看过源码之后,你又会发现,logging模块里面有很多代码没有封装,都定义在类外面;logging模块封装步骤:
import logging
# 创建一个日志收集器
log = logging.getLogger("myLog")
# 设置日志收集器等级
log.setLevel("DEBUG")
# 添加输出渠道
sh = logging.StreamHandler()
# 设置输出渠道日志级别
sh.setLevel("INFO")
# 绑定输出渠道到日志收集器
log.addHandler(sh)
# 设置日志内容格式
formatter = logging.Formatter('%(asctime)s - [%(filename)s -- line:%(lineno)d]--%(levelname)s : %(message)s')
# 设置渠道输出格式
sh.setFormatter(formatter)
# 测试日志代码,你会发现收集器是debug级别,输出级别为info;所以最终debug没有输出
log.debug("我是debug日志级别")
log.info("我是info日志级别")
log.warning("我是warning日志级别")
log.error("我是error日志级别")
log.critical("我是critical日志级别")
- 普通函数放在类里面,一般建议定义成静态方法,因为它跟类没有关系,只是习惯封装成类看着舒服- -!
import logging
class HandlerLogger():
@staticmethod
def logger():
# 创建一个日志收集器
log = logging.getLogger("myLog")
# 设置日志收集器等级
log.setLevel("DEBUG")
# 添加输出渠道:控制台
sh = logging.StreamHandler()
# 设置输出渠道日志级别
sh.setLevel("INFO")
# 绑定输出渠道到日志收集器
log.addHandler(sh)
# 添加输出渠道:文件
fh = logging.FileHandler("log.log",encoding="utf-8")
fh.setLevel("WARNING")
log.addHandler(fh)
# 设置日志内容格式
formatter = logging.Formatter('%(asctime)s - [%(filename)s -- line:%(lineno)d]--%(levelname)s : %(message)s')
# 设置渠道输出格式
sh.setFormatter(formatter)
fh.setFormatter(formatter)
return log
log = HandleLogger().logger()
# 测试日志代码,你会发现收集器是debug级别,输出级别为info;所以最终debug没有输出
log.debug("我是debug日志级别")
log.info("我是info日志级别")
log.warning("我是warning日志级别")
log.error("我是error日志级别")
log.critical("我是critical日志级别")
- 执行测试结果:测试收集了5级别,控制台输出4个级别,文件写入了3个级别:
扩展
再回到封装的日志模块,文件输出会带来上面提到过的问题,只写一个文件,内容越来越多,越往后写入速度会越慢,可能直接导致程序性能BUG。
- 介绍解决问题的两个办法之一:RotatingFileHandler;注意maxBytes,就是写入文件最大的容量(默认byte单位),满了就不写换下一个;backupCount保留文件个数;
class RotatingFileHandler(BaseRotatingHandler):
"""
Handler for logging to a set of files, which switches from one file
to the next when the current file reaches a certain size.
"""
def __init__(self, filename, mode='a', maxBytes=0, backupCount=0, encoding=None, delay=False):
"""
Open the specified file and use it as the stream for logging.
By default, the file grows indefinitely. You can specify particular
values of maxBytes and backupCount to allow the file to rollover at
a predetermined size.
Rollover occurs whenever the current log file is nearly maxBytes in
length. If backupCount is >= 1, the system will successively create
new files with the same pathname as the base file, but with extensions
".1", ".2" etc. appended to it. For example, with a backupCount of 5
and a base file name of "app.log", you would get "app.log",
"app.log.1", "app.log.2", ... through to "app.log.5". The file being
written to is always "app.log" - when it gets filled up, it is closed
and renamed to "app.log.1", and if files "app.log.1", "app.log.2" etc.
exist, then they are renamed to "app.log.2", "app.log.3" etc.
respectively.
If maxBytes is zero, rollover never occurs.
"""
pass
- 办法之二:TimedRotatingFileHandler;注意when和interval两个参数,一个是时间单位,一个是频率,即时间间隔;backupCount保留文件个数。
class TimedRotatingFileHandler(BaseRotatingHandler):
"""
Handler for logging to a file, rotating the log file at certain timed
intervals.
If backupCount is > 0, when rollover is done, no more than backupCount
files are kept - the oldest ones are deleted.
"""
def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False, atTime=None):
BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
pass
- 封装的模块修改就不再赘述了,直接将FileHandler替换即可。
小结
日志模块的应用,难的不是使用和封装,而是位置,在哪里需要记录什么信息,定义什么级别,不要把日志记得花里胡哨,失去它本身的意义,它的作用不要过度使用:所以日志一般都只做核心的、重要的操作,或者是容易出问题的操作,一般建议在捕获异常的代码块进行日志采集。
- 思考?
对于文件名、日志格式、日志级别,这些数据是否可以做成可配置的项?如何做?