Envoy 访问日志 `%COALESCE()%` 操作符行为变更:接受空值作为有效结果与运行时开关回退
2026/9/12 17:42:35 网站建设 项目流程

Envoy 访问日志%COALESCE()%操作符行为变更:接受空值作为有效结果与运行时开关回退

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

导读

本篇技术文章聚焦 Envoy 访问日志格式化操作符%COALESCE()%的一项行为变更:从“跳过空值、继续评估下一个操作符”改为“将存在但为空的取值结果视为有效结果直接返回”。你将理解变更的前因后果、底层实现(CoalesceFormatter的取值循环逻辑)、对应的运行时开关envoy.reloadable_features.coalesce_formatter_accept_empty_values的配置方法,以及如何用官方测试用例验证新旧行为差异。文中所有代码与配置均可在当前仓库中找到原始依据。

变更背景:什么是%COALESCE()%操作符

%COALESCE()%是 Envoy 的“高阶”访问日志格式化操作符,它接受一段 JSON 配置,按顺序评估多个子格式化操作符(operator),并返回第一个“有值”的结果。它的典型应用场景是实现回退(fallback)逻辑,例如“优先记录 SNI(TLS 服务器名),取不到时回退到:authority头”。

官方文档 substitution_formatter.rst 对它的定义是:

A higher-order formatter operator that evaluates multiple formatter operators in sequence and returns the first non-null result. This is useful for implementing fallback behavior, such as using SNI when available but falling back to the:authorityheader when SNI is not set.

其 JSON 配置结构为:

%COALESCE({"operators": [...]})%

每个 operator 可以是两种形式之一:

  • 简单字符串:一个不需要参数的内置命令名,例如"REQUESTED_SERVER_NAME"
  • 对象:包含以下字段
    • command(必填):命令名,例如REQREQUESTED_SERVER_NAME
    • param(可选):命令参数,例如REQ命令的:authority
    • max_length(可选):该 operator 输出结果的最大长度。

%COALESCE(JSON_CONFIG):Z%中的Z为可选参数,表示对最终输出结果截断到Z个字符。

本次变更:空值从“无值”变为“有值”

本仓库 changelogs/current/minor_behavior_changes/access_log__coalesce-formatter-empty-values.rst 记录了这次 minor behavior change 的核心语义:

The%COALESCE()%access log operator now returns the first result that is present, including a result that is present but empty. Previously an operator producing an empty value was treated as if it had produced no value at all, and the next operator in the list was evaluated.

翻译成直白的话:

  • 旧行为:某个 operator 产生了结果,但该结果是空字符串,则它被视为“没有产生任何值”,COALESCE会继续评估列表中的下一个 operator;
  • 新行为:某个 operator 产生了结果,即使结果是空字符串,只要它是“存在(present)”的,就立刻作为最终结果返回,不再向后回退。

这两者之间的差异在“第一个 operator 有值但为空、第二个 operator 有非空值”时尤其关键:旧行为返回第二个 operator 的值,新行为返回空字符串。

关键细节:区分“存在但为空”与“根本不存在”

需要特别强调的是,本次变更并不改变“未设置(not set)/ null”语义:如果某个 operator 的结果是“没有值”(std::nullopt,或 protobuf 中的KIND_NOT_SET/ null),COALESCE依然会跳过它并继续评估下一个。变更只影响“结果存在、但内容为空字符串”这一种情况。

这一点从源码注释可以印证,见 coalesce_formatter.h:

By default an operator that produces a value which is present but empty is accepted as the result. Setting the runtime guardenvoy.reloadable_features.coalesce_formatter_accept_empty_valuesto false restores the legacy behavior where an empty result is skipped and the next operator is evaluated.

底层实现剖析:CoalesceFormatter的取值循环

变更的实现集中在 coalesce_formatter.cc,核心是formatformatValue两个方法。

字符串路径:format()

