github.com/crowdsecurity/crowdsec@v1.6.1/pkg/acquisition/modules/loki/loki.go (about) 1 package loki 2 3 /* 4 https://grafana.com/docs/loki/latest/api/#get-lokiapiv1tail 5 */ 6 7 import ( 8 "context" 9 "errors" 10 "fmt" 11 "net/url" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/prometheus/client_golang/prometheus" 17 log "github.com/sirupsen/logrus" 18 tomb "gopkg.in/tomb.v2" 19 yaml "gopkg.in/yaml.v2" 20 21 "github.com/crowdsecurity/crowdsec/pkg/acquisition/configuration" 22 lokiclient "github.com/crowdsecurity/crowdsec/pkg/acquisition/modules/loki/internal/lokiclient" 23 "github.com/crowdsecurity/crowdsec/pkg/types" 24 ) 25 26 const ( 27 readyTimeout time.Duration = 3 * time.Second 28 readyLoop int = 3 29 readySleep time.Duration = 10 * time.Second 30 lokiLimit int = 100 31 ) 32 33 var linesRead = prometheus.NewCounterVec( 34 prometheus.CounterOpts{ 35 Name: "cs_lokisource_hits_total", 36 Help: "Total lines that were read.", 37 }, 38 []string{"source"}) 39 40 type LokiAuthConfiguration struct { 41 Username string `yaml:"username"` 42 Password string `yaml:"password"` 43 } 44 45 type LokiConfiguration struct { 46 URL string `yaml:"url"` // Loki url 47 Prefix string `yaml:"prefix"` // Loki prefix 48 Query string `yaml:"query"` // LogQL query 49 Limit int `yaml:"limit"` // Limit of logs to read 50 DelayFor time.Duration `yaml:"delay_for"` 51 Since time.Duration `yaml:"since"` 52 Headers map[string]string `yaml:"headers"` // HTTP headers for talking to Loki 53 WaitForReady time.Duration `yaml:"wait_for_ready"` // Retry interval, default is 10 seconds 54 Auth LokiAuthConfiguration `yaml:"auth"` 55 MaxFailureDuration time.Duration `yaml:"max_failure_duration"` // Max duration of failure before stopping the source 56 configuration.DataSourceCommonCfg `yaml:",inline"` 57 } 58 59 type LokiSource struct { 60 metricsLevel int 61 Config LokiConfiguration 62 63 Client *lokiclient.LokiClient 64 65 logger *log.Entry 66 lokiWebsocket string 67 } 68 69 func (l *LokiSource) GetMetrics() []prometheus.Collector { 70 return []prometheus.Collector{linesRead} 71 } 72 73 func (l *LokiSource) GetAggregMetrics() []prometheus.Collector { 74 return []prometheus.Collector{linesRead} 75 } 76 77 func (l *LokiSource) UnmarshalConfig(yamlConfig []byte) error { 78 err := yaml.UnmarshalStrict(yamlConfig, &l.Config) 79 if err != nil { 80 return fmt.Errorf("cannot parse loki acquisition configuration: %w", err) 81 } 82 83 if l.Config.Query == "" { 84 return errors.New("loki query is mandatory") 85 } 86 87 if l.Config.WaitForReady == 0 { 88 l.Config.WaitForReady = 10 * time.Second 89 } 90 91 if l.Config.DelayFor < 0*time.Second || l.Config.DelayFor > 5*time.Second { 92 return errors.New("delay_for should be a value between 1s and 5s") 93 } 94 95 if l.Config.Mode == "" { 96 l.Config.Mode = configuration.TAIL_MODE 97 } 98 if l.Config.Prefix == "" { 99 l.Config.Prefix = "/" 100 } 101 102 if !strings.HasSuffix(l.Config.Prefix, "/") { 103 l.Config.Prefix += "/" 104 } 105 106 if l.Config.Limit == 0 { 107 l.Config.Limit = lokiLimit 108 } 109 110 if l.Config.Mode == configuration.TAIL_MODE { 111 l.logger.Infof("Resetting since") 112 l.Config.Since = 0 113 } 114 115 if l.Config.MaxFailureDuration == 0 { 116 l.Config.MaxFailureDuration = 30 * time.Second 117 } 118 119 return nil 120 } 121 122 func (l *LokiSource) Configure(config []byte, logger *log.Entry, MetricsLevel int) error { 123 l.Config = LokiConfiguration{} 124 l.logger = logger 125 l.metricsLevel = MetricsLevel 126 err := l.UnmarshalConfig(config) 127 if err != nil { 128 return err 129 } 130 131 l.logger.Infof("Since value: %s", l.Config.Since.String()) 132 133 clientConfig := lokiclient.Config{ 134 LokiURL: l.Config.URL, 135 Headers: l.Config.Headers, 136 Limit: l.Config.Limit, 137 Query: l.Config.Query, 138 Since: l.Config.Since, 139 Username: l.Config.Auth.Username, 140 Password: l.Config.Auth.Password, 141 FailMaxDuration: l.Config.MaxFailureDuration, 142 } 143 144 l.Client = lokiclient.NewLokiClient(clientConfig) 145 l.Client.Logger = logger.WithFields(log.Fields{"component": "lokiclient", "source": l.Config.URL}) 146 return nil 147 } 148 149 func (l *LokiSource) ConfigureByDSN(dsn string, labels map[string]string, logger *log.Entry, uuid string) error { 150 l.logger = logger 151 l.Config = LokiConfiguration{} 152 l.Config.Mode = configuration.CAT_MODE 153 l.Config.Labels = labels 154 l.Config.UniqueId = uuid 155 156 u, err := url.Parse(dsn) 157 if err != nil { 158 return fmt.Errorf("while parsing dsn '%s': %w", dsn, err) 159 } 160 if u.Scheme != "loki" { 161 return fmt.Errorf("invalid DSN %s for loki source, must start with loki://", dsn) 162 } 163 if u.Host == "" { 164 return errors.New("empty loki host") 165 } 166 scheme := "http" 167 168 params := u.Query() 169 if q := params.Get("ssl"); q != "" { 170 scheme = "https" 171 } 172 if q := params.Get("query"); q != "" { 173 l.Config.Query = q 174 } 175 if w := params.Get("wait_for_ready"); w != "" { 176 l.Config.WaitForReady, err = time.ParseDuration(w) 177 if err != nil { 178 return err 179 } 180 } else { 181 l.Config.WaitForReady = 10 * time.Second 182 } 183 184 if d := params.Get("delay_for"); d != "" { 185 l.Config.DelayFor, err = time.ParseDuration(d) 186 if err != nil { 187 return fmt.Errorf("invalid duration: %w", err) 188 } 189 if l.Config.DelayFor < 0*time.Second || l.Config.DelayFor > 5*time.Second { 190 return errors.New("delay_for should be a value between 1s and 5s") 191 } 192 } else { 193 l.Config.DelayFor = 0 * time.Second 194 } 195 196 if s := params.Get("since"); s != "" { 197 l.Config.Since, err = time.ParseDuration(s) 198 if err != nil { 199 return fmt.Errorf("invalid since in dsn: %w", err) 200 } 201 } 202 203 if max_failure_duration := params.Get("max_failure_duration"); max_failure_duration != "" { 204 duration, err := time.ParseDuration(max_failure_duration) 205 if err != nil { 206 return fmt.Errorf("invalid max_failure_duration in dsn: %w", err) 207 } 208 l.Config.MaxFailureDuration = duration 209 } else { 210 l.Config.MaxFailureDuration = 5 * time.Second // for OneShot mode it doesn't make sense to have longer duration 211 } 212 213 if limit := params.Get("limit"); limit != "" { 214 limit, err := strconv.Atoi(limit) 215 if err != nil { 216 return fmt.Errorf("invalid limit in dsn: %w", err) 217 } 218 l.Config.Limit = limit 219 } else { 220 l.Config.Limit = 5000 // max limit allowed by loki 221 } 222 223 if logLevel := params.Get("log_level"); logLevel != "" { 224 level, err := log.ParseLevel(logLevel) 225 if err != nil { 226 return fmt.Errorf("invalid log_level in dsn: %w", err) 227 } 228 l.Config.LogLevel = &level 229 l.logger.Logger.SetLevel(level) 230 } 231 232 l.Config.URL = fmt.Sprintf("%s://%s", scheme, u.Host) 233 if u.User != nil { 234 l.Config.Auth.Username = u.User.Username() 235 l.Config.Auth.Password, _ = u.User.Password() 236 } 237 238 clientConfig := lokiclient.Config{ 239 LokiURL: l.Config.URL, 240 Headers: l.Config.Headers, 241 Limit: l.Config.Limit, 242 Query: l.Config.Query, 243 Since: l.Config.Since, 244 Username: l.Config.Auth.Username, 245 Password: l.Config.Auth.Password, 246 DelayFor: int(l.Config.DelayFor / time.Second), 247 } 248 249 l.Client = lokiclient.NewLokiClient(clientConfig) 250 l.Client.Logger = logger.WithFields(log.Fields{"component": "lokiclient", "source": l.Config.URL}) 251 252 return nil 253 } 254 255 func (l *LokiSource) GetMode() string { 256 return l.Config.Mode 257 } 258 259 func (l *LokiSource) GetName() string { 260 return "loki" 261 } 262 263 // OneShotAcquisition reads a set of file and returns when done 264 func (l *LokiSource) OneShotAcquisition(out chan types.Event, t *tomb.Tomb) error { 265 l.logger.Debug("Loki one shot acquisition") 266 l.Client.SetTomb(t) 267 readyCtx, cancel := context.WithTimeout(context.Background(), l.Config.WaitForReady) 268 defer cancel() 269 err := l.Client.Ready(readyCtx) 270 if err != nil { 271 return fmt.Errorf("loki is not ready: %w", err) 272 } 273 274 ctx, cancel := context.WithCancel(context.Background()) 275 c := l.Client.QueryRange(ctx, false) 276 277 for { 278 select { 279 case <-t.Dying(): 280 l.logger.Debug("Loki one shot acquisition stopped") 281 cancel() 282 return nil 283 case resp, ok := <-c: 284 if !ok { 285 l.logger.Info("Loki acquisition done, chan closed") 286 cancel() 287 return nil 288 } 289 for _, stream := range resp.Data.Result { 290 for _, entry := range stream.Entries { 291 l.readOneEntry(entry, l.Config.Labels, out) 292 } 293 } 294 } 295 } 296 } 297 298 func (l *LokiSource) readOneEntry(entry lokiclient.Entry, labels map[string]string, out chan types.Event) { 299 ll := types.Line{} 300 ll.Raw = entry.Line 301 ll.Time = entry.Timestamp 302 ll.Src = l.Config.URL 303 ll.Labels = labels 304 ll.Process = true 305 ll.Module = l.GetName() 306 307 if l.metricsLevel != configuration.METRICS_NONE { 308 linesRead.With(prometheus.Labels{"source": l.Config.URL}).Inc() 309 } 310 expectMode := types.LIVE 311 if l.Config.UseTimeMachine { 312 expectMode = types.TIMEMACHINE 313 } 314 out <- types.Event{ 315 Line: ll, 316 Process: true, 317 Type: types.LOG, 318 ExpectMode: expectMode, 319 } 320 } 321 322 func (l *LokiSource) StreamingAcquisition(out chan types.Event, t *tomb.Tomb) error { 323 l.Client.SetTomb(t) 324 readyCtx, cancel := context.WithTimeout(context.Background(), l.Config.WaitForReady) 325 defer cancel() 326 err := l.Client.Ready(readyCtx) 327 if err != nil { 328 return fmt.Errorf("loki is not ready: %w", err) 329 } 330 ll := l.logger.WithField("websocket_url", l.lokiWebsocket) 331 t.Go(func() error { 332 ctx, cancel := context.WithCancel(context.Background()) 333 defer cancel() 334 respChan := l.Client.QueryRange(ctx, true) 335 if err != nil { 336 ll.Errorf("could not start loki tail: %s", err) 337 return fmt.Errorf("while starting loki tail: %w", err) 338 } 339 for { 340 select { 341 case resp, ok := <-respChan: 342 if !ok { 343 ll.Warnf("loki channel closed") 344 return err 345 } 346 for _, stream := range resp.Data.Result { 347 for _, entry := range stream.Entries { 348 l.readOneEntry(entry, l.Config.Labels, out) 349 } 350 } 351 case <-t.Dying(): 352 return nil 353 } 354 } 355 }) 356 return nil 357 } 358 359 func (l *LokiSource) CanRun() error { 360 return nil 361 } 362 363 func (l *LokiSource) GetUuid() string { 364 return l.Config.UniqueId 365 } 366 367 func (l *LokiSource) Dump() interface{} { 368 return l 369 } 370 371 // SupportedModes returns the supported modes by the acquisition module 372 func (l *LokiSource) SupportedModes() []string { 373 return []string{configuration.TAIL_MODE, configuration.CAT_MODE} 374 }