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  }