通用表达式语言 ( CEL ): CEL 输入如何改进 Elastic Agent 集成中的数据收集

作者:来自 Elastic Chris Berkhout

了解通用表达式语言与其他编程语言的不同之处,我们如何为 Filebeat 的 CEL 输入扩展它,以及它在 Elastic Agent 集成中为你表达数据收集逻辑提供的灵活性。

Agent Builder 现已 GA。立即开始 Elastic Cloud Trial,并在此查看 Agent Builder 的 文档。


Elastic Agent 集成 允许用户从多种来源将数据摄取到 Elasticsearch。它们将数据收集逻辑、ingest pipelines、dashboards 和 其他 构件 组合成一个包,可通过 Kibana web 界面进行安装和管理。

集成通过配置 Filebeat inputs 来进行数据收集。为了从 HTTP APIs 收集数据,我们通常使用 HTTP JSON input。然而,即使是基本的 listing APIs,在细节上也可能差异很大,而 HTTP JSON input 基于 YAML 配置的转换模型,使得表达所需的数据收集逻辑变得笨拙,甚至有时无法实现。

引入 Common Expression Language ( CEL ) input 是为了支持与 HTTP APIs 的更灵活交互。CEL 是一种语言,旨在嵌入到需要以快速、安全且可扩展方式表达条件和数据转换的应用中。CEL input 允许集成构建者编写一个表达式,该表达式可以读取设置、跟踪自身状态、发起请求、处理响应,并最终返回可供摄取的事件。

在本文中,我们将介绍 CEL 与 其他 编程语言 的不同之处,我们如何为 CEL input 扩展它,以及它为你表达数据收集逻辑所带来的灵活性和能力。

CEL 以及它在 input 中的工作方式

CEL 是一种表达式语言。它没有语句。当你编写 CEL 时,你不是通过写语句来告诉它要做什么,而是通过写一个表达式来告诉它要产生什么值。每个 CEL 表达式都会产生一个值,较小的表达式可以组合成一个更大的表达式,根据更复杂的规则产生结果。稍后,我们会看到如何使用表达式来实现在其他语言中可能用语句编写的事情。

CEL 有意被设计为一种非图灵(non-Turing)完备的语言。它不允许无界循环。稍后,我们会看到你如何使用 macros 来处理 lists 和 maps,但通过避免无界循环,该语言可以保证单个表达式具有可预测且受限的执行时间。

CEL input 使用一个 CEL 程序(一个表达式)和一些初始状态进行配置。该状态会作为输入提供给程序。程序被求值以生成一个输出状态。如果输出状态包含一个 events 列表,这些 events 将被移除并发布。输出状态的其余部分将作为下一次求值的输入。如果输出状态包含一个或多个 events,并且标志 want_more: true,则会立即执行下一次求值;否则,它将在继续之前休眠配置的 interval 时间的剩余部分。下面是 input 控制流的一个简化示意图:

每次求值的输出将作为下一次求值的输入传递,只要 input 运行。键 "cursor" 下的输出数据将被持久化到磁盘,并在 input 重启后重新加载,但状态的其余部分不会跨重启保留。

CEL 语言本身功能有限并且避免副作用,但它是可扩展的。cel-go 实现增加了一些功能,例如可选语法和类型。Mito 库在 cel-go 基础上扩展了更多功能,包括发起 HTTP 请求的能力。CEL input 使用 Mito 的 CEL 版本。

使用 Mito

要使用 CEL input 构建或调试一个集成,最重要的是理解你的 CEL 程序在给定输入状态下会产生什么输出状态。在开发过程中,让 CEL 程序在 input 中运行,并且被完整的 Elastic stack 包围,可能会很繁琐。一种实现更快反馈循环的方法是使用 Mito 的命令行工具,它可以让你直接运行 CEL 程序,并查看它在给定输入下产生的输出。

Mito 是用 Go 编写的,可以通过以下方式安装:

bash 复制代码
`go install github.com/elastic/mito/cmd/mito@latest`AI写代码

当你使用 Mito 运行 CEL 程序时,通常需要提供两个文件:一个包含初始输入状态的 JSON 文件,以及另一个包含 CEL 程序源代码的文件。

css 复制代码
`mito -data state.json src.cel`AI写代码

为了便于复制和粘贴,本文中的示例以单条命令的形式编写,通过将每个文件的内容包裹在 <(echo '...content...') 中,让 shell 动态创建临时文件。在你自己的开发中,使用实际文件会更方便。

