github.com/newrelic/go-agent@v3.26.0+incompatible/internal_errors_test.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package newrelic
     5  
     6  import (
     7  	"encoding/json"
     8  	"runtime"
     9  	"strconv"
    10  	"testing"
    11  
    12  	"github.com/newrelic/go-agent/internal"
    13  )
    14  
    15  type myError struct{}
    16  
    17  func (e myError) Error() string { return "my msg" }
    18  
    19  func TestNoticeErrorBackground(t *testing.T) {
    20  	app := testApp(nil, nil, t)
    21  	txn := app.StartTransaction("hello", nil, nil)
    22  	err := txn.NoticeError(myError{})
    23  	if nil != err {
    24  		t.Error(err)
    25  	}
    26  	txn.End()
    27  	app.ExpectErrors(t, []internal.WantError{{
    28  		TxnName: "OtherTransaction/Go/hello",
    29  		Msg:     "my msg",
    30  		Klass:   "newrelic.myError",
    31  	}})
    32  	app.ExpectErrorEvents(t, []internal.WantEvent{{
    33  		Intrinsics: map[string]interface{}{
    34  			"error.class":     "newrelic.myError",
    35  			"error.message":   "my msg",
    36  			"transactionName": "OtherTransaction/Go/hello",
    37  		},
    38  	}})
    39  	app.ExpectMetrics(t, backgroundErrorMetrics)
    40  }
    41  
    42  func TestNoticeErrorWeb(t *testing.T) {
    43  	app := testApp(nil, nil, t)
    44  	txn := app.StartTransaction("hello", nil, helloRequest)
    45  	err := txn.NoticeError(myError{})
    46  	if nil != err {
    47  		t.Error(err)
    48  	}
    49  	txn.End()
    50  	app.ExpectErrors(t, []internal.WantError{{
    51  		TxnName: "WebTransaction/Go/hello",
    52  		Msg:     "my msg",
    53  		Klass:   "newrelic.myError",
    54  	}})
    55  	app.ExpectErrorEvents(t, []internal.WantEvent{{
    56  		Intrinsics: map[string]interface{}{
    57  			"error.class":     "newrelic.myError",
    58  			"error.message":   "my msg",
    59  			"transactionName": "WebTransaction/Go/hello",
    60  		},
    61  		AgentAttributes: helloRequestAttributes,
    62  	}})
    63  	app.ExpectMetrics(t, webErrorMetrics)
    64  }
    65  
    66  func TestNoticeErrorTxnEnded(t *testing.T) {
    67  	app := testApp(nil, nil, t)
    68  	txn := app.StartTransaction("hello", nil, nil)
    69  	txn.End()
    70  	err := txn.NoticeError(myError{})
    71  	if err != errAlreadyEnded {
    72  		t.Error(err)
    73  	}
    74  	txn.End()
    75  	app.ExpectErrors(t, []internal.WantError{})
    76  	app.ExpectErrorEvents(t, []internal.WantEvent{})
    77  	app.ExpectMetrics(t, backgroundMetrics)
    78  }
    79  
    80  func TestNoticeErrorHighSecurity(t *testing.T) {
    81  	cfgFn := func(cfg *Config) { cfg.HighSecurity = true }
    82  	app := testApp(nil, cfgFn, t)
    83  	txn := app.StartTransaction("hello", nil, nil)
    84  	err := txn.NoticeError(myError{})
    85  	if nil != err {
    86  		t.Error(err)
    87  	}
    88  	txn.End()
    89  	app.ExpectErrors(t, []internal.WantError{{
    90  		TxnName: "OtherTransaction/Go/hello",
    91  		Msg:     highSecurityErrorMsg,
    92  		Klass:   "newrelic.myError",
    93  	}})
    94  	app.ExpectErrorEvents(t, []internal.WantEvent{{
    95  		Intrinsics: map[string]interface{}{
    96  			"error.class":     "newrelic.myError",
    97  			"error.message":   highSecurityErrorMsg,
    98  			"transactionName": "OtherTransaction/Go/hello",
    99  		},
   100  	}})
   101  	app.ExpectMetrics(t, backgroundErrorMetrics)
   102  }
   103  
   104  func TestNoticeErrorMessageSecurityPolicy(t *testing.T) {
   105  	replyfn := func(reply *internal.ConnectReply) { reply.SecurityPolicies.AllowRawExceptionMessages.SetEnabled(false) }
   106  	app := testApp(replyfn, nil, t)
   107  	txn := app.StartTransaction("hello", nil, nil)
   108  	err := txn.NoticeError(myError{})
   109  	if nil != err {
   110  		t.Error(err)
   111  	}
   112  	txn.End()
   113  	app.ExpectErrors(t, []internal.WantError{{
   114  		TxnName: "OtherTransaction/Go/hello",
   115  		Msg:     securityPolicyErrorMsg,
   116  		Klass:   "newrelic.myError",
   117  	}})
   118  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   119  		Intrinsics: map[string]interface{}{
   120  			"error.class":     "newrelic.myError",
   121  			"error.message":   securityPolicyErrorMsg,
   122  			"transactionName": "OtherTransaction/Go/hello",
   123  		},
   124  	}})
   125  	app.ExpectMetrics(t, backgroundErrorMetrics)
   126  }
   127  
   128  func TestNoticeErrorLocallyDisabled(t *testing.T) {
   129  	cfgFn := func(cfg *Config) { cfg.ErrorCollector.Enabled = false }
   130  	app := testApp(nil, cfgFn, t)
   131  	txn := app.StartTransaction("hello", nil, nil)
   132  	err := txn.NoticeError(myError{})
   133  	if errorsDisabled != err {
   134  		t.Error(err)
   135  	}
   136  	txn.End()
   137  	app.ExpectErrors(t, []internal.WantError{})
   138  	app.ExpectErrorEvents(t, []internal.WantEvent{})
   139  	app.ExpectMetrics(t, backgroundMetrics)
   140  }
   141  
   142  func TestErrorsDisabledByServerSideConfig(t *testing.T) {
   143  	// Test that errors can be disabled by server-side-config.
   144  	cfgFn := func(cfg *Config) {}
   145  	replyfn := func(reply *internal.ConnectReply) {
   146  		json.Unmarshal([]byte(`{"agent_config":{"error_collector.enabled":false}}`), reply)
   147  	}
   148  	app := testApp(replyfn, cfgFn, t)
   149  	txn := app.StartTransaction("hello", nil, nil)
   150  	err := txn.NoticeError(myError{})
   151  	if errorsDisabled != err {
   152  		t.Error(err)
   153  	}
   154  	txn.End()
   155  	app.ExpectErrors(t, []internal.WantError{})
   156  	app.ExpectErrorEvents(t, []internal.WantEvent{})
   157  	app.ExpectMetrics(t, backgroundMetrics)
   158  }
   159  
   160  func TestErrorsEnabledByServerSideConfig(t *testing.T) {
   161  	// Test that errors can be enabled by server-side-config.
   162  	cfgFn := func(cfg *Config) {
   163  		cfg.ErrorCollector.Enabled = false
   164  	}
   165  	replyfn := func(reply *internal.ConnectReply) {
   166  		json.Unmarshal([]byte(`{"agent_config":{"error_collector.enabled":true}}`), reply)
   167  	}
   168  	app := testApp(replyfn, cfgFn, t)
   169  	txn := app.StartTransaction("hello", nil, nil)
   170  	err := txn.NoticeError(myError{})
   171  	if nil != err {
   172  		t.Error(err)
   173  	}
   174  	txn.End()
   175  	app.ExpectErrors(t, []internal.WantError{{
   176  		TxnName: "OtherTransaction/Go/hello",
   177  		Msg:     "my msg",
   178  		Klass:   "newrelic.myError",
   179  	}})
   180  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   181  		Intrinsics: map[string]interface{}{
   182  			"error.class":     "newrelic.myError",
   183  			"error.message":   "my msg",
   184  			"transactionName": "OtherTransaction/Go/hello",
   185  		},
   186  		UserAttributes:  map[string]interface{}{},
   187  		AgentAttributes: map[string]interface{}{},
   188  	}})
   189  	app.ExpectMetrics(t, backgroundErrorMetrics)
   190  }
   191  
   192  func TestNoticeErrorTracedErrorsRemotelyDisabled(t *testing.T) {
   193  	// This tests that the connect reply field "collect_errors" controls the
   194  	// collection of traced-errors, not error-events.
   195  	replyfn := func(reply *internal.ConnectReply) { reply.CollectErrors = false }
   196  	app := testApp(replyfn, nil, t)
   197  	txn := app.StartTransaction("hello", nil, nil)
   198  	err := txn.NoticeError(myError{})
   199  	if err != nil {
   200  		t.Error(err)
   201  	}
   202  	txn.End()
   203  	app.ExpectErrors(t, []internal.WantError{})
   204  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   205  		Intrinsics: map[string]interface{}{
   206  			"error.class":     "newrelic.myError",
   207  			"error.message":   "my msg",
   208  			"transactionName": "OtherTransaction/Go/hello",
   209  		},
   210  		UserAttributes:  map[string]interface{}{},
   211  		AgentAttributes: map[string]interface{}{},
   212  	}})
   213  	app.ExpectMetrics(t, backgroundErrorMetrics)
   214  }
   215  
   216  func TestNoticeErrorNil(t *testing.T) {
   217  	app := testApp(nil, nil, t)
   218  	txn := app.StartTransaction("hello", nil, nil)
   219  	err := txn.NoticeError(nil)
   220  	if errNilError != err {
   221  		t.Error(err)
   222  	}
   223  	txn.End()
   224  	app.ExpectErrors(t, []internal.WantError{})
   225  	app.ExpectErrorEvents(t, []internal.WantEvent{})
   226  	app.ExpectMetrics(t, backgroundMetrics)
   227  }
   228  
   229  func TestNoticeErrorEventsLocallyDisabled(t *testing.T) {
   230  	cfgFn := func(cfg *Config) { cfg.ErrorCollector.CaptureEvents = false }
   231  	app := testApp(nil, cfgFn, t)
   232  	txn := app.StartTransaction("hello", nil, nil)
   233  	err := txn.NoticeError(myError{})
   234  	if nil != err {
   235  		t.Error(err)
   236  	}
   237  	txn.End()
   238  	app.ExpectErrors(t, []internal.WantError{{
   239  		TxnName: "OtherTransaction/Go/hello",
   240  		Msg:     "my msg",
   241  		Klass:   "newrelic.myError",
   242  	}})
   243  	app.ExpectErrorEvents(t, []internal.WantEvent{})
   244  	app.ExpectMetrics(t, backgroundErrorMetrics)
   245  }
   246  
   247  func TestNoticeErrorEventsRemotelyDisabled(t *testing.T) {
   248  	replyfn := func(reply *internal.ConnectReply) { reply.CollectErrorEvents = false }
   249  	app := testApp(replyfn, nil, t)
   250  	txn := app.StartTransaction("hello", nil, nil)
   251  	err := txn.NoticeError(myError{})
   252  	if nil != err {
   253  		t.Error(err)
   254  	}
   255  	txn.End()
   256  	app.ExpectErrors(t, []internal.WantError{{
   257  		TxnName: "OtherTransaction/Go/hello",
   258  		Msg:     "my msg",
   259  		Klass:   "newrelic.myError",
   260  	}})
   261  	app.ExpectErrorEvents(t, []internal.WantEvent{})
   262  	app.ExpectMetrics(t, backgroundErrorMetrics)
   263  }
   264  
   265  type errorWithClass struct{ class string }
   266  
   267  func (e errorWithClass) Error() string      { return "my msg" }
   268  func (e errorWithClass) ErrorClass() string { return e.class }
   269  
   270  func TestErrorWithClasser(t *testing.T) {
   271  	app := testApp(nil, nil, t)
   272  	txn := app.StartTransaction("hello", nil, nil)
   273  	err := txn.NoticeError(errorWithClass{class: "zap"})
   274  	if nil != err {
   275  		t.Error(err)
   276  	}
   277  	txn.End()
   278  	app.ExpectErrors(t, []internal.WantError{{
   279  		TxnName: "OtherTransaction/Go/hello",
   280  		Msg:     "my msg",
   281  		Klass:   "zap",
   282  	}})
   283  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   284  		Intrinsics: map[string]interface{}{
   285  			"error.class":     "zap",
   286  			"error.message":   "my msg",
   287  			"transactionName": "OtherTransaction/Go/hello",
   288  		},
   289  	}})
   290  	app.ExpectMetrics(t, backgroundErrorMetrics)
   291  }
   292  
   293  func TestErrorWithClasserReturnsEmpty(t *testing.T) {
   294  	app := testApp(nil, nil, t)
   295  	txn := app.StartTransaction("hello", nil, nil)
   296  	err := txn.NoticeError(errorWithClass{class: ""})
   297  	if nil != err {
   298  		t.Error(err)
   299  	}
   300  	txn.End()
   301  	app.ExpectErrors(t, []internal.WantError{{
   302  		TxnName: "OtherTransaction/Go/hello",
   303  		Msg:     "my msg",
   304  		Klass:   "newrelic.errorWithClass",
   305  	}})
   306  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   307  		Intrinsics: map[string]interface{}{
   308  			"error.class":     "newrelic.errorWithClass",
   309  			"error.message":   "my msg",
   310  			"transactionName": "OtherTransaction/Go/hello",
   311  		},
   312  	}})
   313  	app.ExpectMetrics(t, backgroundErrorMetrics)
   314  }
   315  
   316  type withStackTrace struct{ trace []uintptr }
   317  
   318  func makeErrorWithStackTrace() error {
   319  	callers := make([]uintptr, 20)
   320  	written := runtime.Callers(1, callers)
   321  	return withStackTrace{
   322  		trace: callers[0:written],
   323  	}
   324  }
   325  
   326  func (e withStackTrace) Error() string         { return "my msg" }
   327  func (e withStackTrace) StackTrace() []uintptr { return e.trace }
   328  
   329  func TestErrorWithStackTrace(t *testing.T) {
   330  	app := testApp(nil, nil, t)
   331  	txn := app.StartTransaction("hello", nil, nil)
   332  	e := makeErrorWithStackTrace()
   333  	err := txn.NoticeError(e)
   334  	if nil != err {
   335  		t.Error(err)
   336  	}
   337  	txn.End()
   338  	app.ExpectErrors(t, []internal.WantError{{
   339  		TxnName: "OtherTransaction/Go/hello",
   340  		Msg:     "my msg",
   341  		Klass:   "newrelic.withStackTrace",
   342  	}})
   343  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   344  		Intrinsics: map[string]interface{}{
   345  			"error.class":     "newrelic.withStackTrace",
   346  			"error.message":   "my msg",
   347  			"transactionName": "OtherTransaction/Go/hello",
   348  		},
   349  	}})
   350  	app.ExpectMetrics(t, backgroundErrorMetrics)
   351  }
   352  
   353  func TestErrorWithStackTraceReturnsNil(t *testing.T) {
   354  	app := testApp(nil, nil, t)
   355  	txn := app.StartTransaction("hello", nil, nil)
   356  	e := withStackTrace{trace: nil}
   357  	err := txn.NoticeError(e)
   358  	if nil != err {
   359  		t.Error(err)
   360  	}
   361  	txn.End()
   362  	app.ExpectErrors(t, []internal.WantError{{
   363  		TxnName: "OtherTransaction/Go/hello",
   364  		Msg:     "my msg",
   365  		Klass:   "newrelic.withStackTrace",
   366  	}})
   367  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   368  		Intrinsics: map[string]interface{}{
   369  			"error.class":     "newrelic.withStackTrace",
   370  			"error.message":   "my msg",
   371  			"transactionName": "OtherTransaction/Go/hello",
   372  		},
   373  	}})
   374  	app.ExpectMetrics(t, backgroundErrorMetrics)
   375  }
   376  
   377  func TestNewrelicErrorNoAttributes(t *testing.T) {
   378  	app := testApp(nil, nil, t)
   379  	txn := app.StartTransaction("hello", nil, nil)
   380  	err := txn.NoticeError(Error{
   381  		Message: "my msg",
   382  		Class:   "my class",
   383  	})
   384  	if nil != err {
   385  		t.Error(err)
   386  	}
   387  	txn.End()
   388  	app.ExpectErrors(t, []internal.WantError{{
   389  		TxnName: "OtherTransaction/Go/hello",
   390  		Msg:     "my msg",
   391  		Klass:   "my class",
   392  	}})
   393  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   394  		Intrinsics: map[string]interface{}{
   395  			"error.class":     "my class",
   396  			"error.message":   "my msg",
   397  			"transactionName": "OtherTransaction/Go/hello",
   398  		},
   399  	}})
   400  	app.ExpectMetrics(t, backgroundErrorMetrics)
   401  }
   402  
   403  func TestNewrelicErrorValidAttributes(t *testing.T) {
   404  	extraAttributes := map[string]interface{}{
   405  		"zip": "zap",
   406  	}
   407  	app := testApp(nil, nil, t)
   408  	txn := app.StartTransaction("hello", nil, nil)
   409  	err := txn.NoticeError(Error{
   410  		Message:    "my msg",
   411  		Class:      "my class",
   412  		Attributes: extraAttributes,
   413  	})
   414  	if nil != err {
   415  		t.Error(err)
   416  	}
   417  	txn.End()
   418  	app.ExpectErrors(t, []internal.WantError{{
   419  		TxnName:        "OtherTransaction/Go/hello",
   420  		Msg:            "my msg",
   421  		Klass:          "my class",
   422  		UserAttributes: extraAttributes,
   423  	}})
   424  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   425  		Intrinsics: map[string]interface{}{
   426  			"error.class":     "my class",
   427  			"error.message":   "my msg",
   428  			"transactionName": "OtherTransaction/Go/hello",
   429  		},
   430  		UserAttributes: extraAttributes,
   431  	}})
   432  	app.ExpectMetrics(t, backgroundErrorMetrics)
   433  }
   434  
   435  func TestNewrelicErrorAttributesHighSecurity(t *testing.T) {
   436  	extraAttributes := map[string]interface{}{
   437  		"zip": "zap",
   438  	}
   439  	cfgFn := func(cfg *Config) { cfg.HighSecurity = true }
   440  	app := testApp(nil, cfgFn, t)
   441  	txn := app.StartTransaction("hello", nil, nil)
   442  	err := txn.NoticeError(Error{
   443  		Message:    "my msg",
   444  		Class:      "my class",
   445  		Attributes: extraAttributes,
   446  	})
   447  	if nil != err {
   448  		t.Error(err)
   449  	}
   450  	txn.End()
   451  	app.ExpectErrors(t, []internal.WantError{{
   452  		TxnName:        "OtherTransaction/Go/hello",
   453  		Msg:            "message removed by high security setting",
   454  		Klass:          "my class",
   455  		UserAttributes: map[string]interface{}{},
   456  	}})
   457  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   458  		Intrinsics: map[string]interface{}{
   459  			"error.class":     "my class",
   460  			"error.message":   "message removed by high security setting",
   461  			"transactionName": "OtherTransaction/Go/hello",
   462  		},
   463  		UserAttributes: map[string]interface{}{},
   464  	}})
   465  	app.ExpectMetrics(t, backgroundErrorMetrics)
   466  }
   467  
   468  func TestNewrelicErrorAttributesSecurityPolicy(t *testing.T) {
   469  	extraAttributes := map[string]interface{}{
   470  		"zip": "zap",
   471  	}
   472  	replyfn := func(reply *internal.ConnectReply) { reply.SecurityPolicies.CustomParameters.SetEnabled(false) }
   473  	app := testApp(replyfn, nil, t)
   474  	txn := app.StartTransaction("hello", nil, nil)
   475  	err := txn.NoticeError(Error{
   476  		Message:    "my msg",
   477  		Class:      "my class",
   478  		Attributes: extraAttributes,
   479  	})
   480  	if nil != err {
   481  		t.Error(err)
   482  	}
   483  	txn.End()
   484  	app.ExpectErrors(t, []internal.WantError{{
   485  		TxnName:        "OtherTransaction/Go/hello",
   486  		Msg:            "my msg",
   487  		Klass:          "my class",
   488  		UserAttributes: map[string]interface{}{},
   489  	}})
   490  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   491  		Intrinsics: map[string]interface{}{
   492  			"error.class":     "my class",
   493  			"error.message":   "my msg",
   494  			"transactionName": "OtherTransaction/Go/hello",
   495  		},
   496  		UserAttributes: map[string]interface{}{},
   497  	}})
   498  	app.ExpectMetrics(t, backgroundErrorMetrics)
   499  }
   500  
   501  func TestNewrelicErrorAttributeOverridesNormalAttribute(t *testing.T) {
   502  	extraAttributes := map[string]interface{}{
   503  		"zip": "zap",
   504  	}
   505  	app := testApp(nil, nil, t)
   506  	txn := app.StartTransaction("hello", nil, nil)
   507  	if err := txn.AddAttribute("zip", 123); nil != err {
   508  		t.Error(err)
   509  	}
   510  	err := txn.NoticeError(Error{
   511  		Message:    "my msg",
   512  		Class:      "my class",
   513  		Attributes: extraAttributes,
   514  	})
   515  	if nil != err {
   516  		t.Error(err)
   517  	}
   518  	txn.End()
   519  	app.ExpectErrors(t, []internal.WantError{{
   520  		TxnName:        "OtherTransaction/Go/hello",
   521  		Msg:            "my msg",
   522  		Klass:          "my class",
   523  		UserAttributes: extraAttributes,
   524  	}})
   525  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   526  		Intrinsics: map[string]interface{}{
   527  			"error.class":     "my class",
   528  			"error.message":   "my msg",
   529  			"transactionName": "OtherTransaction/Go/hello",
   530  		},
   531  		UserAttributes: extraAttributes,
   532  	}})
   533  	app.ExpectMetrics(t, backgroundErrorMetrics)
   534  }
   535  
   536  func TestNewrelicErrorInvalidAttributes(t *testing.T) {
   537  	extraAttributes := map[string]interface{}{
   538  		"zip":     "zap",
   539  		"INVALID": struct{}{},
   540  	}
   541  	app := testApp(nil, nil, t)
   542  	txn := app.StartTransaction("hello", nil, nil)
   543  	err := txn.NoticeError(Error{
   544  		Message:    "my msg",
   545  		Class:      "my class",
   546  		Attributes: extraAttributes,
   547  	})
   548  	if _, ok := err.(internal.ErrInvalidAttributeType); !ok {
   549  		t.Error(err)
   550  	}
   551  	txn.End()
   552  	app.ExpectErrors(t, []internal.WantError{})
   553  	app.ExpectErrorEvents(t, []internal.WantEvent{})
   554  	app.ExpectMetrics(t, backgroundMetrics)
   555  }
   556  
   557  func TestExtraErrorAttributeRemovedThroughConfiguration(t *testing.T) {
   558  	cfgfn := func(cfg *Config) {
   559  		cfg.ErrorCollector.Attributes.Exclude = []string{"IGNORE_ME"}
   560  	}
   561  	app := testApp(nil, cfgfn, t)
   562  	txn := app.StartTransaction("hello", nil, nil)
   563  	err := txn.NoticeError(Error{
   564  		Message: "my msg",
   565  		Class:   "my class",
   566  		Attributes: map[string]interface{}{
   567  			"zip":       "zap",
   568  			"IGNORE_ME": 123,
   569  		},
   570  	})
   571  	if nil != err {
   572  		t.Error(err)
   573  	}
   574  	txn.End()
   575  	app.ExpectErrors(t, []internal.WantError{{
   576  		TxnName:        "OtherTransaction/Go/hello",
   577  		Msg:            "my msg",
   578  		Klass:          "my class",
   579  		UserAttributes: map[string]interface{}{"zip": "zap"},
   580  	}})
   581  	app.ExpectErrorEvents(t, []internal.WantEvent{{
   582  		Intrinsics: map[string]interface{}{
   583  			"error.class":     "my class",
   584  			"error.message":   "my msg",
   585  			"transactionName": "OtherTransaction/Go/hello",
   586  		},
   587  		UserAttributes: map[string]interface{}{"zip": "zap"},
   588  	}})
   589  	app.ExpectMetrics(t, backgroundErrorMetrics)
   590  
   591  }
   592  
   593  func TestTooManyExtraErrorAttributes(t *testing.T) {
   594  	attrs := make(map[string]interface{})
   595  	for i := 0; i <= internal.AttributeErrorLimit; i++ {
   596  		attrs[strconv.Itoa(i)] = i
   597  	}
   598  	app := testApp(nil, nil, t)
   599  	txn := app.StartTransaction("hello", nil, nil)
   600  	err := txn.NoticeError(Error{
   601  		Message:    "my msg",
   602  		Class:      "my class",
   603  		Attributes: attrs,
   604  	})
   605  	if errTooManyErrorAttributes != err {
   606  		t.Error(err)
   607  	}
   608  	txn.End()
   609  	app.ExpectErrors(t, []internal.WantError{})
   610  	app.ExpectErrorEvents(t, []internal.WantEvent{})
   611  	app.ExpectMetrics(t, backgroundMetrics)
   612  }
   613  
   614  type basicError struct{}
   615  
   616  func (e basicError) Error() string { return "something went wrong" }
   617  
   618  type withClass struct{ class string }
   619  
   620  func (e withClass) Error() string      { return "something went wrong" }
   621  func (e withClass) ErrorClass() string { return e.class }
   622  
   623  type withClassAndCause struct {
   624  	cause error
   625  	class string
   626  }
   627  
   628  func (e withClassAndCause) Error() string      { return e.cause.Error() }
   629  func (e withClassAndCause) Unwrap() error      { return e.cause }
   630  func (e withClassAndCause) ErrorClass() string { return e.class }
   631  
   632  type withCause struct{ cause error }
   633  
   634  func (e withCause) Error() string { return e.cause.Error() }
   635  func (e withCause) Unwrap() error { return e.cause }
   636  
   637  func errWithClass(class string) error           { return withClass{class: class} }
   638  func wrapWithClass(e error, class string) error { return withClassAndCause{cause: e, class: class} }
   639  func wrapError(e error) error                   { return withCause{cause: e} }
   640  
   641  func TestErrorClass(t *testing.T) {
   642  	// First choice is any ErrorClass() of the immediate error.
   643  	// Second choice is any ErrorClass() of the error's cause.
   644  	// Final choice is the reflect type of the error's cause.
   645  	testcases := []struct {
   646  		Error  error
   647  		Expect string
   648  	}{
   649  		{Error: basicError{}, Expect: "newrelic.basicError"},
   650  		{Error: errWithClass("zap"), Expect: "zap"},
   651  		{Error: errWithClass(""), Expect: "newrelic.withClass"},
   652  		{Error: wrapWithClass(errWithClass("zap"), "zip"), Expect: "zip"},
   653  		{Error: wrapWithClass(errWithClass("zap"), ""), Expect: "zap"},
   654  		{Error: wrapWithClass(errWithClass(""), ""), Expect: "newrelic.withClass"},
   655  		{Error: wrapError(basicError{}), Expect: "newrelic.basicError"},
   656  		{Error: wrapError(errWithClass("zap")), Expect: "zap"},
   657  	}
   658  
   659  	for idx, tc := range testcases {
   660  		data, err := errDataFromError(tc.Error)
   661  		if err != nil {
   662  			t.Errorf("testcase %d: got error: %v", idx, err)
   663  			continue
   664  		}
   665  		if data.Klass != tc.Expect {
   666  			t.Errorf("testcase %d: expected %s got %s", idx, tc.Expect, data.Klass)
   667  		}
   668  	}
   669  }