Skip to content

Commit

Permalink
feat(performance): start transaction for fasthttp integration (#723)
Browse files Browse the repository at this point in the history
* feat(performance): start transaction for fasthttp integration

* lint: rename convertedHttpRequest to convertedHTTPRequest

* fix(fasthttp): remove attachments from ignored compare list

* chore(fasthttp): rename GetTransactionFromContext to GetSpanFromContext

* add http.request.method attribute to span data

* chore(fasthttp): remove unrelated docstring for hub

* chore: add changellog

---------

Co-authored-by: Emir Ribić <e@ribic.ba>
  • Loading branch information
aldy505 and ribice authored Mar 26, 2024
1 parent 5942155 commit 71c244b
Show file tree
Hide file tree
Showing 4 changed files with 295 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- Accept `interface{}` for span data values ([#784](https://github.com/getsentry/sentry-go/pull/784))
- Automatic transactions for Echo integration ([#722](https://github.com/getsentry/sentry-go/pull/722))
- Automatic transactions for Fasthttp integration ([#732](https://github.com/getsentry/sentry-go/pull/723))

## 0.27.0

Expand Down
19 changes: 19 additions & 0 deletions _examples/fasthttp/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"context"
"fmt"

"github.com/getsentry/sentry-go"
Expand All @@ -13,6 +14,24 @@ func enhanceSentryEvent(handler fasthttp.RequestHandler) fasthttp.RequestHandler
if hub := sentryfasthttp.GetHubFromContext(ctx); hub != nil {
hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt")
}

expensiveThing := func(ctx context.Context) error {
span := sentry.StartTransaction(ctx, "expensive_thing")
defer span.Finish()
// do resource intensive thing
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 := sentryfasthttp.GetSpanFromContext(ctx)
// Pass in the `.Context()` method from `*sentry.Span` struct.
// The `context.Context` instance inherits the context from `echo.Context`.
err := expensiveThing(sentrySpan.Context())
if err != nil {
sentry.CaptureException(err)
}

handler(ctx)
}
}
Expand Down
50 changes: 40 additions & 10 deletions fasthttp/sentryfasthttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import (
"github.com/valyala/fasthttp"
)

// The identifier of the FastHTTP SDK.
const sdkIdentifier = "sentry.go.fasthttp"

type contextKey int

const ContextKey = contextKey(1)
const valuesKey = "sentry"
const (
ContextKey = contextKey(1)
// The identifier of the FastHTTP SDK.
sdkIdentifier = "sentry.go.fasthttp"
valuesKey = "sentry"
transactionKey = "sentry_transaction"
)

type Handler struct {
repanic bool
Expand Down Expand Up @@ -56,20 +58,39 @@ func New(options Options) *Handler {
// Handle wraps fasthttp.RequestHandler and recovers from caught panics.
func (h *Handler) Handle(handler fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
// Unlike for other integrations, we don't support getting an existing
// hub from the current request context because fasthttp doesn't use the
// standard net/http.Request and because fasthttp.RequestCtx implements
// context.Context but requires string keys.
hub := sentry.CurrentHub().Clone()

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

convertedHTTPRequest := convert(ctx)

options := []sentry.SpanOption{
sentry.WithOpName("http.server"),
sentry.ContinueFromRequest(convertedHTTPRequest),
sentry.WithTransactionSource(sentry.SourceRoute),
}

method := string(ctx.Method())

transaction := sentry.StartTransaction(
sentry.SetHubOnContext(ctx, hub),
fmt.Sprintf("%s %s", method, string(ctx.Path())),
options...,
)
defer func() {
transaction.Status = sentry.HTTPtoSpanStatus(ctx.Response.StatusCode())
transaction.Finish()
}()

transaction.SetData("http.request.method", method)

scope := hub.Scope()
scope.SetRequest(convert(ctx))
scope.SetRequest(convertedHTTPRequest)
scope.SetRequestBody(ctx.Request.Body())
ctx.SetUserValue(valuesKey, hub)
ctx.SetUserValue(transactionKey, transaction)
defer h.recoverWithSentry(hub, ctx)
handler(ctx)
}
Expand Down Expand Up @@ -99,6 +120,15 @@ func GetHubFromContext(ctx *fasthttp.RequestCtx) *sentry.Hub {
return nil
}

// GetSpanFromContext retrieves attached *sentry.Span instance from *fasthttp.RequestCtx.
// If there is no transaction on *fasthttp.RequestCtx, it will return nil.
func GetSpanFromContext(ctx *fasthttp.RequestCtx) *sentry.Span {
if span, ok := ctx.UserValue(transactionKey).(*sentry.Span); ok {
return span
}
return nil
}

func convert(ctx *fasthttp.RequestCtx) *http.Request {
defer func() {
if err := recover(); err != nil {
Expand Down
Loading

0 comments on commit 71c244b

Please sign in to comment.