来卷啊,Flask日志操作从开始到封装

2,233 阅读7分钟

启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第35天,点击查看活动详情

作为一个Web(CRUD)工程师,最近开始琢磨日志,在开发的过程当中,日志并没有太突出的效果,但是到了联调和上线排查的阶段,嗯,真香~(嘿嘿嘿)

image.png

日志简述

日志这个东西,在编程过程当中都有逐渐的接触,但是一些基础的知识还是要知道的,无效的日志只是走个过场,有效的日志对于运行数据统计,错误定位都可以起到极大的辅助作用,所以先聊聊下面的点:

1、日志是分等级的,常见的日志等级:info, warning, error, danger,具体的描述可以看下面的表格,当然还可以有更多,这里先说常见的等级

日志等级描述
info消息,没有报错,就是输出运行内容,一般用来输出执行的流程,运行的结果
warning警告,确实有问题,但是不影响目前代码的运行,比如版本升级信息啥的
error错误,已经报错导致程序中断了,也是排错需要针对的日志等级
danger危险,程序已经中断,并且会对数据,系统造成影响的错误

2、日志输出的元素很重要,不同类型的日志,需要输出不同的元素,但是遵循的原则是一个,就是可读性,通过日志是要进行分析的,所以要绝对的可读,参数有价值,常见的元素:

(1)触发日志的身份,比如ip地址,用户id,用户名。

(2)触发日志的时间,这个是定位错误和数据分析绝对需要的参数。

(3)触发日志的代码位置,比如行号,比如具体的代码,这个真的很香,不论是记录执行的流程还是报错,都可以让你很精确清晰的看到日志提示在那行代码触发的。

(4)具体的错误或者数据,这个不必多解释哈。

(5)不同类型的日志还有自己必须的参数,比如http日志的请求方式,ftp请求的文件地址等等,总之日志输出内容一定是服务日志分析的目的的。

基于上面聊到,我们以一次http请求的日志举例:

2022-12-26 18:00:00 127.0.0.0.0  GET “/search?keyword=日志&from=www.baidu.com” httplog:line(45) 

当然根据需求可以有更多的设置。

Flask 日志记录思路

flask是Python热门的一个轻量的WEB框架,轻量自然有很多功能是需要自己实现的,日志也是如此,这里首先说一下我们设置日志的目的,然后再设置日志:

1、错误定位,这个是日志的普遍的操作

2、操作数据的流程

3、用户行为记录

日志模块logging

基于上面的我们先来聊聊Python操作日志的模块logging,logging是Python内置的模块,大家可以直接导入使用,基本代码如下:

import logging #导入模块

