Skip to content

Commit

Permalink
Merge pull request #433 from axw/error-transaction-type
Browse files Browse the repository at this point in the history
Add "transaction.type" to errors
  • Loading branch information
axw authored Jan 17, 2019
2 parents 18846c7 + ab532dd commit c3a93b0
Show file tree
Hide file tree
Showing 13 changed files with 101 additions and 59 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
}
5 changes: 5 additions & 0 deletions internal/apmschema/jsonschema/errors/error.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@
"sampled": {
"type": ["boolean", "null"],
"description": "Transactions that are 'sampled' will include all available information. Transactions that are not sampled will not have 'spans' or 'context'. Defaults to true."
},
"type": {
"type": ["string", "null"],
"description": "Keyword of specific relevance in the service's domain (eg: 'request', 'backgroundjob', etc)",
"maxLength": 1024
}
}
},
Expand Down
16 changes: 1 addition & 15 deletions internal/apmschema/jsonschema/metricsets/metricset.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,7 @@
"additionalProperties": false
},
"tags": {
"type": [
"object",
"null"
],
"description": "A flat mapping of user-defined tags with string values.",
"patternProperties": {
"^[^*\"]*$": {
"type": [
"string",
"null"
],
"maxLength": 1024
}
},
"additionalProperties": false
"$ref": "../tags.json"
}
},
"required": ["samples"]
Expand Down
4 changes: 2 additions & 2 deletions internal/apmschema/jsonschema/tags.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
"$id": "doc/spec/tags.json",
"title": "Tags",
"type": ["object", "null"],
"description": "A flat mapping of user-defined tags with string values.",
"description": "A flat mapping of user-defined tags with string, boolean or number values.",
"patternProperties": {
"^[^.*\"]*$": {
"type": ["string", "null"],
"type": ["string", "boolean", "number", "null"],
"maxLength": 1024
}
},
Expand Down
4 changes: 4 additions & 0 deletions model/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
set -e
go run ../../fastjson/cmd/generate-fastjson/main.go -f -o marshal_fastjson.go .
exec go-licenser marshal_fastjson.go
2 changes: 1 addition & 1 deletion model/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
"go.elastic.co/fastjson"
)

//go:generate go run ../../fastjson/cmd/generate-fastjson/main.go -f -o marshal_fastjson.go .
//go:generate sh generate.sh

// MarshalFastJSON writes the JSON representation of t to w.
func (t Time) MarshalFastJSON(w *fastjson.Writer) error {
Expand Down
19 changes: 18 additions & 1 deletion model/marshal_fastjson.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion model/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,10 +205,11 @@ func TestMarshalErrorTransactionUnsampled(t *testing.T) {
assert.NoError(t, err)
e.Timestamp = model.Time(time)
e.Transaction.Sampled = new(bool)
e.Transaction.Type = "foo"

var w fastjson.Writer
e.MarshalFastJSON(&w)
assert.Equal(t, `{"id":"00000000000000000000000000000000","timestamp":123000000,"transaction":{"sampled":false}}`, string(w.Bytes()))
assert.Equal(t, `{"id":"00000000000000000000000000000000","timestamp":123000000,"transaction":{"sampled":false,"type":"foo"}}`, string(w.Bytes()))
}

func TestMarshalCookies(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,9 @@ type Error struct {
type ErrorTransaction struct {
// Sampled indicates that the transaction was sampled.
Sampled *bool `json:"sampled,omitempty"`

// Type holds the transaction type.
Type string `json:"type,omitempty"`
}

// Exception represents an exception: an error or panic.
Expand Down
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 c3a93b0

Please sign in to comment.