Skip to content

Commit

Permalink
Error source middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
marefr committed Oct 3, 2024
1 parent 6e35428 commit b95ac03
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 14 deletions.
8 changes: 4 additions & 4 deletions backend/adapter_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func wrapHandler(ctx context.Context, pluginCtx PluginContext, next handlerWrapp
}

func setupHandlerContext(ctx context.Context, pluginCtx PluginContext) context.Context {
ctx = initErrorSource(ctx)
ctx = InitErrorSource(ctx)
ctx = WithGrafanaConfig(ctx, pluginCtx.GrafanaConfig)
ctx = WithPluginContext(ctx, pluginCtx)
ctx = WithUser(ctx, pluginCtx.User)
Expand Down Expand Up @@ -75,7 +75,7 @@ func metricWrapper(next handlerWrapperFunc) handlerWrapperFunc {
endpoint := EndpointFromContext(ctx)
status, err := next(ctx)

pluginRequestCounter.WithLabelValues(endpoint.String(), status.String(), string(errorSourceFromContext(ctx))).Inc()
pluginRequestCounter.WithLabelValues(endpoint.String(), status.String(), string(ErrorSourceFromContext(ctx))).Inc()

return status, err
}
Expand Down Expand Up @@ -106,7 +106,7 @@ func tracingWrapper(next handlerWrapperFunc) handlerWrapperFunc {

span.SetAttributes(
attribute.String("request_status", status.String()),
attribute.String("status_source", string(errorSourceFromContext(ctx))),
attribute.String("status_source", string(ErrorSourceFromContext(ctx))),
)

if err != nil {
Expand Down Expand Up @@ -136,7 +136,7 @@ func logWrapper(next handlerWrapperFunc) handlerWrapperFunc {
logParams = append(logParams, "error", err)
}

logParams = append(logParams, "statusSource", string(errorSourceFromContext(ctx)))
logParams = append(logParams, "statusSource", string(ErrorSourceFromContext(ctx)))

if status > RequestStatusCancelled {
logFunc = ctxLogger.Error
Expand Down
8 changes: 4 additions & 4 deletions backend/adapter_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

func TestErrorWrapper(t *testing.T) {
t.Run("No downstream error should not set downstream error source in context", func(t *testing.T) {
ctx := initErrorSource(context.Background())
ctx := InitErrorSource(context.Background())

actualErr := errors.New("BOOM")
wrapper := errorWrapper(func(_ context.Context) (RequestStatus, error) {
Expand All @@ -19,11 +19,11 @@ func TestErrorWrapper(t *testing.T) {
status, err := wrapper(ctx)
require.ErrorIs(t, err, actualErr)
require.Equal(t, RequestStatusError, status)
require.Equal(t, DefaultErrorSource, errorSourceFromContext(ctx))
require.Equal(t, DefaultErrorSource, ErrorSourceFromContext(ctx))
})

t.Run("Downstream error should set downstream error source in context", func(t *testing.T) {
ctx := initErrorSource(context.Background())
ctx := InitErrorSource(context.Background())

actualErr := errors.New("BOOM")
wrapper := errorWrapper(func(_ context.Context) (RequestStatus, error) {
Expand All @@ -32,6 +32,6 @@ func TestErrorWrapper(t *testing.T) {
status, err := wrapper(ctx)
require.ErrorIs(t, err, actualErr)
require.Equal(t, RequestStatusError, status)
require.Equal(t, ErrorSourceDownstream, errorSourceFromContext(ctx))
require.Equal(t, ErrorSourceDownstream, ErrorSourceFromContext(ctx))
})
}
2 changes: 1 addition & 1 deletion backend/data_adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func TestQueryData(t *testing.T) {
require.NoError(t, err)
}

ss := errorSourceFromContext(actualCtx)
ss := ErrorSourceFromContext(actualCtx)
require.Equal(t, tc.expErrorSource, ss)
})
}
Expand Down
8 changes: 4 additions & 4 deletions backend/error_source.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,18 @@ func (e errorWithSourceImpl) Unwrap() error {

type errorSourceCtxKey struct{}

// errorSourceFromContext returns the error source stored in the context.
// ErrorSourceFromContext returns the error source stored in the context.
// If no error source is stored in the context, [DefaultErrorSource] is returned.
func errorSourceFromContext(ctx context.Context) ErrorSource {
func ErrorSourceFromContext(ctx context.Context) ErrorSource {
value, ok := ctx.Value(errorSourceCtxKey{}).(*ErrorSource)
if ok {
return *value
}
return DefaultErrorSource
}

// initErrorSource initialize the status source for the context.
func initErrorSource(ctx context.Context) context.Context {
// InitErrorSource initialize the error source for the context.
func InitErrorSource(ctx context.Context) context.Context {
s := DefaultErrorSource
return context.WithValue(ctx, errorSourceCtxKey{}, &s)
}
Expand Down
128 changes: 128 additions & 0 deletions backend/error_source_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package backend

import (
"context"
"errors"
"fmt"
)

// NewErrorSourceMiddleware returns a new backend.HandlerMiddleware that sets the error source in the
// context.Context, based on returned errors or query data response errors.
// If at least one query data response has a "downstream" error source and there isn't one with a "plugin" error source,
// the error source in the context is set to "downstream".
func NewErrorSourceMiddleware() HandlerMiddleware {
return HandlerMiddlewareFunc(func(next Handler) Handler {
return &ErrorSourceMiddleware{
BaseHandler: NewBaseHandler(next),
}
})
}

type ErrorSourceMiddleware struct {
BaseHandler
}

func (m *ErrorSourceMiddleware) handleDownstreamError(ctx context.Context, err error) error {
if err == nil {
return nil
}

if IsDownstreamError(err) {
if innerErr := WithDownstreamErrorSource(ctx); innerErr != nil {
return fmt.Errorf("failed to set downstream error source: %w", errors.Join(innerErr, err))
}
}

return err
}

func (m *ErrorSourceMiddleware) QueryData(ctx context.Context, req *QueryDataRequest) (*QueryDataResponse, error) {
resp, err := m.BaseHandler.QueryData(ctx, req)
err = m.handleDownstreamError(ctx, err)

if err != nil {
return resp, err
} else if resp == nil || len(resp.Responses) == 0 {
return nil, errors.New("both response and error are nil, but one must be provided")
}

// Set downstream error source in the context if there's at least one response with downstream error source,
// and if there's no plugin error
var hasPluginError bool
var hasDownstreamError bool
for _, r := range resp.Responses {
if r.Error == nil {
continue
}

// if error source not set and the error is a downstream error, set error source to downstream.
if !r.ErrorSource.IsValid() && IsDownstreamError(r.Error) {
r.ErrorSource = ErrorSourceDownstream
}

if !r.Status.IsValid() {
r.Status = statusFromError(r.Error)
}

if r.ErrorSource == ErrorSourceDownstream {
hasDownstreamError = true
} else {
hasPluginError = true
}
}

// A plugin error has higher priority than a downstream error,
// so set to downstream only if there's no plugin error
if hasDownstreamError && !hasPluginError {
if err := WithDownstreamErrorSource(ctx); err != nil {
return resp, fmt.Errorf("failed to set downstream status source: %w", err)
}
}

return resp, err
}

func (m *ErrorSourceMiddleware) CallResource(ctx context.Context, req *CallResourceRequest, sender CallResourceResponseSender) error {
err := m.BaseHandler.CallResource(ctx, req, sender)
return m.handleDownstreamError(ctx, err)
}

func (m *ErrorSourceMiddleware) CheckHealth(ctx context.Context, req *CheckHealthRequest) (*CheckHealthResult, error) {
resp, err := m.BaseHandler.CheckHealth(ctx, req)
return resp, m.handleDownstreamError(ctx, err)
}

func (m *ErrorSourceMiddleware) CollectMetrics(ctx context.Context, req *CollectMetricsRequest) (*CollectMetricsResult, error) {
resp, err := m.BaseHandler.CollectMetrics(ctx, req)
return resp, m.handleDownstreamError(ctx, err)
}

func (m *ErrorSourceMiddleware) SubscribeStream(ctx context.Context, req *SubscribeStreamRequest) (*SubscribeStreamResponse, error) {
resp, err := m.BaseHandler.SubscribeStream(ctx, req)
return resp, m.handleDownstreamError(ctx, err)
}

func (m *ErrorSourceMiddleware) PublishStream(ctx context.Context, req *PublishStreamRequest) (*PublishStreamResponse, error) {
resp, err := m.BaseHandler.PublishStream(ctx, req)
return resp, m.handleDownstreamError(ctx, err)
}

func (m *ErrorSourceMiddleware) RunStream(ctx context.Context, req *RunStreamRequest, sender *StreamSender) error {
err := m.BaseHandler.RunStream(ctx, req, sender)
return m.handleDownstreamError(ctx, err)
}

func (m *ErrorSourceMiddleware) ValidateAdmission(ctx context.Context, req *AdmissionRequest) (*ValidationResponse, error) {
resp, err := m.BaseHandler.ValidateAdmission(ctx, req)
return resp, m.handleDownstreamError(ctx, err)
}

func (m *ErrorSourceMiddleware) MutateAdmission(ctx context.Context, req *AdmissionRequest) (*MutationResponse, error) {
resp, err := m.BaseHandler.MutateAdmission(ctx, req)
return resp, m.handleDownstreamError(ctx, err)
}

func (m *ErrorSourceMiddleware) ConvertObjects(ctx context.Context, req *ConversionRequest) (*ConversionResponse, error) {
resp, err := m.BaseHandler.ConvertObjects(ctx, req)
return resp, m.handleDownstreamError(ctx, err)
}
Loading

0 comments on commit b95ac03

Please sign in to comment.