github.com/influxdata/telegraf@v1.30.3/internal/templating/template.go (about) 1 package templating 2 3 import ( 4 "fmt" 5 "strings" 6 ) 7 8 // Template represents a pattern and tags to map a metric string to an influxdb Point 9 type Template struct { 10 separator string 11 parts []string 12 defaultTags map[string]string 13 greedyField bool 14 greedyMeasurement bool 15 } 16 17 // Apply extracts the template fields from the given line and returns the measurement 18 // name, tags and field name 19 // 20 //nolint:revive //function-result-limit conditionally 4 return results allowed 21 func (t *Template) Apply(line string, joiner string) (measurementName string, tags map[string]string, field string, err error) { 22 allFields := strings.Split(line, t.separator) 23 var ( 24 measurements []string 25 tagsMap = make(map[string][]string) 26 fields []string 27 ) 28 29 // Set any default tags 30 for k, v := range t.defaultTags { 31 tagsMap[k] = append(tagsMap[k], v) 32 } 33 34 // See if an invalid combination has been specified in the template: 35 for _, tag := range t.parts { 36 if tag == "measurement*" { 37 t.greedyMeasurement = true 38 } else if tag == "field*" { 39 t.greedyField = true 40 } 41 } 42 if t.greedyField && t.greedyMeasurement { 43 return "", nil, "", 44 fmt.Errorf("either 'field*' or 'measurement*' can be used in each "+ 45 "template (but not both together): %q", 46 strings.Join(t.parts, joiner)) 47 } 48 49 for i, tag := range t.parts { 50 if i >= len(allFields) { 51 continue 52 } 53 if tag == "" { 54 continue 55 } 56 57 switch tag { 58 case "measurement": 59 measurements = append(measurements, allFields[i]) 60 case "field": 61 fields = append(fields, allFields[i]) 62 case "field*": 63 fields = append(fields, allFields[i:]...) 64 case "measurement*": 65 measurements = append(measurements, allFields[i:]...) 66 default: 67 tagsMap[tag] = append(tagsMap[tag], allFields[i]) 68 } 69 } 70 71 // Convert to map of strings. 72 tags = make(map[string]string) 73 for k, values := range tagsMap { 74 tags[k] = strings.Join(values, joiner) 75 } 76 77 return strings.Join(measurements, joiner), tags, strings.Join(fields, joiner), nil 78 } 79 80 func NewDefaultTemplateWithPattern(pattern string) (*Template, error) { 81 return NewTemplate(DefaultSeparator, pattern, nil) 82 } 83 84 // NewTemplate returns a new template ensuring it has a measurement 85 // specified. 86 func NewTemplate(separator string, pattern string, defaultTags map[string]string) (*Template, error) { 87 parts := strings.Split(pattern, separator) 88 hasMeasurement := false 89 template := &Template{ 90 separator: separator, 91 parts: parts, 92 defaultTags: defaultTags, 93 } 94 95 for _, part := range parts { 96 if strings.HasPrefix(part, "measurement") { 97 hasMeasurement = true 98 } 99 if part == "measurement*" { 100 template.greedyMeasurement = true 101 } else if part == "field*" { 102 template.greedyField = true 103 } 104 } 105 106 if !hasMeasurement { 107 return nil, fmt.Errorf("no measurement specified for template. %q", pattern) 108 } 109 110 return template, nil 111 } 112 113 // templateSpec is a template string split in its constituent parts 114 type templateSpec struct { 115 separator string 116 filter string 117 template string 118 tagstring string 119 } 120 121 // templateSpecs is simply an array of template specs implementing the sorting interface 122 type templateSpecs []templateSpec 123 124 // Less reports whether the element with 125 // index j should sort before the element with index k. 126 func (e templateSpecs) Less(j, k int) bool { 127 jlen := len(e[j].filter) 128 klen := len(e[k].filter) 129 if jlen == 0 && klen != 0 { 130 return true 131 } 132 if klen == 0 && jlen != 0 { 133 return false 134 } 135 return strings.Count(e[j].template, e[j].separator) < 136 strings.Count(e[k].template, e[k].separator) 137 } 138 139 // Swap swaps the elements with indexes i and j. 140 func (e templateSpecs) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 141 142 // Len is the number of elements in the collection. 143 func (e templateSpecs) Len() int { return len(e) }