【FastAPI 筑基 - Day2】彩色日志封装 + Python 装饰器精讲

专栏:FastAPI 从零到后端接口实战 标签:Python、装饰器、日志封装、colorlog、FastAPI 工程化、代码规范 前置知识:Day1 async/await 异步基础、aiohttp 异步请求


前言

Day1 我们完成了异步语法与 aiohttp 网络请求编写,但代码调试只能依靠原生 print 打印信息。

原生 print 无法区分日志等级、没有时间戳、不能持久化保存日志文件,项目体量变大后排查 BUG 极其麻烦。同时 FastAPI 内部大量使用装饰器做路由注册、参数校验、依赖注入,不懂装饰器很难看懂 FastAPI 源码写法。

本章两大核心目标:

  1. 基于 colorlog 封装彩色分级日志(控制台彩色打印 + 本地文件落盘)
  2. 由浅入深吃透装饰器(基础语法、传参装饰器、wraps 元信息修复、异步函数装饰器)

产出可复用的日志工具类,后续所有 FastAPI 项目直接导入使用。


一、Python 原生 logging 模块基础认知

1.1 日志 5 个等级(由低 → 高)

等级 方法 适用场景
DEBUG logging.debug() 开发调试、打印变量值、流程节点
INFO logging.info() 正常业务运行记录,接口调用、请求进入
WARNING logging.warning() 非报错异常,配置缺失、请求超时等警告
ERROR logging.error() 程序发生异常、接口执行报错
CRITICAL logging.critical() 致命错误,服务无法正常运行

日志默认只会输出 WARNING 及以上级别 ,日常开发一般配置全局等级为 DEBUG

1.2 原生 logging 缺陷

  • 控制台文字全部单色,不同级别日志肉眼无法快速区分;
  • 配置代码冗余,每个文件都要重复编写 handler、formatter;
  • 无法同时实现「控制台打印」+「文件写入」两种输出。

二、colorlog 彩色日志库安装与基础使用

colorlog 对原生 logging 进行封装,给不同日志等级配置区分颜色,大幅提升调试效率。

安装依赖

shell 复制代码
pip install colorlog

最简彩色日志测试代码

python 复制代码
import logging
import colorlog

# 配置日志格式
log_format = colorlog.ColoredFormatter(
    "%(log_color)s%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    log_colors={
        "DEBUG": "cyan",
        "INFO": "green",
        "WARNING": "yellow",
        "ERROR": "red",
        "CRITICAL": "bold_red",
    }
)

# 创建控制台处理器
console_handler = colorlog.StreamHandler()
console_handler.setFormatter(log_format)

# 初始化 logger 对象
logger = colorlog.getLogger("fastapi_project")
logger.addHandler(console_handler)
logger.setLevel(logging.DEBUG)

# 分级打印测试
logger.debug("DEBUG:接口入参数据打印")
logger.info("INFO:服务正常启动成功")
logger.warning("WARNING:请求响应缓慢,存在超时风险")
logger.error("ERROR:数据库查询抛出异常")
logger.critical("CRITICAL:Redis 连接失败,服务不可用")

运行后在控制台可以看到不同颜色的日志输出,一目了然。


三、封装通用日志工具类(工程化标准写法)

实现能力

  • ✅ 控制台彩色输出
  • ✅ 日志自动写入本地 logs/ 文件夹,按日期分割日志文件
  • ✅ 全局统一配置,项目任意文件 from ... import 即可使用

logger_utils.py 完整代码

python 复制代码
import os
import logging
from logging.handlers import TimedRotatingFileHandler
import colorlog

# 1、定义日志根目录(相对当前脚本所在目录)
LOG_ROOT = os.path.join(os.path.dirname(__file__), "logs")
if not os.path.exists(LOG_ROOT):
    os.makedirs(LOG_ROOT)

# 2、日志文件名称,按日期划分
LOG_FILE = os.path.join(LOG_ROOT, "fastapi_run.log")

