Vector配置和运行单元测试

Vector可以让你对处理管道中的转换进行单元测试。在Vector中,单元测试的工作方式与大多数编程语言中的单元测试相同:

  1. 提供一组输入数据给一个转换(或多个连接在一起的转换)。
  2. 指定从转换(或多个转换)所做的更改中期望的输出。
  3. 在任何测试失败时,直接获得可操作的反馈。

在生产环境中运行Vector时,单元测试可以作为一个有用的防护栏。它可以确保您的拓扑结构不会表现出意外的行为,并且通常可以提高您的Vector管道的可维护性,特别是在较大和更复杂的管道中。

Vector支持以下几种配置文件格式:

  1. TOML:这是Vector默认使用的配置文件格式,它是一种易于阅读和编写的格式,具有灵活性和可扩展性。
  2. YAML:这是一种常见的配置文件格式,也是Vector支持的格式之一。
  3. JSON:这是一种轻量级的数据交换格式,在Vector中也可以用作配置文件格式。

总之,Vector支持多种常见的配置文件格式,以便开发者可以根据自己的喜好和需求选择适合自己的格式。

安装Vector

使用脚本安装Vector:

curl --proto '=https' --tlsv1.2 -sSf https://sh.vector.dev | bash

Docker安装Vector:

docker pull timberio/vector:latest-alpine

或者其他安装方式

一旦安装了Vector,请检查确保其正常工作:

vector --version

运行单元测试

使用Vector的test子命令在配置文件中执行测试:

vector test /etc/vector/vector.toml

也可以指定多个配置文件进行测试:

vector test /etc/vector/pipeline1.toml /etc/vector/pipeline2.toml

验证输出

可以使用 VRL 断言来验证被测试的转换的输出是否符合期望。VRL提供了两个断言函数:

  • assert函数将布尔表达式作为其第一个参数。如果布尔值为false,则测试失败并且 Vector 记录一个错误。

  • assert_eq 函数将任意两个值作为其前两个参数。如果这两个值不相等,则测试失败并且 Vector 记录一个错误。

使用这两个函数,您可以提供一个自定义日志消息,以便在断言失败时发出:

# 命名参数
assert!(1 == 2, message: "the rules of arithmetic have been violated")
assert_eq!(1, 2, message: "the rules of arithmetic have been violated")

# 位置参数
assert!(1 == 2, "the rules of arithmetic have been violated")
assert_eq!(1, 2, "the rules of arithmetic have been violated")

建议在单元测试中使用断言时,应用感叹号(!)语法,使assertassert_eq调用准确无误,例如使用assert!(1 == 1)而不是assert(1 == 1)。感叹号表示,如果条件失败,VRL程序应该中止。

如果使用assert函数,则需要将布尔表达式作为第一个参数传递给函数。编写布尔表达式是类型函数时,尤其有用的函数如exists, includes, is_nullishandcontains和 VRL comparison。下面是传递给函数的布尔表达式的示例用法assert

[[tests.outputs.conditions]]
type = "vrl"
source = '''
assert!(is_string(.message) && is_timestamp(.timestamp) && !exists(.other))
'''

在这种情况下,VRL 程序(source)将计算一个布尔值,表达以下内容:

  • message字段必须是一个字符串
  • timestamp字段必须是有效的时间戳
  • other字段不得存在

也可以将测试分解为多个assertorassert_eq语句:

source = '''
assert!(exists(.message), "no message field provided")
assert!(!is_nullish(.message), "message field is an empty string")
assert!(is_string(.message), "message field has as unexpected type")
assert_eq!(.message, "success", "message field had an unexpected value")
assert!(exists(.timestamp), "no timestamp provided")
assert!(is_timestamp(.timestamp), "timestamp is invalid")
assert!(!exists(.other), "extraneous other field present")
'''

还可以将布尔表达式存储在变量中,而不是将整个语句传递给函数assert

source = '''
message_field_valid = exists(.message) &&
  !is_nullish(.message) &&
  .message == "success"

assert!(message_field_valid)
'''

example

