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

contrib/labstack/echo.v4: add WithErrorTranslator Option #2169

Merged
merged 17 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from 9 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
4 changes: 2 additions & 2 deletions contrib/labstack/echo.v4/echotrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ func Middleware(opts ...Option) echo.MiddlewareFunc {

// It is impossible to determine what the final status code of a request is in echo.
// This is the best we can do.
var echoErr *echo.HTTPError
if errors.As(err, &echoErr) {
var echoErr *echo.HTTPError = cfg.customErrorFunc(err)
if echoErr != nil || errors.As(err, &echoErr) {
mattscamp marked this conversation as resolved.
Show resolved Hide resolved
if cfg.isStatusError(echoErr.Code) {
finishOpts = append(finishOpts, tracer.WithError(err))
}
Expand Down
51 changes: 51 additions & 0 deletions contrib/labstack/echo.v4/echotrace_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,57 @@ func TestIgnoreRequestFunc(t *testing.T) {
assert.Len(spans, 0)
}

type testCustomError struct {
TestCode int
}

// Error satisfies the apierror interface
func (e *testCustomError) Error() string {
return "test"
}

func TestCustomErrorFunc(t *testing.T) {
mattscamp marked this conversation as resolved.
Show resolved Hide resolved
assert := assert.New(t)
mt := mocktracer.Start()
defer mt.Stop()
var called, traced bool

// setup
customErrorFunc := func(e error) *echo.HTTPError {
return &echo.HTTPError{
Message: e.(*testCustomError).Error(),
Code: e.(*testCustomError).TestCode,
}
}
mattscamp marked this conversation as resolved.
Show resolved Hide resolved
router := echo.New()
router.Use(Middleware(WithCustomErrorFunc(customErrorFunc)))

// a handler with an error and make the requests
router.GET("/err", func(c echo.Context) error {
_, traced = tracer.SpanFromContext(c.Request().Context())
called = true
return &testCustomError{
TestCode: 401,
}
})
r := httptest.NewRequest("GET", "/err", nil)
w := httptest.NewRecorder()
router.ServeHTTP(w, r)

// verify the error is correct and the stacktrace is disabled
assert.True(called)
assert.True(traced)

spans := mt.FinishedSpans()
require.Len(t, spans, 1)
span := spans[0]
assert.Equal("http.request", span.OperationName())
assert.Equal(ext.SpanTypeWeb, span.Tag(ext.SpanType))
assert.Contains(span.Tag(ext.ResourceName), "/err")
assert.Equal("401", span.Tag(ext.HTTPCode))
assert.Equal("GET", span.Tag(ext.HTTPMethod))
}

func TestNamingSchema(t *testing.T) {
genSpans := namingschematest.GenSpansFn(func(t *testing.T, serviceOverride string) []mocktracer.Span {
var opts []Option
Expand Down
13 changes: 13 additions & 0 deletions contrib/labstack/echo.v4/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type config struct {
noDebugStack bool
ignoreRequestFunc IgnoreRequestFunc
isStatusError func(statusCode int) bool
customErrorFunc CustomErrorFunc
mattscamp marked this conversation as resolved.
Show resolved Hide resolved
headerTags *internal.LockMap
}

Expand All @@ -33,11 +34,15 @@ type Option func(*config)
// IgnoreRequestFunc determines if tracing will be skipped for a request.
type IgnoreRequestFunc func(c echo.Context) bool

// CustomErrorFunc attempts to translate a non-standard echo error to the required format
type CustomErrorFunc func(err error) *echo.HTTPError
mattscamp marked this conversation as resolved.
Show resolved Hide resolved

func defaults(cfg *config) {
cfg.serviceName = namingschema.NewDefaultServiceName(defaultServiceName).GetName()
cfg.analyticsRate = math.NaN()
cfg.isStatusError = isServerError
cfg.headerTags = globalconfig.HeaderTagMap()
cfg.customErrorFunc = func(err error) *echo.HTTPError { return nil }
mattscamp marked this conversation as resolved.
Show resolved Hide resolved
}

// WithServiceName sets the given service name for the system.
Expand Down Expand Up @@ -87,6 +92,14 @@ func WithIgnoreRequest(ignoreRequestFunc IgnoreRequestFunc) Option {
}
}

// WithCustomErrorFunction sets a function to translate custom errors into the
// expected format
func WithCustomErrorFunc(customErrorFunc CustomErrorFunc) Option {
mattscamp marked this conversation as resolved.
Show resolved Hide resolved
return func(cfg *config) {
cfg.customErrorFunc = customErrorFunc
mattscamp marked this conversation as resolved.
Show resolved Hide resolved
}
}

// WithStatusCheck specifies a function fn which reports whether the passed
// statusCode should be considered an error.
func WithStatusCheck(fn func(statusCode int) bool) Option {
Expand Down
Loading