@ -2,11 +2,15 @@ package stages
import (
"strings"
"sync"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/mitchellh/mapstructure"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/model"
"github.com/grafana/loki/pkg/util/flagext"
)
const (
@ -44,9 +48,10 @@ func NewDocker(logger log.Logger, registerer prometheus.Registerer) (Stage, erro
type cri struct {
// bounded buffer for CRI-O Partial logs lines (identified with tag `P` till we reach first `F`)
partialLines map [ model . Fingerprint ] Entry
maxPartialLines int
base * Pipeline
partialLines map [ model . Fingerprint ] Entry
cfg * CriConfig
base * Pipeline
lock sync . Mutex
}
// implement Stage interface
@ -63,16 +68,17 @@ func (c *cri) Run(entry chan Entry) chan Entry {
// We received partial-line (tag: "P")
if e . Extracted [ "flags" ] == "P" {
if len ( c . partialLines ) > c . m axPartialLines {
if len ( c . partialLines ) >= c . cfg . M axPartialLines {
// Merge existing partialLines
entries := make ( [ ] Entry , 0 , len ( c . partialLines ) )
for _ , v := range c . partialLines {
entries = append ( entries , v )
}
level . Warn ( c . base . logger ) . Log ( "msg" , "cri stage: partial lines upperbound exceeded. merging it to single line" , "threshold" , MaxPartialLinesSize )
level . Warn ( c . base . logger ) . Log ( "msg" , "cri stage: partial lines upperbound exceeded. merging it to single line" , "threshold" , c . cfg . MaxPartialLines )
c . partialLines = make ( map [ model . Fingerprint ] Entry )
c . partialLines = make ( map [ model . Fingerprint ] Entry , c . cfg . MaxPartialLines )
c . ensureTruncateIfRequired ( & e )
c . partialLines [ fingerprint ] = e
return entries , false
@ -80,8 +86,12 @@ func (c *cri) Run(entry chan Entry) chan Entry {
prev , ok := c . partialLines [ fingerprint ]
if ok {
e . Line = strings . Join ( [ ] string { prev . Line , e . Line } , "" )
var builder strings . Builder
builder . WriteString ( prev . Line )
builder . WriteString ( e . Line )
e . Line = builder . String ( )
}
c . ensureTruncateIfRequired ( & e )
c . partialLines [ fingerprint ] = e
return [ ] Entry { e } , true // it's a partial-line so skip it.
@ -92,7 +102,11 @@ func (c *cri) Run(entry chan Entry) chan Entry {
// 2. Else just return the full line.
prev , ok := c . partialLines [ fingerprint ]
if ok {
e . Line = strings . Join ( [ ] string { prev . Line , e . Line } , "" )
var builder strings . Builder
builder . WriteString ( prev . Line )
builder . WriteString ( e . Line )
e . Line = builder . String ( )
c . ensureTruncateIfRequired ( & e )
delete ( c . partialLines , fingerprint )
}
return [ ] Entry { e } , false
@ -101,8 +115,29 @@ func (c *cri) Run(entry chan Entry) chan Entry {
return in
}
func ( c * cri ) ensureTruncateIfRequired ( e * Entry ) {
if c . cfg . MaxPartialLineSizeTruncate && len ( e . Line ) > c . cfg . MaxPartialLineSize . Val ( ) {
e . Line = e . Line [ : c . cfg . MaxPartialLineSize . Val ( ) ]
}
}
// CriConfig contains the configuration for the cri stage
type CriConfig struct {
MaxPartialLines int ` mapstructure:"max_partial_lines" `
MaxPartialLineSize flagext . ByteSize ` mapstructure:"max_partial_line_size" `
MaxPartialLineSizeTruncate bool ` mapstructure:"max_partial_line_size_truncate" `
}
// validateDropConfig validates the DropConfig for the dropStage
func validateCriConfig ( cfg * CriConfig ) error {
if cfg . MaxPartialLines == 0 {
cfg . MaxPartialLines = MaxPartialLinesSize
}
return nil
}
// NewCRI creates a CRI format specific pipeline stage
func NewCRI ( logger log . Logger , registerer prometheus . Registerer ) ( Stage , error ) {
func NewCRI ( logger log . Logger , config interface { } , registerer prometheus . Registerer ) ( Stage , error ) {
base := PipelineStages {
PipelineStage {
StageTypeRegex : RegexConfig {
@ -137,10 +172,19 @@ func NewCRI(logger log.Logger, registerer prometheus.Registerer) (Stage, error)
return nil , err
}
cfg := & CriConfig { }
if err := mapstructure . WeakDecode ( config , cfg ) ; err != nil {
return nil , err
}
if err = validateCriConfig ( cfg ) ; err != nil {
return nil , err
}
c := cri {
maxPartialLines : MaxPartialLinesSize ,
base : p ,
cfg : cfg ,
base : p ,
}
c . partialLines = make ( map [ model . Fingerprint ] Entry )
c . partialLines = make ( map [ model . Fingerprint ] Entry , c . cfg . MaxPartialLines )
return & c , nil
}