HTTP/2 Server Push 允许服务器在客户端请求某个资源时,主动将相关联的静态资源推送给浏览器,从而减少后续请求的往返时间。然而在 Nginx 中启用 http2_push 之后,运维人员常常遇到一个难题:默认的访问日志里看不到这些被推送资源的完整记录,也就无法判断推送策略是否真正生效、哪些资源被过度推送以及推送是否造成了额外的带宽浪费。本文会从日志格式改造入手,把推送相关的信息提取到结构化日志中,再通过 Filebeat 和 Kibana 构建一套可观测的推送分析流程。

一、为什么默认日志无法反映 HTTP/2 Server Push 行为
Nginx 处理 HTTP/2 Server Push 时,被推送的请求实际上是以内部子请求的方式完成的。访问日志模块默认只针对客户端发起的主请求产生一条记录,而不会为每个由 http2_push 指令触发的子请求单独写日志。这意味着即使服务器成功推送了某个 CSS 或 JavaScript 文件,主请求的日志里也只有 /index.html 这一条记录,推送资源的相关信息完全丢失。
另一个容易忽略的地方是响应头 Link。当使用 http2_push_preload on 或者手动通过 add_header Link 声明预加载资源时,响应头中会带上这些资源地址。但 Nginx 内置变量 $sent_http_link 可以捕获该响应头,前提是响应阶段确实生成了这个头。这个细节为日志改造提供了切入点:我们可以从主请求的响应头中提取推送资源声明,间接还原推送行为。
需要明确的是,这种方案记录的是配置层面声明的推送资源,而不是 Nginx 在传输层实际推送的每一个字节。对于需要精确审计每个推送子请求状态的场景,可以考虑使用 OpenResty 的 header_filter_by_lua_block 来读取 ngx.headers.link 并写入独立的日志文件。本文重点介绍基于原生 Nginx 配置的实现方式,因为它足够简单且没有额外依赖。
二、配置 Nginx 输出 http2_push 相关日志字段
为了把推送相关信息放进访问日志,第一步是自定义一个 log_format。推荐直接输出 JSON 格式,这样可以省去后续在 Filebeat 中做复杂解析的步骤。以下配置示例展示了如何记录主请求、响应头 Link 以及 HTTP/2 协议标识:
http {
log_format push_json escape=json '{'
'"timestamp":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"server_protocol":"$server_protocol",'
'"http2":"$http2",'
'"push_link":"$sent_http_link",'
'"referer":"$http_referer",'
'"user_agent":"$http_user_agent"'
'}';
server {
listen 443 ssl http2;
server_name ippipp.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
root /var/www/html;
# 声明需要推送的资源
http2_push /css/style.css;
http2_push /js/app.js;
# 让响应头携带 Link 信息,便于日志记录
add_header Link "</css/style.css>; rel=preload; as=style";
add_header Link "</js/app.js>; rel=preload; as=script";
access_log /var/log/nginx/push_access.log push_json;
location / {
try_files $uri $uri/ =404;
}
}
}
上面的配置中,escape=json 可以让 Nginx 自动转义日志中的特殊字符,保证输出是合法的 JSON 对象。$sent_http_link 变量会捕获响应头中的 Link 字段,但由于 Nginx 变量名是对应响应头的标准化形式,因此这里写 $sent_http_link 而不是 $sent_http_Link。如果配置了多个 add_header Link,Nginx 会把它们合并成一个响应头,日志中会以逗号分隔的形式出现。
如果希望看到每个推送资源的独立记录,可以在 Nginx 配置中结合 map 指令对 Link 头进行拆分,但那样会引入更多复杂性。对于大多数分析场景来说,把完整的 Link 字符串写入日志已经足够,后续可以在 Kibana 中使用脚本字段或 Elasticsearch 的 ingest pipeline 进行拆解。
三、使用 Filebeat 将推送日志导入 Elasticsearch
日志一旦以 JSON 格式落盘,Filebeat 的采集配置就会变得非常轻量。只需要指定日志路径,并设置 JSON 解码,就可以直接把字段送入 Elasticsearch。以下是一个最小化的 filebeat.yml 示例:
filebeat.inputs:
- type: filestream
id: nginx-push-log
paths:
- /var/log/nginx/push_access.log
parsers:
- ndjson:
target: ""
overwrite_keys: true
output.elasticsearch:
hosts: ["http://127.0.0.1:9200"]
index: "nginx-push-%{+yyyy.MM.dd}"
setup.template.name: "nginx-push"
setup.template.pattern: "nginx-push-*"
这里使用了 filestream 输入类型,它比传统的 log 输入更高效,能够自动处理日志轮转。解析器选择 ndjson,因为 Nginx 输出的正是换行分隔的 JSON 对象。目标字段设为空字符串表示把解析后的字段直接提升到事件顶层,避免出现多余的嵌套层级。
如果生产环境已经使用 Logstash 作为统一日志管道,也可以让 Filebeat 输出到 Logstash,再用 Logstash 的 json filter 完成解析。但要注意 Logstash 的 JSON filter 默认会把解析结果放到 message 字段下方,需要额外配置 target 参数。相比之下,直接由 Filebeat 解析再写 Elasticsearch 的链路更短,也更容易排查问题。
四、在 Kibana 中构建推送分析面板
日志进入 Elasticsearch 后,第一项任务是在 Kibana 中创建索引模式。进入 Stack Management 的 Index Patterns 页面,输入 nginx-push-* 作为匹配模式,然后选择 @timestamp 作为时间字段。由于 Filebeat 会自动添加 @timestamp,这里不需要手动指定 Nginx 日志中的 timestamp 字段,但也可以通过 ingest pipeline 调整。
创建索引模式之后,就可以在 Discover 中查看原始日志。如果 push_link 字段显示为一条长字符串,建议先创建一个脚本字段,将字符串按逗号拆分成数组,方便后续做聚合。脚本内容大致如下:
if (doc.containsKey('push_link') && doc['push_link'].size() > 0) {
return doc['push_link'].value.splitOnToken(',');
}
return [];
这个脚本字段会在每条文档上生成一个数组,每个元素对应一个推送资源声明。接下来就可以基于这个数组字段构建可视化。例如创建一个垂直条形图,X 轴使用脚本字段的 terms 聚合,Y 轴使用记录数,就能直观看到哪些资源被服务器声明推送的次数最多。再结合 status 字段做过滤,可以查看推送请求是否伴随主请求返回了错误状态码。
另一个有价值的指标是推送命中率,即推送资源实际被浏览器缓存利用的比例。这个指标无法直接从单条日志中获得,但可以通过对比主请求中后续加载资源请求与推送声明列表的重合度来近似估算。在 Kibana 中可以利用 Elasticsearch 的 scripted_metric 聚合实现,不过更实用的做法是在数据管道中加入业务标识,或用 APM 工具辅助分析。
五、推送策略调整与常见问题排查
当可视化面板显示出某些资源被高频推送,但后续请求仍然大量发生时,说明推送策略可能存在配置错误。常见原因包括:http2_push 指定的资源路径与实际请求路径不一致、资源被浏览器缓存导致推送无效、或者 HTTP/2 连接复用不足导致推送无法建立。此时应当回到 Nginx 配置,检查 root 与 location 的匹配关系,并确认 http2_push_preload 是否按预期工作。
过度推送是另一个需要警惕的问题。HTTP/2 Server Push 会增加服务器出口带宽,尤其是当推送的资源体积较大或者推送条件判断失当时。建议在日志中同时记录 body_bytes_sent 字段,用来近似计算推送带来的额外字节数。如果面板显示推送资源总字节数持续增长,但页面加载性能没有提升,则应当缩小推送范围,只保留首屏渲染必需的关键资源。
排查过程中还可以利用 Kibana 的 Discover 搜索功能,筛选 push_link 为空的记录,检查是否所有主请求都正确携带了 Link 响应头。如果发现部分响应没有 Link 头,可能是 add_header 的作用域问题:该指令只在当前层级生效,如果嵌套到 location 内部且内部还有 add_header 指令,外层配置会被覆盖。将 add_header 提升到 server 级别或者合理规划层级,可以避免这类隐性故障。
最后,如果团队规模较大或需要长期保留推送审计记录,建议将推送日志独立存储到专门的 Elasticsearch 索引,并配置生命周期策略自动滚动删除。这样可以避免推送日志混入常规访问日志导致存储成本上升,同时也能保持 Kibana 查询性能稳定。
Nginx HTTP/2推送日志分析Kibana可视化修改时间:2026-08-27 07:47:43