如何深度追踪并分析SSI、镜像流量及内部重定向的子请求执行细节?
- 内容介绍
- 相关推荐
本文共计923个文字,预计阅读时间需要4分钟。
plaintextlog_subrequest 仅在定义了能识别子请求的日志变量(如 $subrequest)且日志格式正确引用时才生效。未配置 log_format 或 access_log 指令,log_subrequest on 等同于未设置。
为什么 $subrequest 总是空?
这是最常卡住人的点:$subrequest 变量本身不输出任何内容,它只在子请求上下文中为字符串 "1",主请求中为空字符串(不是 "0",也不是 NULL)。所以如果你在日志里看到 subreq="",那大概率是主请求;看到 subreq="1",才是子请求。
- 必须启用
log_subrequest on,该指令需放在http、server或location块中,作用域决定是否生效 -
$subrequest仅由ngx_http_core_module提供,无需额外编译,但 Nginx 版本需 ≥ 1.7.9(2014 年后版本基本都满足) - 日志格式中若用了
$subrequest却没开启log_subrequest,该字段会始终为空,且不会报错
SSI 包含和 mirror 指令产生的子请求怎么区分?
两者都会触发子请求,但协议头、请求方法和来源路径明显不同,靠 $request + $subrequest + $http_user_agent 组合就能分辨:
- SSI 子请求:协议固定为
HTTP/1.0,$request_method是GET,$request_uri是被 include 的路径(如/ssi/header.html),$http_user_agent为空 -
mirror子请求:协议也是HTTP/1.0,但$request_method与主请求一致(如主请求是POST,镜像子请求也是POST),$http_user_agent同样为空,关键看mirror配置的 location 是否有唯一标识(比如加个add_header X-Mirror "true") - 二者都不会携带原始请求头(如
Authorization、Cookie),除非显式用proxy_pass_request_headers on等透传
rewrite last 和 internal 重定向如何确认执行链路?
用 rewrite ... last 触发的重写,本质是内部发起新子请求重新匹配 location,不是 HTTP 301/302 跳转。验证方式很直接:
- 主请求日志中
$status是最终响应码(如 200),但$request仍是原始路径(如"GET /old/test HTTP/1.1") - 对应子请求日志中
$subrequest="1",$request是重写后路径(如"GET /new/test HTTP/1.0"),注意协议降级为HTTP/1.0 - 务必配合
$request_id使用 —— 它在主请求和所有衍生子请求中保持一致,是唯一能串起整条执行链的字段 - 如果发现子请求没有
$request_id,说明你没在http块中配置random_index on或类似生成逻辑(实际应使用map+perl/lua或官方推荐的ngx_http_core_module内置支持)
真正难的是跨多个模块嵌套时的归因:比如 SSI 里又 include 了一个带 mirror 的 location,再在里面做 rewrite last。这时单靠 $subrequest 不够,得靠 $request_id + 自定义 header(如 X-Stage: ssi / X-Stage: mirror)打标,否则日志里全是 subreq="1",根本分不清谁是谁。
本文共计923个文字,预计阅读时间需要4分钟。
plaintextlog_subrequest 仅在定义了能识别子请求的日志变量(如 $subrequest)且日志格式正确引用时才生效。未配置 log_format 或 access_log 指令,log_subrequest on 等同于未设置。
为什么 $subrequest 总是空?
这是最常卡住人的点:$subrequest 变量本身不输出任何内容,它只在子请求上下文中为字符串 "1",主请求中为空字符串(不是 "0",也不是 NULL)。所以如果你在日志里看到 subreq="",那大概率是主请求;看到 subreq="1",才是子请求。
- 必须启用
log_subrequest on,该指令需放在http、server或location块中,作用域决定是否生效 -
$subrequest仅由ngx_http_core_module提供,无需额外编译,但 Nginx 版本需 ≥ 1.7.9(2014 年后版本基本都满足) - 日志格式中若用了
$subrequest却没开启log_subrequest,该字段会始终为空,且不会报错
SSI 包含和 mirror 指令产生的子请求怎么区分?
两者都会触发子请求,但协议头、请求方法和来源路径明显不同,靠 $request + $subrequest + $http_user_agent 组合就能分辨:
- SSI 子请求:协议固定为
HTTP/1.0,$request_method是GET,$request_uri是被 include 的路径(如/ssi/header.html),$http_user_agent为空 -
mirror子请求:协议也是HTTP/1.0,但$request_method与主请求一致(如主请求是POST,镜像子请求也是POST),$http_user_agent同样为空,关键看mirror配置的 location 是否有唯一标识(比如加个add_header X-Mirror "true") - 二者都不会携带原始请求头(如
Authorization、Cookie),除非显式用proxy_pass_request_headers on等透传
rewrite last 和 internal 重定向如何确认执行链路?
用 rewrite ... last 触发的重写,本质是内部发起新子请求重新匹配 location,不是 HTTP 301/302 跳转。验证方式很直接:
- 主请求日志中
$status是最终响应码(如 200),但$request仍是原始路径(如"GET /old/test HTTP/1.1") - 对应子请求日志中
$subrequest="1",$request是重写后路径(如"GET /new/test HTTP/1.0"),注意协议降级为HTTP/1.0 - 务必配合
$request_id使用 —— 它在主请求和所有衍生子请求中保持一致,是唯一能串起整条执行链的字段 - 如果发现子请求没有
$request_id,说明你没在http块中配置random_index on或类似生成逻辑(实际应使用map+perl/lua或官方推荐的ngx_http_core_module内置支持)
真正难的是跨多个模块嵌套时的归因:比如 SSI 里又 include 了一个带 mirror 的 location,再在里面做 rewrite last。这时单靠 $subrequest 不够,得靠 $request_id + 自定义 header(如 X-Stage: ssi / X-Stage: mirror)打标,否则日志里全是 subreq="1",根本分不清谁是谁。

