如何在prom-client注册表中恢复从数据库保存的指标状态?
使用prom-client从字符串恢复指标到注册表
我用prom-client开发Node.js应用时,实现了每分钟将指标以字符串形式存入数据库的逻辑,但在加载时,清空注册表后不知道如何把读取到的指标字符串恢复回去。
已实现的保存代码:
async function saveMetricsToDatabase(dbSuite, register) { try { const metrics = register.metrics(); const { metricsMapper } = dbSuite; await metricsMapper.create({ data: metrics }); // 存入数据库 } catch (err) { console.error('Failed to save metrics to database:', err); } }
待完善的加载代码:
async function loadMetricsFromDatabase(dbSuite, register) { try { const { metricsMapper } = dbSuite; const result = await metricsMapper.findOne({ order: [['createdAt', 'DESC']], }); if (result) { const metrics = result.data; register.clear(); register.xxx(metrics); // 这里不知道怎么把指标字符串加载到注册表 } } catch (err) { console.error('Failed to load metrics from database:', err); } }
解决方案
prom-client没有直接提供从字符串加载指标的API,需要自己解析指标文本,重新创建对应类型的指标实例并设置值。
步骤1:解析指标字符串
先解析prometheus格式的指标文本,提取指标名称、类型、标签和值:
function parseMetrics(metricsStr) { const lines = metricsStr.split('\n').filter(line => line); const metricsMap = new Map(); let currentMetricType = null; let currentMetricName = null; lines.forEach(line => { // 提取指标类型注释 if (line.startsWith('# TYPE')) { const parts = line.split(' '); currentMetricName = parts[2]; currentMetricType = parts[3]; metricsMap.set(currentMetricName, { type: currentMetricType, entries: [] }); return; } // 跳过其他注释行 if (line.startsWith('#')) return; // 解析指标行 const [fullNamePart, valueStr] = line.split(' '); const value = parseFloat(valueStr); if (isNaN(value)) return; let name = fullNamePart; let labels = {}; // 处理带标签的指标 if (fullNamePart.includes('{')) { const [namePart, labelsPart] = fullNamePart.split('{'); name = namePart; const labelsStr = labelsPart.slice(0, -1); // 去掉末尾的} labelsStr.split(',').forEach(labelPair => { const [key, val] = labelPair.split('='); labels[key.trim()] = val.replace(/"/g, ''); }); } // 如果没有从TYPE注释获取到类型,默认设为gauge if (!metricsMap.has(name)) { metricsMap.set(name, { type: 'gauge', entries: [] }); } metricsMap.get(name).entries.push({ labels, value }); }); return metricsMap; }
步骤2:将解析后的指标加载到注册表
根据解析出的指标类型,创建对应的Counter/Gauge/Histogram/Summary实例,并设置标签和值:
function loadMetricsToRegistry(metricsMap, register) { const { Counter, Gauge, Histogram, Summary } = require('prom-client'); metricsMap.forEach((metricData, name) => { const { type, entries } = metricData; let metric; // 根据类型创建指标实例 switch (type) { case 'counter': metric = new Counter({ name, help: `${name} metric`, registers: [register] }); break; case 'gauge': metric = new Gauge({ name, help: `${name} metric`, registers: [register] }); break; case 'histogram': // 注意:直方图的buckets需要和原指标配置一致,建议保存指标时同时存储buckets信息 metric = new Histogram({ name, help: `${name} metric`, registers: [register], buckets: [0.1, 0.5, 1, 2, 5] // 替换为你的实际buckets配置 }); break; case 'summary': metric = new Summary({ name, help: `${name} metric`, registers: [register] }); break; default: console.warn(`不支持的指标类型 ${type}:${name},跳过加载`); return; } // 设置每个标签组合的指标值 entries.forEach(({ labels, value }) => { if (Object.keys(labels).length > 0) { metric.labels(labels).set(value); } else { metric.set(value); } }); }); }
步骤3:完善加载函数
修改原有的loadMetricsFromDatabase函数,调用上面的解析和加载方法:
async function loadMetricsFromDatabase(dbSuite, register) { try { const { metricsMapper } = dbSuite; const result = await metricsMapper.findOne({ order: [['createdAt', 'DESC']], }); if (result) { const metrics = result.data; register.clear(); const parsedMetrics = parseMetrics(metrics); loadMetricsToRegistry(parsedMetrics, register); } } catch (err) { console.error('Failed to load metrics from database:', err); } }
注意事项
- 直方图和摘要需要额外配置(如buckets),建议保存指标时同时存储这些元数据,否则恢复后的指标配置可能与原指标不一致
- 如果指标有自定义的help信息,也可以在保存时一并存储,恢复时填入指标实例的配置中
- 解析逻辑需适配你的指标格式,若有特殊标签或命名规则,需调整解析代码
内容的提问来源于stack exchange,提问作者Sudo Trololo
相关产品推荐
相关产品推荐

