mcpField
O
observability-logs-search

observability-logs-search

github.com/elastic云与 DevOps6023.2k 次安装今天更新
代码仓库

关于 observability-logs-search

使用 ES|QL 迭代筛选 Elasticsearch 日志的调查技能。它帮助可观测性用户结合时间趋势和消息分类逐步缩小结果,定位错误或根因。

主要功能

  1. 01使用 ES|QL 搜索日志
  2. 02查看时间序列直方图和计数
  3. 03采样并分类日志消息
  4. 04用 KQL 迭代排除噪声
  5. 05查看关联日志或切换实体

适用场景

  1. 01在事件调查中缩小日志范围
  2. 02查找服务日志激增的原因
  3. 03从结果中过滤重复的正常消息
  4. 04检查关键事件前后的日志

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/elastic/agent-skills --skill observability-logs-search

安装后在 Claude Code 中输入/observability-logs-search确认已加载。

附带文件references/log-search-reference.md

详细介绍

日志搜索

搜索和筛选日志,以支持事件调查。此工作流与 Kibana Discover 类似:应用时间范围和范围筛选条件,然后逐步添加排除筛选条件(NOT),直到剩下少量有价值的日志子集——可能是根本原因,也可能是关键文档。你也可以选择在上下文中查看日志(该文档前后的日志),或切换到其他实体并开始新的搜索。仅使用 ES|QL(POST /_query);不要使用 Query DSL。

不适用的场景

  • 指标或链路追踪 — 请使用专用的指标或链路追踪工具。

参数约定

对 Observability 日志搜索使用一致的名称:

参数类型描述
startstring时间范围的起始时间(Elasticsearch 日期数学表达式,例如 now-1h)
endstring时间范围的结束时间(例如 now)
kqlFilterstring用于缩小结果范围的 KQL 查询字符串。请勿使用 query、filter 或 kql。
limitnumber要返回的日志样本数量上限(例如 10–100)
groupBystring可选的直方图分组字段(例如 log.level、service.name)

实体筛选条件请使用 ECS 字段名称:service.name、host.name、service.environment、kubernetes.pod.name、 kubernetes.namespace。仅查询 ECS 名称;Observability 索引中的 OpenTelemetry 别名会自动映射。

精简上下文

保持上下文窗口较小。在查询的样本分支中,仅保留一部分字段;默认不要返回完整文档。使用 KEEP 的小型摘要(例如 10 个文档)约占用 1000 个 token;单个完整 JSON 文档就可能超过 4000 个 token。

推荐的日志样本 KEEP 列表:
message、error.message、service.name、container.name、host.name、container.id、agent.name、 kubernetes.container.name、kubernetes.node.name、kubernetes.namespace、kubernetes.pod.name

**消息回退顺序:**如果字段存在,请使用以下顺序中第一个非空字段:body.text(OTel)、message、error.message、 event.original、exception.message、error.exception.message、attributes.exception.message(OTel)。Observability 索引模板通常会为这些字段设置别名;构建单个用于显示的“消息”时,请优先采用此顺序。

**限制样本数量:**每次查询默认仅获取少量样本(10–20 条日志)。上限为 500;不要在一次调用中获取数千条日志。 每个筛选步骤的目的只是决定下一次调用——只有最终缩小范围后的结果才应保留在上下文中并进行摘要。

漏斗式工作流