下面是一个针对名为add_metadata的转换的单元测试套件的示例,该转换将唯一标识符和时间戳添加到日志事件中,并添加了注释说明:

[sources.all_container_services]
type = "docker_logs"
docker_host = "http://localhost:2375"
include_images = ["web_frontend", "web_backend", "auth_service"]

# The transform being tested is a Vector Remap Language (VRL) transform that
# adds two fields to each incoming log event: a timestamp and a unique ID
[transforms.add_metadata]
type = "remap"
inputs = ["all_container_services"]
source = '''
.timestamp = now()
.id = uuid_v4()
'''

# Here we begin configuring our test suite
[[tests]]
name = "Test for the add_metadata transform"

# The inputs for the test
[[tests.inputs]]
insert_at = "add_metadata" # The transform into which the testing event is inserted
type = "log"               # The event type (either log or metric)

# The test log event that is passed to the `add_metadata` transform
[tests.inputs.log_fields]
message = "successful transaction"
code = 200

# The expected outputs of the test
[[tests.outputs]]
extract_from = "add_metadata" # The transform from which the resulting event is extracted

# The declaration of what we expect
[[tests.outputs.conditions]]
type = "vrl"
source = '''
assert!(is_timestamp(.timestamp))
assert!(is_string(.id))
assert_eq!(.message, "successful transaction")
'''

这段配置文件是一个示例,展示了一个名为 add_metadata 的 Vector 转换的测试套件配置。此转换使用 Vector Remap Language(VRL)添加了时间戳和唯一 ID 两个字段到每个传入的日志事件中。

在配置文件中,首先定义了数据源 all_container_services,类型为 docker_logs,指定了 Docker 主机地址,并且限定了包含的容器镜像。接着定义了 add_metadata 转换,指定了数据源为 all_container_services,使用 VRL 的语法对每个传入的事件进行转换。

在测试套件的配置中,定义了一个名为 Test for the add_metadata transform 的测试套件。然后是测试输入,其中指定了在哪个转换中插入测试事件,指定了事件类型为 log,并指定了测试事件的字段。接着指定了预期输出,包括从哪个转换中提取结果,并声明了预期的输出条件,即使用 VRL 的语法对结果事件的字段进行断言。

总的来说,这个配置文件可以帮助用户测试 Vector 转换的正确性,以确保其符合预期的行为。

真实输入和测试输入

在这个例子的配置中,一个重要的事情要注意的是,Vector 被设置为使用 docker_logs 数据源从 Docker 镜像中获取真实的日志。如果 Vector 在生产环境中运行,我们在这里进行单元测试的 add_metadata 转换将会修改真实的日志事件。但这并不是我们在这里测试的内容。相反,insert_at = "add_metadata" 指令人为地将我们的测试输入插入到add_metadata转换中。应该将 Vector 单元测试视为一种模拟可观察性数据源并确保您的转换以您期望的方式响应这些模拟源的方法。

配置单元测试

Vector中的单元测试与拓扑配置位于同一文件中。您可以在同一个配置文件中指定测试,也可以将其拆分到单独的文件中。

单元测试需要在tests数组中指定。每个测试都需要一个name

[[tests]]
name = "test 1"
# Other test config

[[tests]]
name = "test_2"
# Other test config

# etc.

在每个测试定义中,需要指定两个内容:

  • 一个 inputs 数组,为测试提供输入事件。

  • 一个 outputs 数组,提供测试的期望输出。

Inputs

在测试的 inputs 数组中,您有以下选项:

参数 类型 描述
insert_at string (name of transform) 插入测试输入的转换名称。当只想测试转换管道的子集时,他的功能特别有用。
value string (raw event value) 在某些情况下,事件可能是原始字符串而不是结构化对象,这时可以使用原始字符串值作为输入事件。
log_fields object 如果 transform 处理的是 log 事件,那么输入事件包含的键值对就是指 log 事件的字段和值。
metric object 如果 transform 处理的是 metric 事件,则这些是构成该度量的字段。子字段包括nametagskind和其他。

下面是一个inputs 示例:

