Apache原生不支持JSON日志,需通过LogFormat手动构造合法单行JSON:使用%{%Y-%m-%dT%H:%M:%S%z}t时间格式、%>s/%B裸写保证数字类型、%{...}i安全变量自动转义、启用mod_logio模块,并配合Filebeat的json.message_key: "@timestamp"等配置确保ELK高效解析。

Apache 默认的 combined 日志格式对 ELK 来说太“松散”——Logstash 要靠 grok 解析,性能差、容错弱、字段类型难控制。直接输出单行合法 JSON 才是高效路径,但 Apache 原生不支持 JSON 序列化,必须靠 mod_log_config 的变量拼接 + 严格转义规则来模拟。
为什么不能直接用 LogFormat 写 JSON 字符串
看似可以这样写:LogFormat "{ \"status\": %>s, \"ip\": \"%a\" }" json,但实际会出问题:
- 双引号、换行、反斜杠等未被自动转义,
$http_user_agent或$request_uri里含引号时直接破坏 JSON 结构 -
%>s、%B等数值字段被包在双引号里,Elasticsearch 会映射为text而非long,影响聚合和范围查询 - 空值字段(如缺失
Referer)变成null字符串或破缺结构,Filebeat 的json.parse会丢整条日志 - 时间格式不符合 ISO 8601(如
[14/Dec/2018:09:35:37 +0800]),Logstashdatefilter 匹配失败率高
正确配置 LogFormat 实现单行 JSON 输出
核心是:用 %{...}i、%{...}o 等安全变量 + 手动包裹引号 + 数值字段裸写 + 时间用 %{%Y-%m-%dT%H:%M:%S%z}t。以下为经验证的最小可行模板:
LogFormat "{ \
\"@timestamp\": \"%{%Y-%m-%dT%H:%M:%S%z}t\", \
\"client_ip\": \"%{X-Forwarded-For}i\", \
\"remote_addr\": \"%a\", \
\"status\": %>s, \
\"method\": \"%m\", \
\"url\": \"%U%q\", \
\"http_host\": \"%{Host}i\", \
\"user_agent\": \"%{User-Agent}i\", \
\"referer\": \"%{Referer}i\", \
\"body_bytes_sent\": %B, \
\"request_time_sec\": %T \
}" access_json关键点:
-
%>s和%B前后不加引号 → Elasticsearch 自动识别为数字类型 - 所有字符串字段(
user_agent、url等)用双引号包裹,Apache 会自动转义内部引号(如"Mozilla/5.0 (Windows; ...)") -
%{X-Forwarded-For}i和%a并存,避免代理场景下真实 IP 丢失 - 必须启用
mod_logio才能使用%B(响应体字节数)和%O(总发送字节数),否则该字段为空或报错
启用并验证 JSON 日志是否真正合法
仅定义 LogFormat 不生效,必须绑定到 CustomLog 指令,并确认日志文件可写、模块已加载:
- 在
httpd.conf或虚拟主机配置中添加:CustomLog "/var/log/apache2/access-json.log" access_json - 确保
mod_logio已启用:LoadModule logio_module modules/mod_logio.so(RHEL/CentOS 路径可能为/usr/lib64/httpd/modules/mod_logio.so) - 重启 Apache 后,手动触发一次请求,然后检查日志首行是否为完整 JSON 对象:
head -1 /var/log/apache2/access-json.log | jq .(需装jq)。如果报错,大概率是某字段含未转义换行或引号 - 特别注意
user_agent和request_uri:某些爬虫 UA 含控制字符,可用od -c查看原始字节,必要时用%{User-Agent}i替为%{HTTP_USER_AGENT}e(环境变量更可控)
Filebeat 配置需匹配 JSON 解析逻辑
Filebeat 不能只靠 json.keys_under_root: true 就完事。常见坑:
- 默认
json.add_error_key: false,解析失败时静默丢弃日志,务必设为true并配合if 'json_error' in event过滤告警 - 必须设置
json.message_key: "@timestamp",否则 Filebeat 无法识别哪条是有效 JSON 行(尤其当日志中混入非 JSON 行时) - 推荐关闭
multiline,因为 JSON 必须单行,开启反而导致跨行合并、解析中断 - 示例片段:
filebeat.inputs: - type: filestream paths: - /var/log/apache2/access-json.log parsers: - json: add_error_key: true message_key: "@timestamp" keys_under_root: true
最易被忽略的是:Apache 写日志是行缓冲的,若访问量低,日志可能卡在内核缓冲区数秒才落盘,导致 Filebeat 读不到最新行。生产环境应加 CustomLog "... access_json" env=!no_buffer 并在关键 location 中设 SetEnv no_buffer 1,或直接用 flush=1s(需 Apache 2.4.33+)。


















