mirror of
https://github.com/foomo/keel.git
synced 2025-10-16 12:35:34 +00:00
feat: add response time middleware
This commit is contained in:
parent
af26e561fb
commit
9830f28b99
@ -438,6 +438,36 @@ func ExampleSessionID() {
|
|||||||
svr.Run()
|
svr.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExmampleResponseTime() {
|
||||||
|
svr := keel.NewServer()
|
||||||
|
|
||||||
|
// get logger
|
||||||
|
l := svr.Logger()
|
||||||
|
|
||||||
|
// create demo service
|
||||||
|
svs := http.NewServeMux()
|
||||||
|
svs.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte("OK"))
|
||||||
|
})
|
||||||
|
svs.HandleFunc("/sleep", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = w.Write([]byte("OK"))
|
||||||
|
})
|
||||||
|
|
||||||
|
svr.AddService(
|
||||||
|
keel.NewServiceHTTP(l, "demo", ":8080", svs,
|
||||||
|
middleware.ResponseTime(
|
||||||
|
// automatically set cookie if not exists
|
||||||
|
middleware.ResponseTimeWithMaxDuration(time.Millisecond * 500),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
svr.Run()
|
||||||
|
}
|
||||||
|
|
||||||
func ExampleSkip() {
|
func ExampleSkip() {
|
||||||
svr := keel.NewServer()
|
svr := keel.NewServer()
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ const (
|
|||||||
HeaderXUrlScheme = "X-Url-Scheme"
|
HeaderXUrlScheme = "X-Url-Scheme"
|
||||||
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
|
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
|
||||||
HeaderXRealIP = "X-Real-IP"
|
HeaderXRealIP = "X-Real-IP"
|
||||||
|
HeaderXResponseTime = "X-Response-Time"
|
||||||
HeaderXRequestID = "X-Request-ID"
|
HeaderXRequestID = "X-Request-ID"
|
||||||
HeaderXRequestedWith = "X-Requested-With"
|
HeaderXRequestedWith = "X-Requested-With"
|
||||||
HeaderXSessionID = "X-Session-ID"
|
HeaderXSessionID = "X-Session-ID"
|
||||||
|
|||||||
@ -37,7 +37,7 @@ func LoggerWithOptions(opts LoggerOptions) Middleware {
|
|||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// wrap response write to get access to status & size
|
// wrap response write to get access to status & size
|
||||||
wr := wrapResponseWriter(w)
|
wr := WrapResponseWriter(w)
|
||||||
|
|
||||||
next.ServeHTTP(wr, r)
|
next.ServeHTTP(wr, r)
|
||||||
|
|
||||||
|
|||||||
75
net/http/middleware/responsetime.go
Normal file
75
net/http/middleware/responsetime.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
|
||||||
|
"github.com/foomo/keel/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
ResponseTimeOptions struct {
|
||||||
|
SetHeader bool
|
||||||
|
MaxDuration time.Duration
|
||||||
|
MaxDurationMessage string
|
||||||
|
}
|
||||||
|
ResponseTimeOption func(*ResponseTimeOptions)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetDefaultResponseTimeOptions returns the default options
|
||||||
|
func GetDefaultResponseTimeOptions() ResponseTimeOptions {
|
||||||
|
return ResponseTimeOptions{
|
||||||
|
SetHeader: true,
|
||||||
|
MaxDurationMessage: "max response time exceeded",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseTimeWithMaxDurationMessage middleware option
|
||||||
|
func ResponseTimeWithMaxDurationMessage(v string) ResponseTimeOption {
|
||||||
|
return func(o *ResponseTimeOptions) {
|
||||||
|
o.MaxDurationMessage = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseTimeWithMaxDuration middleware option
|
||||||
|
func ResponseTimeWithMaxDuration(v time.Duration) ResponseTimeOption {
|
||||||
|
return func(o *ResponseTimeOptions) {
|
||||||
|
o.MaxDuration = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseTimeWithSetHeader middleware option
|
||||||
|
func ResponseTimeWithSetHeader(v bool) ResponseTimeOption {
|
||||||
|
return func(o *ResponseTimeOptions) {
|
||||||
|
o.SetHeader = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseTime middleware
|
||||||
|
func ResponseTime(opts ...ResponseTimeOption) Middleware {
|
||||||
|
options := GetDefaultResponseTimeOptions()
|
||||||
|
for _, opt := range opts {
|
||||||
|
if opt != nil {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResponseTimeWithOptions(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResponseTimeWithOptions middleware
|
||||||
|
func ResponseTimeWithOptions(opts ResponseTimeOptions) Middleware {
|
||||||
|
return func(l *zap.Logger, next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
start := time.Now()
|
||||||
|
rw := WrapResponseWriter(w)
|
||||||
|
rw.SetWriteResponseTimeHeader(opts.SetHeader)
|
||||||
|
next.ServeHTTP(rw, r)
|
||||||
|
duration := time.Since(start)
|
||||||
|
if opts.MaxDuration > 0 && duration > opts.MaxDuration {
|
||||||
|
l.Warn(opts.MaxDurationMessage, log.FDuration(opts.MaxDuration), log.FValue(duration.Microseconds()))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,25 +3,36 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
http2 "github.com/foomo/keel/net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
// responseWriter is a wrapper that includes that http statusCode and size for logging
|
// responseWriter is a wrapper that includes that http statusCode and size for logging
|
||||||
type responseWriter struct {
|
type responseWriter struct {
|
||||||
http.ResponseWriter
|
http.ResponseWriter
|
||||||
wroteHeader bool
|
writeResponseTimeHeader bool
|
||||||
statusCode int
|
wroteHeader bool
|
||||||
size int
|
statusCode int
|
||||||
|
start time.Time
|
||||||
|
size int
|
||||||
}
|
}
|
||||||
|
|
||||||
func wrapResponseWriter(w http.ResponseWriter) *responseWriter {
|
func WrapResponseWriter(w http.ResponseWriter) *responseWriter {
|
||||||
if wr, ok := w.(*responseWriter); ok {
|
if wr, ok := w.(*responseWriter); ok {
|
||||||
return wr
|
return wr
|
||||||
}
|
}
|
||||||
return &responseWriter{
|
return &responseWriter{
|
||||||
ResponseWriter: w,
|
ResponseWriter: w,
|
||||||
|
start: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *responseWriter) SetWriteResponseTimeHeader(write bool) {
|
||||||
|
w.writeResponseTimeHeader = write
|
||||||
|
}
|
||||||
|
|
||||||
func (w *responseWriter) Size() int {
|
func (w *responseWriter) Size() int {
|
||||||
return w.size
|
return w.size
|
||||||
}
|
}
|
||||||
@ -38,9 +49,12 @@ func (w *responseWriter) Status() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) WriteHeader(statusCode int) {
|
func (w *responseWriter) WriteHeader(statusCode int) {
|
||||||
w.wroteHeader = true
|
if w.writeResponseTimeHeader {
|
||||||
w.statusCode = statusCode
|
w.Header().Set(http2.HeaderXResponseTime, strconv.FormatInt(time.Since(w.start).Microseconds(), 10))
|
||||||
|
}
|
||||||
w.ResponseWriter.WriteHeader(statusCode)
|
w.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
w.statusCode = statusCode
|
||||||
|
w.wroteHeader = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *responseWriter) Write(b []byte) (int, error) {
|
func (w *responseWriter) Write(b []byte) (int, error) {
|
||||||
|
|||||||
@ -36,7 +36,7 @@ func TelemetryWithOptions(name string, opts TelemetryOptions) Middleware {
|
|||||||
return func(l *zap.Logger, next http.Handler) http.Handler {
|
return func(l *zap.Logger, next http.Handler) http.Handler {
|
||||||
return otelhttp.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return otelhttp.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// wrap response write to get access to status & size
|
// wrap response write to get access to status & size
|
||||||
wr := wrapResponseWriter(w)
|
wr := WrapResponseWriter(w)
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
next.ServeHTTP(wr, r)
|
next.ServeHTTP(wr, r)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user