Skip to content

Commit

Permalink
feat: Add a method to start a transaction (#482)
Browse files Browse the repository at this point in the history
  • Loading branch information
phacops committed Oct 25, 2022
1 parent 2503eee commit af05cee
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 4 deletions.
14 changes: 10 additions & 4 deletions http/sentryhttp.go
Expand Up @@ -86,15 +86,21 @@ func (h *Handler) handle(handler http.Handler) http.HandlerFunc {
hub = sentry.CurrentHub().Clone()
ctx = sentry.SetHubOnContext(ctx, hub)
}
span := sentry.StartSpan(ctx, "http.server",
sentry.TransactionName(fmt.Sprintf("%s %s", r.Method, r.URL.Path)),
options := []sentry.SpanOption{
sentry.OpName("http.server"),
sentry.ContinueFromRequest(r),
}
// We don't mind getting an existing transaction back so we don't need to
// check if it is.
transaction := sentry.StartTransaction(ctx,
fmt.Sprintf("%s %s", r.Method, r.URL.Path),
options...,
)
defer span.Finish()
defer transaction.Finish()
// TODO(tracing): if the next handler.ServeHTTP panics, store
// information on the transaction accordingly (status, tag,
// level?, ...).
r = r.WithContext(span.Context())
r = r.WithContext(transaction.Context())
hub.Scope().SetRequest(r)
defer h.recoverWithSentry(hub, r)
// TODO(tracing): use custom response writer to intercept
Expand Down
27 changes: 27 additions & 0 deletions tracing.go
Expand Up @@ -567,6 +567,13 @@ func TransactionName(name string) SpanOption {
}
}

// OpName sets the operation name for a given span.
func OpName(name string) SpanOption {
return func(s *Span) {
s.Op = name
}
}

// ContinueFromRequest returns a span option that updates the span to continue
// an existing trace. If it cannot detect an existing trace in the request, the
// span will be left unchanged.
Expand Down Expand Up @@ -626,3 +633,23 @@ func spanFromContext(ctx context.Context) *Span {
}
return nil
}

// StartTransaction will create a transaction (root span) if there's no existing
// transaction in the context otherwise, it will return the existing transaction.
func StartTransaction(ctx context.Context, name string, options ...SpanOption) *Span {
currentTransaction, exists := ctx.Value(spanContextKey{}).(*Span)
if exists {
return currentTransaction
}
hub := GetHubFromContext(ctx)
if hub == nil {
hub = CurrentHub().Clone()
ctx = SetHubOnContext(ctx, hub)
}
options = append(options, TransactionName(name))
return StartSpan(
ctx,
"",
options...,
)
}
72 changes: 72 additions & 0 deletions tracing_test.go
Expand Up @@ -226,6 +226,78 @@ func TestStartChild(t *testing.T) {
}
}

func TestStartTransaction(t *testing.T) {
transport := &TransportMock{}
ctx := NewTestContext(ClientOptions{
Transport: transport,
})
transactionName := "Test Transaction"
description := "A Description"
status := SpanStatusOK
sampled := SampledTrue
startTime := time.Now()
endTime := startTime.Add(3 * time.Second)
data := map[string]interface{}{
"k": "v",
}
transaction := StartTransaction(ctx,
transactionName,
func(s *Span) {
s.Description = description
s.Status = status
s.Sampled = sampled
s.StartTime = startTime
s.EndTime = endTime
s.Data = data
},
)
transaction.Finish()

SpanCheck{
Sampled: sampled,
RecorderLen: 1,
}.Check(t, transaction)

events := transport.Events()
if got := len(events); got != 1 {
t.Fatalf("sent %d events, want 1", got)
}
want := &Event{
Type: transactionType,
Transaction: transactionName,
Contexts: map[string]Context{
"trace": TraceContext{
TraceID: transaction.TraceID,
SpanID: transaction.SpanID,
Description: description,
Status: status,
}.Map(),
},
Tags: nil,
// TODO(tracing): the root span / transaction data field is
// mapped into Event.Extra for now, pending spec clarification.
// https://github.com/getsentry/develop/issues/244#issuecomment-778694182
Extra: transaction.Data,
Timestamp: endTime,
StartTime: startTime,
}
opts := cmp.Options{
cmpopts.IgnoreFields(Event{},
"Contexts", "EventID", "Level", "Platform",
"Release", "Sdk", "ServerName", "Modules",
),
cmpopts.EquateEmpty(),
}
if diff := cmp.Diff(want, events[0], opts); diff != "" {
t.Fatalf("Event mismatch (-want +got):\n%s", diff)
}
// Check trace context explicitly, as we ignored all contexts above to
// disregard other contexts.
if diff := cmp.Diff(want.Contexts["trace"], events[0].Contexts["trace"]); diff != "" {
t.Fatalf("TraceContext mismatch (-want +got):\n%s", diff)
}
}

// testContextKey is used to store a value in a context so that we can check
// that SDK operations on that context preserve the original context values.
type testContextKey struct{}
Expand Down

0 comments on commit af05cee

Please sign in to comment.