metric_rules.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. package internal
  2. import (
  3. "encoding/json"
  4. "regexp"
  5. "sort"
  6. "strings"
  7. )
  8. type ruleResult int
  9. const (
  10. ruleMatched ruleResult = iota
  11. ruleUnmatched
  12. ruleIgnore
  13. )
  14. type metricRule struct {
  15. // 'Ignore' indicates if the entire transaction should be discarded if
  16. // there is a match. This field is only used by "url_rules" and
  17. // "transaction_name_rules", not "metric_name_rules".
  18. Ignore bool `json:"ignore"`
  19. EachSegment bool `json:"each_segment"`
  20. ReplaceAll bool `json:"replace_all"`
  21. Terminate bool `json:"terminate_chain"`
  22. Order int `json:"eval_order"`
  23. OriginalReplacement string `json:"replacement"`
  24. RawExpr string `json:"match_expression"`
  25. // Go's regexp backreferences use '${1}' instead of the Perlish '\1', so
  26. // we transform the replacement string into the Go syntax and store it
  27. // here.
  28. TransformedReplacement string
  29. re *regexp.Regexp
  30. }
  31. type metricRules []*metricRule
  32. // Go's regexp backreferences use `${1}` instead of the Perlish `\1`, so we must
  33. // transform the replacement string. This is non-trivial: `\1` is a
  34. // backreference but `\\1` is not. Rather than count the number of back slashes
  35. // preceding the digit, we simply skip rules with tricky replacements.
  36. var (
  37. transformReplacementAmbiguous = regexp.MustCompile(`\\\\([0-9]+)`)
  38. transformReplacementRegex = regexp.MustCompile(`\\([0-9]+)`)
  39. transformReplacementReplacement = "$${${1}}"
  40. )
  41. func (rules *metricRules) UnmarshalJSON(data []byte) (err error) {
  42. var raw []*metricRule
  43. if err := json.Unmarshal(data, &raw); nil != err {
  44. return err
  45. }
  46. valid := make(metricRules, 0, len(raw))
  47. for _, r := range raw {
  48. re, err := regexp.Compile("(?i)" + r.RawExpr)
  49. if err != nil {
  50. // TODO
  51. // Warn("unable to compile rule", {
  52. // "match_expression": r.RawExpr,
  53. // "error": err.Error(),
  54. // })
  55. continue
  56. }
  57. if transformReplacementAmbiguous.MatchString(r.OriginalReplacement) {
  58. // TODO
  59. // Warn("unable to transform replacement", {
  60. // "match_expression": r.RawExpr,
  61. // "replacement": r.OriginalReplacement,
  62. // })
  63. continue
  64. }
  65. r.re = re
  66. r.TransformedReplacement = transformReplacementRegex.ReplaceAllString(r.OriginalReplacement,
  67. transformReplacementReplacement)
  68. valid = append(valid, r)
  69. }
  70. sort.Sort(valid)
  71. *rules = valid
  72. return nil
  73. }
  74. func (rules metricRules) Len() int {
  75. return len(rules)
  76. }
  77. // Rules should be applied in increasing order
  78. func (rules metricRules) Less(i, j int) bool {
  79. return rules[i].Order < rules[j].Order
  80. }
  81. func (rules metricRules) Swap(i, j int) {
  82. rules[i], rules[j] = rules[j], rules[i]
  83. }
  84. func replaceFirst(re *regexp.Regexp, s string, replacement string) (ruleResult, string) {
  85. // Note that ReplaceAllStringFunc cannot be used here since it does
  86. // not replace $1 placeholders.
  87. loc := re.FindStringIndex(s)
  88. if nil == loc {
  89. return ruleUnmatched, s
  90. }
  91. firstMatch := s[loc[0]:loc[1]]
  92. firstMatchReplaced := re.ReplaceAllString(firstMatch, replacement)
  93. return ruleMatched, s[0:loc[0]] + firstMatchReplaced + s[loc[1]:]
  94. }
  95. func (r *metricRule) apply(s string) (ruleResult, string) {
  96. // Rules are strange, and there is no spec.
  97. // This code attempts to duplicate the logic of the PHP agent.
  98. // Ambiguity abounds.
  99. if r.Ignore {
  100. if r.re.MatchString(s) {
  101. return ruleIgnore, ""
  102. }
  103. return ruleUnmatched, s
  104. }
  105. if r.ReplaceAll {
  106. if r.re.MatchString(s) {
  107. return ruleMatched, r.re.ReplaceAllString(s, r.TransformedReplacement)
  108. }
  109. return ruleUnmatched, s
  110. } else if r.EachSegment {
  111. segments := strings.Split(s, "/")
  112. applied := make([]string, len(segments))
  113. result := ruleUnmatched
  114. for i, segment := range segments {
  115. var segmentMatched ruleResult
  116. segmentMatched, applied[i] = replaceFirst(r.re, segment, r.TransformedReplacement)
  117. if segmentMatched == ruleMatched {
  118. result = ruleMatched
  119. }
  120. }
  121. return result, strings.Join(applied, "/")
  122. } else {
  123. return replaceFirst(r.re, s, r.TransformedReplacement)
  124. }
  125. }
  126. func (rules metricRules) Apply(input string) string {
  127. var res ruleResult
  128. s := input
  129. for _, rule := range rules {
  130. res, s = rule.apply(s)
  131. if ruleIgnore == res {
  132. return ""
  133. }
  134. if (ruleMatched == res) && rule.Terminate {
  135. break
  136. }
  137. }
  138. return s
  139. }