1. 项目概述:为什么 FastapiAdmin 要把日志体系单独拎出来讲
FastapiAdmin 这类基于 FastAPI 的后台管理框架,这几年在 Python 后端圈子里热度一直不低。它把路由、权限、模型管理、API 文档这些重复性极高的活一次性打包好,开发者拿到手就能快速搭起一套可用的后台系统。但真到了上线阶段,大家会发现一个尴尬的事实:框架帮你省掉的每一分钟,最后都会在排查问题上加倍还回去。
排查问题的第一道防线是什么?不是 debugger,不是 IDE 的断点,而是日志。日志体系建得好不好,直接决定一个线上事故从发生到定位需要 5 分钟还是 5 小时。FastapiAdmin 在这方面做了比较完整的设计,但默认配置只保证“能跑”,距离“好用”还有一段距离。这篇文章我想把我实际部署和调优过程中的经验整理出来,重点拆解这套日志体系的工作机制,以及那些真正影响运行行为的核心配置参数。适合正在用 FastapiAdmin 开发项目、准备上线,或者已经被线上日志搞得焦头烂额的同学参考。
先把结论放前面:这套日志体系的核心价值不在于它打了多少日志,而在于它把日志分成了四条互不干扰的链路——请求访问日志、操作审计日志、错误异常日志、SQL 执行日志。每条链路独立开关、独立格式、独立存储,互不拖累。理解了这条主线,后面所有配置参数都能串起来。
2. 整体设计思路拆解:日志体系的结构与分工
2.1 四条日志链路的分工逻辑
我先用一张表把这四条链路的核心定位讲清楚,后面所有配置参数都是围绕这个分工展开的:
| 日志链路 | 职责范围 | 典型输出内容 | 主要消费场景 |
|---|---|---|---|
| 访问日志 | HTTP 请求生命周期 | 请求方法、路径、状态码、耗时、客户端 IP、User-Agent | 接口监控、流量分析、异常请求追溯 |
| 审计日志 | 用户操作行为 | 操作人、操作时间、操作对象、变更前后内容 | 合规审计、内部追责、安全事件回溯 |
| 错误日志 | 系统异常与错误 | 异常类型、堆栈信息、触发接口、上下文参数 | 故障定位、Bug 修复、稳定性建设 |
| SQL 日志 | 数据库操作记录 | SQL 语句、执行参数、执行耗时、影响行数 | 慢查询分析、数据问题排查、性能优化 |
这四条链路为什么不能混在一起?我见过不少项目把所有日志都打到同一个文件里,看起来省事,实际上灾难。访问日志的量级通常是最大的,一天几百万条很正常;错误日志虽然量少,但每一条都是金子;SQL 日志有些库一小时内就能给你撑爆磁盘。混在一起最直接的后果是:你想查一个报错,得从几万条访问记录里翻;你想做接口耗时分析,又会被 SQL 输出刷屏。所以链路分离不是洁癖,是刚需。
FastapiAdmin 的实现思路也比较清晰,它不是把日志全塞给 Python 自带的 logging 模块然后听天由命,而是在中间件层、依赖注入层、ORM 事件层分别做拦截,从不同切面把日志数据采集上来。这就好比你请了四个保安,一个守大门(访问日志),一个跟在你身后记录你干了什么(审计日志),一个专门盯着报警器(错误日志),还有一个蹲在数据库门口数进出时间(SQL 日志)。分工明确,谁也不抢谁的活。
2.2 为什么基于 logging 模块而不是另起炉灶
有些框架图省事直接 print,有些框架过度设计自己实现了一套日志库,FastapiAdmin 选的是 Python 标准库logging作为底座,然后在上层做配置封装。这个选择我认为是合理且务实的。
标准库 logging 虽然配置起来啰嗦,但胜在稳定、可靠、生态兼容好。任何第三方库只要接入了 logging,就能无缝纳入你的日志体系。比如你引入了httpx、sqlalchemy、celery,这些库内部用的都是 logging,你只要在 FastapiAdmin 的配置里调整对应的 logger 层级,就能统一控制它们的输出行为。如果你自己另搞一套,第三方库的日志就全部掉进黑洞。
另外,logging 模块天然支持多 Handler 分发、Formatter 自定义、Filter 过滤器、级别传播控制这些能力。FastapiAdmin 的日志体系本质上就是把 logging 的这些底层能力做了一层业务化的封装,让它更适合 Web 后端场景。理解了这层关系,你以后想扩展任何自定义日志行为,都会顺手很多。
2.3 配置与代码的边界感
FastapiAdmin 把日志相关的配置项集中在配置类里,而不是散落在业务代码各处,这一点对维护极其友好。比如你想调整访问日志的格式、改变日志文件切分频率、关闭某个链路的输出,只需要改配置文件,业务代码一行不用动。
这听起来很简单,但很多项目做不到。我见过有人把日志级别写在业务函数里硬编码,也有人把日志文件路径写死在工具类中,结果上线后发现日志打到临时目录,重启一次全没了。FastapiAdmin 的做法是:业务代码里只负责发出日志记录请求——logger.info(...)或logger.error(...)——至于这些记录最终输出到哪儿、用什么格式、什么级别才放行,全部由配置层决定。职责单一,页面”干净“,排查问题也方便。
3. 核心配置参数逐项拆解:每一个参数背后的逻辑
3.1 日志级别参数:别在生产环境打 DEBUG
配置日志级别的参数在不同版本的 FastapiAdmin 中位置可能略有差异,但核心逻辑是一致的。通常你会看到类似LOG_LEVEL或者LOGGING_LEVEL这样的配置项,它控制的是全局日志输出的最低级别。
这里有个常见的误解:日志级别不是越高越好,也不是越低越好。DEBUG级别会输出海量的调试信息,INFO级别打印关键流程,WARNING只输出潜在风险,ERROR只记录真正的错误。我见过有同学在生产环境把级别调成 DEBUG,原因是“想看详细一点”,结果磁盘半天就被打满,应用直接宕机。这不是开玩笑,是真的会发生的事。
我的建议是:开发环境用DEBUG,测试环境用INFO,生产环境至少用WARNING。如果你希望在生产环境保留接口调用记录,把访问日志链路的级别单独设为INFO,错误链路设为ERROR,不要全部依赖一个全局级别控制。FastapiAdmin 的配置是支持链路级覆盖的,一定要利用好这个能力。
3.2 日志格式参数:可读性和可解析性要兼顾
日志格式直接决定了你后续能不能高效地分析日志。FastapiAdmin 默认的日志格式一般包含时间、级别、模块、行号、消息内容,类似这样:
2025-06-01 14:23:45,678 INFO [app.routers.user:123] 用户 xxx 创建成功这个格式在本地开发调试时很够用,人眼扫过去就能定位问题。但一旦到了生产环境,日志量上来之后,你会发现这种格式对自动化采集和分析极不友好。你需要的是结构化的字段,比如 JSON 格式,方便接入 ELK、Loki 这类日志平台。
我实际是这样处理的:本地开发用默认的文本格式,生产环境把访问日志和错误日志的 Formatter 改成 JSON 结构,包含timestamp、level、logger、message、trace_id、path、method、status_code、duration_ms这些字段。这样接入日志平台后,直接可以用字段筛选和聚合,搜索效率提升不是一点半点。
格式参数虽然看起来只是字符串拼接的问题,但它决定了日志体系的上限。如果你从一开始就规划好结构化字段,后面做告警规则、做统计报表都会非常顺畅。
3.3 日志切割参数:磁盘空间是你最容易忽略的坑
日志切割,也叫日志轮转,是生产环境必须要配置的能力。Python logging 标准库提供了TimedRotatingFileHandler和RotatingFileHandler两种方案,前者按时间切分,后者按大小切分。FastapiAdmin 一般把这两个方案的参数都暴露在配置类里。
我的经验是:优先按大小切分,辅以时间维度保留。因为单纯的按时间切分有个问题——某天流量突增,日志量爆炸,一个文件几百 MB,打开都费劲,更别说分析。按大小切分可以让每个日志文件保持在一个可操作的体积,比如 100MB 一个文件,最多保留 10 份,旧的自动删除。
具体参数逻辑参考:LOG_MAX_BYTES控制单个文件的最大体积,LOG_BACKUP_COUNT控制保留的文件数量。假设你配置LOG_MAX_BYTES = 100 * 1024 * 1024,即 100MB,LOG_BACKUP_COUNT = 10,那么单条日志链路最多占用 1GB 磁盘空间。四条链路加起来就是 4GB。这个数字你要心里有数,否则磁盘满了应用挂了还不知道怎么回事。
3.4 数据库日志参数:SQLAlchemy 的 echo 别乱开
FastapiAdmin 的 SQL 日志链路,很多情况下是通过控制 SQLAlchemy engine 的日志输出实现的。SQLAlchemy 提供了一个echo参数,设为True会打印所有 SQL 语句,设为False则关闭。但直接通过配置项设为True有个问题:它是在 engine 级别强制开启,输出的 SQL 日志会绕过你精心设计的日志格式,直接用 SQLAlchemy 自带的格式刷屏。
更稳妥的做法是把echo保持为False,然后通过控制 SQLAlchemy logger 的级别来控制 SQL 输出。FastapiAdmin 的配置往往提供类似SQL_ECHO这样的参数,实质上是在 engine 创建时传入echo=True或echo=False。
如果你确实需要在开发阶段查看 SQL,建议单独配置一个sqlalchemy.engine的 logger,级别设为INFO,并且用独立的 Handler 输出到单独的文件。这样和生产配置互不干扰,排查 SQL 问题也方便,不用在满屏访问日志里翻 SQL。
3.5 异步与同步日志:性能损耗不可忽视
默认情况下,Python logging 的输出是同步的。也就是说,业务代码发起一个logger.info()调用,日志必须写入文件之后,业务代码才能继续往下执行。在高并发场景下,磁盘 I/O 成为瓶颈,日志写入的耗时会被放大,直接影响接口响应时间。
FastapiAdmin 在异步日志方面做了一定的支持,通常通过参数控制是否启用异步写入,比如LOG_ASYNC_ENABLED。原理上,它把日志写入操作交给后台线程或队列处理,业务线程只需要把日志消息放进队列就可以继续跑,写入磁盘由专门的消费者线程完成。
但这里我要泼一盆冷水:异步日志不是银弹。日志队列容量有限,如果日志量超过消费能力,队列会满,数据会丢失。而且日志丢失往往是“悄无声息”的,你都不知道丢了什么。所以在采用异步写入时,一定要同时配置合理的队列大小、消费线程数,并在关键错误日志上保留同步写入路径,确保重要错误不会丢。
3.6 Trace ID 与请求关联:多日志链路串联的关键
HTTP 请求从进入到返回,会经过中间件、路由处理、数据库操作、外部 API 调用等多个环节。如果每个环节各打各的日志,没有统一的关联标识,那么排查问题时你就得靠时间戳人工拼线索,效率极低。
FastapiAdmin 的日志体系里,一个非常关键的配置是 Trace ID 的生成与传递。通常在中间件层为每个请求生成一个唯一的trace_id,然后把它注入到日志上下文里。后续这个请求产生的所有日志,无论属于哪条链路,都会自动携带这个trace_id。排查问题时,只要拿到一个trace_id,就能把访问日志、错误日志、SQL 日志整条串起来看。
配置上需要注意的点是:Trace ID 作为中间件从请求头中读取还是每次全新生成,以及是否在响应头中返回给调用方。我建议读取和回写都做——外部调用方传入X-Trace-ID就沿用,没传就自己生成,响应头里返回,这样前端调用链排查也能对得上。
4. 实操过程:落地一套完整可用的日志配置
4.1 基础配置的初始化
第一步,先确认当前 FastapiAdmin 实例的配置入口。通常在创建应用实例时传入配置对象,类似这样:
from fastapi_admin.app import FastAPIAdmin app = FastAPIAdmin( config=MyAdminConfig() )在MyAdminConfig里,日志相关的参数集中定义。基础版配置可以参考:
from fastapi_admin.config import AdminConfig class MyAdminConfig(AdminConfig): LOG_LEVEL = "INFO" LOG_FORMAT = "%(asctime)s %(levelname)s [%(module)s:%(lineno)d] %(message)s" LOG_MAX_BYTES = 100 * 1024 * 1024 # 100MB LOG_BACKUP_COUNT = 10 LOG_DIR = "logs/" SQL_ECHO = False这套配置含义是:全局日志级别 INFO,输出到logs/目录,按 100MB 切割,保留 10 份。注意LOG_DIR在项目启动前要保证存在,代码不会自动创建目录,目录不存在日志文件是写不进去的。这一点我踩过坑,第一次部署时忘了创建 logs 文件夹,启动后日志静默丢失,排查问题查了半天才发现是目录问题。
4.2 为四条链路分别配置 Handler
只配置全局参数还不够,四条链路的独立性要靠独立的 Handler 来保证。实务中我会在配置里创建多个 Handler,每个 Handler 绑定一个 logger:
import logging from logging.handlers import TimedRotatingFileHandler # 访问日志 logger access_logger = logging.getLogger("fastapi_admin.access") access_handler = TimedRotatingFileHandler( "logs/access.log", when="MIDNIGHT", backupCount=30, encoding="utf-8" ) access_handler.setFormatter(logging.Formatter( "%(asctime)s | %(message)s" )) access_logger.addHandler(access_handler) access_logger.setLevel(logging.INFO) # 错误日志 logger error_logger = logging.getLogger("fastapi_admin.error") error_handler = TimedRotatingFileHandler( "logs/error.log", when="MIDNIGHT", backupCount=30, encoding="utf-8" ) error_handler.setFormatter(logging.Formatter( "%(asctime)s | %(levelname)s | %(name)s | %(message)s" )) error_logger.addHandler(error_handler) error_logger.setLevel(logging.ERROR)这里我把访问日志和错误日志分文件输出,方便做不同的保留策略。比如访问日志量大,可以只留 7 天,错误日志量小,重要程度高,留 90 天。这些策略完全取决于你的业务需求,没有绝对标准,但一定要提前规划,别等磁盘报警了再处理。
4.3 中间件中注入访问日志与 Trace ID
访问日志的采集点放在中间件层是最合适的。所有请求都必须经过中间件,不会漏。中间件里记录请求时间、路径、方法、状态码、耗时,同时生成或读取 trace_id:
import time import uuid from starlette.middleware.base import BaseHTTPMiddleware class AccessLogMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): trace_id = request.headers.get("X-Trace-ID", str(uuid.uuid4())) request.state.trace_id = trace_id start = time.time() response = await call_next(request) duration_ms = (time.time() - start) * 1000 access_logger.info( f"trace_id={trace_id} | method={request.method} | " f"path={request.url.path} | status={response.status_code} | " f"duration_ms={duration_ms:.2f} | client={request.client.host}" ) response.headers["X-Trace-ID"] = trace_id return response这段代码里有个小细节:request.state.trace_id是 Starlette 提供的 Request state 机制,可以在中间件中写入,在后续的视图函数或依赖中读取。这样业务代码里加日志时就能把 trace_id 带出来了。
访问日志的格式,我建议至少包含 trace_id、method、path、status、duration_ms、client IP 这六个字段。前五个字段是接口监控的标配,client IP 是安全排查的必备信息。如果还想做地理位置分析,可以把 User-Agent 也加进去。
4.4 依赖注入实现操作审计日志
操作审计日志和访问日志不一样,它关心的是“谁在什么时间对什么资源做了什么操作”,语义级别更高。FastapiAdmin 中实现审计日志的推荐方式是在写操作的接口里通过依赖注入来记录。
需要记录审计日志的操作包括:创建、修改、删除、导入导出、权限变更、配置修改等。不记录的是查询操作——查询操作量太大,而且不改变系统状态,记录意义有限。
实现代码大致长这样:
from fastapi import Depends, Request def audit_log(action: str): async def record(request: Request, admin_user=Depends(get_current_user)): def commit(): audit_logger.info( f"user_id={admin_user.id} | username={admin_user.username} | " f"action={action} | resource={request.url.path} | " f"trace_id={request.state.trace_id}" ) return commit return record这里返回一个闭包,在业务逻辑成功后调用commit()写入审计日志。为什么不在操作前记录?因为操作可能失败,如果先记录了“创建成功”,后面实际执行报错,就是脏数据。审计日志里只应该出现真实发生且成功的操作,失败的操作交给错误日志去记录。
4.5 错误日志与全局异常处理器
FastapiAdmin 中未捕获的异常最终会交给全局异常处理器。在这个处理器里记录错误日志是最佳位置,因为这里能看到完整的异常堆栈,同时可以拿到触发异常的请求上下文。
from fastapi import Request from fastapi.responses import JSONResponse from fastapi.exceptions import RequestValidationError async def global_exception_handler(request: Request, exc: Exception): error_logger.error( f"trace_id={request.state.trace_id} | " f"path={request.url.path} | method={request.method} | " f"error_type={type(exc).__name__} | error_msg={str(exc)}", exc_info=True ) return JSONResponse( status_code=500, content={"detail": "Internal Server Error"} )exc_info=True很关键,它会自动把当前异常堆栈加入日志记录,这样排查时能看到具体的错误行号。如果省略这个参数,日志里只有异常类型和消息,堆栈信息丢失,定位问题的成本会高很多。
注意,用户主动触发的 4xx 错误(比如参数校验不通过)不需要记录堆栈,一旦记录会导致错误日志被无效信息刷屏。这类错误只需要记录请求信息,不记录堆栈。
4.6 SQL 日志的配置与性能平衡
SQL 日志的采集方式,取决于底层用的是 SQLAlchemy ORM 还是 Core。FastapiAdmin 默认走 SQLAlchemy,所以配置落地可以这样处理:
logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING)这个配置是所有 SQLAlchemy 日志的开关总闸。设为 WARNING 后,SQL 语句默认不打;如果某个阶段需要排查 SQL 问题,可以临时降到 INFO,看 SQLAlchemy 的日志,排查完再改回来。
如果希望对每条 SQL 的耗时做精细化记录,我会在自己封装的数据库访问层统一记录,而不是依赖 SQLAlchemy 自身的日志。原因在于 SQLAlchemy 自身日志只输出 SQL 语句和参数,不输出 SQL 的总执行耗时(Roundtrips 耗时)。自己包一层 after_execute 事件监听,能拿到更完整的性能数据:
from sqlalchemy import event from sqlalchemy.engine import Engine import time @event.listens_for(Engine, "before_cursor_execute") def before_execute(conn, cursor, statement, parameters, context, executemany): conn.info.setdefault("query_start_time", time.time()) @event.listens_for(Engine, "after_cursor_execute") def after_execute(conn, cursor, statement, parameters, context, executemany): total_ms = (time.time() - conn.info["query_start_time"]) * 1000 sql_logger.info( f"sql={statement[:200]} | params={parameters} | ms={total_ms:.2f}" )日志对 SQL 语句做了截断(200 字符),避免超大 SQL 写满日志文件;参数也直接放在日志里,方便复现问题。老项目里经常有几千字符的超长 SQL,不截断会非常占空间。
5. 常见问题与排查实录:生产环境踩过的坑与避坑技巧
5.1 日志不输出:先查 Logger 名称再查级别
最常见的“日志不打印”问题,90% 出在 logger 名称不匹配。FastapiAdmin 内部基础 logger 可能在fastapi_admin下,子模块是fastapi_admin.routers。如果你自定义 logger 用了别的名字,就没有加载到框架配置的 Handler,自然没有输出。
排查方式:先打开 DEBUG 级别观察logging自身日志,它会告诉你 logger 的层级传播关系。或者直接在当前代码里打印logger.handlers,看看有没有绑定 Handler,再检查logger.disabled是否被误设。这两个检查点能覆盖九成的问题场景。
另外,Python 的 logger 是存在传播链的。一个 logger 默认会向父 logger 传播日志记录。如果你给子 logger 加了 Handler,但父 logger 的 Handler 也在,就可能出现日志重复输出。配置时要注意设置logger.propagate = False(在 Handler 配置处设置),防止日志双写。
5.2 日志时间不对:时区问题
FastapiAdmin 的日志时间默认取服务器本地时间。如果你的服务器部署在 UTC 时区,日志时间就比北京时间慢 8 小时。排查问题时你会以为系统刚报错,其实已经是 8 小时前的事。
解决办法有二:一是把日志 Formatter 的datefmt显式指定为带时区信息的格式,类似%Y-%m-%d %H:%M:%S %z;二是在应用启动时统一设置timezone。风控要求严格的项目,我建议把日志时间统一存 UTC,展示层再转本地时间,避免不同环境时区不一致导致的混乱。
5.3 日志文件权限与编码问题
日志文件打不开或写入乱码,通常和两件事相关:文件权限和编码。
权限问题:如果应用以 root 账户启动,但日志目录只允许特定用户写入,或反过来普通用户启动但日志文件 root 所有,都会导致写入失败。FastapiAdmin 官方文档通常推荐用专门的应用账户运行服务,日志目录的所有者要和应用启动用户一致。
编码问题:Python 写入日志文件时,如果 Formatter 中的某些字符(比如中文)无法被文件编码支持,会直接报UnicodeEncodeError。这不是不常见,尤其在一些旧版 Python 环境或 Windows 部署场景下。解决方式是创建 Handler 时明确指定encoding="utf-8"。
5.4 高并发下日志队列积压
异步日志队列积压是不容易察觉的问题。日志写入跟不上请求量时,队列会持续增长。如果消息积压太多,内存也会同步上涨,甚至会导致 OOM(内存溢出)。
我的观察建议:给异步日志队列加监控,队列长度周期性暴露到监控面板上。如果发现队列持续增长,优先扩容消费线程数,其次检查磁盘 I/O。磁盘是硬瓶颈,I/O 打满的情况下日志消费速度一定跟不上,这个是硬指标。
另外一个思路是按链路分析日志量占比。实际观察中,访问日志的量往往占 80% 以上。如果访问日志不是硬性审计需求,生产环境可以把访问日志的级别从 INFO 提到 WARNING,只记录 4xx/5xx 的异常请求,留错误排查线索,量能降一大半。
5.5 SQL 日志泄露敏感数据
这个坑必须单独提醒:SQL 日志会完整输出 SQL 语句和参数,如果表结构里有用户手机号、身份证号、密码哈希等信息,SQL 日志相当于把明文敏感数据写进了日志文件。一旦日志文件被拖库,等于数据泄露。
如果项目涉及敏感数据,建议在 SQL 日志链路加 Filter,对 SQL 语句中的敏感字段值做掩码处理(比如把密码哈希替换为***),或者干脆生产环境关闭 SQL 日志输出。审计需求如果有,也要落在审计日志链路里做好字段脱敏处理。
5.6 日志文件清理失败
日志轮转策略配置后,如果发现文件没有被自动清理,通常是两个原因:一是文件被进程占用导致删除失败,这在 Windows 环境尤其常见;二是轮转策略的时区/时间配置问题,when="MIDNIGHT"是按服务器本地时区执行,如果服务器时区变了,轮转时间也会变。
排查方式比较直接:检查日志文件的最后修改时间,看轮转是否如期触发,再检查删除失败的报错。修改时间不对说明轮转逻辑的问题,可以调整时区配置;删除失败则考虑在日志维护时间错开业务高峰期,避免文件被占用。
6. 日志体系的灵活扩展:让日志为业务发挥更大价值
6.1 基于日志的接口 SLA 监控
日志不仅是排错工具,也是可靠的业务监控数据源。访问日志里有每个接口的耗时,可以做接口响应时间的持续监控。做法很简单:日志平台定期跑聚合查询,统计每个 path 的平均耗时、P95、P99,超过阈值就触发告警。
FastapiAdmin 访问日志里的duration_ms字段就是为这个设计的。没有这个数据,你是无法感知接口性能劣化的,只能等用户投诉“页面打不开”你才发现。有了这个数据,你可以在 P99 从 200ms 涨到 500ms 时提前介入,而不是等它涨到 5 秒才慌忙救火。
6.2 审计日志与操作回溯
有些行业合规要求所有敏感操作必须可追溯到人。FastapiAdmin 的审计日志链路配合权限系统,可以做到:某年某月某日谁用什么账号删除了什么数据、改了什么配置、导出了哪些数据。
这类需求在金融、医疗、政务等行业尤其常见。审计日志文件要“写后只读”,不能随意覆盖或删除。可以在日志目录权限上做限制,甚至配合对象存储做归档。另外审计日志建议定期打快照,存到独立的存储中,跟应用存储分开,避免应用故障连带审计数据丢失。
6.3 告警:从“事后查日志”到“实时告警”
日志体系建好了之后,下一步自然是告警。现在主流的日志平台(如 Loki、ELK、云日志服务)都支持基于日志内容的告警规则。你可以基于 FastapiAdmin 的错误日志做规则:
- 错误日志中
error_type为OperationalError的次数 5 分钟超过 10 次,触发数据库异常告警 status_code为 500 的接口占比超过 1%,触发服务异常告警- SQL 日志中
ms超过 1000 的 SQL 次数 5 分钟超过 5 次,触发慢查询告警
告警规则的阈值设置需要根据业务实际情况动态调整,不要一上来就想做高精度预测模型。先跑规则告警,积累数据,再逐步优化阈值和收敛噪声。
告警要和响应流程配套:收到告警后,按 trace_id 拉取该请求的完整调用链日志,定位问题环节,然后决定是处理代码 Bug、扩容还是优化 SQL。这套流程跑顺之后,平均故障定位时间能从半小时压缩到几分钟。
7. 我的一点个人体会
FastapiAdmin 这套日志体系,值得花时间把它调好。我见过太多项目只在本地开发时看一眼日志,上生产后完全靠运维手工翻文件。日志体系的投入并不是直接做业务功能,它的回报体现在每一次线上故障的排查速度上,这个隐性收益比大多数人都想象得更重要。
实际操作中,我建议任何团队拿到 FastapiAdmin 的默认配置后,第一件事不是急着加业务代码,而是把日志按本文的四条链路拆开、格式定好、切割策略配好、目录权限建好、Trace ID 贯通。这套基础设施花半天搭完,后面半年你都会感谢这半天。
另外,日志配置完成后一定要做一次“断电测试”,把一条链路故意关掉、把日志写满、把服务重启几轮,模拟真实故障场景下的日志表现。纸上谈兵一点用都没有,日志体系健壮不健壮,故障演练一下见真章。