github.com/yankunsam/loki/v2@v2.6.3-0.20220817130409-389df5235c27/pkg/logql/log/parser_hints.go (about) 1 package log 2 3 import ( 4 "strings" 5 ) 6 7 var noParserHints = &parserHint{} 8 9 // ParserHint are hints given to LogQL parsers. 10 // This is specially useful for parser that extract implicitly all possible label keys. 11 // This is used only within metric queries since it's rare that you need all label keys. 12 // For example in the following expression: 13 // 14 // sum by (status_code) (rate({app="foo"} | json [5m])) 15 // 16 // All we need to extract is the status_code in the json parser. 17 type ParserHint interface { 18 // Tells if a label with the given key should be extracted. 19 ShouldExtract(key string) bool 20 // Tells if there's any hint that start with the given prefix. 21 // This allows to speed up key searching in nested structured like json. 22 ShouldExtractPrefix(prefix string) bool 23 // Tells if we should not extract any labels. 24 // For example in : 25 // sum(rate({app="foo"} | json [5m])) 26 // We don't need to extract any labels from the log line. 27 NoLabels() bool 28 } 29 30 type parserHint struct { 31 noLabels bool 32 requiredLabels []string 33 } 34 35 func (p *parserHint) ShouldExtract(key string) bool { 36 if len(p.requiredLabels) == 0 { 37 return true 38 } 39 for _, l := range p.requiredLabels { 40 if l == key { 41 return true 42 } 43 } 44 return false 45 } 46 47 func (p *parserHint) ShouldExtractPrefix(prefix string) bool { 48 if len(p.requiredLabels) == 0 { 49 return true 50 } 51 for _, l := range p.requiredLabels { 52 if strings.HasPrefix(l, prefix) { 53 return true 54 } 55 } 56 57 return false 58 } 59 60 func (p *parserHint) NoLabels() bool { 61 return p.noLabels 62 } 63 64 // newParserHint creates a new parser hint using the list of labels that are seen and required in a query. 65 func newParserHint(requiredLabelNames, groups []string, without, noLabels bool, metricLabelName string) *parserHint { 66 hints := make([]string, 0, 2*(len(requiredLabelNames)+len(groups)+1)) 67 hints = appendLabelHints(hints, requiredLabelNames...) 68 hints = appendLabelHints(hints, groups...) 69 hints = appendLabelHints(hints, metricLabelName) 70 hints = uniqueString(hints) 71 if noLabels { 72 if len(hints) > 0 { 73 return &parserHint{requiredLabels: hints} 74 } 75 return &parserHint{noLabels: true} 76 } 77 // we don't know what is required when a without clause is used. 78 // Same is true when there's no grouping. 79 // no hints available then. 80 if without || len(groups) == 0 { 81 return noParserHints 82 } 83 return &parserHint{requiredLabels: hints} 84 } 85 86 // appendLabelHints Appends the label to the list of hints with and without the duplicate suffix. 87 // If a parsed label collides with a stream label we add the `_extracted` suffix to it, however hints 88 // are used by the parsers before we know they will collide with a stream label and hence before the 89 // _extracted suffix is added. Therefore we must strip the _extracted suffix from any required labels 90 // that were parsed from somewhere in the query, say in a filter or an aggregation clause. 91 // Because it's possible for a valid json or logfmt key to already end with _extracted, we'll just 92 // leave the existing entry ending with _extracted but also add a version with the suffix removed. 93 func appendLabelHints(dst []string, src ...string) []string { 94 for _, l := range src { 95 dst = append(dst, l) 96 if strings.HasSuffix(l, duplicateSuffix) { 97 dst = append(dst, strings.TrimSuffix(l, duplicateSuffix)) 98 } 99 } 100 return dst 101 }