ThinkPHP 6 需自定义继承 think\log\driver\Driver 的 ELK 日志驱动,实现 write() 方法,通过 HTTP(设超时≤300ms)发送结构化 JSON(含 level、message、context、time、app、env、server_ip 等字段),禁用重试/队列/本地 fallback,并透传 channel 与 getLevelName() 转换的 level 字符串以支持多通道和级别控制。

ThinkPHP 6 如何替换默认日志驱动为 ELK 推送驱动
ThinkPHP 6 默认用 FileHandler 写本地文件,不直接支持发日志到 Logstash 或 Elasticsearch。要进 ELK,得自己写一个日志驱动,注册进 think\log\driver\Driver 体系,而不是改配置或加中间件。
关键点在于:这个驱动必须实现 write() 方法,并在其中把日志结构化后通过 HTTP(推荐)或 TCP 发给 Logstash。别试图在 __construct() 里初始化长连接——TP 日志是无状态的,每次写日志都可能新建实例。
- 驱动类需继承
think\log\driver\Driver,重写write() - 配置里把
type改成你的驱动类名(如elk),TP 才会调用它 - 不要在驱动里做重试、队列、批量合并——TP 不保证多次
write()调用在同一个进程内,异步逻辑容易丢日志
ELK 日志驱动中如何构造符合 Logstash filter 的 JSON 格式
Logstash 常用 json 或 json_lines codec,但 TP 原生日志是扁平数组,直接 json_encode() 会丢失上下文(比如请求 ID、IP、URL)。必须手动补全关键字段,否则 Kibana 里查不到来源。
建议固定写入以下字段:level(转小写)、message、context(原样保留)、time(ISO8601)、app(来自 App::getAppId())、env(Env::get('APP_ENV'))、server_ip($_SERVER['SERVER_ADDR'])。
- 避免嵌套过深——Logstash 的
jsonfilter 对深层嵌套解析不稳定,context.trace这种建议扁平化为trace_id - 不要在 JSON 中塞二进制或未转义换行符,
message字段里的 \n 会破坏json_lines格式 - 用
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json'])显式声明类型,Logstash 才能正确识别
为什么不能用 file_get_contents() + stream_context_create() 发日志
TP 日志是同步阻塞调用,file_get_contents() 在网络超时或 Logstash 拒绝连接时会卡住整个请求,尤其在 debug 模式下日志量大时明显。这不是性能问题,是可用性风险。
实际线上环境常见错误现象:cURL error 28: Operation timed out after 30001 milliseconds 导致接口响应变慢甚至 504;或者 Logstash 重启期间,所有请求 hang 住直到超时。
- 必须设置
CURLOPT_TIMEOUT_MS≤ 300(毫秒),超过就丢弃,宁可少几条日志也不能拖垮业务 - 禁用
CURLOPT_FOLLOWLOCATION,避免重定向引入额外延迟 - 不要捕获
curl_error()后再写本地 fallback 日志——TP 正在执行日志流程,此时再调Log::write()可能引发递归或死锁
如何让 ELK 日志驱动兼容 TP 的多通道和级别控制
TP 允许按 channel(如 sql、debug)和 level(error、info)分流日志,但自定义驱动默认收不到这些元信息。必须从 $data 参数里提取 channel 和 level,再映射成 ELK 可筛选字段。
比如 Log::channel('sql')->error('slow query') 这条日志,$data 数组里会有 channel 键,但不会自动带 level——得靠 getLevelName() 辅助方法还原。
-
$data['channel'] ?? 'default'必须透传,Kibana 里靠它区分业务模块 -
$data['level']是数字(如 400),要用think\log\LogLevel::getLevelName()转成字符串,否则 Logstash 里全是数字难过滤 - 如果配置了
level白名单(如只允许error进 ELK),必须在write()开头判断,不符合就直接 return,别发空请求
最麻烦的是 trace 上下文:TP 的 trace 是闭包或资源类型,不能直接 json_encode。得在写日志前用 App::getTraceId() 或 Request::instance()->getHeader('X-Trace-ID') 主动取,漏掉就找不到链路。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