必须迭代。 不要在查询一次后就停止。持续添加 NOT 排除噪声,直到剩余的日志模式少于 20 种 (不同的消息类别)。**迭代时始终保留完整筛选条件:**将新的 NOT 添加到之前的 KQL 中;不要“缩小视野”或删除之前的排除条件。

  1. **第 1 轮 — 广泛查询:**仅使用范围筛选条件(例如 service.name: advertService)和时间范围运行查询。获取 总数、直方图、日志样本和消息分类(常见模式 + 罕见模式)。
  2. 检查:查看直方图(何时出现峰值或下降)、示例消息和分类后的模式(fork4 = 数量最多的模式,fork5 = 罕见模式)。如果直方图显示某个时间点出现明显峰值,则在下一轮中缩小该峰值附近的时间范围(t_start、t_end)。统计剩余的不同日志 模式数量(根据分类结果);找出需要排除的高频噪声。
  3. 第 2 轮 — 排除噪声:在 KQL 筛选条件中为主要噪声模式添加 NOT 子句。使用完整筛选条件(所有之前的 NOT 和新添加的 NOT)重新运行查询。
  4. 重复:持续添加 NOT 子句,并使用完整筛选条件重新运行查询。不要在一两轮后就停止。继续执行,直到剩余日志模式少于 20 种(使用分类结果统计不同的消息类别)。此时剩余内容足够少,可以将其解读为值得关注的内容(错误、异常、根本原因)。
  5. **切换分析对象(可选):**漏斗筛选出特定实体(例如 container.id、host.name)后,再运行一次针对该实体的查询,查看它的“临终遗言”或周边上下文。
  6. **退一步(如有需要):**如果漏斗未能揭示根本原因,可以考虑结合上下文查看日志(关键文档前后的日志),或改用其他实体并开始全新搜索。

如果停止时日志模式仍不少于 20 种,你报告的将是噪声,而不是实际故障。每个中间结果都只用于决定下一次调用;只有最终筛选后的结果才应保留在上下文中并进行总结。

用于日志搜索的 ES|QL 模式

仅使用 ES|QL(POST /_query);不要使用 Query DSL。始终在一次请求中返回:时间序列直方图、总数、少量日志样本和消息分类(常见模式和罕见模式)。直方图是主要信号,可显示何时出现峰值或下降,并指导下一步筛选。使用 FORK 在单个查询中计算趋势、总数、样本和分类。

**FORK 输出解读:**响应包含多个结果集,由 _fork 列(或等效内容)标识。映射如下:fork1 = 趋势(每个时间桶的数量),fork2 = 总数(单行),fork3 = 日志样本,fork4 = 常见消息模式(按数量排序的前 20 种,来自最多 10k 条日志),fork5 = 罕见消息模式(按数量排序的后 20 种,来自最多 10k 条日志)。使用 fork1 找出应缩小时间范围的时段;使用 fork2 查看剩余噪声的数量;使用 fork3 决定下一步添加哪些 NOT;使用 fork4 和 fork5 查看剩余不同日志模式的数量,并选择下一步要添加的排除条件——持续迭代,直到剩余日志模式少于 20 种。

KQL 指南
  • 当目标文本按预期进行分词时,优先使用短语查询以获得更高的精确度(例如: message: "GET /health"、service.name: "advertService")。
  • 如果目标不会被分词为单个词项,请使用通配符(例如:message: *Returning*、 message: *WARNING*)。不要在引号括起来的短语中使用通配符。
  • 使用明确指定字段的 KQL:service.name: "payment-api"、message: "GET /health"、 NOT kubernetes.namespace: "kube-system"、error.message: * AND NOT message: "Known benign warning"。
  • 按 log.level 筛选(例如 log.level: error)可能有用,但通常不可靠:许多日志缺少或包含错误的级别元数据(例如,所有日志的级别都是 "info",或者级别只出现在消息文本中)。排查故障时,优先按消息内容或 error.message 缩小范围;将 log.level 视为线索,而非可靠的筛选条件。
  • 随机搜索 "error" 等词语的全文也通常不可靠:它会匹配无害的提及(例如 "no error"、"error code 0",以及提及该词的堆栈跟踪)。优先按服务或实体限定范围,并通过对实际消息模式逐步添加 NOT 排除项来迭代,而不要依赖单个关键词。
使用直方图、样本和分类进行基本日志搜索

加入消息分类,以便统计不同的日志模式并逐步迭代,直到剩余模式少于 20 个。使用五路 FORK:趋势、总量、样本、常见模式、罕见模式。

