使用 grafana-foundation-sdk 的类型化构建器(TypeScript 或 Go),以代码方式构建 Grafana 仪表板。适用于创建、修改或生成 Grafana 仪表板。
Grafana Foundation SDK.是一项面向实际任务的技能,主要用于Grafana-foundation-sdk 提供了强大的打字器库,用于定义 Grafana 仪表板为代码. 而不是写原 JSON(它容易出错,而且很硬)。它将相关步骤、工具调用和结果整理方式集中到统一流程中,帮助使用者更快完成目标并减少重复操作。
实际使用前应先确认任务范围、数据来源、运行环境、必要权限和关键参数,再依据技能说明逐步执行;若输入条件不完整,应先补齐信息或采用保守配置,避免因错误假设导致结果偏离需求。
执行过程中需要关注工具调用是否成功、接口或依赖是否可用、输出格式是否符合预期,并对异常提示、缺失字段和边界情况进行处理;涉及批量任务时,还应保存进度,避免中断后重复操作。
grafana-foundation-sdk 提供强类型的构建器库,用于以代码方式定义 Grafana 仪表板。相比直接编写原始 JSON(易出错且在 diff 中难以评审),你可通过链式调用构建器方法来组装仪表板,最终生成合法的 Grafana JSON。
该 SDK 由 Grafana 内部 CUE 模式通过 cog 工具自动生成,支持 Go、TypeScript、Python、PHP 和 Java。本技能聚焦于 TypeScript(主语言)和 Go(次选语言),因为这是基础设施团队最常用的技术栈。
SDK 以具体的 v0.0.x 版本标签发布(最新版:v0.0.16)。请显式锁定版本——当前处于 v1.0 前阶段,各版本间 API 可能发生变更(参见“已知注意事项”)。
TypeScript:
npm install '@grafana/grafana-foundation-sdk@~0.0.16' # 或 pnpm add '@grafana/grafana-foundation-sdk@~0.0.16'
Go:
go get github.com/grafana/grafana-foundation-sdk/go@v0.0.16
所有功能均遵循 构建器模式(Builder Pattern):创建构建器实例 → 链式调用配置方法 → 调用 .build()(TypeScript)或 .Build()(Go)生成最终对象。输出为标准 Grafana 仪表板 JSON,完全兼容 Grafana API、文件式预配(file-based provisioning)及 Kubernetes ConfigMap。
每种面板类型、查询类型和变量类型均位于独立包中,仅需导入所需模块:
// 各关注点对应独立导入
import { DashboardBuilder, RowBuilder } from '@grafana/grafana-foundation-sdk/dashboard';
import { PanelBuilder as TimeseriesBuilder } from '@grafana/grafana-foundation-sdk/timeseries';
import { PanelBuilder as StatBuilder } from '@grafana/grafana-foundation-sdk/stat';
import { DataqueryBuilder as PromQueryBuilder } from '@grafana/grafana-foundation-sdk/prometheus';
import { DataqueryBuilder as LokiQueryBuilder } from '@grafana/grafana-foundation-sdk/loki';
import { DashboardBuilder, RowBuilder, QueryVariableBuilder } from '@grafana/grafana-foundation-sdk/dashboard';
import { PanelBuilder as StatBuilder } from '@grafana/grafana-foundation-sdk/stat';
import { PanelBuilder as TimeseriesBuilder } from '@grafana/grafana-foundation-sdk/timeseries';
import { DataqueryBuilder } from '@grafana/grafana-foundation-sdk/prometheus';
import * as common from '@grafana/grafana-foundation-sdk/common';
const dashboard = new DashboardBuilder('My Service Overview')
.uid('my-service-overview')
.tags(['my-service'])
.editable()
.refresh('30s')
.time({ from: 'now-24h', to: 'now' })
.timezone('browser')
.withVariable(
new QueryVariableBuilder('service')
.label('Service')
.query('label_values(up{namespace="default"}, job)')
.datasource({ type: 'prometheus', uid: 'prometheus' })
.refresh(1)
.includeAll(true)
.allValue('.*')
.sort(1)
)
.withRow(new RowBuilder('Overview'))
.withPanel(
new StatBuilder()
.title('Request Rate')
.datasource({ type: 'prometheus', uid: 'prometheus' })
.withTarget(
new DataqueryBuilder()
.expr('sum(rate(http_requests_total{job=~"$service"}[5m]))')
.legendFormat('req/s')
)
.unit('reqps')
.decimals(1)
.height(4)
.span(6)
.colorMode(common.BigValueColorMode.Background)
.graphMode(common.BigValueGraphMode.Area)
.reduceOptions(
new common.ReduceDataOptionsBuilder().calcs(['lastNotNull'])
)
)
.withPanel(
new TimeseriesBuilder()
.title('Request Rate Over Time')
.datasource({ type: 'prometheus', uid: 'prometheus' })
.withTarget(
new DataqueryBuilder()
.expr('sum by (job)(rate(http_requests_total{job=~"$service"}[5m]))')
.legendFormat('{{job}}')
)
.unit('reqps')
.fillOpacity(15)
.height(8)
.span(12)
);
// 输出仪表板 JSON
console.log(JSON.stringify(dashboard.build(), null, 2));
使用 SDK 最大的收益在于创建可复用的辅助函数,将团队约定固化为代码:
function promDs() {
return { type: 'prometheus', uid: 'prometheus' } as const;
}
function lokiDs() {
return { type: 'loki', uid: 'loki' } as const;
}
function promQuery(expr: string, legend?: string) {
const q = new DataqueryBuilder().expr(expr);
if (legend) q.legendFormat(legend);
return q;
}
function statPanel(title: string, expr: string, opts?: { unit?: string; decimals?: number; color?: string }) {
const panel = new StatBuilder()
.title(title)
.datasource(promDs())
.withTarget(promQuery(expr))
.height(4)
.span(4)
.colorMode(common.BigValueColorMode.Background)
.graphMode(common.BigValueGraphMode.Area)
.reduceOptions(new common.ReduceDataOptionsBuilder().calcs(['lastNotNull']));
if (opts?.unit) panel.unit(opts.unit);
if (opts?.decimals !== undefined) panel.decimals(opts.decimals);
// 如需设置阈值,可调用 .thresholds()
return panel;
}
// 查询变量 —— 从 Prometheus 标签中动态填充
new QueryVariableBuilder('service')
.label('Service')
.query('label_values(http_server_duration_count{namespace="x402"}, job)')
.datasource({ type: 'prometheus', uid: 'prometheus' })
.refresh(2) // 1=仪表板加载时刷新,2=时间范围变更时刷新
.includeAll(true)
.allValue('.*')
.sort(1) // 1=按字母升序
// 自定义变量 —— 静态键值对
new CustomVariableBuilder('level')
.label('Log Level')
.query('All : .+, Error : error|fatal, Warning : warn, Info : info, Debug : debug')
.current({ text: 'All', value: '.+' })
在查询中引用变量时,使用标准 Grafana 语法:$service、$__range、$__rate_interval、$__auto。
面板使用 height(h)(网格行数)与 span(w)(24 列栅格中的列宽)控制布局:
.span(24).span(12).span(8).span(6).height(4).span(4).height(8).span(12)import { ThresholdsConfigBuilder } from '@grafana/grafana-foundation-sdk/dashboard';
// 第一个步骤必须设为 null(作为基础色)
new StatBuilder()
.thresholds(
new ThresholdsConfigBuilder()
.mode(common.ThresholdsMode.Absolute)
.steps([
{ value: null as any, color: 'green' },
{ value: 80, color: 'yellow' },
{ value: 95, color: 'red' },
])
)
new TimeseriesBuilder()
.overrideByName('Revenue', [
{ id: 'color', value: { fixedColor: 'green', mode: 'fixed' } },
])
.overrideByRegexp('.*5..', [
{ id: 'color', value: { fixedColor: 'red', mode: 'fixed' } },
])
// 普通行
.withRow(new RowBuilder('Traffic'))
// 折叠行(含嵌套面板)
.withRow(
new RowBuilder('Business Details')
.collapsed()
.withPanel(/* ... */)
.withPanel(/* ... */)
)
import { DataqueryBuilder as LokiQueryBuilder } from '@grafana/grafana-foundation-sdk/loki';
// 日志查询
new LokiQueryBuilder()
.expr('{namespace="x402", app=~"$service", level=~"$level"}')
.refId('A')
// 从日志中提取指标
new LokiQueryBuilder()
.expr('sum by (buyer_wallet)(count_over_time({namespace="x402"} | event="request" [$__range]))')
.legendFormat('{{buyer_wallet}}')
.refId('A')
由于 SDK 尚未为全部转换类型提供类型化构建器,因此转换需以原始对象形式传入:
new TableBuilder()
.withTransformation({
id: 'reduce',
options: {
reducers: ['lastNotNull'],
mode: 'seriesToRows',
includeTimeField: false,
labelsToFields: true,
},
})
.withTransformation({
id: 'organize',
options: {
excludeByName: { Field: true },
renameByName: { buyer_wallet: 'Buyer Wallet', 'Last not null': 'Requests' },
},
})
.withTransformation({
id: 'sortBy',
options: { sort: [{ field: 'Requests', desc: true }] },
})
.build() 方法返回一个符合 Grafana 仪表板 JSON Schema 的纯对象。你可以按需序列化:
// 标准 JSON 文件(用于预配或 ConfigMap)
const fs = require('fs');
const dashboard = builder.build();
fs.writeFileSync('dashboard.json', JSON.stringify(dashboard, null, 2));
// Kubernetes 资源清单(适配 Grafana 的 k8s API)
const manifest = {
apiVersion: 'dashboard.grafana.app/v1beta1',
kind: 'Dashboard',
metadata: { name: dashboard.uid },
spec: dashboard,
};
console.log(JSON.stringify(manifest, null, 2));
以下问题源自真实使用经验及 SDK 仓库的公开 issue:
SDK 处于 v0.0.x 阶段(公测版) —— Grafana Labs 已在生产环境使用,但各版本间 API 可能变动。请务必显式锁定版本。推荐用于 Grafana >= 12,亦兼容 Grafana >= 10。
Prometheus 中 instant() 与 range() 互斥 —— 调用 .instant() 会同时设置 instant=true 和 range=false;调用 .range() 则相反。如需两者共存,请改用 .rangeAndInstant()。
Loki 的 range()/instant() 已弃用 —— 请改用 .queryType('range') 或 .queryType('instant')。同理,.resolution() 已被 .step() 替代。
首个阈值步骤必须设为 value: null —— 此为基准/默认颜色。若遗漏将导致生成非法 JSON。
面板 ID 自动分配 —— 你无需手动设置面板的 id 字段,Grafana 在导入时自动分配。同理,gridPos.x/y 由 height() 和 span() 自动计算得出。
转换(Transformations)为原始对象 —— SDK 当前未提供类型化转换构建器。请通过 .withTransformation() 传入原始 { id, options } 对象。
CustomVariable 的特殊行为 —— 当通过 Grafana API 预配时,CustomVariableBuilder 必须设置 .query() 字段(以逗号分隔的 key:value 形式,例如 'All : .+, Error : error'),否则选项无法持久化,即使同时调用了 .values()。
Go:必须使用 cog.ToPtr() —— 许多结构体字段为指针类型。对可空字段(如阈值、数据源引用等)应使用 cog.ToPtr[T](value)。TypeScript 无此限制。
Go:Build() 返回 error —— 必须检查其返回值。而 TypeScript 的 .build() 直接返回对象,并通过编译期类型检查保障安全性。
插件类数据源暂无类型化查询构建器 —— 仅核心数据源(Prometheus、Loki、Tempo、Elasticsearch、CloudWatch 等)具备构建器。对于第三方插件,需自行实现 Builder 接口定义自定义查询类型。
仪表板 Schema v1 与 v2 —— 本技能面向 v1 仪表板(@grafana/grafana-foundation-sdk/dashboard,Kubernetes apiVersion dashboard.grafana.app/v1beta1)。新版 v2 Schema 以 dashboardv2beta1 形式发布(Kubernetes apiVersion dashboard.grafana.app/v2beta1),并配备独立构建器。目前 v2beta1 仍在稳定化过程中,存在已知问题(如转换、注解定位、Go 中 SQL 表达式支持等)——除非明确需要 v2 的布局能力,否则优先选用 v1。多数查询/面板构建器在 v1/v2 间共享;部分构建器提供 QueryV2Builder/VisualizationV2Builder 变体以支持 v2。
构建器仅在接入 tsconfig 时才受类型检查 —— SDK 仅当生成器文件实际被 tsconfig 包含并执行类型检查时,才能提供编译期安全保障。若生成器位于非包目录下(例如 Helm Chart 目录),且无 tsconfig 引用,则该文件将静默跳过类型检查,错误仅在运行 .build() 时暴露。此外:SDK 输出目标为 ES2024/bundler 模块解析,旧版全局 tsc 无法处理 —— 请始终使用项目本地编译器(npx tsc),而非陈旧的全局版本。
每次修改生成器后必须重新生成 JSON —— 实际部署的仪表板是生成的 JSON 文件,而非 .ts/.go 源码。编辑生成器后,需重新运行生成脚本,并将新生成的 JSON 一并提交;切勿手动编辑生成的 JSON(下次生成将静默覆盖)。建议在仓库规范中加入“禁止直接编辑仪表板 JSON”的规则。
在本项目中,仪表板通过 monitoring-deps Helm Chart 以 Kubernetes ConfigMap 方式预配:
ops/helmfile-infra/charts/monitoring-deps/dashboards/templates/dashboards.yaml 将每个 JSON 文件包装为带 grafana_dashboard: "1" 标签的 ConfigMapuid: "prometheus")和 Loki(uid: "loki")namespace="x402"$service(job 选择器)、$level(日志级别)为本项目生成仪表板时,请将生成的 JSON 输出至 dashboards/ 目录,并确保 ConfigMap 模板正确引用该文件。
如需完整 API 参考与示例,请查阅:
references/typescript-api.md —— 完整 TypeScript API 文档,涵盖全部面板类型、查询构建器及配置选项references/patterns.md —— 常见仪表板模式、实践配方,以及将本项目现有仪表板迁移至 SDK 的完整示例相关专题
热门下载
相关下载
精品课程
共6课时 | 54.7万人学习
共89课时 | 134.1万人学习
共49课时 | 82.6万人学习
最新文章