Grafana PanelEditNext 崩溃:Prometheus “Both” 查询类型问题
概述
在 Grafana 的可观测性平台中,面板编辑器是构建和调试仪表板的核心组件。当用户尝试编辑一个配置了 Prometheus 数据源且查询类型设置为 “Both”(即同时执行范围查询和即时查询)的面板时,在特定的用户界面(UI)路径下会引发前端应用程序崩溃。这个问题影响了仪表板编辑器的稳定性和用户体验,是 SRE 和前端开发者在维护 Grafana 平台可靠性时需要关注和解决的具体故障案例。理解此问题有助于避免类似因组件状态不一致导致的运行时错误。
核心概念
Prometheus 查询类型
在 Grafana 的 Prometheus 数据源中,一个查询可以指定不同的类型:
- Range: 执行基于时间范围的查询,返回时间序列数据。
- Instant: 在特定时间点执行查询,返回一个单一值。
- Both: 同时启用
range和instant查询。当查询中的range: true和instant: true同时存在时,getQueryTypeValue(query)函数会返回字符串"both"。
面板编辑器路径:v1 与 v2
Grafana 目前存在两套面板编辑器的实现路径:
- v1 (
PanelEdit): 传统的编辑器路径,通过QueryEditorRow组件渲染查询编辑器。 - v2 (
PanelEditNext): 新一代编辑器路径,是默认体验(除非通过localStorage键grafana.dashboard.query-editor-next.v2-preference覆盖)。它通过QueryEditorRenderer组件进行渲染。
崩溃问题仅发生在 v2 路径上,而 v1 路径能正确处理相同的查询。
应用上下文 (app 属性)
渲染查询编辑器的组件需要一个 app 属性来标识当前的应用上下文,例如是仪表板、探索页面还是面板编辑器。不同的上下文决定了可用的 UI 选项。核心的 CoreApp 枚举值包括:
CoreApp.DashboardCoreApp.PanelEditorCoreApp.ExploreCoreApp.Correlations
问题的根源在于 getQueryTypeOptions(includeBoth) 函数,它仅当 includeBoth 参数为 true 时,才会在返回的选项列表中包含 "both" 这个选项。这个参数为 true 的条件是 app 值为 CoreApp.Explore、CoreApp.Correlations 或 CoreApp.PanelEditor。
实践
问题诊断与复现
- 触发条件:使用一个配置了 Prometheus 数据源,并且查询类型明确设置为“Both”的面板。
- 进入 v2 编辑器:打开该面板的编辑界面。确保当前使用的是 v2 路径(
PanelEditNext)。可以通过检查浏览器localStorage中是否存在键grafana.dashboard.query-editor-next.v2-preference并设置为"true"来确认。 - 观察错误:编辑器界面将崩溃,浏览器控制台会抛出
TypeError,指向PromQueryBuilderOptions组件中的代码行。
解决方案与修复
此问题的根本原因是在错误的组件中使用了错误的应用上下文。v2 路径的 QueryEditorRenderer 组件错误地将其渲染的查询编辑器的 app 属性硬编码为 CoreApp.Dashboard,而该上下文不包含对 “Both” 查询类型的支持。
修复步骤:
- 定位问题源文件:
public/app/features/dashboard-scene/panel-edit/PanelEditNext/QueryEditor/QueryEditorRenderer.tsx。 - 修改第 78 行附近的代码,将
app属性的值从错误的CoreApp.Dashboard更正为CoreApp.PanelEditor。
// 修复前 (错误)
app={CoreApp.Dashboard}
// 修复后 (正确)
app={CoreApp.PanelEditor}
为什么这是正确的修复?
QueryEditorRenderer 组件专门用于在面板编辑器(Panel Editor)中渲染查询编辑器。因此,它应该声明自己处于 CoreApp.PanelEditor 的上下文中,而非 CoreApp.Dashboard。这个错误的上下文值一直存在,但只有在查询类型为 “Both” 时才会因为选项列表缺失而导致 .find() 方法返回 undefined 并引发崩溃。将上下文更正后,getQueryTypeOptions(true) 将被调用,“Both” 选项得以正确显示,.find() 能够成功找到匹配项。
SRE 要点
与可靠性及事件响应的关联
此类前端崩溃是影响用户体验和平台可用性的直接故障。SRE 需要确保监控系统能捕获前端的 JavaScript 运行时错误,并触发告警。在事件响应中,需要快速区分这是用户特定的操作问题,还是可重现的代码缺陷,以便决定是否需要发布热修复或通知工程团队。
最佳实践与常见反模式
- 上下文一致性:此案例是一个典型的“属性上下文不一致”反模式。组件应准确声明其运行环境(如
app属性),下游逻辑依赖此属性来提供正确的功能选项。硬编码或传递错误的上下文值会导致隐藏的缺陷,直到触发特定边界条件(如选择“Both”查询类型)才会暴露。 - 防御性编程:在
PromQueryBuilderOptions中,.find(...).label的调用缺乏空值检查。更健壮的写法应先判断find的结果是否存在,例如:const option = queryTypeOptions.find(...); const queryTypeLabel = option ? option.label : 'Unknown';。虽然根本原因在上游(错误的上下文),但下游组件也应增强健壮性。 - 功能标志与渐进发布:v1 与 v2 路径并存体现了渐进式发布的策略。SRE 和开发者应注意,新路径引入时可能需要全面回归测试现有功能(如所有查询类型的支持),避免回归缺陷。
相关指标 (SLI/SLO)
对于影响编辑器核心功能的崩溃,可以定义如下 SLI:
- SLI:面板编辑器的 JavaScript 错误率(例如:每分钟唯一会话中的崩溃次数)。
- SLO:可以设定为“面板编辑器在用户执行关键操作(如打开编辑器、保存更改)时的无崩溃成功率 ≥ 99.9%”。此问题的出现会直接侵蚀该 SLO 的 Error Budget。