POST /_query
{
  "query": "FROM logs-* METADATA _id, _index | WHERE @timestamp >= TO_DATETIME(\"2025-03-06T10:00:00.000Z\") AND @timestamp <= TO_DATETIME(\"2025-03-06T11:00:00.000Z\") | FORK (STATS count = COUNT(*) BY bucket = BUCKET(@timestamp, 1m) | SORT bucket) (STATS total = COUNT(*)) (SORT @timestamp DESC | LIMIT 10 | KEEP _id, _index, message, error.message, service.name, container.name, host.name, kubernetes.container.name, kubernetes.node.name, kubernetes.namespace, kubernetes.pod.name) (LIMIT 10000 | STATS COUNT(*) BY CATEGORIZE(message) | SORT `COUNT(*)` DESC | LIMIT 20) (LIMIT 10000 | STATS COUNT(*) BY CATEGORIZE(message) | SORT `COUNT(*)` ASC | LIMIT 20)"
}
  • fork4(常见):按数量列出前 20 个消息模式,最多分析 10,000 条日志——用于添加 NOT 排除项,以过滤主要噪声。
  • fork5(罕见):按数量列出后 20 个消息模式——有助于在大海捞针时发现目标。
    统计 fork4、fork5 和整体分类中的不同模式数量,并继续迭代,直到剩余的日志模式少于 20 个。

根据需要调整索引模式(例如 logs-*、logs-*-*)、时间范围和分桶大小(例如 30s、5m、1h)。样本 LIMIT 保持较小(默认 10–20;上限为 500)。使用 KEEP,让样本分支只返回汇总字段,而不是完整文档。

添加 KQL 筛选条件

使用 KQL("...") 缩小结果范围。在 ES|QL 中,KQL 表达式是一个由双引号括起来的字符串。

**请求正文中的转义:**查询位于 JSON 中,因此 ES|QL 字符串中的每个双引号都必须转义。使用 \" 包裹 KQL 表达式。如果 KQL 表达式本身包含双引号(例如短语 message: "GET /health"),则在 JSON 中将这些引号转义为 \\\",这样 KQL 解析器才能收到字面引号字符。

POST /_query
{
  "query": "FROM logs-* METADATA _id, _index | WHERE @timestamp >= TO_DATETIME(\"2025-03-06T10:00:00.000Z\") AND @timestamp <= TO_DATETIME(\"2025-03-06T11:00:00.000Z\") | WHERE KQL(\"service.name: checkout AND log.level: error\") | FORK (STATS count = COUNT(*) BY bucket = BUCKET(@timestamp, 1m) | SORT bucket) (STATS total = COUNT(*)) (SORT @timestamp DESC | LIMIT 10 | KEEP _id, _index, message, error.message, service.name, host.name, kubernetes.pod.name) (LIMIT 10000 | STATS COUNT(*) BY CATEGORIZE(message) | SORT `COUNT(*)` DESC | LIMIT 20) (LIMIT 10000 | STATS COUNT(*) BY CATEGORIZE(message) | SORT `COUNT(*)` ASC | LIMIT 20)"
}
使用 NOT 排除噪声

通过排除已知噪声来逐步缩小范围。在请求正文中,使用 \"...\" 包裹 KQL 字符串,并将 KQL 表达式中的任何引号转义为 \\\":

"query": "... | WHERE KQL(\"NOT message: \\\"GET /health\\\" AND NOT kubernetes.namespace: \\\"kube-system\\\"\") | ..."
"query": "... | WHERE KQL(\"error.message: * AND NOT message: \\\"Known benign warning\\\"\") | ..."
按维度分组的直方图

按第二个维度(例如 log.level、service.name)拆分趋势,以查看哪些级别或实体导致了突增:

STATS count = COUNT(*) BY bucket = BUCKET(@timestamp, 1m), log.level

限制响应中的分组值数量,以避免数据量激增(例如,按数量列出前 N 个,其余归为 _other)。

