PHP日志必须结构化为JSON并集中采集:Monolog用JsonFormatter、FrankenPHP开启JSON日志、Whoops禁用PrettyPageHandler;Filebeat按type区分日志源并配置multiline;Logstash用date插件解析真实时间戳,统一字段名;ES索引模板与Kibana Data View对齐。

PHP 应用日志直接写文件再靠 grep 查,根本扛不住线上流量和多实例部署——必须走结构化 + 集中式路径。核心前提是:日志得是机器可读的 JSON,且时间戳字段能被 Logstash 或 Filebeat 正确识别为 @timestamp。
确保 PHP 日志输出 JSON 格式(Monolog / FrankenPHP / Whoops)
非结构化文本日志(如 error_log("DB failed"))进 ELK 后全是 unstructured 字段,grok 规则一换就崩。必须从源头控制格式。
-
Monolog必须配JsonFormatter和StreamHandler,避免用LineFormatter;Logger::warning()的上下文数组会自动转成 JSON 对象字段 -
FrankenPHP要启用frankenphp_log()并确认 Caddyfile 中log { format json }已开启,否则默认是纯文本 access log -
Whoops生产环境禁用PrettyPageHandler,改用CallbackHandler手动json_encode()写入文件,注意加换行符"\n",否则 Filebeat 会把多条日志合并成一个事件 - 所有方案都需验证输出是否合法 JSON:用
tail -n1 /var/log/php/app.log | jq .能解析成功才算过关
Filebeat 配置要点:类型区分 + 多行合并
一个 PHP 项目常混着多种日志源(应用日志、审计日志、错误日志),Filebeat 不做区分就全当一类处理,后续 filter 会混乱。
- 在
filebeat.inputs下定义多个type,例如:type: "php-app-json"指向/var/log/php/app.log,type: "php-audit-text"指向/var/log/php/audit.log - 对非 JSON 的文本日志(如 audit.log),必须启用
multiline:设pattern: '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}',negate: true,否则堆栈跟踪会被切开 -
paths建议用绝对路径,避免因 Filebeat 工作目录变化导致监听失效;include_lines: ['"level":"error"']可前置过滤,减轻 Logstash 压力 - 别忽略
fields:在 input 级加fields: { log_type: "app" },比在 Logstash 里用if [path] =~ "app" {}更稳定
Logstash filter 关键动作:时间解析 + 字段标准化
很多日志查不到,不是 Elasticsearch 没存,而是 @timestamp 是 Logstash 接收时间,不是日志真实发生时间——Kibana 时间范围筛选就失效。
立即学习“PHP免费学习笔记(深入)”;
- 必须用
date插件重写时间:若日志含"timestamp": "2026-05-14T10:23:45+00:00",写date { match => ["timestamp", "ISO8601"] target => "@timestamp" } - JSON 日志用
json { source => "message" }解析后,原始message字段仍存在,建议mutate { remove_field => ["message"] }避免冗余 - 不同来源日志字段名不一致(如
user_idvsuid),用rename统一:rename => { "user_id" => "uid" "trace_id" => "traceId" } - 别在 filter 里做耗时操作(如 HTTP 请求、数据库查询),Logstash 是单线程 pipeline,卡住会影响吞吐
Elasticsearch 索引模板与 Kibana 数据视图对齐
Logstash 输出到 php-logs-%{+YYYY.MM.dd},但 Kibana 里看不到字段,大概率是 mapping 没生效或字段类型冲突。
- 手动创建索引模板(
PUT _index_template/php-logs),明确timestamp字段为date类型,level为keyword,避免被动态映射成text - Kibana 中新建 Data View 时,
Timestamp field必须选@timestamp,不是你日志里的timestamp字段——后者只是普通字段 - 如果日志量大,考虑关闭
_source(仅保留必要字段)或启用 ILM 策略自动 rollover,否则单索引超 50GB 会显著拖慢查询 - 测试阶段用
elasticsearch.cat.indices()查看实际索引字段 mapping,别只信 Kibana 的字段列表
最常被跳过的一步:没验证日志从 PHP 写出 → Filebeat 采集 → Logstash 解析 → ES 存储 → Kibana 查询这条链路中每个环节的时间戳是否一致。任意一环错位,时间筛选和趋势图就全乱。



















