ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Elastic Stack的日志平台UX设计:从检索到排障闭环的实践

基于Elastic Stack的日志平台UX设计:从检索到排障闭环的实践 不扯产品背景了直接说正事。我们内部有一套基于 Elastic Stack 打造的日志流处理平台代号就叫 Elastic Streams。名字听着挺大其实是自嘲日志像洪水一样冲进来Elastic 负责接住这堆洪水我负责的是洪水面前那扇看水位的窗户——Log Processing 的 UX 设计。做了一段时间踩了不少坑也总结出一些经验今天拿出来聊聊希望对正在做日志平台、可观测性系统、或者任何“数据管道 用户界面”项目的朋友有参考价值。先说背景。日志处理的典型场景是日志量每天几个 TB分散在几十个服务节点格式五花八门有的 JSON 有的拼字符串排障时需要在几分钟内找到一条关键报错。后端能力我们可以靠 Elastic Stack 解决但用户面对的不只是查询 API而是一整套操作界面。如果交互设计得烂再快的查询也白搭——用户不知道查什么、不知道怎么查、查到了看不懂整个链路就断了。所以这个项目最核心的命题不是“把查询性能优化到多快”而是“把从发现问题到定位根因的路径缩到最短”。下面的内容都是围绕这个命题展开的。适合看这篇东西的人有三类一类是做日志平台或可观测性产品的后端开发者一类是给数据产品做 UX 的设计师还有一类是天天被日志折磨的运维和 SRE看了至少能知道哪些功能是好工具的标配也能帮你提需求时把话说清楚。1. 项目概述与核心设计思路1.1 日志处理场景里的三个真实痛点我在设计这套系统之前先做了一轮用户调研。这里的“用户”很直接就是公司内部的研发和运维。日志平台是典型的“不爽但不得不用”的工具大家都在一个页面上开票、报障、追问题。调研下来的结果挺有代表性痛点集中在三块。第一块是数据太杂。不同团队的应用日志格式完全不统一有 JSON、有纯文本、有 keyvalue、有又像 JSON 又混了堆栈的多行文本。用户想搜一个报错先得搞清楚这条日志的字段长什么样得去翻文档、翻索引映射效率极低。第二块是时间问题。日志的核心维度是时间但在实际使用中因为时区、毫秒级精度、日志生成时间和写入时间的差异用户经常搜不到刚产生的日志以为是平台丢了数据其实是查询边界没卡对。第三块是链路断裂。一条请求从网关到服务 A 再到服务 B产生十几条日志用户想看完整链路要么靠 grep traceId要么靠肉眼对时间线根本没有“一条请求完整视图”的概念。这三个痛点直接决定了我们 UX 设计的方向数据标准化要做得隐蔽但彻底时间交互要贯穿全局链路关联要从幕后走到台前。1.2 为什么选 Elastic 这套流式栈方案选型是我们一开始就纠结过的问题。当时市面上也有 Loki、ClickHouse 这类方案可选但最终我们仍然把底座放在了 Elastic 上原因有三点而且这三点都和 UX 设计强相关。第一Elastic 的倒排索引决定了它的查询模型是“文本相关性 结构化过滤”的混合形态。这对日志用户是最好的支持因为他们既想全文搜“OutOfMemoryError”又想用 serviceorder-service 精确过滤。第二Elastic 的数据流和生命周期管理非常成熟索引模板、自动滚动、冷热分层都现成这意味着我们不需要在用户体验层处理“索引掉到哪个节点去了”这类底层问题可以把精力省下来打磨交互。第三Elastic 生态的 API 覆盖面全从聚合、查询到数据流推送都有标准接口做二次开发时不会卡壳。我特别想强调一个容易被忽略的点Elastic 的快照恢复和 mapping 演化能力在我们迭代索引方案时帮了大忙。日志这个东西字段是会变的今天加一个 deployment_id明天改一个 user_id 的类型。我们用索引模板管理字段版本用 reindex 做结构升级这些底层操作全都封装在后台前端用户完全感知不到——感知不到就是 UX 最好的状态。1.3 UX 设计目标把“从异常到根因”的路径缩到最短定了技术底座接下来就是设计目标。我们内部有句口头禅好的日志平台不是让用户看得更多而是让用户少看无用的东西。这句话落到设计上具体拆成三个可度量的目标。第一个目标是“三个动作内触达关键日志”。用户进入页面选择时间段输入关键词就必须能见到第一批结果。任何多余的步骤——比如先进索引列表、先配置过滤条件、先理解 DSL 语法——都是原罪。第二个目标是“上下文不丢失”。用户从一条报错跳到另一条报错的时候时间范围、过滤条件、字段展示不能重置哪怕用户切了页面回来也要保留他的检索现场。第三个目标是“状态永远可见”。系统正在做什么、查了多少数据、有没有解析失败、是不是限流了这些状态必须直面用户哪怕做不到实时也要给一个确定性的反馈。这三个目标听起来像产品口号但我后面所有模块的交互细节都是拿它们来当判断标准的。功能再好如果让用户觉得“不知身在何处”那就不合格。2. 核心功能拆解检索、流视图与排障闭环2.1 检索编排把 DSL 门槛藏到交互后面日志检索最大的敌人是 Elasticsearch DSL。虽然熟悉 ES 的工程师写 query 不费劲但日志平台的用户面远不止这些工程师很多后端开发只是偶尔来查一次日志。让所有人都学 DSL 不现实我们的做法是做一个两层检索界面。第一层是“自然语言框”。用户可以直接输入service:order-service AND status:500 AND NullPointerException系统会做轻量解析把冒号、引号、布尔操作符拆成结构化条件。这套语法刻意贴近 Kibana 的 KQL老用户零成本迁移新用户看到示例就会用。第二层是可视化的条件构建器。把字段、操作符、值拆成三个下拉框点一下“添加条件”就能叠加过滤生成的就是完整的 ES query DSL。这里有个细节很多人忽略检索界面要能展示“中间状态”。用户输到一半的条件比如只输了一个字段名系统应该立刻给出候选字段列表而不是等按了回车才校验。我们在这里用到了 ES 的字段映射接口在输入框失焦的瞬间就把可用字段和值枚举抓回来直接做交互提示。2.2 实时流查看器在高流量下保持可读日志场景有个很常见的需求重启了一个服务想看它启动过程中刷出来的日志像tail -f一样。Elastic Stack 侧可以用查询配合轮询来实现但在 UX 层面实时流带来的性能和可读性问题被我们反复打磨过。第一个问题是渲染性能。一秒内可能有上千条日志进来如果每条日志都作为 DOM 节点直接插入页面浏览器马上卡死。我们最终做了“虚拟滚动 窗口截断”的方案屏幕上只渲染可视区域的几十行日志流进来先进入一个内存队列尾部数据自动丢弃头部数据进入视图。用户暂停滚动时可以自由翻页恢复时再回到最新位置。这个交互有点像数据库的 cursor而不是无脑的 append。第二个问题是流数据的粘性。日志刷得太快时人眼根本看不过来所以我们对流视图做了两个辅助功能关键词高亮和自动暂停。高亮词由用户提前设置命中高亮词的行会在视图中标色并自动暂停滚动把用户从“信息洪流”里捞出来。经验是暂停比高亮更重要因为真人根本没法在滚动中看到高亮得先让他停下来才能注意到异常。2.3 从日志到根因关联、聚类与事件闭环如果日志平台只做“查日志”那它只是一个更快的 grep。我们真正投入精力设计的是日志之外的关联层。关联层的第一块是 traceId 链路。我们约定所有业务日志都必须带上 traceId 字段平台在检测到用户点击一条日志的 traceId 时自动发起一次“该 traceId 全链路查询”把所有节点、所有调用、所有异常摊在一个时间轴上。用户不用再手动搜索拼接链路这省下的不是几十秒而是排障时最要命的心智成本。关联层的第二块是异常聚类。原始日志里同一类错误可能每天出现几万次逐条看没有意义。我们设计了基于相似度聚类的“异常指纹”功能把堆栈首行、异常类型、关键字段组合成一个指纹相同指纹的日志归为一条展示首次出现时间、最近出现时间、总次数和受影响服务数。这个功能上线后SRE 排查故障的时间从小时级降到了分钟级。关联层的第三块是事件闭环。日志视图里可以直接创建告警规则也可以把一条日志直接转成工单或关联到已有事件。用户不用切去别的系统确认、升级、备注都在一起。这是整个 UX 设计的终极目标日志不是一个终点而是排障链路的一个环节所有操作逻辑都朝着闭环走。3. 状态反馈与信息架构设计3.1 让数据链路可看见构建全流程状态面板日志平台有一个很容易被吐槽但又很难做好的问题用户明明打了日志但页面上查不到第一反应就是“平台丢数据了”。我们花了很大力气做的就是把这个“黑盒”打开让我们内部的数据管道全程透明化。Elastic Streams 的数据链路可以拆成四段采集端Filebeat→ 解析端Logstash / 预处理管道→ 索引端Elasticsearch→ 查询端Kibana / 前端。我们在这四段都埋了指标页面顶部有一条横向的状态栏采集速率、解析成功率、索引速率、查询响应时间四个指标一个 Link 小方块。哪个环节出问题哪个方块变黄色或红色旁边附上最近的错误摘要。这个状态面板看起来只是四个数字但设计时有个关键决策必须区分“业务日志正常但用户搜不到”和“数据链路真的出问题”。比如用户搜不到日志但采集速率和索引速率都在涨那说明大概率是用户的查询条件写错了或者时间范围不对页面应该引导去检查条件而不是让用户怀疑平台。我们后来把这条规则做成了提示文案在空结果页动态展示客服压力小了不少。3.2 空状态、错误状态与查询解释的统一规范日志平台的交互里空状态其实是最常见的状态但很多产品根本没用心设计。我们统计过搜索一次报错大概率得到的是 0 条结果。如果这个页面只有一个冷冰冰的 “No results”用户就卡住了不知道下一步该干嘛。所以我们给空状态设计了三种分支。第一种是“链路有数据但查询条件太严”页面上直接给出当前查询翻译出来的 ES DSL并用黄色高亮标注可能是主要限制的条件比如时间范围太小比如用错了字段名。第二种是“当前时间范围数据还没到”这种情况多见于刚部署完采集器我们会展示采集端的延迟数据告诉用户“日志还在路上延迟约 x 秒”。第三种是“查询本身不合法”错误提示会精确到哪个字段错了、哪个语法不符合规范而不是给一个笼统的异常码。此外我们做了一个查询解释器侧边栏。用户点一下“解释查询”系统会把表格化的过滤条件翻译成一句人话“从近 15 分钟内筛选 serviceorder-service 且包含 NullPointerException 的日志总共命中 123 条”。这句话对用户非常关键能极大减少“搜不出结果就抓狂”的概率。4. 实操落地记录从数据模型到前端交互4.1 数据流与索引策略Data Stream ILM前面的设计听起来都是概念落到工程上第一步是确定数据模型。我们直接采用了 Elastic 的 Data Stream 方案不用传统索引来存日志。原因很简单日志有固定的时间属性按时间滚动是最合理的切分方式Data Stream 天然支持这种模式。具体配置上我用索引模板来定义字段映射。模板创建后所有写入这个 Data Stream 的文档都自动匹配映射无需手动建索引。下面是我们简化后的配置示例PUT _index_template/logs-service-template { index_patterns: [logs-service-*], data_stream: {}, template: { settings: { number_of_shards: 3, number_of_replicas: 1, index.lifecycle.name: logs-lifecycle }, mappings: { properties: { timestamp: { type: date }, message: { type: text, fields: { keyword: { type: keyword } } }, service: { type: keyword }, level: { type: keyword }, traceId: { type: keyword } } } } }生命周期策略是我们做“稳定状态”的另一半基础。日志这种东西不可能无限存但又不能统一删掉所以我们配置了 ILM热数据保留 1 天温数据 15 天冷数据存 30 天之后删除。这个策略全透明用户端的时间范围选择器里直接展示“可查询最近 30 天更早的日志已归档”。UX 上少了“怎么我7天前的日志没了”这类的抱怨。4.2 服务端流式查询与接口设计要点从前到后接口层是 UX 节奏感的来源。实时流不能用普通的 JSON 响应来推客户端如果定时轮询会有 3 秒到 5 秒的延迟而且短时间大量控制请求对 ES 集群是压力。最终我们选了 SSEServer-Sent Events方案理由很实际日志流是单向推送SSE 天然支持且断线重连、心跳机制都内置在浏览器 EventSource 里不需要像 WebSocket 一样自己维护状态机。接口设计也做了收敛全平台只暴露两个核心读接口。一个面向查询POST /api/logs/search接收过滤条件、时间范围、分页游标返回结果快照。另一个面向流式GET /api/logs/stream入参是同样的查询条件但服务器保持长连接持续往外推新日志。这两个接口对前端来说很容易理解对后端来说也利于加缓存和限流。SSE 推送的字段我们做了精简因为每条日志全量字段太多推送时只带核心字段时间、服务、级别、摘要、traceId。用户想看完整字段时再按日志 ID 拉详情。这样设计让实时流的带宽和前端渲染压力都降了一个量级实测在高流量场景下非常有效。4.3 前端实现SSE、虚拟滚动与性能取舍再细说前端实现。SSE 的接入很简单核心代码就十几行const es new EventSource(/api/logs/stream?query encodeURIComponent(query)); es.onmessage (event) { const log JSON.parse(event.data); buffer.push(log); // 入内存队列 maybeRenderLog(log); // 只渲染可视区域相关行 };难点在渲染层。日志行天然是超长文本和普通列表不一样。我最初用固定行高做虚拟列表很快就发现行被截断得没法看。后来改成“摘要模式 详情展开”默认每行只显示首行摘要用户点开后才展开完整堆栈。这样每条日志高度是确定的虚拟滚动的性能问题基本解决而展开详情的操作成本用一个快捷键补齐没有拖慢用户体验。另外我强烈建议日志平台的前端加一个“Web Worker 预处理”层。日志数据进来后关键词高亮、字符串截断、脱敏处理都在 Worker 线程里做不阻塞主线程的 DOM 更新。实测在 2000 条日志的批量渲染场景下开 Worker 后帧率从 22fps 提升到 58fps体感差别非常明显。5. 真实踩坑记录与常用排查速查5.1 日志明明写了却搜不到排查顺序这个算是日志平台最常见的问题没有之一。我自己在联调阶段就遇到过不下五次每次都把链路都查一遍才发现原因。后来我把排查顺序固定成一套速查表前端和后端同学照着走基本能解决九成的问题。第一先看链路状态面板的“采集速率”和“索引速率”。如果这俩在一路增长说明数据已经进来了问题大概率在查询条件。第二看时间。检查前端传入的时间范围是否和日志生成时间匹配特别注意时区。Elasticsearch 里的 date 字段底层存的是 UTC 毫秒数用户界面显示的是本地时间。如果查询参数直接把本地时间的字符串拼进 DSLES 会默认当 UTC 处理就出现“查询 8 点到 9 点的日志结果显示 16 点到 17 点的”这种鬼畜现象。我们的解法是前端传时间范围时统一传时间戳毫秒数并在请求头里携带时区由后端转成 ES 的time_zone参数。顺序排到最后才看权限。ES 的索引权限是按用户角色划分的很多用户搜不到日志其实是因为新加的索引 alias 没有同步给用户的 role。这类问题最隐蔽用查询接口的返回头信息能快速看到实际生效的集群权限。5.2 实时流卡顿与查询延迟的优化记录实时流上线后收到的第一波反馈就是“有点卡”。我们把问题复现后发现卡顿不是网络也不是 ES 慢而是浏览器渲染扛不住。当时没有做虚拟滚动的时候实时流页面每秒新增近千个 DOM 节点页面直接雪花。后来上了虚拟滚动加 Worker问题解决但这也告诉我们日志 UX 的瓶颈往往在前端而非后端。另一类问题是查询响应时间突然变长。排查发现是索引生命周期滚动后热节点负载不均衡某几个分片扛了 80% 的写入流量。解决方式是给 ILM 加了一个rollover条件让索引在“最大文档数 5000 万”或“最大大小 50GB”时触发热节点滚动数据分散到更多分片。修复后查询响应从均匀的 800ms 降到 200ms 左右。还有一个小经验是日志处理 UX 里的“快”不完全是响应速度也包括可感知的确定性。ES 默认 refresh interval 是 1 秒也就是说日志写入后 1 秒内才可查。这个延迟不能消除但 UI 上如果加一个“延迟说明”或者“最新索引时间”的小标签用户看到后就不会觉得数据丢了。我们把这个标签加在实时流顶部体验稳定了不少。5.3 排障经验速查表日常维护中积累了一套速查表贴出来供参考现象可能原因排查/修复动作搜不到日志链路面板正常查询条件太严或字段名错误打开查询解释器核对限制条件用_exists_检查字段实时流有延迟最新日志不出refresh interval 或存在大数据扫描查看最新索引时间标签必要时缩短非活跃索引的 refresh日志解析结果乱码Logstash pipeline 匹配顺序错误用 pipeline 的 dry-run 模式逐条测试对照 grok 调试器查询结果突然只剩半天数据生命周期策略误删或字段映射变更检查 ILM 状态查看索引模板是否有新版本覆盖旧 mapping前端流页面内存持续上涨内存队列未做长度限制给队列加上限并强制丢弃旧日志设置最大内存占用这些坑今天看都有迹可循但当时踩的时候每一个都花了大半天去排查。特别是字段映射变更那一次索引模板更新后老索引和新索引的 mapping 不一致查询时同一个字段在两个索引里类型不同ES 直接报错“field [xxx] was indexed without type info”。这个问题的规避方法是修改字段类型时永远用_reindex重建数据而不是直接改索引模板。最后再分享一个值得养成的习惯日志平台的 UX 设计一定要跟着真实排障流程走不要凭空想象。我设计实时流页面的第一版时想当然地给了大的时间窗口和复杂的图表上线后用户根本不看他们就是要一个简单的 tail 视图加一个搜索框。后来我搬了把椅子坐在 SRE 旁边看他排障才发现他们要的就三样极简的时间选择、聚焦的关键词过滤、一个能暂停的流视图。设计这种工具本质上是在设计“如何减少用户大脑的负担”而不是设计“如何展示更多信息”。每当你犹豫要不要加一个功能的时候去数一下这个功能到底会让用户少点几次鼠标再决定不迟。
返回列表