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 }