Skip to content

Commit d824822

Browse files
authored
Merge pull request #611 from iamemilio/expectedErrors
Notice Expected Errors
2 parents dc72e67 + e6ffdc6 commit d824822

File tree

12 files changed

+171
-61
lines changed

12 files changed

+171
-61
lines changed

v3/examples/server/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ func noticeError(w http.ResponseWriter, r *http.Request) {
3131
txn.NoticeError(errors.New("my error message"))
3232
}
3333

34+
func noticeExpectedError(w http.ResponseWriter, r *http.Request) {
35+
io.WriteString(w, "noticing an error")
36+
37+
txn := newrelic.FromContext(r.Context())
38+
txn.NoticeExpectedError(errors.New("my expected error message"))
39+
}
40+
3441
func noticeErrorWithAttributes(w http.ResponseWriter, r *http.Request) {
3542
io.WriteString(w, "noticing an error")
3643

@@ -273,6 +280,7 @@ func main() {
273280
http.HandleFunc(newrelic.WrapHandleFunc(app, "/", index))
274281
http.HandleFunc(newrelic.WrapHandleFunc(app, "/version", versionHandler))
275282
http.HandleFunc(newrelic.WrapHandleFunc(app, "/notice_error", noticeError))
283+
http.HandleFunc(newrelic.WrapHandleFunc(app, "/notice_expected_error", noticeExpectedError))
276284
http.HandleFunc(newrelic.WrapHandleFunc(app, "/notice_error_with_attributes", noticeErrorWithAttributes))
277285
http.HandleFunc(newrelic.WrapHandleFunc(app, "/custom_event", customEvent))
278286
http.HandleFunc(newrelic.WrapHandleFunc(app, "/set_name", setName))

v3/newrelic/errors_from_internal.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type errorData struct {
6161
Msg string
6262
Klass string
6363
SpanID string
64+
Expect bool
6465
}
6566

6667
// txnError combines error data with information about a transaction. txnError is used for
@@ -113,7 +114,7 @@ func (h *tracedError) WriteJSON(buf *bytes.Buffer) {
113114
buf.WriteByte(',')
114115
buf.WriteString(`"intrinsics"`)
115116
buf.WriteByte(':')
116-
intrinsicsJSON(&h.txnEvent, buf)
117+
intrinsicsJSON(&h.txnEvent, buf, h.errorData.Expect)
117118
if nil != h.Stack {
118119
buf.WriteByte(',')
119120
buf.WriteString(`"stack_trace"`)
@@ -152,7 +153,7 @@ func mergeTxnErrors(errors *harvestErrors, errs txnErrors, txnEvent txnEvent) {
152153
}
153154

154155
func (errors harvestErrors) Data(agentRunID string, harvestStart time.Time) ([]byte, error) {
155-
if 0 == len(errors) {
156+
if len(errors) == 0 {
156157
return nil, nil
157158
}
158159
estimate := 1024 * len(errors)

v3/newrelic/harvest.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,12 +327,16 @@ func createTxnMetrics(args *txnData, metrics *metricTable) {
327327
}
328328

329329
// Error Metrics
330-
if args.HasErrors() {
330+
if args.NoticeErrors() {
331331
metrics.addSingleCount(errorsRollupMetric.all, forced)
332332
metrics.addSingleCount(errorsRollupMetric.webOrOther(args.IsWeb), forced)
333333
metrics.addSingleCount(errorsPrefix+args.FinalName, forced)
334334
}
335335

336+
if args.HasExpectedErrors() {
337+
metrics.addSingleCount(expectedErrorsRollupMetric.all, forced)
338+
}
339+
336340
// Queueing Metrics
337341
if args.Queuing > 0 {
338342
metrics.addDuration(queueMetric, "", args.Queuing, args.Queuing, forced)

v3/newrelic/harvest_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,7 @@ func TestCreateTxnMetrics(t *testing.T) {
771771
webName := "WebTransaction/zip/zap"
772772
backgroundName := "OtherTransaction/zip/zap"
773773
args := &txnData{}
774+
args.noticeErrors = true
774775
args.Duration = 123 * time.Second
775776
args.TotalTime = 150 * time.Second
776777
args.ApdexThreshold = 2 * time.Second
@@ -803,6 +804,7 @@ func TestCreateTxnMetrics(t *testing.T) {
803804
args.FinalName = webName
804805
args.IsWeb = true
805806
args.Errors = nil
807+
args.noticeErrors = false
806808
args.Zone = apdexTolerating
807809
metrics = newMetricTable(100, time.Now())
808810
createTxnMetrics(args, metrics)
@@ -821,6 +823,7 @@ func TestCreateTxnMetrics(t *testing.T) {
821823
args.FinalName = backgroundName
822824
args.IsWeb = false
823825
args.Errors = txnErrors
826+
args.noticeErrors = true
824827
args.Zone = apdexNone
825828
metrics = newMetricTable(100, time.Now())
826829
createTxnMetrics(args, metrics)
@@ -838,9 +841,32 @@ func TestCreateTxnMetrics(t *testing.T) {
838841
{Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: []float64{1, 0, 0, 0, 0, 0}},
839842
})
840843

844+
// Verify expected errors metrics
845+
args.FinalName = backgroundName
846+
args.IsWeb = false
847+
args.Errors = txnErrors
848+
args.noticeErrors = false
849+
args.expectedErrors = true
850+
args.Zone = apdexNone
851+
metrics = newMetricTable(100, time.Now())
852+
createTxnMetrics(args, metrics)
853+
expectMetrics(t, metrics, []internal.WantMetric{
854+
{Name: backgroundName, Scope: "", Forced: true, Data: []float64{1, 123, 0, 123, 123, 123 * 123}},
855+
{Name: backgroundRollup, Scope: "", Forced: true, Data: []float64{1, 123, 0, 123, 123, 123 * 123}},
856+
{Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: []float64{1, 150, 150, 150, 150, 150 * 150}},
857+
{Name: "OtherTransactionTotalTime/zip/zap", Scope: "", Forced: false, Data: []float64{1, 150, 150, 150, 150, 150 * 150}},
858+
{Name: "ErrorsExpected/all", Scope: "", Forced: true, Data: []float64{1, 0, 0, 0, 0, 0}},
859+
{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: []float64{1, 123, 123, 123, 123, 123 * 123}},
860+
{Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: []float64{1, 123, 123, 123, 123, 123 * 123}},
861+
{Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: []float64{1, 0, 0, 0, 0, 0}},
862+
{Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: []float64{1, 0, 0, 0, 0, 0}},
863+
})
864+
841865
args.FinalName = backgroundName
842866
args.IsWeb = false
843867
args.Errors = nil
868+
args.noticeErrors = false
869+
args.expectedErrors = false
844870
args.Zone = apdexNone
845871
metrics = newMetricTable(100, time.Now())
846872
createTxnMetrics(args, metrics)
@@ -889,6 +915,7 @@ func TestCreateTxnMetricsOldCAT(t *testing.T) {
889915
args.FinalName = webName
890916
args.IsWeb = true
891917
args.Errors = txnErrors
918+
args.noticeErrors = true
892919
args.Zone = apdexTolerating
893920
metrics := newMetricTable(100, time.Now())
894921
createTxnMetrics(args, metrics)
@@ -908,6 +935,7 @@ func TestCreateTxnMetricsOldCAT(t *testing.T) {
908935
args.FinalName = webName
909936
args.IsWeb = true
910937
args.Errors = nil
938+
args.noticeErrors = false
911939
args.Zone = apdexTolerating
912940
metrics = newMetricTable(100, time.Now())
913941
createTxnMetrics(args, metrics)
@@ -924,6 +952,7 @@ func TestCreateTxnMetricsOldCAT(t *testing.T) {
924952
args.FinalName = backgroundName
925953
args.IsWeb = false
926954
args.Errors = txnErrors
955+
args.noticeErrors = true
927956
args.Zone = apdexNone
928957
metrics = newMetricTable(100, time.Now())
929958
createTxnMetrics(args, metrics)
@@ -940,6 +969,7 @@ func TestCreateTxnMetricsOldCAT(t *testing.T) {
940969
args.FinalName = backgroundName
941970
args.IsWeb = false
942971
args.Errors = nil
972+
args.noticeErrors = false
943973
args.Zone = apdexNone
944974
metrics = newMetricTable(100, time.Now())
945975
createTxnMetrics(args, metrics)

v3/newrelic/internal_errors_stacktrace_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestStackTrace(t *testing.T) {
6464
}
6565

6666
for idx, tc := range testcases {
67-
data, err := errDataFromError(tc.Error)
67+
data, err := errDataFromError(tc.Error, false)
6868
if err != nil {
6969
t.Errorf("testcase %d: got error: %v", idx, err)
7070
continue

v3/newrelic/internal_errors_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -636,7 +636,7 @@ func TestErrorClass(t *testing.T) {
636636
}
637637

638638
for idx, tc := range testcases {
639-
data, err := errDataFromError(tc.Error)
639+
data, err := errDataFromError(tc.Error, false)
640640
if err != nil {
641641
t.Errorf("testcase %d: got error: %v", idx, err)
642642
continue

v3/newrelic/internal_txn.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ func headersJustWritten(thd *thread, code int, hdr http.Header) {
366366
if txn.appRun.responseCodeIsError(code) {
367367
e := txnErrorFromResponseCode(time.Now(), code)
368368
e.Stack = getStackTrace()
369-
thd.noticeErrorInternal(e)
369+
thd.noticeErrorInternal(e, false)
370370
}
371371
}
372372

@@ -425,7 +425,7 @@ func (thd *thread) End(recovered interface{}) error {
425425
if nil != recovered {
426426
e := txnErrorFromPanic(time.Now(), recovered)
427427
e.Stack = getStackTrace()
428-
thd.noticeErrorInternal(e)
428+
thd.noticeErrorInternal(e, false)
429429
log.Println(string(debug.Stack()))
430430
}
431431

@@ -447,7 +447,7 @@ func (thd *thread) End(recovered interface{}) error {
447447
txn.ApdexThreshold = internal.CalculateApdexThreshold(txn.Reply, txn.FinalName)
448448

449449
if txn.getsApdex() {
450-
if txn.HasErrors() {
450+
if txn.HasErrors() && txn.NoticeErrors() {
451451
txn.Zone = apdexFailing
452452
} else {
453453
txn.Zone = calculateApdexZone(txn.ApdexThreshold, txn.Duration)
@@ -461,7 +461,7 @@ func (thd *thread) End(recovered interface{}) error {
461461
"name": txn.FinalName,
462462
"duration_ms": txn.Duration.Seconds() * 1000.0,
463463
"ignored": txn.ignore,
464-
"app_connected": "" != txn.Reply.RunID,
464+
"app_connected": txn.Reply.RunID != "",
465465
})
466466
}
467467

@@ -559,12 +559,18 @@ const (
559559
securityPolicyErrorMsg = "message removed by security policy"
560560
)
561561

562-
func (thd *thread) noticeErrorInternal(err errorData) error {
562+
func (thd *thread) noticeErrorInternal(err errorData, expect bool) error {
563563
txn := thd.txn
564564
if !txn.Config.ErrorCollector.Enabled {
565565
return errorsDisabled
566566
}
567567

568+
if !expect {
569+
thd.noticeErrors = true
570+
} else {
571+
thd.expectedErrors = true
572+
}
573+
568574
if nil == txn.Errors {
569575
txn.Errors = newTxnErrors(maxTxnErrors)
570576
}
@@ -643,12 +649,13 @@ func errorAttributesMethod(err error) map[string]interface{} {
643649
return nil
644650
}
645651

646-
func errDataFromError(input error) (data errorData, err error) {
652+
func errDataFromError(input error, expect bool) (data errorData, err error) {
647653
cause := errorCause(input)
648654

649655
data = errorData{
650-
When: time.Now(),
651-
Msg: input.Error(),
656+
When: time.Now(),
657+
Msg: input.Error(),
658+
Expect: expect,
652659
}
653660

654661
if c := errorClassMethod(input); "" != c {
@@ -700,7 +707,7 @@ func errDataFromError(input error) (data errorData, err error) {
700707
return data, nil
701708
}
702709

703-
func (thd *thread) NoticeError(input error) error {
710+
func (thd *thread) NoticeError(input error, expect bool) error {
704711
txn := thd.txn
705712
txn.Lock()
706713
defer txn.Unlock()
@@ -713,7 +720,7 @@ func (thd *thread) NoticeError(input error) error {
713720
return errNilError
714721
}
715722

716-
data, err := errDataFromError(input)
723+
data, err := errDataFromError(input, expect)
717724
if nil != err {
718725
return err
719726
}
@@ -722,7 +729,7 @@ func (thd *thread) NoticeError(input error) error {
722729
data.ExtraAttributes = nil
723730
}
724731

725-
return thd.noticeErrorInternal(data)
732+
return thd.noticeErrorInternal(data, expect)
726733
}
727734

728735
func (txn *txn) SetName(name string) error {

v3/newrelic/intrinsics.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ import (
77
"bytes"
88
)
99

10+
const (
11+
expectErrorAttr = "error.expected"
12+
)
13+
1014
func addOptionalStringField(w *jsonFieldsWriter, key, value string) {
1115
if value != "" {
1216
w.stringField(key, value)
1317
}
1418
}
1519

16-
func intrinsicsJSON(e *txnEvent, buf *bytes.Buffer) {
20+
func intrinsicsJSON(e *txnEvent, buf *bytes.Buffer, expect bool) {
1721
w := jsonFieldsWriter{buf: buf}
1822

1923
buf.WriteByte('{')
@@ -27,6 +31,10 @@ func intrinsicsJSON(e *txnEvent, buf *bytes.Buffer) {
2731
w.boolField("sampled", e.BetterCAT.Sampled)
2832
}
2933

34+
if expect {
35+
w.stringField(expectErrorAttr, "true")
36+
}
37+
3038
if e.CrossProcess.Used() {
3139
addOptionalStringField(&w, "client_cross_process_id", e.CrossProcess.ClientID)
3240
addOptionalStringField(&w, "trip_id", e.CrossProcess.TripID)

v3/newrelic/metric_names.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,8 @@ func (r rollupMetric) webOrOther(isWeb bool) string {
187187
}
188188

189189
var (
190-
errorsRollupMetric = newRollupMetric("Errors/")
191-
190+
errorsRollupMetric = newRollupMetric("Errors/")
191+
expectedErrorsRollupMetric = newRollupMetric("ErrorsExpected/")
192192
// source.datanerd.us/agents/agent-specs/blob/master/APIs/external_segment.md
193193
// source.datanerd.us/agents/agent-specs/blob/master/APIs/external_cat.md
194194
// source.datanerd.us/agents/agent-specs/blob/master/Cross-Application-Tracing-PORTED.md

v3/newrelic/tracing.go

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -63,38 +63,42 @@ func (bc *betterCAT) SetTraceAndTxnIDs(traceID string) {
6363

6464
// txnData contains the recorded data of a transaction.
6565
type txnData struct {
66-
txnEvent
67-
IsWeb bool
68-
Name string // Work in progress name.
69-
Errors txnErrors // Lazily initialized.
70-
Stop time.Time
71-
ApdexThreshold time.Duration
66+
IsWeb bool
67+
SlowQueriesEnabled bool
68+
noticeErrors bool // If errors are not expected or ignored, then true
69+
expectedErrors bool
7270

7371
stamp segmentStamp
7472
threadIDCounter uint64
7573

74+
Name string // Work in progress name.
75+
rootSpanID string
76+
77+
txnEvent
78+
TxnTrace txnTrace
79+
80+
Stop time.Time
81+
ApdexThreshold time.Duration
82+
SlowQueryThreshold time.Duration
83+
84+
SlowQueries *slowQueries
85+
86+
// These better CAT supportability fields are left outside of
87+
// TxnEvent.BetterCAT to minimize the size of transaction event memory.
88+
DistributedTracingSupport distributedTracingSupport
89+
7690
TraceIDGenerator *internal.TraceIDGenerator
7791
ShouldCollectSpanEvents func() bool
7892
ShouldCreateSpanGUID func() bool
79-
rootSpanID string
8093
rootSpanErrData *errorData
94+
Errors txnErrors // Lazily initialized.
8195
SpanEvents []*spanEvent
8296
logs logEventHeap
8397

8498
customSegments map[string]*metricData
8599
datastoreSegments map[datastoreMetricKey]*metricData
86100
externalSegments map[externalMetricKey]*metricData
87101
messageSegments map[internal.MessageMetricKey]*metricData
88-
89-
TxnTrace txnTrace
90-
91-
SlowQueriesEnabled bool
92-
SlowQueryThreshold time.Duration
93-
SlowQueries *slowQueries
94-
95-
// These better CAT supportability fields are left outside of
96-
// TxnEvent.BetterCAT to minimize the size of transaction event memory.
97-
DistributedTracingSupport distributedTracingSupport
98102
}
99103

100104
func (t *txnData) saveTraceSegment(end segmentEnd, name string, attrs spanAttributeMap, externalGUID string) {
@@ -320,11 +324,21 @@ const (
320324
datastoreOperationUnknown = "other"
321325
)
322326

327+
// NoticeErrors indicates whether the errors collected count towards error/ metrics
328+
func (t *txnData) NoticeErrors() bool {
329+
return t.noticeErrors
330+
}
331+
323332
// HasErrors indicates whether the transaction had errors.
324333
func (t *txnData) HasErrors() bool {
325334
return len(t.Errors) > 0
326335
}
327336

337+
// HasExpectedErrors is a special case where the txn has errors but we dont increment error metrics
338+
func (t *txnData) HasExpectedErrors() bool {
339+
return t.expectedErrors
340+
}
341+
328342
func (t *txnData) time(now time.Time) segmentTime {
329343
// Update the stamp before using it so that a 0 stamp can be special.
330344
t.stamp++

0 commit comments

Comments
 (0)