Skip to content

Commit 22a260e

Browse files
authored
Merge pull request #98 from deploymenttheory/dev
Dev
2 parents 20d595a + d71dfbd commit 22a260e

6 files changed

+76
-90
lines changed

httpclient/httpclient_auth_bearer_token.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package httpclient
66

77
import (
88
"encoding/json"
9+
"fmt"
910
"net/http"
1011
"time"
1112

@@ -39,21 +40,21 @@ func (c *Client) ObtainToken(log logger.Logger) error {
3940

4041
req, err := http.NewRequest("POST", authenticationEndpoint, nil)
4142
if err != nil {
42-
log.LogError("POST", authenticationEndpoint, 0, err, "Failed to create new request for token")
43+
log.LogError("authentication_request_creation_error", "POST", authenticationEndpoint, 0, err, "Failed to create new request for token")
4344
return err
4445
}
4546
req.SetBasicAuth(c.BearerTokenAuthCredentials.Username, c.BearerTokenAuthCredentials.Password)
4647

4748
resp, err := c.httpClient.Do(req)
4849
if err != nil {
49-
log.LogError("POST", authenticationEndpoint, 0, err, "Failed to make request for token")
50+
log.LogError("authentication_request_error", "POST", authenticationEndpoint, 0, err, "Failed to make request for token")
5051
return err
5152
}
5253
defer resp.Body.Close()
5354

5455
if resp.StatusCode != http.StatusOK {
55-
log.Error("Received non-OK response while obtaining token", zap.Int("StatusCode", resp.StatusCode))
56-
return err
56+
log.LogError("token_authentication_failed", "POST", authenticationEndpoint, resp.StatusCode, fmt.Errorf("authentication failed with status code: %d", resp.StatusCode), "Token acquisition attempt resulted in a non-OK response")
57+
return fmt.Errorf("received non-OK response status: %d", resp.StatusCode)
5758
}
5859

5960
tokenResp := &TokenResponse{}

httpclient/httpclient_error_response.go

+29-51
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"net/http"
1010

1111
"github.com/deploymenttheory/go-api-http-client/logger"
12-
"go.uber.org/zap"
1312
)
1413

1514
// APIError represents a more flexible structure for API error responses.
@@ -35,68 +34,47 @@ func (e *APIError) Error() string {
3534
return fmt.Sprintf("API Error (Type: %s, Code: %d): %s", e.Type, e.StatusCode, e.Message)
3635
}
3736

38-
// handleAPIErrorResponse attempts to parse the error response from the API and logs using zap logger.
37+
// handleAPIErrorResponse attempts to parse the error response from the API and logs using the zap logger.
3938
func handleAPIErrorResponse(resp *http.Response, log logger.Logger) *APIError {
40-
apiError := &APIError{StatusCode: resp.StatusCode}
41-
42-
// Attempt to parse the response into a StructuredError
43-
var structuredErr StructuredError
44-
if err := json.NewDecoder(resp.Body).Decode(&structuredErr); err == nil && structuredErr.Error.Message != "" {
45-
apiError.Type = structuredErr.Error.Code
46-
apiError.Message = structuredErr.Error.Message
47-
48-
// Log the structured error details with zap logger
49-
log.Warn("API returned structured error",
50-
zap.String("error_code", structuredErr.Error.Code),
51-
zap.String("error_message", structuredErr.Error.Message),
52-
zap.Int("status_code", resp.StatusCode),
53-
)
54-
55-
return apiError
39+
apiError := &APIError{
40+
StatusCode: resp.StatusCode,
41+
Type: "APIError", // Default error type
42+
Message: "An error occurred", // Default error message
5643
}
5744

58-
// If the structured error parsing fails, attempt a more generic parsing
5945
bodyBytes, err := io.ReadAll(resp.Body)
6046
if err != nil {
61-
// If reading the response body fails, store the error message and log the error
62-
apiError.Raw = "Failed to read API error response body"
63-
apiError.Message = err.Error()
64-
apiError.Type = "ReadError"
65-
66-
log.Error("Failed to read API error response body",
67-
zap.Error(err),
68-
)
69-
47+
// Log and return an error if reading the body fails
48+
log.LogError("api_response_read_error", "READ", resp.Request.URL.String(), resp.StatusCode, err, "")
7049
return apiError
7150
}
7251

73-
if err := json.Unmarshal(bodyBytes, &apiError.Errors); err != nil {
74-
// If generic parsing also fails, store the raw response body and log the error
75-
apiError.Raw = string(bodyBytes)
76-
apiError.Message = "Failed to parse API error response"
77-
apiError.Type = "UnexpectedError"
78-
79-
log.Error("Failed to parse API error response",
80-
zap.String("raw_response", apiError.Raw),
81-
)
82-
52+
if err := json.Unmarshal(bodyBytes, &apiError); err == nil && apiError.Message != "" {
53+
// Log the structured error
54+
log.LogError("api_structured_error", "API", resp.Request.URL.String(), resp.StatusCode, fmt.Errorf(apiError.Message), "")
8355
return apiError
8456
}
8557

86-
// Extract fields from the generic error map and log the error with extracted details
87-
if msg, ok := apiError.Errors["message"].(string); ok {
88-
apiError.Message = msg
89-
}
90-
if detail, ok := apiError.Errors["detail"].(string); ok {
91-
apiError.Detail = detail
58+
// If structured parsing fails, attempt to parse into a generic error map
59+
var genericErr map[string]interface{}
60+
if err := json.Unmarshal(bodyBytes, &genericErr); err == nil {
61+
apiError.updateFromGenericError(genericErr)
62+
// Log the error with extracted details
63+
log.LogError("api_generic_error", "API", resp.Request.URL.String(), resp.StatusCode, fmt.Errorf(apiError.Message), "")
64+
return apiError
9265
}
9366

94-
log.Error("API error",
95-
zap.Int("status_code", apiError.StatusCode),
96-
zap.String("type", apiError.Type),
97-
zap.String("message", apiError.Message),
98-
zap.String("detail", apiError.Detail),
99-
)
100-
67+
// If all parsing attempts fail, log the raw response
68+
log.LogError("api_unexpected_error", "API", resp.Request.URL.String(), resp.StatusCode, fmt.Errorf("failed to parse API error response"), string(bodyBytes))
10169
return apiError
10270
}
71+
72+
func (e *APIError) updateFromGenericError(genericErr map[string]interface{}) {
73+
if msg, ok := genericErr["message"].(string); ok {
74+
e.Message = msg
75+
}
76+
if detail, ok := genericErr["detail"].(string); ok {
77+
e.Detail = detail
78+
}
79+
// Add more fields as needed
80+
}

httpclient/httpclient_mocklogger.go

+16-12
Original file line numberDiff line numberDiff line change
@@ -69,26 +69,30 @@ func (m *MockLogger) GetLogLevel() logger.LogLevel {
6969

7070
// Mock implementations for structured logging methods
7171

72-
func (m *MockLogger) LogRequestStart(requestID string, userID string, method string, url string, headers map[string][]string) {
73-
m.Called(requestID, userID, method, url, headers)
72+
func (m *MockLogger) LogRequestStart(event string, requestID string, userID string, method string, url string, headers map[string][]string) {
73+
m.Called(event, requestID, userID, method, url, headers)
7474
}
7575

76-
func (m *MockLogger) LogRequestEnd(method string, url string, statusCode int, duration time.Duration) {
77-
m.Called(method, url, statusCode, duration)
76+
func (m *MockLogger) LogRequestEnd(event string, method string, url string, statusCode int, duration time.Duration) {
77+
m.Called(event, method, url, statusCode, duration)
7878
}
7979

80-
func (m *MockLogger) LogError(method string, url string, statusCode int, err error, stacktrace string) {
81-
m.Called(method, url, statusCode, err, stacktrace)
80+
func (m *MockLogger) LogError(event string, method string, url string, statusCode int, err error, stacktrace string) {
81+
m.Called(event, method, url, statusCode, err, stacktrace)
8282
}
8383

84-
func (m *MockLogger) LogRetryAttempt(method string, url string, attempt int, reason string, waitDuration time.Duration, err error) {
85-
m.Called(method, url, attempt, reason, waitDuration, err)
84+
func (m *MockLogger) LogAuthTokenError(event string, method string, url string, statusCode int, err error) {
85+
m.Called(event, method, url, statusCode, err)
8686
}
8787

88-
func (m *MockLogger) LogRateLimiting(method string, url string, retryAfter string, waitDuration time.Duration) {
89-
m.Called(method, url, retryAfter, waitDuration)
88+
func (m *MockLogger) LogRetryAttempt(event string, method string, url string, attempt int, reason string, waitDuration time.Duration, err error) {
89+
m.Called(event, method, url, attempt, reason, waitDuration, err)
9090
}
9191

92-
func (m *MockLogger) LogResponse(method string, url string, statusCode int, responseBody string, responseHeaders map[string][]string, duration time.Duration) {
93-
m.Called(method, url, statusCode, responseBody, responseHeaders, duration)
92+
func (m *MockLogger) LogRateLimiting(event string, method string, url string, retryAfter string, waitDuration time.Duration) {
93+
m.Called(event, method, url, retryAfter, waitDuration)
94+
}
95+
96+
func (m *MockLogger) LogResponse(event string, method string, url string, statusCode int, responseBody string, responseHeaders map[string][]string, duration time.Duration) {
97+
m.Called(event, method, url, statusCode, responseBody, responseHeaders, duration)
9498
}

httpclient/httpclient_request.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ func (c *Client) executeRequestWithRetries(method, endpoint string, body, out in
207207
if apiErr := handleAPIErrorResponse(resp, log); apiErr != nil {
208208
err = apiErr
209209
}
210-
log.LogError(method, endpoint, resp.StatusCode, err, status.TranslateStatusCode(resp))
210+
log.LogError("request_error", method, endpoint, resp.StatusCode, err, status.TranslateStatusCode(resp))
211+
211212
break
212213
}
213214
}

logger/zaplogger_logfields.go

+16-16
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import (
77
)
88

99
// LogRequestStart logs the initiation of an HTTP request if the current log level permits.
10-
func (d *defaultLogger) LogRequestStart(requestID string, userID string, method string, url string, headers map[string][]string) {
10+
func (d *defaultLogger) LogRequestStart(event string, requestID string, userID string, method string, url string, headers map[string][]string) {
1111
if d.logLevel <= LogLevelInfo {
1212
fields := []zap.Field{
13-
zap.String("event", "request_start"),
13+
zap.String("event", event),
1414
zap.String("method", method),
1515
zap.String("url", url),
1616
zap.String("request_id", requestID),
@@ -22,10 +22,10 @@ func (d *defaultLogger) LogRequestStart(requestID string, userID string, method
2222
}
2323

2424
// LogRequestEnd logs the completion of an HTTP request if the current log level permits.
25-
func (d *defaultLogger) LogRequestEnd(method string, url string, statusCode int, duration time.Duration) {
25+
func (d *defaultLogger) LogRequestEnd(event string, method string, url string, statusCode int, duration time.Duration) {
2626
if d.logLevel <= LogLevelInfo {
2727
fields := []zap.Field{
28-
zap.String("event", "request_end"),
28+
zap.String("event", event),
2929
zap.String("method", method),
3030
zap.String("url", url),
3131
zap.Int("status_code", statusCode),
@@ -35,31 +35,31 @@ func (d *defaultLogger) LogRequestEnd(method string, url string, statusCode int,
3535
}
3636
}
3737

38-
// LogError logs an error that occurs during the processing of an HTTP request if the current log level permits.
39-
func (d *defaultLogger) LogError(method string, url string, statusCode int, err error, stacktrace string) {
38+
// LogError logs an error that occurs during the processing of an HTTP request or any other event, if the current log level permits.
39+
func (d *defaultLogger) LogError(event string, method, url string, statusCode int, err error, stacktrace string) {
4040
if d.logLevel <= LogLevelError {
4141
errorMessage := ""
4242
if err != nil {
4343
errorMessage = err.Error()
4444
}
4545

4646
fields := []zap.Field{
47-
zap.String("event", "request_error"),
47+
zap.String("event", event),
4848
zap.String("method", method),
4949
zap.String("url", url),
5050
zap.Int("status_code", statusCode),
5151
zap.String("error_message", errorMessage),
5252
zap.String("stacktrace", stacktrace),
5353
}
54-
d.logger.Error("Error during HTTP request", fields...)
54+
d.logger.Error("Error occurred", fields...)
5555
}
5656
}
5757

5858
// LogAuthTokenError logs issues encountered during the authentication token acquisition process.
59-
func (d *defaultLogger) LogAuthTokenError(method string, url string, statusCode int, err error) {
59+
func (d *defaultLogger) LogAuthTokenError(event string, method string, url string, statusCode int, err error) {
6060
if d.logLevel <= LogLevelError {
6161
fields := []zap.Field{
62-
zap.String("event", "auth_token_error"),
62+
zap.String("event", event),
6363
zap.String("method", method),
6464
zap.String("url", url),
6565
zap.Int("status_code", statusCode),
@@ -70,10 +70,10 @@ func (d *defaultLogger) LogAuthTokenError(method string, url string, statusCode
7070
}
7171

7272
// LogRetryAttempt logs a retry attempt for an HTTP request if the current log level permits, including wait duration and the error that triggered the retry.
73-
func (d *defaultLogger) LogRetryAttempt(method string, url string, attempt int, reason string, waitDuration time.Duration, err error) {
73+
func (d *defaultLogger) LogRetryAttempt(event string, method string, url string, attempt int, reason string, waitDuration time.Duration, err error) {
7474
if d.logLevel <= LogLevelWarn {
7575
fields := []zap.Field{
76-
zap.String("event", "retry_attempt"),
76+
zap.String("event", event),
7777
zap.String("method", method),
7878
zap.String("url", url),
7979
zap.Int("attempt", attempt),
@@ -86,10 +86,10 @@ func (d *defaultLogger) LogRetryAttempt(method string, url string, attempt int,
8686
}
8787

8888
// LogRateLimiting logs when an HTTP request is rate-limited, including the HTTP method, URL, the value of the 'Retry-After' header, and the actual wait duration.
89-
func (d *defaultLogger) LogRateLimiting(method string, url string, retryAfter string, waitDuration time.Duration) {
89+
func (d *defaultLogger) LogRateLimiting(event string, method string, url string, retryAfter string, waitDuration time.Duration) {
9090
if d.logLevel <= LogLevelWarn {
9191
fields := []zap.Field{
92-
zap.String("event", "rate_limited"),
92+
zap.String("event", event),
9393
zap.String("method", method),
9494
zap.String("url", url),
9595
zap.String("retry_after", retryAfter),
@@ -100,10 +100,10 @@ func (d *defaultLogger) LogRateLimiting(method string, url string, retryAfter st
100100
}
101101

102102
// LogResponse logs details about an HTTP response if the current log level permits.
103-
func (d *defaultLogger) LogResponse(method string, url string, statusCode int, responseBody string, responseHeaders map[string][]string, duration time.Duration) {
103+
func (d *defaultLogger) LogResponse(event string, method string, url string, statusCode int, responseBody string, responseHeaders map[string][]string, duration time.Duration) {
104104
if d.logLevel <= LogLevelInfo {
105105
fields := []zap.Field{
106-
zap.String("event", "response_received"),
106+
zap.String("event", event),
107107
zap.String("method", method),
108108
zap.String("url", url),
109109
zap.Int("status_code", statusCode),

logger/zaplogger_logger.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ type Logger interface {
3131
Panic(msg string, fields ...zapcore.Field)
3232
Fatal(msg string, fields ...zapcore.Field)
3333

34-
LogRequestStart(requestID string, userID string, method string, url string, headers map[string][]string)
35-
LogRequestEnd(method string, url string, statusCode int, duration time.Duration)
36-
LogError(method string, url string, statusCode int, err error, stacktrace string)
37-
LogRetryAttempt(method string, url string, attempt int, reason string, waitDuration time.Duration, err error)
38-
LogRateLimiting(method string, url string, retryAfter string, waitDuration time.Duration)
39-
LogResponse(method string, url string, statusCode int, responseBody string, responseHeaders map[string][]string, duration time.Duration)
34+
// Updated method signatures to include the 'event' parameter
35+
LogRequestStart(event string, requestID string, userID string, method string, url string, headers map[string][]string)
36+
LogRequestEnd(event string, method string, url string, statusCode int, duration time.Duration)
37+
LogError(event string, method string, url string, statusCode int, err error, stacktrace string)
38+
LogAuthTokenError(event string, method string, url string, statusCode int, err error)
39+
LogRetryAttempt(event string, method string, url string, attempt int, reason string, waitDuration time.Duration, err error)
40+
LogRateLimiting(event string, method string, url string, retryAfter string, waitDuration time.Duration)
41+
LogResponse(event string, method string, url string, statusCode int, responseBody string, responseHeaders map[string][]string, duration time.Duration)
4042
}
4143

4244
// GetLogLevel returns the current logging level of the logger. This allows for checking the logger's

0 commit comments

Comments
 (0)