github.com/newrelic/go-agent@v3.26.0+incompatible/internal_txn.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 "errors" 8 "fmt" 9 "net/http" 10 "net/url" 11 "reflect" 12 "strings" 13 "sync" 14 "time" 15 16 "github.com/newrelic/go-agent/internal" 17 ) 18 19 type txnInput struct { 20 // This ResponseWriter should only be accessed using txn.getWriter() 21 writer http.ResponseWriter 22 app Application 23 Consumer dataConsumer 24 *appRun 25 } 26 27 type txn struct { 28 txnInput 29 // This mutex is required since the consumer may call the public API 30 // interface functions from different routines. 31 sync.Mutex 32 // finished indicates whether or not End() has been called. After 33 // finished has been set to true, no recording should occur. 34 finished bool 35 numPayloadsCreated uint32 36 sampledCalculated bool 37 38 ignore bool 39 40 // wroteHeader prevents capturing multiple response code errors if the 41 // user erroneously calls WriteHeader multiple times. 42 wroteHeader bool 43 44 internal.TxnData 45 46 mainThread internal.Thread 47 asyncThreads []*internal.Thread 48 } 49 50 type thread struct { 51 *txn 52 // thread does not have locking because it should only be accessed while 53 // the txn is locked. 54 thread *internal.Thread 55 } 56 57 func (txn *txn) markStart(now time.Time) { 58 txn.Start = now 59 // The mainThread is considered active now. 60 txn.mainThread.RecordActivity(now) 61 62 } 63 64 func (txn *txn) markEnd(now time.Time, thread *internal.Thread) { 65 txn.Stop = now 66 // The thread on which End() was called is considered active now. 67 thread.RecordActivity(now) 68 txn.Duration = txn.Stop.Sub(txn.Start) 69 70 // TotalTime is the sum of "active time" across all threads. A thread 71 // was active when it started the transaction, stopped the transaction, 72 // started a segment, or stopped a segment. 73 txn.TotalTime = txn.mainThread.TotalTime() 74 for _, thd := range txn.asyncThreads { 75 txn.TotalTime += thd.TotalTime() 76 } 77 // Ensure that TotalTime is at least as large as Duration so that the 78 // graphs look sensible. This can happen under the following situation: 79 // goroutine1: txn.start----|segment1| 80 // goroutine2: |segment2|----txn.end 81 if txn.Duration > txn.TotalTime { 82 txn.TotalTime = txn.Duration 83 } 84 } 85 86 func newTxn(input txnInput, name string) *thread { 87 txn := &txn{ 88 txnInput: input, 89 } 90 txn.markStart(time.Now()) 91 92 txn.Name = name 93 txn.Attrs = internal.NewAttributes(input.AttributeConfig) 94 95 if input.Config.DistributedTracer.Enabled { 96 txn.BetterCAT.Enabled = true 97 txn.BetterCAT.Priority = internal.NewPriority() 98 txn.TraceIDGenerator = input.Reply.TraceIDGenerator 99 txn.BetterCAT.ID = txn.TraceIDGenerator.GenerateTraceID() 100 txn.SpanEventsEnabled = txn.Config.SpanEvents.Enabled 101 txn.LazilyCalculateSampled = txn.lazilyCalculateSampled 102 } 103 104 txn.Attrs.Agent.Add(internal.AttributeHostDisplayName, txn.Config.HostDisplayName, nil) 105 txn.TxnTrace.Enabled = txn.Config.TransactionTracer.Enabled 106 txn.TxnTrace.SegmentThreshold = txn.Config.TransactionTracer.SegmentThreshold 107 txn.StackTraceThreshold = txn.Config.TransactionTracer.StackTraceThreshold 108 txn.SlowQueriesEnabled = txn.Config.DatastoreTracer.SlowQuery.Enabled 109 txn.SlowQueryThreshold = txn.Config.DatastoreTracer.SlowQuery.Threshold 110 111 // Synthetics support is tied up with a transaction's Old CAT field, 112 // CrossProcess. To support Synthetics with either BetterCAT or Old CAT, 113 // Initialize the CrossProcess field of the transaction, passing in 114 // the top-level configuration. 115 doOldCAT := txn.Config.CrossApplicationTracer.Enabled 116 noGUID := txn.Config.DistributedTracer.Enabled 117 txn.CrossProcess.Init(doOldCAT, noGUID, input.Reply) 118 119 return &thread{ 120 txn: txn, 121 thread: &txn.mainThread, 122 } 123 } 124 125 // lazilyCalculateSampled calculates and returns whether or not the transaction 126 // should be sampled. Sampled is not computed at the beginning of the 127 // transaction because we want to calculate Sampled only for transactions that 128 // do not accept an inbound payload. 129 func (txn *txn) lazilyCalculateSampled() bool { 130 if !txn.BetterCAT.Enabled { 131 return false 132 } 133 if txn.sampledCalculated { 134 return txn.BetterCAT.Sampled 135 } 136 txn.BetterCAT.Sampled = txn.Reply.AdaptiveSampler.ComputeSampled(txn.BetterCAT.Priority.Float32(), time.Now()) 137 if txn.BetterCAT.Sampled { 138 txn.BetterCAT.Priority += 1.0 139 } 140 txn.sampledCalculated = true 141 return txn.BetterCAT.Sampled 142 } 143 144 type requestWrap struct{ request *http.Request } 145 146 func (r requestWrap) Header() http.Header { return r.request.Header } 147 func (r requestWrap) URL() *url.URL { return r.request.URL } 148 func (r requestWrap) Method() string { return r.request.Method } 149 150 func (r requestWrap) Transport() TransportType { 151 if strings.HasPrefix(r.request.Proto, "HTTP") { 152 if r.request.TLS != nil { 153 return TransportHTTPS 154 } 155 return TransportHTTP 156 } 157 return TransportUnknown 158 159 } 160 161 type staticWebRequest struct { 162 header http.Header 163 url *url.URL 164 method string 165 transport TransportType 166 } 167 168 func (r staticWebRequest) Header() http.Header { return r.header } 169 func (r staticWebRequest) URL() *url.URL { return r.url } 170 func (r staticWebRequest) Method() string { return r.method } 171 func (r staticWebRequest) Transport() TransportType { return TransportHTTP } 172 173 func (txn *txn) SetWebRequest(r WebRequest) error { 174 txn.Lock() 175 defer txn.Unlock() 176 177 if txn.finished { 178 return errAlreadyEnded 179 } 180 181 // Any call to SetWebRequest should indicate a web transaction. 182 txn.IsWeb = true 183 184 if nil == r { 185 return nil 186 } 187 h := r.Header() 188 if nil != h { 189 txn.Queuing = internal.QueueDuration(h, txn.Start) 190 191 if p := h.Get(DistributedTracePayloadHeader); p != "" { 192 txn.acceptDistributedTracePayloadLocked(r.Transport(), p) 193 } 194 195 txn.CrossProcess.InboundHTTPRequest(h) 196 } 197 198 internal.RequestAgentAttributes(txn.Attrs, r.Method(), h, r.URL()) 199 200 return nil 201 } 202 203 func (thd *thread) SetWebResponse(w http.ResponseWriter) Transaction { 204 txn := thd.txn 205 txn.Lock() 206 defer txn.Unlock() 207 208 // Replace the ResponseWriter even if the transaction has ended so that 209 // consumers calling ResponseWriter methods on the transactions see that 210 // data flowing through as expected. 211 txn.writer = w 212 213 return upgradeTxn(&thread{ 214 thread: thd.thread, 215 txn: txn, 216 }) 217 } 218 219 func (txn *txn) freezeName() { 220 if txn.ignore || ("" != txn.FinalName) { 221 return 222 } 223 224 txn.FinalName = internal.CreateFullTxnName(txn.Name, txn.Reply, txn.IsWeb) 225 if "" == txn.FinalName { 226 txn.ignore = true 227 } 228 } 229 230 func (txn *txn) getsApdex() bool { 231 return txn.IsWeb 232 } 233 234 func (txn *txn) shouldSaveTrace() bool { 235 if !txn.Config.TransactionTracer.Enabled { 236 return false 237 } 238 if txn.CrossProcess.IsSynthetics() { 239 return true 240 } 241 return txn.Duration >= txn.txnTraceThreshold(txn.ApdexThreshold) 242 } 243 244 func (txn *txn) MergeIntoHarvest(h *internal.Harvest) { 245 246 var priority internal.Priority 247 if txn.BetterCAT.Enabled { 248 priority = txn.BetterCAT.Priority 249 } else { 250 priority = internal.NewPriority() 251 } 252 253 internal.CreateTxnMetrics(&txn.TxnData, h.Metrics) 254 internal.MergeBreakdownMetrics(&txn.TxnData, h.Metrics) 255 256 if txn.Config.TransactionEvents.Enabled { 257 // Allocate a new TxnEvent to prevent a reference to the large transaction. 258 alloc := new(internal.TxnEvent) 259 *alloc = txn.TxnData.TxnEvent 260 h.TxnEvents.AddTxnEvent(alloc, priority) 261 } 262 263 if txn.Reply.CollectErrors { 264 internal.MergeTxnErrors(&h.ErrorTraces, txn.Errors, txn.TxnEvent) 265 } 266 267 if txn.Config.ErrorCollector.CaptureEvents { 268 for _, e := range txn.Errors { 269 errEvent := &internal.ErrorEvent{ 270 ErrorData: *e, 271 TxnEvent: txn.TxnEvent, 272 } 273 // Since the stack trace is not used in error events, remove the reference 274 // to minimize memory. 275 errEvent.Stack = nil 276 h.ErrorEvents.Add(errEvent, priority) 277 } 278 } 279 280 if txn.shouldSaveTrace() { 281 h.TxnTraces.Witness(internal.HarvestTrace{ 282 TxnEvent: txn.TxnEvent, 283 Trace: txn.TxnTrace, 284 }) 285 } 286 287 if nil != txn.SlowQueries { 288 h.SlowSQLs.Merge(txn.SlowQueries, txn.TxnEvent) 289 } 290 291 if txn.BetterCAT.Sampled && txn.SpanEventsEnabled { 292 h.SpanEvents.MergeFromTransaction(&txn.TxnData) 293 } 294 } 295 296 func headersJustWritten(txn *txn, code int, hdr http.Header) { 297 txn.Lock() 298 defer txn.Unlock() 299 300 if txn.finished { 301 return 302 } 303 if txn.wroteHeader { 304 return 305 } 306 txn.wroteHeader = true 307 308 internal.ResponseHeaderAttributes(txn.Attrs, hdr) 309 internal.ResponseCodeAttribute(txn.Attrs, code) 310 311 if txn.appRun.responseCodeIsError(code) { 312 e := internal.TxnErrorFromResponseCode(time.Now(), code) 313 e.Stack = internal.GetStackTrace() 314 txn.noticeErrorInternal(e) 315 } 316 } 317 318 func (txn *txn) responseHeader(hdr http.Header) http.Header { 319 txn.Lock() 320 defer txn.Unlock() 321 322 if txn.finished { 323 return nil 324 } 325 if txn.wroteHeader { 326 return nil 327 } 328 if !txn.CrossProcess.Enabled { 329 return nil 330 } 331 if !txn.CrossProcess.IsInbound() { 332 return nil 333 } 334 txn.freezeName() 335 contentLength := internal.GetContentLengthFromHeader(hdr) 336 337 appData, err := txn.CrossProcess.CreateAppData(txn.FinalName, txn.Queuing, time.Since(txn.Start), contentLength) 338 if err != nil { 339 txn.Config.Logger.Debug("error generating outbound response header", map[string]interface{}{ 340 "error": err, 341 }) 342 return nil 343 } 344 return internal.AppDataToHTTPHeader(appData) 345 } 346 347 func addCrossProcessHeaders(txn *txn, hdr http.Header) { 348 // responseHeader() checks the wroteHeader field and returns a nil map if the 349 // header has been written, so we don't need a check here. 350 if nil != hdr { 351 for key, values := range txn.responseHeader(hdr) { 352 for _, value := range values { 353 hdr.Add(key, value) 354 } 355 } 356 } 357 } 358 359 // getWriter is used to access the transaction's ResponseWriter. The 360 // ResponseWriter is mutex protected since it may be changed with 361 // txn.SetWebResponse, and we want changes to be visible across goroutines. The 362 // ResponseWriter is accessed using this getWriter() function rather than directly 363 // in mutex protected methods since we do NOT want the transaction to be locked 364 // while calling the ResponseWriter's methods. 365 func (txn *txn) getWriter() http.ResponseWriter { 366 txn.Lock() 367 rw := txn.writer 368 txn.Unlock() 369 return rw 370 } 371 372 func nilSafeHeader(rw http.ResponseWriter) http.Header { 373 if nil == rw { 374 return nil 375 } 376 return rw.Header() 377 } 378 379 func (txn *txn) Header() http.Header { 380 return nilSafeHeader(txn.getWriter()) 381 } 382 383 func (txn *txn) Write(b []byte) (n int, err error) { 384 rw := txn.getWriter() 385 hdr := nilSafeHeader(rw) 386 387 // This is safe to call unconditionally, even if Write() is called multiple 388 // times; see also the commentary in addCrossProcessHeaders(). 389 addCrossProcessHeaders(txn, hdr) 390 391 if rw != nil { 392 n, err = rw.Write(b) 393 } 394 395 headersJustWritten(txn, http.StatusOK, hdr) 396 397 return 398 } 399 400 func (txn *txn) WriteHeader(code int) { 401 rw := txn.getWriter() 402 hdr := nilSafeHeader(rw) 403 404 addCrossProcessHeaders(txn, hdr) 405 406 if nil != rw { 407 rw.WriteHeader(code) 408 } 409 410 headersJustWritten(txn, code, hdr) 411 } 412 413 func (thd *thread) End() error { 414 txn := thd.txn 415 txn.Lock() 416 defer txn.Unlock() 417 418 if txn.finished { 419 return errAlreadyEnded 420 } 421 422 txn.finished = true 423 424 r := recover() 425 if nil != r { 426 e := internal.TxnErrorFromPanic(time.Now(), r) 427 e.Stack = internal.GetStackTrace() 428 txn.noticeErrorInternal(e) 429 } 430 431 txn.markEnd(time.Now(), thd.thread) 432 txn.freezeName() 433 // Make a sampling decision if there have been no segments or outbound 434 // payloads. 435 txn.lazilyCalculateSampled() 436 437 // Finalise the CAT state. 438 if err := txn.CrossProcess.Finalise(txn.Name, txn.Config.AppName); err != nil { 439 txn.Config.Logger.Debug("error finalising the cross process state", map[string]interface{}{ 440 "error": err, 441 }) 442 } 443 444 // Assign apdexThreshold regardless of whether or not the transaction 445 // gets apdex since it may be used to calculate the trace threshold. 446 txn.ApdexThreshold = internal.CalculateApdexThreshold(txn.Reply, txn.FinalName) 447 448 if txn.getsApdex() { 449 if txn.HasErrors() { 450 txn.Zone = internal.ApdexFailing 451 } else { 452 txn.Zone = internal.CalculateApdexZone(txn.ApdexThreshold, txn.Duration) 453 } 454 } else { 455 txn.Zone = internal.ApdexNone 456 } 457 458 if txn.Config.Logger.DebugEnabled() { 459 txn.Config.Logger.Debug("transaction ended", map[string]interface{}{ 460 "name": txn.FinalName, 461 "duration_ms": txn.Duration.Seconds() * 1000.0, 462 "ignored": txn.ignore, 463 "app_connected": "" != txn.Reply.RunID, 464 }) 465 } 466 467 if !txn.ignore { 468 txn.Consumer.Consume(txn.Reply.RunID, txn) 469 } 470 471 // Note that if a consumer uses `panic(nil)`, the panic will not 472 // propagate. 473 if nil != r { 474 panic(r) 475 } 476 477 return nil 478 } 479 480 func (txn *txn) AddAttribute(name string, value interface{}) error { 481 txn.Lock() 482 defer txn.Unlock() 483 484 if txn.Config.HighSecurity { 485 return errHighSecurityEnabled 486 } 487 488 if !txn.Reply.SecurityPolicies.CustomParameters.Enabled() { 489 return errSecurityPolicy 490 } 491 492 if txn.finished { 493 return errAlreadyEnded 494 } 495 496 return internal.AddUserAttribute(txn.Attrs, name, value, internal.DestAll) 497 } 498 499 var ( 500 errorsDisabled = errors.New("errors disabled") 501 errNilError = errors.New("nil error") 502 errAlreadyEnded = errors.New("transaction has already ended") 503 errSecurityPolicy = errors.New("disabled by security policy") 504 errTransactionIgnored = errors.New("transaction has been ignored") 505 errBrowserDisabled = errors.New("browser disabled by local configuration") 506 ) 507 508 const ( 509 highSecurityErrorMsg = "message removed by high security setting" 510 securityPolicyErrorMsg = "message removed by security policy" 511 ) 512 513 func (txn *txn) noticeErrorInternal(err internal.ErrorData) error { 514 if !txn.Config.ErrorCollector.Enabled { 515 return errorsDisabled 516 } 517 518 if nil == txn.Errors { 519 txn.Errors = internal.NewTxnErrors(internal.MaxTxnErrors) 520 } 521 522 if txn.Config.HighSecurity { 523 err.Msg = highSecurityErrorMsg 524 } 525 526 if !txn.Reply.SecurityPolicies.AllowRawExceptionMessages.Enabled() { 527 err.Msg = securityPolicyErrorMsg 528 } 529 530 txn.Errors.Add(err) 531 txn.TxnData.TxnEvent.HasError = true //mark transaction as having an error 532 return nil 533 } 534 535 var ( 536 errTooManyErrorAttributes = fmt.Errorf("too many extra attributes: limit is %d", 537 internal.AttributeErrorLimit) 538 ) 539 540 // errorCause returns the error's deepest wrapped ancestor. 541 func errorCause(err error) error { 542 for { 543 if unwrapper, ok := err.(interface{ Unwrap() error }); ok { 544 if next := unwrapper.Unwrap(); nil != next { 545 err = next 546 continue 547 } 548 } 549 return err 550 } 551 } 552 553 func errorClassMethod(err error) string { 554 if ec, ok := err.(ErrorClasser); ok { 555 return ec.ErrorClass() 556 } 557 return "" 558 } 559 560 func errorStackTraceMethod(err error) internal.StackTrace { 561 if st, ok := err.(StackTracer); ok { 562 return st.StackTrace() 563 } 564 return nil 565 } 566 567 func errorAttributesMethod(err error) map[string]interface{} { 568 if st, ok := err.(ErrorAttributer); ok { 569 return st.ErrorAttributes() 570 } 571 return nil 572 } 573 574 func errDataFromError(input error) (data internal.ErrorData, err error) { 575 cause := errorCause(input) 576 577 data = internal.ErrorData{ 578 When: time.Now(), 579 Msg: input.Error(), 580 } 581 582 if c := errorClassMethod(input); "" != c { 583 // If the error implements ErrorClasser, use that. 584 data.Klass = c 585 } else if c := errorClassMethod(cause); "" != c { 586 // Otherwise, if the error's cause implements ErrorClasser, use that. 587 data.Klass = c 588 } else { 589 // As a final fallback, use the type of the error's cause. 590 data.Klass = reflect.TypeOf(cause).String() 591 } 592 593 if st := errorStackTraceMethod(input); nil != st { 594 // If the error implements StackTracer, use that. 595 data.Stack = st 596 } else if st := errorStackTraceMethod(cause); nil != st { 597 // Otherwise, if the error's cause implements StackTracer, use that. 598 data.Stack = st 599 } else { 600 // As a final fallback, generate a StackTrace here. 601 data.Stack = internal.GetStackTrace() 602 } 603 604 var unvetted map[string]interface{} 605 if ats := errorAttributesMethod(input); nil != ats { 606 // If the error implements ErrorAttributer, use that. 607 unvetted = ats 608 } else { 609 // Otherwise, if the error's cause implements ErrorAttributer, use that. 610 unvetted = errorAttributesMethod(cause) 611 } 612 if unvetted != nil { 613 if len(unvetted) > internal.AttributeErrorLimit { 614 err = errTooManyErrorAttributes 615 return 616 } 617 618 data.ExtraAttributes = make(map[string]interface{}) 619 for key, val := range unvetted { 620 val, err = internal.ValidateUserAttribute(key, val) 621 if nil != err { 622 return 623 } 624 data.ExtraAttributes[key] = val 625 } 626 } 627 628 return data, nil 629 } 630 631 func (txn *txn) NoticeError(input error) error { 632 txn.Lock() 633 defer txn.Unlock() 634 635 if txn.finished { 636 return errAlreadyEnded 637 } 638 639 if nil == input { 640 return errNilError 641 } 642 643 data, err := errDataFromError(input) 644 if nil != err { 645 return err 646 } 647 648 if txn.Config.HighSecurity || !txn.Reply.SecurityPolicies.CustomParameters.Enabled() { 649 data.ExtraAttributes = nil 650 } 651 652 return txn.noticeErrorInternal(data) 653 } 654 655 func (txn *txn) SetName(name string) error { 656 txn.Lock() 657 defer txn.Unlock() 658 659 if txn.finished { 660 return errAlreadyEnded 661 } 662 663 txn.Name = name 664 return nil 665 } 666 667 func (txn *txn) Ignore() error { 668 txn.Lock() 669 defer txn.Unlock() 670 671 if txn.finished { 672 return errAlreadyEnded 673 } 674 txn.ignore = true 675 return nil 676 } 677 678 func (thd *thread) StartSegmentNow() SegmentStartTime { 679 var s internal.SegmentStartTime 680 txn := thd.txn 681 txn.Lock() 682 if !txn.finished { 683 s = internal.StartSegment(&txn.TxnData, thd.thread, time.Now()) 684 } 685 txn.Unlock() 686 return SegmentStartTime{ 687 segment: segment{ 688 start: s, 689 thread: thd, 690 }, 691 } 692 } 693 694 const ( 695 // Browser fields are encoded using the first digits of the license 696 // key. 697 browserEncodingKeyLimit = 13 698 ) 699 700 func browserEncodingKey(licenseKey string) []byte { 701 key := []byte(licenseKey) 702 if len(key) > browserEncodingKeyLimit { 703 key = key[0:browserEncodingKeyLimit] 704 } 705 return key 706 } 707 708 func (txn *txn) BrowserTimingHeader() (*BrowserTimingHeader, error) { 709 txn.Lock() 710 defer txn.Unlock() 711 712 if !txn.Config.BrowserMonitoring.Enabled { 713 return nil, errBrowserDisabled 714 } 715 716 if txn.Reply.AgentLoader == "" { 717 // If the loader is empty, either browser has been disabled 718 // by the server or the application is not yet connected. 719 return nil, nil 720 } 721 722 if txn.finished { 723 return nil, errAlreadyEnded 724 } 725 726 txn.freezeName() 727 728 // Freezing the name might cause the transaction to be ignored, so check 729 // this after txn.freezeName(). 730 if txn.ignore { 731 return nil, errTransactionIgnored 732 } 733 734 encodingKey := browserEncodingKey(txn.Config.License) 735 736 attrs, err := internal.Obfuscate(internal.BrowserAttributes(txn.Attrs), encodingKey) 737 if err != nil { 738 return nil, fmt.Errorf("error getting browser attributes: %v", err) 739 } 740 741 name, err := internal.Obfuscate([]byte(txn.FinalName), encodingKey) 742 if err != nil { 743 return nil, fmt.Errorf("error obfuscating name: %v", err) 744 } 745 746 return &BrowserTimingHeader{ 747 agentLoader: txn.Reply.AgentLoader, 748 info: browserInfo{ 749 Beacon: txn.Reply.Beacon, 750 LicenseKey: txn.Reply.BrowserKey, 751 ApplicationID: txn.Reply.AppID, 752 TransactionName: name, 753 QueueTimeMillis: txn.Queuing.Nanoseconds() / (1000 * 1000), 754 ApplicationTimeMillis: time.Now().Sub(txn.Start).Nanoseconds() / (1000 * 1000), 755 ObfuscatedAttributes: attrs, 756 ErrorBeacon: txn.Reply.ErrorBeacon, 757 Agent: txn.Reply.JSAgentFile, 758 }, 759 }, nil 760 } 761 762 func createThread(txn *txn) *internal.Thread { 763 newThread := internal.NewThread(&txn.TxnData) 764 txn.asyncThreads = append(txn.asyncThreads, newThread) 765 return newThread 766 } 767 768 func (thd *thread) NewGoroutine() Transaction { 769 txn := thd.txn 770 txn.Lock() 771 defer txn.Unlock() 772 773 if txn.finished { 774 // If the transaction has finished, return the same thread. 775 return upgradeTxn(thd) 776 } 777 return upgradeTxn(&thread{ 778 thread: createThread(txn), 779 txn: txn, 780 }) 781 } 782 783 type segment struct { 784 start internal.SegmentStartTime 785 thread *thread 786 } 787 788 func endSegment(s *Segment) error { 789 if nil == s { 790 return nil 791 } 792 thd := s.StartTime.thread 793 if nil == thd { 794 return nil 795 } 796 txn := thd.txn 797 var err error 798 txn.Lock() 799 if txn.finished { 800 err = errAlreadyEnded 801 } else { 802 err = internal.EndBasicSegment(&txn.TxnData, thd.thread, s.StartTime.start, time.Now(), s.Name) 803 } 804 txn.Unlock() 805 return err 806 } 807 808 func endDatastore(s *DatastoreSegment) error { 809 if nil == s { 810 return nil 811 } 812 thd := s.StartTime.thread 813 if nil == thd { 814 return nil 815 } 816 txn := thd.txn 817 txn.Lock() 818 defer txn.Unlock() 819 820 if txn.finished { 821 return errAlreadyEnded 822 } 823 if txn.Config.HighSecurity { 824 s.QueryParameters = nil 825 } 826 if !txn.Config.DatastoreTracer.QueryParameters.Enabled { 827 s.QueryParameters = nil 828 } 829 if txn.Reply.SecurityPolicies.RecordSQL.IsSet() { 830 s.QueryParameters = nil 831 if !txn.Reply.SecurityPolicies.RecordSQL.Enabled() { 832 s.ParameterizedQuery = "" 833 } 834 } 835 if !txn.Config.DatastoreTracer.DatabaseNameReporting.Enabled { 836 s.DatabaseName = "" 837 } 838 if !txn.Config.DatastoreTracer.InstanceReporting.Enabled { 839 s.Host = "" 840 s.PortPathOrID = "" 841 } 842 return internal.EndDatastoreSegment(internal.EndDatastoreParams{ 843 TxnData: &txn.TxnData, 844 Thread: thd.thread, 845 Start: s.StartTime.start, 846 Now: time.Now(), 847 Product: string(s.Product), 848 Collection: s.Collection, 849 Operation: s.Operation, 850 ParameterizedQuery: s.ParameterizedQuery, 851 QueryParameters: s.QueryParameters, 852 Host: s.Host, 853 PortPathOrID: s.PortPathOrID, 854 Database: s.DatabaseName, 855 }) 856 } 857 858 func externalSegmentMethod(s *ExternalSegment) string { 859 if "" != s.Procedure { 860 return s.Procedure 861 } 862 r := s.Request 863 if nil != s.Response && nil != s.Response.Request { 864 r = s.Response.Request 865 } 866 867 if nil != r { 868 if "" != r.Method { 869 return r.Method 870 } 871 // Golang's http package states that when a client's Request has 872 // an empty string for Method, the method is GET. 873 return "GET" 874 } 875 876 return "" 877 } 878 879 func externalSegmentURL(s *ExternalSegment) (*url.URL, error) { 880 if "" != s.URL { 881 return url.Parse(s.URL) 882 } 883 r := s.Request 884 if nil != s.Response && nil != s.Response.Request { 885 r = s.Response.Request 886 } 887 if r != nil { 888 return r.URL, nil 889 } 890 return nil, nil 891 } 892 893 func endExternal(s *ExternalSegment) error { 894 if nil == s { 895 return nil 896 } 897 thd := s.StartTime.thread 898 if nil == thd { 899 return nil 900 } 901 txn := thd.txn 902 txn.Lock() 903 defer txn.Unlock() 904 905 if txn.finished { 906 return errAlreadyEnded 907 } 908 u, err := externalSegmentURL(s) 909 if nil != err { 910 return err 911 } 912 return internal.EndExternalSegment(internal.EndExternalParams{ 913 TxnData: &txn.TxnData, 914 Thread: thd.thread, 915 Start: s.StartTime.start, 916 Now: time.Now(), 917 Logger: txn.Config.Logger, 918 Response: s.Response, 919 URL: u, 920 Host: s.Host, 921 Library: s.Library, 922 Method: externalSegmentMethod(s), 923 }) 924 } 925 926 func endMessage(s *MessageProducerSegment) error { 927 if nil == s { 928 return nil 929 } 930 thd := s.StartTime.thread 931 if nil == thd { 932 return nil 933 } 934 txn := thd.txn 935 txn.Lock() 936 defer txn.Unlock() 937 938 if txn.finished { 939 return errAlreadyEnded 940 } 941 942 if "" == s.DestinationType { 943 s.DestinationType = MessageQueue 944 } 945 946 return internal.EndMessageSegment(internal.EndMessageParams{ 947 TxnData: &txn.TxnData, 948 Thread: thd.thread, 949 Start: s.StartTime.start, 950 Now: time.Now(), 951 Library: s.Library, 952 Logger: txn.Config.Logger, 953 DestinationName: s.DestinationName, 954 DestinationType: string(s.DestinationType), 955 DestinationTemp: s.DestinationTemporary, 956 }) 957 } 958 959 // oldCATOutboundHeaders generates the Old CAT and Synthetics headers, depending 960 // on whether Old CAT is enabled or any Synthetics functionality has been 961 // triggered in the agent. 962 func oldCATOutboundHeaders(txn *txn) http.Header { 963 txn.Lock() 964 defer txn.Unlock() 965 966 if txn.finished { 967 return http.Header{} 968 } 969 970 metadata, err := txn.CrossProcess.CreateCrossProcessMetadata(txn.Name, txn.Config.AppName) 971 if err != nil { 972 txn.Config.Logger.Debug("error generating outbound headers", map[string]interface{}{ 973 "error": err, 974 }) 975 976 // It's possible for CreateCrossProcessMetadata() to error and still have a 977 // Synthetics header, so we'll still fall through to returning headers 978 // based on whatever metadata was returned. 979 } 980 981 return internal.MetadataToHTTPHeader(metadata) 982 } 983 984 func outboundHeaders(s *ExternalSegment) http.Header { 985 thd := s.StartTime.thread 986 987 if nil == thd { 988 return http.Header{} 989 } 990 txn := thd.txn 991 hdr := oldCATOutboundHeaders(txn) 992 993 // hdr may be empty, or it may contain headers. If DistributedTracer 994 // is enabled, add more to the existing hdr 995 if p := thd.CreateDistributedTracePayload().HTTPSafe(); "" != p { 996 hdr.Add(DistributedTracePayloadHeader, p) 997 return hdr 998 } 999 1000 return hdr 1001 } 1002 1003 const ( 1004 maxSampledDistributedPayloads = 35 1005 ) 1006 1007 type shimPayload struct{} 1008 1009 func (s shimPayload) Text() string { return "" } 1010 func (s shimPayload) HTTPSafe() string { return "" } 1011 1012 func (thd *thread) CreateDistributedTracePayload() (payload DistributedTracePayload) { 1013 payload = shimPayload{} 1014 1015 txn := thd.txn 1016 txn.Lock() 1017 defer txn.Unlock() 1018 1019 if !txn.BetterCAT.Enabled { 1020 return 1021 } 1022 1023 if txn.finished { 1024 txn.CreatePayloadException = true 1025 return 1026 } 1027 1028 if "" == txn.Reply.AccountID || "" == txn.Reply.TrustedAccountKey { 1029 // We can't create a payload: The application is not yet 1030 // connected or serverless distributed tracing configuration was 1031 // not provided. 1032 return 1033 } 1034 1035 txn.numPayloadsCreated++ 1036 1037 var p internal.Payload 1038 p.Type = internal.CallerType 1039 p.Account = txn.Reply.AccountID 1040 1041 p.App = txn.Reply.PrimaryAppID 1042 p.TracedID = txn.BetterCAT.TraceID() 1043 p.Priority = txn.BetterCAT.Priority 1044 p.Timestamp.Set(time.Now()) 1045 p.TransactionID = txn.BetterCAT.ID // Set the transaction ID to the transaction guid. 1046 1047 if txn.Reply.AccountID != txn.Reply.TrustedAccountKey { 1048 p.TrustedAccountKey = txn.Reply.TrustedAccountKey 1049 } 1050 1051 sampled := txn.lazilyCalculateSampled() 1052 if sampled && txn.SpanEventsEnabled { 1053 p.ID = txn.CurrentSpanIdentifier(thd.thread) 1054 } 1055 1056 // limit the number of outbound sampled=true payloads to prevent too 1057 // many downstream sampled events. 1058 p.SetSampled(false) 1059 if txn.numPayloadsCreated < maxSampledDistributedPayloads { 1060 p.SetSampled(sampled) 1061 } 1062 1063 txn.CreatePayloadSuccess = true 1064 1065 payload = p 1066 return 1067 } 1068 1069 var ( 1070 errOutboundPayloadCreated = errors.New("outbound payload already created") 1071 errAlreadyAccepted = errors.New("AcceptDistributedTracePayload has already been called") 1072 errInboundPayloadDTDisabled = errors.New("DistributedTracer must be enabled to accept an inbound payload") 1073 errTrustedAccountKey = errors.New("trusted account key missing or does not match") 1074 ) 1075 1076 func (txn *txn) AcceptDistributedTracePayload(t TransportType, p interface{}) error { 1077 txn.Lock() 1078 defer txn.Unlock() 1079 1080 return txn.acceptDistributedTracePayloadLocked(t, p) 1081 } 1082 1083 func (txn *txn) acceptDistributedTracePayloadLocked(t TransportType, p interface{}) error { 1084 1085 if !txn.BetterCAT.Enabled { 1086 return errInboundPayloadDTDisabled 1087 } 1088 1089 if txn.finished { 1090 txn.AcceptPayloadException = true 1091 return errAlreadyEnded 1092 } 1093 1094 if txn.numPayloadsCreated > 0 { 1095 txn.AcceptPayloadCreateBeforeAccept = true 1096 return errOutboundPayloadCreated 1097 } 1098 1099 if txn.BetterCAT.Inbound != nil { 1100 txn.AcceptPayloadIgnoredMultiple = true 1101 return errAlreadyAccepted 1102 } 1103 1104 if nil == p { 1105 txn.AcceptPayloadNullPayload = true 1106 return nil 1107 } 1108 1109 if "" == txn.Reply.AccountID || "" == txn.Reply.TrustedAccountKey { 1110 // We can't accept a payload: The application is not yet 1111 // connected or serverless distributed tracing configuration was 1112 // not provided. 1113 return nil 1114 } 1115 1116 payload, err := internal.AcceptPayload(p) 1117 if nil != err { 1118 if _, ok := err.(internal.ErrPayloadParse); ok { 1119 txn.AcceptPayloadParseException = true 1120 } else if _, ok := err.(internal.ErrUnsupportedPayloadVersion); ok { 1121 txn.AcceptPayloadIgnoredVersion = true 1122 } else if _, ok := err.(internal.ErrPayloadMissingField); ok { 1123 txn.AcceptPayloadParseException = true 1124 } else { 1125 txn.AcceptPayloadException = true 1126 } 1127 return err 1128 } 1129 1130 if nil == payload { 1131 return nil 1132 } 1133 1134 // now that we have a parsed and alloc'd payload, 1135 // let's make sure it has the correct fields 1136 if err := payload.IsValid(); nil != err { 1137 txn.AcceptPayloadParseException = true 1138 return err 1139 } 1140 1141 // and let's also do our trustedKey check 1142 receivedTrustKey := payload.TrustedAccountKey 1143 if "" == receivedTrustKey { 1144 receivedTrustKey = payload.Account 1145 } 1146 if receivedTrustKey != txn.Reply.TrustedAccountKey { 1147 txn.AcceptPayloadUntrustedAccount = true 1148 return errTrustedAccountKey 1149 } 1150 1151 if 0 != payload.Priority { 1152 txn.BetterCAT.Priority = payload.Priority 1153 } 1154 1155 // a nul payload.Sampled means the a field wasn't provided 1156 if nil != payload.Sampled { 1157 txn.BetterCAT.Sampled = *payload.Sampled 1158 txn.sampledCalculated = true 1159 } 1160 1161 txn.BetterCAT.Inbound = payload 1162 1163 // TransportType's name field is not mutable outside of its package 1164 // so the only check needed is if the caller is using an empty TransportType 1165 txn.BetterCAT.Inbound.TransportType = t.name 1166 if t.name == "" { 1167 txn.BetterCAT.Inbound.TransportType = TransportUnknown.name 1168 txn.Config.Logger.Debug("Invalid transport type, defaulting to Unknown", map[string]interface{}{}) 1169 } 1170 1171 if tm := payload.Timestamp.Time(); txn.Start.After(tm) { 1172 txn.BetterCAT.Inbound.TransportDuration = txn.Start.Sub(tm) 1173 } 1174 1175 txn.AcceptPayloadSuccess = true 1176 1177 return nil 1178 } 1179 1180 func (txn *txn) Application() Application { 1181 return txn.app 1182 } 1183 1184 func (thd *thread) AddAgentSpanAttribute(key internal.SpanAttribute, val string) { 1185 thd.thread.AddAgentSpanAttribute(key, val) 1186 } 1187 1188 var ( 1189 // Ensure that txn implements AddAgentAttributer to avoid breaking 1190 // integration package type assertions. 1191 _ internal.AddAgentAttributer = &txn{} 1192 ) 1193 1194 func (txn *txn) AddAgentAttribute(id internal.AgentAttributeID, stringVal string, otherVal interface{}) { 1195 txn.Lock() 1196 defer txn.Unlock() 1197 1198 if txn.finished { 1199 return 1200 } 1201 txn.Attrs.Agent.Add(id, stringVal, otherVal) 1202 } 1203 1204 func (thd *thread) GetTraceMetadata() (metadata TraceMetadata) { 1205 txn := thd.txn 1206 txn.Lock() 1207 defer txn.Unlock() 1208 1209 if txn.finished { 1210 return 1211 } 1212 1213 if txn.BetterCAT.Enabled { 1214 metadata.TraceID = txn.BetterCAT.TraceID() 1215 if txn.SpanEventsEnabled && txn.lazilyCalculateSampled() { 1216 metadata.SpanID = txn.CurrentSpanIdentifier(thd.thread) 1217 } 1218 } 1219 1220 return 1221 } 1222 1223 func (thd *thread) GetLinkingMetadata() (metadata LinkingMetadata) { 1224 txn := thd.txn 1225 metadata.EntityName = txn.appRun.firstAppName 1226 metadata.EntityType = "SERVICE" 1227 metadata.EntityGUID = txn.appRun.Reply.EntityGUID 1228 metadata.Hostname = internal.ThisHost 1229 1230 md := thd.GetTraceMetadata() 1231 metadata.TraceID = md.TraceID 1232 metadata.SpanID = md.SpanID 1233 1234 return 1235 } 1236 1237 func (txn *txn) IsSampled() bool { 1238 txn.Lock() 1239 defer txn.Unlock() 1240 1241 if txn.finished { 1242 return false 1243 } 1244 1245 return txn.lazilyCalculateSampled() 1246 }