prometheus.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. package middleware
  2. import (
  3. "context"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. "github.com/prometheus/client_golang/prometheus"
  6. "github.com/prometheus/client_golang/prometheus/promhttp"
  7. "gxt-api-frame/library/logger"
  8. "io/ioutil"
  9. "net/http"
  10. "strconv"
  11. "time"
  12. )
  13. var defaultMetricPath = "/metrics"
  14. var reqCnt = &Metric{
  15. ID: "reqCnt",
  16. Name: "requests_total",
  17. Description: "How many HTTP requests processed, partitioned by status code and HTTP method.",
  18. Type: "counter_vec",
  19. Args: []string{"code", "method", "host", "url"}}
  20. var reqDur = &Metric{
  21. ID: "reqDur",
  22. Name: "request_duration_seconds",
  23. Description: "The HTTP request latencies in seconds.",
  24. Type: "histogram_vec",
  25. Args: []string{"code", "method", "url"},
  26. }
  27. var resSz = &Metric{
  28. ID: "resSz",
  29. Name: "response_size_bytes",
  30. Description: "The HTTP response sizes in bytes.",
  31. Type: "summary"}
  32. var reqSz = &Metric{
  33. ID: "reqSz",
  34. Name: "request_size_bytes",
  35. Description: "The HTTP request sizes in bytes.",
  36. Type: "summary"}
  37. var standardMetrics = []*Metric{
  38. reqCnt,
  39. reqDur,
  40. resSz,
  41. reqSz,
  42. }
  43. type Metric struct {
  44. MetricCollector prometheus.Collector
  45. ID string
  46. Name string
  47. Description string
  48. Type string
  49. Args []string
  50. }
  51. type RequestCounterURLLabelMappingFn func(c *ghttp.Request) string
  52. type Prometheus struct {
  53. reqCnt *prometheus.CounterVec
  54. reqDur *prometheus.HistogramVec
  55. reqSz, resSz prometheus.Summary
  56. router *ghttp.Server
  57. listenAddress string
  58. Ppg PrometheusPushGateway
  59. MetricsList []*Metric
  60. MetricsPath string
  61. ReqCntURLLabelMappingFn RequestCounterURLLabelMappingFn
  62. // gin.Context string to use as a prometheus URL label
  63. URLLabelFromContext string
  64. }
  65. type PrometheusPushGateway struct {
  66. PushIntervalSeconds time.Duration
  67. PushGatewayURL string
  68. MetricsURL string
  69. Job string
  70. }
  71. // NewPrometheus generates a new set of metrics with a certain subsystem name
  72. func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus {
  73. var metricsList []*Metric
  74. if len(customMetricsList) > 1 {
  75. panic("Too many args. NewPrometheus( string, <optional []*Metric> ).")
  76. } else if len(customMetricsList) == 1 {
  77. metricsList = customMetricsList[0]
  78. }
  79. for _, metric := range standardMetrics {
  80. metricsList = append(metricsList, metric)
  81. }
  82. p := &Prometheus{
  83. MetricsList: metricsList,
  84. MetricsPath: defaultMetricPath,
  85. ReqCntURLLabelMappingFn: func(c *ghttp.Request) string {
  86. return c.Request.URL.Path
  87. },
  88. }
  89. p.registerMetrics(subsystem)
  90. return p
  91. }
  92. // Use adds the middleware to a gin engine.
  93. func (p *Prometheus) Use(e *ghttp.Server) {
  94. e.Use(p.HandlerFunc())
  95. p.SetMetricsPath(e)
  96. }
  97. func (p *Prometheus) getMetrics() []byte {
  98. response, _ := http.Get(p.Ppg.MetricsURL)
  99. defer response.Body.Close()
  100. body, _ := ioutil.ReadAll(response.Body)
  101. return body
  102. }
  103. // NewMetric associates prometheus.Collector based on Metric.Type
  104. func NewMetric(m *Metric, subsystem string) prometheus.Collector {
  105. var metric prometheus.Collector
  106. switch m.Type {
  107. case "counter_vec":
  108. metric = prometheus.NewCounterVec(
  109. prometheus.CounterOpts{
  110. Subsystem: subsystem,
  111. Name: m.Name,
  112. Help: m.Description,
  113. },
  114. m.Args,
  115. )
  116. case "counter":
  117. metric = prometheus.NewCounter(
  118. prometheus.CounterOpts{
  119. Subsystem: subsystem,
  120. Name: m.Name,
  121. Help: m.Description,
  122. },
  123. )
  124. case "gauge_vec":
  125. metric = prometheus.NewGaugeVec(
  126. prometheus.GaugeOpts{
  127. Subsystem: subsystem,
  128. Name: m.Name,
  129. Help: m.Description,
  130. },
  131. m.Args,
  132. )
  133. case "gauge":
  134. metric = prometheus.NewGauge(
  135. prometheus.GaugeOpts{
  136. Subsystem: subsystem,
  137. Name: m.Name,
  138. Help: m.Description,
  139. },
  140. )
  141. case "histogram_vec":
  142. metric = prometheus.NewHistogramVec(
  143. prometheus.HistogramOpts{
  144. Subsystem: subsystem,
  145. Name: m.Name,
  146. Help: m.Description,
  147. },
  148. m.Args,
  149. )
  150. case "histogram":
  151. metric = prometheus.NewHistogram(
  152. prometheus.HistogramOpts{
  153. Subsystem: subsystem,
  154. Name: m.Name,
  155. Help: m.Description,
  156. },
  157. )
  158. case "summary_vec":
  159. metric = prometheus.NewSummaryVec(
  160. prometheus.SummaryOpts{
  161. Subsystem: subsystem,
  162. Name: m.Name,
  163. Help: m.Description,
  164. },
  165. m.Args,
  166. )
  167. case "summary":
  168. metric = prometheus.NewSummary(
  169. prometheus.SummaryOpts{
  170. Subsystem: subsystem,
  171. Name: m.Name,
  172. Help: m.Description,
  173. },
  174. )
  175. }
  176. return metric
  177. }
  178. func (p *Prometheus) registerMetrics(subsystem string) {
  179. for _, metricDef := range p.MetricsList {
  180. metric := NewMetric(metricDef, subsystem)
  181. if err := prometheus.Register(metric); err != nil {
  182. logger.Errorf(context.Background(), "%s could not be registered in Prometheus", metricDef.Name)
  183. }
  184. switch metricDef {
  185. case reqCnt:
  186. p.reqCnt = metric.(*prometheus.CounterVec)
  187. case reqDur:
  188. p.reqDur = metric.(*prometheus.HistogramVec)
  189. case resSz:
  190. p.resSz = metric.(prometheus.Summary)
  191. case reqSz:
  192. p.reqSz = metric.(prometheus.Summary)
  193. }
  194. metricDef.MetricCollector = metric
  195. }
  196. }
  197. // SetMetricsPath set metrics paths
  198. func (p *Prometheus) SetMetricsPath(s *ghttp.Server) {
  199. s.BindHandler(p.MetricsPath, prometheusHandler())
  200. }
  201. func prometheusHandler() ghttp.HandlerFunc {
  202. h := promhttp.Handler()
  203. return func(c *ghttp.Request) {
  204. h.ServeHTTP(c.Response.Writer, c.Request)
  205. }
  206. }
  207. // HandlerFunc defines handler function for middleware
  208. func (p *Prometheus) HandlerFunc() ghttp.HandlerFunc {
  209. return func(c *ghttp.Request) {
  210. if c.Request.URL.Path == p.MetricsPath || c.Request.URL.Path == "/favicon.ico" {
  211. c.Middleware.Next()
  212. return
  213. }
  214. start := time.Now()
  215. reqSz := computeApproximateRequestSize(c.Request)
  216. c.Middleware.Next()
  217. status := strconv.Itoa(c.Response.Status)
  218. elapsed := float64(time.Since(start)) / float64(time.Second)
  219. resSz := float64(c.Response.BufferLength())
  220. url := p.ReqCntURLLabelMappingFn(c)
  221. if len(p.URLLabelFromContext) > 0 {
  222. u := c.Get(p.URLLabelFromContext)
  223. url = u.String()
  224. }
  225. p.reqDur.WithLabelValues(status, c.Request.Method, url).Observe(elapsed)
  226. p.reqCnt.WithLabelValues(status, c.Request.Method, c.Request.Host, url).Inc()
  227. p.reqSz.Observe(float64(reqSz))
  228. p.resSz.Observe(resSz)
  229. }
  230. }
  231. func computeApproximateRequestSize(r *http.Request) int {
  232. s := 0
  233. if r.URL != nil {
  234. s = len(r.URL.Path)
  235. }
  236. s += len(r.Method)
  237. s += len(r.Proto)
  238. for name, values := range r.Header {
  239. s += len(name)
  240. for _, value := range values {
  241. s += len(value)
  242. }
  243. }
  244. s += len(r.Host)
  245. if r.ContentLength != -1 {
  246. s += int(r.ContentLength)
  247. }
  248. return s
  249. }