介绍炬鲸 OBSERVE 的 SQL 风格日志检索:字段索引、聚合函数、与 Trace 的关联查询,以及它与传统关键词搜索的差异、适用场景和成本边界。
全文关键词搜索(grep 风格)在日志量小时够用,但到了每天几百 GB、上万个容器轮转的规模,问题就暴露出来:一个关键词命中的结果动辄几万条,没法按字段过滤,更没法做聚合。运维真正想要的是"过去 1 小时 error 级别日志按服务分组计数",或者"某 traceId 关联的所有日志按时间排序"——这是查询,不是搜索。
炬鲸 OBSERVE 的检索层内置了一个类 SQL 的查询引擎。日志在写入时就被解析成字段:结构化日志自动按 key 拆分,非结构化日志按你配置的正则模板提取。于是检索不再是对原始文本的逐行匹配,而是对字段做索引查找。
查询支持 SELECT / WHERE / GROUP BY / ORDER BY / LIMIT,聚合函数包括 count、sum、avg、percentile、histogram。几个高频用法:
-- 按服务统计最近 1 小时 error 日志
SELECT service, count(*) AS cnt
FROM logs
WHERE level = 'error' AND time > now() - 1h
GROUP BY service ORDER BY cnt DESC
-- 按 1 分钟时间桶看某接口的 P99 延迟
SELECT histogram(time, 1m) AS bucket, percentile(latency, 99)
FROM logs WHERE api = '/order/create'
GROUP BY bucket
WHERE 条件会下推成索引查询,只扫描命中的时间分片,通常比同窗口的全量 grep 快一到两个数量级。字段值默认建倒排索引,数值字段额外建列式存储,聚合计算直接走列式扫描,而不是逐行遍历。
日志带上 traceId / spanId 之后,检索引擎支持跨表关联。查一个慢请求时,先从 Trace 面板拿到 traceId,再用一条语句把该链路里所有 span 的日志拉出来:
SELECT time, service, span_id, message
FROM logs WHERE trace_id = 'e8f2...' ORDER BY time
传统日志系统要做多次搜索、手工拼接才能做到这一步,现在一条语句搞定,故障定位从"翻十几页日志"缩短到"一条查询出结果"。同样的思路也能把日志和发布事件、和具体用户关联起来。
SQL 检索不是没有代价。全字段索引会放大存储与写入成本,所以默认只对常用字段(level、service、trace_id、时间戳)建索引,其余字段按需在 schema 里开启。模糊匹配(LIKE '%xxx%')无论有没有索引都会退化成扫描,大范围模糊查询建议改用告警规则里的定时聚合,周期性跑并缓存结果。聚合结果默认有 10000 行上限,超限时用 WHERE 收窄范围,而不是翻页。
一句话总结:把它当数据库用,但要知道哪些查询"贵"。贵的查询留给定时任务,交互式查询保持窄范围、短时间窗,这样检索层在高负载下也能保持稳定。