Skip to content
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

Use the otel ErrorHandler for handling errors. #196

Merged
merged 6 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions exporter/trace/cloudtrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import (
"context"
"errors"
"fmt"
"log"
"time"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
sdktrace "go.opentelemetry.io/otel/sdk/trace"

Expand All @@ -45,11 +45,21 @@ type options struct {
// project, e.g. on-premise resource like k8s_container or generic_task.
ProjectID string

// Location is the identifier of the GCP or AWS cloud region/zone in which
// the data for a resource is stored.
// If not set, it will default to the location provided by the metadata server.
//
// It will be used in the location label of a Stackdriver monitored resource
// if the resource does not inherently belong to a specific project, e.g.
// on-premise resource like k8s_container or generic_task.
Location string

// OnError is the hook to be called when there is
// an error uploading the stats or tracing data.
// If no custom hook is set, errors are logged.
// If no custom hook is set, errors are handled with the
// OpenTelemetry global handler, which defaults to logging.
// Optional.
OnError func(err error)
errorHandler otel.ErrorHandler

// TraceClientOptions are additional options to be passed
// to the underlying Stackdriver Trace API client.
Expand Down Expand Up @@ -86,12 +96,12 @@ func WithProjectID(projectID string) func(o *options) {
}
}

// WithOnError sets the hook to be called when there is an error
// WithErrorHandler sets the hook to be called when there is an error
// occurred on uploading the span data to Stackdriver.
// If no custom hook is set, errors are logged.
func WithOnError(onError func(err error)) func(o *options) {
func WithErrorHandler(handler otel.ErrorHandler) func(o *options) {
return func(o *options) {
o.OnError = onError
o.errorHandler = handler
}
}

Expand All @@ -118,11 +128,11 @@ func WithTimeout(t time.Duration) func(o *options) {
}

func (o *options) handleError(err error) {
if o.OnError != nil {
o.OnError(err)
if o.errorHandler != nil {
o.errorHandler.Handle(err)
return
}
log.Printf("Failed to export to Stackdriver: %v", err)
otel.Handle(err)
}

// defaultTimeout is used as default when timeout is not set in newContextWithTimout.
Expand Down
19 changes: 13 additions & 6 deletions exporter/trace/cloudtrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,22 +82,29 @@ func TestExporter_ExportSpan(t *testing.T) {
assert.Len(t, mock.GetSpan(1).Links.Link, 1)
}

type errorHandler struct {
errs []error
}

func (e *errorHandler) Handle(err error) {
e.errs = append(e.errs, err)
}

func TestExporter_Timeout(t *testing.T) {
// Initial test precondition
mock := cloudmock.NewCloudMock()
mock.SetDelay(20 * time.Millisecond)
clientOpt := []option.ClientOption{option.WithGRPCConn(mock.ClientConn())}
var exportErrors []error
handler := &errorHandler{}

// Create Google Cloud Trace Exporter
exporter, err := New(
WithProjectID("PROJECT_ID_NOT_REAL"),
WithTraceClientOptions(clientOpt),
WithTimeout(1*time.Millisecond),
// handle bundle as soon as span is received
WithOnError(func(err error) {
exportErrors = append(exportErrors, err)
}))
WithErrorHandler(handler),
)
assert.NoError(t, err)
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
Expand All @@ -113,10 +120,10 @@ func TestExporter_Timeout(t *testing.T) {
// wait for error to be handled
shutdown() // closed grpc connection
assert.EqualValues(t, 0, mock.GetNumSpans())
if got, want := len(exportErrors), 1; got != want {
if got, want := len(handler.errs), 1; got != want {
t.Fatalf("len(exportErrors) = %q; want %q", got, want)
}
got, want := exportErrors[0].Error(), "rpc error: code = (DeadlineExceeded|Unknown) desc = context deadline exceeded"
got, want := handler.errs[0].Error(), "rpc error: code = (DeadlineExceeded|Unknown) desc = context deadline exceeded"
if match, _ := regexp.MatchString(want, got); !match {
t.Fatalf("err.Error() = %q; want %q", got, want)
}
Expand Down
2 changes: 1 addition & 1 deletion exporter/trace/trace.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (e *traceExporter) uploadSpans(ctx context.Context, spans []*tracepb.Span)
if err != nil {
// TODO(ymotongpoo): handle detailed error categories
// span.SetStatus(codes.Unknown)
e.o.handleError(err)
e.o.handleError(fmt.Errorf("failed to export to Google Cloud Monitoring: %w", err))
}
return err
}
Expand Down