8. 日志记录
HAProxy 的强项之一无疑在于其精确的日志。它可能提供了此类产品中可用的最详尽的信息级别,这对排查复杂环境中的问题至关重要。日志中提供的标准信息包括客户端端口、TCP/HTTP 状态定时器、流在终止时的精确状态以及精确的终止原因,关于将流量导向服务器的决策信息,当然还包括捕获任意头字段的能力。
为提升管理员的响应速度,HAProxy 对遇到的内部和外部问题提供了极高的透明度,并且可以同时将日志发送到不同目标,并使用不同的级别过滤器:
- 全局进程级日志(系统错误、启动/停止等)
- 每个实例的系统和内部错误(资源不足、缺陷等)
- 每个实例的外部问题(服务器上下线、连接数上限)
- 每个实例的活动日志(客户端连接),无论是在建立阶段还是终止阶段
- 按请求控制日志级别,例如:http-request set-log-level silent if sensitive_request
将不同级别的日志分发至不同的日志服务器,可使多个生产团队协同工作,并尽快解决各自的问题。例如,系统团队可监控系统级错误,应用团队可实时监控其服务器的启停状态,安全团队则可延迟一小时分析活动日志。
8.1. 日志级别
TCP 和 HTTP 连接可记录包括日期、时间、源 IP 地址、目标地址、连接持续时间、响应时间、HTTP 请求、HTTP 返回码、传输字节数、流结束条件,甚至交换的 Cookie 值等信息。例如,用于追踪特定用户的问题。所有消息可发送至最多两个 syslog 服务器。有关日志 facility 的更多信息,请参阅 第 4.2 节 中 “log” 关键字的说明。
8.2. 日志格式
HAProxy 支持 5 种日志格式。这些格式之间存在若干共用字段,将在后续段落中详细说明。部分字段可能因配置中特定选项的指示而略有差异。支持的格式如下:
-
默认格式,极为简单,极少使用。该格式仅在连接被接受的瞬间提供关于入站连接的极简信息:源 IP:端口、目标 IP:端口和前端名称。此模式最终将被移除,因此不会进行详细描述。
-
TCP 格式,功能更强大。当在前端配置了 “option tcplog” 时,该格式被启用。HAProxy 通常会在连接终止后才进行日志记录。该格式提供更丰富的信息,例如计时器、连接数、队列大小等……推荐用于纯 TCP 代理。
-
HTTP 格式,适用于 HTTP 代理的最先进格式。当在前端启用 “option httplog” 时,该格式被激活。它提供与 TCP 格式相同的信息,并增加了一些 HTTP 特有的字段,例如请求、状态码,以及头和 Cookie 的捕获。建议对 HTTP 代理使用此格式。
-
CLF HTTP 格式,与 HTTP 格式等效,但字段排列顺序与 CLF 格式一致。在此模式下,所有计时器、捕获、标志等均在通用字段结束后按字段逐一出现,顺序与标准 HTTP 格式中一致。
-
自定义日志格式,允许自行构造日志行。
后续段落将深入探讨每种格式的详细信息。格式规范将以“字段”为单位进行描述。除非另有说明,字段是指由任意数量空格分隔的文本片段。由于 syslog 服务器可能在行首插入字段,因此始终假设首个字段为包含进程名称和标识符的字段。
请注意:由于日志行可能非常长,下述各段中的日志示例可能会被拆分为多行。示例日志行将以三个右尖括号(’»>’)开头,每当一条日志被拆分为多行时,非末尾行将以反斜杠(\)结尾,下一行将缩进两个字符。
8.2.1. 默认日志格式
此格式在未设置特定选项时使用。连接一旦被接受,日志即被发出。请注意,当前此格式是唯一记录请求目标 IP 和端口的格式。
示例:
字段格式 从上例提取
详细字段说明:
- “source_ip” 是发起连接的客户端的 IP 地址。
- “source_port” 是发起连接的客户端的 TCP 端口。
- “destination_ip” 是客户端连接的目标 IP 地址。
- “destination_port” 是客户端连接的目标 TCP 端口。
- “frontend_name” 是接收并处理该连接的前端(或监听器)的名称。
- “mode 是前端当前运行的模式(TCP 或 HTTP)。
若为 Unix 套接字,源地址和目标地址将标记为“unix:”,端口反映接受连接的套接字的内部 ID(与统计信息中报告的 ID 相同)。
建议新安装时不要使用此已弃用的格式,因为它最终将被移除。
8.2.2. TCP 日志格式
当在前端中指定 “option tcplog” 时,使用 TCP 格式。该格式是纯 TCP 代理的推荐格式,可提供大量用于故障排查的宝贵信息。由于此格式包含计时器和字节数统计,日志通常在会话结束时输出。若指定 “option logasap”,则可提前输出日志,这在远程终端等具有长会话的环境中尤为合理。匹配 “monitor” 规则的会话将永不记录日志。也可在前端中指定 “option dontlognull”,以避免在客户端与服务器之间未交换任何数据的会话中输出日志。若在前端中指定 “option dontlog-normal”,则成功连接将不会被记录。
TCP 日志格式在内部被声明为基于以下确切字符串的自定义日志格式,该字符串也可用作扩展格式的基础(如需)。此外,可使用 HAPROXY_TCP_LOG_FMT 变量替代。请参阅 第 8.2.6 节 “自定义日志格式”,了解如何使用。
且 CLF 日志格式在内部被声明为基于此确切字符串的自定义日志格式:
部分字段可能因某些配置选项的不同而略有差异,这些字段在下方字段名后以星号(*)标记。
示例:
字段格式 从上例中提取
详细字段说明:
-
“client_ip” 是发起 TCP 连接至 HAProxy 的客户端 IP 地址。如果连接是在 Unix 套接字上接受的,则 IP 地址将被替换为单词 “unix”。请注意,当连接在配置了 “accept-proxy” 的套接字上接受,且正确使用了 PROXY 协议,或在配置了 “accept-netscaler-cip” 的套接字上接受,且正确使用了 NetScaler 客户端 IP 插入协议时,日志将反映转发连接的信息。
-
“client_port” 是发起连接的客户端的 TCP 端口。如果连接是在 Unix 套接字上接受的,则端口将被接受该连接的套接字 ID 替代,该 ID 同样会在统计信息接口中报告。
-
“accept_date” 是 HAProxy 接收到连接的确切日期(若在系统的积压队列(backlog)中存在排队,该日期可能与网络上观察到的日期略有差异)。该日期通常与上游防火墙日志中出现的日期一致。在 HTTP 模式下,accept_date 字段将在连接准备好接收新请求的时刻重置(HTTP/1 为上一个响应结束时,HTTP/2 为上一个请求结束后立即)。
-
“frontend_name” 是接收并处理该连接的前端(或监听器)的名称。
-
“backend_name” 是用于管理与服务器连接的后端(或监听器)的名称。若未应用切换规则,其值将与前端相同,这对 TCP 应用程序而言较为常见。
-
“server_name” 是连接最后被发送至的服务器名称,若发生连接错误并触发重分派,该名称可能与首个服务器不同。请注意,该服务器属于处理请求的后端。若连接在到达服务器前被中止,则显示为 “
<NOSRV>” 而非服务器名称。 -
“Tw” 是连接在各个队列中等待的总时间,单位为毫秒。如果连接在进入队列前被中止,则该值可能为 “-1”。更多详情请参见下方“计时器”(第 8.4 节)。
-
“Tc” 是等待与最终服务器建立连接所花费的总时间,单位为毫秒,包含重试。若在建立连接前连接被中止,则该值可能为 “-1”。更多详情请参见下方“计时器”(第 8.4 节)。
-
“Tt” 是从 accept 到最后一次关闭之间经过的总时间,单位为毫秒,涵盖所有可能的处理过程。有一个例外情况:若指定了 “option logasap”,则时间计数会在日志发出时停止。此时,数值前会附加一个 “+” 号,表示最终值将更大。有关更多详情,请参见下方“计时器”(第 8.4 节)。
-
“bytes_read” 是在记录日志时,从服务器传输到客户端的总字节数。若指定了 “option logasap”,该值前会加上 “+” 号,表示最终值可能更大。请注意,该值为 64 位计数器,因此日志分析工具必须能够处理该值而不会溢出。
-
“termination_state” 是会话结束时所处的状态。这表示会话状态、导致会话终止的一方以及终止原因(超时、错误等)。正常标志应为 “–",表示会话由任一端关闭,且缓冲区中无剩余数据。详见下文“断开连接时的流状态”。
-
“actconn” 是会话记录时进程的并发连接总数。该值可用于检测是否达到每进程的系统限制。例如,当多个连接错误发生时,若 actconn 接近 512,很可能系统将进程的文件描述符使用上限设为 1024,且所有文件描述符均已耗尽。请参阅 第 3 节 “全局段”以了解如何调整系统设置。
-
“feconn” 是会话记录时前端的并发连接总数。该值可用于估算维持高负载所需的资源量,并检测前端的 “maxconn” 是否已达到。当该值出现大幅跃升时,通常是因为后端服务器发生拥塞,但有时也可能由拒绝服务攻击引起。
-
“beconn” 是会话记录时后端处理的并发连接总数。该数值包括后端服务器上当前活跃的并发连接总数,以及队列中等待的连接数量。可用于估算支持特定应用高负载所需的额外服务器数量。当该数值出现大幅跃升时,通常表明后端服务器存在拥塞,但有时也可能由拒绝服务攻击引起。
-
“srv_conn” 是会话记录时服务器上仍处于活跃状态的并发连接总数。该值绝不会超过服务器配置的 “maxconn” 参数。若该值经常接近或等于服务器的 “maxconn”,说明大量涉及流量调控,表明服务器的 maxconn 值可能过低,或用于处理负载的服务器数量不足,无法实现最优响应时间。当仅有一个服务器的 “srv_conn” 值较高时,通常意味着该服务器存在某些问题,导致连接处理时间长于其他服务器。
-
“retries” 是该会话在尝试连接服务器时经历的连接重试次数。通常情况下应为零,除非服务器在连接尝试的瞬间正在停止。频繁重试通常表明 HAProxy 与服务器之间存在网络问题,或服务器端系统队列配置不当,导致无法将新连接加入队列。该字段可选择性地以加号 ‘+’ 作为前缀,表示在初始服务器达到最大重试次数后,会话已发生重分派。此时日志中显示的服务器名称为重分派目标服务器,而非首次尝试的服务器,尽管在使用哈希算法等情况下两者可能相同。因此,一般规则是:当重试次数前出现 ‘+’ 符号时,该次数不应归因于日志中记录的服务器。
-
“srv_queue” 是在当前请求之前,服务器队列中已处理的请求数量。当请求未经过服务器队列时,该值为 0。通过将请求在队列中等待的时间除以队列中的请求数量,可估算服务器响应时间的近似值。请注意,若会话发生重分派并经过两个服务器队列,其位置将累加。除非发生重分派,否则请求不应同时经过服务器队列和后端队列。
-
“backend_queue” 是在当前请求之前,后端全局队列中已处理的请求数量。当请求未经过全局队列时,该值为 0。该值可用于估算平均队列长度,除以服务器的 “maxconn” 参数后,即可得出缺失服务器的数量。请注意,若会话发生重分派,请求可能两次经过后端队列,此时两个位置将累加。除非发生重分派,否则请求不应同时经过服务器队列和后端队列。
8.2.3. HTTP 日志格式
HTTP 格式是内容最完整、也最适合 HTTP 代理的格式。当在前端中指定 “option httplog” 时启用该格式。它提供的信息级别与 TCP 格式相同,同时具备 HTTP 协议特有的附加功能。与 TCP 格式类似,日志通常在流结束时发出,除非指定了 “option logasap”,这通常仅对下载站点有意义。匹配 “monitor” 规则的流将永远不会被记录。还可以通过在前端中指定 “option dontlognull”,避免记录客户端未发送任何数据的流。若在前端中指定 “option dontlog-normal”,则成功连接将不会被记录。
HTTP 日志格式在内部被声明为基于以下确切字符串的自定义日志格式,该字符串也可用作扩展格式的基础(如需)。此外,可使用 HAPROXY_HTTP_LOG_FMT 变量替代。请参阅 第 8.2.6 节 “自定义日志格式”,了解如何使用。
且 CLF 日志格式在内部被声明为基于此确切字符串的自定义日志格式:
大多数字段与 TCP 日志共享,部分字段有所不同。某些字段的值可能因配置选项的不同而略有差异。这些字段在下方字段名后以星号(’*’)标记。
示例:
字段格式 从上例中提取
详细字段说明:
-
“client_ip” 是发起 TCP 连接至 HAProxy 的客户端 IP 地址。如果连接是在 Unix 套接字上接受的,则 IP 地址将被替换为单词 “unix”。请注意,当连接在配置了 “accept-proxy” 的套接字上接受,且正确使用了 PROXY 协议,或在配置了 “accept-netscaler-cip” 的套接字上接受,且正确使用了 NetScaler 客户端 IP 插入协议时,日志将反映转发连接的信息。
-
“client_port” 是发起连接的客户端的 TCP 端口。如果连接是在 Unix 套接字上接受的,则端口将被接受该连接的套接字 ID 替代,该 ID 同样会在统计信息接口中报告。
-
“request_date” 是 HAProxy 收到 HTTP 请求第一个字节的确切时间(日志字段 %tr)。
-
“frontend_name” 是接收并处理该连接的前端(或监听器)的名称。
-
“backend_name” 是用于管理与服务器连接的后端(或监听器)的名称。若未应用切换规则,该名称将与前端相同。
-
“server_name” 是连接最后被发送至的服务器名称,若发生连接错误并触发重分派,该名称可能与首个服务器不同。请注意,该服务器属于处理请求的后端。若请求在到达服务器前被中止,则显示为 “
<NOSRV>” 而非服务器名称。若请求被统计信息子系统拦截,则显示为 “<STATS>"。 -
“TR” 是在接收到首个字节后,等待客户端发送完整 HTTP 请求(不包含正文)所花费的总时间,单位为毫秒。若连接在完整请求接收完成前被中止,或接收到无效请求,则该值可能为 “-1”。该值通常应非常小,因为请求通常可容纳于单个数据包中。此处时间过长通常表明客户端与 HAProxy 之间存在网络问题,或请求为手动输入。详见 第 8.4 节 “时间事件” 获取更多详情。
-
“Tw” 是连接在各个队列中等待的总时间,单位为毫秒。如果连接在进入队列前被中止,则该值可能为 “-1”。有关更多详细信息,请参见 第 8.4 节 “时间事件”。
-
“Tc” 是连接至最终服务器期间花费的总时间(单位:毫秒),包含重试时间。若请求在建立连接前被中止,则该值可能为 “-1”。更多详情请参见 第 8.4 节 “时间事件”。
-
“Tr” 是等待服务器发送完整 HTTP 响应(不包含数据)所花费的总时间,单位为毫秒。若请求在完整响应接收前被中止,则该值可能为 “-1”。该时间通常与服务器处理请求的时间一致,但可能受客户端向服务器发送的数据量影响。在 “GET” 请求中,若该值过大,通常表明服务器过载。更多详情请参见 第 8.4 节 “时间事件”。
-
“Ta” 是请求在 HAProxy 中保持活跃的时间,单位为毫秒,表示从接收到请求的第一个字节到发送响应的最后一个字节所经过的总时间。该时间涵盖所有可能的处理过程,但不包括握手时间(参见 Th)和空闲时间(参见 Ti)。有一个例外情况:若指定了 “option logasap”,则时间计数将在日志发出时停止。此时,数值前会附加一个 “+” 号,表示最终结果将更大。详情请参见 第 8.4 节 “时间事件”。
-
“status_code” 是返回给客户端的 HTTP 状态码。该状态码通常由服务器设置,但在服务器无法访问或其响应被 HAProxy 阻断时,也可能由 HAProxy 设置。
-
“bytes_read” 是在记录日志时向客户端传输的总字节数。该数值包含 HTTP 头。若指定 “option logasap”,此值前会附加 “+” 号,表示最终值可能更大。请注意,该数值为 64 位计数器,因此日志分析工具必须能够处理该数值而不会溢出。
-
“captured_request_cookie” 是一个可选的“name=value”条目,表示客户端在请求中携带了该 Cookie。Cookie 名称及其最大长度由前端配置中的“capture cookie”语句定义。当该选项未设置时,该字段为单个连字符(’-’)。仅可捕获一个 Cookie,通常用于跟踪客户端与服务器之间的会话 ID 交换,以检测因应用程序缺陷导致的客户端会话交叉问题。详情请参阅下文“捕获 HTTP 头和 Cookie”段。
-
“captured_response_cookie” 是一个可选的“name=value”条目,表示服务器在其响应中返回了 Cookie。Cookie 名称及其最大长度由前端配置中的“capture cookie”语句定义。当该选项未设置时,该字段为单个连字符(’-’)。仅可捕获一个 Cookie,通常用于跟踪客户端与服务器之间的会话 ID 交换,以检测因应用程序缺陷导致的客户端会话交叉问题。详情请参阅下方“捕获 HTTP 头和 Cookie”段。
-
“termination_state” 是流结束时所处的状态。这表示流状态、哪一端导致流结束,以及原因(超时、错误等),类似于 TCP 日志,并在最后两个字符中包含关于 Cookie 持久化操作的信息。正常标志应以 “–” 开头,表示流由任一端关闭,且缓冲区中无剩余数据。详见下文“断开连接时的流状态”获取更多详情。
-
“actconn” 是流被记录时进程的并发连接总数。 该值可用于检测是否达到每进程的系统限制。例如,当多个连接错误发生时,若 actconn 接近 512 或 1024,很可能表明系统将进程的文件描述符使用上限设为 1024,且所有文件描述符均已耗尽。 请参阅 第 3 节 “全局段”以了解如何调整系统配置。
-
“feconn” 是流记录时前端的并发连接总数。 该值有助于估算维持高负载所需的资源量,并检测前端的 “maxconn” 是否已达到。 该值通常出现大幅跃升时,往往是因为后端服务器发生拥塞,但有时也可能由拒绝服务攻击引起。
-
“beconn” 是在日志记录时后端处理的并发连接总数。该数值包括后端服务器上当前活跃的并发连接数以及队列中等待的连接数。可用于估算支持特定应用高负载所需的额外服务器数量。当该值出现大幅跃升时,通常表明后端服务器存在拥塞,但有时也可能由拒绝服务攻击引起。
-
“srv_conn” 是流记录时服务器上仍处于活跃状态的并发连接总数。该值绝不会超过服务器配置的 “maxconn” 参数。若该值经常接近或等于服务器的 “maxconn”,表明大量涉及流量调控,即服务器的 maxconn 值可能过低,或用于处理负载的服务器数量不足,无法实现最优响应时间。当仅有一个服务器的 “srv_conn” 值较高时,通常意味着该服务器存在某些问题,导致请求处理时间长于其他服务器。
-
“retries” 是此流在尝试连接服务器时经历的连接重试次数。正常情况下应为零,除非服务器在连接尝试的同一时刻被停止。频繁重试通常表明 HAProxy 与服务器之间存在网络问题,或服务器端系统队列配置不当,导致无法将新连接排队。该字段可选择性地以加号 ‘+’ 开头,表示在初始服务器达到最大重试次数后,连接已被重分派。此时日志中显示的服务器名称是连接被重分派至的目标服务器,而非首个服务器,尽管在某些情况下(例如使用哈希时)两者可能相同。因此,一般规则是:当重试次数前出现 ‘+’ 符号时,该次数不应归因于日志中记录的服务器。
-
“srv_queue” 是在当前请求之前,服务器队列中已处理的请求数量。当请求未经过服务器队列时,该值为 0。通过将请求在队列中等待的时间除以队列中的请求数量,可估算服务器响应时间的近似值。请注意,若流经历重分派并经过两个服务器队列,其位置将累加。除非发生重分派,否则请求不应同时经过服务器队列和后端队列。
-
“backend_queue” 是在当前请求之前,后端全局队列中已处理的请求数量。当请求未经过全局队列时,该值为 0。该值可用于估算平均队列长度,除以服务器的 “maxconn” 参数后,即可得出缺失服务器的数量。请注意,若流经历重分派,可能两次经过后端队列,此时两个位置将累加。除非发生重分派,否则请求不应同时经过服务器队列和后端队列。
-
“captured_request_headers” 是由于前端中存在 “capture request header” 语句而捕获的请求头列表。可捕获多个头,它们之间以竖线字符(’|’)分隔。当未启用捕获时,不显示花括号,导致后续字段位置偏移。请注意,此字段可能包含空格,使用时需要比未使用时更智能的日志解析器。有关更多详细信息,请参阅下方“捕获 HTTP 头和 Cookie”段。
-
“captured_response_headers” 是由于前端中存在 “capture response header” 语句而在响应中捕获的头列表。可捕获多个头,它们将使用竖线字符(’|’)分隔。当未启用捕获时,大括号不会出现,导致后续字段位置偏移。请注意,此字段可能包含空格,使用时需要比未使用时更智能的日志解析器。有关更多详细信息,请参阅下方“捕获 HTTP 头和 Cookie”段。
-
“http_request” 是完整的 HTTP 请求行,包含方法、请求路径和 HTTP 版本字符串。不可打印字符会被编码(详见下文“不可打印字符”段)。此字段始终位于最后,始终用引号包围,且是唯一可包含引号的字段。若向日志格式中添加新字段,将插入此字段之前。当请求过大且超出标准 syslog 缓冲区(1024 字符)容量时,该字段可能被截断。因此,此字段必须始终位于最后。
8.2.4. HTTPS 日志格式
HTTPS 格式最适合用于 SSL 连接上的 HTTP。它是 HTTP 格式(参见 第 8.2.3 节 )的扩展,在其中添加了与 SSL 相关的信息。当在前端中指定 “option httpslog” 时,该格式被启用。与 TCP 和 HTTP 格式类似,日志通常在流结束时发出,除非指定了 “option logasap”。若流匹配 “monitor” 规则,则该流将不会被记录。也可通过在前端中指定 “option dontlognull” 来避免记录客户端未发送任何数据的流。若在前端中指定 “option dontlog-normal”,则成功连接将不会被记录。
HTTPS 日志格式在内部被定义为基于以下精确字符串的自定义日志格式,该字符串也可用作扩展格式的基础(如需)。此外,可使用 HAPROXY_HTTPS_LOG_FMT 变量替代。请参阅 第 8.2.6 节 “自定义日志格式”,了解如何使用。
该格式本质上是 HTTP 格式(参见 第 8.2.3 节 ),并在其基础上附加了新字段。新增字段(第 17 行和第 18 行)将在下文详述。关于 HTTP 字段的说明,请参见 HTTP 段。
示例:
字段格式 从上例中提取
详细字段说明:
-
“fc_err” 是前端一侧连接的状态。它对应于 “fc_err” 样本提取。有关更多信息,请参阅 “fc_err” 和 “fc_err_str” 样本提取函数。
-
“ssl_fc_err” 是从前端视角看,该连接上首次 SSL 错误栈中的最后一个错误。可用于检测 SSL 握手错误等情形。若一切正常,则其值为 0。有关更多信息,请参见 “ssl_fc_err” 样本提取的说明。
-
“ssl_c_err” 表示客户端证书验证过程的状态。即使验证错误码非空,握手仍可能成功,若该错误码属于被忽略的类型。请参见 “ssl_c_err” 样本提取和 “crt-ignore-err” 选项。
-
“ssl_c_ca_err” 表示客户端证书链验证过程的状态。若验证错误码属于被忽略的类型,即使其非空,握手仍可能成功。请参见 “ssl_c_ca_err” 样本提取和 “ca-ignore-err” 选项。
-
“ssl_fc_is_resumed” 在传入的 TLS 会话通过有状态缓存或无状态票据恢复时为真。请注意,一个 TLS 会话可被多个请求共享。
-
“ssl_fc_sni” 是客户端用于选择要使用的证书的 SNI(服务器名称指示)。通常与连接的第一个请求的主机名匹配。若该字段缺失,可能表示客户端未发送 SNI,此时 HAProxy 将使用默认证书,或在启用 strict-sni 时拒绝连接。
-
“ssl_version” 是前端的 SSL 版本。
-
“ssl_ciphers” 是连接所使用的 SSL 密码。
8.2.5. 错误日志格式
当入站连接因 SSL 握手失败或无效的 PROXY 协议头而中断时,HAProxy 将使用较短的固定行格式记录该事件,除非通过 “error-log-format” 行定义了专用错误日志格式。默认情况下,日志级别为 LOG_INFO,除非在后端中设置了选项 “log-separate-errors”,此时将使用 LOG_ERR 级别。若设置了 “dontlognull” 选项,则不会记录未交换任何数据的连接(例如探测连接)。
默认格式如下所示:
这些字段仅提供最少的信息,以帮助排查连接失败问题。
通过使用 “error-log-format” 指令,将不再使用上述传统日志格式,所有错误日志行将遵循已定义的格式。
一个较为完整的 error-log-format 示例,将报告源地址和端口、连接 accept() 日期、前端名称、进程上和该前端的活跃连接数、HAProxy 内部错误标识符(前端连接)、十六进制 OpenSSL 错误编号(可复制粘贴至 “openssl errstr” 以获取完整解码)、客户端证书提取状态(0 表示无错误)、使用 CA 验证客户端证书的状态(0 表示无错误)、连接是否为新建或已恢复的布尔值、客户端提供的可选服务器名称指示(SNI)、SSL 版本名称以及连接上使用的 SSL 密码套件(如有)。请注意,后端连接错误不会在此处报告,因为后端连接失败的前提是其已成功通过流,因此将作为常规流量日志记录(参见 option httplog 或 option httpslog)。
8.2.6. 自定义日志格式
历史上,自定义日志格式仅用于生成日志。但当其被用于通过组合多个复杂表达式来生成字符串时,其便捷性使得许多原本仅接受字符串作为参数的指令开始采用它,这些指令现在也可以接受此类自定义日志格式定义。本文档中通常以“<fmt>”表示的此类参数,其定义方式与本节所述的 “log-format” 指令的参数完全相同。
当涉及日志且默认日志格式无法满足需求时,可极为细致地定义新的日志格式。由于从零开始创建日志格式并非总是简单任务,强烈建议首先查看现有格式(“option tcplog”、“option httplog”、“option httpslog”),选择最接近预期的格式,复制其 “log-format” 对应字符串,并进行调整。
自定义日志格式定义从配置角度而言是一个单一参数。这意味着其内容不得包含空格(空格或制表符),除非这些空格通过反斜杠字符(’\’)进行转义,或整个定义被引号包围(这是推荐的使用方式)。由于历史经验表明,未加引号的格式字符串极易出错,单个遗漏的反斜杠字符就可能导致格式被静默截断,因此不再建议使用未加引号的格式字符串。此类配置至今仍普遍存在,原因是在 1.5-dev9 版本(该版本比引号可用早了三年)之后日志格式被大规模采用,但建议现在就将其转换为带引号的字符串,并移除反斜杠。
日志格式定义由任意数量的日志格式项组成,各项之间用文本和空格分隔。日志格式项以字符 ‘%’ 开头。若需原样输出 ‘%’,必须在其前添加另一个 ‘%’,形成 ‘%%’。
日志格式项可以是别名或样本表达式:
如果某项名称用方括号(’[’ .. ‘]’)括起,则将其用作样本表达式规则(参见 第 7.3 节 )。这可用于添加一些较少见的信息,例如客户端 SSL 证书的 DN,或记录将用于向粘性表中存储条目时所使用的键。该用法也常用于非日志类动作(如头操作、变量处理等)。
否则,如果该项使用字母数字名称命名,则为别名。(有关可用别名的列表,请参见下表)
项目可使用花括号(’{}’)传递参数,多个参数在花括号内以逗号分隔。可通过在标志前加 ‘+’ 或 ‘-’ 符号来添加或移除标志(详见下文可用标志列表)。
特殊别名 “%o” 可用于将其标志传播到同一格式字符串中的所有其他 logformat 项。在使用带引号的(“Q”)和转义的(“E”)字符串格式时,此功能尤为便捷。
特殊别名 “%OG” 可用于以人类可读格式检索日志来源(何时/何地生成日志)。在使用 “option logasap” 时尤为有用,因为某些日志变量或样本提取可能在日志格式表达式被评估的时间或位置不同而报告不完整值,或表现出不同行为。可能的取值包括:
- “sess_error”:在会话错误处理期间生成日志
- “sess_killed”:在会话中止期间生成日志(中止了胚胎期会话)
- “txn_accept”:在前端连接被接受后立即生成日志
- “txn_request”:在客户端请求接收后生成日志
- “txn_connect”:在后端连接建立后生成日志
- “txn_response”:在服务器响应处理期间生成日志
- “txn_close”:在最终事务步骤、即关闭前生成日志
- “unspec”:未知或未指定 “%OG” 仅在日志记录上下文中相关
项目可选择性地通过 (’()’) 命名。名称必须紧跟在 ‘%’ 之后(位于参数之前)。当设置编码标志如 “json” 或 “cbor” 时,该名称将自动作为键名使用。若未指定编码标志(默认情况),则忽略项目名称。也可通过在名称后附加 ‘:type’ 强制指定项目的输出类型,格式如下:%(itemname:itemtype)aliasname 或 %(itemname:itemtype)[expr],其中 itemtype 可为 ‘str’、‘sint’ 或 ‘bool’。指定类型仅在使用编码方法时有意义。此外,支持为匿名项目提供空名称以强制输出类型:%(:itemtype),即在未全局设置编码时使用,详见下方标志定义。
由于自定义日志格式最初仅用于日志记录,因此在不同使用场景下对不可打印字符和不安全字符(ASCII 码 32 至 126 以外的字符,以及少数其他字符)的处理存在特殊规则。第 8.6 节 详细说明了日志中具体采取的措施,以确保不会发送可能影响终端输出可读性的不安全编码。当用于构造 HTTP 头、健康检查或响应负载时,规则较为宽松,仅对 HTTP 头字段中禁止出现的字符使用百分号 % 前缀的十六进制编码进行替换。通常这不会造成问题,但在某些情况下可能影响输出,例如在构建错误页面或完整响应负载时,期望原样重现的换行符可能显示为 “%0A”。
请注意:在配置指令 “log-format”、“log-format-sd” 和 “unique-id-format” 中,空格被视为分隔符并被合并。
请注意:使用 RFC5424 syslog 消息格式时,PARAM-VALUE 内部的字符 ‘"’、\ 和 ‘]’ 应使用 \ 作为前缀进行转义(详见 https://tools.ietf.org/html/rfc5424#section-6.3.3 以获取更多详情)。在此类情况下,应考虑使用标志 “E”。
支持的项目标志包括(可从项目的参数中启用或禁用):
- Q: 为字符串加引号
- X: 十六进制表示(IP 地址、端口、%Ts、%rt、%pid)
- E: 使用 ‘\’ 作为前缀,对字符串中的引号 ‘"’、’\’ 和 ‘]’ 进行转义(用于 RFC5424 结构化数据日志格式)
- bin: 尝试保留二进制数据,这在处理输出二进制数据的样本表达式时可能有用,以保留原始数据。但需注意,这显然可能生成不可打印字符,包括空字节(NULL-byte),而大多数 syslog 接收端不期望此类数据。因此,该选项主要适用于 set-var-fmt、环形缓冲区和具备二进制能力的日志接收端。此选项只能全局设置(使用 %o),若在单个项的选项中设置将被忽略。
- json: 自动将值以 JSON 格式编码(全局设置时,仅考虑命名的日志格式项)。未完成的数值(例如:使用 logasap 时的 ‘%B’),通常以 ‘+’ 前缀但不进行编码,将原样编码。同时,’+E’ 选项将被忽略。 如第 8.6 节 所述,32..126 范围以外的字符会使用 JSON 转义序列逐字节转义,确保无论输入如何,输出都只包含可打印的 ASCII 字符。有效的 UTF-8 序列也会如此处理,因此非 ASCII 文本在解码后会表现为各个字节对应的字符(例如,U+00E9 编码为 0xC3 0xA9,记录为 “\u00C3\u00A9”)。若要保留这类文本,请参见下方 “utf8”。
- utf8:与 “json” 结合使用,允许有效的多字节 UTF-8 序列不经转义直接输出。C1 控制字符(U+0080 至 U+009F)仍会转义为其码点表示。无效序列会逐字节转义,确保输出始终是有效的 UTF-8。请注意,由于有效 UTF-8 会原样输出,客户端可能使用特殊字符改变日志行在终端或日志查看器中的显示,例如书写方向变化、零宽字符、行分隔符或段落分隔符。因此,建议仅对来自可信来源的内容使用此选项,或在日志无需显示于终端时使用。未启用 “json” 时,此选项不生效。
- cbor: 自动将值以 CBOR 格式编码(全局设置时,仅考虑命名的日志格式项)。默认情况下,CBOR 编码数据以十六进制形式表示,以确保其在 stdout 上可打印,并可与常规 syslog 接收端配合使用。与 json 编码类似,未完成的数值将原样编码,’+E’ 选项将被忽略。当与 ‘+bin’ 选项结合使用时,将直接生成原始二进制 CBOR 负载。请注意,这显然可能生成不可打印字符,因此主要适用于 set-var-fmt、环形缓冲区和具备二进制能力的日志接收端。
示例:
请参阅下表以了解当前定义的别名:
R = 限制:H = 仅限 mode http;S = 仅限 SSL;L = 仅限日志
8.3. 高级日志记录选项
一些高级日志选项常被寻找,但仅通过查看各项配置选项往往难以发现。以下是少数可启用更优日志功能的选项入口。有关其用法的更多信息,请参阅关键字参考。
8.3.1. 禁用外部测试日志记录
由某些监控工具对 HAProxy 执行健康检查的情况十分常见。有时是第 3 层负载均衡器,例如 LVS 或任何商用负载均衡器,有时则可能是更完整的监控系统,例如 Nagios。当检查频率非常高时,用户常会询问如何禁用这些检查的日志记录。有三种可能的解决方案:
-
若连接来自任意位置且仅为 TCP 探测,通常建议在前端通过设置 “option dontlognull” 来禁用无数据交换连接的日志记录。该设置同时也会禁用端口扫描日志记录,是否需要此功能应根据实际需求决定。
-
可以在多种条件下(如源网络、路径、User-Agent 等)使用 “http-request set-log-level silent” 动作。
-
若测试在已知的 URI 上执行,请使用 “monitor-uri” 将该 URI 声明为专用监控地址。任何发送此请求的主机仅会收到健康检查结果,且该请求不会被记录。
8.3.2. 在等待流终止之前记录日志
在连接结束时记录日志的问题在于,对于长时间的流(如远程终端会话或大文件下载),无从了解其间发生了什么。此问题可通过在前端配置中指定“option logasap”来规避。HAProxy 将在数据传输开始前尽可能早地记录日志。这意味着在 TCP 情况下,仍会记录与服务器的连接状态;在 HTTP 情况下,则在处理完服务器头后立即记录。此时报告的字节数为发送给客户端的头字节数。为避免与正常日志混淆,总时间字段和字节数前会加上“+”号,表示实际数值肯定更大。
8.3.3. 提高错误日志级别
有时将正常流量日志与错误日志分离更为方便,例如便于从日志文件中监控错误。当使用选项 “log-separate-errors” 时,发生错误、超时、重试、重分派或 HTTP 状态码 5xx 的连接,其 syslog 级别将从 “info” 提升至 “err”。这有助于 syslog 守护进程将日志存储于独立文件中。请注意,将错误日志同时保留在正常流量文件中非常重要,以确保日志顺序不被破坏。如果已配置 syslog 守护进程将所有高于 “notice” 级别的日志存储于 “admin” 文件中,则需格外小心,因为 “err” 级别高于 “notice”。
8.3.4. 禁用成功连接的日志记录
尽管初听之下可能显得奇怪,但一些大型网站每秒需处理数千条日志,且在长期保存日志或从中检测错误方面面临困难。若在前端中设置选项 “dontlog-normal”,所有正常连接将不会被记录。此处定义的正常连接是指无任何错误、超时、重试或重分派的连接。在 HTTP 中,还会检查状态码,状态码为 5xx 的响应不被视为正常,也将被记录。当然,此举强烈不建议,因为它会移除日志中绝大部分有用信息。仅在别无选择时方可执行。
8.3.5. 日志配置文件
虽然某些指令如 “log-format”、“log-format-sd”、“error-log-format” 或 “log-tag” 可用于全局或在代理级别配置日志格式,但将此类设置尽可能靠近日志端点配置可能更为合适,即按每个 “log” 指令配置。
这正是 “log-profile” 段发挥作用之处:“log-profile” 可在配置文件中的任意位置定义。该段接受一组不同的关键字,用于描述针对特定 log 指令所生成日志的构建方式。
通过 “log” 指令,可选择通过名称指定特定的日志配置文件。同一配置文件可从多个 “log” 指令中使用。
log-profile <name> 创建一个标识为 <name> 的新日志配置文件
log-tag <string> 使用 “log-tag” 指令全局或按代理覆盖 syslog 日志标签。
on <step> [drop] [format <fmt>] [sd <sd_fmt>] 覆盖通常用于在 <step> 日志记录步骤构建日志行的日志格式字符串。<fmt> 用于覆盖 “log-format” 或 “error-log-format” 字符串(取决于 <step>),而 <sd_fmt> 用于覆盖 “log-format-sd” 字符串(两者可同时使用)。
“drop” 特殊关键字可用于指定对指定的 <step> 不发出日志。
若先前已定义,该关键字优先于 “format” 和 “sd”。
<step> 的可能取值如下:
- “accept” :在前端连接被接受后立即生成日志时,覆盖 log-format
- “request” :在接收到客户端请求后生成日志时,覆盖 log-format
- “connect” :在后端连接建立后生成日志时,覆盖 log-format
- “response” :在服务器响应处理过程中生成日志时,覆盖 log-format
- “close” :在最终事务(txn)步骤生成日志时,覆盖 log-format
- “error” :在事务错误导致日志生成时,覆盖 error-log-format
- “any” :覆盖所有日志步骤的 log-format 和 error-log-format,除非声明了更精确的步骤覆盖
请参阅 “do-log” 动作以获取相关的额外 <step> 值。
此设置仅对在使用 “log-format” 指令有意义的上下文中的 “log” 指令有效(例如:http 和 tcp 代理)。否则将被忽略。
示例:
8.4. 事件计时
计时器在排查网络问题时提供极大帮助。所有值均以毫秒(ms)为单位报告。这些计时器应与流终止标志配合使用。在 TCP 模式下,若前端启用了 “option tcplog”,将报告 3 个控制点,格式为 “Tw/Tc/Tt”;在 HTTP 模式下,将报告 5 个控制点,格式为 “TR/Tw/Tc/Tr/Ta”。此外,还提供三个其他测量值,分别为 “Th”、“Ti” 和 “Tq”。
HTTP 模式下的时间事件:
TCP 模式下的时间事件:
- Th:接受 TCP 连接并完成低层协议握手的总时间。目前,这些协议包括 proxy-protocol 和 SSL。在整个连接生命周期中,此操作只会发生一次。此处时间较长可能表明客户端仅预先建立了连接而未发送数据,或因网络问题导致无法在合理时间内完成握手(例如 MTU 问题),或 SSL 握手计算开销过大。请注意,此时间仅在首个请求前报告,因此可对所有请求的值取平均以计算摊销值。后续请求在此处始终报告为零。
该计时器在日志格式中命名为 %Th,作为样本提取时则命名为 fc.timer.handshake。
- Ti:为 HTTP 请求的空闲时间(仅限 HTTP 模式)。该计时器在握手完成与 HTTP 请求首个字节之间计时。在持久连接模式下处理第二个请求时,计时器在前一个响应传输结束后开始计时。当使用 HTTP/2 等多路复用协议时,计时器在前一个请求结束后立即开始计时。部分浏览器会预先建立与服务器的连接,以降低未来请求的延迟,并将连接保持待命状态直至需要使用。此延迟将被报告为空闲时间。值 -1 表示连接上未收到任何数据。
该计时器在日志格式中命名为 %Ti,作为样本提取时则为 req.timer.idle。
- TR:获取客户端请求的总时间(仅限 HTTP 模式)。该值表示从接收到首个字节到代理收到标记 HTTP 头结束的空行之间所经过的时间。值 “-1” 表示从未收到头结束标记。这种情况发生在客户端提前关闭或超时时。由于大多数请求可容纳于单个数据包中,该时间通常很短。若时间较长,可能表明在测试过程中手动输入了请求。
该计时器在日志格式中命名为 %TR,作为样本提取时则为 req.timer.hdr。
- Tq:获取客户端请求的总时间,从接受连接的时刻起算,或自上一个响应的最后一个字节发出后起算(仅限 HTTP 模式)。其值严格等于 Th + Ti + TR,除非其中任一项为 -1,此时也返回 -1。在 HTTP 持久连接和浏览器预连接功能出现之前,该计时器曾非常有用。如今建议放弃使用,转而采用 TR,因为空闲时间会显著增加报告中的噪声。
该计时器在日志格式中命名为 %Tq,在样本提取中为 req.timer.tq。
- Tw:在队列中等待连接槽位所花费的总时间。该值包含后端队列以及服务器队列,取决于队列大小以及服务器完成先前请求所需的时间。值 “-1” 表示请求在进入队列前已被终止,通常发生在无效或被拒绝的请求上。
该计时器在日志格式中命名为 %Tw,作为样本提取时则为 req.timer.queue。
- Tc:建立与服务器的 TCP 连接所花费的总时间。该值表示代理发送连接请求的时刻,到服务器确认连接的时刻之间的时间间隔,或表示从 TCP SYN 数据包发出,到收到对应的 SYN/ACK 数据包的时间间隔。值 “-1” 表示连接从未建立成功。
该计时器在日志格式中命名为 %Tc,在样本提取中命名为 bc.timer.connect。
- Tr:服务器响应时间(仅限 HTTP 模式)。该值表示从与服务器建立 TCP 连接的时刻,到服务器发送完整响应头的时刻之间所经过的时间。它仅反映请求处理时间,不包含因数据传输导致的网络开销。请注意,当客户端需向服务器发送数据时(例如在 POST 请求期间),该时间已开始计算,这可能导致观察到的响应时间出现偏差。因此,建议不要过于依赖此字段来评估位于不可信网络中的客户端发起的 POST 请求。此处值为 “-1” 表示从未收到最后一个响应头(空行),极可能是服务器超时发生在服务器完成请求处理之前,或服务器返回了无效响应。
该计时器在日志格式中命名为 %Tr,作为样本提取时则为 res.timer.hdr。
- Td:表示响应负载从开始传输到向客户端发送最后一个字节的总耗时。在 HTTP 中,该时间从最后一个响应头发送完成后开始计算(即 Tr 之后)。
发送的数据无法保证客户端能够收到,数据可能滞留在内核或网络中。
该计时器在日志格式中命名为 %Td,在样本提取中命名为 res.timer.data。
- Ta:HTTP 请求的总活跃时间,指代理接收到请求头第一个字节的时刻,到发出响应体最后一个字节的时刻之间的时长。例外情况是当指定了 “logasap” 选项时,此时仅等于 (TR + Tw + Tc + Tr),并以加号“+”作为前缀。通过该字段,可减去其他有效计时器,推导出 “Td”,即数据传输时间:
值为 "-1" 的计时器必须从该等式中排除。注意,"Ta" 永远不会为负数。
该计时器作为日志格式别名命名为 %Ta,作为样本提取则为
txn.timer.total。
- Tt:从代理接受连接到两端均关闭的总流持续时间。当指定 “logasap” 选项时例外。此时,其值仅等于 (Th+Ti+TR+Tw+Tc+Tr),并以 ‘+’ 号前缀。通过从该字段中减去其他有效计时器,可推导出 “Td”,即数据传输时间:
值为 "-1" 的计时器必须从该等式中排除。在 TCP 模式下,"Ti"、"Tq" 和
"Tr" 也必须排除。注意,"Tt" 永远不会为负数,且在 HTTP 中,Tt 就等于
(Th+Ti+Ta)。
该计时器作为日志格式别名命名为 %Tt,作为样本提取则为
fc.timer.total。
- Tu:从代理接受连接的时刻到两端连接关闭的时刻,客户端所感知的总估算时间,不包含空闲时间。该指标有助于粗略衡量用户所感知的端到端延迟,避免因请求间持久连接(keep-alive)导致的空闲时间干扰。此计时仅为用户所见时间的估算值,因其假设网络延迟在两个方向上相同。例外情况是当指定 “logasap” 选项时,此时其值仅等于 (Th + TR + Tw + Tc + Tr),并以加号(+)作为前缀。
该计时器在日志格式中命名为 %Tu,在样本提取中命名为 txn.timer.user。
这些计时器可提供关于故障原因的宝贵线索。由于 TCP 协议定义的重传延迟为 3、6、12… 秒,因此可以确定,接近 3 秒倍数的超时计时器几乎总是与网络问题(如线路、协商或拥塞)导致的数据包丢失有关。此外,若 “Ta” 或 “Tt” 接近配置中指定的超时值,通常意味着某一流已因超时而被中止。
最常见的场景:
-
若 “Th” 或 “Ti” 接近 3000,表示客户端与代理之间的数据包可能已丢失。在本地网络中这种情况极为罕见,但在客户端位于远端网络且发送大请求时可能发生。有时,即使没有网络原因,此处也可能出现高于正常值的情况。在攻击期间或资源耗尽状况结束后,HAProxy 可能在几毫秒内接受数千个连接。接受这些连接所花费的时间不可避免地会轻微延迟其他连接的处理,因此在一次性接受数千个新连接后,可能会测量到数十毫秒级别的请求耗时。使用任一持久连接模式时,可能会显示更大的空闲时间,因为 “Ti” 测量的是等待额外请求所花费的时间。
-
若 “Tc” 接近 3000,表示在服务器连接阶段,服务器与代理之间可能丢失了数据包。该值应始终非常低,本地网络下约为 1 ms,远程网络下应小于几十毫秒。
-
若 “Tr” 几乎始终低于 3000,仅个别罕见值似乎等于平均值加 3000,则代理与服务器之间可能存在数据包丢失。
-
若 “Ta” 即使在字节数较小时也较大,通常是因为 HAProxy 以隧道模式运行时,客户端和服务器均未决定关闭连接,且双方已协商使用持久连接模式。为解决此问题,需在前端或后端指定一种 HTTP 选项,以控制持久连接或关闭选项。当使用 “maxconn” 选项对服务器进行连接调控时,保持 ‘Ta’ 或 ‘Tt’ 尽可能小十分重要,因为除非已有连接释放,否则不会向服务器发送新的连接。
其他值得注意的 HTTP 日志情况(‘xx’ 表示任意需忽略的值):
8.5. 断开连接时的流状态
TCP 和 HTTP 日志在活跃连接数之前提供流终止指示符,位于 “termination_state” 字段中。在 TCP 模式下,该字段长度为 2 个字符;在 HTTP 模式下,长度扩展为 4 个字符,每个字符具有特殊含义:
- 在第一个字符上,报告导致流终止的第一个事件的代码:
- 在第二个字符中,流关闭时的 TCP 或 HTTP 流状态:
- 第三个字符表示持久性 Cookie 是否由客户端提供(仅在 HTTP 模式下):
- 最后一个字符报告了对服务器返回的持久性 Cookie 执行的操作(仅限 HTTP 模式):
两个首个标志的组合可提供关于流或会话终止时发生的情况及其终止原因的大量信息。这有助于检测服务器过载、网络问题、本地系统资源耗尽、攻击等情况。
最常见的终止标志组合如下所示。组合按字母顺序排列,小写字母紧接在对应大写字母之后,以便于查找和理解。
标志 原因
-- 正常终止。
CC 客户端在与服务器的连接建立之前中止。这可能发生在 HAProxy 尝试连接一台
刚刚宕机(或未检查)的服务器时,客户端在 HAProxy 等待服务器响应或等待
"timeout connect" 到期期间中止了连接。
CD 客户端在数据传输期间意外中止。可能原因包括浏览器崩溃、客户端与
HAProxy 之间的中间设备主动断开连接、客户端与 HAProxy 之间的网络路由
问题,或服务器与客户端之间的持久连接流首先被客户端终止。
cD 客户端在 "timeout client" 时长内既未发送也未确认任何数据。这通常由
客户端侧的网络故障引起,或客户端未正常断开即离开网络。
CH 客户端在等待服务器开始响应期间中止。可能是服务器响应过慢,也可能是
客户端过快点击了“停止”按钮。
cH 在 POST 请求期间等待客户端数据时 "timeout client" 到期。这有时是由于
PPPoE 网络的 TCP MSS 值过大、无法传输全尺寸数据包所致;也可能发生在
客户端超时小于服务器超时且服务器响应过慢时。
CQ 客户端在其流排队等待有足够空闲槽位的服务器接受时中止。可能是所有
服务器都已饱和,或被分配的服务器响应时间过长。
CR 客户端在发送完整 HTTP 请求之前中止。最可能是使用 telnet 客户端手动
输入请求且过早中止。此时 HTTP 状态码很可能是 400。有时这也可能由
IDS 切断 HAProxy 与客户端之间的连接引起。"option http-ignore-probes"
可用于忽略没有任何数据传输的连接。
cR "timeout http-request" 在客户端发送完整 HTTP 请求之前到期。原因有时是
客户端侧 PPPoE 网络的 TCP MSS 值过大、无法传输全尺寸数据包,或是客户
端手动输入请求时输入速度不够快,或忘记在请求末尾输入空行。此时 HTTP
状态码很可能是 408。注意:近来一些浏览器开始实现“预连接”(pre-connect)
功能,会投机性地连接一些最近访问过的网站,以防用户想要访问它们。这导
致与网站建立大量连接:如果超时先触发,最终表现为 408 Request Timeout;
如果浏览器先关闭连接,则表现为 400 Bad Request。这些连接会污染日志并
累加错误计数器。据报告,某些浏览器的某些版本甚至会显示该错误代码。可
以在前端添加 "option http-ignore-probes" 来规避此行为的不良影响,使零
数据传输的连接被完全忽略。不过,这也会彻底隐藏真正遇到连接问题的用户
的错误。
CT 客户端在其流被置入连接陷阱(tarpit)期间中止。应检查这是否发生在合法
请求上,以确保没有编写错误的 tarpit 规则。如果大量出现,可考虑将
"timeout tarpit" 值降低到更接近报告的平均 "Tw" 计时器的水平,以免仅为
少数攻击者消耗资源。
LC 请求被 HAProxy 拦截并在本地处理。请求未发送到服务器。它只在因 server
行上的 "redir" 参数而重定向时出现。
LR 请求被 HAProxy 拦截并在本地处理。请求未发送到服务器。通常意味着返回了
重定向、处理了 HTTP return 语句,或请求由某个 applet 处理(统计信息、
缓存、Prometheus 导出器、lua applet 等)。
LH 响应被 HAProxy 拦截并在本地处理。通常意味着返回了重定向或处理了
HTTP return 语句。
SC 服务器或其与 HAProxy 之间的设备显式拒绝了 TCP 连接(代理收到了 TCP
RST 或 ICMP 消息作为回应)。某些情况下,也可能是网络栈通知代理服务器
不可达(例如无路由,或本地网络无 ARP 响应)。在 HTTP 模式下发生时,
状态码很可能是 502 或 503。
sC "timeout connect" 在与服务器的连接完成之前到期。在 HTTP 模式下发生
时,状态码很可能是 503 或 504。
SD 与服务器的连接在数据传输期间因错误而中断。通常意味着 HAProxy 在与
服务器交换数据时收到了来自服务器的 RST 或来自中间设备的 ICMP 消息。
可能由服务器崩溃或中间设备的网络问题引起。
sD 在数据阶段,服务器在 "timeout server" 设定的时长内既未发送也未确认
任何数据。这通常由服务器前的 L4 设备(防火墙、负载均衡器等)超时过短
引起,也可能是客户端与服务器之间维持的持久连接会话在 HAProxy 上先到
期。
SH 服务器在发送完整 HTTP 响应头之前中止,或在处理请求时崩溃。由于服务
器在此刻中止非常罕见,明智的做法是检查其日志以确认是否崩溃及原因。
被记录的请求可能指示一小批问题请求,反映应用程序中的缺陷。有时这也可
能由 IDS 切断 HAProxy 与服务器之间的连接引起。
sH "timeout server" 在服务器返回其响应头之前到期。这是最常见的异常,表
明事务时间过长,可能由服务器或数据库饱和引起。直接的变通办法是增大
"timeout server" 设置,但切记用户体验会因这些过长的响应时间而受损。
唯一的长期解决方案是修复应用程序。
sQ 流在队列中停留时间过长并已过期。如果频繁发生,请查看 "timeout queue"
和 "timeout connect" 设置以了解如何修复。如果常在短时间内大量发生,可
能表明受影响的服务器存在 I/O 或数据库拥塞等普遍问题,或外部攻击造成
的饱和。
PC 代理拒绝建立到服务器的连接,因为在尝试连接时已达到进程的套接字上限。
可在配置中增大全局 "maxconn" 参数使其不再发生。此状态非常罕见,可能在
手动强制设置全局 "ulimit-n" 参数时出现。
PD 代理在服务器发出头之后,阻止了请求或响应中格式不正确的分块编码消息。
大多数情况下,这表明服务器到客户端的消息无效。HAProxy 支持的最大分块
大小为 2GB - 1(2147483647 字节),任何更大的大小都会被视为错误。
PH 代理阻止了服务器的响应,原因包括响应无效、不完整、危险(缓存控制)
或匹配了安全过滤器。无论哪种情况,都会向客户端发送 HTTP 502 错误。该
错误的一个可能原因是 HTTP 头名称中包含非法字符的无效语法。也有可能
但相当罕见的是:代理在服务器响应之前,因语法无效而阻止了来自客户端的
分块编码请求,此时向客户端发送 HTTP 400 错误并记入日志。最后,也可能
是响应上的 HTTP 头重写失败,此时发送 HTTP 500 错误(详见
"tune.maxrewrite" 和 "http-response strict-mode")。
PR 代理阻止了客户端的 HTTP 请求:或因无效的 HTTP 语法,此时向客户端返回
HTTP 400 错误;或因匹配了 deny 过滤器,此时返回 HTTP 403 错误。也可能
是请求上的 HTTP 头重写失败,此时发送 HTTP 500 错误(详见
"tune.maxrewrite" 和 "http-request strict-mode")。
PT 代理阻止了客户端的请求,并在返回 500 服务器错误之前将其连接置入连接
陷阱。没有向服务器发送任何内容。连接保持打开的时长即 "Tw" 计时器字段
所报告的值。
RC 本地资源(内存、套接字、源端口)已耗尽,导致无法建立到服务器的连接。
错误日志会准确说明缺少什么。此情况非常罕见,只能通过适当的系统调优解
决。
最后两个标志的组合提供了关于客户端、服务器和 HAProxy 如何处理持久化的大量信息。这对排查断连故障至关重要,尤其当用户抱怨需重新认证时。常见的标志包括:
8.6. 不可打印字符
为避免在查阅日志时对日志分析工具或终端造成干扰,不可打印字符不会直接写入日志文件,而是转换为对应 ASCII 码的两位十六进制表示,并以字符 ‘#’ 作为前缀。仅 ASCII 码值在 32 至 126(含)之间的字符可直接记录,无需转义。显然,转义字符 ‘#’ 本身也需编码以避免歧义("#23”)。同理,字符 ‘”’ 被编码为 “#22”,在记录头时,字符 ‘{’、’|’ 和 ‘}’ 也需进行相同处理。
此规则不适用于 cbor 编码,因为该编码使用安全的十六进制数字,或使用边界明确的二进制块;这些块会交给 CBOR 解析器处理,而不会发送至终端。
json 编码改用 JSON 转义序列,+utf8 选项还允许有效的多字节 UTF-8 序列原样输出(参见第 8.2.6 节 的 “utf8” 标志)。
请注意,空格字符(’ ‘)在头中未被编码,这可能导致依赖空格计数来定位字段的工具出现问题。一个包含空格的典型头为 “User-Agent”。
最后,观察到某些 syslog 守护进程(如 syslog-ng)会使用反斜杠(\)转义引号(’"’)。由于日志中其他位置不会出现引号,因此可安全地执行反向操作。
8.7. 捕获 HTTP Cookie
Cookie 捕获可简化对完整用户会话的追踪。可通过在前端中使用 “capture cookie” 语句实现。详细信息请参见 第 4.2 节 。仅可捕获一个 Cookie,该 Cookie 会在请求(“Cookie:” 头)和响应(“Set-Cookie:” 头)中同时被检查。相应值将在 HTTP 日志的 “captured_request_cookie” 和 “captured_response_cookie” 位置报告(有关 HTTP 日志格式的详情请参见 第 8.2.3 节 )。若任一 Cookie 未被发现,将用连字符 ‘-’ 替代其值。通过此方式,可轻松检测用户是否切换至新会话,例如服务器为其重新分配了新的 Cookie。也可用于检测服务器是否意外向客户端设置了错误的 Cookie,从而导致会话交叉。
示例:
可以使用 “http-request” 和 “http-response” 规则,将 Cookie 分配至作用域 “txn” 的变量中。随后,可通过 “req.cook” 和 “res.cook” 样本提取函数从请求或响应中提取 Cookie 值(参见 第 7.3.6 节 ),并使用 “set-var” 或 “set-var-fmt” 动作将其赋值给变量(参见 第 4.3 节 )。自定义日志格式即可在指定位置输出这些变量(参见 第 8.2.6 节 )。
8.8. 捕获 HTTP 头(遗留)
头捕获可用于跟踪上游代理设置的唯一请求标识符、虚拟主机名称、用户代理、POST 内容长度、引用来源(Referrer)等信息。在响应中,可搜索关于响应长度、服务器要求缓存行为的信息,或重定向期间的对象位置。
有两种方式执行头捕获。现代方式涉及从待捕获的头中设置变量,或从 “req.hdr_names”、“req.hdrs”、“res.hdr_names”、“res.hdrs” 返回的复合样本中设置变量(详见 第 7.3.6 节 ),这些变量可使用 “txn” 作用域中的 “set-var” 和 “set-var-fmt” 动作,通过 “http-request” 和 “http-response” 规则集进行赋值(详见 第 4.3 节 ),随后可在自定义日志格式中引用(详见 第 8.2.6 节 )。这是捕获 HTTP 头的推荐方式。
此外,还存在一种较早的方法,该方法在引入 http-request 规则和变量之前即已存在,无需调整日志格式,且长期以来一直用于日志记录,同时作为在 HTTP 事务全程中传递请求信息的一种人工手段,使用较旧的 “capture” 规则集。本文所述即为此方法。
使用前端中的“capture request header”和“capture response header”语句执行旧版头捕获。请参阅 第 4.2 节 以获取更多详细信息。
可以同时包含请求头和响应头。不存在的头字段将记录为空字符串;若某个头字段出现多次,仅记录其最后一次出现的值。请求头按声明顺序用花括号 ‘{’ 和 ‘}’ 包裹,以竖线 ‘|’ 分隔,中间不加空格。响应头采用相同的表示方式,但显示在请求头块之后,以一个空格分隔。这些块在日志中显示于 HTTP 请求之前。
作为特殊情况,可以在 TCP 前端中指定 HTTP 头捕获。其目的是在请求随后被切换至 HTTP 后端时,启用对将被解析的头信息的记录。
示例:
8.9. 日志示例
以下是真实场景中的日志示例,附有说明。部分示例为人工构造。为便于阅读,已移除 syslog 部分。其唯一目的是解释如何解读这些日志。
>>> haproxy[674]: 127.0.0.1:33318 [15/Oct/2003:08:31:57.130] px-http \
px-http/srv1 6559/0/7/147/6723 200 243 - - ---- 5/3/3/1/0 0/0 \
"HEAD / HTTP/1.0"
=> 通过 'telnet' 手动输入的长请求(6.5 秒)。服务器在 147 毫秒内应答,
会话正常结束('----')
>>> haproxy[674]: 127.0.0.1:33319 [15/Oct/2003:08:31:57.149] px-http \
px-http/srv1 6559/1230/7/147/6870 200 243 - - ---- 324/239/239/99/0 \
0/9 "HEAD / HTTP/1.0"
=> 同上,但该请求在全局队列中排在其他 9 个请求之后,并在其中等待了
1230 毫秒。
=> 长数据传输请求。由于指定了 "logasap" 选项,日志在数据传输开始前生成。
服务器在 14 毫秒内应答,向客户端发送了 243 字节的头,从接受连接到第一个
数据字节的总时间为 30 毫秒。
>>> haproxy[674]: 127.0.0.1:33320 [15/Oct/2003:08:32:17.925] px-http \
px-http/srv1 9/0/7/14/30 502 243 - - PH-- 3/2/2/0/0 0/0 \
"GET /cgi-bin/bug.cgi? HTTP/1.0"
=> 代理阻止了服务器响应:或因 "http-response deny" 规则,或因响应格式不当、
不符合 HTTP 规范,或因响应中包含可能被缓存的风险敏感信息。此时,响应被
替换为 "502 bad gateway"。标志("PH--")表明是 HAProxy 而非服务器决定返
回 502。
>>> haproxy[18113]: 127.0.0.1:34548 [15/Oct/2003:15:18:55.798] px-http \
px-http/`<NOSRV>` -1/-1/-1/-1/8490 -1 0 - - CR-- 2/2/2/0/0 0/0 ""
=> 客户端始终未完成其请求并在 8.5 秒后自行中止("C---"),此时代理正在等待
请求头("-R--")。未向任何服务器发送任何内容。
>>> haproxy[18113]: 127.0.0.1:34549 [15/Oct/2003:15:19:06.103] px-http \
px-http/`<NOSRV>` -1/-1/-1/-1/50001 408 0 - - cR-- 2/2/2/0/0 0/0 ""
=> 客户端始终未完成其请求,该请求在 50 秒后被超时中止("c---"),此时代理
正在等待请求头("-R--")。未向任何服务器发送任何内容,但代理可以向客户
端发送 408 返回码。
>>> haproxy[18989]: 127.0.0.1:34550 [15/Oct/2003:15:24:28.312] px-tcp \
px-tcp/srv1 0/0/5007 0 cD 0/0/0/0/0 0/0
=> 此日志由 "option tcplog" 生成。客户端在 5 秒后超时("c----")。
>>> haproxy[18989]: 10.0.0.1:34552 [15/Oct/2003:15:26:31.462] px-http \
px-http/srv1 3183/-1/-1/-1/11215 503 0 - - SC-- 205/202/202/115/3 \
0/0 "HEAD / HTTP/1.0"
=> 请求耗时 3 秒才完成(可能是网络问题),到服务器的连接在 4 次各 2 秒的
尝试后失败(配置为 'retries 3')('SC--'),且未发生重分派(否则会看到
"/+3")。向客户端返回了状态码 503。该服务器上有 115 个连接,该代理上有
202 个,全局进程上有 205 个。可能是服务器因已建立的连接过多而拒绝了该
连接。