add prometheus

This commit is contained in:
bryanqiu 2022-12-19 21:07:54 +08:00
parent 92b821a12d
commit 900d2e9789
5 changed files with 161 additions and 32 deletions

6
api.go
View File

@ -13,7 +13,7 @@ import (
var ( var (
validate = validator.New() validate = validator.New()
requestLogidGetter = defaultLogidGetter requestLogidGetter = defaultLogidGetter
handlerMapper = map[string]Handler{} requestHandlerMapper = map[string]Handler{}
errCodeUnknownError = -10000 errCodeUnknownError = -10000
errCodeParameterError = -20000 errCodeParameterError = -20000
errCodeUnimplementApi = -20401 errCodeUnimplementApi = -20401
@ -47,7 +47,9 @@ func NewEngine() *Engine {
func (e *Engine) POST(uri string, handler Handler) { func (e *Engine) POST(uri string, handler Handler) {
h := handlerWrapper(handler.HandlerFunc()) h := handlerWrapper(handler.HandlerFunc())
handlerMapper[fmt.Sprintf("%v", h)] = handler hkey := fmt.Sprintf("%v", h)
requestHandlerMapper[hkey] = handler
log.Infof("api handler [%s] registor success", hkey)
e.Engine.POST(uri, h) e.Engine.POST(uri, h)
} }

View File

@ -26,6 +26,7 @@ type Context struct {
httpstatus int httpstatus int
starttime time.Time starttime time.Time
output interface{} output interface{}
errcode int
} }
func New(c *gin.Context) *Context { func New(c *gin.Context) *Context {
@ -146,28 +147,62 @@ func (c *Context) RESULT(output interface{}) error {
cost := time.Now().Sub(c.starttime).Milliseconds() cost := time.Now().Sub(c.starttime).Milliseconds()
noticeStr := fmt.Sprintf("cost: %dms, output: '%s'", cost, string(b)) noticeStr := fmt.Sprintf("cost: %dms, output: '%s'", cost, string(b))
log.NoticefWithDepth(calldepth, noticeStr) log.NoticefWithDepth(calldepth, noticeStr)
//// metric ////////////////////////////////////////
c.recordMetric()
}(&output) }(&output)
if _, ok := t.FieldByName("ErrCode"); !ok {
errstr := "Result MUST have 'ErrCode' field"
output = BaseWithErrCodeOutput{errCodeUnknownError, errstr}
c.ShapeOutput(output)
c.IndentedJSON(c.httpstatus, output)
return errors.New(errstr)
}
if _, ok := t.FieldByName("ErrMsg"); !ok { if _, ok := t.FieldByName("ErrMsg"); !ok {
errstr := "Result MUST have 'ErrMsg' field" errstr := "Result MUST have 'ErrMsg' field"
output = BaseWithErrCodeOutput{errCodeUnknownError, errstr} output = BaseWithErrCodeOutput{errCodeUnknownError, errstr}
c.ShapeOutput(output) c.shapeOutput(output)
c.errcode = reflect.ValueOf(output).FieldByName("ErrCode").GetInt()
c.IndentedJSON(c.httpstatus, output) c.IndentedJSON(c.httpstatus, output)
return errors.New(errstr) return errors.New(errstr)
} }
c.ShapeOutput(output) if _, ok := t.FieldByName("ErrCode"); !ok {
errstr := "Result MUST have 'ErrCode' field"
output = BaseWithErrCodeOutput{errCodeUnknownError, errstr}
c.shapeOutput(output)
c.errcode = reflect.ValueOf(output).FieldByName("ErrCode").GetInt()
c.IndentedJSON(c.httpstatus, output)
return errors.New(errstr)
}
c.shapeOutput(output)
c.errcode = reflect.ValueOf(output).FieldByName("ErrCode").GetInt()
c.IndentedJSON(c.httpstatus, output) c.IndentedJSON(c.httpstatus, output)
return nil return nil
} }
func (c *Context) ShapeOutput(o interface{}) { func (c *Context) RESULT_ERROR(eno int, err string) error {
if c.resultentry == "" {
c.resultentry = "RESULT_ERROR"
}
result := BaseWithErrCodeOutput{eno, err}
c.errcode = eno
return c.RESULT(result)
}
func (c *Context) RESULT_PARAMETER_ERROR(err string) error {
if c.resultentry == "" {
c.resultentry = "RESULT_PARAMETER_ERROR"
}
return c.RESULT_ERROR(errCodeParameterError, err)
}
func (c *Context) ApiHandler() Handler {
h := c.Context.Handler()
hkey := fmt.Sprintf("%v", h)
if handler, ok := requestHandlerMapper[hkey]; ok {
return handler
}
log.Fatalf("api handler [%s] not found", hkey)
return nil
}
func (c *Context) shapeOutput(o interface{}) {
if c.output == nil { if c.output == nil {
return return
} }
@ -183,26 +218,23 @@ func (c *Context) ShapeOutput(o interface{}) {
return return
} }
func (c *Context) ApiHandler() Handler { func (c *Context) recordMetric() {
h := c.Context.Handler() if metricApiCounter == nil ||
if handler, ok := handlerMapper[fmt.Sprintf("%v", h)]; ok { metricApiSummary == nil {
return handler return
}
return nil
} }
var (
h = c.ApiHandler()
api = h.Name()
cost = time.Now().Sub(c.starttime).Milliseconds()
appid = c.AppId
errcode = c.errcode
counter = metricApiCounter.WithLabelValues(api, errcode, appid)
summary = metricApiSummary.WithLabelValues(api, errcode, appid)
)
func (c *Context) RESULT_ERROR(eno int, err string) error { // Metric 1. api request counter
if c.resultentry == "" { counter.Inc()
c.resultentry = "RESULT_ERROR" // Metric 2. api request cost/latency
} summary.Observe(cost)
result := BaseWithErrCodeOutput{eno, err}
return c.RESULT(result)
}
func (c *Context) RESULT_PARAMETER_ERROR(err string) error {
if c.resultentry == "" {
c.resultentry = "RESULT_PARAMETER_ERROR"
}
return c.RESULT_ERROR(errCodeParameterError, err)
} }

79
prometheus.go Normal file
View File

@ -0,0 +1,79 @@
package api
import (
"fmt"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/client_golang/prometheus/push"
)
var (
MetricExporterPusher = func() {}
MetricExporterHandler Handler = nil
metricRegistry *prometheus.Registry = nil
metricApiCounter *prometheus.CounterVec = nil
metricApiSummary *prometheus.SummaryVec = nil
)
type metricExporterHandler struct {
name string
promhttpHandler http.Handler
}
func (m *metricExporterHandler) Name() string {
return m.apiName
}
func (m *metricExporterHandler) HandlerFunc() HandlerFunc {
return func(c *Context) (err error) {
m.promhttpHandler.ServeHTTP(c.ResponseWriter, c.Request)
return nil
}
}
func InitMetrics() {
metricApiCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "api_requests_total",
Help: "How many HTTP requests processed",
},
[]string{"api", "errcode", "appid"},
)
metricApiSummary = prometheus.NewSummaryVec(
prometheus.SummaryOpts{
Name: "api_requests_summary",
Help: "The api request summary of cost.",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{"api", "errcode", "appid"},
)
metricRegistry = prometheus.NewRegistry()
opt := promhttp.HandlerOpts{Registry: metricRegistry}
metricRegistry.MustRegister(metricApiCounter)
metricRegistry.MustRegister(metricApiSummary)
}
func SetupMetricsExporterHandler(apiname string) {
if apiname == "" {
apiname = "premetheus_metrics_exporter"
}
MetricExporterHandler = &metricExporterHandler{
name: apiname,
promhttpHandler: promhttp.HandlerFor(metricRegistry, opt),
}
}
func SetupMetricsExporterPusher(pushgateway string, jobname string) {
if jobname == "" {
jobname = fmt.Sprintf("api-metrics-job-%s", getExeFilename())
}
var pusher = push.New(pushgateway, jobname).Gatherer(metricRegistry)
MetricExporterPusher = func() {
pusher.Push()
}
}

View File

@ -34,6 +34,7 @@ package api
// /////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////
type Handler interface { type Handler interface {
Name() string
HandlerFunc() HandlerFunc HandlerFunc() HandlerFunc
} }
type HandlerFunc func(*Context) error type HandlerFunc func(*Context) error

15
util.go Normal file
View File

@ -0,0 +1,15 @@
package api
import (
"os"
"path/filepath"
)
// getExeFilename
func getExeFilename() string {
_, logfilename := filepath.Split(os.Args[0])
if logfilename == "" {
panic("get exe filename failed")
}
return logfilename
}