ThinkPHP 6 的 Log::write() 记录不到 API 请求,因日志默认延迟刷盘,需调用 Log::save() 或启用 'realtime_write';原始请求体须在首层中间件用 php://input 获取;Swoole 下需设 'single'=>true 防日志混叠;耗时需手动打点计算,敏感字段须脱敏、body 要截断。

ThinkPHP 6 的 Log::write() 为什么记录不到 API 请求?
默认中间件不触发日志写入,Log::write() 在控制器里调用也常被忽略——因为 TP6 的日志默认只在应用生命周期结束时批量刷盘,而 API 接口(尤其 JSON 返回)可能提前响应并终止流程,导致日志丢失。
- 必须在响应发送前强制刷新日志:调用
Log::save()或设置'realtime_write' => true在config/log.php - 不要在
__destruct()或finally块里依赖日志自动保存,TP6 的Log类不保证此时上下文完整 - 验证是否生效:临时加一行
Log::write('test', 'api');并立刻访问接口,再查runtime/log/api/下是否有当天文件
用中间件统一记录请求日志,但 Request 对象拿不到原始 body?
TP6 的 Request 在中间件中已解析过 body,$request->post() 或 $request->param() 是解码后数据,原始 JSON、XML 或二进制内容已被消耗。审计需要原始请求体时,必须在最外层中间件用 file_get_contents('php://input') 捕获。
- 注册中间件顺序要靠前:放在
app/middleware.php的第一项,避免被其他中间件提前读取php://input - 注意 Content-Type:只有
application/json、text/xml等非表单类型才需手动读取;application/x-www-form-urlencoded和multipart/form-data仍可用$request->param() - 别直接记录完整 body:大文件上传或长文本会撑爆日志,建议截取前 1024 字节 + 标记长度,例如
substr($raw, 0, 1024) . ' [+' . strlen($raw) . ' bytes]'
think-swoole 下日志异步写入导致时间戳错乱、请求混叠?
Swoole 长生命周期中,TP6 默认日志驱动(File)的静态变量未重置,多个请求共用同一日志缓冲区,造成时间戳滞后、不同请求日志挤在同一行。
- 禁用日志合并:在
config/log.php中设'single' => true,避免多请求写入同一文件句柄 - 强制按请求隔离:给每条日志加唯一标识,例如从
$request->header('x-request-id', uniqid('req_'))取 ID,写入时拼在开头 - 不要用
Log::channel('api')->write()动态切通道——Swoole 下通道实例会被复用,应改用独立配置项如'channels' => ['api' => [...]]并确保path路径含{date}或{request_id}
审计日志字段不够用,想加用户 ID、IP 归属地、响应耗时,但 Response 对象在中间件里拿不到耗时?
TP6 中间件执行时响应尚未生成,$response->getElapsedTime() 返回 0。耗时必须用前后时间差计算,且 IP 归属地等外部查询不能阻塞主流程。
- 用
microtime(true)手动打点:在中间件handle()开头记$start = microtime(true),结尾算round((microtime(true) - $start) * 1000, 2)得毫秒数 - 用户 ID 尽量从 Token 解析,而非 DB 查询:若用 JWT,直接解析 header.payload;若用 session,确保中间件在
SessionInit之后 - IP 归属地异步处理:记录原始
$request->ip(),归属地查库或调 API 放到队列(如think-queue),日志里只存 IP 和占位符,后续补全
真正难的不是记下什么,是日志字段一旦写进磁盘就无法反悔——比如漏了 trace_id、误把敏感字段明文落库、或没控制 body 截断长度,线上跑一周后才发现,基本没法回溯修正。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










