github.com/newrelic/go-agent@v3.26.0+incompatible/internal_segment_attributes_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  	"net/http"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/newrelic/go-agent/internal"
    13  )
    14  
    15  func TestTraceSegments(t *testing.T) {
    16  	replyfn := func(reply *internal.ConnectReply) {
    17  		reply.AdaptiveSampler = internal.SampleEverything{}
    18  	}
    19  	cfgfn := func(cfg *Config) {
    20  		cfg.TransactionTracer.SegmentThreshold = 0
    21  		cfg.TransactionTracer.StackTraceThreshold = 0
    22  		cfg.TransactionTracer.Threshold.IsApdexFailing = false
    23  		cfg.TransactionTracer.Threshold.Duration = 0
    24  
    25  		// Disable span event attributes to ensure they are separate.
    26  		cfg.DistributedTracer.Enabled = true
    27  		cfg.SpanEvents.Attributes.Enabled = false
    28  
    29  	}
    30  	app := testApp(replyfn, cfgfn, t)
    31  	txn := app.StartTransaction("hello", nil, nil)
    32  	basicSegment := StartSegment(txn, "basic")
    33  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west")
    34  	basicSegment.End()
    35  	datastoreSegment := DatastoreSegment{
    36  		StartTime:          StartSegmentNow(txn),
    37  		Product:            DatastoreMySQL,
    38  		Collection:         "mycollection",
    39  		Operation:          "myoperation",
    40  		ParameterizedQuery: "myquery",
    41  		Host:               "myhost",
    42  		PortPathOrID:       "myport",
    43  		DatabaseName:       "dbname",
    44  		QueryParameters:    map[string]interface{}{"zap": "zip"},
    45  	}
    46  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123")
    47  	datastoreSegment.End()
    48  	req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil)
    49  	externalSegment := StartExternalSegment(txn, req)
    50  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret")
    51  	externalSegment.End()
    52  	txn.End()
    53  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
    54  		MetricName: "OtherTransaction/Go/hello",
    55  		Root: internal.WantTraceSegment{
    56  			SegmentName: "ROOT",
    57  			Attributes:  map[string]interface{}{},
    58  			Children: []internal.WantTraceSegment{{
    59  				SegmentName: "OtherTransaction/Go/hello",
    60  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
    61  				Children: []internal.WantTraceSegment{
    62  					{
    63  						SegmentName: "Custom/basic",
    64  						Attributes: map[string]interface{}{
    65  							"backtrace":  internal.MatchAnything,
    66  							"aws.region": "west",
    67  						},
    68  					},
    69  					{
    70  						SegmentName: "Datastore/statement/MySQL/mycollection/myoperation",
    71  						Attributes: map[string]interface{}{
    72  							"backtrace":        internal.MatchAnything,
    73  							"query_parameters": "map[zap:zip]",
    74  							"peer.address":     "myhost:myport",
    75  							"peer.hostname":    "myhost",
    76  							"db.statement":     "myquery",
    77  							"db.instance":      "dbname",
    78  							"aws.requestId":    123,
    79  						},
    80  					},
    81  					{
    82  						SegmentName: "External/example.com/http/GET",
    83  						Attributes: map[string]interface{}{
    84  							"backtrace":     internal.MatchAnything,
    85  							"http.url":      "http://example.com",
    86  							"aws.operation": "secret",
    87  						},
    88  					},
    89  				},
    90  			}},
    91  		},
    92  	}})
    93  }
    94  
    95  func TestTraceSegmentsNoBacktrace(t *testing.T) {
    96  	// Test that backtrace will only appear if the segment's duration
    97  	// exceeds TransactionTracer.StackTraceThreshold.
    98  	replyfn := func(reply *internal.ConnectReply) {
    99  		reply.AdaptiveSampler = internal.SampleEverything{}
   100  	}
   101  	cfgfn := func(cfg *Config) {
   102  		cfg.TransactionTracer.SegmentThreshold = 0
   103  		cfg.TransactionTracer.StackTraceThreshold = 1 * time.Hour
   104  		cfg.TransactionTracer.Threshold.IsApdexFailing = false
   105  		cfg.TransactionTracer.Threshold.Duration = 0
   106  
   107  		// Disable span event attributes to ensure they are separate.
   108  		cfg.DistributedTracer.Enabled = true
   109  		cfg.SpanEvents.Attributes.Enabled = false
   110  
   111  	}
   112  	app := testApp(replyfn, cfgfn, t)
   113  	txn := app.StartTransaction("hello", nil, nil)
   114  	basicSegment := StartSegment(txn, "basic")
   115  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west")
   116  	basicSegment.End()
   117  	datastoreSegment := DatastoreSegment{
   118  		StartTime:          StartSegmentNow(txn),
   119  		Product:            DatastoreMySQL,
   120  		Collection:         "mycollection",
   121  		Operation:          "myoperation",
   122  		ParameterizedQuery: "myquery",
   123  		Host:               "myhost",
   124  		PortPathOrID:       "myport",
   125  		DatabaseName:       "dbname",
   126  		QueryParameters:    map[string]interface{}{"zap": "zip"},
   127  	}
   128  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123")
   129  	datastoreSegment.End()
   130  	req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil)
   131  	externalSegment := StartExternalSegment(txn, req)
   132  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret")
   133  	externalSegment.End()
   134  	txn.End()
   135  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   136  		MetricName: "OtherTransaction/Go/hello",
   137  		Root: internal.WantTraceSegment{
   138  			SegmentName: "ROOT",
   139  			Attributes:  map[string]interface{}{},
   140  			Children: []internal.WantTraceSegment{{
   141  				SegmentName: "OtherTransaction/Go/hello",
   142  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   143  				Children: []internal.WantTraceSegment{
   144  					{
   145  						SegmentName: "Custom/basic",
   146  						Attributes: map[string]interface{}{
   147  							"aws.region": "west",
   148  						},
   149  					},
   150  					{
   151  						SegmentName: "Datastore/statement/MySQL/mycollection/myoperation",
   152  						Attributes: map[string]interface{}{
   153  							"query_parameters": "map[zap:zip]",
   154  							"peer.address":     "myhost:myport",
   155  							"peer.hostname":    "myhost",
   156  							"db.statement":     "myquery",
   157  							"db.instance":      "dbname",
   158  							"aws.requestId":    123,
   159  						},
   160  					},
   161  					{
   162  						SegmentName: "External/example.com/http/GET",
   163  						Attributes: map[string]interface{}{
   164  							"http.url":      "http://example.com",
   165  							"aws.operation": "secret",
   166  						},
   167  					},
   168  				},
   169  			}},
   170  		},
   171  	}})
   172  }
   173  
   174  func TestTraceStacktraceServerSideConfig(t *testing.T) {
   175  	// Test that the server-side-config stack trace threshold is observed.
   176  	replyfn := func(reply *internal.ConnectReply) {
   177  		json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.stack_trace_threshold":0}}`), reply)
   178  	}
   179  	cfgfn := func(cfg *Config) {
   180  		cfg.TransactionTracer.SegmentThreshold = 0
   181  		cfg.TransactionTracer.StackTraceThreshold = 1 * time.Hour
   182  		cfg.TransactionTracer.Threshold.IsApdexFailing = false
   183  		cfg.TransactionTracer.Threshold.Duration = 0
   184  	}
   185  	app := testApp(replyfn, cfgfn, t)
   186  	txn := app.StartTransaction("hello", nil, nil)
   187  	basicSegment := StartSegment(txn, "basic")
   188  	basicSegment.End()
   189  	txn.End()
   190  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   191  		MetricName: "OtherTransaction/Go/hello",
   192  		Root: internal.WantTraceSegment{
   193  			SegmentName: "ROOT",
   194  			Attributes:  map[string]interface{}{},
   195  			Children: []internal.WantTraceSegment{{
   196  				SegmentName: "OtherTransaction/Go/hello",
   197  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   198  				Children: []internal.WantTraceSegment{
   199  					{
   200  						SegmentName: "Custom/basic",
   201  						Attributes: map[string]interface{}{
   202  							"backtrace": internal.MatchAnything,
   203  						},
   204  					},
   205  				},
   206  			}},
   207  		},
   208  	}})
   209  }
   210  
   211  func TestTraceSegmentAttributesExcluded(t *testing.T) {
   212  	// Test that segment attributes can be excluded by Attributes.Exclude.
   213  	replyfn := func(reply *internal.ConnectReply) {
   214  		reply.AdaptiveSampler = internal.SampleEverything{}
   215  	}
   216  	cfgfn := func(cfg *Config) {
   217  		cfg.TransactionTracer.SegmentThreshold = 0
   218  		cfg.TransactionTracer.StackTraceThreshold = 1 * time.Hour
   219  		cfg.TransactionTracer.Threshold.IsApdexFailing = false
   220  		cfg.TransactionTracer.Threshold.Duration = 0
   221  		cfg.Attributes.Exclude = []string{
   222  			SpanAttributeDBStatement,
   223  			SpanAttributeDBInstance,
   224  			SpanAttributeDBCollection,
   225  			SpanAttributePeerAddress,
   226  			SpanAttributePeerHostname,
   227  			SpanAttributeHTTPURL,
   228  			SpanAttributeHTTPMethod,
   229  			SpanAttributeAWSOperation,
   230  			SpanAttributeAWSRequestID,
   231  			SpanAttributeAWSRegion,
   232  			"query_parameters",
   233  		}
   234  
   235  		// Disable span event attributes to ensure they are separate.
   236  		cfg.DistributedTracer.Enabled = true
   237  		cfg.SpanEvents.Attributes.Enabled = false
   238  
   239  	}
   240  	app := testApp(replyfn, cfgfn, t)
   241  	txn := app.StartTransaction("hello", nil, nil)
   242  	basicSegment := StartSegment(txn, "basic")
   243  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west")
   244  	basicSegment.End()
   245  	datastoreSegment := DatastoreSegment{
   246  		StartTime:          StartSegmentNow(txn),
   247  		Product:            DatastoreMySQL,
   248  		Collection:         "mycollection",
   249  		Operation:          "myoperation",
   250  		ParameterizedQuery: "myquery",
   251  		Host:               "myhost",
   252  		PortPathOrID:       "myport",
   253  		DatabaseName:       "dbname",
   254  		QueryParameters:    map[string]interface{}{"zap": "zip"},
   255  	}
   256  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123")
   257  	datastoreSegment.End()
   258  	req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil)
   259  	externalSegment := StartExternalSegment(txn, req)
   260  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret")
   261  	externalSegment.End()
   262  	txn.End()
   263  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   264  		MetricName: "OtherTransaction/Go/hello",
   265  		Root: internal.WantTraceSegment{
   266  			SegmentName: "ROOT",
   267  			Attributes:  map[string]interface{}{},
   268  			Children: []internal.WantTraceSegment{{
   269  				SegmentName: "OtherTransaction/Go/hello",
   270  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   271  				Children: []internal.WantTraceSegment{
   272  					{
   273  						SegmentName: "Custom/basic",
   274  						Attributes:  map[string]interface{}{},
   275  					},
   276  					{
   277  						SegmentName: "Datastore/statement/MySQL/mycollection/myoperation",
   278  						Attributes:  map[string]interface{}{},
   279  					},
   280  					{
   281  						SegmentName: "External/example.com/http/GET",
   282  						Attributes:  map[string]interface{}{},
   283  					},
   284  				},
   285  			}},
   286  		},
   287  	}})
   288  }
   289  
   290  func TestTraceSegmentAttributesSpecificallyExcluded(t *testing.T) {
   291  	// Test that segment attributes can be excluded by
   292  	// TransactionTracer.Segments.Attributes.Exclude.
   293  	replyfn := func(reply *internal.ConnectReply) {
   294  		reply.AdaptiveSampler = internal.SampleEverything{}
   295  	}
   296  	cfgfn := func(cfg *Config) {
   297  		cfg.TransactionTracer.SegmentThreshold = 0
   298  		cfg.TransactionTracer.StackTraceThreshold = 1 * time.Hour
   299  		cfg.TransactionTracer.Threshold.IsApdexFailing = false
   300  		cfg.TransactionTracer.Threshold.Duration = 0
   301  		cfg.TransactionTracer.Segments.Attributes.Exclude = []string{
   302  			SpanAttributeDBStatement,
   303  			SpanAttributeDBInstance,
   304  			SpanAttributeDBCollection,
   305  			SpanAttributePeerAddress,
   306  			SpanAttributePeerHostname,
   307  			SpanAttributeHTTPURL,
   308  			SpanAttributeHTTPMethod,
   309  			SpanAttributeAWSOperation,
   310  			SpanAttributeAWSRequestID,
   311  			SpanAttributeAWSRegion,
   312  			"query_parameters",
   313  		}
   314  
   315  		// Disable span event attributes to ensure they are separate.
   316  		cfg.DistributedTracer.Enabled = true
   317  		cfg.SpanEvents.Attributes.Enabled = false
   318  
   319  	}
   320  	app := testApp(replyfn, cfgfn, t)
   321  	txn := app.StartTransaction("hello", nil, nil)
   322  	basicSegment := StartSegment(txn, "basic")
   323  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west")
   324  	basicSegment.End()
   325  	datastoreSegment := DatastoreSegment{
   326  		StartTime:          StartSegmentNow(txn),
   327  		Product:            DatastoreMySQL,
   328  		Collection:         "mycollection",
   329  		Operation:          "myoperation",
   330  		ParameterizedQuery: "myquery",
   331  		Host:               "myhost",
   332  		PortPathOrID:       "myport",
   333  		DatabaseName:       "dbname",
   334  		QueryParameters:    map[string]interface{}{"zap": "zip"},
   335  	}
   336  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123")
   337  	datastoreSegment.End()
   338  	req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil)
   339  	externalSegment := StartExternalSegment(txn, req)
   340  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret")
   341  	externalSegment.End()
   342  	txn.End()
   343  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   344  		MetricName: "OtherTransaction/Go/hello",
   345  		Root: internal.WantTraceSegment{
   346  			SegmentName: "ROOT",
   347  			Attributes:  map[string]interface{}{},
   348  			Children: []internal.WantTraceSegment{{
   349  				SegmentName: "OtherTransaction/Go/hello",
   350  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   351  				Children: []internal.WantTraceSegment{
   352  					{
   353  						SegmentName: "Custom/basic",
   354  						Attributes:  map[string]interface{}{},
   355  					},
   356  					{
   357  						SegmentName: "Datastore/statement/MySQL/mycollection/myoperation",
   358  						Attributes:  map[string]interface{}{},
   359  					},
   360  					{
   361  						SegmentName: "External/example.com/http/GET",
   362  						Attributes:  map[string]interface{}{},
   363  					},
   364  				},
   365  			}},
   366  		},
   367  	}})
   368  }
   369  
   370  func TestTraceSegmentAttributesDisabled(t *testing.T) {
   371  	// Test that segment attributes can be disabled by Attributes.Enabled
   372  	// but backtrace and transaction_guid still appear.
   373  	cfgfn := func(cfg *Config) {
   374  		cfg.Attributes.Enabled = false
   375  		cfg.TransactionTracer.SegmentThreshold = 0
   376  		cfg.TransactionTracer.StackTraceThreshold = 0
   377  		cfg.TransactionTracer.Threshold.IsApdexFailing = false
   378  		cfg.TransactionTracer.Threshold.Duration = 0
   379  	}
   380  	app := testApp(crossProcessReplyFn, cfgfn, t)
   381  	txn := app.StartTransaction("hello", nil, nil)
   382  	basicSegment := StartSegment(txn, "basic")
   383  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west")
   384  	basicSegment.End()
   385  	datastoreSegment := DatastoreSegment{
   386  		StartTime:          StartSegmentNow(txn),
   387  		Product:            DatastoreMySQL,
   388  		Collection:         "mycollection",
   389  		Operation:          "myoperation",
   390  		ParameterizedQuery: "myquery",
   391  		Host:               "myhost",
   392  		PortPathOrID:       "myport",
   393  		DatabaseName:       "dbname",
   394  		QueryParameters:    map[string]interface{}{"zap": "zip"},
   395  	}
   396  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123")
   397  	datastoreSegment.End()
   398  	req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil)
   399  	externalSegment := StartExternalSegment(txn, req)
   400  	externalSegment.Response = &http.Response{
   401  		Header: outboundCrossProcessResponse(),
   402  	}
   403  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret")
   404  	externalSegment.End()
   405  	txn.End()
   406  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   407  		MetricName: "OtherTransaction/Go/hello",
   408  		Root: internal.WantTraceSegment{
   409  			SegmentName: "ROOT",
   410  			Attributes:  map[string]interface{}{},
   411  			Children: []internal.WantTraceSegment{{
   412  				SegmentName: "OtherTransaction/Go/hello",
   413  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   414  				Children: []internal.WantTraceSegment{
   415  					{
   416  						SegmentName: "Custom/basic",
   417  						Attributes: map[string]interface{}{
   418  							"backtrace": internal.MatchAnything,
   419  						},
   420  					},
   421  					{
   422  						SegmentName: "Datastore/statement/MySQL/mycollection/myoperation",
   423  						Attributes: map[string]interface{}{
   424  							"backtrace": internal.MatchAnything,
   425  						},
   426  					},
   427  					{
   428  						SegmentName: "ExternalTransaction/example.com/12345#67890/WebTransaction/Go/txn",
   429  						Attributes: map[string]interface{}{
   430  							"backtrace":        internal.MatchAnything,
   431  							"transaction_guid": internal.MatchAnything,
   432  						},
   433  					},
   434  				},
   435  			}},
   436  		},
   437  	}})
   438  }
   439  
   440  func TestTraceSegmentAttributesSpecificallyDisabled(t *testing.T) {
   441  	// Test that segment attributes can be disabled by
   442  	// TransactionTracer.Segments.Attributes.Enabled but backtrace and
   443  	// transaction_guid still appear.
   444  	cfgfn := func(cfg *Config) {
   445  		cfg.TransactionTracer.Segments.Attributes.Enabled = false
   446  		cfg.TransactionTracer.SegmentThreshold = 0
   447  		cfg.TransactionTracer.StackTraceThreshold = 0
   448  		cfg.TransactionTracer.Threshold.IsApdexFailing = false
   449  		cfg.TransactionTracer.Threshold.Duration = 0
   450  	}
   451  	app := testApp(crossProcessReplyFn, cfgfn, t)
   452  	txn := app.StartTransaction("hello", nil, nil)
   453  	basicSegment := StartSegment(txn, "basic")
   454  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRegion, "west")
   455  	basicSegment.End()
   456  	datastoreSegment := DatastoreSegment{
   457  		StartTime:          StartSegmentNow(txn),
   458  		Product:            DatastoreMySQL,
   459  		Collection:         "mycollection",
   460  		Operation:          "myoperation",
   461  		ParameterizedQuery: "myquery",
   462  		Host:               "myhost",
   463  		PortPathOrID:       "myport",
   464  		DatabaseName:       "dbname",
   465  		QueryParameters:    map[string]interface{}{"zap": "zip"},
   466  	}
   467  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSRequestID, "123")
   468  	datastoreSegment.End()
   469  	req, _ := http.NewRequest("GET", "http://example.com?ignore=me", nil)
   470  	externalSegment := StartExternalSegment(txn, req)
   471  	externalSegment.Response = &http.Response{
   472  		Header: outboundCrossProcessResponse(),
   473  	}
   474  	internal.AddAgentSpanAttribute(txn, internal.SpanAttributeAWSOperation, "secret")
   475  	externalSegment.End()
   476  	txn.End()
   477  	app.ExpectTxnTraces(t, []internal.WantTxnTrace{{
   478  		MetricName: "OtherTransaction/Go/hello",
   479  		Root: internal.WantTraceSegment{
   480  			SegmentName: "ROOT",
   481  			Attributes:  map[string]interface{}{},
   482  			Children: []internal.WantTraceSegment{{
   483  				SegmentName: "OtherTransaction/Go/hello",
   484  				Attributes:  map[string]interface{}{"exclusive_duration_millis": internal.MatchAnything},
   485  				Children: []internal.WantTraceSegment{
   486  					{
   487  						SegmentName: "Custom/basic",
   488  						Attributes: map[string]interface{}{
   489  							"backtrace": internal.MatchAnything,
   490  						},
   491  					},
   492  					{
   493  						SegmentName: "Datastore/statement/MySQL/mycollection/myoperation",
   494  						Attributes: map[string]interface{}{
   495  							"backtrace": internal.MatchAnything,
   496  						},
   497  					},
   498  					{
   499  						SegmentName: "ExternalTransaction/example.com/12345#67890/WebTransaction/Go/txn",
   500  						Attributes: map[string]interface{}{
   501  							"backtrace":        internal.MatchAnything,
   502  							"transaction_guid": internal.MatchAnything,
   503  						},
   504  					},
   505  				},
   506  			}},
   507  		},
   508  	}})
   509  }