attributes.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. package internal
  2. import (
  3. "bytes"
  4. "fmt"
  5. "net/http"
  6. "sort"
  7. "strconv"
  8. "strings"
  9. )
  10. // AgentAttributeID uniquely identifies each agent attribute.
  11. type AgentAttributeID int
  12. // New agent attributes must be added in the following places:
  13. // * Constants here.
  14. // * Top level attributes.go file.
  15. // * agentAttributeInfo
  16. const (
  17. AttributeHostDisplayName AgentAttributeID = iota
  18. attributeRequestMethod
  19. attributeRequestAcceptHeader
  20. attributeRequestContentType
  21. attributeRequestContentLength
  22. attributeRequestHeadersHost
  23. attributeRequestHeadersUserAgent
  24. attributeRequestHeadersReferer
  25. attributeResponseHeadersContentType
  26. attributeResponseHeadersContentLength
  27. attributeResponseCode
  28. )
  29. var (
  30. usualDests = DestAll &^ destBrowser
  31. tracesDests = destTxnTrace | destError
  32. agentAttributeInfo = map[AgentAttributeID]struct {
  33. name string
  34. defaultDests destinationSet
  35. }{
  36. AttributeHostDisplayName: {name: "host.displayName", defaultDests: usualDests},
  37. attributeRequestMethod: {name: "request.method", defaultDests: usualDests},
  38. attributeRequestAcceptHeader: {name: "request.headers.accept", defaultDests: usualDests},
  39. attributeRequestContentType: {name: "request.headers.contentType", defaultDests: usualDests},
  40. attributeRequestContentLength: {name: "request.headers.contentLength", defaultDests: usualDests},
  41. attributeRequestHeadersHost: {name: "request.headers.host", defaultDests: usualDests},
  42. attributeRequestHeadersUserAgent: {name: "request.headers.User-Agent", defaultDests: tracesDests},
  43. attributeRequestHeadersReferer: {name: "request.headers.referer", defaultDests: tracesDests},
  44. attributeResponseHeadersContentType: {name: "response.headers.contentType", defaultDests: usualDests},
  45. attributeResponseHeadersContentLength: {name: "response.headers.contentLength", defaultDests: usualDests},
  46. attributeResponseCode: {name: "httpResponseCode", defaultDests: usualDests},
  47. }
  48. )
  49. func (id AgentAttributeID) name() string { return agentAttributeInfo[id].name }
  50. // https://source.datanerd.us/agents/agent-specs/blob/master/Agent-Attributes-PORTED.md
  51. // AttributeDestinationConfig matches newrelic.AttributeDestinationConfig to
  52. // avoid circular dependency issues.
  53. type AttributeDestinationConfig struct {
  54. Enabled bool
  55. Include []string
  56. Exclude []string
  57. }
  58. type destinationSet int
  59. const (
  60. destTxnEvent destinationSet = 1 << iota
  61. destError
  62. destTxnTrace
  63. destBrowser
  64. )
  65. const (
  66. destNone destinationSet = 0
  67. // DestAll contains all destinations.
  68. DestAll destinationSet = destTxnEvent | destTxnTrace | destError | destBrowser
  69. )
  70. const (
  71. attributeWildcardSuffix = '*'
  72. )
  73. type attributeModifier struct {
  74. match string // This will not contain a trailing '*'.
  75. includeExclude
  76. }
  77. type byMatch []*attributeModifier
  78. func (m byMatch) Len() int { return len(m) }
  79. func (m byMatch) Swap(i, j int) { m[i], m[j] = m[j], m[i] }
  80. func (m byMatch) Less(i, j int) bool { return m[i].match < m[j].match }
  81. // AttributeConfig is created at connect and shared between all transactions.
  82. type AttributeConfig struct {
  83. disabledDestinations destinationSet
  84. exactMatchModifiers map[string]*attributeModifier
  85. // Once attributeConfig is constructed, wildcardModifiers is sorted in
  86. // lexicographical order. Modifiers appearing later have precedence
  87. // over modifiers appearing earlier.
  88. wildcardModifiers []*attributeModifier
  89. agentDests map[AgentAttributeID]destinationSet
  90. }
  91. type includeExclude struct {
  92. include destinationSet
  93. exclude destinationSet
  94. }
  95. func modifierApply(m *attributeModifier, d destinationSet) destinationSet {
  96. // Include before exclude, since exclude has priority.
  97. d |= m.include
  98. d &^= m.exclude
  99. return d
  100. }
  101. func applyAttributeConfig(c *AttributeConfig, key string, d destinationSet) destinationSet {
  102. // Important: The wildcard modifiers must be applied before the exact
  103. // match modifiers, and the slice must be iterated in a forward
  104. // direction.
  105. for _, m := range c.wildcardModifiers {
  106. if strings.HasPrefix(key, m.match) {
  107. d = modifierApply(m, d)
  108. }
  109. }
  110. if m, ok := c.exactMatchModifiers[key]; ok {
  111. d = modifierApply(m, d)
  112. }
  113. d &^= c.disabledDestinations
  114. return d
  115. }
  116. func addModifier(c *AttributeConfig, match string, d includeExclude) {
  117. if "" == match {
  118. return
  119. }
  120. exactMatch := true
  121. if attributeWildcardSuffix == match[len(match)-1] {
  122. exactMatch = false
  123. match = match[0 : len(match)-1]
  124. }
  125. mod := &attributeModifier{
  126. match: match,
  127. includeExclude: d,
  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. func processDest(c *AttributeConfig, includeEnabled bool, dc *AttributeDestinationConfig, d destinationSet) {
  151. if !dc.Enabled {
  152. c.disabledDestinations |= d
  153. }
  154. if includeEnabled {
  155. for _, match := range dc.Include {
  156. addModifier(c, match, includeExclude{include: d})
  157. }
  158. }
  159. for _, match := range dc.Exclude {
  160. addModifier(c, match, includeExclude{exclude: d})
  161. }
  162. }
  163. // AttributeConfigInput is used as the input to CreateAttributeConfig: it
  164. // transforms newrelic.Config settings into an AttributeConfig.
  165. type AttributeConfigInput struct {
  166. Attributes AttributeDestinationConfig
  167. ErrorCollector AttributeDestinationConfig
  168. TransactionEvents AttributeDestinationConfig
  169. browserMonitoring AttributeDestinationConfig
  170. TransactionTracer AttributeDestinationConfig
  171. }
  172. var (
  173. sampleAttributeConfigInput = AttributeConfigInput{
  174. Attributes: AttributeDestinationConfig{Enabled: true},
  175. ErrorCollector: AttributeDestinationConfig{Enabled: true},
  176. TransactionEvents: AttributeDestinationConfig{Enabled: true},
  177. TransactionTracer: AttributeDestinationConfig{Enabled: true},
  178. }
  179. )
  180. // CreateAttributeConfig creates a new AttributeConfig.
  181. func CreateAttributeConfig(input AttributeConfigInput, includeEnabled bool) *AttributeConfig {
  182. c := &AttributeConfig{
  183. exactMatchModifiers: make(map[string]*attributeModifier),
  184. wildcardModifiers: make([]*attributeModifier, 0, 64),
  185. }
  186. processDest(c, includeEnabled, &input.Attributes, DestAll)
  187. processDest(c, includeEnabled, &input.ErrorCollector, destError)
  188. processDest(c, includeEnabled, &input.TransactionEvents, destTxnEvent)
  189. processDest(c, includeEnabled, &input.TransactionTracer, destTxnTrace)
  190. processDest(c, includeEnabled, &input.browserMonitoring, destBrowser)
  191. sort.Sort(byMatch(c.wildcardModifiers))
  192. c.agentDests = make(map[AgentAttributeID]destinationSet)
  193. for id, info := range agentAttributeInfo {
  194. c.agentDests[id] = applyAttributeConfig(c, info.name, info.defaultDests)
  195. }
  196. return c
  197. }
  198. type userAttribute struct {
  199. value interface{}
  200. dests destinationSet
  201. }
  202. type agentAttributeValue struct {
  203. stringVal string
  204. otherVal interface{}
  205. }
  206. type agentAttributes map[AgentAttributeID]agentAttributeValue
  207. // Add is used to add agent attributes. Only one of stringVal and otherVal
  208. // should be populated. Since most agent attribute values are strings,
  209. // stringVal exists to avoid allocations.
  210. func (attr agentAttributes) Add(id AgentAttributeID, stringVal string, otherVal interface{}) {
  211. if "" != stringVal || otherVal != nil {
  212. attr[id] = agentAttributeValue{
  213. stringVal: truncateStringValueIfLong(stringVal),
  214. otherVal: otherVal,
  215. }
  216. }
  217. }
  218. // Attributes are key value pairs attached to the various collected data types.
  219. type Attributes struct {
  220. config *AttributeConfig
  221. user map[string]userAttribute
  222. Agent agentAttributes
  223. }
  224. // NewAttributes creates a new Attributes.
  225. func NewAttributes(config *AttributeConfig) *Attributes {
  226. return &Attributes{
  227. config: config,
  228. Agent: make(agentAttributes),
  229. }
  230. }
  231. // ErrInvalidAttributeType is returned when the value is not valid.
  232. type ErrInvalidAttributeType struct {
  233. key string
  234. val interface{}
  235. }
  236. func (e ErrInvalidAttributeType) Error() string {
  237. return fmt.Sprintf("attribute '%s' value of type %T is invalid", e.key, e.val)
  238. }
  239. type invalidAttributeKeyErr struct{ key string }
  240. func (e invalidAttributeKeyErr) Error() string {
  241. return fmt.Sprintf("attribute key '%.32s...' exceeds length limit %d",
  242. e.key, attributeKeyLengthLimit)
  243. }
  244. type userAttributeLimitErr struct{ key string }
  245. func (e userAttributeLimitErr) Error() string {
  246. return fmt.Sprintf("attribute '%s' discarded: limit of %d reached", e.key,
  247. attributeUserLimit)
  248. }
  249. func truncateStringValueIfLong(val string) string {
  250. if len(val) > attributeValueLengthLimit {
  251. return StringLengthByteLimit(val, attributeValueLengthLimit)
  252. }
  253. return val
  254. }
  255. // ValidateUserAttribute validates a user attribute.
  256. func ValidateUserAttribute(key string, val interface{}) (interface{}, error) {
  257. if str, ok := val.(string); ok {
  258. val = interface{}(truncateStringValueIfLong(str))
  259. }
  260. switch val.(type) {
  261. case string, bool, nil,
  262. uint8, uint16, uint32, uint64, int8, int16, int32, int64,
  263. float32, float64, uint, int, uintptr:
  264. default:
  265. return nil, ErrInvalidAttributeType{
  266. key: key,
  267. val: val,
  268. }
  269. }
  270. // Attributes whose keys are excessively long are dropped rather than
  271. // truncated to avoid worrying about the application of configuration to
  272. // truncated values or performing the truncation after configuration.
  273. if len(key) > attributeKeyLengthLimit {
  274. return nil, invalidAttributeKeyErr{key: key}
  275. }
  276. return val, nil
  277. }
  278. // AddUserAttribute adds a user attribute.
  279. func AddUserAttribute(a *Attributes, key string, val interface{}, d destinationSet) error {
  280. val, err := ValidateUserAttribute(key, val)
  281. if nil != err {
  282. return err
  283. }
  284. dests := applyAttributeConfig(a.config, key, d)
  285. if destNone == dests {
  286. return nil
  287. }
  288. if nil == a.user {
  289. a.user = make(map[string]userAttribute)
  290. }
  291. if _, exists := a.user[key]; !exists && len(a.user) >= attributeUserLimit {
  292. return userAttributeLimitErr{key}
  293. }
  294. // Note: Duplicates are overridden: last attribute in wins.
  295. a.user[key] = userAttribute{
  296. value: val,
  297. dests: dests,
  298. }
  299. return nil
  300. }
  301. func writeAttributeValueJSON(w *jsonFieldsWriter, key string, val interface{}) {
  302. switch v := val.(type) {
  303. case nil:
  304. w.rawField(key, `null`)
  305. case string:
  306. w.stringField(key, v)
  307. case bool:
  308. if v {
  309. w.rawField(key, `true`)
  310. } else {
  311. w.rawField(key, `false`)
  312. }
  313. case uint8:
  314. w.intField(key, int64(v))
  315. case uint16:
  316. w.intField(key, int64(v))
  317. case uint32:
  318. w.intField(key, int64(v))
  319. case uint64:
  320. w.intField(key, int64(v))
  321. case uint:
  322. w.intField(key, int64(v))
  323. case uintptr:
  324. w.intField(key, int64(v))
  325. case int8:
  326. w.intField(key, int64(v))
  327. case int16:
  328. w.intField(key, int64(v))
  329. case int32:
  330. w.intField(key, int64(v))
  331. case int64:
  332. w.intField(key, v)
  333. case int:
  334. w.intField(key, int64(v))
  335. case float32:
  336. w.floatField(key, float64(v))
  337. case float64:
  338. w.floatField(key, v)
  339. default:
  340. w.stringField(key, fmt.Sprintf("%T", v))
  341. }
  342. }
  343. func agentAttributesJSON(a *Attributes, buf *bytes.Buffer, d destinationSet) {
  344. if nil == a {
  345. buf.WriteString("{}")
  346. return
  347. }
  348. w := jsonFieldsWriter{buf: buf}
  349. buf.WriteByte('{')
  350. for id, val := range a.Agent {
  351. if 0 != a.config.agentDests[id]&d {
  352. if val.stringVal != "" {
  353. w.stringField(id.name(), val.stringVal)
  354. } else {
  355. writeAttributeValueJSON(&w, id.name(), val.otherVal)
  356. }
  357. }
  358. }
  359. buf.WriteByte('}')
  360. }
  361. func userAttributesJSON(a *Attributes, buf *bytes.Buffer, d destinationSet, extraAttributes map[string]interface{}) {
  362. buf.WriteByte('{')
  363. if nil != a {
  364. w := jsonFieldsWriter{buf: buf}
  365. for key, val := range extraAttributes {
  366. outputDest := applyAttributeConfig(a.config, key, d)
  367. if 0 != outputDest&d {
  368. writeAttributeValueJSON(&w, key, val)
  369. }
  370. }
  371. for name, atr := range a.user {
  372. if 0 != atr.dests&d {
  373. if _, found := extraAttributes[name]; found {
  374. continue
  375. }
  376. writeAttributeValueJSON(&w, name, atr.value)
  377. }
  378. }
  379. }
  380. buf.WriteByte('}')
  381. }
  382. // userAttributesStringJSON is only used for testing.
  383. func userAttributesStringJSON(a *Attributes, d destinationSet, extraAttributes map[string]interface{}) string {
  384. estimate := len(a.user) * 128
  385. buf := bytes.NewBuffer(make([]byte, 0, estimate))
  386. userAttributesJSON(a, buf, d, extraAttributes)
  387. return buf.String()
  388. }
  389. // RequestAgentAttributes gathers agent attributes out of the request.
  390. func RequestAgentAttributes(a *Attributes, method string, h http.Header) {
  391. a.Agent.Add(attributeRequestMethod, method, nil)
  392. if nil == h {
  393. return
  394. }
  395. a.Agent.Add(attributeRequestAcceptHeader, h.Get("Accept"), nil)
  396. a.Agent.Add(attributeRequestContentType, h.Get("Content-Type"), nil)
  397. a.Agent.Add(attributeRequestHeadersHost, h.Get("Host"), nil)
  398. a.Agent.Add(attributeRequestHeadersUserAgent, h.Get("User-Agent"), nil)
  399. a.Agent.Add(attributeRequestHeadersReferer, SafeURLFromString(h.Get("Referer")), nil)
  400. if l := GetContentLengthFromHeader(h); l >= 0 {
  401. a.Agent.Add(attributeRequestContentLength, "", l)
  402. }
  403. }
  404. // ResponseHeaderAttributes gather agent attributes from the response headers.
  405. func ResponseHeaderAttributes(a *Attributes, h http.Header) {
  406. if nil == h {
  407. return
  408. }
  409. a.Agent.Add(attributeResponseHeadersContentType, h.Get("Content-Type"), nil)
  410. if l := GetContentLengthFromHeader(h); l >= 0 {
  411. a.Agent.Add(attributeResponseHeadersContentLength, "", l)
  412. }
  413. }
  414. var (
  415. // statusCodeLookup avoids a strconv.Itoa call.
  416. statusCodeLookup = map[int]string{
  417. 100: "100", 101: "101",
  418. 200: "200", 201: "201", 202: "202", 203: "203", 204: "204", 205: "205", 206: "206",
  419. 300: "300", 301: "301", 302: "302", 303: "303", 304: "304", 305: "305", 307: "307",
  420. 400: "400", 401: "401", 402: "402", 403: "403", 404: "404", 405: "405", 406: "406",
  421. 407: "407", 408: "408", 409: "409", 410: "410", 411: "411", 412: "412", 413: "413",
  422. 414: "414", 415: "415", 416: "416", 417: "417", 418: "418", 428: "428", 429: "429",
  423. 431: "431", 451: "451",
  424. 500: "500", 501: "501", 502: "502", 503: "503", 504: "504", 505: "505", 511: "511",
  425. }
  426. )
  427. // ResponseCodeAttribute sets the response code agent attribute.
  428. func ResponseCodeAttribute(a *Attributes, code int) {
  429. rc := statusCodeLookup[code]
  430. if rc == "" {
  431. rc = strconv.Itoa(code)
  432. }
  433. a.Agent.Add(attributeResponseCode, rc, nil)
  434. }