Compare commits

...

9 Commits

Author Message Date
bryanqiu
98eac47127 add AlreadyHaveResult function 2024-02-04 15:34:07 +08:00
bryanqiu
69b0fb05aa add AlreadyHaveResult function 2024-02-04 15:29:11 +08:00
bryanqiu
ba9357e166 add GET 2023-11-24 11:41:57 +08:00
bryanqiu
9df516247c export metricRegistry 2023-05-30 10:46:56 +08:00
bryanqiu
93bdc425e5 add grouping 2023-03-06 11:55:47 +08:00
bryanqiu
426a77f4cf options method buf fixed(record metrics update) 2022-12-22 09:53:14 +08:00
bryanqiu
a098d141c2 add pre middleware 2022-12-21 19:14:14 +08:00
bryanqiu
1d8453af9d surport outside instance 2022-12-21 16:46:56 +08:00
bryanqiu
b70e9ff90d add instancee 2022-12-21 16:01:53 +08:00
5 changed files with 166 additions and 29 deletions

48
api.go
View File

@ -1,6 +1,7 @@
package api package api
import ( import (
"fmt"
"reflect" "reflect"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -12,7 +13,7 @@ import (
var ( var (
validate = validator.New() validate = validator.New()
requestLogidGetter = defaultLogidGetter requestLogidGetter = defaultLogidGetter
requestHandlerMapper = map[string]Handler{} requestHandlerMapper = map[string]gin.HandlerFunc{}
errCodeUnknownError = -10000 errCodeUnknownError = -10000
errCodeParameterError = -20000 errCodeParameterError = -20000
errCodeUnimplementApi = -20401 errCodeUnimplementApi = -20401
@ -37,9 +38,12 @@ type BaseWithErrCodeOutput struct {
ErrMsg string `json:"errMsg"` ErrMsg string `json:"errMsg"`
} }
func NewEngine() *Engine { func NewEngine(pre ...gin.HandlerFunc) *Engine {
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
e := gin.New() e := gin.New()
for _, m := range pre {
e.Use(m)
}
e.Use(middlewareMyApiEngine()) e.Use(middlewareMyApiEngine())
return &Engine{e} return &Engine{e}
} }
@ -50,10 +54,16 @@ func (e *Engine) POST(uri string, handler Handler) {
e.Engine.POST(uri, h) e.Engine.POST(uri, h)
} }
func (e *Engine) GET(uri string, handler Handler) {
h := handlerWrapper(handler)
log.Infof("api handler [%v] registor success", handler)
e.Engine.GET(uri, h)
}
// handlerWrapper // handlerWrapper
func handlerWrapper(handler Handler) gin.HandlerFunc { func handlerWrapper(handler Handler) gin.HandlerFunc {
handlerFunc := handler.HandlerFunc() handlerFunc := handler.HandlerFunc()
return func(c *gin.Context) { wrappedHandlerFunc := func(c *gin.Context) {
// Case 1. get request handler // Case 1. get request handler
if c.Request == nil && c.Keys != nil { if c.Request == nil && c.Keys != nil {
c.Keys["handler"] = handler c.Keys["handler"] = handler
@ -75,6 +85,9 @@ func handlerWrapper(handler Handler) gin.HandlerFunc {
return return
} }
} }
hkey := fmt.Sprintf("%v", wrappedHandlerFunc)
requestHandlerMapper[hkey] = wrappedHandlerFunc
return wrappedHandlerFunc
} }
// handlerWrapper // handlerWrapper
@ -111,6 +124,9 @@ func middlewareMyApiEngine() gin.HandlerFunc {
// Step 3. do request // Step 3. do request
c.Next() c.Next()
// Step 4. recorde metrics
cc.recordMetrics()
} }
} }
@ -138,12 +154,32 @@ func CC(c *gin.Context) *Context {
} }
func ApiHandler(c *gin.Context) Handler { func ApiHandler(c *gin.Context) Handler {
newc := &gin.Context{Request: nil, Keys: map[string]any{}} h := c.Handler()
c.Handler()(newc) hkey := fmt.Sprintf("%v", h)
if _, ok := requestHandlerMapper[hkey]; !ok {
return nil
}
newc := &gin.Context{
Request: nil,
Keys: map[string]any{},
}
h(newc)
return newc.Keys["handler"].(Handler) return newc.Keys["handler"].(Handler)
} }
func DumpHandler(c *Context) error { func RecordMetrics(api, errcode, appid string, costms float64) {
var (
counter = metricApiCounter.WithLabelValues(api, errcode, appid)
summary = metricApiSummary.WithLabelValues(api, errcode, appid)
)
// Metric 1. api request counter
counter.Inc()
// Metric 2. api request cost/latency
summary.Observe(costms)
}
func DumpHandlerFunc(c *Context) error {
errmsg := "api not implemented" errmsg := "api not implemented"
errcode := errCodeUnimplementApi errcode := errCodeUnimplementApi
return c.RESULT_ERROR(errcode, errmsg) return c.RESULT_ERROR(errcode, errmsg)

View File

@ -120,7 +120,14 @@ func (c *Context) SetCookie(cookie *http.Cookie) {
http.SetCookie(c.Writer, cookie) http.SetCookie(c.Writer, cookie)
} }
func (c *Context) AlreadyHaveResult() (done bool, entry string) {
return c.resultentry != "", c.resultentry
}
func (c *Context) RESULT(output interface{}) error { func (c *Context) RESULT(output interface{}) error {
if c.resultentry == "" {
c.resultentry = "RESULT"
}
var t = reflect.TypeOf(output) var t = reflect.TypeOf(output)
defer func(o *interface{}) { defer func(o *interface{}) {
b, err := json.Marshal(o) b, err := json.Marshal(o)
@ -149,9 +156,6 @@ 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 { if _, ok := t.FieldByName("ErrCode"); !ok {
@ -215,24 +219,21 @@ func (c *Context) shapeOutput(o interface{}) {
return return
} }
func (c *Context) recordMetric() { func (c *Context) recordMetrics() {
if metricApiCounter == nil || if metricApiCounter == nil ||
metricApiSummary == nil { metricApiSummary == nil {
return return
} }
var h = c.ApiHandler()
if h == nil {
h = unimplementHandler{c.Context}
}
var ( var (
h = c.ApiHandler()
api = h.ApiName() api = h.ApiName()
appid = c.AppId appid = c.AppId
costus = time.Now().Sub(c.starttime).Microseconds() costus = time.Now().Sub(c.starttime).Microseconds()
costms = float64(costus) / 1000.0 costms = float64(costus) / 1000.0
errcode = strconv.Itoa(c.errcode) errcode = strconv.Itoa(c.errcode)
counter = metricApiCounter.WithLabelValues(api, errcode, appid)
summary = metricApiSummary.WithLabelValues(api, errcode, appid)
) )
RecordMetrics(api, errcode, appid, costms)
// Metric 1. api request counter
counter.Inc()
// Metric 2. api request cost/latency
summary.Observe(costms)
} }

View File

@ -1,20 +1,24 @@
package api package api
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
"strings"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/prometheus/client_golang/prometheus/push" "github.com/prometheus/client_golang/prometheus/push"
"qoobing.com/gomod/log"
) )
var ( var (
MetricExporterPusher = func() {} MetricExporterPusher = func() error { return nil }
MetricExporterHandler Handler = nil MetricExporterHandler Handler = nil
metricRegistry *prometheus.Registry = nil MetricRegistry *prometheus.Registry = nil
metricApiCounter *prometheus.CounterVec = nil metricApiCounter *prometheus.CounterVec = nil
metricApiSummary *prometheus.SummaryVec = nil metricApiSummary *prometheus.SummaryVec = nil
instance string = "--unknown--"
) )
type metricExporterHandler struct { type metricExporterHandler struct {
@ -33,11 +37,24 @@ func (m *metricExporterHandler) HandlerFunc() HandlerFunc {
} }
} }
func InitMetrics() { func dumpMetricExporterPusher() error {
return errors.New("not initilized, forgot call 'SetupMetricsExporterPusher'?")
}
func InitMetrics(instanceValue string) {
if instanceValue == "" {
instance = getInstanceIpAddress()
} else if ap := strings.Split(instanceValue, ":"); len(ap) == 2 &&
ap[0] == "0.0.0.0" || ap[0] == "" {
instance = getInstanceIpAddress() + ":" + ap[1]
}
log.Infof("self instance: [%s]", instance)
metricApiCounter = prometheus.NewCounterVec( metricApiCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{ prometheus.CounterOpts{
Name: "api_requests_total", Name: "api_requests_total",
Help: "How many HTTP requests processed", Help: "How many HTTP requests processed",
//ConstLabels: prometheus.Labels{"instance": instance},
}, },
[]string{"api", "errcode", "appid"}, []string{"api", "errcode", "appid"},
) )
@ -46,12 +63,14 @@ func InitMetrics() {
Name: "api_requests_summary", Name: "api_requests_summary",
Help: "The api request summary of cost.", Help: "The api request summary of cost.",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
//ConstLabels: prometheus.Labels{"instance": instance},
}, },
[]string{"api", "errcode", "appid"}, []string{"api", "errcode", "appid"},
) )
metricRegistry = prometheus.NewRegistry()
metricRegistry.MustRegister(metricApiCounter) MetricRegistry = prometheus.NewRegistry()
metricRegistry.MustRegister(metricApiSummary) MetricRegistry.MustRegister(metricApiCounter)
MetricRegistry.MustRegister(metricApiSummary)
} }
func SetupMetricsExporterHandler(apiname string) { func SetupMetricsExporterHandler(apiname string) {
@ -59,10 +78,10 @@ func SetupMetricsExporterHandler(apiname string) {
apiname = "premetheus_metrics_exporter" apiname = "premetheus_metrics_exporter"
} }
opt := promhttp.HandlerOpts{Registry: metricRegistry} opt := promhttp.HandlerOpts{Registry: MetricRegistry}
MetricExporterHandler = &metricExporterHandler{ MetricExporterHandler = &metricExporterHandler{
name: apiname, name: apiname,
promhttpHandler: promhttp.HandlerFor(metricRegistry, opt), promhttpHandler: promhttp.HandlerFor(MetricRegistry, opt),
} }
} }
@ -71,8 +90,13 @@ func SetupMetricsExporterPusher(pushgateway string, jobname string) {
jobname = fmt.Sprintf("api-metrics-job-%s", getExeFilename()) jobname = fmt.Sprintf("api-metrics-job-%s", getExeFilename())
} }
var pusher = push.New(pushgateway, jobname).Gatherer(metricRegistry) var pusher = push.
MetricExporterPusher = func() { New(pushgateway, jobname).
pusher.Push() Grouping("instance", instance).
Gatherer(MetricRegistry)
MetricExporterPusher = func() error {
RecordMetrics("push_metrics_to_prometheus", "0", "selfmonitoring", 1.0)
return pusher.Push()
} }
} }

