news 2026/9/14 2:49:03

FastMCP 全局日志配置:把 Codex 的模型通道改到 TaoToken 后让它改写 logger.py

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastMCP 全局日志配置:把 Codex 的模型通道改到 TaoToken 后让它改写 logger.py

在做 FastMCP 服务的全局日志配置时,真正让人头疼的不是 config.py 里加几个日志参数,而是你明明把 CustomRotatingFileHandler 写好了,Codex 却因为模型通道的 Key 在多个控制台之间切来切去,改 logger.py 改到一半就超时。与其反复折腾,不如先把 Codex 的模型通道统一接到 TaoToken 上,再让它按“按大小轮转、按日期轮转、自动清理旧日志、生产环境输出 JSON 并带 trace_id”的需求,把 config.py、logger.py、server.py 一次改完。TaoToken 是统一 API 兼容通道,打开 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 注册并创建 API Key,之后 Codex 的模型调用与 FastMCP 服务的日志消耗就能在同一个账单里对得上。

1. 全局日志配置的痛点不在 print,而在可持续性

1.1 一个提示词 FastMCP 服务的日志到底要解决什么

教程里把“提示词 FastMCP 服务之全局日志配置”单独提出来,说明它不只是给人看的,更是给系统看的。日常开发中你会发现,日志文件会越滚越大,排查一个请求的真实链路要 grep 半天;生产环境里文本日志难以接入日志平台;想监控某个函数到底跑了多久,只能手动写time.time()硬编码。更隐蔽的是:日志的轮转策略必须同时照顾大小和日期,因为单看文件大小会漏掉跨天清理,单看日期又会让单日超大文件不断膨胀。

FastMCP 服务本身就是长驻进程,一旦跑起来,日志就没有“人为干预”的机会。如果日志文件不切割、不清理,磁盘会被prompt_mcp_server.log撑爆;如果每条日志没有 trace_id,你只能靠时间戳去猜哪条日志属于哪次工具调用。所以这个章节才需要在 config.py 里增加日志级别、根路径、文件名、文件大小和保留天数,并在 logger.py 里实现一个能同时按大小和日期轮转、能自动清理过期文件、能输出结构化 JSON 的日志模块。

1.2 为什么先给 Codex 换通道,再让它改写 logger.py

这次实操的主角不是“手写代码”,而是把 Codex 当作一个能读文件、改文件、对照需求的执行 Agent。Codex 本身是模型驱动的,模型通道不通,它改代码就会反复超时、中断,导致你无法判断它到底改到哪一步。把 Codex 的模型通道改到 TaoToken 之后,至少有两个好处:一是请求稳定,Base URL 是固定入口,不再因为某个控制台临时抖动而断;二是用量统一,Codex 在改写 logger.py 过程中产生的 token 消耗,与后续 FastMCP 服务运行时的日志都能在 TaoToken 控制台里一起查到,对账不需要去数个平台分别翻。

这里的重点是把“改代码”和“跑日志”放进同一条链路:Codex 改完CustomRotatingFileHandler,你在本地启动服务,日志切割是否生效,回来再让 Codex 做下一轮调整。整个过程模型的消耗都走 TaoToken,避免一边跑代码一边为模型通道不稳定买单。

2. 准备材料:在 TaoToken 拿 Key,再把 Codex 指过去

2.1 官网只做一件事:注册、创建 Key、看模型广场

先打开 TaoToken 注册账号。进入控制台创建 API Key,创建后把 Key 复制到本地,全文统一用占位符YOUR_API_KEY表示。注意官网落地页只用于注册、创建 Key、查看模型广场和用量;真正需要填进 Codex 配置文件的是 Base URL:https://taotoken.net/api,末尾没有/v1,也不需要带任何 UTM 参数。

这里有一个容易混淆的点:很多人习惯把注册页网址直接粘进工具的 Base URL,结果 HTTP 请求发去了网页地址而不是 API 地址,自然 404。官网与接口是两回事:网页用于管理,/api用于程序调用。创建好 Key 后,去模型广场复制一个你常用的模型 ID,我们后面把它填进 Codex 的配置。模型 ID 以模型广场为准,不要自己拼一个版本名。

