diff --git a/githubapp/middleware.go b/githubapp/middleware.go index ec11fbc1..6cafc98b 100644 --- a/githubapp/middleware.go +++ b/githubapp/middleware.go @@ -75,8 +75,9 @@ func ClientMetrics(registry metrics.Registry) ClientMiddleware { remainingMetric := fmt.Sprintf("%s[installation:%d]", MetricsKeyRateLimitRemaining, installationID) // Headers from https://developer.github.com/v3/#rate-limiting - updateRegistryForHeader(res.Header, "X-RateLimit-Limit", metrics.GetOrRegisterGauge(limitMetric, registry)) - updateRegistryForHeader(res.Header, "X-RateLimit-Remaining", metrics.GetOrRegisterGauge(remainingMetric, registry)) + updateRegistryForHeader(res.Header, HTTPHeaderRateLimit, metrics.GetOrRegisterGauge(limitMetric, registry)) + updateRegistryForHeader(res.Header, HTTPHeaderRateRemaining, metrics.GetOrRegisterGauge(remainingMetric, registry)) + // TODO Think about to add X-Ratelimit-Used, X-Ratelimit-Reset and X-Ratelimit-Resource as well } return res, err diff --git a/githubapp/middleware_logging.go b/githubapp/middleware_logging.go index 8e4dfec4..0766be05 100644 --- a/githubapp/middleware_logging.go +++ b/githubapp/middleware_logging.go @@ -19,12 +19,21 @@ import ( "io" "net/http" "regexp" + "strconv" "time" "github.com/gregjones/httpcache" "github.com/rs/zerolog" ) +const ( + HTTPHeaderRateLimit = "X-Ratelimit-Limit" + HTTPHeaderRateRemaining = "X-Ratelimit-Remaining" + HTTPHeaderRateUsed = "X-Ratelimit-Used" + HTTPHeaderRateReset = "X-Ratelimit-Reset" + HTTPHeaderRateResource = "X-Ratelimit-Resource" +) + // ClientLogging creates client middleware that logs request and response // information at the given level. If the request fails without creating a // response, it is logged with a status code of -1. The middleware uses a @@ -83,6 +92,7 @@ func ClientLogging(lvl zerolog.Level, opts ...ClientLoggingOption) ClientMiddlew Int64("size", -1) } + addRateLimitInformationToLog(options.LogRateLimitInformation, evt, res) evt.Msg("github_request") return res, err }) @@ -95,6 +105,18 @@ type ClientLoggingOption func(*clientLoggingOptions) type clientLoggingOptions struct { RequestBodyPatterns []*regexp.Regexp ResponseBodyPatterns []*regexp.Regexp + + // Output control + LogRateLimitInformation *RateLimitLoggingOption +} + +// RateLimitLoggingOption controls which rate limit information is logged. +type RateLimitLoggingOption struct { + Limit bool + Remaining bool + Used bool + Reset bool + Resource bool } // LogRequestBody enables request body logging for requests to paths matching @@ -117,6 +139,14 @@ func LogResponseBody(patterns ...string) ClientLoggingOption { } } +// SetRateLimitInformation defines which rate limit information like +// the number of requests remaining in the current rate limit window is getting logged. +func SetRateLimitInformation(options *RateLimitLoggingOption) ClientLoggingOption { + return func(opts *clientLoggingOptions) { + opts.LogRateLimitInformation = options + } +} + func mirrorRequestBody(r *http.Request) (*http.Request, []byte, error) { switch { case r.Body == nil || r.Body == http.NoBody: @@ -174,3 +204,26 @@ func requestMatches(r *http.Request, pats []*regexp.Regexp) bool { func closeBody(b io.ReadCloser) { _ = b.Close() // per http.Transport impl, ignoring close errors is fine } + +func addRateLimitInformationToLog(loggingOptions *RateLimitLoggingOption, evt *zerolog.Event, res *http.Response) { + if limitHeader := res.Header.Get(HTTPHeaderRateLimit); loggingOptions.Limit && limitHeader != "" { + limit, _ := strconv.Atoi(limitHeader) + evt.Int("ratelimit-limit", limit) + } + if remainingHeader := res.Header.Get(HTTPHeaderRateRemaining); loggingOptions.Remaining && remainingHeader != "" { + remaining, _ := strconv.Atoi(remainingHeader) + evt.Int("ratelimit-remaining", remaining) + } + if usedHeader := res.Header.Get(HTTPHeaderRateUsed); loggingOptions.Used && usedHeader != "" { + used, _ := strconv.Atoi(usedHeader) + evt.Int("ratelimit-used", used) + } + if resetHeader := res.Header.Get(HTTPHeaderRateReset); loggingOptions.Reset && resetHeader != "" { + if v, _ := strconv.ParseInt(resetHeader, 10, 64); v != 0 { + evt.Time("ratelimit-reset", time.Unix(v, 0)) + } + } + if resourceHeader := res.Header.Get(HTTPHeaderRateResource); loggingOptions.Resource && resourceHeader != "" { + evt.Str("ratelimit-resource", resourceHeader) + } +}