std::optional<std::string> CoalesceFormatter::format(const Context& context, const StreamInfo::StreamInfo& stream_info) const { for (const auto& formatter : formatters_) { auto result = formatter->format(context, stream_info); if (!result.has_value()) { continue; // 无值:跳过,继续下一个 } // An empty result is only accepted when the runtime guard is enabled. if (result.value().empty() && !accept_empty_values_) { continue; // 空值 + 开关关闭:视为无值,跳过 } if (max_length_.has_value()) { SubstitutionFormatUtils::truncate(result.value(), max_length_.value()); } return result; // 有值(或空值且开关开启):直接返回 } return std::nullopt; }

对应 coalesce_formatter.cc:

  • result.has_value() == false表示该 operator 根本没有产出值,无论开关状态如何都会被跳过;
  • result.value().empty() && !accept_empty_values_是新增的判空逻辑:只有当空值且开关关闭时才跳过;
  • 其余情况(非空值,或空值但开关开启)立即返回,max_length_截断逻辑在返回前统一应用。

protobuf 路径:formatValue()

formatValue用于结构化取值场景,逻辑与format对称,对应 coalesce_formatter.cc:

Protobuf::Value CoalesceFormatter::formatValue(const Context& context, const StreamInfo::StreamInfo& stream_info) const { for (const auto& formatter : formatters_) { auto result = formatter->formatValue(context, stream_info); // Skip values that are not set or are explicitly null. if (result.kind_case() == Protobuf::Value::KIND_NOT_SET || result.kind_case() == Protobuf::Value::kNullValue) { continue; } if (result.kind_case() == Protobuf::Value::kStringValue) { // An empty string is only accepted when the runtime guard is enabled. if (result.string_value().empty() && !accept_empty_values_) { continue; } if (max_length_.has_value() && result.string_value().size() > max_length_.value()) { result.set_string_value(result.string_value().substr(0, max_length_.value())); } } return result; } return SubstitutionFormatUtils::unspecifiedValue(); }

可以看到,KIND_NOT_SETkNullValue(显式 null)一律跳过;只有kStringValue且为空字符串时,才受accept_empty_values_开关影响。若所有 operator 都没有产出值,返回unspecifiedValue()

开关的读取时机

accept_empty_values_是构造时确定的一次性常量,读取逻辑在create()中,对应 coalesce_formatter.cc:

const bool accept_empty_values = Runtime::runtimeFeatureEnabled( "envoy.reloadable_features.coalesce_formatter_accept_empty_values"); return std::make_unique<CoalesceFormatter>(std::move(formatters), max_length, accept_empty_values);

也就是说,开关状态在 formatter创建时被快照进accept_empty_values_成员(见 coalesce_formatter.h 的成员声明),之后每次取值不再重新查询运行时状态。

内置命令解析

每个 operator 最终通过createFormatterForCommand交给内置命令解析器(BuiltInCommandParserFactoryHelper::commandParsers())创建对应的FormatterProvider,因此COALESCE支持任意内置格式化命令,见 coalesce_formatter.cc。

运行时开关配置:如何回退到旧行为

本变更属于 minor behavior change,Envoy 提供了标准的运行时开关用于平滑回退:

项目
开关名称envoy.reloadable_features.coalesce_formatter_accept_empty_values
默认值true(接受空值作为结果)
设为false的效果恢复旧行为:空值被跳过,继续评估下一个 operator

开关的注册位置在 runtime_features.cc:

RUNTIME_GUARD(envoy_reloadable_features_coalesce_formatter_accept_empty_values);

在 Envoy 的 bootstrap 配置中通过runtime_layer的磁盘层或 admin 层的runtime模块动态设置,例如写入运行时 key-value 文件:

runtime: symlink_root: /srv/runtime/current subdirectory: envoy

并在对应层文件中放置:

reloadable_features.coalesce_formatter_accept_empty_values: "false"

设置后需重新加载运行时(或在生成新 formatter 时生效),由于开关是在 formatter 创建时快照的,已创建并复用的 formatter 实例在开关翻转前仍保持旧快照行为。

实操示例:配置与行为对照

以下配置均来自官方文档 substitution_formatter.rst,可在访问日志format字段中直接使用。

示例一:SNI 优先,回退到:authority

%COALESCE({"operators": ["REQUESTED_SERVER_NAME", {"command": "REQ", "param": ":authority"}]})%

先尝试REQUESTED_SERVER_NAME(TLS SNI),取不到时回退到:authority头。

示例二:三级级联回退

%COALESCE({"operators": ["REQUESTED_SERVER_NAME", {"command": "REQ", "param": ":authority"}, {"command": "REQ", "param": "x-envoy-original-host"}]})%

依次尝试 SNI →:authorityx-envoy-original-host

示例三:带长度截断

%COALESCE({"operators": [{"command": "REQ", "param": ":authority"}]}):50%

返回:authority头值并截断到 50 个字符。

新旧行为对照(变更直接影响此场景)

假设请求头为:authority: ""(存在但为空)且x-envoy-original-host: original.example.com,配置:

%COALESCE({"operators": [{"command": "REQ", "param": ":authority"}, {"command": "REQ", "param": "x-envoy-original-host"}]})%
开关状态结果说明
true(新默认)""(空字符串)第一个 operator 的结果“存在”,即使是空的,直接返回
false(旧行为)original.example.com空值被跳过,继续评估第二个 operator

测试验证:仓库中的行为证据

本变更在单元测试中有完整的双向验证,见 substitution_formatter_test.cc 的CoalesceFormatterEmptyValues用例:

  • 默认行为":authority"存在但为空、"x-envoy-original-host"有值,未设置开关时,formatForTest结果断言为"",即空值被接受;
  • 关闭开关:通过TestScopedRuntimeenvoy.reloadable_features.coalesce_formatter_accept_empty_values合并为"false"后,同一配置的结果断言为original.example.com
  • 关闭开关且无可用非空值:仅存在空:authority时,字符串路径断言format无值(has_value() == false),结构化路径断言为 null 值。

此外,同文件的CoalesceFormatterErrorCases用例(substitution_formatter_test.cc)还覆盖了配置校验的各类报错:空 JSON 配置(COALESCE requires a JSON configuration parameter)、非法 JSON、缺少operators数组、operators非数组、空数组、operator 元素既非字符串也非对象、对象缺command字段、未知命令等,对应create()中的校验逻辑。

兼容性与注意事项

  • 升级影响面:任何依赖“空值回退到下一个 operator”的既有%COALESCE()%配置,在升级后行为都会改变——如果第一个可取值的 operator 返回空字符串,日志中会出现空值而非回退值。建议升级前排查访问日志配置,必要时显式关闭开关。
  • max_lengthZ的区分:operator 对象内部的max_length是单 operator 输出上限;%COALESCE(...):Z%Z是整个COALESCE最终输出的截断长度。两者可同时使用。
  • JSON 参数限制COALESCE的 JSON 参数中不能出现字面量)字符,否则会干扰命令解析器的正则。若字符串值中确实需要),请使用 Unicode 转义\u0029,见 substitution_formatter.rst。
  • TCP/UDP 场景COALESCE在 TCP/UDP 监听器上未实现,访问日志中会显示"-"
  • 回退机制:作为 minor behavior change,官方推荐先以false运行观察日志,再逐步放开到新默认行为,这是 Envoy 运行时开关的典型平滑升级路径。

小结

%COALESCE()%的空值语义变更,把“结果存在但为空”从“无值”重新归类为“有值”,使回退逻辑更加符合直觉:只有真正取不到(未设置或 null)才回退。无论你依赖新语义、还是需要借助envoy.reloadable_features.coalesce_formatter_accept_empty_values保持旧行为,都可以依据本文的源码依据(coalesce_formatter.cc)、官方文档(substitution_formatter.rst)与测试用例(substitution_formatter_test.cc)进行验证与决策。

【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询