146 lines
2.7 KiB
Go
146 lines
2.7 KiB
Go
|
package internal
|
||
|
|
||
|
// https://newrelic.atlassian.net/wiki/display/eng/Language+agent+transaction+segment+terms+rules
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
placeholder = "*"
|
||
|
separator = "/"
|
||
|
)
|
||
|
|
||
|
type segmentRule struct {
|
||
|
Prefix string `json:"prefix"`
|
||
|
Terms []string `json:"terms"`
|
||
|
TermsMap map[string]struct{}
|
||
|
}
|
||
|
|
||
|
// segmentRules is keyed by each segmentRule's Prefix field with any trailing
|
||
|
// slash removed.
|
||
|
type segmentRules map[string]*segmentRule
|
||
|
|
||
|
func buildTermsMap(terms []string) map[string]struct{} {
|
||
|
m := make(map[string]struct{}, len(terms))
|
||
|
for _, t := range terms {
|
||
|
m[t] = struct{}{}
|
||
|
}
|
||
|
return m
|
||
|
}
|
||
|
|
||
|
func (rules *segmentRules) UnmarshalJSON(b []byte) error {
|
||
|
var raw []*segmentRule
|
||
|
|
||
|
if err := json.Unmarshal(b, &raw); nil != err {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
rs := make(map[string]*segmentRule)
|
||
|
|
||
|
for _, rule := range raw {
|
||
|
prefix := strings.TrimSuffix(rule.Prefix, "/")
|
||
|
if len(strings.Split(prefix, "/")) != 2 {
|
||
|
// TODO
|
||
|
// Warn("invalid segment term rule prefix",
|
||
|
// {"prefix": rule.Prefix})
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
if nil == rule.Terms {
|
||
|
// TODO
|
||
|
// Warn("segment term rule has missing terms",
|
||
|
// {"prefix": rule.Prefix})
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
rule.TermsMap = buildTermsMap(rule.Terms)
|
||
|
|
||
|
rs[prefix] = rule
|
||
|
}
|
||
|
|
||
|
*rules = rs
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (rule *segmentRule) apply(name string) string {
|
||
|
if !strings.HasPrefix(name, rule.Prefix) {
|
||
|
return name
|
||
|
}
|
||
|
|
||
|
s := strings.TrimPrefix(name, rule.Prefix)
|
||
|
|
||
|
leadingSlash := ""
|
||
|
if strings.HasPrefix(s, separator) {
|
||
|
leadingSlash = separator
|
||
|
s = strings.TrimPrefix(s, separator)
|
||
|
}
|
||
|
|
||
|
if "" != s {
|
||
|
segments := strings.Split(s, separator)
|
||
|
|
||
|
for i, segment := range segments {
|
||
|
_, whitelisted := rule.TermsMap[segment]
|
||
|
if whitelisted {
|
||
|
segments[i] = segment
|
||
|
} else {
|
||
|
segments[i] = placeholder
|
||
|
}
|
||
|
}
|
||
|
|
||
|
segments = collapsePlaceholders(segments)
|
||
|
s = strings.Join(segments, separator)
|
||
|
}
|
||
|
|
||
|
return rule.Prefix + leadingSlash + s
|
||
|
}
|
||
|
|
||
|
func (rules segmentRules) apply(name string) string {
|
||
|
if nil == rules {
|
||
|
return name
|
||
|
}
|
||
|
|
||
|
rule, ok := rules[firstTwoSegments(name)]
|
||
|
if !ok {
|
||
|
return name
|
||
|
}
|
||
|
|
||
|
return rule.apply(name)
|
||
|
}
|
||
|
|
||
|
func firstTwoSegments(name string) string {
|
||
|
firstSlashIdx := strings.Index(name, separator)
|
||
|
if firstSlashIdx == -1 {
|
||
|
return name
|
||
|
}
|
||
|
|
||
|
secondSlashIdx := strings.Index(name[firstSlashIdx+1:], separator)
|
||
|
if secondSlashIdx == -1 {
|
||
|
return name
|
||
|
}
|
||
|
|
||
|
return name[0 : firstSlashIdx+secondSlashIdx+1]
|
||
|
}
|
||
|
|
||
|
func collapsePlaceholders(segments []string) []string {
|
||
|
j := 0
|
||
|
prevStar := false
|
||
|
for i := 0; i < len(segments); i++ {
|
||
|
segment := segments[i]
|
||
|
if placeholder == segment {
|
||
|
if prevStar {
|
||
|
continue
|
||
|
}
|
||
|
segments[j] = placeholder
|
||
|
j++
|
||
|
prevStar = true
|
||
|
} else {
|
||
|
segments[j] = segment
|
||
|
j++
|
||
|
prevStar = false
|
||
|
}
|
||
|
}
|
||
|
return segments[0:j]
|
||
|
}
|