View File

@ -1,5 +1,12 @@
package api package api
import (
"fmt"
"strings"
"github.com/gin-gonic/gin"
)
// /////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////
// IRouter & IRoutes in gin, we redefine it simple // IRouter & IRoutes in gin, we redefine it simple
// /////////////////////////////////////////////////////////////////// // ///////////////////////////////////////////////////////////////////
@ -40,5 +47,19 @@ type Handler interface {
type HandlerFunc func(*Context) error type HandlerFunc func(*Context) error
type IRoutes interface { type IRoutes interface {
GET(uri string, handler Handler)
POST(uri string, handler Handler) POST(uri string, handler Handler)
} }
type unimplementHandler struct {
c *gin.Context
}
func (u unimplementHandler) ApiName() string {
req := u.c.Request
return strings.ToLower(fmt.Sprintf("[%s]%s", req.Method, req.RequestURI))
}
func (u unimplementHandler) HandlerFunc() HandlerFunc {
return DumpHandlerFunc
}

55
util.go
View File

@ -1,8 +1,11 @@
package api package api
import ( import (
"fmt"
"net"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
) )
// getExeFilename // getExeFilename
@ -13,3 +16,55 @@ func getExeFilename() string {
} }
return logfilename return logfilename
} }
// preferencesMatch
func preferencesMatch(s string, preferences ...string) bool {
for _, pstr := range preferences {
reg := regexp.MustCompile(pstr)
if reg.Match([]byte(s)) {
return true
}
}
return false
}
// getInstanceIpAddress
func getInstanceIpAddress(preferences ...string) string {
addrs, err := net.InterfaceAddrs()
if err != nil {
panic(fmt.Sprintf("getInstanceIpAddress failed: [%s]", err))
}
prefer := func() func(string) bool {
if len(preferences) <= 0 {
return nil
}
return func(ip string) bool {
return preferencesMatch(ip, preferences...)
}
}()
var ipnets = []*net.IPNet{}
for _, address := range addrs {
if ipnet, ok := address.(*net.IPNet); !ok {
continue
} else if ipnet.IP.IsLoopback() {
continue
} else if ipv4 := ipnet.IP.To4(); ipv4 == nil {
continue
} else if prefer == nil {
var n = *ipnet
ipnets = append(ipnets, &n)
continue
} else if prefer != nil && prefer(ipv4.String()) {
return ipv4.String()
}
}
// TODO: sort & return first
if len(ipnets) > 0 {
return ipnets[0].IP.To4().String()
}
panic(fmt.Sprintf("getInstanceIpAddreass failed,"+
"preferences=[%v], ipnets=[%v]", preferences, ipnets))
}