logging.basicConfig(level = logging.INFO,format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s') #设置日志的等级和日志的格式,格式变量是logging定义好的

logger = logging.getLogger(__name__)  #设置日志对象

logger.info("Start print log")  #开始编写不同等级的日志

logger.debug("Do something")

logger.warning("Something maybe fail.")

logger.info("Finish")

这里需要解释的是logging的日志等级

等级描述
debug打印全部的日志( notset 等同于 debug )
info打印 info, warning, error, critical 级别的日志
warning打印 warning, error, critical 级别的日志
error打印 error, critical 级别的日志
critical类似上面说的danger级别,打印 critical 级别

logging日志常用变量

变量描述
asctime当前时间,格式类似:2022-12-26 16:14:01,700(年-月-日 时:分:秒,毫秒)
evelname输出日志级别
filename文件名称
lineno报错文件行号
message输出信息

flask结合logging

那么基于上面的代码,我们来把flask和日志模块结合起来:

import logging
from logging.handlers import TimedRotatingFileHandler

from flask import Flask

app = Flask(__name__)

formatter = logging.Formatter(
    "[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s") # 设置日志格式
handler = TimedRotatingFileHandler(
    "E:\worker\companyGit\dj\junwang2_multi_level_plat\logs\flask.log", when="D", interval=1, backupCount=15,
    encoding="UTF-8", delay=False, utc=True) # 定义日志句柄,设置日志的地址,编码,时区,等级
app.logger.addHandler(handler) # flask加载日志
handler.setFormatter(formatter) # 日志句柄加载日志格式

那么这样设置完成后,如何使用呢,flask提供了一个current_app变量(这个变量后面会专门聊),可以在flask请求的上下文当中指代app实例,所以可以借助current_app来调用到刚才设置的日志对象:

from flask import current_app
current_app.logger.info("get 请求:%s"%str(result))

flask logging多日志文件

上面确实实现了flask记录日志,但是又两个问题需要考虑:

1、项目里添加散装代码(不是函数或者类这样的代码块),维护起来会比较麻烦。

2、当前只实现了一个日志文件,但是基于后期考虑,还是把访问日志,用户行为日志,访问日志进行划分放到不同的文件当中比较好。

所以,考虑在一个flask代码当中可以创建多个日志文件,提供给不同的需求使用,一番努力(百度)思考(cv)之后,发现我们可以定义一个创建日志的功能,然后绑定到current_app变量上,先定义日志对象生成函数:

import logging
from logging.handlers import RotatingFileHandler

def setup_log(logger_name,log_file,level = logging.INFO,):
    log = logging.getLogger(logger_name)
    formatter = logging.Formatter("[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s")
    fileHeader = RotatingFileHandler(log_file,maxBytes = 100*1024*1024,backupCount = 10,encoding = "utf-8")
    fileHeader.setFormatter(formatter)
    streamHandler = logging.StreamHandler()
    streamHandler.setFormatter(formatter)

    log.setLevel(level)
    log.addHandler(fileHeader)
    return log

想把这个日志绑定到current_app变量上,自然,配置文件当中加载是个不错的选择。

from loggers import setup_log #刚才定义生成日志函数的文件

# 基于配置文件的获取项目的根目录绝对路径
BASEDIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 基于项目根目录拼接日志目录
LOGDIR = os.path.join(BASEDIR,"logs")

class BaseConfig:
    #创建出两个日志
    RUNLOG = setup_log("runing_log",log_file = os.path.join(LOGDIR,"runing.log"))
    POINTLOG = setup_log("point_log",log_file = os.path.join(LOGDIR,"point.log"))

然后flask app加载日志

from flask import Flask
from settings import BaseConfig

app = Flask(__name__)
app.config.from_object(BaseConfig)

然后自然就可以使用了

from flask import current_app

running_log = current_app.config.RUNLOG
running_log.info("日志信息")

结合起来

基于上面的代码,我们来进行日志的设计,以错误定位为例,错误定位,首先要知道,flask本身的报错机制决定了在上线后如果不经过任何处理,只会返回状态吗,如果你的flask接口返回了一个500,啥也没有说,那么这个时候就知道日志的重要性了,要不然在线断点(画面太美了),所以对于错误定位来说:

(1)错误需要进行捕获,不可以让一个错误导致服务宕机。

(2)错误需要有返回,比如对错误的提示。

所以完成的思路就是 捕获异常+日志: Python werkzeug 拥有定义好的Http错误类,HTTPException,可以基于这个类来定义一个错误类来捕获异常。

from flask import request, json
from werkzeug.exceptions import HTTPException

from flask import current_app

running_log = current_app.config.RUNLOG

class APIException(HTTPException):
    errno = -5
    errmsg = '服务器逻辑错误!'
    data = "null"

    def __init__(self, errmsg=None, errno=None, data=None,
                 headers=None):
        """
        设置错误可以设定的参数
        errmsg 自定义的错误提示信息
        errno 错误的编码,可以自己设计,提供给前端进行判断使用
        data 错误的数据
        headers 错误的句柄,一般用不到
        """
        if data:
            self.data = data
        if errno:
            self.errno = errno
        if errmsg:
            self.errmsg = errmsg
        #这里保持父类的init方法
        super(APIException, self).__init__(errmsg, None) 

    def get_body(self, *args, **kwargs):
        """
        设置错误的返回数据格式,这里是以字典格式返回json数据
        """
        body = dict(
            errmsg=self.errmsg,
            errno=self.errno,
            data=self.data,
            request=request.method + ' ' + self.get_url_no_param()
        )
        text = json.dumps(body)
        running_log.info(text) #进行日志记录,返回和记录两不耽误
        return text

    @staticmethod
    def get_url_no_param():
        """
        处理请求的地址
        """
        full_path = str(request.full_path)
        main_path = full_path.split('?')
        return main_path[0]

类定义好了之后,然后就是编写一个简单的触发了,flask可以自定义函数绑定到app上,所以可以这么搞一下:

from flask import Flask
from SelfException import APIException


def excetion_handler(app):
    @app.errorhandler(Exception) #app加载错误句柄的方法
    def _(exc):
        """
        exc 捕获错误的内容
        """
        return APIException(errmsg = str(exc))

app = Flask(__name__)
excetion_handler(app)

当然,对于记录用户行为的日志来说,flask也定义了生命周期函数,也可以通过before_request方法在每次请求结束后记录日志。

from flask import Flask
from flask import request
from flask import current_app

def point_log_fun():
    message = "%s - %s - %s - %s"%(
        request.remote_addr,
        request.method,
        request.cookies.get("username","匿名用户"),
        request.path
    )
    loger = current_app.config.get("POINTLOG")
    loger.info(message)

app = Flask(__name__)

app.before_request(point_log_fun)

ok,关于flask日志操作,先聊到这里,由于测试代码使用了蓝图,所以分了功能,视图,路由文件(之前又聊过),所以博客当中的代码进行了重新拼接,欢迎各位大佬多多指点哈。