Skip to content

Commit 75c75fd

Browse files
committed
Refactor LogHTTPHeaders function to convert http.Header to string representation
1 parent 487b529 commit 75c75fd

File tree

2 files changed

+122
-119
lines changed

2 files changed

+122
-119
lines changed
Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,21 @@
11
package httpclient
22

33
import (
4+
"fmt"
45
"net/http"
5-
6-
"github.com/deploymenttheory/go-api-http-client/internal/logger"
6+
"strings"
77
)
88

9-
// LogHTTPHeaders logs the HTTP headers of an HTTP request or response, with an option to hide sensitive information like the token in secure mode.
10-
func LogHTTPHeaders(log logger.Logger, headers http.Header, secureMode bool) {
11-
var keysAndValues []interface{}
12-
if secureMode {
13-
for key, values := range headers {
14-
if key != "Authorization" { // Exclude the token header
15-
// Assuming each header has a single value for simplicity
16-
keysAndValues = append(keysAndValues, key, values[0])
17-
}
18-
}
19-
} else {
20-
for key, values := range headers {
21-
// Assuming each header has a single value for simplicity
22-
keysAndValues = append(keysAndValues, key, values[0])
23-
}
24-
}
9+
// HeadersToString converts an http.Header map to a single string representation.
10+
func HeadersToString(headers http.Header) string {
11+
var headerStrings []string
2512

26-
// Log the headers using the logger from the httpclient package
27-
if len(keysAndValues) > 0 {
28-
log.Debug("HTTP Headers", keysAndValues...)
13+
// Iterate over the map and append each key-value pair to the slice
14+
for name, values := range headers {
15+
// Combine each header's key with its values, which are joined by a comma
16+
headerStrings = append(headerStrings, fmt.Sprintf("%s: %s", name, strings.Join(values, ", ")))
2917
}
18+
19+
// Join all header strings into a single string
20+
return strings.Join(headerStrings, "; ")
3021
}

internal/httpclient/http_request.go

Lines changed: 110 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import (
88
"net/http"
99
"time"
1010

11+
"github.com/deploymenttheory/go-api-http-client/internal/errors"
1112
"github.com/deploymenttheory/go-api-http-client/internal/logger"
13+
"go.uber.org/zap"
1214
)
1315

1416
// DoRequest constructs and executes a standard HTTP request with support for retry logic.
@@ -78,7 +80,7 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
7880
}
7981

8082
// Construct URL using the ConstructAPIResourceEndpoint function
81-
url := apiHandler.ConstructAPIResourceEndpoint(endpoint)
83+
url := apiHandler.ConstructAPIResourceEndpoint(endpoint, log)
8284

8385
// Initialize total request counter
8486
c.PerfMetrics.lock.Lock()
@@ -103,7 +105,8 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
103105
req.Header.Set("User-Agent", GetUserAgentHeader())
104106

105107
// Debug: Print request headers if in debug mode
106-
log.Debug("HTTP Request Headers:", req.Header)
108+
headersStr := HeadersToString(req.Header)
109+
log.Debug("HTTP Multipart Request Headers:", zap.String("headers", headersStr))
107110

108111
// Define if request is retryable
109112
retryableHTTPMethods := map[string]bool{
@@ -133,10 +136,11 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
133136
// Execute the request
134137
resp, err := c.httpClient.Do(req)
135138
if err != nil {
136-
log.Error("Failed to send multipart request",
137-
"method", method,
138-
"endpoint", endpoint,
139-
"error", err.Error(),
139+
log.Error("Failed to send retryable request",
140+
zap.String("method", method),
141+
zap.String("endpoint", endpoint),
142+
zap.Int("status_code", resp.StatusCode),
143+
zap.String("status_text", http.StatusText(resp.StatusCode)),
140144
)
141145
return nil, err
142146
}
@@ -153,85 +157,88 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
153157

154158
// Handle (unmarshal) response with API Handler
155159
if err := apiHandler.UnmarshalResponse(resp, out, log); err != nil {
156-
switch e := err.(type) {
157-
case *APIError: // Assuming APIError is a type that includes StatusCode and Message
158-
// Log the API error with structured logging
160+
// Use type assertion to check if the error is of type *errors.APIError
161+
if apiErr, ok := err.(*errors.APIError); ok {
162+
// Log the API error with structured logging for specific APIError handling
159163
log.Error("Received an API error",
160-
"method", method,
161-
"endpoint", endpoint,
162-
"status_code", e.StatusCode,
163-
"message", e.Message,
164+
zap.String("method", method),
165+
zap.String("endpoint", endpoint),
166+
zap.Int("status_code", apiErr.StatusCode),
167+
zap.String("message", apiErr.Message),
164168
)
165-
return resp, e
166-
default:
167-
// Log the default error with structured logging
169+
return resp, apiErr // Return the typed error for further handling if needed
170+
} else {
171+
// Log other errors with structured logging for general error handling
168172
log.Error("Failed to unmarshal HTTP response",
169-
"method", method,
170-
"endpoint", endpoint,
171-
"error", err.Error(), // Convert error to string to log as a value
173+
zap.String("method", method),
174+
zap.String("endpoint", endpoint),
175+
zap.Error(err), // Use zap.Error to log the error object
172176
)
173177
return resp, err
174178
}
175179
}
176180

181+
// Successful response
177182
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
178-
// Log successful HTTP request
179183
log.Info("HTTP request succeeded",
180-
"method", method,
181-
"endpoint", endpoint,
182-
"status_code", resp.StatusCode,
184+
zap.String("method", method),
185+
zap.String("endpoint", endpoint),
186+
zap.Int("status_code", resp.StatusCode),
183187
)
184188
return resp, nil
185-
} else if resp.StatusCode == http.StatusNotFound {
186-
// Log when resource is not found
189+
} else if
190+
// Resource not found
191+
resp.StatusCode == http.StatusNotFound {
187192
log.Warn("Resource not found",
188-
"method", method,
189-
"endpoint", endpoint,
190-
"status_code", resp.StatusCode,
193+
zap.String("method", method),
194+
zap.String("endpoint", endpoint),
195+
zap.Int("status_code", resp.StatusCode),
191196
)
192-
return resp, fmt.Errorf("resource not found: %s", endpoint)
197+
// Use a centralized method for handling not found error
198+
return resp, err
193199
}
194200

195201
// Retry Logic
196-
if IsNonRetryableError(resp) {
202+
// Non-retryable error
203+
if errors.IsNonRetryableError(resp) {
197204
log.Warn("Encountered a non-retryable error",
198-
"method", method,
199-
"endpoint", endpoint,
200-
"status_code", resp.StatusCode,
201-
"description", TranslateStatusCode(resp.StatusCode),
205+
zap.String("method", method),
206+
zap.String("endpoint", endpoint),
207+
zap.Int("status_code", resp.StatusCode),
208+
zap.String("description", errors.TranslateStatusCode(resp.StatusCode)),
202209
)
203-
return resp, c.HandleAPIError(resp)
204-
} else if IsRateLimitError(resp) {
210+
return resp, errors.HandleAPIError(resp, log) // Assume this method logs the error internally
211+
} else if errors.IsRateLimitError(resp) {
205212
waitDuration := parseRateLimitHeaders(resp) // Parses headers to determine wait duration
206213
log.Warn("Encountered a rate limit error. Retrying after wait duration.",
207-
"method", method,
208-
"endpoint", endpoint,
209-
"status_code", resp.StatusCode,
210-
"wait_duration", waitDuration,
214+
zap.String("method", method),
215+
zap.String("endpoint", endpoint),
216+
zap.Int("status_code", resp.StatusCode),
217+
zap.Duration("wait_duration", waitDuration),
211218
)
212219
time.Sleep(waitDuration)
213220
i++
214221
continue // This will restart the loop, effectively "retrying" the request
215-
} else if IsTransientError(resp) {
222+
} else if errors.IsTransientError(resp) {
216223
waitDuration := calculateBackoff(i) // Calculates backoff duration
217224
log.Warn("Encountered a transient error. Retrying after backoff.",
218-
"method", method,
219-
"endpoint", endpoint,
220-
"status_code", resp.StatusCode,
221-
"wait_duration", waitDuration,
222-
"attempt", i,
225+
zap.String("method", method),
226+
zap.String("endpoint", endpoint),
227+
zap.Int("status_code", resp.StatusCode),
228+
zap.Duration("wait_duration", waitDuration),
229+
zap.Int("attempt", i),
223230
)
224231
time.Sleep(waitDuration)
225232
i++
226233
continue // This will restart the loop, effectively "retrying" the request
227234
} else {
228235
log.Error("Received unexpected error status from HTTP request",
229-
"method", method,
230-
"endpoint", endpoint,
231-
"status_code", resp.StatusCode,
232-
"description", TranslateStatusCode(resp.StatusCode),
236+
zap.String("method", method),
237+
zap.String("endpoint", endpoint),
238+
zap.Int("status_code", resp.StatusCode),
239+
zap.String("description", errors.TranslateStatusCode(resp.StatusCode)),
233240
)
234-
return resp, c.HandleAPIError(resp)
241+
return resp, errors.HandleAPIError(resp, log)
235242
}
236243
}
237244
} else {
@@ -242,10 +249,11 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
242249
// Execute the request
243250
resp, err := c.httpClient.Do(req)
244251
if err != nil {
245-
log.Error("Failed to send multipart request",
246-
"method", method,
247-
"endpoint", endpoint,
248-
"error", err.Error(),
252+
log.Error("Failed to send request",
253+
zap.String("method", method),
254+
zap.String("endpoint", endpoint),
255+
zap.Int("status_code", resp.StatusCode),
256+
zap.String("status_text", http.StatusText(resp.StatusCode)),
249257
)
250258
return nil, err
251259
}
@@ -260,24 +268,24 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
260268

261269
// Handle (unmarshal) response with API Handler
262270
if err := apiHandler.UnmarshalResponse(resp, out, log); err != nil {
263-
switch e := err.(type) {
264-
case *APIError: // Assuming APIError is a type that includes StatusCode and Message
265-
// Log the API error with structured logging
271+
// Use type assertion to check if the error is of type *errors.APIError
272+
if apiErr, ok := err.(*errors.APIError); ok {
273+
// Log the API error with structured logging for specific APIError handling
266274
log.Error("Received an API error",
267-
"method", method,
268-
"endpoint", endpoint,
269-
"status_code", e.StatusCode,
270-
"message", e.Message,
275+
zap.String("method", method),
276+
zap.String("endpoint", endpoint),
277+
zap.Int("status_code", apiErr.StatusCode),
278+
zap.String("message", apiErr.Message),
271279
)
272-
return resp, e
273-
default:
274-
// Log the default error with structured logging
280+
return resp, apiErr // Return the typed error for further handling if needed
281+
} else {
282+
// Log other errors with structured logging for general error handling
275283
log.Error("Failed to unmarshal HTTP response",
276-
"method", method,
277-
"endpoint", endpoint,
278-
"error", err.Error(), // Convert error to string to log as a value
284+
zap.String("method", method),
285+
zap.String("endpoint", endpoint),
286+
zap.Error(err), // Use zap.Error to log the error object
279287
)
280-
return resp, err
288+
return resp, err // Return the original error
281289
}
282290
}
283291

@@ -287,15 +295,16 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
287295
return resp, nil
288296
} else {
289297
// Translate the status code to a human-readable description
290-
statusDescription := TranslateStatusCode(resp.StatusCode)
291-
292-
// Log the error with structured context
293-
log.Error("Received non-success status code from HTTP request",
294-
"method", method,
295-
"endpoint", endpoint,
296-
"status_code", resp.StatusCode,
297-
"description", statusDescription,
298-
)
298+
statusDescription := errors.TranslateStatusCode(resp.StatusCode)
299+
if apiErr, ok := err.(*errors.APIError); ok {
300+
// Log the API error with structured logging for specific APIError handling
301+
log.Error("Received an API error",
302+
zap.String("method", method),
303+
zap.String("endpoint", endpoint),
304+
zap.Int("status_code", apiErr.StatusCode),
305+
zap.String("message", apiErr.Message),
306+
)
307+
}
299308

300309
// Return an error with the status code and its description
301310
return resp, fmt.Errorf("Error status code: %d - %s", resp.StatusCode, statusDescription)
@@ -331,9 +340,9 @@ func (c *Client) DoRequest(method, endpoint string, body, out interface{}, log l
331340
//
332341
// Note:
333342
// The caller should handle closing the response body when successful.
334-
func (c *Client) DoMultipartRequest(method, endpoint string, fields map[string]string, files map[string]string, out interface{}) (*http.Response, error) {
343+
func (c *Client) DoMultipartRequest(method, endpoint string, fields map[string]string, files map[string]string, out interface{}, log logger.Logger) (*http.Response, error) {
335344
// Auth Token validation check
336-
valid, err := c.ValidAuthTokenCheck()
345+
valid, err := c.ValidAuthTokenCheck(log)
337346
if err != nil || !valid {
338347
return nil, fmt.Errorf("validity of the authentication token failed with error: %w", err)
339348
}
@@ -348,7 +357,7 @@ func (c *Client) DoMultipartRequest(method, endpoint string, fields map[string]s
348357
}
349358

350359
// Construct URL using the ConstructAPIResourceEndpoint function
351-
url := apiHandler.ConstructAPIResourceEndpoint(endpoint)
360+
url := apiHandler.ConstructAPIResourceEndpoint(endpoint, log)
352361

353362
// Create the request
354363
req, err := http.NewRequest(method, url, bytes.NewBuffer(requestData))
@@ -362,41 +371,44 @@ func (c *Client) DoMultipartRequest(method, endpoint string, fields map[string]s
362371
req.Header.Set("User-Agent", GetUserAgentHeader())
363372

364373
// Debug: Print request headers if in debug mode
365-
366-
// Check if logging level is DEBUG or higher before logging headers
367-
if log.GetLogLevel() <= LogLevelDebug {
368-
// Debug: Print request headers without hiding sensitive information
369-
LogHTTPHeaders(log, req.Header, false) // Use false to display all headers
370-
}
374+
headersStr := HeadersToString(req.Header)
375+
log.Debug("HTTP Multipart Request Headers:", zap.String("headers", headersStr))
371376

372377
// Execute the request
373378
resp, err := c.httpClient.Do(req)
374379
if err != nil {
375380
log.Error("Failed to send multipart request",
376-
"method", method,
377-
"endpoint", endpoint,
378-
"error", err.Error(),
381+
zap.String("method", method),
382+
zap.String("endpoint", endpoint),
383+
zap.Int("status_code", resp.StatusCode),
384+
zap.String("status_text", http.StatusText(resp.StatusCode)),
379385
)
380386
return nil, err
381387
}
382388

383389
// Check for successful status code
384390
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
391+
// Use HandleAPIError to process the error response and log it accordingly
392+
apiErr := errors.HandleAPIError(resp, log)
393+
394+
// Log additional context about the request that led to the error
385395
log.Error("Received non-success status code from multipart request",
386-
"method", method,
387-
"endpoint", endpoint,
388-
"status_code", resp.StatusCode,
389-
"status_text", http.StatusText(resp.StatusCode),
396+
zap.String("method", method),
397+
zap.String("endpoint", endpoint),
398+
zap.Int("status_code", resp.StatusCode),
399+
zap.String("status_text", http.StatusText(resp.StatusCode)),
390400
)
391-
return resp, fmt.Errorf("received non-success status code: %d - %s", resp.StatusCode, http.StatusText(resp.StatusCode))
401+
402+
// Return the original HTTP response and the API error
403+
return resp, apiErr
392404
}
393405

394406
// Unmarshal the response
395407
if err := apiHandler.UnmarshalResponse(resp, out, log); err != nil {
396408
log.Error("Failed to unmarshal HTTP response",
397-
"method", method,
398-
"endpoint", endpoint,
399-
"error", err.Error(),
409+
zap.String("method", method),
410+
zap.String("endpoint", endpoint),
411+
zap.String("error", err.Error()),
400412
)
401413
return resp, err
402414
}

0 commit comments

Comments
 (0)