# 3、控制台彩色格式
console_formatter = colorlog.ColoredFormatter(
    fmt="%(log_color)s%(asctime)s | %(filename)s:%(lineno)d | %(levelname)s | %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
    log_colors={
        "DEBUG": "cyan",
        "INFO": "green",
        "WARNING": "yellow",
        "ERROR": "red",
        "CRITICAL": "bold_red,bg_white",
    },
)

# 4、文件日志普通格式(文件不需要颜色代码)
file_formatter = logging.Formatter(
    fmt="%(asctime)s | %(filename)s:%(lineno)d | %(levelname)s | %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)

# 5、文件处理器:按天切割日志,保留 30 天日志记录
file_handler = TimedRotatingFileHandler(
    filename=LOG_FILE,
    when="D",
    interval=1,
    backupCount=30,
    encoding="utf-8"
)
file_handler.setFormatter(file_formatter)

# 6、控制台处理器
console_handler = colorlog.StreamHandler()
console_handler.setFormatter(console_formatter)

# 7、组装 logger
def get_logger(name="fastapi_log"):
    log = logging.getLogger(name)
    log.setLevel(logging.DEBUG)
    # 避免重复添加 handler
    if not log.handlers:
        log.addHandler(file_handler)
        log.addHandler(console_handler)
    return log

# 全局实例化导出
logger = get_logger()

提示if not log.handlers 的判断能避免多次调用 get_logger() 时重复添加 handler,导致日志重复打印。

项目中调用示例

python 复制代码
from logger_utils import logger

def test_log():
    logger.info("项目日志工具加载完成")
    logger.debug("测试 debug 日志")
    try:
        1 / 0
    except Exception as e:
        logger.error("程序运算异常", exc_info=True)

if __name__ == "__main__":
    test_log()

exc_info=True 会自动打印异常堆栈信息,排查报错必备参数。


四、Python 装饰器核心知识(FastAPI 重中之重)

4.1 装饰器本质

复制代码
装饰器 = 接收函数为参数,返回新函数的高阶函数

作用:在不修改原有函数内部代码的前提下,对函数执行前后进行功能增强(统计耗时、打印日志、权限校验、异常捕获等)。

4.2 基础无参装饰器模板

python 复制代码
import time

# 定义装饰器
def timer_decorator(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        # 执行原函数
        res = func(*args, **kwargs)
        cost = round(time.time() - start, 3)
        print(f"函数 {func.__name__} 执行耗时:{cost} s")
        return res
    return wrapper

# 使用装饰器语法糖 @
@timer_decorator
def demo_func(sleep_time):
    time.sleep(sleep_time)
    return "函数执行完毕"

if __name__ == "__main__":
    print(demo_func(1.2))

执行流程拆解demo_func(1.2) → 实际调用的是 wrapper(1.2)wrapper 内部计时 + 调用原函数 + 打印耗时 → 返回原函数结果。

4.3 带参数的装饰器写法

外层再包裹一层函数,用来接收装饰器自身参数:

python 复制代码
import time

def timer_param(threshold: float):
    """threshold: 超时阈值,单位秒"""
    def outer(func):
        def wrapper(*args, **kwargs):
            s = time.time()
            ret = func(*args, **kwargs)
            c = time.time() - s
            if c > threshold:
                print(f"函数 {func.__name__} 执行超时,耗时 {c:.3f}s,阈值 {threshold}s")
            else:
                print(f"函数 {func.__name__} 正常执行,耗时 {c:.3f}s")
            return ret
        return wrapper
    return outer

# 传入阈值参数 0.5 秒
@timer_param(threshold=0.5)
def test_sleep(t):
    time.sleep(t)

test_sleep(0.8)  # 输出:执行超时
test_sleep(0.2)  # 输出:正常执行

三层嵌套结构timer_param(参数) → 返回 outer@outer 修饰 func → 返回 wrapper

4.4 装饰器修复函数元信息(wraps)

被装饰后的函数会丢失原函数名称、注释文档,使用 functools.wraps 修复:

python 复制代码
from functools import wraps

def log_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"开始执行函数:{func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_decorator
def hello():
    """测试函数文档注释"""
    pass

print(hello.__name__)  # 输出:hello(不加 wraps 会输出 wrapper)
print(hello.__doc__)   # 输出:测试函数文档注释(不加 wraps 会输出 None)

记忆口诀 :定义装饰器时,@wraps(func) 写在最内层 wrapper 上面,养成习惯。

4.5 装饰器修饰异步函数(适配 Day1 异步代码)

异步函数必须在内部 await 调用原函数,普通同步装饰器无法直接修饰 async 函数:

python 复制代码
import asyncio
import time
from functools import wraps

def async_timer(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        st = time.time()
        result = await func(*args, **kwargs)  # 注意:await 调用原函数
        cost = time.time() - st
        print(f"异步函数 {func.__name__} 耗时:{cost:.3f}s")
        return result
    return wrapper

# 修饰 aiohttp 异步请求函数
@async_timer
async def async_test():
    await asyncio.sleep(1)
    print("异步任务执行完成")

asyncio.run(async_test())

同步装饰器 vs 异步装饰器对比

同步装饰器 异步装饰器
wrapper 定义 def wrapper(...) async def wrapper(...)
调用原函数 func(*args, **kwargs) await func(*args, **kwargs)
适用场景 同步函数 async def 异步函数

五、结合日志 + 装饰器实战:统一接口耗时 & 异常捕获装饰器

日常 FastAPI 开发高频通用装饰器,直接放入工具类复用:

python 复制代码
import time
from functools import wraps
from logger_utils import logger

def api_wrapper(func):
    """统一接口日志 + 耗时统计 + 异常捕获装饰器"""

    @wraps(func)
    async def wrapper(*args, **kwargs):
        start = time.time()
        try:
            resp = await func(*args, **kwargs)
            cost = round(time.time() - start, 4)
            logger.info(f"接口 {func.__name__} 请求正常,耗时:{cost}s")
            return resp
        except Exception:
            cost = round(time.time() - start, 4)
            logger.error(f"接口 {func.__name__} 执行异常,耗时:{cost}s", exc_info=True)
            raise   # 原样抛出异常,保留原始调用栈

    return wrapper

要点说明

  • exc_info=True 会自动将异常堆栈写入日志,无需手动拼接错误信息。
  • raise(不带异常对象)会原样抛出,保留原始 traceback,比 raise e 更规范。

六、Day2 知识点总结

序号 知识点 掌握程度
1 logging 5 级分级规则 理解并会使用
2 colorlog 控制台彩色日志 能独立配置
3 可复用日志工具类(控制台 + 文件) 能直接导入使用
4 基础无参装饰器 能手写模板
5 带参数装饰器(三层嵌套) 理解原理
6 functools.wraps 元信息修复 养成习惯
7 异步函数装饰器编写 适配 FastAPI 异步接口
8 日志 + 装饰器联合实战 产出通用工具

下期预告

Day3:FastAPI 第一个 HelloWorld 接口、路径参数、查询参数、请求体 POST 参数详解,正式开始编写后端接口。

相关推荐
Muselit1 小时前
Python 泛型:把 list[User] 讲明白
python·fastapi
05664612 小时前
agent学习Day15——SQLAlchemy 查询过滤、分页与历史列表接口
python·学习·fastapi
淼澄研学1 天前
Python构建AI应用:从环境配置到FastAPI部署的5个实操步骤
人工智能·python·fastapi
逻极2 天前
FastAPI 实战:从入门到自动化文档,如何把API开发效率提升200%
python·api·fastapi·swagger·异步
天使day3 天前
FastAPI快速入门
python·fastapi
Maiko Star3 天前
FastAPI如何解决跨越问题
fastapi
weixin_471383034 天前
07 FastAPI
python·fastapi
Python私教4 天前
AI 生成到 90% 突然断了怎么办?我用 SSE、检查点和幂等把任务接着跑
fastapi
李昊哲小课5 天前
FastAPI + Echarts 构建可交互数据仪表板
信息可视化·echarts·fastapi