从 GitHub 获取 issues 数据

下面的示例包含一个完整的 CEL 程序,用于从 GitHub API 获取关于 issues 的数据。其初始输入状态包含 API 端点的 URL,以及一些关于如何处理分页的信息。CEL 程序使用输入状态中的数据生成请求,它会解码响应,从中生成 events,并将它们作为输出状态的一部分返回。

less 复制代码
`

1.  mito -data <(echo '
2.    {
3.      "url": "https://api.github.com/repos/elastic/integrations/issues",
4.      "per_page": 3,
5.      "max_pages": 3
6.    }
7.  ') <(echo '
8.    int(state.?cursor.page.orValue(1)).as(page,
9.      (
10.        state.url + "?" + {
11.          "state": ["all"],
12.          "sort": ["created"],
13.          "direction": ["asc"],
14.          "per_page": [string(state.per_page)],
15.          "page": [string(page)],
16.        }.format_query()
17.      ).as(full_url,
18.        request("GET", full_url).with({
19.          "Header": {
20.            "Accept": ["application/vnd.github+json"],
21.            "X-GitHub-Api-Version": ["2022-11-28"],
22.          }
23.        }).do_request().as(resp,
24.          resp.Body.decode_json().as(data,
25.            state.with({
26.              "events": data.map(i, {
27.                "html_url": i.html_url,
28.                "title": i.title,
29.                "created_at": i.created_at,
30.              }),
31.              "cursor": { "page": page + 1 },
32.              "want_more": size(data) == state.per_page && page < state.max_pages,
33.            })
34.          )
35.        )
36.      )
37.    )
38.  ')

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

它的第一次求值产生以下输出:

bash 复制代码
`

