@@ -19,12 +19,21 @@ import (
1919 "io"
2020 "net/http"
2121 "regexp"
22+ "strconv"
2223 "time"
2324
2425 "github.com/gregjones/httpcache"
2526 "github.com/rs/zerolog"
2627)
2728
29+ const (
30+ httpHeaderRateLimit = "X-Ratelimit-Limit"
31+ httpHeaderRateRemaining = "X-Ratelimit-Remaining"
32+ httpHeaderRateUsed = "X-Ratelimit-Used"
33+ httpHeaderRateReset = "X-Ratelimit-Reset"
34+ httpHeaderRateResource = "X-Ratelimit-Resource"
35+ )
36+
2837// ClientLogging creates client middleware that logs request and response
2938// information at the given level. If the request fails without creating a
3039// 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
8392 Int64 ("size" , - 1 )
8493 }
8594
95+ addRateLimitInformationToLog (options .LogRateLimitInformation , evt , res )
8696 evt .Msg ("github_request" )
8797 return res , err
8898 })
@@ -95,6 +105,18 @@ type ClientLoggingOption func(*clientLoggingOptions)
95105type clientLoggingOptions struct {
96106 RequestBodyPatterns []* regexp.Regexp
97107 ResponseBodyPatterns []* regexp.Regexp
108+
109+ // Output control
110+ LogRateLimitInformation * RateLimitLoggingOption
111+ }
112+
113+ // RateLimitLoggingOption controls which rate limit information is logged.
114+ type RateLimitLoggingOption struct {
115+ Limit bool
116+ Remaining bool
117+ Used bool
118+ Reset bool
119+ Resource bool
98120}
99121
100122// LogRequestBody enables request body logging for requests to paths matching
@@ -117,6 +139,14 @@ func LogResponseBody(patterns ...string) ClientLoggingOption {
117139 }
118140}
119141
142+ // LogRateLimitInformation defines which rate limit information like
143+ // the number of requests remaining in the current rate limit window is getting logged.
144+ func LogRateLimitInformation (options * RateLimitLoggingOption ) ClientLoggingOption {
145+ return func (opts * clientLoggingOptions ) {
146+ opts .LogRateLimitInformation = options
147+ }
148+ }
149+
120150func mirrorRequestBody (r * http.Request ) (* http.Request , []byte , error ) {
121151 switch {
122152 case r .Body == nil || r .Body == http .NoBody :
@@ -174,3 +204,34 @@ func requestMatches(r *http.Request, pats []*regexp.Regexp) bool {
174204func closeBody (b io.ReadCloser ) {
175205 _ = b .Close () // per http.Transport impl, ignoring close errors is fine
176206}
207+
208+ func addRateLimitInformationToLog (loggingOptions * RateLimitLoggingOption , evt * zerolog.Event , res * http.Response ) {
209+ // Exit early if no rate limit information is requested
210+ if loggingOptions == nil {
211+ return
212+ }
213+
214+ rateLimitDict := zerolog .Dict ()
215+ if limitHeader := res .Header .Get (httpHeaderRateLimit ); loggingOptions .Limit && limitHeader != "" {
216+ limit , _ := strconv .Atoi (limitHeader )
217+ rateLimitDict .Int ("limit" , limit )
218+ }
219+ if remainingHeader := res .Header .Get (httpHeaderRateRemaining ); loggingOptions .Remaining && remainingHeader != "" {
220+ remaining , _ := strconv .Atoi (remainingHeader )
221+ rateLimitDict .Int ("remaining" , remaining )
222+ }
223+ if usedHeader := res .Header .Get (httpHeaderRateUsed ); loggingOptions .Used && usedHeader != "" {
224+ used , _ := strconv .Atoi (usedHeader )
225+ rateLimitDict .Int ("used" , used )
226+ }
227+ if resetHeader := res .Header .Get (httpHeaderRateReset ); loggingOptions .Reset && resetHeader != "" {
228+ if v , _ := strconv .ParseInt (resetHeader , 10 , 64 ); v != 0 {
229+ rateLimitDict .Time ("reset" , time .Unix (v , 0 ))
230+ }
231+ }
232+ if resourceHeader := res .Header .Get (httpHeaderRateResource ); loggingOptions .Resource && resourceHeader != "" {
233+ rateLimitDict .Str ("resource" , resourceHeader )
234+ }
235+
236+ evt .Dict ("ratelimit" , rateLimitDict )
237+ }
0 commit comments