@@ -13,6 +13,8 @@ import (
1313
1414 "github.com/grafana/loki/v3/pkg/loghttp/push"
1515 "github.com/grafana/loki/v3/pkg/logproto"
16+ "github.com/grafana/loki/v3/pkg/logql/log"
17+ "github.com/grafana/loki/v3/pkg/logql/log/jsonexpr"
1618 "github.com/grafana/loki/v3/pkg/logql/log/logfmt"
1719 "github.com/grafana/loki/v3/pkg/util/constants"
1820)
@@ -31,46 +33,43 @@ var (
3133 errorAbbrv = []byte ("err" )
3234 critical = []byte ("critical" )
3335 fatal = []byte ("fatal" )
36+
37+ defaultAllowedLevelFields = []string {"level" , "LEVEL" , "Level" , "severity" , "SEVERITY" , "Severity" , "lvl" , "LVL" , "Lvl" }
3438)
3539
36- func allowedLabelsForLevel (allowedFields []string ) map [ string ] struct {} {
40+ func allowedLabelsForLevel (allowedFields []string ) [] string {
3741 if len (allowedFields ) == 0 {
38- return map [string ]struct {}{
39- "level" : {}, "LEVEL" : {}, "Level" : {},
40- "severity" : {}, "SEVERITY" : {}, "Severity" : {},
41- "lvl" : {}, "LVL" : {}, "Lvl" : {},
42- }
43- }
44- allowedFieldsMap := make (map [string ]struct {}, len (allowedFields ))
45- for _ , field := range allowedFields {
46- allowedFieldsMap [field ] = struct {}{}
42+ return defaultAllowedLevelFields
4743 }
48- return allowedFieldsMap
44+ return allowedFields
4945}
5046
51- type LevelDetector struct {
52- validationContext validationContext
53- allowedLabels map [ string ] struct {}
47+ type FieldDetector struct {
48+ validationContext validationContext
49+ allowedLevelLabels [] string
5450}
5551
56- func newLevelDetector (validationContext validationContext ) * LevelDetector {
57- logLevelFields := validationContext .logLevelFields
58- return & LevelDetector {
59- validationContext : validationContext ,
60- allowedLabels : allowedLabelsForLevel (logLevelFields ),
52+ func newFieldDetector (validationContext validationContext ) * FieldDetector {
53+ return & FieldDetector {
54+ validationContext : validationContext ,
55+ allowedLevelLabels : allowedLabelsForLevel (validationContext .logLevelFields ),
6156 }
6257}
6358
64- func (l * LevelDetector ) shouldDiscoverLogLevels () bool {
59+ func (l * FieldDetector ) shouldDiscoverLogLevels () bool {
6560 return l .validationContext .allowStructuredMetadata && l .validationContext .discoverLogLevels
6661}
6762
68- func (l * LevelDetector ) extractLogLevel (labels labels.Labels , structuredMetadata labels.Labels , entry logproto.Entry ) (logproto.LabelAdapter , bool ) {
69- levelFromLabel , hasLevelLabel := l .hasAnyLevelLabels (labels )
63+ func (l * FieldDetector ) shouldDiscoverGenericFields () bool {
64+ return l .validationContext .allowStructuredMetadata && len (l .validationContext .discoverGenericFields ) > 0
65+ }
66+
67+ func (l * FieldDetector ) extractLogLevel (labels labels.Labels , structuredMetadata labels.Labels , entry logproto.Entry ) (logproto.LabelAdapter , bool ) {
68+ levelFromLabel , hasLevelLabel := labelsContainAny (labels , l .allowedLevelLabels )
7069 var logLevel string
7170 if hasLevelLabel {
7271 logLevel = levelFromLabel
73- } else if levelFromMetadata , ok := l . hasAnyLevelLabels (structuredMetadata ); ok {
72+ } else if levelFromMetadata , ok := labelsContainAny (structuredMetadata , l . allowedLevelLabels ); ok {
7473 logLevel = levelFromMetadata
7574 } else {
7675 logLevel = l .detectLogLevelFromLogEntry (entry , structuredMetadata )
@@ -85,16 +84,33 @@ func (l *LevelDetector) extractLogLevel(labels labels.Labels, structuredMetadata
8584 }, true
8685}
8786
88- func (l * LevelDetector ) hasAnyLevelLabels (labels labels.Labels ) (string , bool ) {
89- for lbl := range l .allowedLabels {
90- if labels .Has (lbl ) {
91- return labels .Get (lbl ), true
87+ func (l * FieldDetector ) extractGenericField (name string , hints []string , labels labels.Labels , structuredMetadata labels.Labels , entry logproto.Entry ) (logproto.LabelAdapter , bool ) {
88+
89+ var value string
90+ if v , ok := labelsContainAny (labels , hints ); ok {
91+ value = v
92+ } else if v , ok := labelsContainAny (structuredMetadata , hints ); ok {
93+ value = v
94+ } else {
95+ value = l .detectGenericFieldFromLogEntry (entry , hints )
96+ }
97+
98+ if value == "" {
99+ return logproto.LabelAdapter {}, false
100+ }
101+ return logproto.LabelAdapter {Name : name , Value : value }, true
102+ }
103+
104+ func labelsContainAny (labels labels.Labels , names []string ) (string , bool ) {
105+ for _ , name := range names {
106+ if labels .Has (name ) {
107+ return labels .Get (name ), true
92108 }
93109 }
94110 return "" , false
95111}
96112
97- func (l * LevelDetector ) detectLogLevelFromLogEntry (entry logproto.Entry , structuredMetadata labels.Labels ) string {
113+ func (l * FieldDetector ) detectLogLevelFromLogEntry (entry logproto.Entry , structuredMetadata labels.Labels ) string {
98114 // otlp logs have a severity number, using which we are defining the log levels.
99115 // Significance of severity number is explained in otel docs here https://opentelemetry.io/docs/specs/otel/logs/data-model/#field-severitynumber
100116 if otlpSeverityNumberTxt := structuredMetadata .Get (push .OTLPSeverityNumber ); otlpSeverityNumberTxt != "" {
@@ -123,13 +139,24 @@ func (l *LevelDetector) detectLogLevelFromLogEntry(entry logproto.Entry, structu
123139 return l .extractLogLevelFromLogLine (entry .Line )
124140}
125141
126- func (l * LevelDetector ) extractLogLevelFromLogLine (log string ) string {
127- logSlice := unsafe .Slice (unsafe .StringData (log ), len (log ))
142+ func (l * FieldDetector ) detectGenericFieldFromLogEntry (entry logproto.Entry , hints []string ) string {
143+ lineBytes := unsafe .Slice (unsafe .StringData (entry .Line ), len (entry .Line ))
144+ var v []byte
145+ if isJSON (entry .Line ) {
146+ v = getValueUsingJSONParser (lineBytes , hints )
147+ } else if isLogFmt (lineBytes ) {
148+ v = getValueUsingLogfmtParser (lineBytes , hints )
149+ }
150+ return string (v )
151+ }
152+
153+ func (l * FieldDetector ) extractLogLevelFromLogLine (log string ) string {
154+ lineBytes := unsafe .Slice (unsafe .StringData (log ), len (log ))
128155 var v []byte
129156 if isJSON (log ) {
130- v = l . getValueUsingJSONParser (logSlice )
131- } else if isLogFmt (logSlice ) {
132- v = l . getValueUsingLogfmtParser (logSlice )
157+ v = getValueUsingJSONParser (lineBytes , l . allowedLevelLabels )
158+ } else if isLogFmt (lineBytes ) {
159+ v = getValueUsingLogfmtParser (lineBytes , l . allowedLevelLabels )
133160 } else {
134161 return detectLevelFromLogLine (log )
135162 }
@@ -154,24 +181,42 @@ func (l *LevelDetector) extractLogLevelFromLogLine(log string) string {
154181 }
155182}
156183
157- func ( l * LevelDetector ) getValueUsingLogfmtParser (line []byte ) []byte {
184+ func getValueUsingLogfmtParser (line []byte , hints [] string ) []byte {
158185 d := logfmt .NewDecoder (line )
186+ // In order to have the same behaviour as the JSON field extraction,
187+ // the full line needs to be parsed to extract all possible matching fields.
188+ pos := len (hints ) // the index of the hint that matches
189+ var res []byte
159190 for ! d .EOL () && d .ScanKeyval () {
160- if _ , ok := l .allowedLabels [string (d .Key ())]; ok {
161- return d .Value ()
191+ k := unsafe .String (unsafe .SliceData (d .Key ()), len (d .Key ()))
192+ for x , hint := range hints {
193+ if strings .EqualFold (k , hint ) && x < pos {
194+ res , pos = d .Value (), x
195+ // If there is only a single hint, or the matching hint is the first one,
196+ // we can stop parsing the rest of the line and return early.
197+ if x == 0 {
198+ return res
199+ }
200+ }
162201 }
163202 }
164- return nil
203+ return res
165204}
166205
167- func (l * LevelDetector ) getValueUsingJSONParser (log []byte ) []byte {
168- for allowedLabel := range l .allowedLabels {
169- l , _ , _ , err := jsonparser .Get (log , allowedLabel )
170- if err == nil {
171- return l
206+ func getValueUsingJSONParser (line []byte , hints []string ) []byte {
207+ var res []byte
208+ for _ , allowedLabel := range hints {
209+ parsed , err := jsonexpr .Parse (allowedLabel , false )
210+ if err != nil {
211+ continue
212+ }
213+ l , _ , _ , err := jsonparser .Get (line , log .JSONPathToStrings (parsed )... )
214+ if err != nil {
215+ continue
172216 }
217+ return l
173218 }
174- return nil
219+ return res
175220}
176221
177222func isLogFmt (line []byte ) bool {
0 commit comments