资讯详情

资讯详情

FastAPI异常处理与日志系统实战:构建可观测的Web服务

你有没有过这种体验接口写得好好的一到线上就莫名其妙报500翻服务器日志翻了半天只看到一行“Internal Server Error”压根不知道是参数传错、数据库连不上、还是代码里的低级Bug。我在某个模拟项目X里接手过一套FastAPI服务第一个星期几乎天天在干这事儿后来痛定思痛把异常处理和日志体系认认真真补了一遍才发现“能跑”和“好维护”之间隔着一整套可观测性设计。这期教程就专门聊这个面向0基础读者把FastAPI的异常处理机制和日志系统从零拆开讲清楚。异常处理解决的是“系统崩了能不能崩得明白”日志解决的是“系统跑起来之后能不能看得懂”这两件事合在一起才叫“好维护”。不管你是刚学FastAPI还是已经写了一阵子这篇文章都建议完整看一遍代码可以直接抄思路可以直接复用。1. 异常处理从“不崩”到“崩得明白”异常处理这件事很多人有个误区觉得“不就是try except吗包一层就完事了”。但实际在FastAPI项目里异常处理是一整套链路接口函数里主动抛出可预期的错误、框架帮我们翻译HTTP状态码、自定义异常处理器统一兜底、最后还有一层全局捕获防止堆栈信息直接泄露给前端。1.1 为什么异常不能靠“try except一把梭”我们先从最基础的讲起。你在FastAPI里写一个接口正常逻辑是用户传参数进来我们处理完返回结果。但如果用户传了一个不符合预期的值呢比如年龄传了-5比如商品ID传了一个根本不存在的字符串再比如调第三方接口时对方服务挂了。这些情况如果不在代码里处理FastAPI框架只会回一个500前端拿到的信息就是“服务器内部错误”这种体验无论是给小程序还是网页用都等于直接劝退用户。有人会说那我就在函数里全部用try except包起来捕获到异常我自己返回一个JSON不就行了思路没错但有几个麻烦。第一每个接口都写try except代码会变得非常臃肿重复代码一堆第二不同接口返回的错误格式如果不统一前端处理起来非常痛苦第三有些异常比如数据库连接超时是全局性的你在每个接口里各写一套捕获根本维护不动。所以FastAPI给了我们更优雅的方案内置的HTTPException 全局异常处理器。1.2 HTTPException最基础的一张“安全网”FastAPI里最常用的主动抛错方式就是HTTPException。用法非常直接比如用户传入一个不存在的用户ID我们希望返回404代码可以这样写from fastapi import FastAPI, HTTPException app FastAPI() # 模拟一个用户数据库 fake_users_db { 1: {name: 张三, age: 18}, 2: {name: 李四, age: 20}, } app.get(/users/{user_id}) def get_user(user_id: int): if user_id not in fake_users_db: raise HTTPException(status_code404, detail用户不存在) return fake_users_db[user_id]这里面的逻辑很简单如果用户ID不在我们模拟的数据库里就主动抛出一个404异常FastAPI会自动把响应编码成JSON格式返回给前端。detail字段就是错误信息前端可以直接读取并展示给用户。你可能注意到了HTTPException是一个异常类我们是用raise关键字抛出去的。实际上FastAPI内部对HTTPException做了专门处理一旦捕获到它就会立刻停止当前请求的处理流程直接返回对应的错误响应。这样我们就不需要自己在函数里写return来中断流程了代码逻辑会显得非常利落。那我建议你的项目从一开始就统一错误响应格式。什么意思呢比如你规定所有的错误响应必须是{ code: 404, message: 用户不存在, data: null }那么你就不应该直接用FastAPI默认的HTTPException因为FastAPI默认返回的格式是{detail: 用户不存在}两个字段对不上前端就要写两种解析逻辑。所以更合理的做法是自定义我们自己项目的异常基类然后把HTTPException的响应格式统一成我们自己的模板。这个后面的章节会专门展开。1.3 自定义异常类把错误“翻译”成业务语言真实项目里的异常远远不止“404”和“400”这两种。比如用户登录时密码错误、下单时库存不足、调用支付接口时余额不够这些其实都是“业务异常”。如果全部用HTTPException处理代码里会到处是重复的raise HTTPException(status_codexxx, detailxxx)而且状态码本身也无法精确表达业务状态。比如“库存不足”算400还是409不同团队理解都不一样这种混乱会直接影响前后端联调效率。更好的做法是为项目定义一套业务异常体系。先写一个基础异常类比如叫BizException它至少包含两个字段业务错误码和错误信息。然后根据错误码决定对应的HTTP状态码。class BizException(Exception): def __init__(self, code: int 400, message: str 业务异常): self.code code self.message message接着在FastAPI里注册一个全局异常处理器把BizException翻译成统一的JSON响应from fastapi import FastAPI, Request from fastapi.responses import JSONResponse app FastAPI() class BizException(Exception): def __init__(self, code: int 400, message: str 业务异常): self.code code self.message message app.exception_handler(BizException) async def biz_exception_handler(request: Request, exc: BizException): return JSONResponse( status_codeexc.code, content{code: exc.code, message: exc.message, data: None} )这样一来业务代码里就可以直接这样抛异常了raise BizException(code400, message用户名或密码错误)所有调用方收到错误响应时格式永远一致前端只需要解析code和message就行。这套逻辑才是异常处理的核心思路不是“捕获异常”而是“统一异常的出口”。1.4 全局兜底防止堆栈信息“裸奔”给用户自己抛的异常好处理最怕的是那些意想不到的异常。比如代码里偶发的NoneType错误、数据库突然断连、第三方接口超时这些异常我们没有主动捕获FastAPI默认会返回500状态码而且响应体里可能直接带着Python的异常堆栈。这个信息一旦泄露给前端相当于把服务器代码结构、变量名、依赖库版本全暴露了安全隐患非常大。所以项目里必须有一个全局兜底异常处理器捕获所有未处理的异常记录到日志里但返回给用户的只是一个通用的“服务器开小差了”提示。import logging from fastapi import FastAPI, Request from fastapi.responses import JSONResponse logger logging.getLogger(app) app FastAPI() app.exception_handler(Exception) async def unhandled_exception_handler(request: Request, exc: Exception): # 注意这里必须记录完整堆栈否则出了问题根本没法排查 logger.error(未捕获异常: %s, exc, exc_infoTrue) return JSONResponse( status_code500, content{code: 500, message: 服务器内部错误请稍后重试, data: None} )exc_infoTrue这个参数非常重要它会把完整的堆栈信息写入日志文件方便排查。而返回给用户的响应中完全没有堆栈内容安全性和体验都兼顾了。2. 日志系统设计让系统学会“开口说话”异常处理解决了“错误怎么反馈”的问题但一个系统好不好维护更关键的在于日志。日志就是系统的“黑匣子”线上出问题的时候你只能靠日志去还原现场。但很多项目在日志上极其随意print到处飞、日志格式杂乱无章、日志级别乱用结果真出问题的时候翻日志翻到怀疑人生。2.1 为什么说“print大法”是灾难零基础写代码最容易依赖print因为它简单直观控制台一跑就能看到结果。但这个习惯一旦带到Web项目里就是灾难。第一print的内容只输出到控制台如果服务器重启、进程崩溃这些输出就永远丢了第二print没有日志级别你分不清哪条是INFO、哪条是WARNING、哪条是ERROR检索起来无从下手第三print不能控制输出位置生产环境里你通常希望日志写入文件甚至传给日志收集系统print完全做不到。所以第一步就是引入Python标准库的logging模块。我见过很多初学者觉得logging难用觉得print多省事。实际上logging配置一次之后后面用起来和print一样简单但它带来的收益是print完全比不了的可以写入文件、可以滚动切割、可以控制级别、可以加结构化字段。2.2 一套可以直接“抄作业”的日志配置我们先实现一个最基础但很完善的日志配置用logging模块的dictConfig来集中管理这样做的好处是配置都在一个字典里改起来非常直观。import logging import sys from logging.config import dictConfig LOGGING_CONFIG { version: 1, disable_existing_loggers: False, formatters: { default: { format: %(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s, datefmt: %Y-%m-%d %H:%M:%S }, json: { format: %(asctime)s %(name)s %(levelname)s %(filename)s %(lineno)d %(message)s, datefmt: %Y-%m-%d %H:%M:%S } }, handlers: { default: { class: logging.StreamHandler, formatter: default, stream: sys.stderr }, file: { class: logging.handlers.TimedRotatingFileHandler, formatter: default, filename: logs/app.log, when: midnight, backupCount: 7, encoding: utf-8 } }, root: { level: INFO, handlers: [default, file] } } dictConfig(LOGGING_CONFIG)这段配置里做了几件事设置统一的日志格式时间、模块名、级别、代码位置全都有配置了控制台输出和文件输出两个handler文件输出用TimedRotatingFileHandler按天切割日志保留最近7天的日志避免单个日志文件无限膨胀。其中formatters里的json格式我们下一节专门讲这里可以先保持默认文本格式。配置好之后整个项目里直接用模块级logger记日志就行import logging logger logging.getLogger(app.user) logger.info(用户模块启动) logger.error(数据库连接失败重试中)getLogger里的参数可以按模块命名比如“app.user”“app.order”这样日志里会自动带上这个名称排查问题时可以快速过滤出某个模块的运行轨迹。2.3 结构化日志让日志能被机器“读懂”如果你只在小项目里自己看看日志纯文本格式没问题。但一旦服务多了、日志量大了你肯定会上日志收集平台比如基于ELK的日志系统或者云厂商的日志服务。到了那个阶段纯文本日志的劣势就暴露了日志内容靠肉眼扫字段没有明确的key全文检索根本无从下手。结构化日志就是把日志内容变成JSON格式。比如一行日志不再是2024-01-15 10:00:02 - app.order - ERROR - [order.py:56] - 订单创建失败库存不足而是{timestamp: 2024-01-15T10:00:02Z, module: app.order, level: ERROR, line: 56, message: 订单创建失败, order_id: 12345, sku_id: xx}这样日志系统就可以直接按字段过滤比如查所有order_id为12345的日志、查所有level为ERROR的日志效率完全不是一个量级。在FastAPI项目里实现结构化日志方式有很多。我推荐的做法是用一个自定义Formatter把原本的日志字段组装成JSON字符串import json import logging class JsonFormatter(logging.Formatter): def format(self, record): log_entry { timestamp: self.formatTime(record, %Y-%m-%d %H:%M:%S), name: record.name, level: record.levelname, filename: record.filename, line: record.lineno, message: record.getMessage() } # 如果传入了额外的extra字段也拼到JSON里 for key, value in record.__dict__.items(): if key not in log_entry and not key.startswith(_): log_entry[key] value return json.dumps(log_entry, ensure_asciiFalse)然后在配置文件里把formatter替换成这个自定义类。用的时候可以这样带业务字段logger.info(订单创建成功, extra{order_id: 12345, amount: 99.5})这样日志收集平台就能直接解析这些字段了。需要说明的是ensure_asciiFalse是为了保留中文日志内容不然日志里全是\u开头自己看也难受。2.4 日志级别的正确使用姿势日志级别是很多人不太重视但又特别影响排查效率的设计。Python logging标准级别从低到高是DEBUG、INFO、WARNING、ERROR、CRITICAL项目里一般把根日志级别设为INFO或WARNING。很多人喜欢把全部日志都记成INFO美其名曰“详细一点总没坏处”其实这样反而把真正重要的ERROR日志淹没在大量无效信息里。我的习惯是DEBUG开发调试时用比如SQL语句、函数入参出参线上环境一般不开启INFO关键业务流程比如接收请求、订单创建成功、用户注册成功WARNING系统能跑但是有点不对劲比如重试N次终于成功、缓存穿透、接口响应超过预期ERROR确实出错了比如数据库连接失败、异常被捕获CRITICAL系统基本不可用比如启动失败、磁盘写满在日志配置里把root级别设为INFO然后单独给某些特别吵的第三方库比如SQLAlchemy、urllib调成WARNING这样日志会干净很多。3. 请求日志中间件让每个请求都有迹可循异常处理和基础日志配置都到位了但还差一个重要环节每个HTTP请求的完整轨迹。生产环境排查问题的时候最经典的三连问是到底是谁调的接口调这个接口花了多久这个接口当时返回了什么这三点用FastAPI的中间件机制非常容易实现。3.1 FastAPI中间件的工作原理中间件在FastAPI里的位置是请求到达路由处理函数之前先经过中间件响应从路由返回之后也经过中间件。你可以把它理解成一道安检门进也检查一次出也检查一次。代码写起来非常简单FastAPI提供了decorator风格和纯类两种写法。先看一种最常用的import time import logging from fastapi import FastAPI, Request logger logging.getLogger(app.access) app FastAPI() app.middleware(http) async def request_logging_middleware(request: Request, call_next): # 记录开始时间 start_time time.time() # 调用下一个处理层也就是路由函数 response await call_next(request) # 计算耗时 duration time.time() - start_time # 组装日志 log_data { client_ip: request.client.host if request.client else unknown, method: request.method, path: request.url.path, status_code: response.status_code, duration_ms: round(duration * 1000, 2) } logger.info(请求完成, extralog_data) return response这段中间件会在每个请求进来的时候自动记录客户端IP、请求方法、请求路径、响应状态码和处理耗时。日志会输出到我们之前配置的handler里。就是这么简单但这条日志是整个系统排查问题的地基。3.2 中间件里应该记录哪些关键字段上面只是基础版实际项目里我还会加上更多字段请求IDRequest ID每个请求生成一个唯一ID响应头里也带上前端报错时把这个ID提供给后端后端日志一搜就定位到对应请求用户ID如果系统有登录状态中间件里可以拿到当前用户ID记录进去Query参数和请求体用于调试但要注意脱敏密码、token之类绝对不能记UA信息记录客户端类型排查兼容性问题时有用耗时这是最重要的性能指标之一后面单独讲请求ID的实现不复杂可以用uuid自动生成import uuid app.middleware(http) async def request_id_middleware(request: Request, call_next): request_id str(uuid.uuid4()) # 尝试从Header中获取比如前端网关可能已经生成过了 request_id request.headers.get(X-Request-ID, request_id) response await call_next(request) response.headers[X-Request-ID] request_id return response然后把这个request_id再传给其他日志可以通过在Request对象上绑定属性或者用ContextVar传。对零基础来说最省事的方法是给中间件的日志单独记一条包含request_id。这样至少能通过请求ID把中间件日志和应用日志串联起来。3.3 用耗时统计快速揪出“慢接口”请求耗时的价值很多时候比你想得大。当系统响应变慢的时候到底慢在哪是某个接口本身慢还是数据库查询慢还是被别人调用拖慢了没有耗时统计你只能凭感觉猜。我建议在中间件日志里把duration_ms当作一个核心字段同时可以做慢请求告警。比如超过1秒的请求记成WARNING超过3秒的记成ERROR这样日志里一搜WARNING就能迅速定位那些拖垮系统的接口。if duration 3: logger.error(慢请求预警: %s %s 耗时 %.2f 秒, request.method, request.url.path, duration) elif duration 1: logger.warning(慢请求预警: %s %s 耗时 %.2f 秒, request.method, request.url.path, duration)另外补充一个实际经验中间件里记录的时间和实际路由函数内的耗时不完全等价它包含了框架内部的调度时间和中间件自身的时间。如果发现所有接口耗时都偏大先检查是不是中间件里做了耗时操作比如请求体解析、数据库查询中间件本身一定要保持轻量。4. 实战整合一套完整的FastAPI异常与日志体系前面把理论、配置、中间件都拆开了这章我们直接搭一套完整的项目骨架把异常处理和日志系统整合到一起。以后新开项目直接复制这套结构就能用。4.1 推荐的项目目录结构为了好维护日志和异常相关的代码最好独立成模块不要全堆在main.py里。我常用的目录结构是这样myproject/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI入口 │ ├── core/ │ │ ├── __init__.py │ │ ├── exceptions.py # 业务异常类 │ │ ├── logging_config.py # 日志配置 │ │ ├── handlers.py # 全局异常处理器 │ │ └── middleware.py # 请求日志中间件 │ └── routers/ │ ├── __init__.py │ └── user.py # 业务路由 ├── logs/ │ └── app.log ├── requirements.txt └── run.py4.2 核心代码逐段拆解先看exceptions.py定义项目统一的业务异常和错误码。为了后面扩展方便这里建议把常见的错误码设计成枚举或常量# app/core/exceptions.py class ErrorCode: # 通用错误 BAD_REQUEST (400, 请求参数错误) UNAUTHORIZED (401, 未登录或登录已过期) FORBIDDEN (403, 没有权限访问) NOT_FOUND (404, 资源不存在) # 业务错误 USER_NOT_EXIST (40001, 用户不存在) PASSWORD_ERROR (40002, 用户名或密码错误) STOCK_NOT_ENOUGH (40003, 库存不足) class BizException(Exception): def __init__(self, code: int 400, message: str 业务异常): self.code code self.message message需要注意HTTP状态码和业务错误码是两套体系。比如“用户不存在”这种业务错误HTTP状态码可以统一返回400但业务错误码是40001前端通过业务错误码做具体逻辑判断HTTP状态码仅表示大类。再看handlers.py把FastAPI框架自带的异常和咱们自定义的异常统一接管# app/core/handlers.py import logging from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse from .exceptions import BizException logger logging.getLogger(app.exception) def register_exception_handlers(app: FastAPI): app.exception_handler(BizException) async def biz_exception_handler(request: Request, exc: BizException): logger.warning(业务异常: %s - %s, exc.code, exc.message) return JSONResponse( status_code400, content{code: exc.code, message: exc.message, data: None} ) app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): # FastAPI参数校验失败的异常在这里统一处理 logger.warning(参数校验失败: %s, exc.errors()) return JSONResponse( status_code422, content{code: 422, message: 参数校验失败, data: exc.errors()} ) app.exception_handler(Exception) async def unhandled_exception_handler(request: Request, exc: Exception): logger.error(未捕获异常: %s, exc, exc_infoTrue) return JSONResponse( status_code500, content{code: 500, message: 服务器内部错误, data: None} )这里我专门处理了RequestValidationError这是FastAPI内置的参数校验异常。当请求参数不符合Pydantic模型定义时会抛出这个异常默认返回422。我们把它统一成自己的响应格式同时把校验细节放到data字段里方便前端定位是哪个参数错了。中间件这块我们把请求ID和访问日志合并到同一个中间件里# app/core/middleware.py import logging import time import uuid from fastapi import Request logger logging.getLogger(app.access) async def request_context_middleware(request: Request, call_next): request_id request.headers.get(X-Request-ID, str(uuid.uuid4())) start_time time.time() response await call_next(request) duration_ms round((time.time() - start_time) * 1000, 2) log_data { request_id: request_id, client_ip: request.client.host if request.client else unknown, method: request.method, path: request.url.path, status_code: response.status_code, duration_ms: duration_ms } response.headers[X-Request-ID] request_id if duration_ms 3000: logger.error(慢请求预警, extralog_data) elif duration_ms 1000: logger.warning(慢请求预警, extralog_data) else: logger.info(请求完成, extralog_data) return responsemain.py把上面这些全部装配起来# app/main.py from fastapi import FastAPI from .core.logging_config import setup_logging from .core.handlers import register_exception_handlers from .core.middleware import request_context_middleware from .routers import user setup_logging() app FastAPI(title示例项目, version1.0.0) app.middleware(http)(request_context_middleware) register_exception_handlers(app) app.include_router(user.router, prefix/api)4.3 业务路由里怎么用这套体系最后写一个用户相关的接口展示异常处理在业务层的真实用法# app/routers/user.py from fastapi import APIRouter from pydantic import BaseModel from ..core.exceptions import BizException, ErrorCode router APIRouter() class UserLoginRequest(BaseModel): username: str password: str # 模拟用户数据 mock_users { admin: 123456, test: abc123 } router.post(/login) def login(req: UserLoginRequest): if req.username not in mock_users: raise BizException(codeErrorCode.USER_NOT_EXIST[0], messageErrorCode.USER_NOT_EXIST[1]) if mock_users[req.username] ! req.password: raise BizException(codeErrorCode.PASSWORD_ERROR[0], messageErrorCode.PASSWORD_ERROR[1]) return {code: 0, message: 登录成功, data: {token: fake_token_123}}这里有个小细节值得注意登录成功时我也定义了统一响应格式code为0表示成功和失败时的业务错误码区分开。很多团队前后端联调效率低很大原因是成功响应和失败响应的格式不一致成功是一种结构、失败是另一种结构前端每个接口都要写两套解析逻辑。所以统一响应体这件事一定要尽早定下来。5. 常见问题与排查技巧实录写到这里异常处理和日志系统的核心内容都过了一遍。这章我专门整理几个我在实际项目中踩过的坑以及对应的排查思路希望能帮你少走弯路。5.1 日志重复输出每条日志打两遍这个问题很常见。原因是你在配置里设置了root logger同时在业务模块里又给logger添加了handler。比如我在一个模块里这么写过logger logging.getLogger(app.user) logger.addHandler(file_handler) # 画蛇添足结果就是日志既走root的handler又走logger自己的handler重复打印。正确做法是子logger只管用getLogger获取不要手动添加handler所有handler统一在root级别配置。实在需要单独控制的用Logger.propagate False阻断向上传递但一般用不到。5.2 异常处理器里自己也抛异常导致“异常套异常”全局兜底处理器的作用是捕获所有异常但如果你在异常处理器里访问了某些可能失败的资源比如又查了一次数据库、又写了日志文件一旦这些操作也失败了新异常会直接抛出前端拿到的响应又变成默认500了。所以异常处理器里只做最安全的事记录日志、返回固定JSON不要执行复杂逻辑。如果必须做额外操作用try except再包一层避免二次崩溃。5.3 日志时间比实际时间早了8小时Python的logging模块默认用本地时间但有些服务器环境变量设置不当会导致日志时间打印成UTC时间。排查问题的时候发现日志时间对不上特别容易乱。解决的方案是formatter里明确指定时区或者用更可靠的方案在日志格式化时用timezone字段import time class CustomFormatter(logging.Formatter): def formatTime(self, record, datefmtNone): ct time.localtime(record.created) if datefmt: return time.strftime(datefmt, ct) return time.strftime(%Y-%m-%d %H:%M:%S, ct)自己实现formatTime可以确保始终使用服务器本地时间。另外在配置日志时建议一开始就把文件名加上日期比如app-20240115.log这样日志切割后文件名就能直接看出是哪天的日志查找起来会方便很多。5.4 中间件里捕获的异常和exception_handler捕获的异常顺序这是个比较进阶的坑。FastAPI里异常处理的顺序是中间件包裹外层如果中间件在调用call_next时捕获到了路由函数里抛出的异常它有可能会“吞掉”这个异常导致全局异常处理器根本收不到。比如你如果这么写app.middleware(http) async def bad_middleware(request, call_next): try: response await call_next(request) except Exception: # 自己吞掉了异常异常处理器永远触发不了 response JSONResponse({code: 500, message: 错误}, status_code500) return response实际上这个中间件把异常响应用固定JSON替换了虽然看起来也能工作但日志上下文没了我们也拿不到原始异常信息。正确的做法是中间件里尽量不要捕获异常让异常由FastAPI的全局异常处理器统一处理。如果确实需要在中间件里记录错误日志那就catch之后重新raise出来不要吞掉。5.5 日志文件权限问题部署到Linux服务器上时如果启动服务的账号对logs目录没有写权限进程启动时不会直接报错但日志会静默丢弃。排查这个问题可以在启动时检查一下日志handler是否初始化成功或者直接chmod给logs目录写权限。我在某模拟项目X部署时就遇到过本地跑一切正常上服务器就死活不落盘最后发现是logs目录权限不对。6. 把“好维护”真正落到日常开发里写到这里整套异常处理和日志系统的内容就讲完了。我自己的体会是这个模块最神奇的地方在于把它搭好之后日常开发时候的“手感”完全不一样。以前接口报错要靠前端截图、靠用户描述去猜现在看日志文件从请求进来、参数校验、业务处理、异常抛出每一步都清清楚楚定位问题的时间从小时级缩短到了分钟级。尤其是当你的项目脱离了单机调试、真正部署到服务器上的时候这套东西的价值会成倍放大。本地开发时看控制台输出总觉得“这不挺好的吗”那是因为你心里清楚代码走到哪了到了线上你面对的唯一线索就是日志日志不好用写代码再顺手也等于在黑暗里摸路。最后再分享一个小技巧。日志配置里有一项我强烈建议从一开始就加进去在启动时打一条显眼的INFO日志把当前FastAPI版本、Python版本、启动时间、配置参数全部打出来。别小看这一条日志线上排查版本不匹配、配置没生效的问题时它是一条决定性线索。你可以把这条日志输出格式做得显眼一点用分隔线框起来这样一次部署是否成功、用了哪份配置打开日志一眼就知道了。这套体系搭好之后以后新开接口只需要做三件事业务逻辑里该抛异常就抛异常该记日志就记日志其他事情全部交给框架和中间件。维护成本降下来了出问题的概率自然也就小了。希望这篇教程能帮你把FastAPI项目的“地基”打结实后面再加什么功能都稳得住。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →