的使用go_使用 Go 开发 Prometheus Exporter

f96d21f9004b1dd32d7c981df444b754.pngExporter 是 Prometheus 监控的核心,如果你遇到一些应用不存在相应的 Exporter,那么我们可以自己去编写 Exporter。下面我们简单介绍如何使用 Golang 来快速编写一个 Exporter。

1. 安装 GO 和依赖包

按照 https://golang.org/doc/install 上的步骤进行安装配置 GO 环境,创建一个名为 my_first_exporter 的文件夹。

$ go mod init my_first_exporter 
$ go get github.com/prometheus/client_golang 
$ go get github.com/joho/godotenv
--> creates go.mod file
--> Installs dependency into the go.mod file

2. 创建入口点和导入依赖包

package main

import (
 "github.com/joho/godotenv"
 "github.com/prometheus/client_golang/prometheus"
 "github.com/prometheus/client_golang/prometheus/promhttp"
)

3. 创建 main() 函数

func main()

4. 添加 prometheus metrics 端点,并在某个服务端口上监听

func main() {
   http.Handle("/metrics", promhttp.Handler())
   log.Fatal(http.ListenAndServe(":9101", nil))
}

5. 使用 curl 请求外部服务接口

比如我们这里监控的应用程序是 MirthConnect,所以我需要进行两个 API 接口调用:

  • 获取 channel 统计数据
  • 获取 channel id 和名称映射
curl -k --location --request GET 'https://apihost/api/channels/statistics' \
--user admin:admin

curl -k --location --request GET 'https://apihost/api/channels/idsAndNames' \
--user admin:admin

6. 将 curl 调用转换为 go http 调用,并解析结果

如果你是 Go 新手,这应该是最困难的一步。对于我这里的例子,端点返回的是 XML 格式的数据,所以我必须用 "encoding/xml" 包来反序列化 XML。转换成功后意味着我的 GO 程序可以执行和 curl 命令一样的 API 调用。

7. 声明 Prometheus metrics

在 Prometheus 中,每个 metric 指标都由以下几个部分组成:metric name/metric label values/metric help text/metric type/measurement ,例如:

Example:
# HELP promhttp_metric_handler_requests_total Total number of scrapes by HTTP status code.
# TYPE promhttp_metric_handler_requests_total counter
promhttp_metric_handler_requests_total{code=”200"} 1.829159e+06
promhttp_metric_handler_requests_total{code=”500"} 0
promhttp_metric_handler_requests_total{code=”503"} 0

对于应用 scrapers,我们将定义 Prometheus metrics 描述信息,其中包括 metric 名称、metric label 标签以及 metric 帮助信息。

messagesReceived = prometheus.NewDesc(
 prometheus.BuildFQName(namespace, "", "messages_received_total"),
 "How many messages have been received (per channel).",
 []string{"channel"}, nil,
)

8. 定义一个结构体实现 Prometheus 的 Collector 接口

Prometheus 的 client 库提供了实现自定义 Exportor 的接口,Collector 接口定义了两个方法 Describe 和 Collect,实现这两个方法就可以暴露自定义的数据:

  • Describe(chan
  • Collect(chan

如下所示:

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 {
}func (e *Exporter) Collect(ch chan {
}

9. 在 Describe 函数中,把第7步的 metric 描述信息发送给它

func (e *Exporter) Describe(ch chan {
 ch  ch  ch  ch  ch  ch }

10. 将接口调用逻辑从第6步移到 Collect 函数中

直接将采集的数据发送到 prometheus.Metric 通道中。

func (e *Exporter) Collect(ch chan {
 channelIdNameMap, err := e.LoadChannelIdNameMap()if err != nil {
  ch    up, prometheus.GaugeValue, 0,
  )
  log.Println(err)return
 }
 ch   up, prometheus.GaugeValue, 1,
 )
 e.HitMirthRestApisAndUpdateMetrics(channelIdNameMap, ch)
}

当执行 api 调用时,确保使用prometheus.MustNewConstMetric(prometheus.Desc, metric type, measurement)发送测量值,如果你需要传入额外的标签,可以像下面这样在参数列表的后面加入:

channelError, _ := strconv.ParseFloat(channelStatsList.Channels[i].Error, 64)
ch  messagesErrored, prometheus.GaugeValue, channelError, channelName,
)

11. 在 main 函数中声明 exporter

exporter := NewExporter(mirthEndpoint, mirthUsername, mirthPassword)
prometheus.MustRegister(exporter)

到这里其实这个 Exporter 就可以使用了,每次访问 metrics 路由的时候,它会执行 api 调用,并以 Prometheus Text 文本格式返回数据。下面的步骤主要是方便部署了。

12. 将硬编码的 api 路径放到 flag 中

前面我们硬编码了好多参数,比如应用程序的网址、metrics 路由地址以及 exporter 端口,我们可以通过从命令行参数中来解析这些值使程序更加灵活。

var (
listenAddress = flag.String("web.listen-address", ":9141",
 "Address to listen on for telemetry")
metricsPath = flag.String("web.telemetry-path", "/metrics",
 "Path under which to expose metrics")
)
func main() {
   flag.Parse()
   ...
   http.Handle(*metricsPath, promhttp.Handler())
   log.Fatal(http.ListenAndServe(*listenAddress, nil))
}

13. 将凭证放入环境变量

如果应用端点改变了或者登录凭证改变了怎么办?我们可以从环境变量中来加载这些数据,在这个例子中,我们使用 godotenv 这个包来帮助将变量值存储在本地的一个目录中:

import (
  "os"
)
func main() {
 err := godotenv.Load()
 if err != nil {
  log.Println("Error loading .env file, assume env variables are set.")
 }
 mirthEndpoint := os.Getenv("MIRTH_ENDPOINT")
 mirthUsername := os.Getenv("MIRTH_USERNAME")
 mirthPassword := os.Getenv("MIRTH_PASSWORD")
}

整个 Exporter 完整的代码如下所示:

package main

import (
 "crypto/tls"
 "encoding/xml"
 "flag"
 "io/ioutil"
 "log"
 "net/http"
 "os"
 "strconv"

 "github.com/joho/godotenv"
 "github.com/prometheus/client_golang/prometheus"
 "github.com/prometheus/client_golang/prometheus/promhttp"
)

/*101af57f-f26c-40d3-86a3-309e74b93512Send-Email-Notification
*/
type ChannelIdNameMap struct {
 XMLName xml.Name       `xml:"map"`
 Entries []ChannelEntry `xml:"entry"`
}
type ChannelEntry struct {
 XMLName xml.Name `xml:"entry"`
 Values  []string `xml:"string"`
}

/*c5e6a736-0e88-46a7-bf32-5b4908c4d859101af57f-f26c-40d3-86a3-309e74b9351200000
*/
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"

var (
 tr = &http.Transport{
  TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
 }
 client = &http.Client{Transport: tr}

 listenAddress = flag.String("web.listen-address", ":9141",
  "Address to listen on for telemetry")
 metricsPath = flag.String("web.telemetry-path", "/metrics",
  "Path under which to expose metrics")

 // 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 are currently queued (per channel).",
  []string{"channel"}, nil,
 )
 messagesSent = prometheus.NewDesc(
  prometheus.BuildFQName(namespace, "", "messages_sent_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 errored (per channel).",
  []string{"channel"}, nil,
 )
)

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 {
 ch  ch  ch  ch  ch  ch }func (e *Exporter) Collect(ch chan {
 channelIdNameMap, err := e.LoadChannelIdNameMap()if err != nil {
  ch    up, prometheus.GaugeValue, 0,
  )
  log.Println(err)return
 }
 ch   up, prometheus.GaugeValue, 1,
 )
 e.HitMirthRestApisAndUpdateMetrics(channelIdNameMap, ch)
}func (e *Exporter) LoadChannelIdNameMap() (map[string]string, error) {// Create the map of channel id to names
 channelIdNameMap := make(map[string]string)
 req, err := http.NewRequest("GET", e.mirthEndpoint+channelIdNameApi, nil)if err != nil {return nil, err
 }// This one line implements the authentication required for the task.
 req.SetBasicAuth(e.mirthUsername, e.mirthPassword)// Make request and show output.
 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
 }// fmt.Println(string(body))// we initialize our arrayvar channelIdNameMapXML ChannelIdNameMap// we unmarshal our byteArray which contains our// xmlFiles content into 'users' which we defined above
 err = xml.Unmarshal(body, &channelIdNameMapXML)if err != nil {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 (e *Exporter) HitMirthRestApisAndUpdateMetrics(channelIdNameMap map[string]string, ch chan {// Load channel stats
 req, err := http.NewRequest("GET", e.mirthEndpoint+channelStatsApi, nil)if err != nil {
  log.Fatal(err)
 }// This one line implements the authentication required for the task.
 req.SetBasicAuth(e.mirthUsername, e.mirthPassword)// Make request and show output.
 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)
 }// fmt.Println(string(body))// we initialize our arrayvar channelStatsList ChannelStatsList// we unmarshal our byteArray which contains our// xmlFiles content into 'users' which we defined above
 err = 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    messagesReceived, prometheus.GaugeValue, channelReceived, channelName,
  )
  channelSent, _ := strconv.ParseFloat(channelStatsList.Channels[i].Sent, 64)
  ch    messagesSent, prometheus.GaugeValue, channelSent, channelName,
  )
  channelError, _ := strconv.ParseFloat(channelStatsList.Channels[i].Error, 64)
  ch    messagesErrored, prometheus.GaugeValue, channelError, channelName,
  )
  channelFiltered, _ := strconv.ParseFloat(channelStatsList.Channels[i].Filtered, 64)
  ch    messagesFiltered, prometheus.GaugeValue, channelFiltered, channelName,
  )
  channelQueued, _ := strconv.ParseFloat(channelStatsList.Channels[i].Queued, 64)
  ch    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(`Mirth Channel Exporter

Mirth Channel Exporter

 + *metricsPath + `'>Metrics


             `))
 })
 log.Fatal(http.ListenAndServe(*listenAddress, nil))
}

14. 编写一个 Makefile 文件,方便在不同平台上快速构建

Makefile 可以让你在开发过程中省去很多多余的操作,比如我们要构建多个平台的构建程序,可以创建如下所示的 Makefile 文件。

linux:
   GOOS=linux GOARCH=amd64 go build
mac:
   GOOS=darwin GOARCH=amd64 go build

只要调用 make macmake linux 命令就可以看到不同的可执行文件出现。

15. 编写一个 service 文件,将这个 go 应用作为守护进程运行

我们可以为这个 Exporter 编写一个 service 文件或者 Dockerfile 文件来管理该应用,比如这里我们直接在 Centos 7 上用 systemd 来管理该应用。这可以编写一个如下所示的 service 文件:

[Unit]
Description=mirth channel exporter
After=network.target
StartLimitIntervalSec=0
[Service]
Type=simple
Restart=always
RestartSec=1
WorkingDirectory=/mirth/mirthconnect
EnvironmentFile=/etc/sysconfig/mirth_channel_exporter
ExecStart=/mirth/mirthconnect/mirth_channel_exporter

[Install]
WantedBy=multi-user.target

到这里就完成了使用 Golang 编写一个简单的 Prometheus Exporter。

原文链接:https://medium.com/teamzerolabs/15-steps-to-write-an-application-prometheus-exporter-in-go-9746b4520e26

6018d8180b147c16c934ec08ebfcc590.gif 点击屏末  | 即刻学习a7ec5ad122542c5927cbc0dc5bd9d5f7.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/506452.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flask 获取前端form内容_flask 项目中使用 bootstrapFileInput(进阶篇)

bootstrap 为 flask 使用人员提供了一个非常优美且有效的前端页面组件,但是完美之处还存在些许缺陷,比如文件的上传功能.而 bootstrap-fileinput 是基于 bootstrap 的控件,非常完美的填补了这个空缺.注意: 本文是基于 bootstrap-fileinput v4.4.2. github 地址: https://github…

phpstudy能安装不带mysql的么_装了phpstudy还需要装mysql吗

phpStudy是一个PHP调试环境的程序集成包。该程序包集成最新的ApachePHPMySQLphpMyAdminZendOptimizer,一次性安装,无须配置即可使用,是非常方便、好用的PHP调试环境。该程序不仅包括PHP调试环境,还包括了开发工具、开发手册等。对…

mysql 5.1.53_mysql 5.1.53免安装版的优化配备和精简

mysql 5.1.53免安装版的优化配置和精简[摘要]MySQL是一种关联数据库管理系统,关联数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内。这样就增加了速度并提高了灵活性。本文介绍mysql-noinstall.zip免安装版的优化配置和精简。欢迎大家阅…

python牛顿法寻找极值_python使用梯度下降和牛顿法寻找Rosenbrock函数最小值实例...

Rosenbrock函数的定义如下:其函数图像如下:我分别使用梯度下降法和牛顿法做了寻找Rosenbrock函数的实验。梯度下降梯度下降的更新公式:图中蓝色的点为起点,橙色的曲线(实际上是折线)是寻找最小值点的轨迹,终点(最小值点…

mysql报11004_使用mysqli扩展技术查看服务器连接错误报告的方法

使用mysqli扩展技术查看服务器连接错误报告的方法作为PHP的黄金搭档MySQL数据库,在PHP项目开发过程中有着举足轻重的作用,这不仅因为MySQL是完全免费的,而且和PHP一样都是完全跨平台的。但在实际项目开发过程中,PHP与MySQL数据库产…

_Linux进程信号详解

信号是什么一个信号就是一条小消息,它通知进程系统中发生了一个某种类型的事件信号是多种多样的,并且一个信号对应一个事件,这样才能做到收到一个信号后,知道到底是一个什么事件,应该如何处理(但是要保证必…

java乘法表_Java中四种9*9乘法表的实现方式(附代码)

前言: 初学java,实现99乘法表是必学必会的内容。需求 : 分别写出上下左右,对应四个角的乘法表。思路: 可以先打印出*星星,形成一个直角三角形,然后再替换成乘法公式。代码如下:public class Demo {public static void main(String[] args) {for (int i 1; i <5 ; i) {for (…

excel表格不够怎么添加_Excel表格水印,你以前好象添加错了!

为excel表格添加水印&#xff0c;通常是使用插入-艺术字来完成。但这样做有一个很大的问题&#xff1a;如果表格有很多页&#xff0c;就需要添加N多个艺术字&#xff0c;太麻烦了。其实有一个超简单的批量设置方法&#xff0c;再多页也可以一次性设置。需要添加水印的Excel表格…

电脑手机wifi互传文件_安卓手机文件互传

怎么不借用第三方工具&#xff0c;安卓手机实现相互文件快传呢&#xff1f;苹果&#xff1a;首先不用多说&#xff0c;苹果可以使用Air Drop功能&#xff0c;苹果全家桶可以无障碍互传。长期以来&#xff0c;除开微信和QQ&#xff0c;不同品牌安卓手机互传文件依靠的途径只有蓝…

传统的线性降维方法效果不佳。_10分钟数据降维入门

1. 前言在硕士期间学习研究了数据降维相关的知识&#xff0c;阅读了一些相关文章&#xff0c;也断断续续在知乎上写了一些数据降维的入门级文章&#xff0c;收获了一些小伙伴的赞同&#xff0c;并在GitHub上开源了一些特征抽取算法的源代码&#xff0c;获得了的1.1kstar。因为在…

嵌入式开发网络配置——windows连热点,开发板和电脑网线直连

目录 电脑 WiFi 上网&#xff0c;开发板和电脑直连 使用场景 设置VMware虚拟机的网络配置 Ubuntu设置——版本18.04 ​编辑 windows设置 开发板设置 原因&#xff1a;虚拟机Linux移植可执行程序到开发板失败 最后发现虚拟机的Linuxping不通开发板 下面是我的解决方法 …

java ajax data_jquery ajax 方法中传递的data参数,如何在java类中获取

展开全部var params"username""1";$.ajax({type : "POST", //数据发送方式url : "../servlet/clearCache",dataType : "json", //接受数据格式 (这里有很多,常用的有html,xml,js,json)data:params,//datenew Date(), 要传递…

python环境变量配置_Python的安装、认识、配置环境变量以及helloworld打印的两种方式

Python的安装、认识、配置环境变量以及helloworld打印的两种方式 一、 安装和配置环境变量 首先我们去到Python的官方下载地址根据自己的电脑机型来下载最新的python安装包&#xff0c;网址是https://www.python.org/downloads/ 。 或者点击这里跳转 。 下载之后可以去https://…

java 报表程序_java 报表

http://www.codeceo.com/article/8-java-graph.html前段时间我们为大家分享过一些最常用的Java图表应用和Android图表应用&#xff0c;无论是在PC平台上还是移动平台上&#xff0c;图表和报表功能都是不可或缺的。本文推荐了8款最精彩实用的Java图表应用&#xff0c;大部分图表…

excel跨多个表格求和_看完财务同事用Excel函数完成的进销存报表,老板惊呆了...

有仓库、有商品的地方都离不开进销存软件&#xff0c;有花钱买的、也有花大力气自已找人订制的。网上下载破解版&#xff1f;真的不可靠&#xff0c;万一出现什么问题&#xff0c;你们公司将面临信息损坏或丢失的风险&#xff01;其实&#xff0c;如果只是小规模的库存商品管理…

苹果自带相册打马赛克_如果你用苹果手机!学会这3个技巧,就能让手机变得更加好用...

如果你用苹果手机&#xff01;这3个技巧一定要学会&#xff0c;能让手机变得更好用现在手机中的黑科技功能越来越多&#xff0c;就拿iPhone手机来说&#xff0c;很多朋友选择这款手机不仅是因为拥有非常流畅的系统&#xff0c;手机中自带的黑科技功能也是大家选择它的原因。今天…

给与 x 距离不超过 d 的点权值 a_一年级不上网课,妈妈陪孩子一起来完成三单元各课练习及单元卷吧...

部编一年级下册第5课小公鸡和小鸭子课后练习题一、将下列字的音节补充完整。k____ x____ zh____ 块 行 捉 二、选择加点字的正确读音&#xff0c;画“√”。忽然(rn rnɡ)  大喊(hǎn hǎnɡ)上身(shēn sēn)  死去(sǐ shǐ)三、看拼音&#xff0c;写字词。1小明说(&am…

java games_Java Me Games

在GWT的文档里说&#xff0c;大致上CSS的命名规则是这样的"[project]-[widget]&#xff0c;比如gwt-Button&#xff0c;你可以在CSS里定义如下&#xff1a;.gwt-Button{font-size:150%;}但是这样的说明是不充分的&#xff0c;所以这里有必要把他真正的样式表来列一下&…

java timestamp 转换_Java:String和Date、Timestamp之间的转换

一、String与Date(java.util.Date)互转1.1 String -> DateJava代码 String dateStr "2010/05/04 12:34:23";Date date new Date();//注意format的格式要与日期String的格式相匹配DateFormat sdf new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");try {…

bugku 杂项 就五层你能解开吗_9.65米解放龙V杂项危险废物厢式运输车62

亲爱的,点击上方蓝字关注我吧9类危险废物厢式运输车解放龙V2.0半高顶半浮驾驶室底盘参数&#xff1a;龙V2.0半高顶半浮驾驶室、锡柴225马力&#xff0c;6.6排量&#xff0c;164千瓦&#xff0c;百公里耗油30.3L、陕齿8JS85E、435升级冲焊桥(速比4.444)、275/80R22.5-18PR层级、…