@@ -8,9 +8,11 @@ import (
88 "io"
99 "maps"
1010 "os"
11+ "time"
1112 "slices"
1213 "strings"
1314
15+ "github.com/cenkalti/backoff/v5"
1416 "github.com/expr-lang/expr"
1517 "github.com/expr-lang/expr/vm"
1618 "github.com/goccy/go-yaml"
@@ -69,6 +71,11 @@ type Tailer interface {
6971 StreamingAcquisition (ctx context.Context , out chan pipeline.Event , acquisTomb * tomb.Tomb ) error
7072}
7173
74+ // RestartableStreamer works Like Tailer but should return any error and leave the retry logic to the caller
75+ type RestartableStreamer interface {
76+ Stream (ctx context.Context , out chan pipeline.Event ) error
77+ }
78+
7279type MetricsProvider interface {
7380 // Returns pointers to metrics that are managed by the module
7481 GetMetrics () []prometheus.Collector
@@ -121,14 +128,14 @@ func registerDataSource(dataSourceType string, dsGetter func() DataSource) {
121128}
122129
123130// setupLogger creates a logger for the datasource to use at runtime.
124- func setupLogger (source , name string , level log.Level ) (* log.Entry , error ) {
131+ func setupLogger (typ , name string , level log.Level ) (* log.Entry , error ) {
125132 clog := log .New ()
126133 if err := logging .ConfigureLogger (clog , level ); err != nil {
127- return nil , fmt .Errorf ("while configuring datasource logger: %w" , err )
134+ return nil , fmt .Errorf ("configuring datasource logger: %w" , err )
128135 }
129136
130137 fields := log.Fields {
131- "type" : source ,
138+ "type" : typ ,
132139 }
133140
134141 if name != "" {
@@ -167,18 +174,20 @@ func DataSourceConfigure(ctx context.Context, commonConfig configuration.DataSou
167174 return dataSrc , nil
168175}
169176
170- func LoadAcquisitionFromDSN (ctx context.Context , dsn string , labels map [string ]string , transformExpr string ) ([] DataSource , error ) {
177+ func LoadAcquisitionFromDSN (ctx context.Context , dsn string , labels map [string ]string , transformExpr string ) (DataSource , error ) {
171178 frags := strings .Split (dsn , ":" )
172179 if len (frags ) == 1 {
173- return nil , fmt .Errorf ("%s isn't valid dsn (no protocol)" , dsn )
180+ return nil , fmt .Errorf ("%s is not a valid dsn (no protocol)" , dsn )
174181 }
175182
176183 dataSrc , err := GetDataSourceIface (frags [0 ])
177184 if err != nil {
178185 return nil , fmt .Errorf ("no acquisition for protocol %s:// - %w" , frags [0 ], err )
179186 }
180187
181- subLogger , err := setupLogger (dsn , "" , 0 )
188+ typ := labels ["type" ]
189+
190+ subLogger , err := setupLogger (typ , "" , 0 )
182191 if err != nil {
183192 return nil , err
184193 }
@@ -200,10 +209,10 @@ func LoadAcquisitionFromDSN(ctx context.Context, dsn string, labels map[string]s
200209 }
201210
202211 if err = dsnConf .ConfigureByDSN (ctx , dsn , labels , subLogger , uniqueID ); err != nil {
203- return nil , fmt .Errorf ("while configuration datasource for %s : %w" , dsn , err )
212+ return nil , fmt .Errorf ("configuring datasource for %q : %w" , dsn , err )
204213 }
205214
206- return [] DataSource { dataSrc } , nil
215+ return dataSrc , nil
207216}
208217
209218func GetMetricsLevelFromPromCfg (prom * csconfig.PrometheusCfg ) metrics.AcquisitionMetricsLevel {
@@ -349,7 +358,7 @@ func sourcesFromFile(ctx context.Context, acquisFile string, metricsLevel metric
349358 continue
350359 }
351360
352- return nil , fmt .Errorf ("while configuring datasource of type %s from %s (position %d): %w" , sub .Source , acquisFile , idx , err )
361+ return nil , fmt .Errorf ("configuring datasource of type %s from %s (position %d): %w" , sub .Source , acquisFile , idx , err )
353362 }
354363
355364 if sub .TransformExpr != "" {
@@ -484,6 +493,72 @@ func transform(transformChan chan pipeline.Event, output chan pipeline.Event, ac
484493 }
485494}
486495
496+
497+ func runRestartableStream (ctx context.Context , rs RestartableStreamer , name string , output chan pipeline.Event , acquisTomb * tomb.Tomb ) error {
498+ // wrap tomb logic with context
499+ ctx , cancel := context .WithCancel (ctx )
500+ go func () {
501+ <- acquisTomb .Dying ()
502+ cancel ()
503+ }()
504+
505+ acquisTomb .Go (func () error {
506+ // TODO: check timing and exponential?
507+ bo := backoff .NewConstantBackOff (10 * time .Second )
508+ bo .Reset () // TODO: reset according to run time
509+
510+ for {
511+ select {
512+ case <- ctx .Done ():
513+ return nil
514+ default :
515+ }
516+
517+ if err := rs .Stream (ctx , output ); err != nil {
518+ log .Errorf ("datasource %q: stream error: %v (retrying)" , name , err )
519+ }
520+
521+ select {
522+ case <- ctx .Done ():
523+ return nil
524+ default :
525+ }
526+
527+ d := bo .NextBackOff ()
528+ log .Infof ("datasource %q: restarting stream in %s" , name , d )
529+
530+ select {
531+ case <- ctx .Done ():
532+ return nil
533+ case <- time .After (d ):
534+ }
535+ }
536+ })
537+
538+ return nil
539+ }
540+
541+
542+ func acquireSource (ctx context.Context , source DataSource , name string , output chan pipeline.Event , acquisTomb * tomb.Tomb ) error {
543+ if source .GetMode () == configuration .CAT_MODE {
544+ if s , ok := source .(Fetcher ); ok {
545+ return s .OneShotAcquisition (ctx , output , acquisTomb )
546+ }
547+
548+ return fmt .Errorf ("%s: cat mode is set but OneShotAcquisition is not supported" , source .GetName ())
549+ }
550+
551+ if s , ok := source .(Tailer ); ok {
552+ return s .StreamingAcquisition (ctx , output , acquisTomb )
553+ }
554+
555+ if s , ok := source .(RestartableStreamer ); ok {
556+ return runRestartableStream (ctx , s , name , output , acquisTomb )
557+ }
558+
559+ return fmt .Errorf ("%s: tail mode is set but the datasource does not support streaming acquisition" , source .GetName ())
560+ }
561+
487562func StartAcquisition (ctx context.Context , sources []DataSource , output chan pipeline.Event , acquisTomb * tomb.Tomb ) error {
488563 // Don't wait if we have no sources, as it will hang forever
489564 if len (sources ) == 0 {
@@ -497,8 +572,6 @@ func StartAcquisition(ctx context.Context, sources []DataSource, output chan pip
497572 acquisTomb .Go (func () error {
498573 defer trace .CatchPanic ("crowdsec/acquis" )
499574
500- var err error
501-
502575 outChan := output
503576
504577 log .Debugf ("datasource %s UUID: %s" , subsrc .GetName (), subsrc .GetUuid ())
@@ -519,21 +592,7 @@ func StartAcquisition(ctx context.Context, sources []DataSource, output chan pip
519592 })
520593 }
521594
522- if subsrc .GetMode () == configuration .TAIL_MODE {
523- if s , ok := subsrc .(Tailer ); ok {
524- err = s .StreamingAcquisition (ctx , outChan , acquisTomb )
525- } else {
526- err = fmt .Errorf ("%s: tail mode is set but StreamingAcquisition is not supported" , subsrc .GetName ())
527- }
528- } else {
529- if s , ok := subsrc .(Fetcher ); ok {
530- err = s .OneShotAcquisition (ctx , outChan , acquisTomb )
531- } else {
532- err = fmt .Errorf ("%s: cat mode is set but OneShotAcquisition is not supported" , subsrc .GetName ())
533- }
534- }
535-
536- if err != nil {
595+ if err := acquireSource (ctx , subsrc , subsrc .GetName (), output , acquisTomb ); err != nil {
537596 // if one of the acqusition returns an error, we kill the others to properly shutdown
538597 acquisTomb .Kill (err )
539598 }
0 commit comments