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:用于定义枚举类。

注意事项

  • 元类是高级特性,通常只在需要控制类的行为时使用。
  • 过度使用元类会使代码难以理解和维护。
相关推荐
程序小武38 分钟前
python编辑器如何选择?
后端·python
一叶知秋12111 小时前
UV管理python项目
python
AndrewHZ1 小时前
【图像处理入门】2. Python中OpenCV与Matplotlib的图像操作指南
图像处理·python·opencv·计算机视觉·matplotlib·图像操作
golitter.1 小时前
langchain学习 01
python·学习·langchain
一叶知秋12112 小时前
LangChain Prompts模块
python
量化金策3 小时前
截面动量策略思路
python
心软且酷丶3 小时前
leetcode:7. 整数反转(python3解法,数学相关算法题)
python·算法·leetcode
逾非时3 小时前
python:selenium爬取网站信息
开发语言·python·selenium
天才测试猿3 小时前
Selenium操作指南(全)
自动化测试·软件测试·python·selenium·测试工具·职场和发展·测试用例
不学无术の码农4 小时前
《Effective Python》第六章 推导式和生成器——避免在推导式中使用超过两个控制子表达式
开发语言·python