我印象很深有一次线上凌晨报警,开发让我去Elasticsearch里查一条报错日志,我打开Kibana愣了半天——界面里全是柱状图和折线图,就是找不到日志在哪儿。后来搞清楚才发现,Kibana查日志这事,90%的时间其实都花在Discover界面和查询语法上,可视化面板只是锦上添花。如果你手上正好有一套ELK或者EFK,日常排查问题却总觉得Kibana用不顺手,这篇就是写给你看的。
本文会从日志链路的基本认知讲起,再到KQL查询语法、字段类型、时间范围、TraceId串联请求、常见报错和排查技巧,最后分享一些我在实际工单排查中踩过的坑。不搞网上的文档搬运,只写我在真实环境里用Kibana看日志总结出来的那套操作习惯,帮你缩短那种“日志明明在,就是搜不到”的绝望时间。
1. 先搞明白Kibana到底在查什么:日志链路全景
1.1 一条日志从产生到出现在Kibana,中间发生了什么
很多新人会把Kibana当成一个“日志查看器”,觉得它跟vi打开一个log文件差不多。这个理解其实偏差很大。你按下搜索按钮的那个瞬间,Kibana并不是在某个服务器上的文本文件里用grep找关键字,而是在向Elasticsearch发起一次分布式查询,由ES集群的多个节点协同返回结果。
一条日志走到你眼前通常要经过这样一条链路:应用代码里打的日志 -> Logback/Log4j2等日志框架写到本地文件 -> Filebeat/Logstash/Fluentd等采集器推送到Elasticsearch -> ES按索引和分片存储 -> Kibana从对应索引里查询展示。
这里有三件事对你“查日志”的行为影响最大。
第一是采集器做了什么加工。Filebeat默认会按行读取文件,每行作为一条独立的日志记录。如果你的应用日志里有异常堆栈,也就是那种一个异常跨了十几行的情况,必须配置multiline多行合并,否则你在Kibana里会看到十几条独立的记录,每条只有堆栈中的一行,排查问题的时候想死的心都有。
第二是索引的命名规则。常见约定是按天分索引,比如
springboot-app-2025.01.15
这样每天一个索引,定期清理。你在Kibana里选的索引模式必须和实际索引命名匹配,否则什么都搜不到。
第三是时间字段的处理。Elasticsearch默认存储的是UTC时间,Kibana展示的时候再转成你浏览器的本地时区。如果采集端、应用端、ES服务器三边时区都不一致,你搜的时候就会觉得“日志时间怎么差了8小时”。
1.2 Kibana里最能打的功能:Discover页面
Kibana左侧导航栏里有一个Discovery功能,英文版叫Discover。这才是日常查日志的主战场,不是Dashboard,不是Visualize,更不是Canvas。
Discover界面核心就几个部分:顶部是时间选择器,中间是搜索输入框,左侧是索引模式下拉框和字段列表,右侧是命中日志的列表。你可以在搜索栏里输入查询语句,也可以通过左侧字段列表点击筛选,两者可以组合使用。
时间选择器是我第一个强调的点。Kibana默认只查最近15分钟的数据,这个默认值是很多人“搜不到日志”的第一原因。你半夜排查问题,应用日志是下午3点产生的,不把时间范围调到绝对时间或者今天全天,结果肯定是空的。
搜索输入框要配合索引模式使用。索引模式决定了你查的是哪些索引,比如你选择了
springboot-*
,那么所有以
springboot-
开头的索引都会被搜索到。如果你之前不知道索引命名,也可以在Stack Management -> Index Patterns里查看已有的索引模式。
1.3 索引模式(Index Pattern)为什么重要
索引模式说白了就是一组索引的统配符匹配规则。它本身不存储日志,只定义了你“能看哪些索引的数据”。比如日志按照应用名加日期分索引,uac-service-2025.01.15、uac-service-2025.01.16这样,那索引模式就可以定义为
uac-service-*
。
创建索引模式的时候有个关键选项:
Time field
,也就是哪个字段作为时间字段。通常选
@timestamp
。如果你选的索引里没有
@timestamp
,建立一个自定义时间字段也是可以的,但是要注意那个字段必须是一个合法的date类型。
我自己踩过这样一个坑:有个系统用的是Spring Boot框架,日志文件在本地日期正常,但是进入ELK之后时间字段解析成了UTC,我在Kibana里北京时间下午3点去查,一直提示最近15分钟内没有数据,切到UTC时间才发现日志时间是早上7点。后来统一在Filebeat配置里做时间处理,这个问题才解决。
2. 搜索语法实战:KQL和Lucene的细节差异
2.1 KQL查询语法速成,日常排查够用了
Kibana的搜索栏默认是KQL模式,全称Kibana Query Language。这套语法非常贴近自然语言,比Lucene语法简单不少。常用的几个核心写法如下。
最简单的全文搜索,直接输入关键词,比如
error
,就会在所有字段里查。注意KQL里全文搜索的规则和ES的query_string不完全一样,它是默认在所有启用了搜索的字段里匹配。
字段精确匹配的写法是
字段名:值
,比如
level:ERROR
。如果值是一个短语,也就是中间有空格或者特殊字符,需要用双引号包起来,比如
message:"NullPointerException"
。
逻辑组合用
and
、
or
、
not
,大写小写都行。比如
level:ERROR and service:user-service
,或者
level:ERROR and not message:"Expected exception"
。
范围查询在KQL里也可以写。数值和时间字段都支持,比如
statusCode >= 500
,或者
@timestamp >= now-24h
。日期比较在排查超时问题时很常用。
通配符在KQL里也有,
*
匹配任意字符串,
?
匹配单个字符。比如
message:NullPointer*
。但是要提醒你,字段值开头的通配符会对性能有较大影响,逼不得已别这么用。
2.2 什么时候要用Lucene语法
如果你在Kibana搜索栏的右侧切换语言模式到Lucene,或者在某些版本里叫“旧版查询语法”,那查询写法会有些不同。Lucene语法里,全文搜索直接用
error
,字段查询依然用
message:error
,等价于KQL的
message:error
。
Lucene语法最大的特点是支持模糊搜索和正则,比如
message:/fail(ed|ure)?/
这样的正则写法。KQL是不支持正则的。那么什么时候需要切Lucene呢?我的经验是,当你需要复杂正则匹配,或者是在某些Dashboard里的旧配置,才用得着。日常排查,KQL已经覆盖了90%的场景。
有个比较坑的地方:如果你拿到一个Kibana链接分享给同事,链接里带的是查询参数,同事打开后语言模式可能和你不一样。分享查日志链接的时候,一定要确认对方的Kibana里也是相同的语言模式,不然打开后语法解析失败,出现一片红色波浪线。
2.3 组合过滤条件的几个习惯
搜索日志的时候,不要想着一条大查询搞定所有条件。那是写SQL的习惯,搬到Kibana里效率很差。我的习惯是先用搜索栏查核心关键内容,然后再用左侧字段列表的过滤按钮来缩小范围。
EQL确实是Elasticsearch的安全检测语言,这里容易混淆,日常日志排查中我们常用的还是KQL和Lucene,不要把两者混在一起。实际排查时,搜索栏负责语义过滤,左侧字段点击
+
号可以添加筛选条件,点击
-
号则是排除。这些筛选条件会和搜索栏的条件共同生效。
举个例子,我要查用户服务今天出现的超时异常,我会这样做:索引模式选择
user-service-*
,时间范围选
Today
,搜索栏输入
message:"timeout" or message:"TimeoutException"
,然后左侧字段列表里点开
service.name
,加上
user-service
这个筛选条件,再点开
level
,加上
ERROR
。这样一来,条件层级清晰,每一步都能单独去掉验证。
加完一堆积条件后,Kibana搜索栏上方会显示一串A标签,这些是当前生效的过滤条件。你可以随时点x关掉其中一个,直接验证是该条件过滤掉了数据,还是原本就没有日志。这种方式比在搜索栏里涂改一长串语句要高效得多。
3. 字段类型和索引映射:查日志最容易翻车的地方
3.1 字段类型这事,为什么这么重要
很多人正面卡在字符串类型上。你搜
statusCode:500
,结果一条都匹配不出来,原因就是
statusCode
这个字段在ES里被映射成了
keyword
类型,存储的值是字符串
"500"
而不是整数
500
。搜索的时候写
statusCode:500
和
statusCode:"500"
都有可能返回不同结果。
ES的字段类型决定了一个字段能不能被分词、能不能做聚合、能不能范围查询。比如
keyword
类型支持精确匹配和Term查询,但是不支持全文分词。
text
类型支持分词搜索,但不支持聚合和排序,而且有时会用
message.keyword
这样的子字段来做精确匹配。
查日志的时候,如果发现某个字段搜不出来,第一反应应该是去看字段的映射类型。操作方法是在Discover页面左侧字段列表里找到那个字段,鼠标悬停或者点击,Kibana会显示字段类型。如果是
text
类型,全文搜索可能带分词,用短语查询更准确。如果是
keyword
类型,就要注意输入值是否精确。
还有一个高频的问题:同一个日志字段,在不同索引里类型不一样,搜索的时候就报错。原因多半是索引模板没有统一,或者Filebeat的配置在某个版本之后把字段类型改了。
3.2 索引模板与动态映射
日志系统里,索引通常是动态创建的,也就是每天自动生成新索引。ES会根据索引模板(Index Template)来定义新索引的settings和mappings。如果你的团队没有好好管理索引模板,那么新老索引之间的字段类型就可能不一致。
举个实际例子:某天开发在代码里加了个字段
requestId
,一开始Filebeat自动映射成了
text
类型,后来有人改了模板把
requestId
改成
keyword
类型,那么新索引没问题,历史索引还是
text
,你在Kibana里按
requestId.keyword
查历史数据和查今天数据,结果完全不一样。
这个问题的排查方法也不难,在Elasticsearch的Dev Tools里执行:
GET /user-service-2025.01.15/_mapping
GET /user-service-2025.01.16/_mapping
对比两个索引里
requestId
字段的类型即可。如果发现不一致,要么重建老索引重新导入数据,要么在模板里改好类型后等新索引创建,老数据基本只能放弃新类型的查询。
3.3 时区、时间字段和那8小时偏差
做日志系统的人,几乎都被时区折磨过。默认情况下,Elasticsearch内部存储和返回的时间都是UTC格式,而Kibana会把它转为用户浏览器的本地时区。
你在中国,时间是UTC+8。如果你的应用打的日志时间戳是北京时间,Filebeat采集后直接写入,没有转换为UTC,ES会认为那是UTC时间,你的日志时间就会比实际慢8小时。反过来,如果你的应用打的是UTC时间,Kibana自动转成北京时间显示,那是没问题的。
怎么办?建议在Filebeat或者Logstash配置里统一做时区转换,让Logstash解析时指定
timezone => "Asia/Shanghai"
,最终存入ES的时间严格是UTC。这样在Kibana里查看时,按浏览器时区转回北京时间显示,时间是对的。
还有一种情况,日志里有两个时间字段,一个是
@timestamp
,一个是业务字段
log_time
。如果
@timestamp
被采集时间覆盖,你就应该用
log_time
作为索引模式的时间字段,同时把时区处理好。
我个人的习惯是:在Kibana的Advanced Settings里,把
dateFormat:tz
设置为浏览器时区之外的固定值,比如
Asia/Shanghai
。这样无论谁打开Kibana,看到的都是北京时间,避免同事在美国出差时查日志,两个人看到的时间不一样,沟通成本极高。
4. 实操案例:用Kibana定位一个真实报错的全过程
4.1 从一条NPE日志到定位代码的位置
我分享一下最近一次用Kibana排查线上问题的完整过程,你跟着走一遍就知道实际操作大概是怎样的。
那天业务方反馈用户下单接口偶发失败,我需要在日志里找到具体的异常信息。我先在索引模式里选了下单服务的索引
order-service-*
,时间范围选了最近1小时,然后输入搜索词
message:"NullPointerException"
。结果显示确实有NPE异常,但数量不多,不到10条。
异常堆栈在Discover页面里默认只显示前几行,这不够定位问题。我点击某一条日志展开,看到完整堆栈。堆栈里第一行是
java.lang.NullPointerException: null
,第二行是
at com.xxx.order.service.OrderServiceImpl.calculate(OrderServiceImpl.java:218)
,这说明NPE发生在订单计算的218行。
到这里还不够,我想知道这个请求是从哪里进入的。点开这条日志的详细信息,看到里面有
requestId
、
userId
、
orderId
等字段。拿
orderId
去搜关联的SQL日志、Redis日志和其他微服务日志,就能完整还原这次请求的调用链。
4.2 用TraceId串起一个完整请求
很多公司会在日志里加
traceId
,也就是一次请求的全局唯一标识。这个字段配合Kibana查日志非常方便,搜到一个异常后直接点traceId的值,再切到其他服务的索引搜索,就能把一个请求在不同服务间的执行路径都串起来。
这里有一个很关键的实操细节:日志框架打出来的traceId,有时候是跟在消息文本里的,没有单独的字段。比如
2025-01-15 10:00:00.123 [http-nio-8080-exec-3] INFO [traceId:abc123] xxx method started
,如果采集端没有解析出
traceId
这个字段,你在Kibana里是无法直接用
traceId:abc123
来搜的。
这个时候有两种处理方式。一是搜索全文,直接用
abc123
去搜,命中所有包含这个traceId的日志。这种方式最直接但要注意别带特殊字符,否则全文搜索可能解析失败。二是改造日志输出,在logback或者log4j2的pattern里把traceId用
@
或者
|
这样的分隔符标识出来,配合Filebeat的grok规则提取字段,这样以后就能用字段查询了。
更推荐的方案是接入开源的Trace系统,比如SkyWalking、Zipkin、Jaeger,用它们生成traceId和spanId,配合日志采集系统自动关联。如果没有条件上整套Trace系统,退而求其次也要保证日志pattern里包含traceId并做好字段提取。
4.3 聚合分析:统计错误出现的频率
有时候你需要的不只是一条日志的内容,而是某个异常出现多少次、在哪个接口、哪个节点最频繁。Kibana的Discover页面里有一个“共展示 xxx 条”的信息,但是要真正做统计,还是得靠Lens或者旧版Visualize来做聚合分析。
我举个简单的例子:我要统计过去24小时内
order-service-*
索引里,
level=ERROR
按错误类型分组出现的次数。在Lens里拖拽字段,选择
level
作为垂直轴,选择
message.keyword
作为水平轴,就能看到关键词Top N。这个操作比写ES聚合JSON要快很多。
如果你是老派操作,喜欢直接在Dev Tools里看ES返回结果,也可以写一个简单的聚合查询:
GET /order-service-*/_search
{
"size": 0,
"query": {
"range": {
"@timestamp": {
"gte": "now-24h"
}
}
},
"aggs": {
"error_keyword": {
"terms": {
"field": "message.keyword",
"size": 20
}
}
}
}
这条查询会返回出现次数最多的20条错误消息,用来快速判断当前主要故障点在哪。
用这个方式要小心
fielddata
的问题:
text
字段默认是不允许做聚合的,聚合前必须使用
keyword
子字段,比如
message.keyword
。如果没有映射对应的keyword子字段,那就需要预先在索引模板里定义好。
5. 常见问题与排查技巧实录
5.1 搜索不到日志,先排查这五个地方
搜不到日志是最高频的问题,而且往往不是ES本身出问题,而是查询条件或者数据链路出问题。我总结了一套排查顺序,建议你按这个顺序来检查。
第一,确认时间范围。Kibana默认15分钟,你要查的数据是不是在这个范围内?最简单的方式是切换到绝对时间范围,输入具体起止时间。
第二,确认索引模式。你选的索引模式是不是覆盖了目标索引?如果索引是按天建立的,先确认当天日期的索引存在,比如
curl -X GET "localhost:9200/_cat/indices?v"
看一眼。
第三,确认KQL语法是否解析成功。搜索栏里如果出现红色波浪线,说明语法有问题,Kibana不会执行这个查询。判定方法是把搜索内容删到只剩一个关键词,加上去,反复排查是哪个条件引发的语法错误。
第四,确认字段名。有时候开发日志里写的是
requestId
,采集端解析后成了
Request_Id
,大小写和下划线一字之差,差之千里。在左侧字段列表里找到准确字段名再搜。
第五,确认是否存在权限问题。多租户环境里,Kibana的空间和用户角色会限制索引访问权限。你明明知道某个索引有数据,搜不到,可能是当前空间压根没有这个索引的权限。
5.2 日志时间总是不对:时区和大坑
日志时间不对,我已经在3.3节里说过,这里再补充一个排查技巧。你把日志的原始JSON打开,看
@timestamp
字段和
log_time
字段的原始值。如果
@timestamp
和实际时间差8小时,那就是UTC时区转换没做好。如果
log_time
本身就不对,那就要看应用打的日志时间是否正确。
还有个冷门问题:应用容器里跑的时区是UTC,但宿主机是北京时间,开发在日志里用
new Date()
打印时间,也会有8小时偏差。这种情况要从应用本身的时区设置入手,调整JVM参数
-Duser.timezone=Asia/Shanghai
,或者容器环境变量
TZ=Asia/Shanghai
。
5.3 索引被清理导致查不到历史数据
ELK系统因为存储成本问题,通常只保留最近30天或者90天的数据。这个保留策略在Elasticsearch 7.x以后通常用ILM索引生命周期管理来实现。如果你查历史日志发现索引不存在,先去ILM策略里看当前索引的保留周期。
我见过一个情况:项目组为了省存储,把保留期设置成了7天,结果业务方问“上个月的日志查不到”才发现,这时候只能告知数据已清理,没有回退余地。所以建议在搭建ELK的时候就明确保留期限,和业务方对齐需求,避免后面扯皮。
补充一个和热词相关的细节,如果你要用MySQL的binlog日志恢复数据,那和Kibana看日志是两条路线,binlog通常不会采集到ELK里。但如果合规要求需要留存“数据库操作审计日志”180天,建议直接从应用层或者审计插件采集,不要指望binlog的原始文件做关联分析,这会导致ES存储暴涨。
5.4 Kibana查询慢的优化方向
如果你发现搜日志越来越慢,大概率不是Kibana的问题,而是ES查询本身太重。我梳理几个常见的优化方向。
查询条件收紧:时间范围越大,扫描的分片越多。查一个月的日志永远比查一天慢得多,尽量精确时间范围,别一上来就选“Last 1 year”。
避免前方通配符:
*error
这种查询会让ES扫描大量term字典,非常慢。
message.keyword:订单查询*
这种后方通配符会好很多。
过滤性强的字段优先:查询时先通过
level
、
service.name
、
environment
索引字段缩得越小越好,最后才用
message
做全文搜索。
增加副本分片和调整刷新间隔:ES索引的
refresh_interval
默认1秒,日志场景通常可以调整到30秒甚至1分钟,减少IO开销。副本分片可以提升查询吞吐,但会增加存储成本。
冷热数据分离:日志查询频率通常是新数据高、旧数据低。把索引按照时间分成hot和warm阶段,hot阶段用SSD,warm阶段用普通磁盘,ILM自动迁移,性价比很高。
5.5 那些不起眼但能救命的小技巧
我在日常工作中积累了一些不是文档里写得很明白的小技巧,这里一并分享给做日志排查的人。
Kibana的搜索栏支持把多个查询快速保存为“已保存的查询(Saved Query)”。遇到重复排查场景,比如每次都是查某个接口的超时,直接把当前查询条件和时间范围保存下来,下次一键加载,省很多事。
Discover页面右上角的“Share”按钮可以生成短链接,方便把当前搜索条件发给同事。链接里带上了时间范围和过滤条件,对方打开就能看到和你一样的结果,不用重复描述。
有时候日志里的字段值特别长,Kibana默认只显示前50个字符。在Discover里可以调整
table density
和字段显示的宽度,或者直接点击“Toggle column in table”把需要看的字段固定到表格列上。这个操作对长消息拼接错误堆栈帮助特别大。
Kibana的“导出CSV”功能适合处理少于1万条的日志数据。超过1万条导出会报错,需要调整
xpack.reporting.csv.maxSizeBytes
参数。但说实话,超过1万条你更该做的不是导出CSV,而是用聚合或者查询去缩小范围。
写日志本身这件事也很重要。代码里打日志要带上下文信息,比如用户ID、订单ID、商品ID这些业务标识,否则后来查日志的时候完全无从下手。日志不要打无意义的废话,比如
method called
、
method end
这种,对排查问题毫无帮助,还徒增存储压力。一次请求最好能有一个唯一的requestId贯穿线程上下文,这是现代日志系统的基本要求。
我在实际运维ELK的过程中最深的一个体会是:Kibana查日志的快慢和顺不顺手,最大的决定因素不是Kibana本身的版本和功能,而是日志在源头有没有打到位、有没有格式化和字段化。你在Kibana里的每一个高效查询,背后都靠的是应用日志规范的支撑。日常开发中少些“临时加一条日志看看”的随意,多些对日志字段、traceId、时间戳和业务上下文的长远规划,事后排查起来会轻松非常多。
如果你现在正准备在新项目里搭日志体系,我建议把Kibana的索引模式设计和日志格式约定提前纳入技术方案评审,而不是等日志量大了再回头补。一个好的日志体系,不是Kibana用得有多花哨,而是哪怕团队里的新人也能在3分钟内搜到一条关键报错日志并定位问题。
1316



被折叠的 条评论
为什么被折叠?



