@ -26,13 +26,16 @@ import (
"github.com/prometheus/prometheus/model/labels"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/plog"
"google.golang.org/grpc"
"google.golang.org/grpc/health/grpc_health_v1"
"github.com/grafana/loki/pkg/ingester"
"github.com/grafana/loki/pkg/ingester/client"
loghttp_push "github.com/grafana/loki/pkg/loghttp/push"
"github.com/grafana/loki/pkg/logproto"
"github.com/grafana/loki/pkg/logql/syntax"
"github.com/grafana/loki/pkg/push"
"github.com/grafana/loki/pkg/runtime"
"github.com/grafana/loki/pkg/util/constants"
fe "github.com/grafana/loki/pkg/util/flagext"
@ -1491,3 +1494,146 @@ func TestDistributorTee(t *testing.T) {
require . Equal ( t , "test" , tee . tenant )
}
}
func Test_DetectLogLevels ( t * testing . T ) {
setup := func ( discoverLogLevels bool ) ( * validation . Limits , * mockIngester ) {
limits := & validation . Limits { }
flagext . DefaultValues ( limits )
limits . DiscoverLogLevels = discoverLogLevels
limits . DiscoverServiceName = nil
limits . AllowStructuredMetadata = true
return limits , & mockIngester { }
}
t . Run ( "log level detection disabled" , func ( t * testing . T ) {
limits , ingester := setup ( false )
distributors , _ := prepare ( t , 1 , 5 , limits , func ( addr string ) ( ring_client . PoolClient , error ) { return ingester , nil } )
writeReq := makeWriteRequestWithLabels ( 1 , 10 , [ ] string { ` { foo="bar"} ` } )
_ , err := distributors [ 0 ] . Push ( ctx , writeReq )
require . NoError ( t , err )
topVal := ingester . Peek ( )
require . Equal ( t , ` { foo="bar"} ` , topVal . Streams [ 0 ] . Labels )
require . Len ( t , topVal . Streams [ 0 ] . Entries [ 0 ] . StructuredMetadata , 0 )
} )
t . Run ( "log level detection enabled" , func ( t * testing . T ) {
limits , ingester := setup ( true )
distributors , _ := prepare ( t , 1 , 5 , limits , func ( addr string ) ( ring_client . PoolClient , error ) { return ingester , nil } )
writeReq := makeWriteRequestWithLabels ( 1 , 10 , [ ] string { ` { foo="bar"} ` } )
_ , err := distributors [ 0 ] . Push ( ctx , writeReq )
require . NoError ( t , err )
topVal := ingester . Peek ( )
require . Equal ( t , ` { foo="bar"} ` , topVal . Streams [ 0 ] . Labels )
require . Equal ( t , push . LabelsAdapter {
{
Name : labelLevel ,
Value : logLevelInfo ,
} ,
} , topVal . Streams [ 0 ] . Entries [ 0 ] . StructuredMetadata )
} )
t . Run ( "log level detection enabled but log level already present in stream" , func ( t * testing . T ) {
limits , ingester := setup ( true )
distributors , _ := prepare ( t , 1 , 5 , limits , func ( addr string ) ( ring_client . PoolClient , error ) { return ingester , nil } )
writeReq := makeWriteRequestWithLabels ( 1 , 10 , [ ] string { ` { foo="bar", level="debug"} ` } )
_ , err := distributors [ 0 ] . Push ( ctx , writeReq )
require . NoError ( t , err )
topVal := ingester . Peek ( )
require . Equal ( t , ` { foo="bar", level="debug"} ` , topVal . Streams [ 0 ] . Labels )
require . Len ( t , topVal . Streams [ 0 ] . Entries [ 0 ] . StructuredMetadata , 0 )
} )
t . Run ( "log level detection enabled but log level already present as structured metadata" , func ( t * testing . T ) {
limits , ingester := setup ( true )
distributors , _ := prepare ( t , 1 , 5 , limits , func ( addr string ) ( ring_client . PoolClient , error ) { return ingester , nil } )
writeReq := makeWriteRequestWithLabels ( 1 , 10 , [ ] string { ` { foo="bar"} ` } )
writeReq . Streams [ 0 ] . Entries [ 0 ] . StructuredMetadata = push . LabelsAdapter {
{
Name : labelLevel ,
Value : logLevelWarn ,
} ,
}
_ , err := distributors [ 0 ] . Push ( ctx , writeReq )
require . NoError ( t , err )
topVal := ingester . Peek ( )
require . Equal ( t , ` { foo="bar"} ` , topVal . Streams [ 0 ] . Labels )
require . Equal ( t , push . LabelsAdapter {
{
Name : labelLevel ,
Value : logLevelWarn ,
} ,
} , topVal . Streams [ 0 ] . Entries [ 0 ] . StructuredMetadata )
} )
}
func Test_detectLogLevelFromLogEntry ( t * testing . T ) {
for _ , tc := range [ ] struct {
name string
entry logproto . Entry
expectedLogLevel string
} {
{
name : "use severity number from otlp logs" ,
entry : logproto . Entry {
Line : "error" ,
StructuredMetadata : push . LabelsAdapter {
{
Name : loghttp_push . OTLPSeverityNumber ,
Value : fmt . Sprintf ( "%d" , plog . SeverityNumberDebug3 ) ,
} ,
} ,
} ,
expectedLogLevel : logLevelDebug ,
} ,
{
name : "invalid severity number should not cause any issues" ,
entry : logproto . Entry {
StructuredMetadata : push . LabelsAdapter {
{
Name : loghttp_push . OTLPSeverityNumber ,
Value : "foo" ,
} ,
} ,
} ,
expectedLogLevel : logLevelInfo ,
} ,
{
name : "non otlp without any of the log level keywords in log line" ,
entry : logproto . Entry {
Line : "foo" ,
} ,
expectedLogLevel : logLevelInfo ,
} ,
{
name : "non otlp with log level keywords in log line" ,
entry : logproto . Entry {
Line : "this is a warning log" ,
} ,
expectedLogLevel : logLevelWarn ,
} ,
{
name : "json log line with an error" ,
entry : logproto . Entry {
Line : ` { "foo":"bar","level":"error"} ` ,
} ,
expectedLogLevel : logLevelError ,
} ,
{
name : "logfmt log line with a warn" ,
entry : logproto . Entry {
Line : ` foo=bar level=warn ` ,
} ,
expectedLogLevel : logLevelWarn ,
} ,
} {
t . Run ( tc . name , func ( t * testing . T ) {
detectedLogLevel := detectLogLevelFromLogEntry ( tc . entry , logproto . FromLabelAdaptersToLabels ( tc . entry . StructuredMetadata ) )
require . Equal ( t , tc . expectedLogLevel , detectedLogLevel )
} )
}
}