Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
loki/pkg/logql/log/parser_hints.go

101 lines
3.2 KiB

package log
import (
"strings"
)
var noParserHints = &parserHint{}
// ParserHint are hints given to LogQL parsers.
// This is specially useful for parser that extract implicitly all possible label keys.
// This is used only within metric queries since it's rare that you need all label keys.
// For example in the following expression:
//
// sum by (status_code) (rate({app="foo"} | json [5m]))
//
// All we need to extract is the status_code in the json parser.
type ParserHint interface {
// Tells if a label with the given key should be extracted.
ShouldExtract(key string) bool
// Tells if there's any hint that start with the given prefix.
// This allows to speed up key searching in nested structured like json.
ShouldExtractPrefix(prefix string) bool
// Tells if we should not extract any labels.
// For example in :
// sum(rate({app="foo"} | json [5m]))
// We don't need to extract any labels from the log line.
NoLabels() bool
}
type parserHint struct {
noLabels bool
requiredLabels []string
}
func (p *parserHint) ShouldExtract(key string) bool {
if len(p.requiredLabels) == 0 {
return true
}
for _, l := range p.requiredLabels {
if l == key {
return true
}
}
return false
}
func (p *parserHint) ShouldExtractPrefix(prefix string) bool {
if len(p.requiredLabels) == 0 {
return true
}
for _, l := range p.requiredLabels {
if strings.HasPrefix(l, prefix) {
return true
}
}
return false
}
func (p *parserHint) NoLabels() bool {
return p.noLabels
}
// newParserHint creates a new parser hint using the list of labels that are seen and required in a query.
func newParserHint(requiredLabelNames, groups []string, without, noLabels bool, metricLabelName string) *parserHint {
hints := make([]string, 0, 2*(len(requiredLabelNames)+len(groups)+1))
hints = appendLabelHints(hints, requiredLabelNames...)
hints = appendLabelHints(hints, groups...)
hints = appendLabelHints(hints, metricLabelName)
hints = uniqueString(hints)
if noLabels {
if len(hints) > 0 {
return &parserHint{requiredLabels: hints}
}
return &parserHint{noLabels: true}
}
// we don't know what is required when a without clause is used.
// Same is true when there's no grouping.
// no hints available then.
if without || len(groups) == 0 {
return noParserHints
}
return &parserHint{requiredLabels: hints}
}
// appendLabelHints Appends the label to the list of hints with and without the duplicate suffix.
// If a parsed label collides with a stream label we add the `_extracted` suffix to it, however hints
// are used by the parsers before we know they will collide with a stream label and hence before the
// _extracted suffix is added. Therefore we must strip the _extracted suffix from any required labels
// that were parsed from somewhere in the query, say in a filter or an aggregation clause.
// Because it's possible for a valid json or logfmt key to already end with _extracted, we'll just
// leave the existing entry ending with _extracted but also add a version with the suffix removed.
func appendLabelHints(dst []string, src ...string) []string {
for _, l := range src {
dst = append(dst, l)
if strings.HasSuffix(l, duplicateSuffix) {
dst = append(dst, strings.TrimSuffix(l, duplicateSuffix))
}
}
return dst
}