Skip to content

Commit

Permalink
contrib/labstack/echo.v4: add WithErrorTranslator Option (#2169)
Browse files Browse the repository at this point in the history
Co-authored-by: Zarir Hamza <zarir.hamza@datadoghq.com>
Co-authored-by: Rodrigo Argüello <rodrigo.arguello@datadoghq.com>
Co-authored-by: Andrew Glaude <andrew.glaude@datadoghq.com>
  • Loading branch information
4 people committed Sep 7, 2023
1 parent 9314217 commit c487104
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 3 deletions.
4 changes: 1 addition & 3 deletions contrib/labstack/echo.v4/echotrace.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
package echo

import (
"errors"
"fmt"
"math"
"net/http"
Expand Down Expand Up @@ -88,8 +87,7 @@ 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) {
if echoErr, ok := cfg.translateError(err); ok {
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 TestWithErrorTranslator(t *testing.T) {
assert := assert.New(t)
mt := mocktracer.Start()
defer mt.Stop()
var called, traced bool

// setup
translateError := func(e error) (*echo.HTTPError, bool) {
return &echo.HTTPError{
Message: e.(*testCustomError).Error(),
Code: e.(*testCustomError).TestCode,
}, true
}
router := echo.New()
router.Use(Middleware(WithErrorTranslator(translateError)))

// 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
17 changes: 17 additions & 0 deletions contrib/labstack/echo.v4/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package echo

import (
"errors"
"math"

"gopkg.in/DataDog/dd-trace-go.v1/internal"
Expand All @@ -24,6 +25,7 @@ type config struct {
noDebugStack bool
ignoreRequestFunc IgnoreRequestFunc
isStatusError func(statusCode int) bool
translateError func(err error) (*echo.HTTPError, bool)
headerTags *internal.LockMap
}

Expand All @@ -38,6 +40,13 @@ func defaults(cfg *config) {
cfg.analyticsRate = math.NaN()
cfg.isStatusError = isServerError
cfg.headerTags = globalconfig.HeaderTagMap()
cfg.translateError = func(err error) (*echo.HTTPError, bool) {
var echoErr *echo.HTTPError
if errors.As(err, &echoErr) {
return echoErr, true
}
return nil, false
}
}

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

// WithErrorTranslator sets a function to translate Go errors into echo Errors.
// This is used for extracting the HTTP response status code.
func WithErrorTranslator(fn func(err error) (*echo.HTTPError, bool)) Option {
return func(cfg *config) {
cfg.translateError = fn
}
}

// 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

0 comments on commit c487104

Please sign in to comment.