Vector 0.22 升级指南:破坏性变更、VRL 模板字符串与 17 个废弃 Transform 的迁移实践
【免费下载链接】vectorA high-performance observability data pipeline.项目地址: https://gitcode.com/GitHub_Trending/vect/vector
Vector 0.22.0 是一次包含多项破坏性变更(breaking changes)的重要版本。本文基于仓库中的官方升级指南(2022-05-03-0-22-0-upgrade-guide.md),系统梳理三类破坏性变更——gcp_stackdriver_metrics配置结构调整、VRL 模板字符串支持、encode_key_value/encode_logfmt引号包裹行为变化——以及 0.23.0 即将移除的 17 个废弃 transform 的完整迁移方案,并结合当前仓库源码给出每项变更的实现级印证,帮助你在升级时快速定位并改写配置。
0.22.0 版本变更总览
0.22.0 的变更清单分为两大块:
破坏性变更(3 项):
gcp_stackdriver_metricssink 配置结构调整- VRL 语言新增模板字符串(template strings)支持
encode_key_value与encode_logfmt编码器的引号包裹行为变化
弃用声明(1 项):
- 已被
remap(以及reduce)替代的多个 transform 将在 0.23.0 中被最终移除
以下逐一展开。
破坏性变更一:gcp_stackdriver_metrics配置结构调整
变更内容
gcp_stackdriver_metricssink 的配置现在与gcp_stackdriver_logs保持一致:resource字段下不再需要额外的labels子节来承载资源标签,标签直接平铺在resource内部。
旧配置与新版配置对照
旧配置(0.22 之前):
sinks: my_sink_id: type: "gcp_stackdriver_metrics" inputs: ["my-source-or-transform-id"] credentials_path: "/path/to/credentials.json" project_id: "vector-123456" resource: type: "global" labels: projectId: "vector-123456" instanceId: "Twilight" zone: "us-central1-a"新配置(0.22 起):
sinks: my_sink_id: type: "gcp_stackdriver_metrics" inputs: ["my-source-or-transform-id"] credentials_path: "/path/to/credentials.json" project_id: "vector-123456" resource: type: "global" projectId: "vector-123456" instanceId: "Twilight" zone: "us-central1-a"核心差异只有一个:resource.labels这一层被去掉,projectId、instanceId、zone等标签键值直接作为resource的平铺字段。
源码印证:新配置结构是如何定义的
从当前仓库源码看,该 sink 的配置定义在 src/sinks/gcp/stackdriver/metrics/config.rs。其中与resource相关的字段声明为:
/// The monitored resource to associate the metrics with. pub(super) resource: gcp::GcpTypedResource,即resource直接采用gcp::GcpTypedResource类型(而非“嵌套 labels 的包装结构”),这正是配置中平铺写法在代码侧的对应物。同一文件还定义了其他关键配置项,可作为升级后完整配置的参照:
project_id: String— 指标发布的目标 GCP 项目 ID;resource: gcp::GcpTypedResource— 与指标关联的被监控资源;default_namespace: String— 无命名空间的指标使用的默认命名空间,默认值为"namespace"(由default_metric_namespace_value()提供);auth— 通过#[serde(flatten)]平铺展开的GcpAuthConfig,即credentials_path、api_key等认证字段直接写在 sink 顶层,而不是嵌套在auth子节里;endpoint— 默认端点为https://monitoring.googleapis.com,请求 URI 在validate()中被拼接为/v3/projects/{project_id}/timeSeries。
认证配置GcpAuthConfig定义在 src/gcp.rs,支持credentials_path(服务账号密钥文件)与 API key 两种方式,并处理了 GCE 元数据服务的隐式令牌(metadata server token)刷新逻辑。
迁移要点:如果你的配置里仍有resource.labels一层,删除该层、将标签上移一层即可;其余字段(credentials_path、project_id、default_namespace等)保持不变。
破坏性变更二:VRL 支持模板字符串
语法说明
VRL(Vector Remap Language)的字符串现在支持模板插值:在字符串中使用{{ 变量名 }}占位符即可嵌入变量的值。
beverage = "coffee" preference = "I love to drink {{ beverage }}!" assert!(preference == "I love to drink coffee!")三条重要限制
- 占位符必须是一个简单变量名,且该变量必须能解析为字符串(string)。不能直接写路径或表达式:
# 不合法:42 是整数,不是字符串 stars = 42 sky = "There are {{ stars }} in the sky."需要先显式转换为字符串:
stars = to_string(42) sky = "There are {{ stars }} in the sky."- 不支持路径(path)占位符。以下写法不合法:
message = "The message is {{ .message }}."正确做法是先把字段值赋给变量:
message = .message message = "The message is {{ message }}."- 转义与原始字符串:如果确实需要在字符串中写入字面量
{{/}},可以用\{{和\}}转义,也可以使用原始字符串s'...':
assert!("\{{ right here \}}" == s'{{ right here }}')为什么这是"破坏性"变更
对升级者而言,风险点在于:0.22 之前不含{{语义的 VRL 字符串,如果字符串内容里恰好出现了{{ 标识 }}形态(例如处理模板文本、正则样例、日志样本数据),升级后其含义会从字面量变为插值。升级后建议:
- 用
vector validate校验配置中的 VRL 脚本; - 对包含
{{字面量内容的字符串,改用s'...'原始字符串或\{{转义写法; - 检查依赖字符串拼接逻辑的断言/过滤条件,确认插值结果符合预期。
破坏性变更三:encode_key_value与encode_logfmt的引号包裹行为变化
行为对比
此前,只有包含空白字符的键或值才会被双引号包裹;从 0.22 起,包含空白字符和/或双引号的键与值都会被双引号包裹,且值中原有的双引号会被转义。这一变更使encode_logfmt与其他生态库对齐到 logfmt 的[定义规范](kr/logfmt规范)。
encode_logfmt的输出示例变化:
# 0.22 之前 lvl=info msg={"some":"val"} # 0.22 及之后 lvl=info msg="{\"some\":\"val\"}"源码印证:编码实现的位置
从当前仓库源码结构看,logfmt 编码的核心函数位于 lib/vector-common/shared/src/lib.rs(encode_logfmt模块)。日志编码器的封装实现为 lib/codecs/src/encoding/format/logfmt.rs,其中LogfmtSerializer实现Encoder<Event>trait,内部直接调用上述共享实现:
impl Encoder<Event> for LogfmtSerializer { type Error = vector_common::Error; fn encode(&mut self, event: Event, buffer: &mut BytesMut) -> Result<(), Self::Error> { let log = event.as_log(); let string = encode_logfmt::encode_value(log.value())?; buffer.extend_from_slice(string.as_bytes()); Ok(()) } }这说明所有走logfmt/key_value编码器的 sink(file sink 的encoding.logfmt、各 HTTP sink 的编码器等)共享同一份转义逻辑,行为变更是全局一致的。
迁移要点:
- 若下游解析器依赖"值中裸双引号不转义"的旧格式(例如
msg={"some":"val"}直接按 JSON 解析),升级到 0.22 后需同步调整解析逻辑以处理"{\"some\":\"val\"}"形态; - 若下游是标准 logfmt 解析器(如 Go 的
kr/logfmt、多数 Java/Python 实现),新行为反而修正了原来"含引号但不含空格的值不加引号"导致的解析歧义,属于兼容性收益。
弃用声明:17 个废弃 transform 将在 0.23.0 移除
背景
remaptransform 引入时,一批旧 transform 被标记为废弃并从文档中移除,但实现仍保留在 Vector 中。0.22 的升级指南明确宣布:这些 transform 将在 0.23.0 中最终移除,同时merge由reduce替代。
将移除的 transform 完整清单:
| Transform | 替代方案 |
|---|---|
add_fields | remap |
add_tags | remap |
ansi_stripper | remap(strip_ansi_escape_codes) |
aws_cloudwatch_logs_subscription_parser | remap(parse_aws_cloudwatch_log_subscription_message) |
coercer | remap(to_bool!/to_int!等) |
concat | remap(join!) |
grok_parser | remap(parse_grok!) |
json_parser | remap(parse_json) |
key_value_parser | remap(parse_key_value!) |
logfmt_parser | remap(parse_logfmt!) |
merge | reduce |
regex_parser | remap(parse_regex!) |
remove_fields | remap(del) |
remove_tags | remap(del) |
rename_fields | remap(del+ 赋值) |
split | remap(split) |
tokenizer | remap(parse_tokens!) |
从当前仓库的源码结构看,src/transforms/目录下已不存在coercer.rs、tokenizer.rs、add_fields.rs等对应文件,印证了这批 transform 已在新版本中彻底移除——当前仓库即处于"移除之后"的状态,因此下面的迁移对照表既是升级指南,也是这批 transform 历史行为的完整档案。
逐项迁移对照
add_fields
# Before transforms: add_fields: type: "add_fields" inputs: ["some_input"] fields: parent: child2: "value2" # After transforms: add_fields: type: "remap" inputs: ["some_input"] source: | .parent.child2 = "value2"add_tags
# Before transforms: add_tags: type: "add_tags" inputs: ["some_input"] tags: some_tag: "some_value" # After transforms: add_tags: type: "remap" inputs: ["some_input"] source: | .tags.some_tag = "some_value"ansi_stripper
# Before transforms: ansi_stripper: type: "ansi_stripper" inputs: ["some_input"] # After transforms: ansi_stripper: type: "remap" inputs: ["some_input"] drop_on_error: false source: | .message = strip_ansi_escape_codes(string!(.message))aws_cloudwatch_logs_subscription_parser
# Before transforms: aws_cloudwatch_logs_subscription_parser: type: "aws_cloudwatch_logs_subscription_parser" inputs: ["some_input"] # After transforms: aws_cloudwatch_logs_subscription_parser: type: "remap" inputs: ["some_input"] drop_on_error: false source: | . |= parse_aws_cloudwatch_log_subscription_message!(.message)coercer
coercer支持对多个字段指定目标类型(bool/float/int/string/timestamp),迁移时用 VRL 的对应强制转换函数逐字段表达,!后缀表示出错时丢弃事件(与旧 transform 的严格模式语义对应):
# Before transforms: coercer: type: "coercer" inputs: ["some_input"] types: some_bool: "bool" some_float: "float" some_int: "int" some_string: "string" some_timestamp: "timestamp" # After transforms: coercer: type: "remap" inputs: ["some_input"] source: | .some_bool = to_bool!(.some_bool) .some_float = to_float!(.some_float) .some_int = to_int!(.some_int) .some_string = to_string!(.some_string) .some_timestamp = to_timestamp!(.some_timestamp)concat
# Before transforms: concat: type: "concat" inputs: ["some_input"] items: ["month", "day", "year"] target: "date" joiner: "/" # After transforms: concat: type: "remap" inputs: ["some_input"] drop_on_error: false source: | .date = join!([.month, .day, .year], "/")grok_parser
迁移时注意:旧的types中timestamp带时间格式("%+")表示时间戳解析,remap 版本用parse_timestamp!显式表达:
# Before transforms: grok_parser: type: "grok_parser" inputs: ["some_input"] pattern: "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:message}" types: timestamp: "timestamp|%+" level: "string" message: "string" # After transforms: grok_parser: type: "remap" inputs: ["some_input"] drop_on_error: false source: | . |= parse_grok!(.message, "%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:message}") .timestamp = parse_timestamp!(.timestamp, format: "%+")json_parser
# Before transforms: json_parser: type: "json_parser" inputs: ["some_input"] # After transforms: json_parser: type: "remap" inputs: ["some_input"] drop_on_error: false source: | . |= object!(parse_json(.message))key_value_parser
# Before transforms: key_value_parser: type: "key_value_parser" inputs: ["some_input"] # After transforms: key_value_parser: type: "remap" inputs: ["some_input"] drop_on_error: false source: | . |= parse_key_value!(.message)logfmt_parser
# Before transforms: logfmt_parser: type: "logfmt_parser" inputs: ["some_input"] # After transforms: logfmt_parser: type: "remap" inputs: ["some_input"] drop_on_error: false source: | . |= parse_logfmt!(.message)merge→reduce
merge由reduce替代,需要通过starts_when与merge_strategies表达原来的"按._partial分组拼接message"逻辑:
# Before transforms: merge: type: "merge" inputs: ["some_input"] # After transforms: merge: type: "reduce" inputs: ["some_input"] starts_when: "._partial == true" merge_strategies: message: "concat"regex_parser
旧的types中timestamp|%d/%m/%Y:%H:%M:%S %z这类"类型|格式"复合声明,迁移后拆分为parse_regex!+ 逐字段类型转换两步:
# Before transforms: regex_parser: type: "regex_parser" inputs: ["some_input"] patterns: - '^(?P<host>[\w\.]+) - (?P<user>[\w]+) (?P<bytes_in>[\d]+) \[(?P<timestamp>.*)\] "(?P<method>[\w]+) (?P<path>.*)" (?P<status>[\d]+) (?P<bytes_out>[\d]+)$' types: bytes_in: "int" timestamp: "timestamp|%d/%m/%Y:%H:%M:%S %z" status: "int" bytes_out: "int" # After transforms: regex_parser: type: "remap" inputs: ["some_input"] drop_on_error: false source: | . |= parse_regex!(.message, [#"^(?P<host>[\w\.]+) - (?P<user>[\w]+) (?P<bytes_in>[\d]+) \[(?P<timestamp>.*)\] "(?P<method>[\w]+) (?P<path>.*)" (?P<status>[\d]+) (?P<bytes_out>[\d]+)$"#]) .bytes_in = to_int!(.bytes_in) .some_timestamp = parse_timestamp!(.some_timestamp, "%d/%m/%Y:%H:%M:%S %z") .status = to_int!(.status) .bytes_out = to_int!(.bytes_out)(原文档示例中.some_timestamp的字段名为示意写法,实际迁移时请以你自己 pattern 中命名捕获组的真实字段名为准。)
remove_fields
# Before transforms: remove_fields: type: "remove_fields" inputs: ["some_input"] fields: ["parent.child"] # After transforms: remove_fields: type: "remap" inputs: ["some_input"] source: | del(.parent.child)remove_tags
# Before transforms: remove_tags: type: "remove_tags" inputs: ["some_input"] tags: ["some_tag"] # After transforms: remove_tags: type: "remap" inputs: ["some_input"] source: | del(.tags.some_tag)rename_fields
del的返回值是"删除前的值",这一特性正好用来完成"取值 + 删除"两步合一的重命名:
# Before transforms: rename_fields: type: "rename_fields" inputs: ["some_input"] fields: new_name: ["old_name"] # After transforms: rename_fields: type: "remap" inputs: ["some_input"] source: | .new_name = del(.old_name)split
# Before transforms: split: type: "split" inputs: ["some_input"] field_names: ["remote_addr", "user_id", "timestamp", "message", "status", "bytes"] types: status: "int" bytes: "int" # After transforms: split: type: "remap" inputs: ["some_input"] drop_on_error: false source: | values = split(.message) .remote_addr = values[0] .user_id = values[1] .timestamp = values[2] .message = values[3] .status = to_int!(values[4]) .bytes = to_int!(values[5])tokenizer
tokenizer按空白分词(不区分引号内的空格),remap 版本使用parse_tokens!:
# Before transforms: tokenizer: type: "tokenizer" inputs: ["some_input"] field_names: ["remote_addr", "ident", "user_id", "timestamp", "message", "status", "bytes"] types: status: "int" bytes: "int" # After transforms: tokenizer: type: "remap" inputs: ["some_input"] drop_on_error: false source: | values = parse_tokens!(.message) .remote_addr = values[0] .user_id = values[1] .timestamp = values[2] .message = values[3] .status = to_int!(values[4]) .bytes = to_int!(values[5])(注意原文档示例中tokenizer的field_names有 7 项但示例源码只映射了 6 项,迁移时请根据实际字段个数调整索引。)
升级操作清单
结合以上全部变更,推荐的升级步骤:
- 全文检索配置中的废弃 transform 类型名(
add_fields、coercer、grok_parser、json_parser等 17 个),按上表逐一改写为remap/reduce; - 检查
gcp_stackdriver_metricssink:若resource下存在labels层,去掉该层并将标签平铺; - 审查 VRL 脚本:确认没有字符串意外引入
{{ var }}插值语义;需要字面量{{/}}的地方改用\{{/\}}转义或s'...'原始字符串; - 评估 logfmt/key_value 编码的下游影响:确认消费端能处理"含引号的值带转义引号"的新输出格式;
- 运行
vector validate校验改写后的配置,确认所有 VRL 脚本编译通过、组件类型有效。
参考文件
- 升级指南原文:website/content/en/highlights/2022-05-03-0-22-0-upgrade-guide.md
gcp_stackdriver_metricssink 配置实现:src/sinks/gcp/stackdriver/metrics/config.rs- GCP 认证配置(
GcpAuthConfig):src/gcp.rs - logfmt 编码器封装:lib/codecs/src/encoding/format/logfmt.rs
- logfmt/key_value 核心编码实现:lib/vector-common/shared/src/lib.rs
需要说明的是,0.22 升级指南是 2022 年 5 月针对当时版本发布的;本文引用的源码路径与实现细节(如GcpTypedResource平铺结构、src/transforms/中旧 transform 已不存在等)均以当前仓库的实际状态为准,两者在时间上存在代差,若你在较新版本上排查同类问题,建议以当前仓库源码为最终依据。
【免费下载链接】vectorA high-performance observability data pipeline.项目地址: https://gitcode.com/GitHub_Trending/vect/vector
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考