暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

两个Python日志文件的记录库

AI有温度 2021-10-27
1373

背景:由于日志文件未设置定时清理机制导致磁盘满载、或因服务器运行程序过多导致数据解析服务挂掉,但你竟然不知道数据处理进度如何、出现了哪些Bug,是不是会很着急?本文将介绍如何写入与清除日志文件。

 日志记录如何生成?

日志收集与分析是运维过程中十分重要的内容,部署的定时或长期运行的数据解析任务出现异常或错误信息时,我们就需要查看日志记录来排查问题并解决Bug。为避免记录文件不断增长对服务器运行产生影响,有必要对存储日志进行定时清除或转存。本文介绍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)level:设置rootlogger的日志级别。
2)format:指定Handler使用的日志显示格式。常用参数如下:
  %(asctime)s字符串形式的当前时间 ;
  %(filename)s日志输出函数的模块的文件名;
  %(lineno)d %调用日志输出函数的语句所在代码行;
  %(levelname)s文本形式的日志级别;
  %(message)s用户输出信息;
3)datefmt:指定日期时间格式。
4)filename:指定的文件名创建FiledHandler,日志将存储到指定的文件夹中。
5)filemode:文件打开方式,默认a。
注:logging模块配置详细介绍可参考官方文档。

复制


1.1 实例:将日志信息记录文件中

注意:默认输出仅显示大于等于WARNING级别日志,日志级别:DEBUG < INFO < WARNING < ERROR < CRITICAL。下例中我们将级别设置为DEBUG。

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",
)  #日志信息记录到文件
# 可自定义需求记录的内容,在test.log文件查看
logging.debug('debug message')  #调试时信息打印
logging.info('info message')    #正常信息记录
logging.warning('warning message')  #发生警告信息,仍能正常工作
logging.error('error message')  #发生错误,部分功能不正常
logging.critical('critical message')   #严重错误,程序已崩溃  

复制

输出结果:

# 格式:时间、函数模块名称、代码所在行、日志级别、自定义信息
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

复制


1.2 实例:日志记录到文件+IDE/终端显示

handler日志级别以logger日志级别为基础,低于INFO级别的如DEBUG调试信息均不会在handler中出现。

日志配置信息可直接调用配置文件logging.config.fileConfig('logging.conf')

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/终端输出:

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输出:

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

复制


1.3 实例:日志滚动输出+IDE/终端显示+自定义文件大小

RotatingFileHandler:将日志文件记录到磁盘文件,可以设置每个日志文件的最大占用空间。

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 类,只需导入使用即可。

#pipenv install loguru
from loguru import logger

复制

当然,loguru也可像logging一样可配置,但更简单化。使用add()方法对logger进行简单的配置。

2.1 将日志信息记录文件中

在不指定任何参数时,logger默认采用sys.stderr标准错误输出将日志输出到控制台(console)中;通常 linux 服务器上会以文件留存,添加字符串路径即可。

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 参数:压缩日志,传入压缩文件扩展名即可,如 zip、tar、gz等。只要满足rotation分割后的日志文件都被直接压缩成了zip文件
import os
from loguru import logger
#指定日志文件存储文件夹
log_dir = os.path.expanduser(r"D:\myProjectfile\logging_demo\logs")

#指定日志文件格式
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}")

复制
  • retention参数:对超过一定时间的日志删除
#保持其他不变,只需更改一下代码
logger.add(LOG_FILE, rotation="100KB",retention=1)

复制

当然对 retention 传入整数时,该参数表示的是所有文件的索引,而非要保留的文件数。所以最后我们会看到只有两个时间最近的日志文件会被保留下来,其他都被直接清理掉了。

若日志系统没有设置过滤器处理过期文件,可利用以下函数模块定期调用清理过期文件。

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.seconds%3600//60:2d}]")#打印相关信息
            os.remove(absPathFile)

复制

四、以数据库形式留存日志

首先需要通过serialize参数将其转化成序列化的json格式,导入非关系型数据库如MongoDB中用作后续的日志分析。

from loguru import logger
import os

logger.add(os.path.expanduser(r"F:\AutoOps_platform\日志文件\logs\test.log"), serialize=True)
logger.info("test")

复制

序列号记录如下:

{
    "text""2021-07-20 19:14:25.399 | INFO     | __main__:<module>:22 - test\n",
    "record": {
        "elapsed": {
            "repr""0:00:00.071955",
            "seconds": 0.071955},
        "exception": null,
        "extra": {},
        "file": {
            "name""log_file.py",
            "path""F:/AutoOps_platform/\u65e5\u5fd7\u6587\u4ef6/log_file.py"},
        "function""<module>",
        "level": {
                    "icon""\u2139\ufe0f",
                    "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}}}

复制

本文介绍了日志文件处理常用模块的使用方法,基本能够满足常规脚本开发需求。其他更高级姿势配置部分没有进一步展开,若对这个库感兴趣,建议深入阅读一下其官方文档,更好的运用到实际开发中。

推荐阅读

Python多线程总结

AI科普(一):什么是人工智能?

为什么正态分布如此常见?

文章转载自AI有温度,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论