通过golang 编写一个通用的 prometheus_exporter,数据更新非每次访问更新自定义指标的数据。而是通过定时的方式进行更新。
代码写的不好,见谅哈
package main
import (
"bytes"
"errors"
"flag"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/text/encoding/simplifiedchinese"
"gopkg.in/yaml.v2"
"log"
"net/http"
"os"
"os/exec"
"regexp"
"runtime"
"strconv"
"strings"
"time"
)
var conf Config
// up 服务状态
var up = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "up", Help: "Service status"}, []string{"job", "namespace", "server"})
// grafana筛选项
var Custom = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: "Custom", Help: "Grafana Custom"}, []string{"job", "namespace", "server"})
// metrics 监控数据(多个监控项)
var metric = make(map[string]*prometheus.GaugeVec)
// 配置文件
type Config struct {
Monitoring MonitoringConfiguration `yaml:"monitoring_configuration"`
Promconf struct {
Job string `yaml:"job"`
Path string `yaml:"path"`
Port string `yaml:"port"`
Namespace string `yaml:"namespace"`
Server string `yaml:"server"`
Sleep time.Duration `yaml:"sleep"`
} `yaml:"prometheus"`
}
// MonitoringConfiguration 监控项配置
type MonitoringConfiguration struct {
CustomMonitors []Monitoring `yaml:"custom_monitors"`
}
// Monitoring 监控项
type Monitoring struct {
Name string `yaml:"name"`
Description interface{} `yaml:"description"`
Command string `yaml:"command"`
}
// 判断一个字符串是否由数字组成
func isNumericString(s string) bool {
_, err := strconv.Atoi(removeExtraSpace(s))
return err == nil
}
// 去除字符串首尾的空白字符和引号
func removeExtraSpace(input string) string {
input = strings.TrimSpace(input)
reg := regexp.MustCompile(`\s+`)
input = reg.ReplaceAllString(input, "")
if len(input) == 0 {
log.Println("原始数字为空")
return "0"
}
return strings.Trim(input, `"`)
}
// 执行 command 命令
func getValue(cmd string) (float64, error) {
defer func() {
err := recover()
if err != nil {
log.Println("Error", err)
}
}()
var command *exec.Cmd
if runtime.GOOS == "linux" {
// 获取入口通道
command = exec.Command("/bin/bash", "-c", cmd)
} else if runtime.GOOS == "windows" {
// 获取入口通道
command = exec.Command("cmd.exe", "/C", cmd)
}
var out bytes.Buffer
var stderr bytes.Buffer
command.Stdout = &out
command.Stderr = &stderr
var strerr string
err := command.Run()
if err != nil {
if runtime.GOOS == "windows" {
// 使用GBK解码,否则 windows 执行命令会乱码
decoder := simplifiedchinese.GBK.NewDecoder()
decodedStr, _ := decoder.Bytes(stderr.Bytes())
strerr = string(decodedStr)
} else if runtime.GOOS == "linux" {
strerr = string(stderr.Bytes())
}
//服务状态判断为异常
return 0, errors.New(strerr)
}
if !isNumericString(out.String()) {
return 0, errors.New("值不是纯数字组成: " + out.String())
}
ifloat, err := strconv.ParseFloat(removeExtraSpace(out.String()), 64)
if err != nil {
log.Println("数据类型转化失败", err)
return 0, err
}
return ifloat, nil
}
// 加载配置文件
func LoadXml(filepath string) error {
file, err := os.ReadFile(filepath)
if err != nil {
return err
}
err = yaml.Unmarshal(file, &conf)
if err != nil {
return err
}
return nil
}
func main() {
defer func() {
err := recover()
if err != nil {
log.Println("Error", err)
}
}()
//启动参数
conffile := flag.String("e", "/etc/general_export.yaml", "指定配置文件 默认: /etc/general_export.yaml")
flag.Parse()
err := LoadXml(*conffile)
if err != nil {
log.Println("配置文件加载失败", err)
return
}
//初始化监控指标
for _, monitor := range conf.Monitoring.CustomMonitors {
// 监控指标
metric[monitor.Name] = prometheus.NewGaugeVec(prometheus.GaugeOpts{Name: monitor.Name, Help: monitor.Description.(string)}, []string{"job", "namespace", "server"})
if ifloat, metricserr := getValue(monitor.Command); metricserr == nil {
metric[monitor.Name].WithLabelValues(conf.Promconf.Job, conf.Promconf.Namespace, conf.Promconf.Server).Set(ifloat)
//设置服务状态
up.WithLabelValues(conf.Promconf.Job, conf.Promconf.Namespace, conf.Promconf.Server).Set(1)
//注册自定义监控指标
prometheus.MustRegister(metric[monitor.Name])
} else {
log.Printf("[%s]监控值获取失败: %v", monitor.Name, metricserr)
metric[monitor.Name].WithLabelValues(conf.Promconf.Job, conf.Promconf.Namespace, conf.Promconf.Server).Set(ifloat)
up.WithLabelValues(conf.Promconf.Job, conf.Promconf.Namespace, conf.Promconf.Server).Set(0)
}
}
//筛选
Custom.WithLabelValues(conf.Promconf.Job, conf.Promconf.Namespace, conf.Promconf.Server).Set(0)
//注册up指标
prometheus.MustRegister(up, Custom)
//更新数据指标
go func() {
for range time.Tick(time.Second * conf.Promconf.Sleep) {
for _, monitor := range conf.Monitoring.CustomMonitors {
// 获取监控值
ifloat, err := getValue(monitor.Command)
if err != nil {
log.Printf("[%s]监控值获取失败: %v", monitor.Name, err)
metric[monitor.Name].WithLabelValues(conf.Promconf.Job, conf.Promconf.Namespace, conf.Promconf.Server).Set(ifloat)
up.WithLabelValues(conf.Promconf.Job, conf.Promconf.Namespace, conf.Promconf.Server).Set(0) //更新服务状态为DOWN
} else {
metric[monitor.Name].WithLabelValues(conf.Promconf.Job, conf.Promconf.Namespace, conf.Promconf.Server).Set(ifloat)
//设置服务状态 UP
up.WithLabelValues(conf.Promconf.Job, conf.Promconf.Namespace, conf.Promconf.Server).Set(1)
}
}
}
}()
// 暴露指标
http.Handle(conf.Promconf.Path, promhttp.Handler())
log.Println("server up http://0.0.0.0:" + conf.Promconf.Port + conf.Promconf.Path)
err = http.ListenAndServe(":"+conf.Promconf.Port, nil)
if err != nil {
log.Println("Error", err)
return
}
}
配置文件
monitoring_configuration:
#custom_monitors 可以自定义多个自定义监控项(可执行脚本)
custom_monitors:
- name: "自定义监控项名称1"
description: "监控项描述"
command: "windows 或 linux 指令,需要返回数值"
- name: "Args2"
description: "用于监控入口并发"
command: "type D:\\工作目录\\工作目录-整理\\test2.txt"
prometheus:
job: "测试JOB"
path: "/metrics" #exporter 监听路径
port: 9901 #exporter 监听端口
sleep: 10 #数据更新的间隔时间时间
namespace: "测试项目" #项目名称空间
server: "服务名称" #服务器类型或服务名称