segment_terms.go 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package internal
  2. // https://newrelic.atlassian.net/wiki/display/eng/Language+agent+transaction+segment+terms+rules
  3. import (
  4. "encoding/json"
  5. "strings"
  6. )
  7. const (
  8. placeholder = "*"
  9. separator = "/"
  10. )
  11. type segmentRule struct {
  12. Prefix string `json:"prefix"`
  13. Terms []string `json:"terms"`
  14. TermsMap map[string]struct{}
  15. }
  16. // segmentRules is keyed by each segmentRule's Prefix field with any trailing
  17. // slash removed.
  18. type segmentRules map[string]*segmentRule
  19. func buildTermsMap(terms []string) map[string]struct{} {
  20. m := make(map[string]struct{}, len(terms))
  21. for _, t := range terms {
  22. m[t] = struct{}{}
  23. }
  24. return m
  25. }
  26. func (rules *segmentRules) UnmarshalJSON(b []byte) error {
  27. var raw []*segmentRule
  28. if err := json.Unmarshal(b, &raw); nil != err {
  29. return err
  30. }
  31. rs := make(map[string]*segmentRule)
  32. for _, rule := range raw {
  33. prefix := strings.TrimSuffix(rule.Prefix, "/")
  34. if len(strings.Split(prefix, "/")) != 2 {
  35. // TODO
  36. // Warn("invalid segment term rule prefix",
  37. // {"prefix": rule.Prefix})
  38. continue
  39. }
  40. if nil == rule.Terms {
  41. // TODO
  42. // Warn("segment term rule has missing terms",
  43. // {"prefix": rule.Prefix})
  44. continue
  45. }
  46. rule.TermsMap = buildTermsMap(rule.Terms)
  47. rs[prefix] = rule
  48. }
  49. *rules = rs
  50. return nil
  51. }
  52. func (rule *segmentRule) apply(name string) string {
  53. if !strings.HasPrefix(name, rule.Prefix) {
  54. return name
  55. }
  56. s := strings.TrimPrefix(name, rule.Prefix)
  57. leadingSlash := ""
  58. if strings.HasPrefix(s, separator) {
  59. leadingSlash = separator
  60. s = strings.TrimPrefix(s, separator)
  61. }
  62. if "" != s {
  63. segments := strings.Split(s, separator)
  64. for i, segment := range segments {
  65. _, whitelisted := rule.TermsMap[segment]
  66. if whitelisted {
  67. segments[i] = segment
  68. } else {
  69. segments[i] = placeholder
  70. }
  71. }
  72. segments = collapsePlaceholders(segments)
  73. s = strings.Join(segments, separator)
  74. }
  75. return rule.Prefix + leadingSlash + s
  76. }
  77. func (rules segmentRules) apply(name string) string {
  78. if nil == rules {
  79. return name
  80. }
  81. rule, ok := rules[firstTwoSegments(name)]
  82. if !ok {
  83. return name
  84. }
  85. return rule.apply(name)
  86. }
  87. func firstTwoSegments(name string) string {
  88. firstSlashIdx := strings.Index(name, separator)
  89. if firstSlashIdx == -1 {
  90. return name
  91. }
  92. secondSlashIdx := strings.Index(name[firstSlashIdx+1:], separator)
  93. if secondSlashIdx == -1 {
  94. return name
  95. }
  96. return name[0 : firstSlashIdx+secondSlashIdx+1]
  97. }
  98. func collapsePlaceholders(segments []string) []string {
  99. j := 0
  100. prevStar := false
  101. for i := 0; i < len(segments); i++ {
  102. segment := segments[i]
  103. if placeholder == segment {
  104. if prevStar {
  105. continue
  106. }
  107. segments[j] = placeholder
  108. j++
  109. prevStar = true
  110. } else {
  111. segments[j] = segment
  112. j++
  113. prevStar = false
  114. }
  115. }
  116. return segments[0:j]
  117. }