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
17 changes: 8 additions & 9 deletions crosstest/http_link_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ func TestHTTPFamilyIntegrationsLinkManualErrorsLogsMetricsAndPanicsToOTel(t *tes
t.Parallel()
sentrytest.Run(t, func(t *testing.T, f *sentrytest.Fixture) {
const identifier = "gin"
baseCtx := sentry.SetHubOnContext(context.Background(), f.Hub)
baseCtx := f.NewContext(context.Background())
logger := sentry.NewLogger(baseCtx)
meter := sentry.NewMeter(baseCtx)
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.Use(func(c *gin.Context) {
c.Request = c.Request.WithContext(sentry.SetHubOnContext(otelCtx, f.Hub))
c.Request = c.Request.WithContext(f.NewContext(otelCtx))
c.Next()
})
router.Use(sentrygin.New(sentrygin.Options{WaitForDelivery: true}))
Expand All @@ -97,14 +97,13 @@ func TestHTTPFamilyIntegrationsLinkManualErrorsLogsMetricsAndPanicsToOTel(t *tes
t.Parallel()
sentrytest.Run(t, func(t *testing.T, f *sentrytest.Fixture) {
const identifier = "echo"
baseCtx := sentry.SetHubOnContext(context.Background(), f.Hub)
baseCtx := f.NewContext(context.Background())
logger := sentry.NewLogger(baseCtx)
meter := sentry.NewMeter(baseCtx)
e := echo.New()
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error {
sentryecho.SetHubOnContext(c, f.Hub)
c.SetRequest(c.Request().WithContext(sentry.SetHubOnContext(otelCtx, f.Hub)))
c.SetRequest(c.Request().WithContext(f.NewContext(otelCtx)))
return next(c)
}
})
Expand All @@ -126,12 +125,12 @@ func TestHTTPFamilyIntegrationsLinkManualErrorsLogsMetricsAndPanicsToOTel(t *tes
t.Parallel()
sentrytest.Run(t, func(t *testing.T, f *sentrytest.Fixture) {
const identifier = "negroni"
baseCtx := sentry.SetHubOnContext(context.Background(), f.Hub)
baseCtx := f.NewContext(context.Background())
logger := sentry.NewLogger(baseCtx)
meter := sentry.NewMeter(baseCtx)
n := negroni.New()
n.Use(negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
next(w, r.WithContext(sentry.SetHubOnContext(otelCtx, f.Hub)))
next(w, r.WithContext(f.NewContext(otelCtx)))
}))
n.Use(sentrynegroni.New(sentrynegroni.Options{WaitForDelivery: true}))
n.UseHandler(http.HandlerFunc(func(_ http.ResponseWriter, r *http.Request) {
Expand All @@ -150,12 +149,12 @@ func TestHTTPFamilyIntegrationsLinkManualErrorsLogsMetricsAndPanicsToOTel(t *tes
t.Parallel()
sentrytest.Run(t, func(t *testing.T, f *sentrytest.Fixture) {
const identifier = "iris"
baseCtx := sentry.SetHubOnContext(context.Background(), f.Hub)
baseCtx := f.NewContext(context.Background())
logger := sentry.NewLogger(baseCtx)
meter := sentry.NewMeter(baseCtx)
app := iris.New()
app.Use(func(ctx iris.Context) {
ctx.ResetRequest(ctx.Request().WithContext(sentry.SetHubOnContext(otelCtx, f.Hub)))
ctx.ResetRequest(ctx.Request().WithContext(f.NewContext(otelCtx)))
ctx.Next()
})
app.Use(sentryiris.New(sentryiris.Options{WaitForDelivery: true}))
Expand Down
29 changes: 12 additions & 17 deletions echo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ go get github.com/getsentry/sentry-go/echo
```go
import (
"fmt"
"log"
"net/http"

"github.com/getsentry/sentry-go"
Expand All @@ -38,7 +39,7 @@ if err := sentry.Init(sentry.ClientOptions{
// Then create your app
app := echo.New()

app.Use(middleware.Logger())
app.Use(middleware.RequestLogger())
app.Use(middleware.Recover())

// Once it's done, you can attach the handler as one of your middleware
Expand All @@ -50,7 +51,7 @@ app.GET("/", func(ctx *echo.Context) error {
})

// And run it
app.Logger.Fatal(app.Start(":3000"))
log.Fatal(app.Start(":3000"))
```

## Configuration
Expand All @@ -73,16 +74,15 @@ Timeout time.Duration

## Usage

`sentryecho` attaches an instance of `*sentry.Hub` (https://pkg.go.dev/github.com/getsentry/sentry-go#Hub) to the `echo.Context`, which makes it available throughout the rest of the request's lifetime.
You can access it by using the `sentryecho.GetHubFromContext()` method on the context itself in any of your proceeding middleware and routes.
And it should be used instead of the global `sentry.CaptureMessage`, `sentry.CaptureException`, or any other calls, as it keeps the separation of data between the requests.
`sentryecho` attaches a request-specific `*sentry.Scope` and transaction to the request context. Pass `ctx.Request().Context()` to capture functions such as `sentry.CaptureMessage` and `sentry.CaptureException` so request data, custom scope data, and trace information are applied to the event.
Use `sentry.ScopeFromContext(ctx.Request().Context())` when you need to add data that should be available to captures made during the request.

**Keep in mind that `*sentry.Hub` won't be available in middleware attached before to `sentryecho`!**
**Keep in mind that the request scope won't be available in middleware attached before `sentryecho`!**

```go
app := echo.New()

app.Use(middleware.Logger())
app.Use(middleware.RequestLogger())
app.Use(middleware.Recover())

app.Use(sentryecho.New(sentryecho.Options{
Expand All @@ -91,20 +91,15 @@ app.Use(sentryecho.New(sentryecho.Options{

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

app.GET("/", func(ctx *echo.Context) error {
if hub := sentryecho.GetHubFromContext(ctx); hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetTag("unwantedQuery", "someQueryDataMaybe")
hub.CaptureMessage("User provided unwanted query string, but we recovered just fine")
})
}
scope := sentry.ScopeFromContext(ctx.Request().Context())
scope.SetTag("unwantedQuery", "someQueryDataMaybe")
sentry.CaptureMessage(ctx.Request().Context(), "User provided unwanted query string, but we recovered just fine")
return ctx.String(http.StatusOK, "Hello, World!")
})

Expand All @@ -114,7 +109,7 @@ app.GET("/foo", func(ctx *echo.Context) error {
panic("y tho")
})

app.Logger.Fatal(app.Start(":3000"))
log.Fatal(app.Start(":3000"))
```

### Accessing Request in `BeforeSend` callback
Expand Down
15 changes: 8 additions & 7 deletions echo/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/labstack/echo/v5"
)

func ExampleGetSpanFromContext() {
func Example() {
router := echo.New()
router.Use(sentryecho.New(sentryecho.Options{}))
router.GET("/", func(c *echo.Context) error {
Expand All @@ -20,12 +20,13 @@ func ExampleGetSpanFromContext() {
return nil
}

// Acquire transaction on current hub that's created by the SDK.
// Be careful, it might be a nil value if you didn't set up sentryecho middleware.
sentrySpan := sentryecho.GetSpanFromContext(c)
// Pass in the `.Context()` method from `*sentry.Span` struct.
// The `context.Context` instance inherits the context from `echo.Context`.
err := expensiveThing(sentrySpan.Context())
// Acquire the span from the request context. It may be nil if
// you did not set up the sentryecho middleware.
spanContext := c.Request().Context()
if sentrySpan := sentry.SpanFromContext(spanContext); sentrySpan != nil {
spanContext = sentrySpan.Context()
}
err := expensiveThing(spanContext)
if err != nil {
return err
}
Expand Down
105 changes: 35 additions & 70 deletions echo/sentryecho.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,14 @@ import (

"github.com/getsentry/sentry-go"
"github.com/getsentry/sentry-go/internal/debuglog"
"github.com/getsentry/sentry-go/internal/traceutils"
"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 = "sentry"

// 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 = "error"
)
Expand Down Expand Up @@ -59,17 +54,11 @@ func New(options Options) echo.MiddlewareFunc {

func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx *echo.Context) error {
hub := GetHubFromContext(ctx)
if hub == nil {
hub = sentry.CurrentHub().Clone()
}

if client := hub.Client(); client != nil {
client.SetSDKIdentifier(sdkIdentifier)
}

r := ctx.Request()
requestCtx := sentry.SetHubOnContext(r.Context(), hub)
created := sentry.SpanFromContext(r.Context()) == nil
requestCtx, scope := sentry.WithIsolationScope(r.Context())

sentry.ClientFromContext(requestCtx).SetSDKIdentifier(sdkIdentifier)

transactionName := r.URL.Path
transactionSource := sentry.SourceURL
Expand All @@ -80,7 +69,7 @@ func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc {
}

options := []sentry.SpanOption{
sentry.ContinueTrace(r.Header.Get(sentry.SentryTraceHeader), r.Header.Get(sentry.SentryBaggageHeader)),
traceutils.ContinueFromRequest(r),
sentry.WithOpName("http.server"),
sentry.WithTransactionSource(transactionSource),
sentry.WithSpanOrigin(sentry.SpanOriginEcho),
Expand All @@ -92,34 +81,34 @@ func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc {
options...,
)

transaction.SetData("http.request.method", r.Method)
if created {
requestCtx = transaction.Context()
transaction.SetData("http.request.method", r.Method)
defer func() {
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 coder, ok := err.(echo.HTTPStatusCoder); ok {
status = coder.StatusCode()
}
}

defer func() {
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 coder, ok := err.(echo.HTTPStatusCoder); ok {
status = coder.StatusCode()
if status == 0 {
debuglog.Printf("sentryecho: unable to determine HTTP response status code")
} else {
transaction.Status = sentry.HTTPtoSpanStatus(status)
transaction.SetData("http.response.status_code", status)
}
}

if status == 0 {
debuglog.Printf("sentryecho: unable to determine HTTP response status code")
} else {
transaction.Status = sentry.HTTPtoSpanStatus(status)
transaction.SetData("http.response.status_code", status)
}
transaction.Finish()
}()

hub.Scope().SetRequest(r)
ctx.Set(valuesKey, hub)
ctx.Set(transactionKey, transaction)
r = r.WithContext(transaction.Context())
transaction.Finish()
}()
}

r = r.WithContext(requestCtx)
ctx.SetRequest(r)
defer h.recoverWithSentry(hub, r)
scope.SetRequest(r)
defer h.recoverWithSentry(r)

err := next(ctx)
if err != nil {
Expand All @@ -131,39 +120,15 @@ func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc {
}
}

func (h *handler) recoverWithSentry(hub *sentry.Hub, r *http.Request) {
func (h *handler) recoverWithSentry(r *http.Request) {
if err := recover(); err != nil {
eventID := hub.RecoverWithContext(
context.WithValue(r.Context(), sentry.RequestContextKey, r),
err,
)
ctx := context.WithValue(r.Context(), sentry.RequestContextKey, r)
eventID := sentry.Recover(ctx, err)
if eventID != nil && h.waitForDelivery {
hub.Flush(h.timeout)
sentry.ClientFromContext(ctx).Flush(h.timeout)
}
if h.repanic {
panic(err)
}
}
}

// 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) {
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 {
if span, ok := ctx.Get(transactionKey).(*sentry.Span); ok {
return span
}
return nil
}
Loading
Loading