文章目录
- 环境准备
- 代码编写
- 搭建开发环境和包依赖
- 创建main文件并进行初始化
- 添加prometheus metrics endpoint并监听服务端口
- 通过模拟url获取监控项的值
- 通过编写函数获取监控项的值
- 声明prometheus指标信息
- 声明prometheus接口框架
- 在main函数中声明exporter并注册
- 完整代码如下
环境准备
演示用例使用go语言进行开发,请准备golang开发环境。搭建方式可以参照golang环境搭建
代码编写
搭建开发环境和包依赖
- 创建工作目录my_exporter
go mod init my_exporter
go get github.com/prometheus/client_golang
go get github.com/joho/godotenv
创建main文件并进行初始化
package mainimport ("github.com/joho/godotenv""github.com/prometheus/client_golang/prometheus""github.com/prometheus/client_golang/prometheus/promhttp"
)
package mainimport ("github.com/joho/godotenv""github.com/prometheus/client_golang/prometheus""github.com/prometheus/client_golang/prometheus/promhttp"
)func main() {}
添加prometheus metrics endpoint并监听服务端口
func main() {http.Handle("/metrics", promhttp.Handler())log.Fatal(http.ListenAndServe(":9141", nil))
}
通过模拟url获取监控项的值
http.HandleFunc("/api/channels/idsAndNames", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte(`<map><entry><string>101af57f-f26c-40d3-86a3-309e74b93512</string><string>Send-Email-Notification</string></entry>
</map>`))})http.HandleFunc("/api/channels/statistics", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte(`<list><channelStatistics><serverId>c5e6a736-0e88-46a7-bf32-5b4908c4d859</serverId><channelId>101af57f-f26c-40d3-86a3-309e74b93512</channelId><received>0</received><sent>0</sent><error>0</error><filtered>0</filtered><queued>0</queued></channelStatistics>
</list>`))})
通过编写函数获取监控项的值
func get_sys_file_node_count() string {cmd := exec.Command("bash", "-c", "cat /proc/sys/fs/file-nr | awk -F' ' '{ print $1 }'")out, err := cmd.CombinedOutput()if err != nil {log.Fatalf("cmd.Run() failed with %s\n", err)}return string(out)
}
声明prometheus指标信息
在prometheus中,每个metric都由下面几部分组成:
- metric name:指标名称
- metric label value:指标标签的值
- metric help text:指标帮助文本
- metric type:指标类型
- measurement:测量值
messagesReceived = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "messages_received_total"),"How many messages have been received (per channel).",[]string{"channel"}, nil,
)
声明prometheus接口框架
自定义exporter需要4个部分:
- A structure with member variables一个结构体
- A factory method that returns the structure返回结构体的工厂方法
- Describe function Describe函数
- Collect function Collect函数
type Exporter struct {mirthEndpoint, mirthUsername, mirthPassword string
}func NewExporter(mirthEndpoint string, mirthUsername string, mirthPassword string) *Exporter {return &Exporter{mirthEndpoint: mirthEndpoint,mirthUsername: mirthUsername,mirthPassword: mirthPassword,}
}
func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {
}
func (e *Exporter) Collect(ch chan<- prometheus.Metric) {
}
在main函数中声明exporter并注册
exporter := NewExporter(mirthEndpoint, mirthUsername, mirthPassword)
prometheus.MustRegister(exporter)
完整代码如下
package mainimport ("crypto/tls""encoding/xml""flag""io/ioutil""log""net/http""net""os""os/exec""strconv""github.com/joho/godotenv""github.com/prometheus/client_golang/prometheus""github.com/prometheus/client_golang/prometheus/promhttp"
)type ChannelIdNameMap struct {XMLName xml.Name `xml:"map"`Entries []ChannelEntry `xml:"entry"`
}type ChannelEntry struct {XMLName xml.Name `xml:"entry"`Values []string `xml:"string"`
}type ChannelStatsList struct {XMLName xml.Name `xml:"list"`Channels []ChannelStats `xml:"channelStatistics"`
}type ChannelStats struct {XMLName xml.Name `xml:"channelStatistics"`ServerId string `xml:"serverId"`ChannelId string `xml:"channelId"`Received string `xml:"received"`Sent string `xml:"sent"`Error string `xml:"error"`Filtered string `xml:"filtered"`Queued string `xml:"queued"`
}const namespace = "mirth"
const channelIdNameApi = "/api/channels/idsAndNames"
const channelStatsApi = "/api/channels/statistics"type tcpKeepAliveListener struct {*net.TCPListener
}func ListenAndServe(addr string, handler http.Handler) error {srv := &http.Server{Addr: addr, Handler: handler}addr = srv.Addrif addr == "" {addr = ":http"}ln, err := net.Listen("tcp4", addr)if err != nil {return err}return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}var (tr = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true},}client = &http.Client{Transport: tr}listenAddress = flag.String("web.listen-address", "10.12.23.22:9141", "Address to listen on for telemetry")metricsPath = flag.String("web.telemetry-path", "/metrics1", "Path under which to expose metrics")up = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "up"),"Was the last Mirth query successful.",nil, nil,)messagesReceived = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "messages_received_total"),"How many messages have been received (per channel).",[]string{"channel"}, nil,)messagesFiltered = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "messages_filtered_total"),"How many messages have been filtered (per channel).",[]string{"channel"}, nil,)messagesQueued = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "messages_queued"),"How many messages have been queued (per channel).",[]string{"channel"}, nil,)messagesSent = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "messages_send_total"),"How many messages have been sent (per channel).",[]string{"channel"}, nil,)messagesErrored = prometheus.NewDesc(prometheus.BuildFQName(namespace, "", "messages_errored_total"),"How many messages have been errored (per channel).",[]string{"channel"}, nil,)count string = "10"
)type Exporter struct {mirthEndpoint, mirthUsername, mirthPassword string
}func NewExporter(mirthEndpoint string, mirthUsername string, mirthPassword string) *Exporter {return &Exporter{mirthEndpoint: mirthEndpoint,mirthUsername: mirthUsername,mirthPassword: mirthPassword,}
}func (e *Exporter) Describe(ch chan<- *prometheus.Desc) {ch <- upch <- messagesReceivedch <- messagesFilteredch <- messagesQueuedch <- messagesSentch <- messagesErrored
}func (e *Exporter) Collect(ch chan<- prometheus.Metric) {channelIdNameMap, err := e.LoadChannelIdNameMap()if err !=nil {ch <- prometheus.MustNewConstMetric(up, prometheus.GaugeValue, 0,)log.Println(err)return}ch <- prometheus.MustNewConstMetric(up, prometheus.GaugeValue, 1,)e.HitMirthRestApisAndUpdateMetrics(channelIdNameMap, ch)
}func (e *Exporter) LoadChannelIdNameMap() (map[string]string, error) {channelIdNameMap := make(map[string]string)req, err := http.NewRequest("GET", e.mirthEndpoint+channelIdNameApi, nil)if err != nil {return nil, err}req.SetBasicAuth(e.mirthUsername, e.mirthPassword)resp, err := client.Do(req)if err != nil {return nil, err}body, err := ioutil.ReadAll(resp.Body)resp.Body.Close()if err != nil {return nil, err}var channelIdNameMapXML ChannelIdNameMaperr = xml.Unmarshal(body, &channelIdNameMapXML)if err != nil {log.Println(err)return nil, err}for i := 0; i < len(channelIdNameMapXML.Entries); i++ {channelIdNameMap[channelIdNameMapXML.Entries[i].Values[0]] = channelIdNameMapXML.Entries[i].Values[1]}return channelIdNameMap, nil
}func get_sys_file_node_count() string {cmd := exec.Command("bash", "-c", "cat /proc/sys/fs/file-nr | awk -F' ' '{ print $1 }'")out, err := cmd.CombinedOutput()if err != nil {log.Fatalf("cmd.Run() failed with %s\n", err)}return string(out)
}func (e *Exporter) HitMirthRestApisAndUpdateMetrics(channelIdNameMap map[string]string, ch chan<- prometheus.Metric) {req, err := http.NewRequest("GET", e.mirthEndpoint+channelStatsApi, nil)if err != nil {log.Fatal(err)}req.SetBasicAuth(e.mirthUsername, e.mirthPassword)resp, err := client.Do(req)if err != nil {log.Fatal(err)}body, err := ioutil.ReadAll(resp.Body)resp.Body.Close()if err != nil {log.Fatal(err)}var channelStatsList ChannelStatsListerr = xml.Unmarshal(body, &channelStatsList)if err != nil {log.Fatal(err)}for i := 0; i < len(channelStatsList.Channels); i++ {channelName := channelIdNameMap[channelStatsList.Channels[i].ChannelId]channelReceived, _ := strconv.ParseFloat(channelStatsList.Channels[i].Received, 64)ch <- prometheus.MustNewConstMetric(messagesReceived, prometheus.GaugeValue, channelReceived, channelName,)channelSent, _ := strconv.ParseFloat(channelStatsList.Channels[i].Sent, 64)ch <- prometheus.MustNewConstMetric(messagesSent, prometheus.GaugeValue, channelSent, channelName,)channelError, _ := strconv.ParseFloat(channelStatsList.Channels[i].Error, 64)ch <- prometheus.MustNewConstMetric(messagesErrored, prometheus.GaugeValue, channelError, channelName,)channelFiltered, _ := strconv.ParseFloat(channelStatsList.Channels[i].Filtered, 64)ch <- prometheus.MustNewConstMetric(messagesFiltered, prometheus.GaugeValue, channelFiltered, channelName,)var count1 = get_sys_file_node_count()channelQueued, err := strconv.ParseFloat(count1[:len(count1)-1], 64)if err != nil {log.Println(err)}ch <- prometheus.MustNewConstMetric(messagesQueued, prometheus.GaugeValue, channelQueued, channelName,)}// log.Println("Endpoint scraped")
}func main() {err := godotenv.Load()if err != nil {log.Println("Error loading .env file, assume env variables are set.")}flag.Parse()mirthEndpoint := os.Getenv("MIRTH_ENDPOINT")mirthUsername := os.Getenv("MIRTH_USERNAME")mirthPassword := os.Getenv("MIRTH_PASSWORD")exporter := NewExporter(mirthEndpoint, mirthUsername, mirthPassword)prometheus.MustRegister(exporter)http.Handle(*metricsPath, promhttp.Handler())http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte(`<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Node Exporter</title><style>body {font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,Liberation Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;margin: 0;
}
header {background-color: #e6522c;color: #fff;font-size: 1rem;padding: 1rem;
}
main {padding: 1rem;
}
label {display: inline-block;width: 0.5em;
}</style></head><body><header><h1>Node Exporter</h1></header><main><h2>Prometheus Node Exporter</h2><div>Version: (version=1.8.2, branch=HEAD, revision=f1e0e8360aa60b6cb5e5cc1560bed348fc2c1895)</div><div><ul><li><a href="/metrics1">Metrics</a></li></ul></div></main></body>
</html>`))
})http.HandleFunc("/api/channels/idsAndNames", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte(`<map><entry><string>101af57f-f26c-40d3-86a3-309e74b93512</string><string>Send-Email-Notification</string></entry>
</map>`))})http.HandleFunc("/api/channels/statistics", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte(`<list><channelStatistics><serverId>c5e6a736-0e88-46a7-bf32-5b4908c4d859</serverId><channelId>101af57f-f26c-40d3-86a3-309e74b93512</channelId><received>0</received><sent>0</sent><error>0</error><filtered>0</filtered><queued>0</queued></channelStatistics>
</list>`))})log.Fatal(http.ListenAndServe(*listenAddress, nil))
}