1.  {
2.    "cursor": {
3.      "page": 2
4.    },
5.    "events": [
6.      {
7.        "created_at": "2018-09-14T09:47:35Z",
8.        "html_url": "https://github.com/elastic/integrations/issues/3250",
9.        "title": "Increase support of log formats in haproxy filebeat module"
10.      },
11.      {
12.        "created_at": "2019-02-06T12:37:37Z",
13.        "html_url": "https://github.com/elastic/integrations/issues/487",
14.        "title": "ETCD Metricbeat module needs polishing and grooming"
15.      },
16.      {
17.        "created_at": "2019-08-13T11:33:11Z",
18.        "html_url": "https://github.com/elastic/integrations/pull/1",
19.        "title": "Initial structure"
20.      }
21.    ],
22.    "max_pages": 3,
23.    "per_page": 3,
24.    "url": "https://api.github.com/repos/elastic/integrations/issues",
25.    "want_more": true
26.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

这些 events 将被移除,当在 CEL input 中运行时,它们会被发布以供摄取。输出的其余部分将作为下一次 CEL 程序求值的输入状态提供。

为了理解该 CEL 程序的工作原理,我们将查看一些更小的 CEL 示例,并讨论 CEL input 的更多操作细节。

CEL 基础

在 CEL 语言中,没有语句,只有表达式。每个成功的 CEL 表达式都会求值为最终值。下面是一个最小的 CEL 表达式示例及其输出:

vbnet 复制代码
`

1.  mito <(echo '
2.    "hello" + " " + "world"
3.  ')

`AI写代码
arduino 复制代码
`"hello world"`AI写代码

许多简单表达式是直观的。数学运算只支持相同类型的值(例如,int 与 int),所以根据需要进行类型转换(这里从 int 转为 double):

markdown 复制代码
`

1.  mito <(echo '
2.    double((1 + 2) * (3 + 4)) / 2.0
3.  ')

`AI写代码
go 复制代码
`10.5`AI写代码

CEL 语言中没有变量,但可以通过 Mito 的 as 宏 给表达式命名,并在更大的表达式中使用。在这个示例中,表达式 (1 + 1) 求值为 2,.as(n, ...) 给该值命名为 n,以便在表达式 "one plus one is "+string(n) 中使用:

vbnet 复制代码
`

1.  mito <(echo '
2.    (1 + 1).as(n, "one plus one is "+string(n))
3.  ')

`AI写代码
arduino 复制代码
`"one plus one is 2"`AI写代码

还可以在 map 中累积信息,并在表达式中稍后使用,如这里使用 with 演示的:

vbnet 复制代码
`

1.  mito <(echo '
2.    { "key": "value" }.with({ "key2": "value2" }).as(data,
3.      {
4.        "data": data,
5.        "size": size(data),
6.      }
7.    )
8.  ')

`AI写代码
markdown 复制代码
`

1.  {
2.    "data": {
3.      "key": "value",
4.      "key2": "value2"
5.    },
6.    "size": 2
7.  }

`AI写代码

再看这个示例。注意嵌套部分 ({ "data": data, "size": size(data), }),它给出了最终值的结构。它是一个包含键 "data" 和 "size" 的 map。这些键的值取决于 data,而 data 是由表达式的外部部分定义的。从内向外阅读 CEL 表达式可以帮助快速了解它们将返回什么。

CEL 没有控制流语句,如 if,但可以使用三元运算符进行条件分支:

vbnet 复制代码
`

1.  mito <(echo '
2.    1 + 1 < 12 ? "few" : "many"
3.  ')

`AI写代码
arduino 复制代码
`"few"`AI写代码

CEL 不支持无界循环和递归,因为 CEL 不是图灵完备语言。这使得执行时间可预测,并与输入数据的大小和表达式复杂度成正比。

虽然单个 CEL 表达式中无法使用无界循环,但可以使用宏(如 map)来处理 lists 和 maps:

markdown 复制代码
`

1.  mito <(echo '
2.    [1, 2, 3].map(x, x * 2)
3.  ')

`AI写代码
css 复制代码
`[2, 4, 6]`AI写代码

在本节中,我们介绍了:

Strings、numbers、lists 和 maps。

  • 字符串拼接。
  • 数学运算。
  • 类型转换。
  • 条件语句。
  • 命名子表达式。
  • 处理集合。

接下来,我们将了解如何发起 HTTP requests。

Requests

Mito 扩展了 CEL,使其能够发起 HTTP requests

vbnet 复制代码
`

1.  mito <(echo '
2.    get("https://example.com").as(resp, string(resp.Body))
3.  ')

`AI写代码
xml 复制代码
`"<!doctype html><html lang=\"en\"><head><title>Example Domain</title>..."`AI写代码

Requests 可以在执行前显式构建。这使得可以使用不同的 HTTP methods,并添加 headers 和 body。

在这个示例中,我们借助 format_query 构建 URL,为 request 添加 header,并使用 decode_json 解析响应 body。使用 -log_requests 选项时,Mito 会以 JSON 格式记录每个 request 和 response 的详细信息。

less 复制代码
`

1.  mito -log_requests <(echo '
2.    request("GET",
3.      "https://postman-echo.com/get?" + {
4.          "q": ["query value"]
5.       }.format_query()
6.    ).with({
7.      "Header": { "Accept": ["application/json"] }
8.    }).do_request().as(resp, {
9.      "status": resp.StatusCode,
10.      "data": resp.Body.decode_json(),
11.    })
12.  ')

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)
css 复制代码
`

1.  {"time":"...","level":"INFO","msg":"HTTP request",...}
2.  {"time":"...","level":"INFO","msg":"HTTP response",...}
3.  {
4.    "data": {
5.      "args": {
6.        "q": "query value"
7.      },
8.      "headers": {
9.        "accept": "application/json",
10.        "accept-encoding": "gzip, br",
11.        "host": "postman-echo.com",
12.        "user-agent": "Go-http-client/2.0",
13.        "x-forwarded-proto": "https"
14.      },
15.      "url": "https://postman-echo.com/get?q=query+value"
16.    },
17.    "status": 200
18.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

管理状态和求值

现在我们已经介绍了如何发起 requests 以及生成所需输出状态所需的 CEL 基础,让我们仔细看看应该放入输出状态的内容,以及它如何指导后续处理。

集成的 CEL 程序需要确保其输出状态适合作为下一次求值的输入。配置设置初始状态,并且应该在输出中以任何适当的更改重复该状态。一个简单的方法是使用 state.with({ ... }),在重复状态 map 时进行一些覆盖。小程序的常见模式是将整个程序包裹在 state.with() 中,这样 state 的传播就不必在每个生成输出数据的分支中重复(例如,success、errors)。

当有些状态值是由求值初始化的,而不是在初始输入状态中硬编码时,程序需要在设置初始值之前检查是否已有值。这是可选语法和类型支持可以帮助的地方。在 map key 的字段名前使用问号,可以让访问变为可选:它可能有值,也可能没有值,但可以进行进一步的可选访问,如果没有值也容易提供默认值:

less 复制代码
`

1.  mito -data <(echo '{}') <(echo '
2.    int(state.?counter.orValue(0)).as(counter,
3.      state.with({
4.        "counter": counter + 1,
5.        "want_more": counter + 1 < 3,
6.      })
7.    )
8.  ')

`AI写代码
json 复制代码
`

1.  { "counter": 1, "want_more": true }
2.  { "counter": 2, "want_more": true }
3.  { "counter": 3, "want_more": false }

`AI写代码

在该示例中,从 state 读取的 counter 值被转换为 int,因为所有数字在 state 中都以浮点数序列化,以符合 JSON 和 JavaScript 的 Number 类型约定。还需要注意,Mito 会在这里遵循 "want_more": true,但在 CEL input 中运行时,只有当输出中也包含 events 时,才会重复求值。

由 CEL input 运行的 CEL 程序要求输出 map 中返回一个 "events" 键。它的值可以是一个 event map 列表、空列表,或单个 event map。单个 event 情况通常用于 errors。该 event 会被 input 发布,其值也会被记录,如果设置了 error.message,则用于更新集成的 Fleet 健康状态。如果你的程序生成单个非错误事件,最好将其包装在列表中。

再看一下我们之前 GitHub issues 程序的输出:

markdown 复制代码
`

1.  {
2.    "url": "https://api.github.com/repos/elastic/integrations/issues",
3.    "per_page": 3,
4.    "max_pages": 3,
5.    "cursor": {
6.      "page": 2
7.    },
8.    "events": [
9.      { ... },
10.      { ... },
11.      { ... }
12.    ],
13.    "want_more": true
14.  }

`AI写代码![](https://csdnimg.cn/release/blogv2/dist/pc/img/runCode/icon-arrowwhite.png)

该程序有效地管理了其 state,通过:

  • 重复初始 state 值在 url、per_page 和 max_pages 中。
  • 在 cursor.page 中添加应跨重启持久化的 state。
  • 在 events 列表中返回准备发布的 events。
  • 使用 want_more: true 请求立即重新求值。

现在你已经理解了可选访问和状态管理,以及 CEL 基础和 HTTP requests,完整的 GitHub issues 程序应该可以阅读。尝试使用 Mito 运行它,并尝试一些修改。

复习与资源

在本文中,我们了解了 CEL 语言是什么,以及它在 Mito 库中如何扩展以用于 CEL input。我们通过一个从 GitHub API 获取 issues 信息的示例程序,看到 CEL 的灵活性,并详细讲解了理解该程序所需的所有细节,包括访问初始 state 中的设置、与 HTTP APIs 交互、返回待摄取的 events,以及管理 state 以供后续程序执行。

要了解更多并使用 CEL input 构建集成,有一些值得探索的资源:

也许构建 CEL input 集成最有价值的资源是现有 Elastic 集成的 CEL 代码,可以在 GitHub 上找到:

Elastic integrations 仓库中的 cel.yml.hbs 文件 - GitHub

原文:www.elastic.co/search-labs...

相关推荐
海兰2 天前
离线合同结构化提取与检索:LangExtract + 本地DeepSeek + Elasticsearch 9.x
大数据·elasticsearch·django
yumgpkpm2 天前
AI视频生成:Wan 2.2(阿里通义万相)在华为昇腾下的部署?
人工智能·hadoop·elasticsearch·zookeeper·flink·kafka·cloudera
Sheffield2 天前
如果把ZooKeeper按字面意思比作动物园管理员……
elasticsearch·zookeeper·kafka
嗝屁小孩纸2 天前
ES索引重建(零工具纯脚本执行)
大数据·elasticsearch·搜索引擎
Elastic 中国社区官方博客2 天前
使用 Jina Embeddings v5 和 Elasticsearch 构建“与你的网站数据聊天”的 agent
大数据·人工智能·elasticsearch·搜索引擎·容器·全文检索·jina
Elastic 中国社区官方博客2 天前
Elastic 公共 roadmap 在此
大数据·elasticsearch·ai·云原生·serverless·全文检索·aws
码云数智-大飞2 天前
像写 SQL 一样搜索:dbVisitor 如何用 MyBatis 范式颠覆 ElasticSearch 开发
sql·elasticsearch·mybatis
海兰2 天前
Jina Embeddings V5 Text + Elasticsearch 9.x 本地部署指南
elasticsearch·jenkins·jina