背景:数据解析服务挂了,查看由于日志文件未设置定时清理机制,磁盘空间快满载了,于是手动清理一下日志文件重启服务,作为一名程序员怎么能够忍受时刻担心文件资源占用情况,于是动手开发一个日志定时处理工具。
日志收集与分析是运维过程中十分重要的内容,部署的定时运行或者长期运行的数据解析任务时,出现异常或错误的信息,一般查看日志记录来排查问题并解决Bug,为避免记录文件不断增长对服务器运行产生影响,有必要对存储日志进行定时清除或转存 。本文来介绍一下Python日志相关的处理库的使用方法与技巧!
日志记录是如何生成的?
本文介绍Python 的两个日志记录库:logging内置标准库 和loguru库。
- logging库采用的是模块化设计,可以设置不同的 handler来进行组合,但是在配置上通常较为繁琐;而且如果不是特别处理,在一些多线程或多进程的场景下使用 logging还会导致日志记录会出现错乱或是丢失的情况。
- loguru库能够减少繁琐的配置过程还能实现和 logging类似的功能,同时还能保证日志记录的线程进程安全(不用担心日志模块异常导致程序崩溃),又能够和 logging相兼容,并进一步追踪异常也能进行代码回溯。
一、logging模块来记录日志需求
logging.basicConfig(level=logging.DEBUG, #设置日志级别
format='%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s', #日志格式
datefmt='%Y-%m-%d %H:%M:%S', #时间格式
filename='./test.log', #指定文件位置
filemode='w') #指定写入方式
参数注释:
1)filename:指定的文件名创建FiledHandler,日志将存储到指定的文件夹中。
2)filemode:文件打开方式,默认a。
3)format:指定Handler使用的日志显示格式。常用参数如下:
%(asctime)s字符串形式的当前时间 ;
%(filename)s日志输出函数的模块的文件名;
%(lineno)d %调用日志输出函数的语句所在代码行;
%(levelname)s文本形式的日志级别;
%(message)s用户输出信息;
4)datefmt:指定日期时间格式。
5)level:设置rootlogger的日志级别。
logging模块配置详细介绍可参考官方文档。
实例1:将日志信息记录文件中
默认输出仅显示大于等于Warning级别日志
代码语言:javascript复制import logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s %(filename)s[line:%(lineno)d] %(levelname)s %(message)s", # 日志的格式
datefmt=" %Y-%m-%d %H:%M:%S", # 时间格式
filename="./test.log", # 指定文件位置
filemode="w",
) #日志信息记录到文件
logging.debug('debug message') #调试时信息打印
logging.info('info message') #正常信息记录
logging.warning('warning message') #发生警告信息,仍能正常工作
logging.error('error message') #发生错误,部分功能不正常
logging.critical('critical message') #严重错误,程序已崩溃
输出结果
代码语言:javascript复制2021-07-18 23:25:14 loggingdemo.py[line:9] DEBUG debug message
2021-07-18 23:25:14 loggingdemo.py[line:10] INFO info message
2021-07-18 23:25:14 loggingdemo.py[line:11] WARNING warning message
2021-07-18 23:25:14 loggingdemo.py[line:12] ERROR error message
2021-07-18 23:25:14 loggingdemo.py[line:13] CRITICAL critical message
实例2:日志记录到文件 IDE/终端显示
handler日志级别以logger日志级别为基础,低于INFO级别的如DEBUG调试信息均不会在handler中出现。
日志配置信息可直接调用配置文件:logging.config.fileConfig('logging.conf')
代码语言:javascript复制import logging
#logger:记录器,应用程序代码能直接使用的接口
#第一步:获取logger名称,名称自定义
logger = logging.getLogger("test2")
print(logger.name)
# 第二步:配置logger
# 1)设置logger的日志级别
logger.setLevel(logging.INFO)
#handler:处理器,将日志记录发送到合适的目的地
#2)设置logger输出位置,一次性可输出到不同位置,创建不同的handler,并分别设置日志级别
# 输出到终端
handler1 = logging.StreamHandler()
handler1.setLevel(logging.DEBUG)
# 输出到文件
handler2 = logging.FileHandler(filename="test2.log", mode="a", encoding="utf-8")
handler2.setLevel(logging.WARNING)
#formatter:格式化器,输出中日志记录的布局。
# 创建两个格式化器
formatter1 = logging.Formatter("%(asctime)s %(name)s %(levelname)s %(message)s")
formatter2 = logging.Formatter("%(asctime)s-%(name)s-%(levelname)s-%(message)s")
# 设置两个handler的格式化器
handler1.setFormatter(formatter1)
handler2.setFormatter(formatter2)
# 为logger添加两个handler
logger.addHandler(handler1)
logger.addHandler(handler2)
#第三步:程序中记录日志
logger.debug("debug message")
logger.info("info message")
logger.warning("warning message")
logger.error("error message")
logger.critical("critical message")
IDE/终端
代码语言:javascript复制2021-07-19 22:36:21,128 test2 INFO info message
2021-07-19 22:36:21,128 test2 WARNING warning message
2021-07-19 22:36:21,129 test2 ERROR error message
2021-07-19 22:36:21,129 test2 CRITICAL critical message
test2.log
代码语言:javascript复制2021-07-19 22:36:21,128-test2-WARNING-warning message
2021-07-19 22:36:21,129-test2-ERROR-error message
2021-07-19 22:36:21,129-test2-CRITICAL-critical message
实例3:日志滚动输出 IDE/终端显示 自定义文件大小
RotatingFileHandler:将日志文件记录到磁盘文件,可以设置每个日志文件的最大占用空间
代码语言:javascript复制import logging
import logging.handlers
def setup_logger(logger_name, level=logging.INFO):
mylog = logging.getLogger(logger_name)
mylog.setLevel(level)
# 日志格式
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
# IDE/终端输出
streamHandler = logging.StreamHandler()
streamHandler.setFormatter(formatter)
# 滚动文件输出
# 计算机中各种存储容量的单位都是用字节(Byte)来表示,此外还有KB、MB、GB和TB
# 1KB = 1024Bytes = 2的10次方Bytes
# 1MB = 1024KB = 2的20次方Bytes
# 1GB = 1024MB = 2的30次方Bytes
# 1TB = 1024GB = 2的40次方Bytes
# 写入文件,RotatingFileHandler设置每个日志文件的最大占用空间。maxBytes设置文件大小,backupCount设置保留文件个数。
rotatingHandler = logging.handlers.RotatingFileHandler('logs/mylog.log', maxBytes=5 * 1024 , backupCount=3)
rotatingHandler.setFormatter(formatter)
mylog.addHandler(streamHandler)
mylog.addHandler(rotatingHandler)
if __name__ == '__main__':
setup_logger('mylog')
mylog = logging.getLogger('mylog')
while True:
mylog.info("file test")
这里主要介绍了handler的StreamHandler和FileHandler子类,logging模块提供非常强大的其他子类,感兴趣的小伙伴可以尝试一下:
TimedRoatatingFileHandler:将日志文件记录到磁盘文件,按固定时间间隔来循环记录日志。
SMTPHandler:可以将日志发送到邮箱;
HTTPHander:使用Get或Post方法向HTTP服务器发送消息
二、loguru模块来记录日志需求
loguru 库已高度封装,logger 本身就是一个实例化对象,可直接调用 logger 类。只需导入使用即可。
代码语言:javascript复制#pipenv install loguru
from loguru import logger
当然,loguru 也可像 logging 一样可配置,但更简单化。
使用 add() 方法对 logger 进行简单的配置。
实例1:将日志信息记录文件中
在不指定任何参数时,logger 默认采用 sys.stderr 标准错误输出将日志输出到控制台(console)中;通常 linux 服务器上会以文件留存,添加字符串路径即可。
代码语言:javascript复制from loguru import logger
import os
logger.add(os.path.expanduser("testlog.log"))
logger.debug("debug message")
logger.info("info level message")
logger.warning("warning level message")
logger.critical("critical level message")
2021-07-19 23:51:01.808 | DEBUG | __main__:<module>:9 - debug message
2021-07-19 23:51:01.808 | INFO | __main__:<module>:10 - info level message
2021-07-19 23:51:01.808 | WARNING | __main__:<module>:11 - warning level message
2021-07-19 23:51:01.808 | CRITICAL | __main__:<module>:12 - critical level message
当你在 IDE 或终端里运行时,loguru 输出的日志信息带上了不同的颜色样式(schema),十分美观。
如何定期清洗日志文件?
一般数据开发平台已定制好日志管理体系,大型项目,一般通过集成的日志平台或数据库来对日志信息进行存储和留存,便于后续日志分析。一些中小型项目,通常只需要以文件形式留存日志。
一、以文件形式留存日志
需要考虑日志的留存、压缩,甚至定期清理,随着系统长时间运行,若向单个文件追加日志记录,当日志内容增长到一定数量级时,将影响外部读取、查找及分析。loguru模块可通过添加关键参数设置满足以上需求。
下面主要介绍3个关键参数:rotation 、compression 和 retention
1)rotation 参数:将日志记录以大小、时间等方式进行分割或划分:
2)compression 参数:压缩日志,传入压缩文件扩展名即可,如 zip、tar、gz等。只要满足 rotation
分割后的日志文件都被直接压缩成了 zip
文件。
import os
from loguru import logger
#指定日志文件存储文件夹
log_dir = os.path.expanduser(r"D:myProjectfilelogging_demologs")
#指定日志文件格式
log_file = os.path.join(LOG_DIR, "file_{time}.log")
#设置rotation参数,以固定文件大小100KB存储文件
logger.add(log_file, rotation = "100KB")
#追加compression参数,压缩文件
#logger.add(log_file, rotation = "100KB",compression="zip")
#日志记录
for n in range(10000):
logger.info(f"test - {n}")
3)retention参数:对超过一定时间的日志删除
代码语言:javascript复制#保持其他不变,只需更改一下代码
logger.add(LOG_FILE, rotation="100KB",retention=1)
当然对 retention 传入整数时,该参数表示的是所有文件的索引,而非要保留的文件数。所以最后我们会看到只有两个时间最近的日志文件会被保留下来,其他都被直接清理掉了。
若日志系统没有设置过滤器处理过期文件,可利用以下函数模块定期调用清理过期文件。
代码语言:javascript复制import os
import datetime
#os.walk返回的是一个3个元素的元组 (root, dirs, files) ,分别表示遍历的路径名,该路径下的目录列表和该路径下文件列表
for root,dirs,files in os.walk(r"F:AutoOps_platform日志文件logs",topdown=False):#循环D:works目录和子目录,该处指定目录即可
print(files)
for file in files:
print(file)
absPathFile=os.path.join(root,file)
print(absPathFile)
now=datetime.datetime.now()
modefiedTime = datetime.datetime.fromtimestamp(os.path.getmtime(absPathFile))
diffTime=now-modefiedTime
if diffTime.days>30:#条件筛选超过30天内的文件
print(f"{absPathFile:<27s}修改时间[{modefiedTime.strftime('%Y-%m-%d %H:%M:%S')}] 距今[{diffTime.days:3d}天{diffTime.seconds//3600:2d}时{diffTime.seconds600//60:2d}]")#打印相关信息
os.remove(absPathFile)
文件相关操作可参考以往推文:
Python内置的文件夹操作
Python内置十大文件操作
Python文件目录操作就是这么6
二、以数据库形式留存日志
首先需要通过 serialize 参数将其转化成序列化的 json 格式,导入非关系型数据库如 MongoDB中用作后续的日志分析。
代码语言:javascript复制from loguru import logger
import os
logger.add(os.path.expanduser(r"F:AutoOps_platform日志文件logstest.log"), serialize=True)
logger.info("test")
序列号记录如下:
代码语言:javascript复制{
"text": "2021-07-20 19:14:25.399 | INFO | __main__:<module>:22 - testn",
"record": {
"elapsed": {
"repr": "0:00:00.071955",
"seconds": 0.071955},
"exception": null,
"extra": {},
"file": {
"name": "log_file.py",
"path": "F:/AutoOps_platform/u65e5u5fd7u6587u4ef6/log_file.py"},
"function": "<module>",
"level": {
"icon": "u2139ufe0f",
"name": "INFO",
"no": 20},
"line": 22,
"message": "test",
"module": "log_file",
"name": "__main__",
"process": {
"id": 4712,
"name": "MainProcess"},
"thread": {
"id": 3288,
"name": "MainThread"},
"time": {
"repr": "2021-07-20 19:14:25.399480 08:00",
"timestamp": 1626779665.39948}}}
本文介绍了日志文件处理常用模块的使用方法,基本能够满足常规脚本开发需求。其他更高级姿势配置部分没有进一步展开,若对这个库感兴趣,建议深入阅读一下其官方文档,更好的运用到实际开发中。