ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

Envoy 访问日志 `%COALESCE()%` 操作符行为变更:接受空值作为有效结果与运行时开关回退 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逻辑例如“优先记录 SNITLS 服务器名取不到时回退到: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必填命令名例如REQ、REQUESTED_SERVER_NAMEparam可选命令参数例如REQ命令的:authoritymax_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/ nullCOALESCE依然会跳过它并继续评估下一个。变更只影响“结果存在、但内容为空字符串”这一种情况。这一点从源码注释可以印证见 coalesce_formatter.hBy 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核心是format与formatValue两个方法。字符串路径format()std::optionalstd::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.ccresult.has_value() false表示该 operator 根本没有产出值无论开关状态如何都会被跳过result.value().empty() !accept_empty_values_是新增的判空逻辑只有当空值且开关关闭时才跳过其余情况非空值或空值但开关开启立即返回max_length_截断逻辑在返回前统一应用。protobuf 路径formatValue()formatValue用于结构化取值场景逻辑与format对称对应 coalesce_formatter.ccProtobuf::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_SET和kNullValue显式 null一律跳过只有kStringValue且为空字符串时才受accept_empty_values_开关影响。若所有 operator 都没有产出值返回unspecifiedValue()。开关的读取时机accept_empty_values_是构造时确定的一次性常量读取逻辑在create()中对应 coalesce_formatter.ccconst bool accept_empty_values Runtime::runtimeFeatureEnabled( envoy.reloadable_features.coalesce_formatter_accept_empty_values); return std::make_uniqueCoalesceFormatter(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 changeEnvoy 提供了标准的运行时开关用于平滑回退项目值开关名称envoy.reloadable_features.coalesce_formatter_accept_empty_values默认值true接受空值作为结果设为false的效果恢复旧行为空值被跳过继续评估下一个 operator开关的注册位置在 runtime_features.ccRUNTIME_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_NAMETLS SNI取不到时回退到:authority头。示例二三级级联回退%COALESCE({operators: [REQUESTED_SERVER_NAME, {command: REQ, param: :authority}, {command: REQ, param: x-envoy-original-host}]})%依次尝试 SNI →:authority→x-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结果断言为即空值被接受关闭开关通过TestScopedRuntime将envoy.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_length与Z的区分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),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进