github.com/newrelic/go-agent@v3.26.0+incompatible/internal_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 "errors" 9 "math" 10 "net/http" 11 "net/http/httptest" 12 "testing" 13 "time" 14 15 "github.com/newrelic/go-agent/internal" 16 ) 17 18 var ( 19 singleCount = []float64{1, 0, 0, 0, 0, 0, 0} 20 webMetrics = []internal.WantMetric{ 21 {Name: "WebTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, 22 {Name: "WebTransaction", Scope: "", Forced: true, Data: nil}, 23 {Name: "WebTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, 24 {Name: "WebTransactionTotalTime", Scope: "", Forced: true, Data: nil}, 25 {Name: "HttpDispatcher", Scope: "", Forced: true, Data: nil}, 26 {Name: "Apdex", Scope: "", Forced: true, Data: nil}, 27 {Name: "Apdex/Go/hello", Scope: "", Forced: false, Data: nil}, 28 } 29 webErrorMetrics = append([]internal.WantMetric{ 30 {Name: "Errors/all", Scope: "", Forced: true, Data: singleCount}, 31 {Name: "Errors/allWeb", Scope: "", Forced: true, Data: singleCount}, 32 {Name: "Errors/WebTransaction/Go/hello", Scope: "", Forced: true, Data: singleCount}, 33 }, webMetrics...) 34 backgroundMetrics = []internal.WantMetric{ 35 {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, 36 {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, 37 {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, 38 {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, 39 } 40 backgroundMetricsUnknownCaller = append([]internal.WantMetric{ 41 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, 42 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, 43 }, backgroundMetrics...) 44 backgroundErrorMetrics = append([]internal.WantMetric{ 45 {Name: "Errors/all", Scope: "", Forced: true, Data: singleCount}, 46 {Name: "Errors/allOther", Scope: "", Forced: true, Data: singleCount}, 47 {Name: "Errors/OtherTransaction/Go/hello", Scope: "", Forced: true, Data: singleCount}, 48 }, backgroundMetrics...) 49 ) 50 51 // compatibleResponseRecorder wraps ResponseRecorder to ensure consistent behavior 52 // between different versions of Go. 53 // 54 // Unfortunately, there was a behavior change in go1.6: 55 // 56 // "The net/http/httptest package's ResponseRecorder now initializes a default 57 // Content-Type header using the same content-sniffing algorithm as in 58 // http.Server." 59 type compatibleResponseRecorder struct { 60 *httptest.ResponseRecorder 61 wroteHeader bool 62 } 63 64 func newCompatibleResponseRecorder() *compatibleResponseRecorder { 65 return &compatibleResponseRecorder{ 66 ResponseRecorder: httptest.NewRecorder(), 67 } 68 } 69 70 func (rw *compatibleResponseRecorder) Header() http.Header { 71 return rw.ResponseRecorder.Header() 72 } 73 74 func (rw *compatibleResponseRecorder) Write(buf []byte) (int, error) { 75 if !rw.wroteHeader { 76 rw.WriteHeader(200) 77 rw.wroteHeader = true 78 } 79 return rw.ResponseRecorder.Write(buf) 80 } 81 82 func (rw *compatibleResponseRecorder) WriteHeader(code int) { 83 rw.wroteHeader = true 84 rw.ResponseRecorder.WriteHeader(code) 85 } 86 87 var ( 88 validParams = map[string]interface{}{"zip": 1, "zap": 2} 89 ) 90 91 var ( 92 helloResponse = []byte("hello") 93 helloPath = "/hello" 94 helloQueryParams = "?secret=hideme" 95 helloRequest = func() *http.Request { 96 r, err := http.NewRequest("GET", helloPath+helloQueryParams, nil) 97 if nil != err { 98 panic(err) 99 } 100 101 r.Header.Add(`Accept`, `text/plain`) 102 r.Header.Add(`Content-Type`, `text/html; charset=utf-8`) 103 r.Header.Add(`Content-Length`, `753`) 104 r.Header.Add(`Host`, `my_domain.com`) 105 r.Header.Add(`User-Agent`, `Mozilla/5.0`) 106 r.Header.Add(`Referer`, `http://en.wikipedia.org/zip?secret=password`) 107 108 return r 109 }() 110 helloRequestAttributes = map[string]interface{}{ 111 "request.uri": "/hello", 112 "request.headers.host": "my_domain.com", 113 "request.headers.referer": "http://en.wikipedia.org/zip", 114 "request.headers.contentLength": 753, 115 "request.method": "GET", 116 "request.headers.accept": "text/plain", 117 "request.headers.User-Agent": "Mozilla/5.0", 118 "request.headers.contentType": "text/html; charset=utf-8", 119 } 120 ) 121 122 func TestNewApplicationNil(t *testing.T) { 123 cfg := NewConfig("appname", "wrong length") 124 cfg.Enabled = false 125 app, err := NewApplication(cfg) 126 if nil == err { 127 t.Error("error expected when license key is short") 128 } 129 if nil != app { 130 t.Error("app expected to be nil when error is returned") 131 } 132 } 133 134 func handler(w http.ResponseWriter, req *http.Request) { 135 w.Write(helloResponse) 136 } 137 138 const ( 139 testLicenseKey = "0123456789012345678901234567890123456789" 140 ) 141 142 type expectApp interface { 143 internal.Expect 144 Application 145 } 146 147 func testApp(replyfn func(*internal.ConnectReply), cfgfn func(*Config), t testing.TB) expectApp { 148 cfg := NewConfig("my app", testLicenseKey) 149 150 if nil != cfgfn { 151 cfgfn(&cfg) 152 } 153 154 // Prevent spawning app goroutines in tests. 155 if !cfg.ServerlessMode.Enabled { 156 cfg.Enabled = false 157 } 158 159 app, err := newApp(cfg) 160 if nil != err { 161 t.Fatal(err) 162 } 163 164 internal.HarvestTesting(app, replyfn) 165 166 return app.(expectApp) 167 } 168 169 func TestRecordCustomEventSuccess(t *testing.T) { 170 app := testApp(nil, nil, t) 171 err := app.RecordCustomEvent("myType", validParams) 172 if nil != err { 173 t.Error(err) 174 } 175 app.ExpectCustomEvents(t, []internal.WantEvent{{ 176 Intrinsics: map[string]interface{}{ 177 "type": "myType", 178 "timestamp": internal.MatchAnything, 179 }, 180 UserAttributes: validParams, 181 }}) 182 } 183 184 func TestRecordCustomEventHighSecurityEnabled(t *testing.T) { 185 cfgfn := func(cfg *Config) { cfg.HighSecurity = true } 186 app := testApp(nil, cfgfn, t) 187 err := app.RecordCustomEvent("myType", validParams) 188 if err != errHighSecurityEnabled { 189 t.Error(err) 190 } 191 app.ExpectCustomEvents(t, []internal.WantEvent{}) 192 } 193 194 func TestRecordCustomEventSecurityPolicy(t *testing.T) { 195 replyfn := func(reply *internal.ConnectReply) { reply.SecurityPolicies.CustomEvents.SetEnabled(false) } 196 app := testApp(replyfn, nil, t) 197 err := app.RecordCustomEvent("myType", validParams) 198 if err != errSecurityPolicy { 199 t.Error(err) 200 } 201 app.ExpectCustomEvents(t, []internal.WantEvent{}) 202 } 203 204 func TestRecordCustomEventEventsDisabled(t *testing.T) { 205 cfgfn := func(cfg *Config) { cfg.CustomInsightsEvents.Enabled = false } 206 app := testApp(nil, cfgfn, t) 207 err := app.RecordCustomEvent("myType", validParams) 208 if err != errCustomEventsDisabled { 209 t.Error(err) 210 } 211 app.ExpectCustomEvents(t, []internal.WantEvent{}) 212 } 213 214 func TestRecordCustomEventBadInput(t *testing.T) { 215 app := testApp(nil, nil, t) 216 err := app.RecordCustomEvent("????", validParams) 217 if err != internal.ErrEventTypeRegex { 218 t.Error(err) 219 } 220 app.ExpectCustomEvents(t, []internal.WantEvent{}) 221 } 222 223 func TestRecordCustomEventRemoteDisable(t *testing.T) { 224 replyfn := func(reply *internal.ConnectReply) { reply.CollectCustomEvents = false } 225 app := testApp(replyfn, nil, t) 226 err := app.RecordCustomEvent("myType", validParams) 227 if err != errCustomEventsRemoteDisabled { 228 t.Error(err) 229 } 230 app.ExpectCustomEvents(t, []internal.WantEvent{}) 231 } 232 233 func TestRecordCustomMetricSuccess(t *testing.T) { 234 app := testApp(nil, nil, t) 235 err := app.RecordCustomMetric("myMetric", 123.0) 236 if nil != err { 237 t.Error(err) 238 } 239 expectData := []float64{1, 123.0, 123.0, 123.0, 123.0, 123.0 * 123.0} 240 app.ExpectMetrics(t, []internal.WantMetric{ 241 {Name: "Custom/myMetric", Scope: "", Forced: false, Data: expectData}, 242 }) 243 } 244 245 func TestRecordCustomMetricNameEmpty(t *testing.T) { 246 app := testApp(nil, nil, t) 247 err := app.RecordCustomMetric("", 123.0) 248 if err != errMetricNameEmpty { 249 t.Error(err) 250 } 251 } 252 253 func TestRecordCustomMetricNaN(t *testing.T) { 254 app := testApp(nil, nil, t) 255 err := app.RecordCustomMetric("myMetric", math.NaN()) 256 if err != errMetricNaN { 257 t.Error(err) 258 } 259 } 260 261 func TestRecordCustomMetricPositiveInf(t *testing.T) { 262 app := testApp(nil, nil, t) 263 err := app.RecordCustomMetric("myMetric", math.Inf(0)) 264 if err != errMetricInf { 265 t.Error(err) 266 } 267 } 268 269 func TestRecordCustomMetricNegativeInf(t *testing.T) { 270 app := testApp(nil, nil, t) 271 err := app.RecordCustomMetric("myMetric", math.Inf(-1)) 272 if err != errMetricInf { 273 t.Error(err) 274 } 275 } 276 277 type sampleResponseWriter struct { 278 code int 279 written int 280 header http.Header 281 } 282 283 func (w *sampleResponseWriter) Header() http.Header { return w.header } 284 func (w *sampleResponseWriter) Write([]byte) (int, error) { return w.written, nil } 285 func (w *sampleResponseWriter) WriteHeader(x int) { w.code = x } 286 287 func TestTxnResponseWriter(t *testing.T) { 288 // NOTE: Eventually when the ResponseWriter is instrumented, this test 289 // should be expanded to make sure that calling ResponseWriter methods 290 // after the transaction has ended is not problematic. 291 w := &sampleResponseWriter{ 292 header: make(http.Header), 293 } 294 app := testApp(nil, nil, t) 295 txn := app.StartTransaction("hello", w, nil) 296 w.header.Add("zip", "zap") 297 if out := txn.Header(); out.Get("zip") != "zap" { 298 t.Error(out.Get("zip")) 299 } 300 w.written = 123 301 if out, _ := txn.Write(nil); out != 123 { 302 t.Error(out) 303 } 304 if txn.WriteHeader(503); w.code != 503 { 305 t.Error(w.code) 306 } 307 } 308 309 func TestTransactionEventWeb(t *testing.T) { 310 app := testApp(nil, nil, t) 311 txn := app.StartTransaction("hello", nil, helloRequest) 312 err := txn.End() 313 if nil != err { 314 t.Error(err) 315 } 316 app.ExpectTxnEvents(t, []internal.WantEvent{{ 317 Intrinsics: map[string]interface{}{ 318 "name": "WebTransaction/Go/hello", 319 "nr.apdexPerfZone": "S", 320 }, 321 }}) 322 } 323 324 func TestTransactionEventBackground(t *testing.T) { 325 app := testApp(nil, nil, t) 326 txn := app.StartTransaction("hello", nil, nil) 327 err := txn.End() 328 if nil != err { 329 t.Error(err) 330 } 331 app.ExpectTxnEvents(t, []internal.WantEvent{{ 332 Intrinsics: map[string]interface{}{ 333 "name": "OtherTransaction/Go/hello", 334 }, 335 }}) 336 } 337 338 func TestTransactionEventLocallyDisabled(t *testing.T) { 339 cfgFn := func(cfg *Config) { cfg.TransactionEvents.Enabled = false } 340 app := testApp(nil, cfgFn, t) 341 txn := app.StartTransaction("hello", nil, helloRequest) 342 err := txn.End() 343 if nil != err { 344 t.Error(err) 345 } 346 app.ExpectTxnEvents(t, []internal.WantEvent{}) 347 } 348 349 func TestTransactionEventRemotelyDisabled(t *testing.T) { 350 replyfn := func(reply *internal.ConnectReply) { reply.CollectAnalyticsEvents = false } 351 app := testApp(replyfn, nil, t) 352 txn := app.StartTransaction("hello", nil, helloRequest) 353 err := txn.End() 354 if nil != err { 355 t.Error(err) 356 } 357 app.ExpectTxnEvents(t, []internal.WantEvent{}) 358 } 359 360 func myErrorHandler(w http.ResponseWriter, req *http.Request) { 361 w.Write([]byte("my response")) 362 if txn, ok := w.(Transaction); ok { 363 txn.NoticeError(myError{}) 364 } 365 } 366 367 func TestWrapHandleFunc(t *testing.T) { 368 app := testApp(nil, nil, t) 369 mux := http.NewServeMux() 370 mux.HandleFunc(WrapHandleFunc(app, helloPath, myErrorHandler)) 371 w := newCompatibleResponseRecorder() 372 mux.ServeHTTP(w, helloRequest) 373 374 out := w.Body.String() 375 if "my response" != out { 376 t.Error(out) 377 } 378 379 app.ExpectErrors(t, []internal.WantError{{ 380 TxnName: "WebTransaction/Go/hello", 381 Msg: "my msg", 382 Klass: "newrelic.myError", 383 }}) 384 app.ExpectErrorEvents(t, []internal.WantEvent{{ 385 Intrinsics: map[string]interface{}{ 386 "error.class": "newrelic.myError", 387 "error.message": "my msg", 388 "transactionName": "WebTransaction/Go/hello", 389 }, 390 AgentAttributes: mergeAttributes(helloRequestAttributes, map[string]interface{}{ 391 "httpResponseCode": "200", 392 }), 393 }}) 394 app.ExpectMetrics(t, webErrorMetrics) 395 } 396 397 func TestWrapHandle(t *testing.T) { 398 app := testApp(nil, nil, t) 399 mux := http.NewServeMux() 400 mux.Handle(WrapHandle(app, helloPath, http.HandlerFunc(myErrorHandler))) 401 w := newCompatibleResponseRecorder() 402 mux.ServeHTTP(w, helloRequest) 403 404 out := w.Body.String() 405 if "my response" != out { 406 t.Error(out) 407 } 408 409 app.ExpectErrors(t, []internal.WantError{{ 410 TxnName: "WebTransaction/Go/hello", 411 Msg: "my msg", 412 Klass: "newrelic.myError", 413 }}) 414 app.ExpectErrorEvents(t, []internal.WantEvent{{ 415 Intrinsics: map[string]interface{}{ 416 "error.class": "newrelic.myError", 417 "error.message": "my msg", 418 "transactionName": "WebTransaction/Go/hello", 419 }, 420 AgentAttributes: mergeAttributes(helloRequestAttributes, map[string]interface{}{ 421 "httpResponseCode": "200", 422 }), 423 }}) 424 app.ExpectMetrics(t, webErrorMetrics) 425 } 426 427 func TestWrapHandleNilApp(t *testing.T) { 428 var app Application 429 mux := http.NewServeMux() 430 mux.Handle(WrapHandle(app, helloPath, http.HandlerFunc(myErrorHandler))) 431 w := newCompatibleResponseRecorder() 432 mux.ServeHTTP(w, helloRequest) 433 434 out := w.Body.String() 435 if "my response" != out { 436 t.Error(out) 437 } 438 } 439 440 func TestSetName(t *testing.T) { 441 app := testApp(nil, nil, t) 442 txn := app.StartTransaction("one", nil, nil) 443 if err := txn.SetName("hello"); nil != err { 444 t.Error(err) 445 } 446 txn.End() 447 if err := txn.SetName("three"); err != errAlreadyEnded { 448 t.Error(err) 449 } 450 451 app.ExpectMetrics(t, backgroundMetrics) 452 } 453 454 func deferEndPanic(txn Transaction, panicMe interface{}) (r interface{}) { 455 defer func() { 456 r = recover() 457 }() 458 459 defer txn.End() 460 461 panic(panicMe) 462 } 463 464 func TestPanicError(t *testing.T) { 465 app := testApp(nil, nil, t) 466 txn := app.StartTransaction("hello", nil, nil) 467 468 e := myError{} 469 r := deferEndPanic(txn, e) 470 if r != e { 471 t.Error("panic not propagated", r) 472 } 473 474 app.ExpectErrors(t, []internal.WantError{{ 475 TxnName: "OtherTransaction/Go/hello", 476 Msg: "my msg", 477 Klass: internal.PanicErrorKlass, 478 }}) 479 app.ExpectErrorEvents(t, []internal.WantEvent{{ 480 Intrinsics: map[string]interface{}{ 481 "error.class": internal.PanicErrorKlass, 482 "error.message": "my msg", 483 "transactionName": "OtherTransaction/Go/hello", 484 }, 485 }}) 486 app.ExpectMetrics(t, backgroundErrorMetrics) 487 } 488 489 func TestPanicString(t *testing.T) { 490 app := testApp(nil, nil, t) 491 txn := app.StartTransaction("hello", nil, nil) 492 493 e := "my string" 494 r := deferEndPanic(txn, e) 495 if r != e { 496 t.Error("panic not propagated", r) 497 } 498 499 app.ExpectErrors(t, []internal.WantError{{ 500 TxnName: "OtherTransaction/Go/hello", 501 Msg: "my string", 502 Klass: internal.PanicErrorKlass, 503 }}) 504 app.ExpectErrorEvents(t, []internal.WantEvent{{ 505 Intrinsics: map[string]interface{}{ 506 "error.class": internal.PanicErrorKlass, 507 "error.message": "my string", 508 "transactionName": "OtherTransaction/Go/hello", 509 }, 510 }}) 511 app.ExpectMetrics(t, backgroundErrorMetrics) 512 } 513 514 func TestPanicInt(t *testing.T) { 515 app := testApp(nil, nil, t) 516 txn := app.StartTransaction("hello", nil, nil) 517 518 e := 22 519 r := deferEndPanic(txn, e) 520 if r != e { 521 t.Error("panic not propagated", r) 522 } 523 524 app.ExpectErrors(t, []internal.WantError{{ 525 TxnName: "OtherTransaction/Go/hello", 526 Msg: "22", 527 Klass: internal.PanicErrorKlass, 528 }}) 529 app.ExpectErrorEvents(t, []internal.WantEvent{{ 530 Intrinsics: map[string]interface{}{ 531 "error.class": internal.PanicErrorKlass, 532 "error.message": "22", 533 "transactionName": "OtherTransaction/Go/hello", 534 }, 535 }}) 536 app.ExpectMetrics(t, backgroundErrorMetrics) 537 } 538 539 func TestPanicNil(t *testing.T) { 540 app := testApp(nil, nil, t) 541 txn := app.StartTransaction("hello", nil, nil) 542 543 r := deferEndPanic(txn, nil) 544 if nil != r { 545 t.Error(r) 546 } 547 548 app.ExpectErrors(t, []internal.WantError{}) 549 app.ExpectErrorEvents(t, []internal.WantEvent{}) 550 app.ExpectMetrics(t, backgroundMetrics) 551 } 552 553 func TestResponseCodeError(t *testing.T) { 554 app := testApp(nil, nil, t) 555 w := newCompatibleResponseRecorder() 556 txn := app.StartTransaction("hello", w, helloRequest) 557 558 txn.WriteHeader(http.StatusBadRequest) // 400 559 txn.WriteHeader(http.StatusUnauthorized) // 401 560 561 txn.End() 562 563 if http.StatusBadRequest != w.Code { 564 t.Error(w.Code) 565 } 566 567 app.ExpectErrors(t, []internal.WantError{{ 568 TxnName: "WebTransaction/Go/hello", 569 Msg: "Bad Request", 570 Klass: "400", 571 }}) 572 app.ExpectErrorEvents(t, []internal.WantEvent{{ 573 Intrinsics: map[string]interface{}{ 574 "error.class": "400", 575 "error.message": "Bad Request", 576 "transactionName": "WebTransaction/Go/hello", 577 }, 578 AgentAttributes: mergeAttributes(helloRequestAttributes, map[string]interface{}{ 579 "httpResponseCode": "400", 580 }), 581 }}) 582 app.ExpectMetrics(t, webErrorMetrics) 583 } 584 585 func TestResponseCode404Filtered(t *testing.T) { 586 app := testApp(nil, nil, t) 587 w := newCompatibleResponseRecorder() 588 txn := app.StartTransaction("hello", w, helloRequest) 589 590 txn.WriteHeader(http.StatusNotFound) 591 592 txn.End() 593 594 if http.StatusNotFound != w.Code { 595 t.Error(w.Code) 596 } 597 598 app.ExpectErrors(t, []internal.WantError{}) 599 app.ExpectErrorEvents(t, []internal.WantEvent{}) 600 app.ExpectMetrics(t, webMetrics) 601 } 602 603 func TestResponseCodeCustomFilter(t *testing.T) { 604 cfgFn := func(cfg *Config) { 605 cfg.ErrorCollector.IgnoreStatusCodes = 606 append(cfg.ErrorCollector.IgnoreStatusCodes, 405) 607 } 608 app := testApp(nil, cfgFn, t) 609 w := newCompatibleResponseRecorder() 610 txn := app.StartTransaction("hello", w, helloRequest) 611 612 txn.WriteHeader(405) 613 614 txn.End() 615 616 app.ExpectErrors(t, []internal.WantError{}) 617 app.ExpectErrorEvents(t, []internal.WantEvent{}) 618 app.ExpectMetrics(t, webMetrics) 619 } 620 621 func TestResponseCodeServerSideFilterObserved(t *testing.T) { 622 // Test that server-side ignore_status_codes are observed. 623 cfgFn := func(cfg *Config) { 624 cfg.ErrorCollector.IgnoreStatusCodes = nil 625 } 626 replyfn := func(reply *internal.ConnectReply) { 627 json.Unmarshal([]byte(`{"agent_config":{"error_collector.ignore_status_codes":[405]}}`), reply) 628 } 629 app := testApp(replyfn, cfgFn, t) 630 w := newCompatibleResponseRecorder() 631 txn := app.StartTransaction("hello", w, helloRequest) 632 633 txn.WriteHeader(405) 634 635 txn.End() 636 637 app.ExpectErrors(t, []internal.WantError{}) 638 app.ExpectErrorEvents(t, []internal.WantEvent{}) 639 app.ExpectMetrics(t, webMetrics) 640 } 641 642 func TestResponseCodeServerSideOverwriteLocal(t *testing.T) { 643 // Test that server-side ignore_status_codes are used in place of local 644 // Config.ErrorCollector.IgnoreStatusCodes. 645 cfgFn := func(cfg *Config) { 646 } 647 replyfn := func(reply *internal.ConnectReply) { 648 json.Unmarshal([]byte(`{"agent_config":{"error_collector.ignore_status_codes":[402]}}`), reply) 649 } 650 app := testApp(replyfn, cfgFn, t) 651 w := newCompatibleResponseRecorder() 652 txn := app.StartTransaction("hello", w, helloRequest) 653 654 txn.WriteHeader(404) 655 656 txn.End() 657 658 app.ExpectErrors(t, []internal.WantError{{ 659 TxnName: "WebTransaction/Go/hello", 660 Msg: "Not Found", 661 Klass: "404", 662 }}) 663 app.ExpectErrorEvents(t, []internal.WantEvent{{ 664 Intrinsics: map[string]interface{}{ 665 "error.class": "404", 666 "error.message": "Not Found", 667 "transactionName": "WebTransaction/Go/hello", 668 }, 669 AgentAttributes: mergeAttributes(helloRequestAttributes, map[string]interface{}{ 670 "httpResponseCode": "404", 671 }), 672 }}) 673 app.ExpectMetrics(t, webErrorMetrics) 674 } 675 676 func TestResponseCodeAfterEnd(t *testing.T) { 677 app := testApp(nil, nil, t) 678 w := newCompatibleResponseRecorder() 679 txn := app.StartTransaction("hello", w, helloRequest) 680 681 txn.End() 682 txn.WriteHeader(http.StatusBadRequest) 683 684 if http.StatusBadRequest != w.Code { 685 t.Error(w.Code) 686 } 687 688 app.ExpectErrors(t, []internal.WantError{}) 689 app.ExpectErrorEvents(t, []internal.WantEvent{}) 690 app.ExpectMetrics(t, webMetrics) 691 } 692 693 func TestResponseCodeAfterWrite(t *testing.T) { 694 app := testApp(nil, nil, t) 695 w := newCompatibleResponseRecorder() 696 txn := app.StartTransaction("hello", w, helloRequest) 697 698 txn.Write([]byte("zap")) 699 txn.WriteHeader(http.StatusBadRequest) 700 701 txn.End() 702 703 if out := w.Body.String(); "zap" != out { 704 t.Error(out) 705 } 706 707 if http.StatusOK != w.Code { 708 t.Error(w.Code) 709 } 710 711 app.ExpectErrors(t, []internal.WantError{}) 712 app.ExpectErrorEvents(t, []internal.WantEvent{}) 713 app.ExpectMetrics(t, webMetrics) 714 } 715 716 func TestQueueTime(t *testing.T) { 717 app := testApp(nil, nil, t) 718 req, err := http.NewRequest("GET", helloPath+helloQueryParams, nil) 719 req.Header.Add("X-Queue-Start", "1465793282.12345") 720 if nil != err { 721 t.Fatal(err) 722 } 723 txn := app.StartTransaction("hello", nil, req) 724 txn.NoticeError(myError{}) 725 txn.End() 726 727 app.ExpectErrors(t, []internal.WantError{{ 728 TxnName: "WebTransaction/Go/hello", 729 Msg: "my msg", 730 Klass: "newrelic.myError", 731 }}) 732 app.ExpectErrorEvents(t, []internal.WantEvent{{ 733 Intrinsics: map[string]interface{}{ 734 "error.class": "newrelic.myError", 735 "error.message": "my msg", 736 "transactionName": "WebTransaction/Go/hello", 737 "queueDuration": internal.MatchAnything, 738 }, 739 AgentAttributes: map[string]interface{}{ 740 "request.uri": "/hello", 741 "request.method": "GET", 742 }, 743 }}) 744 app.ExpectMetrics(t, append([]internal.WantMetric{ 745 {Name: "WebFrontend/QueueTime", Scope: "", Forced: true, Data: nil}, 746 }, webErrorMetrics...)) 747 app.ExpectTxnEvents(t, []internal.WantEvent{{ 748 Intrinsics: map[string]interface{}{ 749 "name": "WebTransaction/Go/hello", 750 "nr.apdexPerfZone": "F", 751 "queueDuration": internal.MatchAnything, 752 }, 753 AgentAttributes: nil, 754 }}) 755 } 756 757 func TestIgnore(t *testing.T) { 758 app := testApp(nil, nil, t) 759 txn := app.StartTransaction("hello", nil, nil) 760 txn.NoticeError(myError{}) 761 err := txn.Ignore() 762 if nil != err { 763 t.Error(err) 764 } 765 txn.End() 766 app.ExpectErrors(t, []internal.WantError{}) 767 app.ExpectErrorEvents(t, []internal.WantEvent{}) 768 app.ExpectMetrics(t, []internal.WantMetric{}) 769 app.ExpectTxnEvents(t, []internal.WantEvent{}) 770 } 771 772 func TestIgnoreAlreadyEnded(t *testing.T) { 773 app := testApp(nil, nil, t) 774 txn := app.StartTransaction("hello", nil, nil) 775 txn.NoticeError(myError{}) 776 txn.End() 777 err := txn.Ignore() 778 if err != errAlreadyEnded { 779 t.Error(err) 780 } 781 app.ExpectErrors(t, []internal.WantError{{ 782 TxnName: "OtherTransaction/Go/hello", 783 Msg: "my msg", 784 Klass: "newrelic.myError", 785 }}) 786 app.ExpectErrorEvents(t, []internal.WantEvent{{ 787 Intrinsics: map[string]interface{}{ 788 "error.class": "newrelic.myError", 789 "error.message": "my msg", 790 "transactionName": "OtherTransaction/Go/hello", 791 }, 792 }}) 793 app.ExpectMetrics(t, backgroundErrorMetrics) 794 app.ExpectTxnEvents(t, []internal.WantEvent{{ 795 Intrinsics: map[string]interface{}{ 796 "name": "OtherTransaction/Go/hello", 797 }, 798 }}) 799 } 800 801 func TestExternalSegmentMethod(t *testing.T) { 802 req, err := http.NewRequest("POST", "http://request.com/", nil) 803 if err != nil { 804 t.Fatal(err) 805 } 806 responsereq, err := http.NewRequest("POST", "http://response.com/", nil) 807 if err != nil { 808 t.Fatal(err) 809 } 810 response := &http.Response{Request: responsereq} 811 812 // empty segment 813 m := externalSegmentMethod(&ExternalSegment{}) 814 if "" != m { 815 t.Error(m) 816 } 817 818 // empty request 819 m = externalSegmentMethod(&ExternalSegment{ 820 Request: nil, 821 }) 822 if "" != m { 823 t.Error(m) 824 } 825 826 // segment containing request and response 827 m = externalSegmentMethod(&ExternalSegment{ 828 Request: req, 829 Response: response, 830 }) 831 if "POST" != m { 832 t.Error(m) 833 } 834 835 // Procedure field overrides request and response. 836 m = externalSegmentMethod(&ExternalSegment{ 837 Procedure: "GET", 838 Request: req, 839 Response: response, 840 }) 841 if "GET" != m { 842 t.Error(m) 843 } 844 845 req, err = http.NewRequest("", "http://request.com/", nil) 846 if err != nil { 847 t.Fatal(err) 848 } 849 responsereq, err = http.NewRequest("", "http://response.com/", nil) 850 if err != nil { 851 t.Fatal(err) 852 } 853 response = &http.Response{Request: responsereq} 854 855 // empty string method means a client GET request 856 m = externalSegmentMethod(&ExternalSegment{ 857 Request: req, 858 Response: response, 859 }) 860 if "GET" != m { 861 t.Error(m) 862 } 863 864 } 865 866 func TestExternalSegmentURL(t *testing.T) { 867 rawURL := "http://url.com" 868 req, err := http.NewRequest("GET", "http://request.com/", nil) 869 if err != nil { 870 t.Fatal(err) 871 } 872 responsereq, err := http.NewRequest("GET", "http://response.com/", nil) 873 if err != nil { 874 t.Fatal(err) 875 } 876 response := &http.Response{Request: responsereq} 877 878 // empty segment 879 u, err := externalSegmentURL(&ExternalSegment{}) 880 host := internal.HostFromURL(u) 881 if nil != err || nil != u || "" != host { 882 t.Error(u, err, internal.HostFromURL(u)) 883 } 884 // segment only containing url 885 u, err = externalSegmentURL(&ExternalSegment{URL: rawURL}) 886 host = internal.HostFromURL(u) 887 if nil != err || host != "url.com" { 888 t.Error(u, err, internal.HostFromURL(u)) 889 } 890 // segment only containing request 891 u, err = externalSegmentURL(&ExternalSegment{Request: req}) 892 host = internal.HostFromURL(u) 893 if nil != err || "request.com" != host { 894 t.Error(host) 895 } 896 // segment only containing response 897 u, err = externalSegmentURL(&ExternalSegment{Response: response}) 898 host = internal.HostFromURL(u) 899 if nil != err || "response.com" != host { 900 t.Error(host) 901 } 902 // segment containing request and response 903 u, err = externalSegmentURL(&ExternalSegment{ 904 Request: req, 905 Response: response, 906 }) 907 host = internal.HostFromURL(u) 908 if nil != err || "response.com" != host { 909 t.Error(host) 910 } 911 // segment containing url, request, and response 912 u, err = externalSegmentURL(&ExternalSegment{ 913 URL: rawURL, 914 Request: req, 915 Response: response, 916 }) 917 host = internal.HostFromURL(u) 918 if nil != err || "url.com" != host { 919 t.Error(err, host) 920 } 921 } 922 923 func TestZeroSegmentsSafe(t *testing.T) { 924 s := Segment{} 925 s.End() 926 927 StartSegmentNow(nil) 928 929 ds := DatastoreSegment{} 930 ds.End() 931 932 es := ExternalSegment{} 933 es.End() 934 935 StartSegment(nil, "").End() 936 937 StartExternalSegment(nil, nil).End() 938 } 939 940 func TestTraceSegmentDefer(t *testing.T) { 941 app := testApp(nil, nil, t) 942 txn := app.StartTransaction("hello", nil, helloRequest) 943 func() { 944 defer StartSegment(txn, "segment").End() 945 }() 946 txn.End() 947 scope := "WebTransaction/Go/hello" 948 app.ExpectMetrics(t, append([]internal.WantMetric{ 949 {Name: "Custom/segment", Scope: "", Forced: false, Data: nil}, 950 {Name: "Custom/segment", Scope: scope, Forced: false, Data: nil}, 951 }, webMetrics...)) 952 } 953 954 func TestTraceSegmentNilErr(t *testing.T) { 955 app := testApp(nil, nil, t) 956 txn := app.StartTransaction("hello", nil, helloRequest) 957 err := StartSegment(txn, "segment").End() 958 if nil != err { 959 t.Error(err) 960 } 961 txn.End() 962 scope := "WebTransaction/Go/hello" 963 app.ExpectMetrics(t, append([]internal.WantMetric{ 964 {Name: "Custom/segment", Scope: "", Forced: false, Data: nil}, 965 {Name: "Custom/segment", Scope: scope, Forced: false, Data: nil}, 966 }, webMetrics...)) 967 } 968 969 func TestTraceSegmentOutOfOrder(t *testing.T) { 970 app := testApp(nil, nil, t) 971 txn := app.StartTransaction("hello", nil, helloRequest) 972 s1 := StartSegment(txn, "s1") 973 s2 := StartSegment(txn, "s1") 974 err1 := s1.End() 975 err2 := s2.End() 976 if nil != err1 { 977 t.Error(err1) 978 } 979 if nil == err2 { 980 t.Error(err2) 981 } 982 txn.End() 983 scope := "WebTransaction/Go/hello" 984 app.ExpectMetrics(t, append([]internal.WantMetric{ 985 {Name: "Custom/s1", Scope: "", Forced: false, Data: nil}, 986 {Name: "Custom/s1", Scope: scope, Forced: false, Data: nil}, 987 }, webMetrics...)) 988 } 989 990 func TestTraceSegmentEndedBeforeStartSegment(t *testing.T) { 991 app := testApp(nil, nil, t) 992 txn := app.StartTransaction("hello", nil, helloRequest) 993 txn.End() 994 s := StartSegment(txn, "segment") 995 err := s.End() 996 if err != errAlreadyEnded { 997 t.Error(err) 998 } 999 app.ExpectMetrics(t, webMetrics) 1000 } 1001 1002 func TestTraceSegmentEndedBeforeEndSegment(t *testing.T) { 1003 app := testApp(nil, nil, t) 1004 txn := app.StartTransaction("hello", nil, helloRequest) 1005 s := StartSegment(txn, "segment") 1006 txn.End() 1007 err := s.End() 1008 if err != errAlreadyEnded { 1009 t.Error(err) 1010 } 1011 1012 app.ExpectMetrics(t, webMetrics) 1013 } 1014 1015 func TestTraceSegmentPanic(t *testing.T) { 1016 app := testApp(nil, nil, t) 1017 txn := app.StartTransaction("hello", nil, helloRequest) 1018 func() { 1019 defer func() { 1020 recover() 1021 }() 1022 1023 func() { 1024 defer StartSegment(txn, "f1").End() 1025 1026 func() { 1027 t := StartSegment(txn, "f2") 1028 1029 func() { 1030 defer StartSegment(txn, "f3").End() 1031 1032 func() { 1033 StartSegment(txn, "f4") 1034 1035 panic(nil) 1036 }() 1037 }() 1038 1039 t.End() 1040 }() 1041 }() 1042 }() 1043 1044 txn.End() 1045 scope := "WebTransaction/Go/hello" 1046 app.ExpectMetrics(t, append([]internal.WantMetric{ 1047 {Name: "Custom/f1", Scope: "", Forced: false, Data: nil}, 1048 {Name: "Custom/f1", Scope: scope, Forced: false, Data: nil}, 1049 {Name: "Custom/f3", Scope: "", Forced: false, Data: nil}, 1050 {Name: "Custom/f3", Scope: scope, Forced: false, Data: nil}, 1051 }, webMetrics...)) 1052 } 1053 1054 func TestTraceSegmentNilTxn(t *testing.T) { 1055 app := testApp(nil, nil, t) 1056 txn := app.StartTransaction("hello", nil, helloRequest) 1057 s := Segment{Name: "hello"} 1058 err := s.End() 1059 if err != nil { 1060 t.Error(err) 1061 } 1062 txn.End() 1063 app.ExpectMetrics(t, webMetrics) 1064 } 1065 1066 func TestTraceDatastore(t *testing.T) { 1067 app := testApp(nil, nil, t) 1068 txn := app.StartTransaction("hello", nil, helloRequest) 1069 s := DatastoreSegment{} 1070 s.StartTime = txn.StartSegmentNow() 1071 s.Product = DatastoreMySQL 1072 s.Collection = "my_table" 1073 s.Operation = "SELECT" 1074 err := s.End() 1075 if nil != err { 1076 t.Error(err) 1077 } 1078 txn.NoticeError(myError{}) 1079 txn.End() 1080 scope := "WebTransaction/Go/hello" 1081 app.ExpectMetrics(t, append([]internal.WantMetric{ 1082 {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, 1083 {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, 1084 {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, 1085 {Name: "Datastore/MySQL/allWeb", Scope: "", Forced: true, Data: nil}, 1086 {Name: "Datastore/operation/MySQL/SELECT", Scope: "", Forced: false, Data: nil}, 1087 {Name: "Datastore/statement/MySQL/my_table/SELECT", Scope: "", Forced: false, Data: nil}, 1088 {Name: "Datastore/statement/MySQL/my_table/SELECT", Scope: scope, Forced: false, Data: nil}, 1089 }, webErrorMetrics...)) 1090 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1091 Intrinsics: map[string]interface{}{ 1092 "error.class": "newrelic.myError", 1093 "error.message": "my msg", 1094 "transactionName": "WebTransaction/Go/hello", 1095 "databaseCallCount": 1, 1096 "databaseDuration": internal.MatchAnything, 1097 }, 1098 }}) 1099 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1100 Intrinsics: map[string]interface{}{ 1101 "name": "WebTransaction/Go/hello", 1102 "nr.apdexPerfZone": "F", 1103 "databaseCallCount": 1, 1104 "databaseDuration": internal.MatchAnything, 1105 }, 1106 }}) 1107 } 1108 1109 func TestTraceDatastoreBackground(t *testing.T) { 1110 app := testApp(nil, nil, t) 1111 txn := app.StartTransaction("hello", nil, nil) 1112 s := DatastoreSegment{ 1113 StartTime: txn.StartSegmentNow(), 1114 Product: DatastoreMySQL, 1115 Collection: "my_table", 1116 Operation: "SELECT", 1117 } 1118 err := s.End() 1119 if nil != err { 1120 t.Error(err) 1121 } 1122 txn.NoticeError(myError{}) 1123 txn.End() 1124 scope := "OtherTransaction/Go/hello" 1125 app.ExpectMetrics(t, append([]internal.WantMetric{ 1126 {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, 1127 {Name: "Datastore/allOther", Scope: "", Forced: true, Data: nil}, 1128 {Name: "Datastore/MySQL/all", Scope: "", Forced: true, Data: nil}, 1129 {Name: "Datastore/MySQL/allOther", Scope: "", Forced: true, Data: nil}, 1130 {Name: "Datastore/operation/MySQL/SELECT", Scope: "", Forced: false, Data: nil}, 1131 {Name: "Datastore/statement/MySQL/my_table/SELECT", Scope: "", Forced: false, Data: nil}, 1132 {Name: "Datastore/statement/MySQL/my_table/SELECT", Scope: scope, Forced: false, Data: nil}, 1133 }, backgroundErrorMetrics...)) 1134 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1135 Intrinsics: map[string]interface{}{ 1136 "error.class": "newrelic.myError", 1137 "error.message": "my msg", 1138 "transactionName": "OtherTransaction/Go/hello", 1139 "databaseCallCount": 1, 1140 "databaseDuration": internal.MatchAnything, 1141 }, 1142 }}) 1143 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1144 Intrinsics: map[string]interface{}{ 1145 "name": "OtherTransaction/Go/hello", 1146 "databaseCallCount": 1, 1147 "databaseDuration": internal.MatchAnything, 1148 }, 1149 }}) 1150 } 1151 1152 func TestTraceDatastoreMissingProductOperationCollection(t *testing.T) { 1153 app := testApp(nil, nil, t) 1154 txn := app.StartTransaction("hello", nil, helloRequest) 1155 s := DatastoreSegment{ 1156 StartTime: txn.StartSegmentNow(), 1157 } 1158 err := s.End() 1159 if nil != err { 1160 t.Error(err) 1161 } 1162 txn.NoticeError(myError{}) 1163 txn.End() 1164 scope := "WebTransaction/Go/hello" 1165 app.ExpectMetrics(t, append([]internal.WantMetric{ 1166 {Name: "Datastore/all", Scope: "", Forced: true, Data: nil}, 1167 {Name: "Datastore/allWeb", Scope: "", Forced: true, Data: nil}, 1168 {Name: "Datastore/Unknown/all", Scope: "", Forced: true, Data: nil}, 1169 {Name: "Datastore/Unknown/allWeb", Scope: "", Forced: true, Data: nil}, 1170 {Name: "Datastore/operation/Unknown/other", Scope: "", Forced: false, Data: nil}, 1171 {Name: "Datastore/operation/Unknown/other", Scope: scope, Forced: false, Data: nil}, 1172 }, webErrorMetrics...)) 1173 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1174 Intrinsics: map[string]interface{}{ 1175 "error.class": "newrelic.myError", 1176 "error.message": "my msg", 1177 "transactionName": "WebTransaction/Go/hello", 1178 "databaseCallCount": 1, 1179 "databaseDuration": internal.MatchAnything, 1180 }, 1181 }}) 1182 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1183 Intrinsics: map[string]interface{}{ 1184 "name": "WebTransaction/Go/hello", 1185 "nr.apdexPerfZone": "F", 1186 "databaseCallCount": 1, 1187 "databaseDuration": internal.MatchAnything, 1188 }, 1189 }}) 1190 } 1191 1192 func TestTraceDatastoreNilTxn(t *testing.T) { 1193 app := testApp(nil, nil, t) 1194 txn := app.StartTransaction("hello", nil, helloRequest) 1195 var s DatastoreSegment 1196 s.Product = DatastoreMySQL 1197 s.Collection = "my_table" 1198 s.Operation = "SELECT" 1199 err := s.End() 1200 if nil != err { 1201 t.Error(err) 1202 } 1203 txn.NoticeError(myError{}) 1204 txn.End() 1205 app.ExpectMetrics(t, webErrorMetrics) 1206 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1207 Intrinsics: map[string]interface{}{ 1208 "error.class": "newrelic.myError", 1209 "error.message": "my msg", 1210 "transactionName": "WebTransaction/Go/hello", 1211 }, 1212 }}) 1213 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1214 Intrinsics: map[string]interface{}{ 1215 "name": "WebTransaction/Go/hello", 1216 "nr.apdexPerfZone": "F", 1217 }, 1218 }}) 1219 } 1220 1221 func TestTraceDatastoreTxnEnded(t *testing.T) { 1222 app := testApp(nil, nil, t) 1223 txn := app.StartTransaction("hello", nil, helloRequest) 1224 txn.NoticeError(myError{}) 1225 s := DatastoreSegment{ 1226 StartTime: txn.StartSegmentNow(), 1227 Product: DatastoreMySQL, 1228 Collection: "my_table", 1229 Operation: "SELECT", 1230 } 1231 txn.End() 1232 err := s.End() 1233 if errAlreadyEnded != err { 1234 t.Error(err) 1235 } 1236 app.ExpectMetrics(t, webErrorMetrics) 1237 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1238 Intrinsics: map[string]interface{}{ 1239 "error.class": "newrelic.myError", 1240 "error.message": "my msg", 1241 "transactionName": "WebTransaction/Go/hello", 1242 }, 1243 }}) 1244 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1245 Intrinsics: map[string]interface{}{ 1246 "name": "WebTransaction/Go/hello", 1247 "nr.apdexPerfZone": "F", 1248 }, 1249 }}) 1250 } 1251 1252 func TestTraceExternal(t *testing.T) { 1253 app := testApp(nil, nil, t) 1254 txn := app.StartTransaction("hello", nil, helloRequest) 1255 s := ExternalSegment{ 1256 StartTime: txn.StartSegmentNow(), 1257 URL: "http://example.com/", 1258 } 1259 err := s.End() 1260 if nil != err { 1261 t.Error(err) 1262 } 1263 txn.NoticeError(myError{}) 1264 txn.End() 1265 scope := "WebTransaction/Go/hello" 1266 app.ExpectMetrics(t, append([]internal.WantMetric{ 1267 {Name: "External/all", Scope: "", Forced: true, Data: nil}, 1268 {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, 1269 {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, 1270 {Name: "External/example.com/http", Scope: scope, Forced: false, Data: nil}, 1271 }, webErrorMetrics...)) 1272 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1273 Intrinsics: map[string]interface{}{ 1274 "error.class": "newrelic.myError", 1275 "error.message": "my msg", 1276 "transactionName": "WebTransaction/Go/hello", 1277 "externalCallCount": 1, 1278 "externalDuration": internal.MatchAnything, 1279 }, 1280 }}) 1281 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1282 Intrinsics: map[string]interface{}{ 1283 "name": "WebTransaction/Go/hello", 1284 "nr.apdexPerfZone": "F", 1285 "externalCallCount": 1, 1286 "externalDuration": internal.MatchAnything, 1287 }, 1288 }}) 1289 } 1290 1291 func TestExternalSegmentCustomFieldsWithURL(t *testing.T) { 1292 replyfn := func(reply *internal.ConnectReply) { 1293 reply.AdaptiveSampler = internal.SampleEverything{} 1294 } 1295 cfgfn := func(cfg *Config) { 1296 cfg.DistributedTracer.Enabled = true 1297 cfg.CrossApplicationTracer.Enabled = false 1298 } 1299 app := testApp(replyfn, cfgfn, t) 1300 txn := app.StartTransaction("hello", nil, helloRequest) 1301 s := ExternalSegment{ 1302 StartTime: txn.StartSegmentNow(), 1303 URL: "https://otherhost.com/path/zip/zap?secret=ssshhh", 1304 Host: "bufnet", 1305 Procedure: "TestApplication/DoUnaryUnary", 1306 Library: "grpc", 1307 } 1308 err := s.End() 1309 if nil != err { 1310 t.Error(err) 1311 } 1312 txn.End() 1313 scope := "WebTransaction/Go/hello" 1314 app.ExpectMetrics(t, append([]internal.WantMetric{ 1315 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, 1316 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, 1317 {Name: "External/all", Scope: "", Forced: true, Data: nil}, 1318 {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, 1319 {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, 1320 {Name: "External/bufnet/grpc/TestApplication/DoUnaryUnary", Scope: scope, Forced: false, Data: nil}, 1321 }, webMetrics...)) 1322 app.ExpectSpanEvents(t, []internal.WantEvent{ 1323 { 1324 Intrinsics: map[string]interface{}{ 1325 "name": "WebTransaction/Go/hello", 1326 "sampled": true, 1327 "category": "generic", 1328 "nr.entryPoint": true, 1329 }, 1330 UserAttributes: map[string]interface{}{}, 1331 AgentAttributes: map[string]interface{}{}, 1332 }, 1333 { 1334 Intrinsics: map[string]interface{}{ 1335 "parentId": internal.MatchAnything, 1336 "name": "External/bufnet/grpc/TestApplication/DoUnaryUnary", 1337 "category": "http", 1338 "component": "grpc", 1339 "span.kind": "client", 1340 }, 1341 UserAttributes: map[string]interface{}{}, 1342 AgentAttributes: map[string]interface{}{ 1343 // "http.url" and "http.method" are not saved if 1344 // library is not "http". 1345 }, 1346 }, 1347 }) 1348 } 1349 1350 func TestExternalSegmentCustomFieldsWithRequest(t *testing.T) { 1351 replyfn := func(reply *internal.ConnectReply) { 1352 reply.AdaptiveSampler = internal.SampleEverything{} 1353 } 1354 cfgfn := func(cfg *Config) { 1355 cfg.DistributedTracer.Enabled = true 1356 cfg.CrossApplicationTracer.Enabled = false 1357 } 1358 app := testApp(replyfn, cfgfn, t) 1359 txn := app.StartTransaction("hello", nil, helloRequest) 1360 req, _ := http.NewRequest("GET", "https://www.something.com/path/zip/zap?secret=ssshhh", nil) 1361 s := StartExternalSegment(txn, req) 1362 s.Host = "bufnet" 1363 s.Procedure = "TestApplication/DoUnaryUnary" 1364 s.Library = "grpc" 1365 err := s.End() 1366 if nil != err { 1367 t.Error(err) 1368 } 1369 txn.End() 1370 scope := "WebTransaction/Go/hello" 1371 app.ExpectMetrics(t, append([]internal.WantMetric{ 1372 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, 1373 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, 1374 {Name: "External/all", Scope: "", Forced: true, Data: nil}, 1375 {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, 1376 {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, 1377 {Name: "External/bufnet/grpc/TestApplication/DoUnaryUnary", Scope: scope, Forced: false, Data: nil}, 1378 }, webMetrics...)) 1379 app.ExpectSpanEvents(t, []internal.WantEvent{ 1380 { 1381 Intrinsics: map[string]interface{}{ 1382 "name": "WebTransaction/Go/hello", 1383 "sampled": true, 1384 "category": "generic", 1385 "nr.entryPoint": true, 1386 }, 1387 UserAttributes: map[string]interface{}{}, 1388 AgentAttributes: map[string]interface{}{}, 1389 }, 1390 { 1391 Intrinsics: map[string]interface{}{ 1392 "parentId": internal.MatchAnything, 1393 "name": "External/bufnet/grpc/TestApplication/DoUnaryUnary", 1394 "category": "http", 1395 "component": "grpc", 1396 "span.kind": "client", 1397 }, 1398 UserAttributes: map[string]interface{}{}, 1399 AgentAttributes: map[string]interface{}{ 1400 // "http.url" and "http.method" are not saved if 1401 // library is not "http". 1402 }, 1403 }, 1404 }) 1405 } 1406 1407 func TestExternalSegmentCustomFieldsWithResponse(t *testing.T) { 1408 replyfn := func(reply *internal.ConnectReply) { 1409 reply.AdaptiveSampler = internal.SampleEverything{} 1410 } 1411 cfgfn := func(cfg *Config) { 1412 cfg.DistributedTracer.Enabled = true 1413 cfg.CrossApplicationTracer.Enabled = false 1414 } 1415 app := testApp(replyfn, cfgfn, t) 1416 txn := app.StartTransaction("hello", nil, helloRequest) 1417 req, _ := http.NewRequest("GET", "https://www.something.com/path/zip/zap?secret=ssshhh", nil) 1418 resp := &http.Response{Request: req} 1419 s := ExternalSegment{ 1420 StartTime: txn.StartSegmentNow(), 1421 Response: resp, 1422 Host: "bufnet", 1423 Procedure: "TestApplication/DoUnaryUnary", 1424 Library: "grpc", 1425 } 1426 err := s.End() 1427 if nil != err { 1428 t.Error(err) 1429 } 1430 txn.End() 1431 scope := "WebTransaction/Go/hello" 1432 app.ExpectMetrics(t, append([]internal.WantMetric{ 1433 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, 1434 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allWeb", Scope: "", Forced: false, Data: nil}, 1435 {Name: "External/all", Scope: "", Forced: true, Data: nil}, 1436 {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, 1437 {Name: "External/bufnet/all", Scope: "", Forced: false, Data: nil}, 1438 {Name: "External/bufnet/grpc/TestApplication/DoUnaryUnary", Scope: scope, Forced: false, Data: nil}, 1439 }, webMetrics...)) 1440 app.ExpectSpanEvents(t, []internal.WantEvent{ 1441 { 1442 Intrinsics: map[string]interface{}{ 1443 "name": "WebTransaction/Go/hello", 1444 "sampled": true, 1445 "category": "generic", 1446 "nr.entryPoint": true, 1447 }, 1448 UserAttributes: map[string]interface{}{}, 1449 AgentAttributes: map[string]interface{}{}, 1450 }, 1451 { 1452 Intrinsics: map[string]interface{}{ 1453 "parentId": internal.MatchAnything, 1454 "name": "External/bufnet/grpc/TestApplication/DoUnaryUnary", 1455 "category": "http", 1456 "component": "grpc", 1457 "span.kind": "client", 1458 }, 1459 UserAttributes: map[string]interface{}{}, 1460 AgentAttributes: map[string]interface{}{ 1461 // "http.url" and "http.method" are not saved if 1462 // library is not "http". 1463 }, 1464 }, 1465 }) 1466 } 1467 1468 func TestTraceExternalBadURL(t *testing.T) { 1469 app := testApp(nil, nil, t) 1470 txn := app.StartTransaction("hello", nil, helloRequest) 1471 s := ExternalSegment{ 1472 StartTime: txn.StartSegmentNow(), 1473 URL: ":example.com/", 1474 } 1475 err := s.End() 1476 if nil == err { 1477 t.Error(err) 1478 } 1479 txn.NoticeError(myError{}) 1480 txn.End() 1481 app.ExpectMetrics(t, webErrorMetrics) 1482 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1483 Intrinsics: map[string]interface{}{ 1484 "error.class": "newrelic.myError", 1485 "error.message": "my msg", 1486 "transactionName": "WebTransaction/Go/hello", 1487 }, 1488 }}) 1489 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1490 Intrinsics: map[string]interface{}{ 1491 "name": "WebTransaction/Go/hello", 1492 "nr.apdexPerfZone": "F", 1493 }, 1494 }}) 1495 } 1496 1497 func TestTraceExternalBackground(t *testing.T) { 1498 app := testApp(nil, nil, t) 1499 txn := app.StartTransaction("hello", nil, nil) 1500 s := ExternalSegment{ 1501 StartTime: txn.StartSegmentNow(), 1502 URL: "http://example.com/", 1503 } 1504 err := s.End() 1505 if nil != err { 1506 t.Error(err) 1507 } 1508 txn.NoticeError(myError{}) 1509 txn.End() 1510 scope := "OtherTransaction/Go/hello" 1511 app.ExpectMetrics(t, append([]internal.WantMetric{ 1512 {Name: "External/all", Scope: "", Forced: true, Data: nil}, 1513 {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, 1514 {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, 1515 {Name: "External/example.com/http", Scope: scope, Forced: false, Data: nil}, 1516 }, backgroundErrorMetrics...)) 1517 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1518 Intrinsics: map[string]interface{}{ 1519 "error.class": "newrelic.myError", 1520 "error.message": "my msg", 1521 "transactionName": "OtherTransaction/Go/hello", 1522 "externalCallCount": 1, 1523 "externalDuration": internal.MatchAnything, 1524 }, 1525 }}) 1526 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1527 Intrinsics: map[string]interface{}{ 1528 "name": "OtherTransaction/Go/hello", 1529 "externalCallCount": 1, 1530 "externalDuration": internal.MatchAnything, 1531 }, 1532 }}) 1533 } 1534 1535 func TestTraceExternalMissingURL(t *testing.T) { 1536 app := testApp(nil, nil, t) 1537 txn := app.StartTransaction("hello", nil, helloRequest) 1538 s := ExternalSegment{ 1539 StartTime: txn.StartSegmentNow(), 1540 } 1541 err := s.End() 1542 if nil != err { 1543 t.Error(err) 1544 } 1545 txn.NoticeError(myError{}) 1546 txn.End() 1547 scope := "WebTransaction/Go/hello" 1548 app.ExpectMetrics(t, append([]internal.WantMetric{ 1549 {Name: "External/all", Scope: "", Forced: true, Data: nil}, 1550 {Name: "External/allWeb", Scope: "", Forced: true, Data: nil}, 1551 {Name: "External/unknown/all", Scope: "", Forced: false, Data: nil}, 1552 {Name: "External/unknown/http", Scope: scope, Forced: false, Data: nil}, 1553 }, webErrorMetrics...)) 1554 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1555 Intrinsics: map[string]interface{}{ 1556 "error.class": "newrelic.myError", 1557 "error.message": "my msg", 1558 "transactionName": "WebTransaction/Go/hello", 1559 "externalCallCount": 1, 1560 "externalDuration": internal.MatchAnything, 1561 }, 1562 }}) 1563 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1564 Intrinsics: map[string]interface{}{ 1565 "name": "WebTransaction/Go/hello", 1566 "nr.apdexPerfZone": "F", 1567 "externalCallCount": 1, 1568 "externalDuration": internal.MatchAnything, 1569 }, 1570 }}) 1571 } 1572 1573 func TestTraceExternalNilTxn(t *testing.T) { 1574 app := testApp(nil, nil, t) 1575 txn := app.StartTransaction("hello", nil, helloRequest) 1576 txn.NoticeError(myError{}) 1577 var s ExternalSegment 1578 err := s.End() 1579 if nil != err { 1580 t.Error(err) 1581 } 1582 txn.End() 1583 app.ExpectMetrics(t, webErrorMetrics) 1584 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1585 Intrinsics: map[string]interface{}{ 1586 "error.class": "newrelic.myError", 1587 "error.message": "my msg", 1588 "transactionName": "WebTransaction/Go/hello", 1589 }, 1590 }}) 1591 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1592 Intrinsics: map[string]interface{}{ 1593 "name": "WebTransaction/Go/hello", 1594 "nr.apdexPerfZone": "F", 1595 }, 1596 }}) 1597 } 1598 1599 func TestTraceExternalTxnEnded(t *testing.T) { 1600 app := testApp(nil, nil, t) 1601 txn := app.StartTransaction("hello", nil, helloRequest) 1602 txn.NoticeError(myError{}) 1603 s := ExternalSegment{ 1604 StartTime: txn.StartSegmentNow(), 1605 URL: "http://example.com/", 1606 } 1607 txn.End() 1608 err := s.End() 1609 if err != errAlreadyEnded { 1610 t.Error(err) 1611 } 1612 app.ExpectMetrics(t, webErrorMetrics) 1613 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1614 Intrinsics: map[string]interface{}{ 1615 "error.class": "newrelic.myError", 1616 "error.message": "my msg", 1617 "transactionName": "WebTransaction/Go/hello", 1618 }, 1619 }}) 1620 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1621 Intrinsics: map[string]interface{}{ 1622 "name": "WebTransaction/Go/hello", 1623 "nr.apdexPerfZone": "F", 1624 }, 1625 }}) 1626 } 1627 1628 func TestRoundTripper(t *testing.T) { 1629 app := testApp(distributedTracingReplyFields, enableBetterCAT, t) 1630 txn := app.StartTransaction("hello", nil, nil) 1631 url := "http://example.com/" 1632 req, err := http.NewRequest("GET", url, nil) 1633 if err != nil { 1634 t.Fatal(err) 1635 } 1636 req.Header.Add("zip", "zap") 1637 client := &http.Client{} 1638 inner := roundTripperFunc(func(r *http.Request) (*http.Response, error) { 1639 catHdr := r.Header.Get(DistributedTracePayloadHeader) 1640 if "" == catHdr { 1641 t.Error("cat header missing") 1642 } 1643 // Test that headers are preserved during reqest cloning: 1644 if z := r.Header.Get("zip"); z != "zap" { 1645 t.Error("missing header", z) 1646 } 1647 if r.URL.String() != url { 1648 t.Error(r.URL.String()) 1649 } 1650 return nil, errors.New("hello") 1651 }) 1652 client.Transport = NewRoundTripper(txn, inner) 1653 resp, err := client.Do(req) 1654 if resp != nil || err == nil { 1655 t.Error(resp, err.Error()) 1656 } 1657 // Ensure that the request was cloned: 1658 catHdr := req.Header.Get(DistributedTracePayloadHeader) 1659 if "" != catHdr { 1660 t.Error("cat header unexpectedly present") 1661 } 1662 txn.NoticeError(myError{}) 1663 txn.End() 1664 scope := "OtherTransaction/Go/hello" 1665 app.ExpectMetrics(t, append([]internal.WantMetric{ 1666 {Name: "External/all", Scope: "", Forced: true, Data: nil}, 1667 {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, 1668 {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, 1669 {Name: "External/example.com/http/GET", Scope: scope, Forced: false, Data: nil}, 1670 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Data: nil}, 1671 {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Data: nil}, 1672 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Data: nil}, 1673 {Name: "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Data: nil}, 1674 {Name: "Supportability/DistributedTrace/CreatePayload/Success", Scope: "", Data: nil}, 1675 }, backgroundErrorMetrics...)) 1676 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1677 Intrinsics: map[string]interface{}{ 1678 "error.class": "newrelic.myError", 1679 "error.message": "my msg", 1680 "transactionName": "OtherTransaction/Go/hello", 1681 "externalCallCount": 1, 1682 "externalDuration": internal.MatchAnything, 1683 "guid": internal.MatchAnything, 1684 "traceId": internal.MatchAnything, 1685 "priority": internal.MatchAnything, 1686 "sampled": internal.MatchAnything, 1687 }, 1688 }}) 1689 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1690 Intrinsics: map[string]interface{}{ 1691 "name": "OtherTransaction/Go/hello", 1692 "externalCallCount": 1, 1693 "externalDuration": internal.MatchAnything, 1694 "guid": internal.MatchAnything, 1695 "traceId": internal.MatchAnything, 1696 "priority": internal.MatchAnything, 1697 "sampled": internal.MatchAnything, 1698 }, 1699 }}) 1700 } 1701 1702 func TestRoundTripperOldCAT(t *testing.T) { 1703 app := testApp(nil, nil, t) 1704 txn := app.StartTransaction("hello", nil, nil) 1705 url := "http://example.com/" 1706 client := &http.Client{} 1707 inner := roundTripperFunc(func(r *http.Request) (*http.Response, error) { 1708 // TODO test that request headers have been set here. 1709 if r.URL.String() != url { 1710 t.Error(r.URL.String()) 1711 } 1712 return nil, errors.New("hello") 1713 }) 1714 client.Transport = NewRoundTripper(txn, inner) 1715 resp, err := client.Get(url) 1716 if resp != nil || err == nil { 1717 t.Error(resp, err.Error()) 1718 } 1719 txn.NoticeError(myError{}) 1720 txn.End() 1721 scope := "OtherTransaction/Go/hello" 1722 app.ExpectMetrics(t, append([]internal.WantMetric{ 1723 {Name: "External/all", Scope: "", Forced: true, Data: nil}, 1724 {Name: "External/allOther", Scope: "", Forced: true, Data: nil}, 1725 {Name: "External/example.com/all", Scope: "", Forced: false, Data: nil}, 1726 {Name: "External/example.com/http/GET", Scope: scope, Forced: false, Data: nil}, 1727 }, backgroundErrorMetrics...)) 1728 app.ExpectErrorEvents(t, []internal.WantEvent{{ 1729 Intrinsics: map[string]interface{}{ 1730 "error.class": "newrelic.myError", 1731 "error.message": "my msg", 1732 "transactionName": "OtherTransaction/Go/hello", 1733 "externalCallCount": 1, 1734 "externalDuration": internal.MatchAnything, 1735 }, 1736 }}) 1737 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1738 Intrinsics: map[string]interface{}{ 1739 "name": "OtherTransaction/Go/hello", 1740 "externalCallCount": 1, 1741 "externalDuration": internal.MatchAnything, 1742 "nr.tripId": internal.MatchAnything, 1743 "nr.guid": internal.MatchAnything, 1744 "nr.pathHash": internal.MatchAnything, 1745 }, 1746 }}) 1747 } 1748 1749 func TestTraceBelowThreshold(t *testing.T) { 1750 app := testApp(nil, nil, t) 1751 txn := app.StartTransaction("hello", nil, helloRequest) 1752 txn.End() 1753 app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) 1754 } 1755 1756 func TestTraceBelowThresholdBackground(t *testing.T) { 1757 app := testApp(nil, nil, t) 1758 txn := app.StartTransaction("hello", nil, nil) 1759 txn.End() 1760 app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) 1761 } 1762 1763 func TestTraceNoSegments(t *testing.T) { 1764 cfgfn := func(cfg *Config) { 1765 cfg.TransactionTracer.Threshold.IsApdexFailing = false 1766 cfg.TransactionTracer.Threshold.Duration = 0 1767 cfg.TransactionTracer.SegmentThreshold = 0 1768 } 1769 app := testApp(nil, cfgfn, t) 1770 txn := app.StartTransaction("hello", nil, helloRequest) 1771 txn.End() 1772 app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ 1773 MetricName: "WebTransaction/Go/hello", 1774 NumSegments: 0, 1775 }}) 1776 } 1777 1778 func TestTraceDisabledLocally(t *testing.T) { 1779 cfgfn := func(cfg *Config) { 1780 cfg.TransactionTracer.Threshold.IsApdexFailing = false 1781 cfg.TransactionTracer.Threshold.Duration = 0 1782 cfg.TransactionTracer.SegmentThreshold = 0 1783 cfg.TransactionTracer.Enabled = false 1784 } 1785 app := testApp(nil, cfgfn, t) 1786 txn := app.StartTransaction("hello", nil, helloRequest) 1787 txn.End() 1788 app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) 1789 } 1790 1791 func TestTraceDisabledByServerSideConfig(t *testing.T) { 1792 // Test that server-side-config trace-enabled-setting can disable transaction 1793 // traces. 1794 cfgfn := func(cfg *Config) { 1795 cfg.TransactionTracer.Threshold.IsApdexFailing = false 1796 cfg.TransactionTracer.Threshold.Duration = 0 1797 cfg.TransactionTracer.SegmentThreshold = 0 1798 } 1799 replyfn := func(reply *internal.ConnectReply) { 1800 json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.enabled":false}}`), reply) 1801 } 1802 app := testApp(replyfn, cfgfn, t) 1803 txn := app.StartTransaction("hello", nil, helloRequest) 1804 txn.End() 1805 app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) 1806 } 1807 1808 func TestTraceEnabledByServerSideConfig(t *testing.T) { 1809 // Test that server-side-config trace-enabled-setting can enable 1810 // transaction traces (and hence server-side-config has priority). 1811 cfgfn := func(cfg *Config) { 1812 cfg.TransactionTracer.Threshold.IsApdexFailing = false 1813 cfg.TransactionTracer.Threshold.Duration = 0 1814 cfg.TransactionTracer.SegmentThreshold = 0 1815 cfg.TransactionTracer.Enabled = false 1816 } 1817 replyfn := func(reply *internal.ConnectReply) { 1818 json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.enabled":true}}`), reply) 1819 } 1820 app := testApp(replyfn, cfgfn, t) 1821 txn := app.StartTransaction("hello", nil, helloRequest) 1822 txn.End() 1823 app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ 1824 MetricName: "WebTransaction/Go/hello", 1825 NumSegments: 0, 1826 }}) 1827 } 1828 1829 func TestTraceDisabledRemotelyOverridesServerSideConfig(t *testing.T) { 1830 // Test that the connect reply "collect_traces" setting overrides the 1831 // "transaction_tracer.enabled" server side config setting. 1832 cfgfn := func(cfg *Config) { 1833 cfg.TransactionTracer.Threshold.IsApdexFailing = false 1834 cfg.TransactionTracer.Threshold.Duration = 0 1835 cfg.TransactionTracer.SegmentThreshold = 0 1836 cfg.TransactionTracer.Enabled = true 1837 } 1838 replyfn := func(reply *internal.ConnectReply) { 1839 json.Unmarshal([]byte(`{"agent_config":{"transaction_tracer.enabled":true},"collect_traces":false}`), reply) 1840 } 1841 app := testApp(replyfn, cfgfn, t) 1842 txn := app.StartTransaction("hello", nil, helloRequest) 1843 txn.End() 1844 app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) 1845 } 1846 1847 func TestTraceDisabledRemotely(t *testing.T) { 1848 cfgfn := func(cfg *Config) { 1849 cfg.TransactionTracer.Threshold.IsApdexFailing = false 1850 cfg.TransactionTracer.Threshold.Duration = 0 1851 cfg.TransactionTracer.SegmentThreshold = 0 1852 } 1853 replyfn := func(reply *internal.ConnectReply) { 1854 reply.CollectTraces = false 1855 } 1856 app := testApp(replyfn, cfgfn, t) 1857 txn := app.StartTransaction("hello", nil, helloRequest) 1858 txn.End() 1859 app.ExpectTxnTraces(t, []internal.WantTxnTrace{}) 1860 } 1861 1862 func TestTraceWithSegments(t *testing.T) { 1863 cfgfn := func(cfg *Config) { 1864 cfg.TransactionTracer.Threshold.IsApdexFailing = false 1865 cfg.TransactionTracer.Threshold.Duration = 0 1866 cfg.TransactionTracer.SegmentThreshold = 0 1867 } 1868 app := testApp(nil, cfgfn, t) 1869 txn := app.StartTransaction("hello", nil, helloRequest) 1870 s1 := StartSegment(txn, "s1") 1871 s1.End() 1872 s2 := ExternalSegment{ 1873 StartTime: StartSegmentNow(txn), 1874 URL: "http://example.com", 1875 } 1876 s2.End() 1877 s3 := DatastoreSegment{ 1878 StartTime: StartSegmentNow(txn), 1879 Product: DatastoreMySQL, 1880 Collection: "my_table", 1881 Operation: "SELECT", 1882 } 1883 s3.End() 1884 txn.End() 1885 app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ 1886 MetricName: "WebTransaction/Go/hello", 1887 NumSegments: 3, 1888 }}) 1889 } 1890 1891 func TestTraceSegmentsBelowThreshold(t *testing.T) { 1892 cfgfn := func(cfg *Config) { 1893 cfg.TransactionTracer.Threshold.IsApdexFailing = false 1894 cfg.TransactionTracer.Threshold.Duration = 0 1895 cfg.TransactionTracer.SegmentThreshold = 1 * time.Hour 1896 } 1897 app := testApp(nil, cfgfn, t) 1898 txn := app.StartTransaction("hello", nil, helloRequest) 1899 s1 := StartSegment(txn, "s1") 1900 s1.End() 1901 s2 := ExternalSegment{ 1902 StartTime: StartSegmentNow(txn), 1903 URL: "http://example.com", 1904 } 1905 s2.End() 1906 s3 := DatastoreSegment{ 1907 StartTime: StartSegmentNow(txn), 1908 Product: DatastoreMySQL, 1909 Collection: "my_table", 1910 Operation: "SELECT", 1911 } 1912 s3.End() 1913 txn.End() 1914 app.ExpectTxnTraces(t, []internal.WantTxnTrace{{ 1915 MetricName: "WebTransaction/Go/hello", 1916 NumSegments: 0, 1917 }}) 1918 } 1919 1920 func TestNoticeErrorTxnEvents(t *testing.T) { 1921 app := testApp(nil, nil, t) 1922 txn := app.StartTransaction("hello", nil, nil) 1923 err := txn.NoticeError(myError{}) 1924 if nil != err { 1925 t.Error(err) 1926 } 1927 txn.End() 1928 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1929 Intrinsics: map[string]interface{}{ 1930 "name": "OtherTransaction/Go/hello", 1931 "error": true, 1932 }, 1933 }}) 1934 } 1935 1936 func TestTransactionApplication(t *testing.T) { 1937 txn := testApp(nil, nil, t).StartTransaction("hello", nil, nil) 1938 app := txn.Application() 1939 err := app.RecordCustomMetric("myMetric", 123.0) 1940 if nil != err { 1941 t.Error(err) 1942 } 1943 expectData := []float64{1, 123.0, 123.0, 123.0, 123.0, 123.0 * 123.0} 1944 app.(expectApp).ExpectMetrics(t, []internal.WantMetric{ 1945 {Name: "Custom/myMetric", Scope: "", Forced: false, Data: expectData}, 1946 }) 1947 } 1948 1949 func TestNilSegmentPointerEnd(t *testing.T) { 1950 var basicSegment *Segment 1951 var datastoreSegment *DatastoreSegment 1952 var externalSegment *ExternalSegment 1953 1954 // These calls on nil pointer receivers should not panic. 1955 basicSegment.End() 1956 datastoreSegment.End() 1957 externalSegment.End() 1958 } 1959 1960 type flushWriter struct{} 1961 1962 func (f flushWriter) WriteHeader(int) {} 1963 func (f flushWriter) Write([]byte) (int, error) { return 0, nil } 1964 func (f flushWriter) Header() http.Header { return nil } 1965 func (f flushWriter) Flush() {} 1966 1967 func TestAsync(t *testing.T) { 1968 app := testApp(nil, nil, t) 1969 txn := app.StartTransaction("hello", flushWriter{}, nil) 1970 if _, ok := txn.(http.Flusher); !ok { 1971 t.Error("transaction should have flush") 1972 } 1973 s1 := StartSegment(txn, "mainThread") 1974 asyncThread := txn.NewGoroutine() 1975 // Test that the async transaction reference has the correct optional 1976 // interface behavior. 1977 if _, ok := asyncThread.(http.Flusher); !ok { 1978 t.Error("async transaction reference should have flush") 1979 } 1980 s2 := StartSegment(asyncThread, "asyncThread") 1981 // End segments in interleaved order. 1982 s1.End() 1983 s2.End() 1984 // Test that the async transaction reference has the expected 1985 // transaction method behavior. 1986 asyncThread.AddAttribute("zip", "zap") 1987 // Test that the transaction ends when the async transaction is ended. 1988 if err := asyncThread.End(); nil != err { 1989 t.Error(err) 1990 } 1991 threadAfterEnd := asyncThread.NewGoroutine() 1992 if _, ok := threadAfterEnd.(http.Flusher); !ok { 1993 t.Error("after end transaction reference should have flush") 1994 } 1995 if err := threadAfterEnd.End(); err != errAlreadyEnded { 1996 t.Error(err) 1997 } 1998 app.ExpectTxnEvents(t, []internal.WantEvent{{ 1999 Intrinsics: map[string]interface{}{ 2000 "name": "OtherTransaction/Go/hello", 2001 }, 2002 UserAttributes: map[string]interface{}{ 2003 "zip": "zap", 2004 }, 2005 }}) 2006 app.ExpectMetrics(t, []internal.WantMetric{ 2007 {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, 2008 {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, 2009 {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, 2010 {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, 2011 {Name: "Custom/mainThread", Scope: "", Forced: false, Data: nil}, 2012 {Name: "Custom/mainThread", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, 2013 {Name: "Custom/asyncThread", Scope: "", Forced: false, Data: nil}, 2014 {Name: "Custom/asyncThread", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, 2015 }) 2016 } 2017 2018 func TestMessageProducerSegmentBasic(t *testing.T) { 2019 replyfn := func(reply *internal.ConnectReply) { 2020 reply.AdaptiveSampler = internal.SampleEverything{} 2021 } 2022 cfgfn := func(cfg *Config) { 2023 cfg.DistributedTracer.Enabled = true 2024 } 2025 app := testApp(replyfn, cfgfn, t) 2026 txn := app.StartTransaction("hello", nil, nil) 2027 s := MessageProducerSegment{ 2028 StartTime: StartSegmentNow(txn), 2029 Library: "RabbitMQ", 2030 DestinationType: MessageQueue, 2031 DestinationName: "myQueue", 2032 } 2033 err := s.End() 2034 if err != nil { 2035 t.Error(err) 2036 } 2037 txn.End() 2038 app.ExpectMetrics(t, []internal.WantMetric{ 2039 {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, 2040 {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, 2041 {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, 2042 {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, 2043 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/all", Scope: "", Forced: false, Data: nil}, 2044 {Name: "DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther", Scope: "", Forced: false, Data: nil}, 2045 {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", Scope: "", Forced: false, Data: nil}, 2046 {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, 2047 }) 2048 app.ExpectSpanEvents(t, []internal.WantEvent{ 2049 { 2050 Intrinsics: map[string]interface{}{ 2051 "name": "OtherTransaction/Go/hello", 2052 "sampled": true, 2053 "category": "generic", 2054 "nr.entryPoint": true, 2055 }, 2056 UserAttributes: map[string]interface{}{}, 2057 AgentAttributes: map[string]interface{}{}, 2058 }, 2059 { 2060 Intrinsics: map[string]interface{}{ 2061 "parentId": internal.MatchAnything, 2062 "name": "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", 2063 "category": "generic", 2064 }, 2065 UserAttributes: map[string]interface{}{}, 2066 AgentAttributes: map[string]interface{}{}, 2067 }, 2068 }) 2069 } 2070 2071 func TestMessageProducerSegmentMissingDestinationType(t *testing.T) { 2072 app := testApp(nil, nil, t) 2073 txn := app.StartTransaction("hello", nil, nil) 2074 s := MessageProducerSegment{ 2075 StartTime: StartSegmentNow(txn), 2076 Library: "RabbitMQ", 2077 DestinationName: "myQueue", 2078 } 2079 err := s.End() 2080 if err != nil { 2081 t.Error(err) 2082 } 2083 txn.End() 2084 app.ExpectMetrics(t, []internal.WantMetric{ 2085 {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, 2086 {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, 2087 {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, 2088 {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, 2089 {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", Scope: "", Forced: false, Data: nil}, 2090 {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/myQueue", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, 2091 }) 2092 } 2093 2094 func TestMessageProducerSegmentTemp(t *testing.T) { 2095 app := testApp(nil, nil, t) 2096 txn := app.StartTransaction("hello", nil, nil) 2097 s := MessageProducerSegment{ 2098 StartTime: StartSegmentNow(txn), 2099 Library: "RabbitMQ", 2100 DestinationType: MessageQueue, 2101 DestinationTemporary: true, 2102 DestinationName: "myQueue0123456789", 2103 } 2104 err := s.End() 2105 if err != nil { 2106 t.Error(err) 2107 } 2108 txn.End() 2109 app.ExpectMetrics(t, []internal.WantMetric{ 2110 {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, 2111 {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, 2112 {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, 2113 {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, 2114 {Name: "MessageBroker/RabbitMQ/Queue/Produce/Temp", Scope: "", Forced: false, Data: nil}, 2115 {Name: "MessageBroker/RabbitMQ/Queue/Produce/Temp", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, 2116 }) 2117 } 2118 2119 func TestMessageProducerSegmentNoName(t *testing.T) { 2120 app := testApp(nil, nil, t) 2121 txn := app.StartTransaction("hello", nil, nil) 2122 s := MessageProducerSegment{ 2123 StartTime: StartSegmentNow(txn), 2124 Library: "RabbitMQ", 2125 DestinationType: MessageQueue, 2126 } 2127 err := s.End() 2128 if err != nil { 2129 t.Error(err) 2130 } 2131 txn.End() 2132 app.ExpectMetrics(t, []internal.WantMetric{ 2133 {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, 2134 {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, 2135 {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, 2136 {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, 2137 {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/Unknown", Scope: "", Forced: false, Data: nil}, 2138 {Name: "MessageBroker/RabbitMQ/Queue/Produce/Named/Unknown", Scope: "OtherTransaction/Go/hello", Forced: false, Data: nil}, 2139 }) 2140 } 2141 2142 func TestMessageProducerSegmentTxnEnded(t *testing.T) { 2143 app := testApp(nil, nil, t) 2144 txn := app.StartTransaction("hello", nil, nil) 2145 s := MessageProducerSegment{ 2146 StartTime: StartSegmentNow(txn), 2147 Library: "RabbitMQ", 2148 DestinationType: MessageQueue, 2149 DestinationTemporary: true, 2150 DestinationName: "myQueue0123456789", 2151 } 2152 txn.End() 2153 err := s.End() 2154 if err != errAlreadyEnded { 2155 t.Error("expected already ended error", err) 2156 } 2157 app.ExpectMetrics(t, []internal.WantMetric{ 2158 {Name: "OtherTransaction/Go/hello", Scope: "", Forced: true, Data: nil}, 2159 {Name: "OtherTransaction/all", Scope: "", Forced: true, Data: nil}, 2160 {Name: "OtherTransactionTotalTime", Scope: "", Forced: true, Data: nil}, 2161 {Name: "OtherTransactionTotalTime/Go/hello", Scope: "", Forced: false, Data: nil}, 2162 }) 2163 } 2164 2165 func TestMessageProducerSegmentNilTxn(t *testing.T) { 2166 s := MessageProducerSegment{ 2167 StartTime: StartSegmentNow(nil), 2168 Library: "RabbitMQ", 2169 DestinationType: MessageQueue, 2170 DestinationTemporary: true, 2171 DestinationName: "myQueue0123456789", 2172 } 2173 s.End() 2174 } 2175 2176 func TestMessageProducerSegmentNilSegment(t *testing.T) { 2177 var s *MessageProducerSegment 2178 s.End() 2179 }