// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you under // the Apache License, Version 2.0 (the "License"); you may // not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package input import ( "fmt" "sync" "github.com/gofrs/uuid" "github.com/elastic/beats/journalbeat/checkpoint" "github.com/elastic/beats/journalbeat/reader" "github.com/elastic/beats/libbeat/beat" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/libbeat/processors" ) // Input manages readers and forwards entries from journals. type Input struct { readers []*reader.Reader done chan struct{} config Config pipeline beat.Pipeline states map[string]checkpoint.JournalState id uuid.UUID logger *logp.Logger eventMeta common.EventMetadata processors beat.ProcessorList } // New returns a new Inout func New( c *common.Config, pipeline beat.Pipeline, done chan struct{}, states map[string]checkpoint.JournalState, ) (*Input, error) { config := DefaultConfig if err := c.Unpack(&config); err != nil { return nil, err } id, err := uuid.NewV4() if err != nil { return nil, fmt.Errorf("error while generating ID for input: %v", err) } logger := logp.NewLogger("input").With("id", id) var readers []*reader.Reader if len(config.Paths) == 0 { cfg := reader.Config{ Path: reader.LocalSystemJournalID, // used to identify the state in the registry Backoff: config.Backoff, MaxBackoff: config.MaxBackoff, Seek: config.Seek, Matches: config.Matches, } state := states[reader.LocalSystemJournalID] r, err := reader.NewLocal(cfg, done, state, logger) if err != nil { return nil, fmt.Errorf("error creating reader for local journal: %v", err) } readers = append(readers, r) } for _, p := range config.Paths { cfg := reader.Config{ Path: p, Backoff: config.Backoff, MaxBackoff: config.MaxBackoff, Seek: config.Seek, Matches: config.Matches, } state := states[p] r, err := reader.New(cfg, done, state, logger) if err != nil { return nil, fmt.Errorf("error creating reader for journal: %v", err) } readers = append(readers, r) } processors, err := processors.New(config.Processors) if err != nil { return nil, err } logger.Debugf("New input is created for paths %v", config.Paths) return &Input{ readers: readers, done: done, config: config, pipeline: pipeline, states: states, id: id, logger: logger, eventMeta: config.EventMetadata, processors: processors, }, nil } // Run connects to the output, collects entries from the readers // and then publishes the events. func (i *Input) Run() { client, err := i.pipeline.ConnectWith(beat.ClientConfig{ PublishMode: beat.GuaranteedSend, EventMetadata: i.eventMeta, Meta: nil, Processor: i.processors, ACKCount: func(n int) { i.logger.Infof("journalbeat successfully published %d events", n) }, }) if err != nil { i.logger.Error("Error connecting to output: %v", err) return } defer client.Close() i.publishAll(client) } // publishAll reads events from all readers and publishes them. func (i *Input) publishAll(client beat.Client) { out := make(chan *beat.Event) defer close(out) var wg sync.WaitGroup defer wg.Wait() for _, r := range i.readers { wg.Add(1) r := r go func() { defer wg.Done() for { select { case <-i.done: return default: } event, err := r.Next() if event == nil { if err != nil { i.logger.Errorf("Error while reading event: %v", err) } continue } select { case <-i.done: case out <- event: } } }() } for { select { case <-i.done: return case e := <-out: client.Publish(*e) } } } // Stop stops all readers of the input. func (i *Input) Stop() { for _, r := range i.readers { r.Close() } } // Wait waits until all readers are done. func (i *Input) Wait() { i.Stop() }