示例

某个服务最近一小时的日志
POST /_query
{
  "query": "FROM logs-* METADATA _id, _index | WHERE @timestamp >= NOW() - 1 hour AND @timestamp <= NOW() | WHERE KQL(\"service.name: api-gateway\") | SORT @timestamp DESC | LIMIT 20"
}
错误日志趋势与样本
POST /_query
{
  "query": "FROM logs-* METADATA _id, _index | WHERE @timestamp >= NOW() - 2 hours AND @timestamp <= NOW() | WHERE KQL(\"log.level: error\") | FORK (STATS count = COUNT(*) BY bucket = BUCKET(@timestamp, 5m) | SORT bucket) (STATS total = COUNT(*)) (SORT @timestamp DESC | LIMIT 15)"
}
迭代漏斗:不断添加 NOT,直到找到有价值的信息

不要只排除一次。每一轮都针对当前最主要的噪声添加更多 NOT,然后重新运行。

第 1 轮: KQL("service.name: advertService") → 例如 55k 条日志;样本显示“Returning N ads”、“WARNING: request...”和“received ad request”。

第 2 轮: 排除最大的噪声:
KQL("service.name: advertService AND NOT message: *Returning* AND NOT message: *WARNING*") → 重新运行,并查看新的总数 和样本。

第 3 轮: 排除下一类噪声(例如请求/缓存相关消息):
KQL("service.name: advertService AND NOT message: *Returning* AND NOT message: *WARNING* AND NOT message: *received ad request* AND NOT message: *Adding* AND NOT message: *Cache miss*") → 重新运行。

第 4 轮及以后: 针对样本中仍占主导的内容继续添加 NOT(使用 fork4/fork5 分类查看 模式)。持续操作,直到剩余的日志模式少于 20 个;此时剩下的就是需要报告的有效信息(例如“获取广告时出错”、 编码问题)。

转义:在 JSON 中用 \"...\" 包裹 KQL 字符串;对于 KQL 中的带引号短语,使用 \\\"。

指南

  • 漏斗:迭代使用 NOT。 不要只运行一次宽泛查询就报告发现。针对主要噪声添加 NOT 子句,使用完整筛选条件 (保留之前的所有 NOT)重新运行,并重复操作,直到剩余的日志模式少于 20 个(使用 分类 fork4/fork5 计数)。过早停止会得到噪声,而非有效信息。
  • 先看直方图: 使用趋势(fork1)查看尖峰或下降发生的时间;如有需要,在添加更多 NOT 之前缩小尖峰附近的时间范围。
  • 尽量减少上下文: 样本分支中只保留摘要字段;默认 LIMIT 为 10–20,上限为 500。每个漏斗步骤都用于决定下一次调用;只有最终缩小范围后的结果才用于上下文和摘要。
  • 请求正文转义: query 的值是 JSON。转义 ES|QL 字符串中的双引号:KQL 封装层使用 \",KQL 表达式内部的引号(例如短语值)使用 \\\"。
  • 以编程方式构建查询时,start 和 end 使用 Elasticsearch 日期数学(例如 now-1h、now-15m)。
  • 根据时间范围选择分桶大小:目标是大约 20–50 个分桶(例如,1h 时间窗口使用 1m 或 2m)。
  • 优先使用 ECS 字段名。在 Observability 索引模板中,OTel 字段已别名为 ECS;资源元数据字段的回退方案(容器、主机、集群、命名空间、pod、工作负载)请参阅 references/log-search-reference.md。
  • log.level: 按此字段筛选或分组有时可行,但当日志级别缺失或设置错误时,结果往往不可靠; 查找故障时优先使用消息内容或 error.message。
  • 关键词搜索: 只搜索“error”或“fail”等词通常有缺陷(例如“no error”、“error code 0”);优先限定实体范围,并根据实际消息模式通过 NOT 逐步筛选。

参考资料

评论

支持 Markdown