diff --git a/api.go b/api.go index 28c4585..5fc7872 100644 --- a/api.go +++ b/api.go @@ -13,7 +13,7 @@ import ( var ( validate = validator.New() requestLogidGetter = defaultLogidGetter - handlerMapper = map[string]Handler{} + requestHandlerMapper = map[string]Handler{} errCodeUnknownError = -10000 errCodeParameterError = -20000 errCodeUnimplementApi = -20401 @@ -47,7 +47,9 @@ func NewEngine() *Engine { func (e *Engine) POST(uri string, handler Handler) { 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) } diff --git a/context.go b/context.go index 8f46607..f14065c 100644 --- a/context.go +++ b/context.go @@ -26,6 +26,7 @@ type Context struct { httpstatus int starttime time.Time output interface{} + errcode int } func New(c *gin.Context) *Context { @@ -146,28 +147,62 @@ func (c *Context) RESULT(output interface{}) error { cost := time.Now().Sub(c.starttime).Milliseconds() noticeStr := fmt.Sprintf("cost: %dms, output: '%s'", cost, string(b)) log.NoticefWithDepth(calldepth, noticeStr) + + //// metric //////////////////////////////////////// + c.recordMetric() }(&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 { errstr := "Result MUST have 'ErrMsg' field" output = BaseWithErrCodeOutput{errCodeUnknownError, errstr} - c.ShapeOutput(output) + c.shapeOutput(output) + c.errcode = reflect.ValueOf(output).FieldByName("ErrCode").GetInt() c.IndentedJSON(c.httpstatus, output) 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) 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 { return } @@ -183,26 +218,23 @@ func (c *Context) ShapeOutput(o interface{}) { return } -func (c *Context) ApiHandler() Handler { - h := c.Context.Handler() - if handler, ok := handlerMapper[fmt.Sprintf("%v", h)]; ok { - return handler +func (c *Context) recordMetric() { + if metricApiCounter == nil || + metricApiSummary == nil { + 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 { - if c.resultentry == "" { - c.resultentry = "RESULT_ERROR" - } - - 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) + // Metric 1. api request counter + counter.Inc() + // Metric 2. api request cost/latency + summary.Observe(cost) } diff --git a/prometheus.go b/prometheus.go new file mode 100644 index 0000000..5c7af3e --- /dev/null +++ b/prometheus.go @@ -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() + } +} diff --git a/routes.go b/routes.go index 9b3e17f..ce67880 100644 --- a/routes.go +++ b/routes.go @@ -34,6 +34,7 @@ package api // /////////////////////////////////////////////////////////////////// type Handler interface { + Name() string HandlerFunc() HandlerFunc } type HandlerFunc func(*Context) error diff --git a/util.go b/util.go new file mode 100644 index 0000000..9f55402 --- /dev/null +++ b/util.go @@ -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 +}