github.com/lulzWill/go-agent@v2.1.2+incompatible/internal/attributes.go (about)

     1  package internal
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"net/http"
     7  	"sort"
     8  	"strconv"
     9  	"strings"
    10  )
    11  
    12  // New agent attributes must be added in the following places:
    13  // * Constants here.
    14  // * Top level attributes.go file.
    15  // * agentAttributes
    16  // * agentAttributeDests
    17  // * calculateAgentAttributeDests
    18  // * writeAgentAttributes
    19  const (
    20  	responseCode          = "httpResponseCode"
    21  	requestMethod         = "request.method"
    22  	requestAccept         = "request.headers.accept"
    23  	requestContentType    = "request.headers.contentType"
    24  	requestContentLength  = "request.headers.contentLength"
    25  	requestHost           = "request.headers.host"
    26  	responseContentType   = "response.headers.contentType"
    27  	responseContentLength = "response.headers.contentLength"
    28  	hostDisplayName       = "host.displayName"
    29  	requestUserAgent      = "request.headers.User-Agent"
    30  	requestReferer        = "request.headers.referer"
    31  )
    32  
    33  // https://source.datanerd.us/agents/agent-specs/blob/master/Agent-Attributes-PORTED.md
    34  
    35  // AttributeDestinationConfig matches newrelic.AttributeDestinationConfig to
    36  // avoid circular dependency issues.
    37  type AttributeDestinationConfig struct {
    38  	Enabled bool
    39  	Include []string
    40  	Exclude []string
    41  }
    42  
    43  type destinationSet int
    44  
    45  const (
    46  	destTxnEvent destinationSet = 1 << iota
    47  	destError
    48  	destTxnTrace
    49  	destBrowser
    50  )
    51  
    52  const (
    53  	destNone destinationSet = 0
    54  	// DestAll contains all destinations.
    55  	DestAll destinationSet = destTxnEvent | destTxnTrace | destError | destBrowser
    56  )
    57  
    58  const (
    59  	attributeWildcardSuffix = '*'
    60  )
    61  
    62  type attributeModifier struct {
    63  	match string // This will not contain a trailing '*'.
    64  	includeExclude
    65  }
    66  
    67  type byMatch []*attributeModifier
    68  
    69  func (m byMatch) Len() int           { return len(m) }
    70  func (m byMatch) Swap(i, j int)      { m[i], m[j] = m[j], m[i] }
    71  func (m byMatch) Less(i, j int) bool { return m[i].match < m[j].match }
    72  
    73  // AttributeConfig is created at connect and shared between all transactions.
    74  type AttributeConfig struct {
    75  	disabledDestinations destinationSet
    76  	exactMatchModifiers  map[string]*attributeModifier
    77  	// Once attributeConfig is constructed, wildcardModifiers is sorted in
    78  	// lexicographical order.  Modifiers appearing later have precedence
    79  	// over modifiers appearing earlier.
    80  	wildcardModifiers []*attributeModifier
    81  	agentDests        agentAttributeDests
    82  }
    83  
    84  type includeExclude struct {
    85  	include destinationSet
    86  	exclude destinationSet
    87  }
    88  
    89  func modifierApply(m *attributeModifier, d destinationSet) destinationSet {
    90  	// Include before exclude, since exclude has priority.
    91  	d |= m.include
    92  	d &^= m.exclude
    93  	return d
    94  }
    95  
    96  func applyAttributeConfig(c *AttributeConfig, key string, d destinationSet) destinationSet {
    97  	// Important: The wildcard modifiers must be applied before the exact
    98  	// match modifiers, and the slice must be iterated in a forward
    99  	// direction.
   100  	for _, m := range c.wildcardModifiers {
   101  		if strings.HasPrefix(key, m.match) {
   102  			d = modifierApply(m, d)
   103  		}
   104  	}
   105  
   106  	if m, ok := c.exactMatchModifiers[key]; ok {
   107  		d = modifierApply(m, d)
   108  	}
   109  
   110  	d &^= c.disabledDestinations
   111  
   112  	return d
   113  }
   114  
   115  func addModifier(c *AttributeConfig, match string, d includeExclude) {
   116  	if "" == match {
   117  		return
   118  	}
   119  	exactMatch := true
   120  	if attributeWildcardSuffix == match[len(match)-1] {
   121  		exactMatch = false
   122  		match = match[0 : len(match)-1]
   123  	}
   124  	mod := &attributeModifier{
   125  		match:          match,
   126  		includeExclude: d,
   127  	}
   128  
   129  	if exactMatch {
   130  		if m, ok := c.exactMatchModifiers[mod.match]; ok {
   131  			m.include |= mod.include
   132  			m.exclude |= mod.exclude
   133  		} else {
   134  			c.exactMatchModifiers[mod.match] = mod
   135  		}
   136  	} else {
   137  		for _, m := range c.wildcardModifiers {
   138  			// Important: Duplicate entries for the same match
   139  			// string would not work because exclude needs
   140  			// precedence over include.
   141  			if m.match == mod.match {
   142  				m.include |= mod.include
   143  				m.exclude |= mod.exclude
   144  				return
   145  			}
   146  		}
   147  		c.wildcardModifiers = append(c.wildcardModifiers, mod)
   148  	}
   149  }
   150  
   151  func processDest(c *AttributeConfig, includeEnabled bool, dc *AttributeDestinationConfig, d destinationSet) {
   152  	if !dc.Enabled {
   153  		c.disabledDestinations |= d
   154  	}
   155  	if includeEnabled {
   156  		for _, match := range dc.Include {
   157  			addModifier(c, match, includeExclude{include: d})
   158  		}
   159  	}
   160  	for _, match := range dc.Exclude {
   161  		addModifier(c, match, includeExclude{exclude: d})
   162  	}
   163  }
   164  
   165  // AttributeConfigInput is used as the input to CreateAttributeConfig:  it
   166  // transforms newrelic.Config settings into an AttributeConfig.
   167  type AttributeConfigInput struct {
   168  	Attributes        AttributeDestinationConfig
   169  	ErrorCollector    AttributeDestinationConfig
   170  	TransactionEvents AttributeDestinationConfig
   171  	browserMonitoring AttributeDestinationConfig
   172  	TransactionTracer AttributeDestinationConfig
   173  }
   174  
   175  var (
   176  	sampleAttributeConfigInput = AttributeConfigInput{
   177  		Attributes:        AttributeDestinationConfig{Enabled: true},
   178  		ErrorCollector:    AttributeDestinationConfig{Enabled: true},
   179  		TransactionEvents: AttributeDestinationConfig{Enabled: true},
   180  		TransactionTracer: AttributeDestinationConfig{Enabled: true},
   181  	}
   182  )
   183  
   184  // CreateAttributeConfig creates a new AttributeConfig.
   185  func CreateAttributeConfig(input AttributeConfigInput, includeEnabled bool) *AttributeConfig {
   186  	c := &AttributeConfig{
   187  		exactMatchModifiers: make(map[string]*attributeModifier),
   188  		wildcardModifiers:   make([]*attributeModifier, 0, 64),
   189  	}
   190  
   191  	processDest(c, includeEnabled, &input.Attributes, DestAll)
   192  	processDest(c, includeEnabled, &input.ErrorCollector, destError)
   193  	processDest(c, includeEnabled, &input.TransactionEvents, destTxnEvent)
   194  	processDest(c, includeEnabled, &input.TransactionTracer, destTxnTrace)
   195  	processDest(c, includeEnabled, &input.browserMonitoring, destBrowser)
   196  
   197  	sort.Sort(byMatch(c.wildcardModifiers))
   198  
   199  	c.agentDests = calculateAgentAttributeDests(c)
   200  
   201  	return c
   202  }
   203  
   204  type userAttribute struct {
   205  	value interface{}
   206  	dests destinationSet
   207  }
   208  
   209  // Attributes are key value pairs attached to the various collected data types.
   210  type Attributes struct {
   211  	config *AttributeConfig
   212  	user   map[string]userAttribute
   213  	Agent  agentAttributes
   214  }
   215  
   216  type agentAttributes struct {
   217  	HostDisplayName              string
   218  	RequestMethod                string
   219  	RequestAcceptHeader          string
   220  	RequestContentType           string
   221  	RequestContentLength         int
   222  	RequestHeadersHost           string
   223  	RequestHeadersUserAgent      string
   224  	RequestHeadersReferer        string
   225  	ResponseHeadersContentType   string
   226  	ResponseHeadersContentLength int
   227  	ResponseCode                 string
   228  }
   229  
   230  type agentAttributeDests struct {
   231  	HostDisplayName              destinationSet
   232  	RequestMethod                destinationSet
   233  	RequestAcceptHeader          destinationSet
   234  	RequestContentType           destinationSet
   235  	RequestContentLength         destinationSet
   236  	RequestHeadersHost           destinationSet
   237  	RequestHeadersUserAgent      destinationSet
   238  	RequestHeadersReferer        destinationSet
   239  	ResponseHeadersContentType   destinationSet
   240  	ResponseHeadersContentLength destinationSet
   241  	ResponseCode                 destinationSet
   242  }
   243  
   244  func calculateAgentAttributeDests(c *AttributeConfig) agentAttributeDests {
   245  	usual := DestAll &^ destBrowser
   246  	traces := destTxnTrace | destError
   247  	return agentAttributeDests{
   248  		HostDisplayName:              applyAttributeConfig(c, hostDisplayName, usual),
   249  		RequestMethod:                applyAttributeConfig(c, requestMethod, usual),
   250  		RequestAcceptHeader:          applyAttributeConfig(c, requestAccept, usual),
   251  		RequestContentType:           applyAttributeConfig(c, requestContentType, usual),
   252  		RequestContentLength:         applyAttributeConfig(c, requestContentLength, usual),
   253  		RequestHeadersHost:           applyAttributeConfig(c, requestHost, usual),
   254  		RequestHeadersUserAgent:      applyAttributeConfig(c, requestUserAgent, traces),
   255  		RequestHeadersReferer:        applyAttributeConfig(c, requestReferer, traces),
   256  		ResponseHeadersContentType:   applyAttributeConfig(c, responseContentType, usual),
   257  		ResponseHeadersContentLength: applyAttributeConfig(c, responseContentLength, usual),
   258  		ResponseCode:                 applyAttributeConfig(c, responseCode, usual),
   259  	}
   260  }
   261  
   262  type agentAttributeWriter struct {
   263  	jsonFieldsWriter
   264  	d destinationSet
   265  }
   266  
   267  func (w *agentAttributeWriter) writeString(name string, val string, d destinationSet) {
   268  	if "" != val && 0 != w.d&d {
   269  		w.stringField(name, truncateStringValueIfLong(val))
   270  	}
   271  }
   272  
   273  func (w *agentAttributeWriter) writeInt(name string, val int, d destinationSet) {
   274  	if val >= 0 && 0 != w.d&d {
   275  		w.intField(name, int64(val))
   276  	}
   277  }
   278  
   279  func writeAgentAttributes(buf *bytes.Buffer, d destinationSet, values agentAttributes, dests agentAttributeDests) {
   280  	w := &agentAttributeWriter{
   281  		jsonFieldsWriter: jsonFieldsWriter{buf: buf},
   282  		d:                d,
   283  	}
   284  	buf.WriteByte('{')
   285  	w.writeString(hostDisplayName, values.HostDisplayName, dests.HostDisplayName)
   286  	w.writeString(requestMethod, values.RequestMethod, dests.RequestMethod)
   287  	w.writeString(requestAccept, values.RequestAcceptHeader, dests.RequestAcceptHeader)
   288  	w.writeString(requestContentType, values.RequestContentType, dests.RequestContentType)
   289  	w.writeInt(requestContentLength, values.RequestContentLength, dests.RequestContentLength)
   290  	w.writeString(requestHost, values.RequestHeadersHost, dests.RequestHeadersHost)
   291  	w.writeString(requestUserAgent, values.RequestHeadersUserAgent, dests.RequestHeadersUserAgent)
   292  	w.writeString(requestReferer, values.RequestHeadersReferer, dests.RequestHeadersReferer)
   293  	w.writeString(responseContentType, values.ResponseHeadersContentType, dests.ResponseHeadersContentType)
   294  	w.writeInt(responseContentLength, values.ResponseHeadersContentLength, dests.ResponseHeadersContentLength)
   295  	w.writeString(responseCode, values.ResponseCode, dests.ResponseCode)
   296  	buf.WriteByte('}')
   297  }
   298  
   299  // NewAttributes creates a new Attributes.
   300  func NewAttributes(config *AttributeConfig) *Attributes {
   301  	return &Attributes{
   302  		config: config,
   303  		Agent: agentAttributes{
   304  			RequestContentLength:         -1,
   305  			ResponseHeadersContentLength: -1,
   306  		},
   307  	}
   308  }
   309  
   310  // ErrInvalidAttributeType is returned when the value is not valid.
   311  type ErrInvalidAttributeType struct {
   312  	key string
   313  	val interface{}
   314  }
   315  
   316  func (e ErrInvalidAttributeType) Error() string {
   317  	return fmt.Sprintf("attribute '%s' value of type %T is invalid", e.key, e.val)
   318  }
   319  
   320  type invalidAttributeKeyErr struct{ key string }
   321  
   322  func (e invalidAttributeKeyErr) Error() string {
   323  	return fmt.Sprintf("attribute key '%.32s...' exceeds length limit %d",
   324  		e.key, attributeKeyLengthLimit)
   325  }
   326  
   327  type userAttributeLimitErr struct{ key string }
   328  
   329  func (e userAttributeLimitErr) Error() string {
   330  	return fmt.Sprintf("attribute '%s' discarded: limit of %d reached", e.key,
   331  		attributeUserLimit)
   332  }
   333  
   334  func truncateStringValueIfLong(val string) string {
   335  	if len(val) > attributeValueLengthLimit {
   336  		return StringLengthByteLimit(val, attributeValueLengthLimit)
   337  	}
   338  	return val
   339  }
   340  
   341  // ValidateUserAttribute validates a user attribute.
   342  func ValidateUserAttribute(key string, val interface{}) (interface{}, error) {
   343  	if str, ok := val.(string); ok {
   344  		val = interface{}(truncateStringValueIfLong(str))
   345  	}
   346  
   347  	switch val.(type) {
   348  	case string, bool, nil,
   349  		uint8, uint16, uint32, uint64, int8, int16, int32, int64,
   350  		float32, float64, uint, int, uintptr:
   351  	default:
   352  		return nil, ErrInvalidAttributeType{
   353  			key: key,
   354  			val: val,
   355  		}
   356  	}
   357  
   358  	// Attributes whose keys are excessively long are dropped rather than
   359  	// truncated to avoid worrying about the application of configuration to
   360  	// truncated values or performing the truncation after configuration.
   361  	if len(key) > attributeKeyLengthLimit {
   362  		return nil, invalidAttributeKeyErr{key: key}
   363  	}
   364  	return val, nil
   365  }
   366  
   367  // AddUserAttribute adds a user attribute.
   368  func AddUserAttribute(a *Attributes, key string, val interface{}, d destinationSet) error {
   369  	val, err := ValidateUserAttribute(key, val)
   370  	if nil != err {
   371  		return err
   372  	}
   373  	dests := applyAttributeConfig(a.config, key, d)
   374  	if destNone == dests {
   375  		return nil
   376  	}
   377  	if nil == a.user {
   378  		a.user = make(map[string]userAttribute)
   379  	}
   380  
   381  	if _, exists := a.user[key]; !exists && len(a.user) >= attributeUserLimit {
   382  		return userAttributeLimitErr{key}
   383  	}
   384  
   385  	// Note: Duplicates are overridden: last attribute in wins.
   386  	a.user[key] = userAttribute{
   387  		value: val,
   388  		dests: dests,
   389  	}
   390  	return nil
   391  }
   392  
   393  func writeAttributeValueJSON(w *jsonFieldsWriter, key string, val interface{}) {
   394  	switch v := val.(type) {
   395  	case nil:
   396  		w.rawField(key, `null`)
   397  	case string:
   398  		w.stringField(key, v)
   399  	case bool:
   400  		if v {
   401  			w.rawField(key, `true`)
   402  		} else {
   403  			w.rawField(key, `false`)
   404  		}
   405  	case uint8:
   406  		w.intField(key, int64(v))
   407  	case uint16:
   408  		w.intField(key, int64(v))
   409  	case uint32:
   410  		w.intField(key, int64(v))
   411  	case uint64:
   412  		w.intField(key, int64(v))
   413  	case uint:
   414  		w.intField(key, int64(v))
   415  	case uintptr:
   416  		w.intField(key, int64(v))
   417  	case int8:
   418  		w.intField(key, int64(v))
   419  	case int16:
   420  		w.intField(key, int64(v))
   421  	case int32:
   422  		w.intField(key, int64(v))
   423  	case int64:
   424  		w.intField(key, v)
   425  	case int:
   426  		w.intField(key, int64(v))
   427  	case float32:
   428  		w.floatField(key, float64(v))
   429  	case float64:
   430  		w.floatField(key, v)
   431  	default:
   432  		w.stringField(key, fmt.Sprintf("%T", v))
   433  	}
   434  }
   435  
   436  func agentAttributesJSON(a *Attributes, buf *bytes.Buffer, d destinationSet) {
   437  	if nil == a {
   438  		buf.WriteString("{}")
   439  		return
   440  	}
   441  	writeAgentAttributes(buf, d, a.Agent, a.config.agentDests)
   442  }
   443  
   444  func userAttributesJSON(a *Attributes, buf *bytes.Buffer, d destinationSet, extraAttributes map[string]interface{}) {
   445  	buf.WriteByte('{')
   446  	if nil != a {
   447  		w := jsonFieldsWriter{buf: buf}
   448  		for key, val := range extraAttributes {
   449  			outputDest := applyAttributeConfig(a.config, key, d)
   450  			if 0 != outputDest&d {
   451  				writeAttributeValueJSON(&w, key, val)
   452  			}
   453  		}
   454  		for name, atr := range a.user {
   455  			if 0 != atr.dests&d {
   456  				if _, found := extraAttributes[name]; found {
   457  					continue
   458  				}
   459  				writeAttributeValueJSON(&w, name, atr.value)
   460  			}
   461  		}
   462  	}
   463  	buf.WriteByte('}')
   464  }
   465  
   466  // userAttributesStringJSON is only used for testing.
   467  func userAttributesStringJSON(a *Attributes, d destinationSet, extraAttributes map[string]interface{}) string {
   468  	estimate := len(a.user) * 128
   469  	buf := bytes.NewBuffer(make([]byte, 0, estimate))
   470  	userAttributesJSON(a, buf, d, extraAttributes)
   471  	return buf.String()
   472  }
   473  
   474  // RequestAgentAttributes gathers agent attributes out of the request.
   475  func RequestAgentAttributes(a *Attributes, r *http.Request) {
   476  	a.Agent.RequestMethod = r.Method
   477  
   478  	h := r.Header
   479  	if nil == h {
   480  		return
   481  	}
   482  	a.Agent.RequestAcceptHeader = h.Get("Accept")
   483  	a.Agent.RequestContentType = h.Get("Content-Type")
   484  	a.Agent.RequestHeadersHost = h.Get("Host")
   485  	a.Agent.RequestHeadersUserAgent = h.Get("User-Agent")
   486  	a.Agent.RequestHeadersReferer = SafeURLFromString(h.Get("Referer"))
   487  
   488  	// Per NewAttributes(), the default for this field is -1 (which is also what
   489  	// GetContentLengthFromHeader() returns if no content length is found), so we
   490  	// can just use the return value unconditionally.
   491  	a.Agent.RequestContentLength = int(GetContentLengthFromHeader(h))
   492  }
   493  
   494  // ResponseHeaderAttributes gather agent attributes from the response headers.
   495  func ResponseHeaderAttributes(a *Attributes, h http.Header) {
   496  	if nil == h {
   497  		return
   498  	}
   499  	a.Agent.ResponseHeadersContentType = h.Get("Content-Type")
   500  
   501  	// Per NewAttributes(), the default for this field is -1 (which is also what
   502  	// GetContentLengthFromHeader() returns if no content length is found), so we
   503  	// can just use the return value unconditionally.
   504  	a.Agent.ResponseHeadersContentLength = int(GetContentLengthFromHeader(h))
   505  }
   506  
   507  var (
   508  	// statusCodeLookup avoids a strconv.Itoa call.
   509  	statusCodeLookup = map[int]string{
   510  		100: "100", 101: "101",
   511  		200: "200", 201: "201", 202: "202", 203: "203", 204: "204", 205: "205", 206: "206",
   512  		300: "300", 301: "301", 302: "302", 303: "303", 304: "304", 305: "305", 307: "307",
   513  		400: "400", 401: "401", 402: "402", 403: "403", 404: "404", 405: "405", 406: "406",
   514  		407: "407", 408: "408", 409: "409", 410: "410", 411: "411", 412: "412", 413: "413",
   515  		414: "414", 415: "415", 416: "416", 417: "417", 418: "418", 428: "428", 429: "429",
   516  		431: "431", 451: "451",
   517  		500: "500", 501: "501", 502: "502", 503: "503", 504: "504", 505: "505", 511: "511",
   518  	}
   519  )
   520  
   521  // ResponseCodeAttribute sets the response code agent attribute.
   522  func ResponseCodeAttribute(a *Attributes, code int) {
   523  	a.Agent.ResponseCode = statusCodeLookup[code]
   524  	if a.Agent.ResponseCode == "" {
   525  		a.Agent.ResponseCode = strconv.Itoa(code)
   526  	}
   527  }