fastapi接口里日志重复写,用metaclass 单例模式解决了

遇到这个妖

我用fastapi写接口,打印日志用我自定义的日志类,但只要是fastapi 接口[即注解@app.get('/') 或者 @app.post('/') ] 之内打印的都是两遍,其他地方都是正常。这我很费解。说是我日志类的问题吧,我这类放其他地方都好使;说是fastapi的问题吧,人家日志格式跟我自定义的差别又很明显。

我自定义的logging类:

import logging
from logging.handlers import RotatingFileHandler
import os
class My_Logger():
    def __init__(self, logger_name):
        self.logger_name = logger_name
        logfile = "{0}/logs/{1}.log".format(os.path.abspath("."), self.logger_name)
        self.my_logger = logging.getLogger(self.logger_name)
        self.my_logger.setLevel(logging.DEBUG)
        formatter = logging.Formatter(
            "%(asctime)s - %(name)s - %(process)s - %(levelname)s - %(message)s"
        )
        test_formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
        stream_handler = logging.StreamHandler()
        stream_handler.setLevel(logging.DEBUG)
        stream_handler.setFormatter(formatter)
        rotating_handler = RotatingFileHandler(
            logfile, maxBytes=10 * 1024 * 1024, backupCount=1, encoding="utf-8"
        )
        rotating_handler.setLevel(logging.DEBUG)
        rotating_handler.setFormatter(test_formatter)

        self.my_logger.addHandler(stream_handler)
        self.my_logger.addHandler(rotating_handler)

    def debug(self, msg):
        self.my_logger.debug(msg)

    def info(self, msg):
        self.my_logger.info(msg)

    def warning(self, msg):
        self.my_logger.warning(msg)

    def error(self, msg):
        self.my_logger.error(msg)

我调用的地方:

from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.encoders import jsonable_encoder

# lifespan
@asynccontextmanager
async def lifespan(app: FastAPI):
    session = await init_redis_pool()
    app.state.redis = session
    yield
    await session.close()

app = FastAPI(lifespan=lifespan)
# 日志
customer_logger = My_Logger("test")
@app.post("/abc")
async def understand(req=Depends(request_body_and_redis_cache)):
	# 这个日志会打印两遍
    customer_logger.info(f"abc api request: {req}")

    url = req["url"]
    if url == None:
        return {"code": 9999, "errorMessage": "back server is not online"}
    api_req = req["item"]
    content = api_req.text
	if content == "":
        return {"code": 9998, "errorMessage": "param error" }

    jsonData = jsonable_encoder(api_req)
    try:
        return await async_http_query(url, jsonData)
    except Exception as e:
        customer_logger.warning(f"Failed to fetch data. Error: {e}")
        raise HTTPException(status_code=400, detail=str(e))
if __name__ == "__main__":
    import uvicorn
    # 这个日志正常
    customer_logger.info("test api is starting")
    uvicorn.run("redis_tool:app", host="0.0.0.0", port=8008, reload=True)
    customer_logger.info("test api is end")

搜到一个解决办法:

fastapi使用loguru日志记录时 重复记录日志

这个大佬说:

"原因:

这个问题是因为loguru和FastAPI都使用了logging这个内置的模块进行日志记录"

放在我这里,就是我的自定义日志类和FastAPI都使用了logging这个内置的模块进行日志记录

对上了,但我还是不理解。

不过大佬说的解决方案是非常好使:单例模式

# 单例模式
class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class My_Logger(metaclass=SingletonMeta):
    # 不再赘述

那么问题又来了 metaclass 是什么?

以下来自Kimi

在Python中,元类(metaclass)是类的类,也就是说,它们定义了其他类的行为。元类在Python中是一个高级主题,通常用于框架和库的开发中,而不是日常的应用程序开发。

基本概念

  1. 类(Class):定义对象的属性和方法。
  2. 实例(Instance):根据类创建的对象。
  3. 元类(Metaclass):定义类的类。

创建元类

在Python中,你可以使用type函数来创建一个元类。type是Python中所有类的默认元类。

python 复制代码
class Meta(type):
    def __new__(cls, name, bases, dct):
        print(f"Creating class: {name}")
        return super().__new__(cls, name, bases, dct)

class MyClass(metaclass=Meta):
    pass

# 输出: Creating class: MyClass

元类的作用

  1. 控制类的行为:元类可以在类创建时修改类的定义。
  2. 注册类:在类创建时自动注册到某个注册表中。
  3. 自动添加方法:为类自动添加一些方法。

示例:自动添加方法

下面是一个示例,展示如何使用元类为所有类自动添加一个方法。

python 复制代码
class AutoMethodMeta(type):
    def __new__(cls, name, bases, dct):
        dct['hello'] = lambda self: f"Hello from {self.__class__.__name__}"
        return super().__new__(cls, name, bases, dct)

class MyClass(metaclass=AutoMethodMeta):
    pass

obj = MyClass()
print(obj.hello())  # 输出: Hello from MyClass

常用的元类

  1. type:Python默认的元类。
  2. abc.ABCMeta:用于定义抽象基类(ABC)。
  3. enum.EnumMeta:用于定义枚举类。

注意事项

  • 元类是高级特性,通常只在需要控制类的行为时使用。
  • 过度使用元类会使代码难以理解和维护。
相关推荐
深度学习lover1 小时前
<项目代码>YOLOv8 苹果腐烂识别<目标检测>
人工智能·python·yolo·目标检测·计算机视觉·苹果腐烂识别
API快乐传递者2 小时前
淘宝反爬虫机制的主要手段有哪些?
爬虫·python
阡之尘埃4 小时前
Python数据分析案例61——信贷风控评分卡模型(A卡)(scorecardpy 全面解析)
人工智能·python·机器学习·数据分析·智能风控·信贷风控
丕羽7 小时前
【Pytorch】基本语法
人工智能·pytorch·python
bryant_meng7 小时前
【python】Distribution
开发语言·python·分布函数·常用分布
m0_594526308 小时前
Python批量合并多个PDF
java·python·pdf
工业互联网专业9 小时前
Python毕业设计选题:基于Hadoop的租房数据分析系统的设计与实现
vue.js·hadoop·python·flask·毕业设计·源码·课程设计
钱钱钱端9 小时前
【压力测试】如何确定系统最大并发用户数?
自动化测试·软件测试·python·职场和发展·压力测试·postman
慕卿扬9 小时前
基于python的机器学习(二)—— 使用Scikit-learn库
笔记·python·学习·机器学习·scikit-learn
Json____9 小时前
python的安装环境Miniconda(Conda 命令管理依赖配置)
开发语言·python·conda·miniconda