From 08a73c6f39e8e0aec2a757ef55874220b1e04c39 Mon Sep 17 00:00:00 2001 From: Andrew Wilkins Date: Mon, 25 Feb 2019 09:14:05 +0800 Subject: [PATCH] module/apmmongo: mongo.CommandError ErrorDetailer Register an ErrorDetailer for mongo.CommandError, extract the error code and labels. --- error.go | 6 ++++- module/apmmongo/monitor.go | 15 ++++++++++++ module/apmmongo/monitor_integration_test.go | 2 +- module/apmmongo/monitor_test.go | 26 +++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/error.go b/error.go index f0cd9726d..85283b5a7 100644 --- a/error.go +++ b/error.go @@ -568,7 +568,8 @@ var ( ) // ErrorDetails holds details of an error, which can be altered or -// extended by registering an ErrorDetailer with RegisterErrorDetailer. +// extended by registering an ErrorDetailer with RegisterErrorDetailer +// or RegisterTypeErrorDetailer. type ErrorDetails struct { attrs map[string]interface{} @@ -615,6 +616,9 @@ func (d *ErrorDetails) SetAttr(k string, v interface{}) { } // ErrorDetailer defines an interface for altering or extending the ErrorDetails for an error. +// +// ErrorDetailers can be registered using the package-level functions RegisterErrorDetailer and +// RegisterTypeErrorDetailer. type ErrorDetailer interface { // ErrorDetails is called to update or alter details for err. ErrorDetails(err error, details *ErrorDetails) diff --git a/module/apmmongo/monitor.go b/module/apmmongo/monitor.go index e3357c222..edfd96f42 100644 --- a/module/apmmongo/monitor.go +++ b/module/apmmongo/monitor.go @@ -19,6 +19,7 @@ package apmmongo import ( "context" + "reflect" "sync" "time" @@ -26,6 +27,7 @@ import ( "go.mongodb.org/mongo-driver/bson/bsoncodec" "go.mongodb.org/mongo-driver/bson/bsonrw" "go.mongodb.org/mongo-driver/event" + "go.mongodb.org/mongo-driver/mongo" "go.elastic.co/apm" ) @@ -39,6 +41,19 @@ var ( } ) +func init() { + apm.RegisterTypeErrorDetailer( + reflect.TypeOf(mongo.CommandError{}), + apm.ErrorDetailerFunc(func(err error, details *apm.ErrorDetails) { + commandErr := err.(mongo.CommandError) + details.Code.String = commandErr.Name + if len(commandErr.Labels) > 0 { + details.SetAttr("labels", commandErr.Labels) + } + }), + ) +} + // CommandMonitor returns a new event.CommandMonitor which will report a span // for each command executed within a context containing a sampled transaction. func CommandMonitor(opts ...Option) *event.CommandMonitor { diff --git a/module/apmmongo/monitor_integration_test.go b/module/apmmongo/monitor_integration_test.go index 091865a76..e90ece91e 100644 --- a/module/apmmongo/monitor_integration_test.go +++ b/module/apmmongo/monitor_integration_test.go @@ -110,5 +110,5 @@ func (suite *IntegrationSuite) TestCommandMonitor() { suite.Require().Len(errs, 1) suite.Equal(tx.ID, errs[0].ParentID) suite.Equal("(UserNotFound) User 'bob@test_db' not found", errs[0].Exception.Message) - suite.Equal(model.ExceptionCode{}, errs[0].Exception.Code) // BUG(axw) https://github.com/elastic/apm-agent-go/issues/447 + suite.Equal(model.ExceptionCode{String: "UserNotFound"}, errs[0].Exception.Code) } diff --git a/module/apmmongo/monitor_test.go b/module/apmmongo/monitor_test.go index c7e8a08af..7acb7e4d9 100644 --- a/module/apmmongo/monitor_test.go +++ b/module/apmmongo/monitor_test.go @@ -26,7 +26,9 @@ import ( "github.com/stretchr/testify/require" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/event" + "go.mongodb.org/mongo-driver/mongo" + "go.elastic.co/apm" "go.elastic.co/apm/apmtest" "go.elastic.co/apm/model" "go.elastic.co/apm/module/apmmongo" @@ -179,6 +181,30 @@ func TestCommandMonitorFinishedNotStarted(t *testing.T) { assert.Empty(t, errs) } +func TestCommandErrorDetails(t *testing.T) { + _, _, errs := apmtest.WithTransaction(func(ctx context.Context) { + apm.CaptureError(ctx, mongo.CommandError{ + Code: 11, + Name: "UserNotFound", + Message: "Robert'); DROP TABLE Students;-- not found", + Labels: []string{"black", "blue", "red"}, + }).Send() + }) + require.Len(t, errs, 1) + + errs[0].Exception.Stacktrace = nil + assert.Equal(t, model.Exception{ + Message: `(UserNotFound) Robert'); DROP TABLE Students;-- not found`, + Type: "CommandError", + Module: "go.mongodb.org/mongo-driver/mongo", + Code: model.ExceptionCode{String: "UserNotFound"}, + Handled: true, + Attributes: map[string]interface{}{ + "labels": []interface{}{"black", "blue", "red"}, + }, + }, errs[0].Exception) +} + func mustRawBSON(val interface{}) bson.Raw { out, err := bson.Marshal(val) if err != nil {