...

Source file src/github.com/palantir/go-githubapp/githubapp/middleware.go

Documentation: github.com/palantir/go-githubapp/githubapp

     1  // Copyright 2018 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package githubapp
    16  
    17  import (
    18  	"fmt"
    19  	"net/http"
    20  	"strconv"
    21  
    22  	"github.com/gregjones/httpcache"
    23  	"github.com/rcrowley/go-metrics"
    24  )
    25  
    26  const (
    27  	MetricsKeyRequests    = "github.requests"
    28  	MetricsKeyRequests2xx = "github.requests.2xx"
    29  	MetricsKeyRequests3xx = "github.requests.3xx"
    30  	MetricsKeyRequests4xx = "github.requests.4xx"
    31  	MetricsKeyRequests5xx = "github.requests.5xx"
    32  
    33  	MetricsKeyRequestsCached = "github.requests.cached"
    34  
    35  	MetricsKeyRateLimit          = "github.rate.limit"
    36  	MetricsKeyRateLimitRemaining = "github.rate.remaining"
    37  )
    38  
    39  // ClientMetrics creates client middleware that records metrics about all
    40  // requests. It also defines the metrics in the provided registry.
    41  func ClientMetrics(registry metrics.Registry) ClientMiddleware {
    42  	for _, key := range []string{
    43  		MetricsKeyRequests,
    44  		MetricsKeyRequests2xx,
    45  		MetricsKeyRequests3xx,
    46  		MetricsKeyRequests4xx,
    47  		MetricsKeyRequests5xx,
    48  		MetricsKeyRequestsCached,
    49  	} {
    50  		// Use GetOrRegister for thread-safety when creating multiple
    51  		// RoundTrippers that share the same registry
    52  		metrics.GetOrRegisterCounter(key, registry)
    53  	}
    54  
    55  	return func(next http.RoundTripper) http.RoundTripper {
    56  		return roundTripperFunc(func(r *http.Request) (*http.Response, error) {
    57  			installationID, ok := r.Context().Value(installationKey).(int64)
    58  			if !ok {
    59  				installationID = 0
    60  			}
    61  
    62  			res, err := next.RoundTrip(r)
    63  
    64  			if res != nil {
    65  				registry.Get(MetricsKeyRequests).(metrics.Counter).Inc(1)
    66  				if key := bucketStatus(res.StatusCode); key != "" {
    67  					registry.Get(key).(metrics.Counter).Inc(1)
    68  				}
    69  
    70  				if res.Header.Get(httpcache.XFromCache) != "" {
    71  					registry.Get(MetricsKeyRequestsCached).(metrics.Counter).Inc(1)
    72  				}
    73  
    74  				limitMetric := fmt.Sprintf("%s[installation:%d]", MetricsKeyRateLimit, installationID)
    75  				remainingMetric := fmt.Sprintf("%s[installation:%d]", MetricsKeyRateLimitRemaining, installationID)
    76  
    77  				// Headers from https://developer.github.com/v3/#rate-limiting
    78  				updateRegistryForHeader(res.Header, "X-RateLimit-Limit", metrics.GetOrRegisterGauge(limitMetric, registry))
    79  				updateRegistryForHeader(res.Header, "X-RateLimit-Remaining", metrics.GetOrRegisterGauge(remainingMetric, registry))
    80  			}
    81  
    82  			return res, err
    83  		})
    84  	}
    85  }
    86  
    87  func updateRegistryForHeader(headers http.Header, header string, metric metrics.Gauge) {
    88  	headerString := headers.Get(header)
    89  	if headerString != "" {
    90  		headerVal, err := strconv.ParseInt(headerString, 10, 64)
    91  		if err == nil {
    92  			metric.Update(headerVal)
    93  		}
    94  	}
    95  }
    96  
    97  func bucketStatus(status int) string {
    98  	switch {
    99  	case status >= 200 && status < 300:
   100  		return MetricsKeyRequests2xx
   101  	case status >= 300 && status < 400:
   102  		return MetricsKeyRequests3xx
   103  	case status >= 400 && status < 500:
   104  		return MetricsKeyRequests4xx
   105  	case status >= 500 && status < 600:
   106  		return MetricsKeyRequests5xx
   107  	}
   108  	return ""
   109  }
   110  
   111  type roundTripperFunc func(*http.Request) (*http.Response, error)
   112  
   113  func (fn roundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error) {
   114  	return fn(r)
   115  }
   116  

View as plain text