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 }