2.2 在 ~/.codex/config.toml 里加一个 TaoToken provider

Codex 的模型通道不是通过环境变量改 Base URL,而是在~/.codex/config.toml中定义 provider。下面是一个可直接落地的配置骨架,注意base_url只到https://taotoken.net/api,不拼/v1model占位YOUR_MODEL_ID需要替换成你在模型广场复制的 ID。

# ~/.codex/config.toml model = "YOUR_MODEL_ID" # 从 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 模型广场复制 model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY"

写好之后,在 shell 里导出 Key,让 Codex 运行时从环境变量里读取:

export TAOTOKEN_API_KEY="YOUR_API_KEY"

如果你本地 Codex 版本提示wire_api缺失,就按该版本的默认协议保留,不要自行编造。核心是:base_url必须是https://taotoken.net/api,Key 必须来自上一步创建的YOUR_API_KEY

2.3 先做一次最小调用,确认 Codex 真的能用

不要急着改代码,先跑一条简单指令验证通道。在项目根目录执行:

codex exec "用一句话解释 Python logging 的 RotatingFileHandler 和 TimedRotatingFileHandler 区别"

如果 Codex 返回正常,说明model_providerbase_urlenv_key三者已经配对成功。如果返回 400,多半是模型 ID 与模型广场不一致;如果返回 401,多半是TAOTOKEN_API_KEY没有正确导出。通道验证通过后,再给 Codex 下发改写任务,这时候它改代码才不会频繁断言。

3. 让 Codex 按原文需求改写三个文件

3.1 config.py 中新增日志配置参数

原文中日志配置落在src/prompt_mcp_server/config.py,核心是六个参数:日志级别、日志根路径、日志文件名、单文件大小、保留天数、调试开关。向 Codex 下达指令时,可以把下面这段作为“需求卡片”交给它:

# src/prompt_mcp_server/config.py log_level: str = Field(default="INFO", alias="LOG_LEVEL") log_root_path: str = Field(default="logs", alias="LOG_ROOT_PATH") log_file_name: str = Field(default="prompt_mcp_server.log", alias="LOG_FILE_NAME") log_file_size_mb: int = Field(default=50, alias="LOG_FILE_SIZE_MB") log_backup_data: int = Field(default=30, alias="LOG_BACKUP_DATA") debug: bool = Field(default=False, alias="DEBUG")

