2017-01-12 16:34:54 +03:00
package middlewares
import (
"net/http"
"strconv"
2018-01-26 13:58:03 +03:00
"strings"
"sync/atomic"
2017-01-12 16:34:54 +03:00
"time"
2017-09-08 12:22:03 +03:00
"unicode/utf8"
2017-04-18 09:22:06 +03:00
2017-09-08 12:22:03 +03:00
"github.com/containous/traefik/log"
2017-08-23 21:46:03 +03:00
"github.com/containous/traefik/metrics"
gokitmetrics "github.com/go-kit/kit/metrics"
2018-01-26 13:58:03 +03:00
"github.com/urfave/negroni"
2017-01-12 16:34:54 +03:00
)
2018-01-26 13:58:03 +03:00
const (
protoHTTP = "http"
protoSSE = "sse"
protoWebsocket = "websocket"
)
// NewEntryPointMetricsMiddleware creates a new metrics middleware for an Entrypoint.
func NewEntryPointMetricsMiddleware ( registry metrics . Registry , entryPointName string ) negroni . Handler {
return & metricsMiddleware {
reqsCounter : registry . EntrypointReqsCounter ( ) ,
reqDurationHistogram : registry . EntrypointReqDurationHistogram ( ) ,
openConnsGauge : registry . EntrypointOpenConnsGauge ( ) ,
baseLabels : [ ] string { "entrypoint" , entryPointName } ,
}
2017-01-12 16:34:54 +03:00
}
2018-01-26 13:58:03 +03:00
// NewBackendMetricsMiddleware creates a new metrics middleware for a Backend.
func NewBackendMetricsMiddleware ( registry metrics . Registry , backendName string ) negroni . Handler {
return & metricsMiddleware {
reqsCounter : registry . BackendReqsCounter ( ) ,
reqDurationHistogram : registry . BackendReqDurationHistogram ( ) ,
openConnsGauge : registry . BackendOpenConnsGauge ( ) ,
baseLabels : [ ] string { "backend" , backendName } ,
2017-01-12 16:34:54 +03:00
}
2018-01-26 13:58:03 +03:00
}
2017-01-12 16:34:54 +03:00
2018-01-26 13:58:03 +03:00
type metricsMiddleware struct {
reqsCounter gokitmetrics . Counter
reqDurationHistogram gokitmetrics . Histogram
openConnsGauge gokitmetrics . Gauge
baseLabels [ ] string
openConns int64
2017-01-12 16:34:54 +03:00
}
2018-01-26 13:58:03 +03:00
func ( m * metricsMiddleware ) ServeHTTP ( rw http . ResponseWriter , r * http . Request , next http . HandlerFunc ) {
labels := [ ] string { "method" , getMethod ( r ) , "protocol" , getRequestProtocol ( r ) }
labels = append ( labels , m . baseLabels ... )
openConns := atomic . AddInt64 ( & m . openConns , 1 )
m . openConnsGauge . With ( labels ... ) . Set ( float64 ( openConns ) )
defer func ( labelValues [ ] string ) {
openConns := atomic . AddInt64 ( & m . openConns , - 1 )
m . openConnsGauge . With ( labelValues ... ) . Set ( float64 ( openConns ) )
} ( labels )
2017-01-12 16:34:54 +03:00
start := time . Now ( )
2018-01-26 13:58:03 +03:00
recorder := & responseRecorder { rw , http . StatusOK }
next ( recorder , r )
2017-06-15 17:06:02 +03:00
2018-01-26 13:58:03 +03:00
labels = append ( labels , "code" , strconv . Itoa ( recorder . statusCode ) )
m . reqsCounter . With ( labels ... ) . Add ( 1 )
2018-02-13 19:14:04 +03:00
m . reqDurationHistogram . With ( labels ... ) . Observe ( time . Since ( start ) . Seconds ( ) )
2018-01-26 13:58:03 +03:00
}
2017-06-15 17:06:02 +03:00
2018-01-26 13:58:03 +03:00
func getRequestProtocol ( req * http . Request ) string {
switch {
case isWebsocketRequest ( req ) :
return protoWebsocket
case isSSERequest ( req ) :
return protoSSE
default :
return protoHTTP
}
2017-08-23 21:46:03 +03:00
}
2018-01-26 13:58:03 +03:00
// isWebsocketRequest determines if the specified HTTP request is a websocket handshake request.
func isWebsocketRequest ( req * http . Request ) bool {
return containsHeader ( req , "Connection" , "upgrade" ) && containsHeader ( req , "Upgrade" , "websocket" )
2017-08-23 21:46:03 +03:00
}
2018-01-26 13:58:03 +03:00
// isSSERequest determines if the specified HTTP request is a request for an event subscription.
func isSSERequest ( req * http . Request ) bool {
return containsHeader ( req , "Accept" , "text/event-stream" )
}
func containsHeader ( req * http . Request , name , value string ) bool {
items := strings . Split ( req . Header . Get ( name ) , "," )
for _ , item := range items {
if value == strings . ToLower ( strings . TrimSpace ( item ) ) {
return true
}
}
return false
2017-04-18 09:22:06 +03:00
}
2017-09-08 12:22:03 +03:00
func getMethod ( r * http . Request ) string {
if ! utf8 . ValidString ( r . Method ) {
log . Warnf ( "Invalid HTTP method encoding: %s" , r . Method )
return "NON_UTF8_HTTP_METHOD"
}
return r . Method
}
2018-01-26 13:58:03 +03:00
type retryMetrics interface {
BackendRetriesCounter ( ) gokitmetrics . Counter
}
// NewMetricsRetryListener instantiates a MetricsRetryListener with the given retryMetrics.
func NewMetricsRetryListener ( retryMetrics retryMetrics , backendName string ) RetryListener {
return & MetricsRetryListener { retryMetrics : retryMetrics , backendName : backendName }
}
2017-04-18 09:22:06 +03:00
// MetricsRetryListener is an implementation of the RetryListener interface to
2017-08-23 21:46:03 +03:00
// record RequestMetrics about retry attempts.
2017-04-18 09:22:06 +03:00
type MetricsRetryListener struct {
2017-08-23 21:46:03 +03:00
retryMetrics retryMetrics
backendName string
2017-01-12 16:34:54 +03:00
}
2017-08-23 21:46:03 +03:00
// Retried tracks the retry in the RequestMetrics implementation.
2017-08-28 13:50:02 +03:00
func ( m * MetricsRetryListener ) Retried ( req * http . Request , attempt int ) {
2018-01-26 13:58:03 +03:00
m . retryMetrics . BackendRetriesCounter ( ) . With ( "backend" , m . backendName ) . Add ( 1 )
2017-01-12 16:34:54 +03:00
}