Skip to content

Commit bc6c78b

Browse files
committed
Return proper http response code based on retryable error
1 parent f7c380d commit bc6c78b

File tree

7 files changed

+219
-77
lines changed

7 files changed

+219
-77
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package errors // import "go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
5+
6+
import (
7+
"net/http"
8+
9+
"google.golang.org/grpc/codes"
10+
"google.golang.org/grpc/status"
11+
12+
"go.opentelemetry.io/collector/consumer/consumererror"
13+
)
14+
15+
func GetStatusFromError(err error) *status.Status {
16+
s, ok := status.FromError(err)
17+
if !ok {
18+
// Default to a retryable error
19+
// https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#failures
20+
code := codes.Unavailable
21+
if consumererror.IsPermanent(err) {
22+
code = codes.InvalidArgument
23+
}
24+
s = status.New(code, err.Error())
25+
}
26+
return s
27+
}
28+
29+
func GetHTTPStatusCodeFromStatus(err error) int {
30+
s, ok := status.FromError(err)
31+
if !ok {
32+
return http.StatusInternalServerError
33+
}
34+
// See https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#failures
35+
// to see if a code is retryable.
36+
// See https://github.com/open-telemetry/opentelemetry-proto/blob/main/docs/specification.md#failures-1
37+
// to see a list of retryable http status codes.
38+
switch s.Code() {
39+
// Retryable
40+
case codes.Canceled, codes.DeadlineExceeded, codes.Aborted, codes.OutOfRange, codes.Unavailable, codes.DataLoss:
41+
return http.StatusServiceUnavailable
42+
// Not Retryable
43+
default:
44+
return http.StatusInternalServerError
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package errors // import "go.opentelemetry.io/collector/receiver/otlpreceiver/internal/util"
5+
6+
import (
7+
"fmt"
8+
"net/http"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"google.golang.org/grpc/codes"
13+
"google.golang.org/grpc/status"
14+
15+
"go.opentelemetry.io/collector/consumer/consumererror"
16+
)
17+
18+
func Test_GetStatusFromError(t *testing.T) {
19+
tests := []struct {
20+
name string
21+
input error
22+
expected *status.Status
23+
}{
24+
{
25+
name: "Status",
26+
input: status.Error(codes.Aborted, "test"),
27+
expected: status.New(codes.Aborted, "test"),
28+
},
29+
{
30+
name: "Permanent Error",
31+
input: consumererror.NewPermanent(fmt.Errorf("test")),
32+
expected: status.New(codes.InvalidArgument, "Permanent error: test"),
33+
},
34+
{
35+
name: "Non-Permanent Error",
36+
input: fmt.Errorf("test"),
37+
expected: status.New(codes.Unavailable, "test"),
38+
},
39+
}
40+
for _, tt := range tests {
41+
t.Run(tt.name, func(t *testing.T) {
42+
result := GetStatusFromError(tt.input)
43+
assert.Equal(t, tt.expected, result)
44+
})
45+
}
46+
}
47+
48+
func Test_GetHTTPStatusCodeFromStatus(t *testing.T) {
49+
tests := []struct {
50+
name string
51+
input error
52+
expected int
53+
}{
54+
{
55+
name: "Not a Status",
56+
input: fmt.Errorf("not a status error"),
57+
expected: http.StatusInternalServerError,
58+
},
59+
{
60+
name: "Retryable Status",
61+
input: status.New(codes.Unavailable, "test").Err(),
62+
expected: http.StatusServiceUnavailable,
63+
},
64+
{
65+
name: "Non-retryable Status",
66+
input: status.New(codes.InvalidArgument, "test").Err(),
67+
expected: http.StatusInternalServerError,
68+
},
69+
}
70+
for _, tt := range tests {
71+
t.Run(tt.name, func(t *testing.T) {
72+
result := GetHTTPStatusCodeFromStatus(tt.input)
73+
assert.Equal(t, tt.expected, result)
74+
})
75+
}
76+
}

receiver/otlpreceiver/internal/logs/otlp.go

+2-13
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ package logs // import "go.opentelemetry.io/collector/receiver/otlpreceiver/inte
66
import (
77
"context"
88

9-
"google.golang.org/grpc/codes"
10-
"google.golang.org/grpc/status"
11-
129
"go.opentelemetry.io/collector/consumer"
13-
"go.opentelemetry.io/collector/consumer/consumererror"
1410
"go.opentelemetry.io/collector/pdata/plog/plogotlp"
11+
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
1512
"go.opentelemetry.io/collector/receiver/receiverhelper"
1613
)
1714

@@ -51,15 +48,7 @@ func (r *Receiver) Export(ctx context.Context, req plogotlp.ExportRequest) (plog
5148
// NonPermanent errors will be converted to codes.Unavailable (equivalent to HTTP 503)
5249
// Permanent errors will be converted to codes.InvalidArgument (equivalent to HTTP 400)
5350
if err != nil {
54-
s, ok := status.FromError(err)
55-
if !ok {
56-
code := codes.Unavailable
57-
if consumererror.IsPermanent(err) {
58-
code = codes.InvalidArgument
59-
}
60-
s = status.New(code, err.Error())
61-
}
62-
return plogotlp.NewExportResponse(), s.Err()
51+
return plogotlp.NewExportResponse(), errors.GetStatusFromError(err).Err()
6352
}
6453

6554
return plogotlp.NewExportResponse(), nil

receiver/otlpreceiver/internal/metrics/otlp.go

+2-13
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ package metrics // import "go.opentelemetry.io/collector/receiver/otlpreceiver/i
66
import (
77
"context"
88

9-
"google.golang.org/grpc/codes"
10-
"google.golang.org/grpc/status"
11-
129
"go.opentelemetry.io/collector/consumer"
13-
"go.opentelemetry.io/collector/consumer/consumererror"
1410
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
11+
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
1512
"go.opentelemetry.io/collector/receiver/receiverhelper"
1613
)
1714

@@ -51,15 +48,7 @@ func (r *Receiver) Export(ctx context.Context, req pmetricotlp.ExportRequest) (p
5148
// NonPermanent errors will be converted to codes.Unavailable (equivalent to HTTP 503)
5249
// Permanent errors will be converted to codes.InvalidArgument (equivalent to HTTP 400)
5350
if err != nil {
54-
s, ok := status.FromError(err)
55-
if !ok {
56-
code := codes.Unavailable
57-
if consumererror.IsPermanent(err) {
58-
code = codes.InvalidArgument
59-
}
60-
s = status.New(code, err.Error())
61-
}
62-
return pmetricotlp.NewExportResponse(), s.Err()
51+
return pmetricotlp.NewExportResponse(), errors.GetStatusFromError(err).Err()
6352
}
6453

6554
return pmetricotlp.NewExportResponse(), nil

receiver/otlpreceiver/internal/trace/otlp.go

+2-13
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,9 @@ package trace // import "go.opentelemetry.io/collector/receiver/otlpreceiver/int
66
import (
77
"context"
88

9-
"google.golang.org/grpc/codes"
10-
"google.golang.org/grpc/status"
11-
129
"go.opentelemetry.io/collector/consumer"
13-
"go.opentelemetry.io/collector/consumer/consumererror"
1410
"go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp"
11+
"go.opentelemetry.io/collector/receiver/otlpreceiver/internal/errors"
1512
"go.opentelemetry.io/collector/receiver/receiverhelper"
1613
)
1714

@@ -52,15 +49,7 @@ func (r *Receiver) Export(ctx context.Context, req ptraceotlp.ExportRequest) (pt
5249
// NonPermanent errors will be converted to codes.Unavailable (equivalent to HTTP 503)
5350
// Permanent errors will be converted to codes.InvalidArgument (equivalent to HTTP 400)
5451
if err != nil {
55-
s, ok := status.FromError(err)
56-
if !ok {
57-
code := codes.Unavailable
58-
if consumererror.IsPermanent(err) {
59-
code = codes.InvalidArgument
60-
}
61-
s = status.New(code, err.Error())
62-
}
63-
return ptraceotlp.NewExportResponse(), s.Err()
52+
return ptraceotlp.NewExportResponse(), errors.GetStatusFromError(err).Err()
6453
}
6554

6655
return ptraceotlp.NewExportResponse(), nil

0 commit comments

Comments
 (0)