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  }