Skip to content

Commit

Permalink
apm: record transaction type on errors
Browse files Browse the repository at this point in the history
When an error is captured within a sampled
transaction, record the transaction type on
the error. The transaction type is taken at
the time the CaptureError function is called,
or when Error.SetTransaction is called.
  • Loading branch information
axw committed Jan 16, 2019
1 parent 403779e commit ab532dd
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 39 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Introduce `ELASTIC_APM_CAPTURE_HEADERS` to control HTTP header capture (#418)
- module/apmzap: introduce zap log correlation and exception-tracking hook (#426)
- type Error implements error interface (#399)
- Add "transaction.type" to errors (#433)

## [v1.1.3](https://github.com/elastic/apm-agent-go/releases/tag/v1.1.3)

Expand Down
57 changes: 33 additions & 24 deletions error.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ type ErrorData struct {
exception exceptionData
log ErrorLogRecord
transactionSampled bool
transactionType string

// exceptionStacktraceFrames holds the number of stacktrace
// frames for the exception; stacktrace may hold frames for
Expand Down Expand Up @@ -219,17 +220,6 @@ type ErrorData struct {
Context Context
}

// SetTransaction sets TraceID, TransactionID, and ParentID to the transaction's IDs.
//
// SetTransaction has no effect if called with an ended transaction.
func (e *Error) SetTransaction(tx *Transaction) {
tx.mu.RLock()
if !tx.ended() {
e.setTransactionData(tx.TransactionData)
}
tx.mu.RUnlock()
}

// Cause returns original error assigned to Error, nil if Error or Error.cause is nil.
// https://godoc.org/github.com/pkg/errors#Cause
func (e *Error) Cause() error {
Expand All @@ -248,30 +238,49 @@ func (e *Error) Error() string {
return "[EMPTY]"
}

func (e *Error) setTransactionData(td *TransactionData) {
e.TraceID = td.traceContext.Trace
e.ParentID = td.traceContext.Span
e.TransactionID = e.ParentID
e.transactionSampled = td.traceContext.Options.Recorded()
// SetTransaction sets TraceID, TransactionID, and ParentID to the transaction's
// IDs, and records the transaction's Type and whether or not it was sampled.
//
// SetTransaction has no effect if called with an ended transaction.
func (e *Error) SetTransaction(tx *Transaction) {
tx.mu.RLock()
if !tx.ended() {
e.setSpanData(tx.TransactionData, nil)
}
tx.mu.RUnlock()
}

// SetSpan sets TraceID, TransactionID, and ParentID to the span's IDs. If you call
// this, it is not necessary to call SetTransaction.
// SetSpan sets TraceID, TransactionID, and ParentID to the span's IDs.
//
// If you call both SetTransaction and SetSpan, SetSpan must be called second
// in order to set the error's ParentID correctly. When calling SetSpan, it is
// only necessary to call SetTransaction in order to set the error's transaction
// type.
//
// SetSpan has no effect if called with an ended span.
func (e *Error) SetSpan(s *Span) {
s.mu.RLock()
if !s.ended() {
e.setSpanData(s.SpanData)
e.setSpanData(nil, s.SpanData)
}
s.mu.RUnlock()
}

func (e *Error) setSpanData(s *SpanData) {
e.TraceID = s.traceContext.Trace
e.ParentID = s.traceContext.Span
e.TransactionID = s.transactionID
e.transactionSampled = true // by virtue of there being a span
func (e *Error) setSpanData(td *TransactionData, sd *SpanData) {
if sd != nil {
e.TraceID = sd.traceContext.Trace
e.ParentID = sd.traceContext.Span
e.TransactionID = sd.transactionID
e.transactionSampled = true // by virtue of there being a span
} else if td != nil {
e.TraceID = td.traceContext.Trace
e.ParentID = td.traceContext.Span
e.TransactionID = e.ParentID
e.transactionSampled = td.traceContext.Options.Recorded()
}
if e.transactionSampled && td != nil {
e.transactionType = td.Type
}
}

// Send enqueues the error for sending to the Elastic APM server.
Expand Down
8 changes: 8 additions & 0 deletions error_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ func TestErrorNilError(t *testing.T) {

func TestErrorTransactionSampled(t *testing.T) {
_, _, errors := apmtest.WithTransaction(func(ctx context.Context) {
apm.TransactionFromContext(ctx).Type = "foo"
apm.CaptureError(ctx, errors.New("boom")).Send()

span, ctx := apm.StartSpan(ctx, "name", "type")
Expand All @@ -192,6 +193,8 @@ func TestErrorTransactionSampled(t *testing.T) {
})
assertErrorTransactionSampled(t, errors[0], true)
assertErrorTransactionSampled(t, errors[1], true)
assert.Equal(t, "foo", errors[0].Transaction.Type)
assert.Equal(t, "foo", errors[1].Transaction.Type)
}

func TestErrorTransactionNotSampled(t *testing.T) {
Expand Down Expand Up @@ -222,6 +225,11 @@ func TestErrorTransactionSampledNoTransaction(t *testing.T) {

func assertErrorTransactionSampled(t *testing.T, e model.Error, sampled bool) {
assert.Equal(t, &sampled, e.Transaction.Sampled)
if sampled {
assert.NotEmpty(t, e.Transaction.Type)
} else {
assert.Empty(t, e.Transaction.Type)
}
}

func makeError(msg string) error {
Expand Down
35 changes: 20 additions & 15 deletions gocontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,27 +88,32 @@ func CaptureError(ctx context.Context, err error) *Error {
if err == nil {
return nil
}
var e = &Error{
cause: err,
err: err.Error(),

var tracer *Tracer
var txData *TransactionData
var spanData *SpanData
if tx := TransactionFromContext(ctx); tx != nil {
tx.mu.RLock()
defer tx.mu.RUnlock()
if !tx.ended() {
txData = tx.TransactionData
tracer = tx.tracer
}
}
if span := SpanFromContext(ctx); span != nil {
span.mu.RLock()
defer span.mu.RUnlock()
if !span.ended() {
e = span.tracer.NewError(err)
e.setSpanData(span.SpanData)
spanData = span.SpanData
tracer = span.tracer
}
span.mu.RUnlock()
} else if tx := TransactionFromContext(ctx); tx != nil {
tx.mu.RLock()
if !tx.ended() {
e = tx.tracer.NewError(err)
e.setTransactionData(tx.TransactionData)
}
tx.mu.RUnlock()
}
if e.ErrorData != nil {
e.Handled = true
if tracer == nil {
return &Error{cause: err, err: err.Error()}
}

e := tracer.NewError(err)
e.Handled = true
e.setSpanData(txData, spanData)
return e
}
3 changes: 3 additions & 0 deletions modelwriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@ func (w *modelWriter) buildModelError(out *model.Error, e *ErrorData) {

if !e.TransactionID.isZero() {
out.Transaction.Sampled = &e.transactionSampled
if e.transactionSampled {
out.Transaction.Type = e.transactionType
}
}

w.modelStacktrace = w.modelStacktrace[:0]
Expand Down

0 comments on commit ab532dd

Please sign in to comment.