github.com/newrelic/go-agent@v3.26.0+incompatible/internal/attributes.go (about) 1 // Copyright 2020 New Relic Corporation. All rights reserved. 2 // SPDX-License-Identifier: Apache-2.0 3 4 package internal 5 6 import ( 7 "bytes" 8 "fmt" 9 "net/http" 10 "net/url" 11 "sort" 12 "strconv" 13 "strings" 14 ) 15 16 // AgentAttributeID uniquely identifies each agent attribute. 17 type AgentAttributeID int 18 19 // New agent attributes must be added in the following places: 20 // * Constants here. 21 // * Top level attributes.go file. 22 // * agentAttributeInfo 23 const ( 24 AttributeHostDisplayName AgentAttributeID = iota 25 attributeRequestMethod 26 attributeRequestAcceptHeader 27 attributeRequestContentType 28 attributeRequestContentLength 29 attributeRequestHeadersHost 30 attributeRequestHeadersUserAgent 31 attributeRequestHeadersReferer 32 attributeRequestURI 33 attributeResponseHeadersContentType 34 attributeResponseHeadersContentLength 35 attributeResponseCode 36 AttributeAWSRequestID 37 AttributeAWSLambdaARN 38 AttributeAWSLambdaColdStart 39 AttributeAWSLambdaEventSourceARN 40 AttributeMessageRoutingKey 41 AttributeMessageQueueName 42 AttributeMessageExchangeType 43 AttributeMessageReplyTo 44 AttributeMessageCorrelationID 45 ) 46 47 // SpanAttribute is an attribute put in span events. 48 type SpanAttribute string 49 50 // AddAgentSpanAttributer should be implemented by the Transaction. 51 type AddAgentSpanAttributer interface { 52 AddAgentSpanAttribute(key SpanAttribute, val string) 53 } 54 55 // AddAgentSpanAttribute allows instrumentation packages to add span attributes. 56 func AddAgentSpanAttribute(txn interface{}, key SpanAttribute, val string) { 57 if aa, ok := txn.(AddAgentSpanAttributer); ok { 58 aa.AddAgentSpanAttribute(key, val) 59 } 60 } 61 62 // These span event string constants must match the contents of the top level 63 // attributes.go file. 64 const ( 65 spanAttributeDBStatement SpanAttribute = "db.statement" 66 spanAttributeDBInstance SpanAttribute = "db.instance" 67 spanAttributeDBCollection SpanAttribute = "db.collection" 68 spanAttributePeerAddress SpanAttribute = "peer.address" 69 spanAttributePeerHostname SpanAttribute = "peer.hostname" 70 spanAttributeHTTPURL SpanAttribute = "http.url" 71 spanAttributeHTTPMethod SpanAttribute = "http.method" 72 // query parameters only appear in segments, not span events, but is 73 // listed as span attributes to simplify code. 74 spanAttributeQueryParameters SpanAttribute = "query_parameters" 75 // These span attributes are added by aws sdk instrumentation. 76 // https://source.datanerd.us/agents/agent-specs/blob/master/implementation_guides/aws-sdk.md#span-and-segment-attributes 77 SpanAttributeAWSOperation SpanAttribute = "aws.operation" 78 SpanAttributeAWSRequestID SpanAttribute = "aws.requestId" 79 SpanAttributeAWSRegion SpanAttribute = "aws.region" 80 ) 81 82 func (sa SpanAttribute) String() string { return string(sa) } 83 84 var ( 85 usualDests = DestAll &^ destBrowser 86 tracesDests = destTxnTrace | destError 87 agentAttributeInfo = map[AgentAttributeID]struct { 88 name string 89 defaultDests destinationSet 90 }{ 91 AttributeHostDisplayName: {name: "host.displayName", defaultDests: usualDests}, 92 attributeRequestMethod: {name: "request.method", defaultDests: usualDests}, 93 attributeRequestAcceptHeader: {name: "request.headers.accept", defaultDests: usualDests}, 94 attributeRequestContentType: {name: "request.headers.contentType", defaultDests: usualDests}, 95 attributeRequestContentLength: {name: "request.headers.contentLength", defaultDests: usualDests}, 96 attributeRequestHeadersHost: {name: "request.headers.host", defaultDests: usualDests}, 97 attributeRequestHeadersUserAgent: {name: "request.headers.User-Agent", defaultDests: tracesDests}, 98 attributeRequestHeadersReferer: {name: "request.headers.referer", defaultDests: tracesDests}, 99 attributeRequestURI: {name: "request.uri", defaultDests: usualDests}, 100 attributeResponseHeadersContentType: {name: "response.headers.contentType", defaultDests: usualDests}, 101 attributeResponseHeadersContentLength: {name: "response.headers.contentLength", defaultDests: usualDests}, 102 attributeResponseCode: {name: "httpResponseCode", defaultDests: usualDests}, 103 AttributeAWSRequestID: {name: "aws.requestId", defaultDests: usualDests}, 104 AttributeAWSLambdaARN: {name: "aws.lambda.arn", defaultDests: usualDests}, 105 AttributeAWSLambdaColdStart: {name: "aws.lambda.coldStart", defaultDests: usualDests}, 106 AttributeAWSLambdaEventSourceARN: {name: "aws.lambda.eventSource.arn", defaultDests: usualDests}, 107 AttributeMessageRoutingKey: {name: "message.routingKey", defaultDests: usualDests}, 108 AttributeMessageQueueName: {name: "message.queueName", defaultDests: usualDests}, 109 AttributeMessageExchangeType: {name: "message.exchangeType", defaultDests: destNone}, 110 AttributeMessageReplyTo: {name: "message.replyTo", defaultDests: destNone}, 111 AttributeMessageCorrelationID: {name: "message.correlationId", defaultDests: destNone}, 112 } 113 spanAttributes = []SpanAttribute{ 114 spanAttributeDBStatement, 115 spanAttributeDBInstance, 116 spanAttributeDBCollection, 117 spanAttributePeerAddress, 118 spanAttributePeerHostname, 119 spanAttributeHTTPURL, 120 spanAttributeHTTPMethod, 121 spanAttributeQueryParameters, 122 SpanAttributeAWSOperation, 123 SpanAttributeAWSRequestID, 124 SpanAttributeAWSRegion, 125 } 126 ) 127 128 func (id AgentAttributeID) name() string { return agentAttributeInfo[id].name } 129 130 // https://source.datanerd.us/agents/agent-specs/blob/master/Agent-Attributes-PORTED.md 131 132 // AttributeDestinationConfig matches newrelic.AttributeDestinationConfig to 133 // avoid circular dependency issues. 134 type AttributeDestinationConfig struct { 135 Enabled bool 136 Include []string 137 Exclude []string 138 } 139 140 type destinationSet int 141 142 const ( 143 destTxnEvent destinationSet = 1 << iota 144 destError 145 destTxnTrace 146 destBrowser 147 destSpan 148 destSegment 149 ) 150 151 const ( 152 destNone destinationSet = 0 153 // DestAll contains all destinations. 154 DestAll destinationSet = destTxnEvent | destTxnTrace | destError | destBrowser | destSpan | destSegment 155 ) 156 157 const ( 158 attributeWildcardSuffix = '*' 159 ) 160 161 type attributeModifier struct { 162 match string // This will not contain a trailing '*'. 163 includeExclude 164 } 165 166 type byMatch []*attributeModifier 167 168 func (m byMatch) Len() int { return len(m) } 169 func (m byMatch) Swap(i, j int) { m[i], m[j] = m[j], m[i] } 170 func (m byMatch) Less(i, j int) bool { return m[i].match < m[j].match } 171 172 // AttributeConfig is created at connect and shared between all transactions. 173 type AttributeConfig struct { 174 disabledDestinations destinationSet 175 exactMatchModifiers map[string]*attributeModifier 176 // Once attributeConfig is constructed, wildcardModifiers is sorted in 177 // lexicographical order. Modifiers appearing later have precedence 178 // over modifiers appearing earlier. 179 wildcardModifiers []*attributeModifier 180 agentDests map[AgentAttributeID]destinationSet 181 spanDests map[SpanAttribute]destinationSet 182 } 183 184 type includeExclude struct { 185 include destinationSet 186 exclude destinationSet 187 } 188 189 func modifierApply(m *attributeModifier, d destinationSet) destinationSet { 190 // Include before exclude, since exclude has priority. 191 d |= m.include 192 d &^= m.exclude 193 return d 194 } 195 196 func applyAttributeConfig(c *AttributeConfig, key string, d destinationSet) destinationSet { 197 // Important: The wildcard modifiers must be applied before the exact 198 // match modifiers, and the slice must be iterated in a forward 199 // direction. 200 for _, m := range c.wildcardModifiers { 201 if strings.HasPrefix(key, m.match) { 202 d = modifierApply(m, d) 203 } 204 } 205 206 if m, ok := c.exactMatchModifiers[key]; ok { 207 d = modifierApply(m, d) 208 } 209 210 d &^= c.disabledDestinations 211 212 return d 213 } 214 215 func addModifier(c *AttributeConfig, match string, d includeExclude) { 216 if "" == match { 217 return 218 } 219 exactMatch := true 220 if attributeWildcardSuffix == match[len(match)-1] { 221 exactMatch = false 222 match = match[0 : len(match)-1] 223 } 224 mod := &attributeModifier{ 225 match: match, 226 includeExclude: d, 227 } 228 229 if exactMatch { 230 if m, ok := c.exactMatchModifiers[mod.match]; ok { 231 m.include |= mod.include 232 m.exclude |= mod.exclude 233 } else { 234 c.exactMatchModifiers[mod.match] = mod 235 } 236 } else { 237 for _, m := range c.wildcardModifiers { 238 // Important: Duplicate entries for the same match 239 // string would not work because exclude needs 240 // precedence over include. 241 if m.match == mod.match { 242 m.include |= mod.include 243 m.exclude |= mod.exclude 244 return 245 } 246 } 247 c.wildcardModifiers = append(c.wildcardModifiers, mod) 248 } 249 } 250 251 func processDest(c *AttributeConfig, includeEnabled bool, dc *AttributeDestinationConfig, d destinationSet) { 252 if !dc.Enabled { 253 c.disabledDestinations |= d 254 } 255 if includeEnabled { 256 for _, match := range dc.Include { 257 addModifier(c, match, includeExclude{include: d}) 258 } 259 } 260 for _, match := range dc.Exclude { 261 addModifier(c, match, includeExclude{exclude: d}) 262 } 263 } 264 265 // AttributeConfigInput is used as the input to CreateAttributeConfig: it 266 // transforms newrelic.Config settings into an AttributeConfig. 267 type AttributeConfigInput struct { 268 Attributes AttributeDestinationConfig 269 ErrorCollector AttributeDestinationConfig 270 TransactionEvents AttributeDestinationConfig 271 BrowserMonitoring AttributeDestinationConfig 272 TransactionTracer AttributeDestinationConfig 273 SpanEvents AttributeDestinationConfig 274 TraceSegments AttributeDestinationConfig 275 } 276 277 var ( 278 sampleAttributeConfigInput = AttributeConfigInput{ 279 Attributes: AttributeDestinationConfig{Enabled: true}, 280 ErrorCollector: AttributeDestinationConfig{Enabled: true}, 281 TransactionEvents: AttributeDestinationConfig{Enabled: true}, 282 TransactionTracer: AttributeDestinationConfig{Enabled: true}, 283 BrowserMonitoring: AttributeDestinationConfig{Enabled: true}, 284 SpanEvents: AttributeDestinationConfig{Enabled: true}, 285 TraceSegments: AttributeDestinationConfig{Enabled: true}, 286 } 287 ) 288 289 // CreateAttributeConfig creates a new AttributeConfig. 290 func CreateAttributeConfig(input AttributeConfigInput, includeEnabled bool) *AttributeConfig { 291 c := &AttributeConfig{ 292 exactMatchModifiers: make(map[string]*attributeModifier), 293 wildcardModifiers: make([]*attributeModifier, 0, 64), 294 } 295 296 processDest(c, includeEnabled, &input.Attributes, DestAll) 297 processDest(c, includeEnabled, &input.ErrorCollector, destError) 298 processDest(c, includeEnabled, &input.TransactionEvents, destTxnEvent) 299 processDest(c, includeEnabled, &input.TransactionTracer, destTxnTrace) 300 processDest(c, includeEnabled, &input.BrowserMonitoring, destBrowser) 301 processDest(c, includeEnabled, &input.SpanEvents, destSpan) 302 processDest(c, includeEnabled, &input.TraceSegments, destSegment) 303 304 sort.Sort(byMatch(c.wildcardModifiers)) 305 306 c.agentDests = make(map[AgentAttributeID]destinationSet) 307 for id, info := range agentAttributeInfo { 308 c.agentDests[id] = applyAttributeConfig(c, info.name, info.defaultDests) 309 } 310 c.spanDests = make(map[SpanAttribute]destinationSet, len(spanAttributes)) 311 for _, id := range spanAttributes { 312 c.spanDests[id] = applyAttributeConfig(c, id.String(), destSpan|destSegment) 313 } 314 315 return c 316 } 317 318 type userAttribute struct { 319 value interface{} 320 dests destinationSet 321 } 322 323 type agentAttributeValue struct { 324 stringVal string 325 otherVal interface{} 326 } 327 328 type agentAttributes map[AgentAttributeID]agentAttributeValue 329 330 func (a *Attributes) filterSpanAttributes(s map[SpanAttribute]jsonWriter, d destinationSet) map[SpanAttribute]jsonWriter { 331 if nil != a { 332 for key := range s { 333 if a.config.spanDests[key]&d == 0 { 334 delete(s, key) 335 } 336 } 337 } 338 return s 339 } 340 341 // GetAgentValue is used to access agent attributes. This function returns ("", 342 // nil) if the attribute doesn't exist or it doesn't match the destinations 343 // provided. 344 func (a *Attributes) GetAgentValue(id AgentAttributeID, d destinationSet) (string, interface{}) { 345 if nil == a || 0 == a.config.agentDests[id]&d { 346 return "", nil 347 } 348 v, _ := a.Agent[id] 349 return v.stringVal, v.otherVal 350 } 351 352 // AddAgentAttributer allows instrumentation to add agent attributes without 353 // exposing a Transaction method. 354 type AddAgentAttributer interface { 355 AddAgentAttribute(id AgentAttributeID, stringVal string, otherVal interface{}) 356 } 357 358 // Add is used to add agent attributes. Only one of stringVal and 359 // otherVal should be populated. Since most agent attribute values are strings, 360 // stringVal exists to avoid allocations. 361 func (attr agentAttributes) Add(id AgentAttributeID, stringVal string, otherVal interface{}) { 362 if "" != stringVal || otherVal != nil { 363 attr[id] = agentAttributeValue{ 364 stringVal: truncateStringValueIfLong(stringVal), 365 otherVal: otherVal, 366 } 367 } 368 } 369 370 // Attributes are key value pairs attached to the various collected data types. 371 type Attributes struct { 372 config *AttributeConfig 373 user map[string]userAttribute 374 Agent agentAttributes 375 } 376 377 // NewAttributes creates a new Attributes. 378 func NewAttributes(config *AttributeConfig) *Attributes { 379 return &Attributes{ 380 config: config, 381 Agent: make(agentAttributes), 382 } 383 } 384 385 // ErrInvalidAttributeType is returned when the value is not valid. 386 type ErrInvalidAttributeType struct { 387 key string 388 val interface{} 389 } 390 391 func (e ErrInvalidAttributeType) Error() string { 392 return fmt.Sprintf("attribute '%s' value of type %T is invalid", e.key, e.val) 393 } 394 395 type invalidAttributeKeyErr struct{ key string } 396 397 func (e invalidAttributeKeyErr) Error() string { 398 return fmt.Sprintf("attribute key '%.32s...' exceeds length limit %d", 399 e.key, attributeKeyLengthLimit) 400 } 401 402 type userAttributeLimitErr struct{ key string } 403 404 func (e userAttributeLimitErr) Error() string { 405 return fmt.Sprintf("attribute '%s' discarded: limit of %d reached", e.key, 406 attributeUserLimit) 407 } 408 409 func truncateStringValueIfLong(val string) string { 410 if len(val) > attributeValueLengthLimit { 411 return StringLengthByteLimit(val, attributeValueLengthLimit) 412 } 413 return val 414 } 415 416 // ValidateUserAttribute validates a user attribute. 417 func ValidateUserAttribute(key string, val interface{}) (interface{}, error) { 418 if str, ok := val.(string); ok { 419 val = interface{}(truncateStringValueIfLong(str)) 420 } 421 422 switch val.(type) { 423 case string, bool, 424 uint8, uint16, uint32, uint64, int8, int16, int32, int64, 425 float32, float64, uint, int, uintptr: 426 default: 427 return nil, ErrInvalidAttributeType{ 428 key: key, 429 val: val, 430 } 431 } 432 433 // Attributes whose keys are excessively long are dropped rather than 434 // truncated to avoid worrying about the application of configuration to 435 // truncated values or performing the truncation after configuration. 436 if len(key) > attributeKeyLengthLimit { 437 return nil, invalidAttributeKeyErr{key: key} 438 } 439 return val, nil 440 } 441 442 // AddUserAttribute adds a user attribute. 443 func AddUserAttribute(a *Attributes, key string, val interface{}, d destinationSet) error { 444 val, err := ValidateUserAttribute(key, val) 445 if nil != err { 446 return err 447 } 448 dests := applyAttributeConfig(a.config, key, d) 449 if destNone == dests { 450 return nil 451 } 452 if nil == a.user { 453 a.user = make(map[string]userAttribute) 454 } 455 456 if _, exists := a.user[key]; !exists && len(a.user) >= attributeUserLimit { 457 return userAttributeLimitErr{key} 458 } 459 460 // Note: Duplicates are overridden: last attribute in wins. 461 a.user[key] = userAttribute{ 462 value: val, 463 dests: dests, 464 } 465 return nil 466 } 467 468 func writeAttributeValueJSON(w *jsonFieldsWriter, key string, val interface{}) { 469 switch v := val.(type) { 470 case string: 471 w.stringField(key, v) 472 case bool: 473 if v { 474 w.rawField(key, `true`) 475 } else { 476 w.rawField(key, `false`) 477 } 478 case uint8: 479 w.intField(key, int64(v)) 480 case uint16: 481 w.intField(key, int64(v)) 482 case uint32: 483 w.intField(key, int64(v)) 484 case uint64: 485 w.intField(key, int64(v)) 486 case uint: 487 w.intField(key, int64(v)) 488 case uintptr: 489 w.intField(key, int64(v)) 490 case int8: 491 w.intField(key, int64(v)) 492 case int16: 493 w.intField(key, int64(v)) 494 case int32: 495 w.intField(key, int64(v)) 496 case int64: 497 w.intField(key, v) 498 case int: 499 w.intField(key, int64(v)) 500 case float32: 501 w.floatField(key, float64(v)) 502 case float64: 503 w.floatField(key, v) 504 default: 505 w.stringField(key, fmt.Sprintf("%T", v)) 506 } 507 } 508 509 func agentAttributesJSON(a *Attributes, buf *bytes.Buffer, d destinationSet) { 510 if nil == a { 511 buf.WriteString("{}") 512 return 513 } 514 w := jsonFieldsWriter{buf: buf} 515 buf.WriteByte('{') 516 for id, val := range a.Agent { 517 if 0 != a.config.agentDests[id]&d { 518 if val.stringVal != "" { 519 w.stringField(id.name(), val.stringVal) 520 } else { 521 writeAttributeValueJSON(&w, id.name(), val.otherVal) 522 } 523 } 524 } 525 buf.WriteByte('}') 526 527 } 528 529 func userAttributesJSON(a *Attributes, buf *bytes.Buffer, d destinationSet, extraAttributes map[string]interface{}) { 530 buf.WriteByte('{') 531 if nil != a { 532 w := jsonFieldsWriter{buf: buf} 533 for key, val := range extraAttributes { 534 outputDest := applyAttributeConfig(a.config, key, d) 535 if 0 != outputDest&d { 536 writeAttributeValueJSON(&w, key, val) 537 } 538 } 539 for name, atr := range a.user { 540 if 0 != atr.dests&d { 541 if _, found := extraAttributes[name]; found { 542 continue 543 } 544 writeAttributeValueJSON(&w, name, atr.value) 545 } 546 } 547 } 548 buf.WriteByte('}') 549 } 550 551 // userAttributesStringJSON is only used for testing. 552 func userAttributesStringJSON(a *Attributes, d destinationSet, extraAttributes map[string]interface{}) string { 553 estimate := len(a.user) * 128 554 buf := bytes.NewBuffer(make([]byte, 0, estimate)) 555 userAttributesJSON(a, buf, d, extraAttributes) 556 return buf.String() 557 } 558 559 // RequestAgentAttributes gathers agent attributes out of the request. 560 func RequestAgentAttributes(a *Attributes, method string, h http.Header, u *url.URL) { 561 a.Agent.Add(attributeRequestMethod, method, nil) 562 563 if nil != u { 564 a.Agent.Add(attributeRequestURI, SafeURL(u), nil) 565 } 566 567 if nil == h { 568 return 569 } 570 a.Agent.Add(attributeRequestAcceptHeader, h.Get("Accept"), nil) 571 a.Agent.Add(attributeRequestContentType, h.Get("Content-Type"), nil) 572 a.Agent.Add(attributeRequestHeadersHost, h.Get("Host"), nil) 573 a.Agent.Add(attributeRequestHeadersUserAgent, h.Get("User-Agent"), nil) 574 a.Agent.Add(attributeRequestHeadersReferer, SafeURLFromString(h.Get("Referer")), nil) 575 576 if l := GetContentLengthFromHeader(h); l >= 0 { 577 a.Agent.Add(attributeRequestContentLength, "", l) 578 } 579 } 580 581 // ResponseHeaderAttributes gather agent attributes from the response headers. 582 func ResponseHeaderAttributes(a *Attributes, h http.Header) { 583 if nil == h { 584 return 585 } 586 a.Agent.Add(attributeResponseHeadersContentType, h.Get("Content-Type"), nil) 587 588 if l := GetContentLengthFromHeader(h); l >= 0 { 589 a.Agent.Add(attributeResponseHeadersContentLength, "", l) 590 } 591 } 592 593 var ( 594 // statusCodeLookup avoids a strconv.Itoa call. 595 statusCodeLookup = map[int]string{ 596 100: "100", 101: "101", 597 200: "200", 201: "201", 202: "202", 203: "203", 204: "204", 205: "205", 206: "206", 598 300: "300", 301: "301", 302: "302", 303: "303", 304: "304", 305: "305", 307: "307", 599 400: "400", 401: "401", 402: "402", 403: "403", 404: "404", 405: "405", 406: "406", 600 407: "407", 408: "408", 409: "409", 410: "410", 411: "411", 412: "412", 413: "413", 601 414: "414", 415: "415", 416: "416", 417: "417", 418: "418", 428: "428", 429: "429", 602 431: "431", 451: "451", 603 500: "500", 501: "501", 502: "502", 503: "503", 504: "504", 505: "505", 511: "511", 604 } 605 ) 606 607 // ResponseCodeAttribute sets the response code agent attribute. 608 func ResponseCodeAttribute(a *Attributes, code int) { 609 rc := statusCodeLookup[code] 610 if rc == "" { 611 rc = strconv.Itoa(code) 612 } 613 a.Agent.Add(attributeResponseCode, rc, nil) 614 }