[transforms.add_metadata]
# transform config

[[tests]]
name = "Test add_metadata transform"

[[tests.inputs]]
insert_at = "add_metadata"

[tests.inputs.log_fields]
message = "<102>1 2020-12-22T15:22:31.111Z vector-user.biz su 2666 ID389 - Something went wrong"

Outputs

在单元测试配置的outputs数组中,需要指定两个内容:

参数 类型 描述
extract_from string (name of transform) 要测试的输出转换。
conditions array of objects 需要运行以检查输出的 VRL 条件。

每个 conditions 数组中的条件都有两个字段:

参数 类型 描述
type string 表示提供的条件的类型。在当前版本中,唯一有效的值为 vrl
source string (VRL Boolean expression)

下面是一个outputs 示例:

[[tests.outputs]]
extract_from = "add_metadata"

[[tests.outputs.conditions]]
type = "vrl"
source = '''
assert!(is_string(.id))
assert!(exists(.tags))
'''

Asserting no output

在某些情况下,您可能需要断言某个转换不会输出任何事件。您可以使用no_outputs_from参数在特定测试配置的根级别上指定这一点,该参数接受一个转换名称列表。以下是一个示例:

[[tests]]
name = "Ensure no output"
no_outputs_from = ["log_filter", "metric_filter"]

在这个测试配置中,Vector将期望 log_filtermetric_filter 转换器不会输出任何事件。

以下是 no_outputs_from 的一些使用案例:

  • 当测试一个 filter 转换时,您可能希望断言输入事件被过滤掉了。

  • 当测试一个remap转换时,您可能需要断言,当在 VRL 程序处理输入事件过程中abort函数被调用时。

下面是在Vector单元测试中使用no_outputs_from的完整示例:

[transforms.log_filter]
type = "filter"
inputs = ["log_source"]
condition = '.env == "production"'

[[tests]]
name = "Filter out non-production events"
no_outputs_from = ["log_filter"]

[[tests.inputs]]
type = "log"
insert_at = "log_filter"

[tests.inputs.log_fields]
message = "success"
code = 202
endpoint = "/transactions"
method = "POST"
env = "staging"

Event types

当前有两种事件类型可以在 Vector 中进行单元测试:

Logs

Object

要将结构化日志事件指定为测试输入,请使用 log_fields

[tests.inputs.log_fields]
message = "successful transaction"
code = 200
id = "38c5b0d0-5e7e-42aa-ae86-2b642ad2d1b8"

如果字段名称中有连字符(hyphens -),则需要将其部分引用起来(至少在 YAML 中需要这样做):

  - name: hyphens
    inputs:
      - insert_at: hyphens
        type: log
        log_fields:
          labels."this-has-hyphens": "this is a test"
Raw string value

要为日志事件指定原始字符串值,请使用value

[[tests.inputs]]
insert_at = "add_metadata"
value = "<102>1 2020-12-22T15:22:31.111Z vector-user.biz su 2666 ID389 - Something went wrong"

Metrics

你可以使用 metric 对象来指定要进行单元测试的指标事件中的字段:

[[tests.inputs]]
insert_at = "my_metric_transform"
type = "metric"

[tests.inputs.metric]
name = "count"
kind = "absolute"
counter = { value = 1 }

聚合指标稍有不同:

tests:
  inputs:
    insert_at: my_aggregate_metrics_transform
    type: metric
    metric:
      name: http_rtt
      kind: incremental
      aggregated_histogram:
        buckets: []
        sum: 0
        count: 0

以下是通过转换对指标进行单元测试的完整端到端示例:

[transforms.add_env_to_metric]
type = "remap"
inputs = []
source = '''
env, err = get_env_var("ENV")
if err != null {
  log(err, level: "error")
}
tags.environment = env
'''

[[tests]]
name = "add_unique_id_test"

[[tests.inputs]]
insert_at = "add_unique_id_to_metric"
type = "metric"

[tests.inputs.metric]
name = "website_hits"
kind = "absolute"
counter = { value = 1 }

[[tests.outputs]]
extract_from = "add_unique_id_to_metric"

