-
Notifications
You must be signed in to change notification settings - Fork 90
/
result.go
145 lines (126 loc) · 4.22 KB
/
result.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
/*
* Tencent is pleased to support the open source community by making TKEStack available.
*
* Copyright (C) 2012-2019 Tencent. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of the
* License at
*
* https://opensource.org/licenses/Apache-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package api
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
)
// Status indicate the result status of request, success or error
type Status string
// ErrorType is not empty if result status is not success
type ErrorType string
// Result is the common format of all response
type Result struct {
// ErrorType is the type of result if Status is not success
ErrorType ErrorType `json:"errorType,omitempty"`
// Err indicate the error detail
Err string `json:"error,omitempty"`
// Data is the real data of result, data may be nil even if Status is success
Data interface{} `json:"data,omitempty"`
// Status indicate whether the result is success
Status Status `json:"status"`
}
// InternalErr make a result with ErrorType ErrorInternal
func InternalErr(err error, format string, args ...interface{}) *Result {
return &Result{
ErrorType: ErrorInternal,
Status: StatusError,
Err: errors.Wrapf(err, format, args...).Error(),
}
}
// BadDataErr make a result with ErrorType ErrorBadData
func BadDataErr(err error, format string, args ...interface{}) *Result {
return &Result{
ErrorType: ErrorBadData,
Status: StatusError,
Err: errors.Wrapf(err, format, args...).Error(),
}
}
// Data make a result with data or nil, the Status will be set to StatusSuccess
func Data(data interface{}) *Result {
return &Result{
Data: data,
Status: StatusSuccess,
}
}
const (
// StatusSuccess indicate result Status is success, the data of result is available
StatusSuccess Status = "success"
// StatusError indicate result is failed, the data may be empty
StatusError Status = "error"
// ErrorBadData indicate that result is failed because the wrong request data
ErrorBadData ErrorType = "bad_data"
// ErrorInternal indicate that result is failed because the request data may be right but the server is something wrong
ErrorInternal ErrorType = "internal"
)
// Helper provider some function to build a service
type Helper struct {
log logrus.FieldLogger
httpDurationSeconds *prometheus.HistogramVec
register *prometheus.Registry
}
// NewHelper create a new APIWrapper
func NewHelper(lg logrus.FieldLogger, register *prometheus.Registry, metricsPrefix string) *Helper {
w := &Helper{
log: lg,
register: register,
httpDurationSeconds: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Name: fmt.Sprintf("%s_http_request_duration_seconds", metricsPrefix),
Help: "http request duration seconds",
Buckets: []float64{0.01, 0.1, 0.3, 0.5, 1, 3, 5, 10},
}, []string{"path", "code"}),
}
w.register.MustRegister(w.httpDurationSeconds)
return w
}
// MetricsHandler process metrics request
func (h *Helper) MetricsHandler(c *gin.Context) {
promhttp.HandlerFor(h.register, promhttp.HandlerOpts{
ErrorLog: h.log,
}).ServeHTTP(c.Writer, c.Request)
}
// Wrap return a gin handler function with common result processed
func (h *Helper) Wrap(f func(ctx *gin.Context) *Result) func(ctx *gin.Context) {
return func(ctx *gin.Context) {
var (
path = ctx.Request.URL.Path
code = 200
)
defer func(start time.Time) {
h.httpDurationSeconds.WithLabelValues(path, fmt.Sprint(code)).
Observe(float64(time.Since(start).Seconds()))
}(time.Now())
r := f(ctx)
if r == nil {
ctx.Status(code)
return
}
if r.ErrorType != "" {
h.log.Error(r.Err)
code = 503
if r.ErrorType == ErrorBadData {
code = 400
}
}
ctx.JSON(code, r)
}
}