github.com/lulzWill/go-agent@v2.1.2+incompatible/internal_txn.go (about) 1 package newrelic 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 "net/url" 8 "reflect" 9 "sync" 10 "time" 11 12 "github.com/lulzWill/go-agent/internal" 13 ) 14 15 type txnInput struct { 16 W http.ResponseWriter 17 Config Config 18 Reply *internal.ConnectReply 19 Consumer dataConsumer 20 attrConfig *internal.AttributeConfig 21 } 22 23 type txn struct { 24 txnInput 25 // This mutex is required since the consumer may call the public API 26 // interface functions from different routines. 27 sync.Mutex 28 // finished indicates whether or not End() has been called. After 29 // finished has been set to true, no recording should occur. 30 finished bool 31 32 ignore bool 33 34 // wroteHeader prevents capturing multiple response code errors if the 35 // user erroneously calls WriteHeader multiple times. 36 wroteHeader bool 37 38 internal.TxnData 39 } 40 41 func newTxn(input txnInput, req *http.Request, name string) *txn { 42 txn := &txn{ 43 txnInput: input, 44 } 45 txn.Start = time.Now() 46 txn.Name = name 47 txn.IsWeb = nil != req 48 txn.Attrs = internal.NewAttributes(input.attrConfig) 49 if nil != req { 50 txn.Queuing = internal.QueueDuration(req.Header, txn.Start) 51 internal.RequestAgentAttributes(txn.Attrs, req) 52 } 53 txn.Attrs.Agent.HostDisplayName = txn.Config.HostDisplayName 54 txn.TxnTrace.Enabled = txn.txnTracesEnabled() 55 txn.TxnTrace.SegmentThreshold = txn.Config.TransactionTracer.SegmentThreshold 56 txn.StackTraceThreshold = txn.Config.TransactionTracer.StackTraceThreshold 57 txn.SlowQueriesEnabled = txn.slowQueriesEnabled() 58 txn.SlowQueryThreshold = txn.Config.DatastoreTracer.SlowQuery.Threshold 59 if nil != req && nil != req.URL { 60 txn.CleanURL = internal.SafeURL(req.URL) 61 } 62 txn.CrossProcess.InitFromHTTPRequest(txn.crossProcessEnabled(), input.Reply, req) 63 64 return txn 65 } 66 67 func (txn *txn) crossProcessEnabled() bool { 68 return txn.Config.CrossApplicationTracer.Enabled 69 } 70 71 func (txn *txn) slowQueriesEnabled() bool { 72 return txn.Config.DatastoreTracer.SlowQuery.Enabled && 73 txn.Reply.CollectTraces 74 } 75 76 func (txn *txn) txnTracesEnabled() bool { 77 return txn.Config.TransactionTracer.Enabled && 78 txn.Reply.CollectTraces 79 } 80 81 func (txn *txn) txnEventsEnabled() bool { 82 return txn.Config.TransactionEvents.Enabled && 83 txn.Reply.CollectAnalyticsEvents 84 } 85 86 func (txn *txn) errorEventsEnabled() bool { 87 return txn.Config.ErrorCollector.CaptureEvents && 88 txn.Reply.CollectErrorEvents 89 } 90 91 func (txn *txn) freezeName() { 92 if txn.ignore || ("" != txn.FinalName) { 93 return 94 } 95 96 txn.FinalName = internal.CreateFullTxnName(txn.Name, txn.Reply, txn.IsWeb) 97 if "" == txn.FinalName { 98 txn.ignore = true 99 } 100 } 101 102 func (txn *txn) getsApdex() bool { 103 return txn.IsWeb 104 } 105 106 func (txn *txn) txnTraceThreshold() time.Duration { 107 if txn.Config.TransactionTracer.Threshold.IsApdexFailing { 108 return internal.ApdexFailingThreshold(txn.ApdexThreshold) 109 } 110 return txn.Config.TransactionTracer.Threshold.Duration 111 } 112 113 func (txn *txn) shouldSaveTrace() bool { 114 return txn.CrossProcess.IsSynthetics() || 115 (txn.txnTracesEnabled() && (txn.Duration >= txn.txnTraceThreshold())) 116 } 117 118 func (txn *txn) MergeIntoHarvest(h *internal.Harvest) { 119 internal.CreateTxnMetrics(&txn.TxnData, h.Metrics) 120 internal.MergeBreakdownMetrics(&txn.TxnData, h.Metrics) 121 122 if txn.txnEventsEnabled() { 123 // Allocate a new TxnEvent to prevent a reference to the large transaction. 124 alloc := new(internal.TxnEvent) 125 *alloc = txn.TxnData.TxnEvent 126 h.TxnEvents.AddTxnEvent(alloc) 127 } 128 129 internal.MergeTxnErrors(&h.ErrorTraces, txn.Errors, txn.TxnEvent) 130 131 if txn.errorEventsEnabled() { 132 for _, e := range txn.Errors { 133 errEvent := &internal.ErrorEvent{ 134 ErrorData: *e, 135 TxnEvent: txn.TxnEvent, 136 } 137 // Since the stack trace is not used in error events, remove the reference 138 // to minimize memory. 139 errEvent.Stack = nil 140 h.ErrorEvents.Add(errEvent) 141 } 142 } 143 144 if txn.shouldSaveTrace() { 145 h.TxnTraces.Witness(internal.HarvestTrace{ 146 TxnEvent: txn.TxnEvent, 147 Trace: txn.TxnTrace, 148 }) 149 } 150 151 if nil != txn.SlowQueries { 152 h.SlowSQLs.Merge(txn.SlowQueries, txn.FinalName, txn.CleanURL) 153 } 154 } 155 156 func responseCodeIsError(cfg *Config, code int) bool { 157 if code < http.StatusBadRequest { // 400 158 return false 159 } 160 for _, ignoreCode := range cfg.ErrorCollector.IgnoreStatusCodes { 161 if code == ignoreCode { 162 return false 163 } 164 } 165 return true 166 } 167 168 func headersJustWritten(txn *txn, code int) { 169 txn.Lock() 170 defer txn.Unlock() 171 172 if txn.finished { 173 return 174 } 175 if txn.wroteHeader { 176 return 177 } 178 txn.wroteHeader = true 179 180 internal.ResponseHeaderAttributes(txn.Attrs, txn.W.Header()) 181 internal.ResponseCodeAttribute(txn.Attrs, code) 182 183 if responseCodeIsError(&txn.Config, code) { 184 e := internal.TxnErrorFromResponseCode(time.Now(), code) 185 e.Stack = internal.GetStackTrace(1) 186 txn.noticeErrorInternal(e) 187 } 188 } 189 190 func (txn *txn) responseHeader() http.Header { 191 txn.Lock() 192 defer txn.Unlock() 193 194 if txn.finished { 195 return nil 196 } 197 if txn.wroteHeader { 198 return nil 199 } 200 if !txn.CrossProcess.Enabled { 201 return nil 202 } 203 if !txn.CrossProcess.IsInbound() { 204 return nil 205 } 206 txn.freezeName() 207 contentLength := internal.GetContentLengthFromHeader(txn.W.Header()) 208 209 appData, err := txn.CrossProcess.CreateAppData(txn.FinalName, txn.Queuing, time.Since(txn.Start), contentLength) 210 if err != nil { 211 txn.Config.Logger.Debug("error generating outbound response header", map[string]interface{}{ 212 "error": err, 213 }) 214 return nil 215 } 216 return internal.AppDataToHTTPHeader(appData) 217 } 218 219 func addCrossProcessHeaders(txn *txn) { 220 // responseHeader() checks the wroteHeader field and returns a nil map if the 221 // header has been written, so we don't need a check here. 222 for key, values := range txn.responseHeader() { 223 for _, value := range values { 224 txn.W.Header().Add(key, value) 225 } 226 } 227 } 228 229 func (txn *txn) Header() http.Header { return txn.W.Header() } 230 231 func (txn *txn) Write(b []byte) (int, error) { 232 // This is safe to call unconditionally, even if Write() is called multiple 233 // times; see also the commentary in addCrossProcessHeaders(). 234 addCrossProcessHeaders(txn) 235 236 n, err := txn.W.Write(b) 237 238 headersJustWritten(txn, http.StatusOK) 239 240 return n, err 241 } 242 243 func (txn *txn) WriteHeader(code int) { 244 addCrossProcessHeaders(txn) 245 246 txn.W.WriteHeader(code) 247 248 headersJustWritten(txn, code) 249 } 250 251 func (txn *txn) End() error { 252 txn.Lock() 253 defer txn.Unlock() 254 255 if txn.finished { 256 return errAlreadyEnded 257 } 258 259 txn.finished = true 260 261 r := recover() 262 if nil != r { 263 e := internal.TxnErrorFromPanic(time.Now(), r) 264 e.Stack = internal.GetStackTrace(0) 265 txn.noticeErrorInternal(e) 266 } 267 268 txn.Stop = time.Now() 269 txn.Duration = txn.Stop.Sub(txn.Start) 270 if children := internal.TracerRootChildren(&txn.TxnData); txn.Duration > children { 271 txn.Exclusive = txn.Duration - children 272 } 273 274 txn.freezeName() 275 276 // Finalise the CAT state. 277 if err := txn.CrossProcess.Finalise(txn.Name, txn.Config.AppName); err != nil { 278 txn.Config.Logger.Debug("error finalising the cross process state", map[string]interface{}{ 279 "error": err, 280 }) 281 } 282 283 // Assign apdexThreshold regardless of whether or not the transaction 284 // gets apdex since it may be used to calculate the trace threshold. 285 txn.ApdexThreshold = internal.CalculateApdexThreshold(txn.Reply, txn.FinalName) 286 287 if txn.getsApdex() { 288 if txn.HasErrors() { 289 txn.Zone = internal.ApdexFailing 290 } else { 291 txn.Zone = internal.CalculateApdexZone(txn.ApdexThreshold, txn.Duration) 292 } 293 } else { 294 txn.Zone = internal.ApdexNone 295 } 296 297 if txn.Config.Logger.DebugEnabled() { 298 txn.Config.Logger.Debug("transaction ended", map[string]interface{}{ 299 "name": txn.FinalName, 300 "duration_ms": txn.Duration.Seconds() * 1000.0, 301 "ignored": txn.ignore, 302 "run": txn.Reply.RunID, 303 }) 304 } 305 306 if !txn.ignore { 307 txn.Consumer.Consume(txn.Reply.RunID, txn) 308 } 309 310 // Note that if a consumer uses `panic(nil)`, the panic will not 311 // propagate. 312 if nil != r { 313 panic(r) 314 } 315 316 return nil 317 } 318 319 func (txn *txn) AddAttribute(name string, value interface{}) error { 320 txn.Lock() 321 defer txn.Unlock() 322 323 if txn.Config.HighSecurity { 324 return errHighSecurityEnabled 325 } 326 327 if !txn.Reply.SecurityPolicies.CustomParameters.Enabled() { 328 return errSecurityPolicy 329 } 330 331 if txn.finished { 332 return errAlreadyEnded 333 } 334 335 return internal.AddUserAttribute(txn.Attrs, name, value, internal.DestAll) 336 } 337 338 var ( 339 errorsLocallyDisabled = errors.New("errors locally disabled") 340 errorsRemotelyDisabled = errors.New("errors remotely disabled") 341 errNilError = errors.New("nil error") 342 errAlreadyEnded = errors.New("transaction has already ended") 343 errSecurityPolicy = errors.New("disabled by security policy") 344 ) 345 346 const ( 347 highSecurityErrorMsg = "message removed by high security setting" 348 securityPolicyErrorMsg = "message removed by security policy" 349 ) 350 351 func (txn *txn) noticeErrorInternal(err internal.ErrorData) error { 352 if !txn.Config.ErrorCollector.Enabled { 353 return errorsLocallyDisabled 354 } 355 356 if !txn.Reply.CollectErrors { 357 return errorsRemotelyDisabled 358 } 359 360 if nil == txn.Errors { 361 txn.Errors = internal.NewTxnErrors(internal.MaxTxnErrors) 362 } 363 364 if txn.Config.HighSecurity { 365 err.Msg = highSecurityErrorMsg 366 } 367 368 if !txn.Reply.SecurityPolicies.AllowRawExceptionMessages.Enabled() { 369 err.Msg = securityPolicyErrorMsg 370 } 371 372 txn.Errors.Add(err) 373 374 return nil 375 } 376 377 var ( 378 errTooManyErrorAttributes = fmt.Errorf("too many extra attributes: limit is %d", 379 internal.AttributeErrorLimit) 380 ) 381 382 func (txn *txn) NoticeError(err error) error { 383 txn.Lock() 384 defer txn.Unlock() 385 386 if txn.finished { 387 return errAlreadyEnded 388 } 389 390 if nil == err { 391 return errNilError 392 } 393 394 e := internal.ErrorData{ 395 When: time.Now(), 396 Msg: err.Error(), 397 } 398 if ec, ok := err.(ErrorClasser); ok { 399 e.Klass = ec.ErrorClass() 400 } 401 if "" == e.Klass { 402 e.Klass = reflect.TypeOf(err).String() 403 } 404 if st, ok := err.(StackTracer); ok { 405 e.Stack = st.StackTrace() 406 // Note that if the provided stack trace is excessive in length, 407 // it will be truncated during JSON creation. 408 } 409 if nil == e.Stack { 410 e.Stack = internal.GetStackTrace(2) 411 } 412 413 if ea, ok := err.(ErrorAttributer); ok && !txn.Config.HighSecurity && txn.Reply.SecurityPolicies.CustomParameters.Enabled() { 414 unvetted := ea.ErrorAttributes() 415 if len(unvetted) > internal.AttributeErrorLimit { 416 return errTooManyErrorAttributes 417 } 418 419 e.ExtraAttributes = make(map[string]interface{}) 420 for key, val := range unvetted { 421 val, errr := internal.ValidateUserAttribute(key, val) 422 if nil != errr { 423 return errr 424 } 425 e.ExtraAttributes[key] = val 426 } 427 } 428 429 return txn.noticeErrorInternal(e) 430 } 431 432 func (txn *txn) SetName(name string) error { 433 txn.Lock() 434 defer txn.Unlock() 435 436 if txn.finished { 437 return errAlreadyEnded 438 } 439 440 txn.Name = name 441 return nil 442 } 443 444 func (txn *txn) Ignore() error { 445 txn.Lock() 446 defer txn.Unlock() 447 448 if txn.finished { 449 return errAlreadyEnded 450 } 451 txn.ignore = true 452 return nil 453 } 454 455 func (txn *txn) StartSegmentNow() SegmentStartTime { 456 var s internal.SegmentStartTime 457 txn.Lock() 458 if !txn.finished { 459 s = internal.StartSegment(&txn.TxnData, time.Now()) 460 } 461 txn.Unlock() 462 return SegmentStartTime{ 463 segment: segment{ 464 start: s, 465 txn: txn, 466 }, 467 } 468 } 469 470 type segment struct { 471 start internal.SegmentStartTime 472 txn *txn 473 } 474 475 func endSegment(s *Segment) error { 476 txn := s.StartTime.txn 477 if nil == txn { 478 return nil 479 } 480 var err error 481 txn.Lock() 482 if txn.finished { 483 err = errAlreadyEnded 484 } else { 485 err = internal.EndBasicSegment(&txn.TxnData, s.StartTime.start, time.Now(), s.Name) 486 } 487 txn.Unlock() 488 return err 489 } 490 491 func endDatastore(s *DatastoreSegment) error { 492 txn := s.StartTime.txn 493 if nil == txn { 494 return nil 495 } 496 txn.Lock() 497 defer txn.Unlock() 498 499 if txn.finished { 500 return errAlreadyEnded 501 } 502 if txn.Config.HighSecurity { 503 s.QueryParameters = nil 504 } 505 if !txn.Config.DatastoreTracer.QueryParameters.Enabled { 506 s.QueryParameters = nil 507 } 508 if txn.Reply.SecurityPolicies.RecordSQL.IsSet() { 509 s.QueryParameters = nil 510 if !txn.Reply.SecurityPolicies.RecordSQL.Enabled() { 511 s.ParameterizedQuery = "" 512 } 513 } 514 if !txn.Config.DatastoreTracer.DatabaseNameReporting.Enabled { 515 s.DatabaseName = "" 516 } 517 if !txn.Config.DatastoreTracer.InstanceReporting.Enabled { 518 s.Host = "" 519 s.PortPathOrID = "" 520 } 521 return internal.EndDatastoreSegment(internal.EndDatastoreParams{ 522 Tracer: &txn.TxnData, 523 Start: s.StartTime.start, 524 Now: time.Now(), 525 Product: string(s.Product), 526 Collection: s.Collection, 527 Operation: s.Operation, 528 ParameterizedQuery: s.ParameterizedQuery, 529 QueryParameters: s.QueryParameters, 530 Host: s.Host, 531 PortPathOrID: s.PortPathOrID, 532 Database: s.DatabaseName, 533 }) 534 } 535 536 func externalSegmentURL(s *ExternalSegment) (*url.URL, error) { 537 if "" != s.URL { 538 return url.Parse(s.URL) 539 } 540 r := s.Request 541 if nil != s.Response && nil != s.Response.Request { 542 r = s.Response.Request 543 } 544 if r != nil { 545 return r.URL, nil 546 } 547 return nil, nil 548 } 549 550 func endExternal(s *ExternalSegment) error { 551 txn := s.StartTime.txn 552 if nil == txn { 553 return nil 554 } 555 txn.Lock() 556 defer txn.Unlock() 557 558 if txn.finished { 559 return errAlreadyEnded 560 } 561 u, err := externalSegmentURL(s) 562 if nil != err { 563 return err 564 } 565 return internal.EndExternalSegment(&txn.TxnData, s.StartTime.start, time.Now(), u, s.Response) 566 } 567 568 func outboundHeaders(s *ExternalSegment) http.Header { 569 txn := s.StartTime.txn 570 if nil == txn { 571 return http.Header{} 572 } 573 txn.Lock() 574 defer txn.Unlock() 575 576 if txn.finished { 577 return http.Header{} 578 } 579 580 metadata, err := txn.CrossProcess.CreateCrossProcessMetadata(txn.Name, txn.Config.AppName) 581 if err != nil { 582 txn.Config.Logger.Debug("error generating outbound headers", map[string]interface{}{ 583 "error": err, 584 }) 585 586 // It's possible for CreateCrossProcessMetadata() to error and still have a 587 // Synthetics header, so we'll still fall through to returning headers 588 // based on whatever metadata was returned. 589 } 590 591 return internal.MetadataToHTTPHeader(metadata) 592 }