Skip to content
Open
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
12 changes: 6 additions & 6 deletions echo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import (

"github.com/getsentry/sentry-go"
sentryecho "github.com/getsentry/sentry-go/echo"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/labstack/echo/v5"
"github.com/labstack/echo/v5/middleware"
)

// To initialize Sentry's handler, you need to initialize Sentry itself beforehand
Expand All @@ -45,7 +45,7 @@ app.Use(middleware.Recover())
app.Use(sentryecho.New(sentryecho.Options{}))

// Set up routes
app.GET("/", func(ctx echo.Context) error {
app.GET("/", func(ctx *echo.Context) error {
return ctx.String(http.StatusOK, "Hello, World!")
})

Expand Down Expand Up @@ -90,15 +90,15 @@ app.Use(sentryecho.New(sentryecho.Options{
}))

app.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
return func(ctx *echo.Context) error {
if hub := sentryecho.GetHubFromContext(ctx); hub != nil {
hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt")
}
return next(ctx)
}
})

app.GET("/", func(ctx echo.Context) error {
app.GET("/", func(ctx *echo.Context) error {
if hub := sentryecho.GetHubFromContext(ctx); hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("unwantedQuery", "someQueryDataMaybe")
Expand All @@ -108,7 +108,7 @@ app.GET("/", func(ctx echo.Context) error {
return ctx.String(http.StatusOK, "Hello, World!")
})

app.GET("/foo", func(ctx echo.Context) error {
app.GET("/foo", func(ctx *echo.Context) error {
// sentryecho handler will catch it just fine. Also, because we attached "someRandomTag"
// in the middleware before, it will be sent through as well
panic("y tho")
Expand Down
4 changes: 2 additions & 2 deletions echo/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import (

"github.com/getsentry/sentry-go"
sentryecho "github.com/getsentry/sentry-go/echo"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
)

func ExampleGetSpanFromContext() {
router := echo.New()
router.Use(sentryecho.New(sentryecho.Options{}))
router.GET("/", func(c echo.Context) error {
router.GET("/", func(c *echo.Context) error {
expensiveThing := func(ctx context.Context) error {
span := sentry.StartTransaction(ctx, "expensive_thing")
defer span.Finish()
Expand Down
15 changes: 4 additions & 11 deletions echo/go.mod
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
module github.com/getsentry/sentry-go/echo

go 1.23.0
go 1.25.0

replace github.com/getsentry/sentry-go => ../

require (
github.com/getsentry/sentry-go v0.41.0
github.com/google/go-cmp v0.5.9
github.com/labstack/echo/v4 v4.10.1
github.com/labstack/echo/v5 v5.0.0
)

require (
github.com/labstack/gommon v0.4.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
)
35 changes: 10 additions & 25 deletions echo/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,23 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/labstack/echo/v4 v4.10.1 h1:rB+D8In9PWjsp1OpHaqK+t04nQv/SBD1IoIcXCg0lpY=
github.com/labstack/echo/v4 v4.10.1/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/labstack/echo/v5 v5.0.0 h1:JHKGrI0cbNsNMyKvranuY0C94O4hSM7yc/HtwcV3Na4=
github.com/labstack/echo/v5 v5.0.0/go.mod h1:SyvlSdObGjRXeQfCCXW/sybkZdOOQZBmpKF0bvALaeo=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
33 changes: 18 additions & 15 deletions echo/sentryecho.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ import (
"time"

"github.com/getsentry/sentry-go"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
)

const (
// sdkIdentifier is the identifier of the Echo SDK.
sdkIdentifier = "sentry.go.echo"

// valuesKey is used as a key to store the Sentry Hub instance on the echo.Context.
// valuesKey is used as a key to store the Sentry Hub instance on the *echo.Context.
valuesKey = "sentry"

// transactionKey is used as a key to store the Sentry transaction on the echo.Context.
// transactionKey is used as a key to store the Sentry transaction on the *echo.Context.
transactionKey = "sentry_transaction"

// errorKey is used as a key to store the error on the echo.Context.
// errorKey is used as a key to store the error on the *echo.Context.
errorKey = "error"
)

Expand Down Expand Up @@ -57,7 +57,7 @@ func New(options Options) echo.MiddlewareFunc {
}

func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
return func(ctx *echo.Context) error {
hub := GetHubFromContext(ctx)
if hub == nil {
hub = sentry.CurrentHub().Clone()
Expand Down Expand Up @@ -93,10 +93,13 @@ func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc {
transaction.SetData("http.request.method", r.Method)

defer func() {
status := ctx.Response().Status
var status int
if resp, err := echo.UnwrapResponse(ctx.Response()); err == nil && resp.Status != 0 {
status = resp.Status
}
if err := ctx.Get(errorKey); err != nil {
if httpError, ok := err.(*echo.HTTPError); ok {
status = httpError.Code
if coder, ok := err.(echo.HTTPStatusCoder); ok {
status = coder.StatusCode()
}
}

Expand Down Expand Up @@ -135,22 +138,22 @@ func (h *handler) recoverWithSentry(hub *sentry.Hub, r *http.Request) {
}
}

// GetHubFromContext retrieves attached *sentry.Hub instance from echo.Context.
func GetHubFromContext(ctx echo.Context) *sentry.Hub {
// GetHubFromContext retrieves attached *sentry.Hub instance from *echo.Context.
func GetHubFromContext(ctx *echo.Context) *sentry.Hub {
if hub, ok := ctx.Get(valuesKey).(*sentry.Hub); ok {
return hub
}
return nil
}

// SetHubOnContext attaches *sentry.Hub instance to echo.Context.
func SetHubOnContext(ctx echo.Context, hub *sentry.Hub) {
// SetHubOnContext attaches *sentry.Hub instance to *echo.Context.
func SetHubOnContext(ctx *echo.Context, hub *sentry.Hub) {
ctx.Set(valuesKey, hub)
}

// GetSpanFromContext retrieves attached *sentry.Span instance from echo.Context.
// If there is no transaction on echo.Context, it will return nil.
func GetSpanFromContext(ctx echo.Context) *sentry.Span {
// GetSpanFromContext retrieves attached *sentry.Span instance from *echo.Context.
// If there is no transaction on *echo.Context, it will return nil.
func GetSpanFromContext(ctx *echo.Context) *sentry.Span {
if span, ok := ctx.Get(transactionKey).(*sentry.Span); ok {
return span
}
Expand Down
67 changes: 57 additions & 10 deletions echo/sentryecho_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"github.com/getsentry/sentry-go/internal/testutils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v5"
)

func TestIntegration(t *testing.T) {
Expand All @@ -37,7 +37,7 @@ func TestIntegration(t *testing.T) {
RoutePath: "/panic/:id",
Method: "GET",
WantStatus: 200,
Handler: func(c echo.Context) error {
Handler: func(c *echo.Context) error {
panic("test")
},
WantEvent: &sentry.Event{
Expand Down Expand Up @@ -114,7 +114,7 @@ func TestIntegration(t *testing.T) {
Method: "POST",
WantStatus: 200,
Body: "payload",
Handler: func(c echo.Context) error {
Handler: func(c *echo.Context) error {
hub := sentryecho.GetHubFromContext(c)
body, err := io.ReadAll(c.Request().Body)
if err != nil {
Expand Down Expand Up @@ -168,7 +168,7 @@ func TestIntegration(t *testing.T) {
RoutePath: "/get",
Method: "GET",
WantStatus: 200,
Handler: func(c echo.Context) error {
Handler: func(c *echo.Context) error {
hub := sentryecho.GetHubFromContext(c)
hub.CaptureMessage("get")
return c.JSON(http.StatusOK, map[string]string{"status": "get"})
Expand Down Expand Up @@ -215,7 +215,7 @@ func TestIntegration(t *testing.T) {
Method: "POST",
WantStatus: 200,
Body: largePayload,
Handler: func(c echo.Context) error {
Handler: func(c *echo.Context) error {
hub := sentryecho.GetHubFromContext(c)
body, err := io.ReadAll(c.Request().Body)
if err != nil {
Expand Down Expand Up @@ -270,7 +270,7 @@ func TestIntegration(t *testing.T) {
Method: "POST",
WantStatus: 200,
Body: "client sends, server ignores, SDK doesn't read",
Handler: func(c echo.Context) error {
Handler: func(c *echo.Context) error {
hub := sentryecho.GetHubFromContext(c)
hub.CaptureMessage("body ignored")
return nil
Expand Down Expand Up @@ -322,7 +322,7 @@ func TestIntegration(t *testing.T) {
RoutePath: "/badreq",
Method: "GET",
WantStatus: 400,
Handler: func(c echo.Context) error {
Handler: func(c *echo.Context) error {
return c.JSON(http.StatusBadRequest, map[string]string{"status": "bad_request"})
},
WantTransaction: &sentry.Event{
Expand Down Expand Up @@ -376,6 +376,9 @@ func TestIntegration(t *testing.T) {
router.Use(sentryecho.New(sentryecho.Options{}))

for _, tt := range tests {
if tt.Handler == nil {
continue // no route to register (e.g. 404 case: path /404/1 must not exist)
}
switch tt.Method {
case http.MethodGet:
router.GET(tt.RoutePath, tt.Handler)
Expand Down Expand Up @@ -495,7 +498,7 @@ func TestSetHubOnContext(t *testing.T) {

hub := sentry.CurrentHub().Clone()
router := echo.New()
router.GET("/set-hub", func(c echo.Context) error {
router.GET("/set-hub", func(c *echo.Context) error {
sentryecho.SetHubOnContext(c, hub)
retrievedHub := sentryecho.GetHubFromContext(c)
if retrievedHub == nil {
Expand Down Expand Up @@ -540,14 +543,14 @@ func TestGetSpanFromContext(t *testing.T) {
}

router := echo.New()
router.GET("/no-span", func(c echo.Context) error {
router.GET("/no-span", func(c *echo.Context) error {
span := sentryecho.GetSpanFromContext(c)
if span != nil {
t.Error("expecting span to be nil")
}
return c.NoContent(http.StatusOK)
})
router.GET("/with-span", func(c echo.Context) error {
router.GET("/with-span", func(c *echo.Context) error {
span := sentryecho.GetSpanFromContext(c)
if span == nil {
t.Error("expecting span to not be nil")
Expand Down Expand Up @@ -594,3 +597,47 @@ func TestGetSpanFromContext(t *testing.T) {
}
}
}

func TestUnwrapResponseError(t *testing.T) {
ch := make(chan *sentry.Event, 1)
if err := sentry.Init(sentry.ClientOptions{
EnableTracing: true,
TracesSampleRate: 1.0,
BeforeSendTransaction: func(e *sentry.Event, _ *sentry.EventHint) *sentry.Event {
ch <- e
return e
},
}); err != nil {
t.Fatal(err)
}

router := echo.New()
router.Use(sentryecho.New(sentryecho.Options{}))
// ResponseWriter that does not implement Unwrap(), so echo.UnwrapResponse() returns an error.
router.GET("/unwrap-err", func(c *echo.Context) error {
c.SetResponse(&struct{ http.ResponseWriter }{c.Response()})
return c.JSON(http.StatusOK, "ok")
})

srv := httptest.NewServer(router)
defer srv.Close()

res, err := srv.Client().Get(srv.URL + "/unwrap-err")
if err != nil {
t.Fatal(err)
}
res.Body.Close()
if res.StatusCode != http.StatusOK {
t.Errorf("expected 200, got %d", res.StatusCode)
}

if !sentry.Flush(testutils.FlushTimeout()) {
t.Fatal("Flush timed out")
}
tx := <-ch

code := tx.Contexts["trace"]["data"].(map[string]interface{})["http.response.status_code"].(int)
if code != 0 {
t.Errorf("when UnwrapResponse fails, expected status_code 0, got %d", code)
}
}