Skip to content

Commit 01ba339

Browse files
Preeti DewaniPreeti Dewani
authored andcommitted
parse-errormsgs-in-retryable-cases: In case of retyable scenario for http, only status code comes as a highlighter of failure, actual reason is very difficult to understand. This issue solves parsing of response body in case if we received msg
1 parent f6a5aa2 commit 01ba339

File tree

5 files changed

+50
-10
lines changed

5 files changed

+50
-10
lines changed

exporters/otlp/otlpmetric/otlpmetricgrpc/internal/otest/collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ type HTTPResponseError struct {
177177
}
178178

179179
func (e *HTTPResponseError) Error() string {
180-
return fmt.Sprintf("%d: %s", e.Status, e.Err)
180+
return e.Err.Error()
181181
}
182182

183183
func (e *HTTPResponseError) Unwrap() error { return e.Err }

exporters/otlp/otlpmetric/otlpmetrichttp/client.go

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@ import (
1414
"net/http"
1515
"net/url"
1616
"strconv"
17+
"strings"
1718
"sync"
1819
"time"
1920

2021
"google.golang.org/protobuf/proto"
2122

2223
"go.opentelemetry.io/otel"
24+
colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
25+
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
26+
2327
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal"
2428
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf"
2529
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/retry"
26-
colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1"
27-
metricpb "go.opentelemetry.io/proto/otlp/metrics/v1"
2830
)
2931

3032
type client struct {
@@ -189,11 +191,21 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou
189191
// Retry-able failure.
190192
rErr = newResponseError(resp.Header)
191193

192-
// Going to retry, drain the body to reuse the connection.
193-
if _, err := io.Copy(io.Discard, resp.Body); err != nil {
194-
_ = resp.Body.Close()
194+
// In all these cases, since the response body is
195+
// not getting parsed, actual reason of failure is
196+
// very hard to find out for the user.
197+
// If body is not empty, then we can use the msg
198+
// of underlying service as error message.
199+
var respData bytes.Buffer
200+
if _, err := io.Copy(&respData, resp.Body); err != nil {
195201
return err
196202
}
203+
204+
respStr := strings.TrimSpace(respData.String())
205+
if respStr != "" {
206+
rErr = errors.New(respStr)
207+
}
208+
197209
default:
198210
rErr = fmt.Errorf("failed to send metrics to %s: %s", request.URL, resp.Status)
199211
}

exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@ import (
1717
"github.com/stretchr/testify/assert"
1818
"github.com/stretchr/testify/require"
1919

20-
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf"
21-
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest"
2220
"go.opentelemetry.io/otel/sdk/metric"
2321
"go.opentelemetry.io/otel/sdk/metric/metricdata"
2422
mpb "go.opentelemetry.io/proto/otlp/metrics/v1"
23+
24+
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf"
25+
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest"
2526
)
2627

2728
type clientShim struct {
@@ -192,6 +193,33 @@ func TestConfig(t *testing.T) {
192193
assert.Len(t, rCh, 0, "failed HTTP responses did not occur")
193194
})
194195

196+
t.Run("WithRetryAndExporterErr", func(t *testing.T) {
197+
exporterErr := errors.New("rpc error: code = Unavailable desc = service.name not found in resource attributes")
198+
rCh := make(chan otest.ExportResult, 1)
199+
header := http.Header{http.CanonicalHeaderKey("Retry-After"): {"10"}}
200+
rCh <- otest.ExportResult{Err: &otest.HTTPResponseError{
201+
Status: http.StatusServiceUnavailable,
202+
Err: exporterErr,
203+
Header: header,
204+
}}
205+
206+
exp, coll := factoryFunc("", rCh, WithRetry(RetryConfig{
207+
Enabled: true,
208+
InitialInterval: time.Nanosecond,
209+
MaxInterval: time.Millisecond,
210+
MaxElapsedTime: time.Minute,
211+
}))
212+
ctx := context.Background()
213+
t.Cleanup(func() { require.NoError(t, coll.Shutdown(ctx)) })
214+
// Push this after Shutdown so the HTTP server doesn't hang.
215+
t.Cleanup(func() { close(rCh) })
216+
t.Cleanup(func() { require.NoError(t, exp.Shutdown(ctx)) })
217+
218+
target := exp.Export(ctx, &metricdata.ResourceMetrics{})
219+
assert.ErrorAs(t, fmt.Errorf("failed to upload metrics: %s", exporterErr.Error()), &target)
220+
assert.Len(t, rCh, 0, "failed HTTP responses did not occur")
221+
})
222+
195223
t.Run("WithURLPath", func(t *testing.T) {
196224
path := "/prefix/v2/metrics"
197225
ePt := fmt.Sprintf("http://localhost:0%s", path)

exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/collector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ type HTTPResponseError struct {
177177
}
178178

179179
func (e *HTTPResponseError) Error() string {
180-
return fmt.Sprintf("%d: %s", e.Status, e.Err)
180+
return e.Err.Error()
181181
}
182182

183183
func (e *HTTPResponseError) Unwrap() error { return e.Err }

internal/shared/otlp/otlpmetric/otest/collector.go.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ type HTTPResponseError struct {
177177
}
178178

179179
func (e *HTTPResponseError) Error() string {
180-
return fmt.Sprintf("%d: %s", e.Status, e.Err)
180+
return e.Err.Error()
181181
}
182182

183183
func (e *HTTPResponseError) Unwrap() error { return e.Err }

0 commit comments

Comments
 (0)