// readLine reads a full line into buffer and returns it. // In case of partial lines, readLine does return and error and en empty string // This could potentialy be improved / replaced by https://github.com/elastic/beats/libbeat/tree/master/common/streambuf func readLine(reader reader.Reader) (time.Time, string, int, common.MapStr, error) { l, err := reader.Next() // Full line read to be returned if l.Bytes != 0 && err == nil { return l.Ts, string(l.Content), l.Bytes, l.Fields, err } return time.Time{}, "", 0, nil, err }
// Harvest reads files line by line and sends events to the defined output func (h *Harvester) Harvest(r reader.Reader) { harvesterStarted.Add(1) harvesterRunning.Add(1) defer harvesterRunning.Add(-1) // Makes sure file is properly closed when the harvester is stopped defer h.close() // Channel to stop internal harvester routines harvestDone := make(chan struct{}) defer close(harvestDone) // Closes reader after timeout or when done channel is closed // This routine is also responsible to properly stop the reader go func() { var closeTimeout <-chan time.Time if h.config.CloseTimeout > 0 { closeTimeout = time.After(h.config.CloseTimeout) } select { // Applies when timeout is reached case <-closeTimeout: logp.Info("Closing harvester because close_timeout was reached: %s", h.state.Source) // Required for shutdown when hanging inside reader case <-h.done: // Required when reader loop returns and reader finished case <-harvestDone: } h.fileReader.Close() }() logp.Info("Harvester started for file: %s", h.state.Source) for { select { case <-h.done: return default: } message, err := r.Next() if err != nil { switch err { case ErrFileTruncate: logp.Info("File was truncated. Begin reading file from offset 0: %s", h.state.Source) h.state.Offset = 0 filesTruncated.Add(1) case ErrRemoved: logp.Info("File was removed: %s. Closing because close_removed is enabled.", h.state.Source) case ErrRenamed: logp.Info("File was renamed: %s. Closing because close_renamed is enabled.", h.state.Source) case ErrClosed: logp.Info("Reader was closed: %s. Closing.", h.state.Source) case io.EOF: logp.Info("End of file reached: %s. Closing because close_eof is enabled.", h.state.Source) case ErrInactive: logp.Info("File is inactive: %s. Closing because close_inactive of %v reached.", h.state.Source, h.config.CloseInactive) default: logp.Err("Read line error: %s; File: ", err, h.state.Source) } return } // Strip UTF-8 BOM if beginning of file // As all BOMS are converted to UTF-8 it is enough to only remove this one if h.state.Offset == 0 { message.Content = bytes.Trim(message.Content, "\xef\xbb\xbf") } // Update offset h.state.Offset += int64(message.Bytes) // Create state event event := input.NewEvent(h.getState()) text := string(message.Content) // Check if data should be added to event. Only export non empty events. if !message.IsEmpty() && h.shouldExportLine(text) { event.ReadTime = message.Ts event.Bytes = message.Bytes event.Text = &text event.JSONFields = message.Fields event.EventMetadata = h.config.EventMetadata event.InputType = h.config.InputType event.DocumentType = h.config.DocumentType event.JSONConfig = h.config.JSON } // Always send event to update state, also if lines was skipped // Stop harvester in case of an error if !h.sendEvent(event) { return } } }
// Harvest reads files line by line and sends events to the defined output func (h *Harvester) Harvest(r reader.Reader) { harvesterStarted.Add(1) harvesterRunning.Add(1) defer harvesterRunning.Add(-1) // Makes sure file is properly closed when the harvester is stopped defer h.close() logp.Info("Harvester started for file: %s", h.state.Source) for { select { case <-h.done: return default: } message, err := r.Next() if err != nil { switch err { case ErrFileTruncate: logp.Info("File was truncated. Begin reading file from offset 0: %s", h.state.Source) h.state.Offset = 0 filesTruncated.Add(1) case ErrRemoved: logp.Info("File was removed: %s. Closing because close_removed is enabled.", h.state.Source) case ErrRenamed: logp.Info("File was renamed: %s. Closing because close_renamed is enabled.", h.state.Source) case ErrClosed: logp.Info("Reader was closed: %s. Closing.", h.state.Source) case io.EOF: logp.Info("End of file reached: %s. Closing because close_eof is enabled.", h.state.Source) case ErrInactive: logp.Info("File is inactive: %s. Closing because close_inactive of %v reached.", h.state.Source, h.config.CloseInactive) default: logp.Err("Read line error: %s; File: ", err, h.state.Source) } return } // Strip UTF-8 BOM if beginning of file // As all BOMS are converted to UTF-8 it is enough to only remove this one if h.state.Offset == 0 { message.Content = bytes.Trim(message.Content, "\xef\xbb\xbf") } // Update offset h.state.Offset += int64(message.Bytes) // Create state event event := input.NewEvent(h.getState()) text := string(message.Content) // Check if data should be added to event. Only export non empty events. if !message.IsEmpty() && h.shouldExportLine(text) { event.ReadTime = message.Ts event.Bytes = message.Bytes event.Text = &text event.JSONFields = message.Fields event.EventMetadata = h.config.EventMetadata event.InputType = h.config.InputType event.DocumentType = h.config.DocumentType event.JSONConfig = h.config.JSON } // Always send event to update state, also if lines was skipped // Stop harvester in case of an error if !h.sendEvent(event) { return } } }