告诉 Codex:生产环境读/.env或环境变量中的LOG_LEVEL,开发环境用默认值;LOG_FILE_SIZE_MB控制单个日志文件大小阈值;LOG_BACKUP_DATA` 控制过期清理天数。这样 Codex 在启动时才能判断当前是开发模式还是生产模式,决定使用文本 Formatter 还是 JSONFormatter。

3.2 logger.py:让 Codex 实现按大小加日期双轮转

logger.py是整个章节里最需要“和 Codex 反复确认”的文件。核心有三个组件:CustomRotatingFileHandler继承TimedRotatingFileHandler,保留按午夜日期轮转的能力;在emit中先根据max_bytes判断是否需要按大小提前轮转;轮转后立即cleanup_old_logs清理超过保留天数的文件。给 Codex 的指令可以这样写:

在 src/prompt_mcp_server/logger.py 中实现 CustomRotatingFileHandler: 1. 继承 TimedRotatingFileHandler,when="midnight",interval=1,编码 UTF-8; 2. 每次写入前检查 current_size + 本条日志大小是否超过 max_bytes,如果超过则按大小执行一次轮转; 3. 按大小轮转时不继承父类默认文件名,改用 原文件名_时间戳_序号 的格式; 4. 每次轮转后调用 cleanup_old_logs,遍历日志目录,删除备份日在 LOG_BACKUP_DATA 之前的文件; 5. output 时读取 settings.is_production,为 True 时使用 JSONFormatter,否则使用文本 Formatter。

实现完成后,logger.py 里应该有这几个对外方法:get_logger(name)保证日志器已初始化并返回指定 logger;log_execution_time装饰器同时支持同步函数和异步函数,记录开始执行、完成执行、失败执行的耗时;如果使用 Logger 包装类,则每条日志自动注入一个trace_id。相比纯手写,让 Codex 来做的价值在于:你只要把需求边界讲清楚,它会把_LOGGER_INITIALIZED的幂等保护、第三方库的日志级别收敛、os.makedirs自动建目录这些细节一并补齐。

关键片段如下,可作为 Codex 输出后的对照检查:

class JSONFormatter(logging.Formatter): def format(self, record: logging.LogRecord) -> str: log_data = { "timestamp": datetime.now(timezone.utc).isoformat(), "level": record.levelname, "logger": record.name, "message": record.getMessage(), "module": record.module, "function": record.funcName, "line": record.lineno, } if getattr(record, "trace_id", None): log_data["trace_id"] = record.trace_id else: log_data["trace_id"] = str(uuid.uuid4()) if record.exc_info: log_data["exception"] = self.formatException(record.exc_info) return json.dumps(log_data, ensure_ascii=False)

JSON 输出中trace_id的出现,是确认日志链路是否打通的关键。如果日志里没有 trace_id,后续基于日志平台做全链路追踪就是空谈。

3.3 server.py 中使用日志服务

server.py是演示如何把这些日志能力串起来的地方。Codex 应该在这里创建一个 FastMCP 服务实例,并把get_logger拿到的 logger 用在业务函数里,再给测试函数加上@log_execution_time装饰器。参考结构如下:

# src/prompt_mcp_server/server.py import time from fastmcp import FastMCP from prompt_mcp_server.config import settings from prompt_mcp_server.logger import get_logger, log_execution_time logger = get_logger(__name__) mcp = FastMCP(name=settings.prompt_mcp_server_name) @log_execution_time def test_logger(): time.sleep(1) logger.info("日志打印测试") if __name__ == "__main__": test_logger()

这样做的效果是:每次调用test_logger都会输出两条日志,一条是“开始执行”,一条是“完成执行(耗时 1.00 秒)”,并且这两条日志在 JSON 模式下都会带上同一个trace_id。用这个 trace_id 去日志文件里 grep,能看到一次调用的完整生命周期,而不需要再去猜时间戳。

4. 验证:运行服务后应该看到切割后的日志文件

4.1 开发模式先用文本日志确认基本链路

在项目根目录执行:

python -m src.prompt_mcp_server.server

如果DEBUGTrue,日志目录下会生成logs/prompt_mcp_server/prompt_mcp_server.log,控制台输出类似:

2025-03-02 10:15:00 12345 [INFO ] prompt_mcp_server.server.test_logger:16 - 开始执行: test_logger 2025-03-02 10:15:01 12345 [INFO ] prompt_mcp_server.server.test_logger:18 - 完成执行: test_logger, 耗时: 1.00秒

此时logger.py的初始化逻辑已经跑通,get_loggerlog_execution_time、文件 handler 都在正常工作。

4.2 切换生产模式验证 JSON 输出

把环境变量DEBUG设为false,重启服务。此时日志应变成 JSON 行,每条日志都包含timestamplevelloggermessagetrace_id字段。你可以在终端里做一次精确验证:

python -m src.prompt_mcp_server.server | grep "完成执行"

返回的结果中应当出现"message": "完成执行: test_logger, 耗时: 1.00秒",并伴随一个非空的trace_id。这说明JSONFormatter已经接管了日志输出,生产环境的结构化日志需求被满足了。

4.3 确认真实切割而不是“看起来能用”

验证轮转不需要等 30 天。临时把LOG_FILE_SIZE_MB调整为 1,然后在test_logger里循环写日志,写入超过 1MB 后,观察日志目录:

ls logs/prompt_mcp_server/

你会看到类似prompt_mcp_server_20250302_101500_1.log这样的文件,而不是所有内容都堆在同一个prompt_mcp_server.log里。再检查日期切分,可以让服务跨天运行,或者直接手动修改系统时间(不推荐在共享环境做)验证。这里需要明确的结论是:按大小轮转生成_时间戳_序号文件,按日期轮转生成_日期_235959_序号文件,两份文件保留策略都由LOG_BACKUP_DATA统一控制。

5. 排障:Codex 返回 400、日志轮转在 Windows 上报 PermissionError

5.1 Codex 调用时报 400 而不是正常回答

之前最小调用我们已经验证过通道,如果你在让 Codex 改写代码时才开始 400,大多数情况是model字段在 config.toml 里写的模型 ID 与模型广场不一致。这时候去 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 的模型广场复制准确 ID,替换YOUR_MODEL_ID后重启 Codex。还有一种情况是你在base_url后面的路径写成了/api/v1,请去掉v1,只保留https://taotoken.net/api

5.2 日志轮转时遇到 PermissionError

Windows 上如果日志文件被另一个进程打开,os.rename会抛出PermissionError。Codex 生成的实现里应该包含降级策略:先尝试关闭当前stream,再执行重命名;如果文件仍被占用,则复制原文件到新文件,然后清空原文件。不必要求一次轮转一定成功,但至少要保证服务不因为日志轮转而崩溃。如果你的项目运行在 Linux 服务器上,PermissionError往往来自运行用户对logs/prompt_mcp_server目录没有写权限,直接chown或把日志目录归属到当前用户即可。

5.3 日志文件创建了,但 JSON 输出全是单行堆叠

JSON 格式化输出在日志采集系统里是友好的,但人看起来费力。如果你在开发环境想读文本,又不想改代码,可以在启动服务时把DEBUG设回True。这样ensure_logger_initialized会使用文本 Formatter,而不会影响生产环境的 JSON 输出。Codex 在实现时会把settings.is_production的判断放在 handler 设置处,所以不要手动去logger.py里删掉 JSONFormatter,否则生产环境结构化的能力就丢了。

6. 回控制台看这次 Codex 改日志消耗了多少

这次全链路里,从 Codex 读取 config.py、生成CustomRotatingFileHandler、补全 JSONFormatter,到你在本地反复启动服务观察日志,每一步的模型请求都会在 TaoToken 控制台留下用量记录。跑通之后回到 https://taotoken.net/?utm_source=taotoken_aicg_blog_end 看一眼“用量”页面,你会发现“让 Codex 改 logger.py”这件事本身是可量化的,不是一笔糊涂账。后续如果还想优化LOG_BACKUP_DATA或把trace_id注入到更上层的请求入口,也可以直接在控制台里查看这次调用用的模型 ID,再去模型广场确认有没有更适合长上下文任务的模型,换完配置文件重启 Codex 即可。这样日志系统改完,链路也验完,账也核上了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 2:47:42

STM32驱动DS1302实时时钟:GPIO模拟时序从零实现

1. 项目背景与整体设计思路 做嵌入式开发的同学,几乎都会遇到需要给设备加一个“时间戳”的场景。不管是做数据采集器、智能家居网关,还是毕业设计里的电子时钟,都绕不开实时时钟(RTC)这颗小芯片。市面上常见的RTC方案…

作者头像 李华
网站建设 2026/9/14 2:47:05

SpringBoot+Vue全栈在线教育系统开发实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 2:45:53

AI 大模型全景解析进阶指南,让走 TaoToken 的 Codex 帮你划重点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/14 2:45:30

基于U-Net的风机叶片语义分割实战:从数据预处理到推理部署

简介:面向风电叶片监测场景的风扇语义分割数据集及配套Python训练代码,适合计算机视觉研究人员、风电运维算法工程师及深度学习者使用。全部数据由1994个tif文件构成,包含风扇叶片图像及对应标签图,涵盖多种工作环境和光照条件&am…

作者头像 李华
网站建设 2026/9/14 2:42:34

FastExcel替代EasyExcel:高并发Excel导出性能优化实战

1. 项目概述:从EasyExcel切换到Apache Fesod的真实动因“再见了EasyExcel,我决定用Apache Fesod”——这句话不是标题党,而是我在连续三个高并发Excel导入导出项目踩坑后,亲手写下的技术迁移声明。过去五年,我主导过12…

作者头像 李华
网站建设 2026/9/14 2:40:49

光学系统设计:波段、光源、光纤与探测器匹配指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华