-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[ffresty] [metric] HTTP Response Time and Complete Gauge Support #160
base: main
Are you sure you want to change the base?
Changes from all commits
9b99686
fd3c3db
f1ada49
29bfa10
f6fc2ac
c5821a0
4efbf8c
fb8c447
27ea4fe
1b2e68b
4ac2fee
052c797
8180cc5
1d3ce59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,4 +4,6 @@ coverage.txt | |
**/debug.test | ||
.DS_Store | ||
__debug* | ||
.vscode/*.log | ||
.vscode/*.log | ||
*.iml | ||
.idea/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,7 +40,14 @@ | |
"golang.org/x/time/rate" | ||
) | ||
|
||
const ( | ||
metricsHTTPResponsesTotal = "http_responses_total" | ||
metricsNetworkErrorsTotal = "network_errors_total" | ||
metricsHTTPResponseTime = "http_response_time_seconds" | ||
) | ||
|
||
type retryCtxKey struct{} | ||
type hostCtxKey struct{} | ||
|
||
type retryCtx struct { | ||
id string | ||
|
@@ -98,27 +105,21 @@ | |
if err != nil { | ||
return err | ||
} | ||
metricsManager.NewCounterMetricWithLabels(ctx, "http_response", "HTTP response", []string{"status", "error", "host", "method"}, false) | ||
metricsManager.NewCounterMetricWithLabels(ctx, "network_error", "Network error", []string{"host", "method"}, false) | ||
metricsManager.NewCounterMetricWithLabels(ctx, metricsHTTPResponsesTotal, "HTTP response", []string{"status", "error", "host", "method"}, false) | ||
metricsManager.NewCounterMetricWithLabels(ctx, metricsNetworkErrorsTotal, "Network error", []string{"host", "method"}, false) | ||
metricsManager.NewSummaryMetricWithLabels(ctx, metricsHTTPResponseTime, "HTTP response time", []string{"status", "host", "method"}, false) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @onelapahead what's your consideration between There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Histogram is more costly in terms of cardinality bc of all the buckets you make. So this felt light enough to only calculate average response time across a few basic dimensions. Would love to toggle btwn histogram and summaries based on certain settings eventually, but that was a bigger change than I had an appetite for. |
||
} | ||
|
||
// create hooks | ||
onErrorMetricsHook := func(req *resty.Request, _ error) { | ||
method := req.Method | ||
u, _ := url.Parse(req.URL) | ||
host := u.Host | ||
// whilst there it is a possibility to get an response returned in the error here (and resty doc for OnError shows this) it seems to be a special case and the statuscode in such cases was not set. | ||
// therefore we log all cases as network_error we may in future find reason to extract more detail from the error | ||
metricsManager.IncCounterMetricWithLabels(ctx, "network_error", map[string]string{"host": host, "method": method}, nil) | ||
metricsManager.IncCounterMetricWithLabels(ctx, metricsNetworkErrorsTotal, map[string]string{"host": req.Context().Value(hostCtxKey{}).(string), "method": req.Method}, nil) | ||
} | ||
RegisterGlobalOnError(onErrorMetricsHook) | ||
|
||
onSuccessMetricsHook := func(_ *resty.Client, resp *resty.Response) { | ||
method := resp.Request.Method | ||
u, _ := url.Parse(resp.Request.URL) | ||
host := u.Host | ||
code := resp.RawResponse.StatusCode | ||
metricsManager.IncCounterMetricWithLabels(ctx, "http_response", map[string]string{"status": fmt.Sprintf("%d", code), "error": "false", "host": host, "method": method}, nil) | ||
metricsManager.IncCounterMetricWithLabels(ctx, metricsHTTPResponsesTotal, map[string]string{"status": fmt.Sprintf("%d", resp.RawResponse.StatusCode), "error": "false", "host": resp.Request.Context().Value(hostCtxKey{}).(string), "method": resp.Request.Method}, nil) | ||
} | ||
RegisterGlobalOnSuccess(onSuccessMetricsHook) | ||
return nil | ||
|
@@ -142,13 +143,17 @@ | |
} | ||
rCtx := resp.Request.Context() | ||
rc := rCtx.Value(retryCtxKey{}).(*retryCtx) | ||
elapsed := float64(time.Since(rc.start)) / float64(time.Millisecond) | ||
elapsed := time.Since(rc.start) | ||
level := logrus.DebugLevel | ||
status := resp.StatusCode() | ||
if status >= 300 { | ||
level = logrus.ErrorLevel | ||
} | ||
log.L(rCtx).Logf(level, "<== %s %s [%d] (%.2fms)", resp.Request.Method, resp.Request.URL, status, elapsed) | ||
log.L(rCtx).Logf(level, "<== %s %s [%d] (%dms)", resp.Request.Method, resp.Request.URL, status, time.Since(rc.start).Milliseconds()) | ||
if metricsManager != nil { | ||
metricsManager.ObserveSummaryMetricWithLabels(rCtx, metricsHTTPResponseTime, elapsed.Seconds(), map[string]string{"status": fmt.Sprintf("%d", status), "host": rCtx.Value(hostCtxKey{}).(string), "method": resp.Request.Method}, nil) | ||
} | ||
// TODO use req.TraceInfo() for richer metrics at the DNS and transport layer | ||
} | ||
|
||
func OnError(req *resty.Request, err error) { | ||
|
@@ -193,7 +198,7 @@ | |
// | ||
// You can use the normal Resty builder pattern, to set per-instance configuration | ||
// as required. | ||
func NewWithConfig(ctx context.Context, ffrestyConfig Config) (client *resty.Client) { | ||
if ffrestyConfig.HTTPCustomClient != nil { | ||
if httpClient, ok := ffrestyConfig.HTTPCustomClient.(*http.Client); ok { | ||
client = resty.NewWithClient(httpClient) | ||
|
@@ -232,10 +237,10 @@ | |
|
||
rateLimiterMap[client] = GetRateLimiter(ffrestyConfig.ThrottleRequestsPerSecond, ffrestyConfig.ThrottleBurst) | ||
|
||
url := strings.TrimSuffix(ffrestyConfig.URL, "/") | ||
if url != "" { | ||
client.SetBaseURL(url) | ||
log.L(ctx).Debugf("Created REST client to %s", url) | ||
_url := strings.TrimSuffix(ffrestyConfig.URL, "/") | ||
if _url != "" { | ||
client.SetBaseURL(_url) | ||
log.L(ctx).Debugf("Created REST client to %s", _url) | ||
} | ||
|
||
if ffrestyConfig.ProxyURL != "" { | ||
|
@@ -244,7 +249,7 @@ | |
|
||
client.SetTimeout(time.Duration(ffrestyConfig.HTTPRequestTimeout)) | ||
|
||
client.OnBeforeRequest(func(_ *resty.Client, req *resty.Request) error { | ||
client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error { | ||
if rateLimiterMap[client] != nil { | ||
// Wait for permission to proceed with the request | ||
err := rateLimiterMap[client].Wait(req.Context()) | ||
|
@@ -253,6 +258,26 @@ | |
} | ||
} | ||
rCtx := req.Context() | ||
// Record host in context to avoid redundant parses in hooks | ||
var u *url.URL | ||
if req.URL != "" { | ||
u, _ = url.Parse(req.URL) | ||
} | ||
// The req.URL might have only set a path i.e. /home, fallbacking to the base URL of the client. | ||
// So if the URL is nil, that's likely the case and we'll derive the host from the configured | ||
// based instead. | ||
if u == nil && _url != "" { | ||
u, _ = url.Parse(_url) | ||
} | ||
if u == nil && c.BaseURL != "" { | ||
u, _ = url.Parse(c.BaseURL) | ||
} | ||
if u != nil && u.Host != "" { | ||
host := u.Host | ||
rCtx = context.WithValue(rCtx, hostCtxKey{}, host) | ||
} else { | ||
rCtx = context.WithValue(rCtx, hostCtxKey{}, "unknown") | ||
} | ||
rc := rCtx.Value(retryCtxKey{}) | ||
if rc == nil { | ||
// First attempt | ||
|
@@ -289,8 +314,9 @@ | |
} | ||
} | ||
|
||
log.L(rCtx).Debugf("==> %s %s%s", req.Method, url, req.URL) | ||
log.L(rCtx).Debugf("==> %s %s%s", req.Method, _url, req.URL) | ||
log.L(rCtx).Tracef("==> (body) %+v", req.Body) | ||
|
||
return nil | ||
}) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some size metrics for data transmitted would be useful to add as well.