[[tests.outputs.conditions]]
type = "vrl"
source = '''
assert_eq!(.name, "website_hits")
assert_eq!(.kind, "absolute)
assert_eq!(.tags.environment, "production")
'''

Multiple transforms

到目前为止,这个文档提供的示例仅涉及到单个转换的单元测试。然而,也可以测试多个链接在一起的转换的输出。想象一下这样一个场景:你有一个名为add_env_metadata的转换,用于给事件打上环境元数据的标记;一个名为sanitize的转换,用于删除一些不需要的字段;最后一个名为add_host_metadata的转换,用于为事件打上主机名的标记。以下是这组转换的示例单元测试配置,其中包含解释性注释:

# This source, like all sources, is ignored in the unit test itself
[sources.web_backend]
type = "docker_logs"
docker_host = "http://localhost:2375"
include_images = ["web_backend"]

# The first transform in the chain
[transforms.add_env_metadata]
type = "remap"
inputs = ["web_backend"]
source = '''
.tags.environment = "production"
'''

# The second transform in the chain
[transforms.sanitize]
type = "remap"
inputs = ["add_env_metadata"]
source = '''
del(.username)
del(.email)
'''

# The final transform in the chain
[transforms.add_host_metadata]
type = "remap"
inputs = ["sanitize"]
source = '''
.tags.host = "web-backend1.vector-user.biz"
'''

[[tests]]
name = "Multiple chained remap transforms"

[[tests.inputs]]
type = "log"
# Insert test input events into the first transform
insert_at = "add_env_metadata"

# The input event to insert into the first transform in the chain
[tests.inputs.log_fields]
message = "image successfully uploaded"
code = 202
username = "tonydanza1337"
email = "[email protected]"
transaction_id = "bcef6a6a-2b72-4a9a-99a0-97ae89d82815"

[[tests.outputs]]
# Extract test outputs from the last transform
extract_from = "add_host_metadata"

[[tests.outputs.conditions]]
type = "vrl"
# Our VRL assertions for the test output
source = '''
assert_eq!(.tags.environment, "production", "incorrect environment tag")
assert_eq!(.tags.host, "web-backend1.vector-user.biz", "incorrect host tag")
assert!(!exists(.username))
assert!(!exists(.email))

valid_transaction_id = exists(.transaction_id) &&
  is_string(.transaction_id) &&
  length!(.transaction_id) == 36

assert!(valid_transaction_id, "transaction ID invalid")
'''

从测试的角度来看,这里的三个转换可以被视为1个单元。一个示例事件插入到链的开头(add_env_metadata),一个输出测试事件从链的末端(add_host_metadata)提取,一组 VRL 断言验证输出事件是否符合我们的预期。

也可以测试此转换链的子集。例如,此配置将仅测试前两个转换(add_env_metadatasanitize):

[[tests]]
name = "First two transforms"

[[tests.inputs]]
type = "log"
# Insert test input into the first transform
insert_at = "add_env_metadata"

# For comparison, we can use the same input event as above
[tests.inputs.log_fields]
message = "image successfully uploaded"
code = 202
username = "tonydanza1337"
email = "[email protected]"
transaction_id = "bcef6a6a-2b72-4a9a-99a0-97ae89d82815"

[[tests.outputs]]
# Extract test output from the second transform rather than the last
extract_from = "sanitize"

[[tests.outputs.conditions]]
type = "vrl"
source = '''
assert_eq!(.tags.environment, "production", "incorrect environment tag")
assert!(!exists(.tags.host), "host tag included")
assert!(!exists(.username))
assert!(!exists(.email))

valid_transaction_id = exists(.transaction_id) &&
  is_string(.transaction_id) &&
  length!(.transaction_id) == 36

assert!(valid_transaction_id, "transaction ID invalid")
'''

在两个 transform 的 VRL 条件中,请注意关于标签的断言host 更改为这个,它验证该标签不存在,这是我们应该期望的,因为add_host_metadata此处不包含变换:

assert!(!exists(.tags.host), "host tag included")