private void ProcessNewOrExpandedFiles(string filePath, string relativeFilePath, long fileSize)
        {
            //Only process new or expanded files
            if (_logFiles.TryGetValue(relativeFilePath, out TContext context))
            {
                // If there is no registered bookmark and we're bookmarking on buffer flush,
                // that means the file was read and events buffered, but never uploaded by the source.
                var position = this.bookmarkOnBufferFlush
                    ? (BookmarkManager.GetBookmark(this.GetBookmarkName(filePath)) ?? BookmarkManager.RegisterBookmark(this.GetBookmarkName(filePath), 0, (pos) => this.SaveBookmark())).Position
                    : context.Position;

                if (fileSize > position)
                {
                    // The file is expanded compared with position saved in bookmark
                    AddToBuffer(relativeFilePath);
                }
            }
            else
            {
                //New file
                _logFiles.Add(relativeFilePath, CreateLogSourceInfo(filePath, 0));
                if (this.bookmarkOnBufferFlush)
                {
                    BookmarkManager.RegisterBookmark(this.GetBookmarkName(filePath), 0, (pos) => this.SaveBookmark());
                }
                AddToBuffer(relativeFilePath);
            }
        }
        protected virtual void OnChanged(object source, FileSystemEventArgs e)
        {
            try
            {
                if (this.includeSubdirectories && this.ShouldSkip(e.FullPath))
                {
                    return;
                }

                string relativeFilePath = e.Name;

                //Sometimes we receive event where e.name is null so we should just skip it
                if (string.IsNullOrEmpty(relativeFilePath) || ShouldExclude(relativeFilePath) || !ShouldInclude(relativeFilePath))
                {
                    return;
                }

                //The entries in _buffer should be deleted before _logfiles and added after _logfiles
                switch (e.ChangeType)
                {
                case WatcherChangeTypes.Deleted:
                    if (!File.Exists(e.FullPath))     //macOS sometimes fires this event when a file is created so we need this extra check.
                    {
                        RemoveFromBuffer(relativeFilePath);
                        _logFiles.Remove(relativeFilePath);
                        BookmarkManager.RemoveBookmark(this.GetBookmarkName(e.FullPath));
                    }
                    break;

                case WatcherChangeTypes.Created:
                    if (!_logFiles.ContainsKey(relativeFilePath))
                    {
                        _logFiles[relativeFilePath] = CreateLogSourceInfo(e.FullPath, 0);
                        if (this.bookmarkOnBufferFlush)
                        {
                            BookmarkManager.RegisterBookmark(this.GetBookmarkName(e.FullPath), 0, (pos) => this.SaveBookmark());
                        }
                        AddToBuffer(relativeFilePath);
                    }
                    break;

                case WatcherChangeTypes.Changed:
                    AddToBuffer(relativeFilePath);
                    break;
                }
                _logger?.LogDebug($"ThreadId{Thread.CurrentThread.ManagedThreadId} File: {e.FullPath} ChangeType: {e.ChangeType}");
            }
            catch (Exception ex)
            {
                _logger?.LogError(ex.ToMinimized());
            }
        }
        protected virtual void OnRenamed(object source, RenamedEventArgs e)
        {
            try
            {
                // this is for Subdirectories check only
                if (this.includeSubdirectories && this.ShouldSkip(e.FullPath))
                {
                    return;
                }

                //Sometimes we receive event where e.name is null so we should just skip it
                if (string.IsNullOrEmpty(e.Name) || string.IsNullOrEmpty(e.OldName) ||
                    ShouldExclude(e.Name) || ShouldExclude(e.OldName) ||
                    (!ShouldInclude(e.Name) && !ShouldInclude(e.OldName)))
                {
                    return;
                }

                //File name rotation
                RemoveFromBuffer(e.OldName);
                if (_logFiles.ContainsKey(e.OldName))
                {
                    var newSourceInfo = CreateLogSourceInfo(e.FullPath, _logFiles[e.OldName].Position);
                    newSourceInfo.LineNumber = _logFiles[e.OldName].LineNumber;
                    _logFiles[e.Name]        = newSourceInfo;
                    _logFiles.Remove(e.OldName);

                    var bookmark = BookmarkManager.GetBookmark(this.GetBookmarkName(e.OldFullPath));
                    if (bookmark != null)
                    {
                        BookmarkManager.RemoveBookmark(bookmark.Id);
                        BookmarkManager.RegisterBookmark(this.GetBookmarkName(e.FullPath), bookmark.Position, (id) => this.SaveBookmark());
                    }
                }
                else
                {
                    var newSource = CreateLogSourceInfo(e.FullPath, 0);
                    _logFiles.Add(e.Name, newSource);
                    BookmarkManager.RegisterBookmark(this.GetBookmarkName(e.FullPath), 0, (id) => this.SaveBookmark());
                }
            }
            catch (Exception ex)
            {
                _logger?.LogError(ex.ToMinimized());
            }
            finally
            {
                AddToBuffer(e.Name);
                _logger?.LogInformation("File: {0} renamed to {1}", e.OldFullPath, e.FullPath);
            }
        }
 public void LoadSavedBookmark()
 {
     lock (_bookmarkFileLock)
     {
         try
         {
             using (var fs = new FileStream(this.bookmarkPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                 using (var sr = new StreamReader(fs))
                 {
                     while (!sr.EndOfStream)
                     {
                         string line = sr.ReadLine();
                         if (!string.IsNullOrWhiteSpace(line))
                         {
                             try
                             {
                                 string[] parts     = line.Split(',');
                                 var      logSource = CreateLogSourceInfo(parts[0], long.Parse(parts[1]));
                                 _logFiles[GetRelativeFilePath(parts[0], _directory)] = logSource;
                                 if (this.bookmarkOnBufferFlush)
                                 {
                                     BookmarkManager.RegisterBookmark(this.GetBookmarkName(logSource.FilePath), logSource.Position, (id) => this.SaveBookmark());
                                 }
                             }
                             catch (Exception ex)
                             {
                                 //Allow continue processing because it is legitimate for system to remove log files while the agent is stopped
                                 _logger?.LogWarning($"Fail to process bookmark {line}: {ex.ToMinimized()}");
                             }
                         }
                     }
                 }
         }
         catch (Exception ex)
         {
             _logger?.LogError($"Failed loading bookmark: {ex.ToMinimized()}");
             throw; //Inform caller the error
         }
     }
 }
        private void ReadBookmarkFromLogFiles()
        {
            var candidateFiles = _fileFilters.SelectMany(filter => this.GetFiles(_directory, filter))
                                 .Where(file => !ShouldExclude(file));

            if (_fileFilters.Length > 1)
            {
                //If there are multiple filters, they may overlap so we need to dedupe
                candidateFiles = candidateFiles.Distinct();
            }
            string[] files = candidateFiles.ToArray();

            foreach (string filePath in files)
            {
                FileInfo fi = new FileInfo(filePath);
                var      relativeFilePath = GetRelativeFilePath(filePath, _directory);
                long     fileSize         = fi.Length;

                if (_hasBookmark && this.InitialPosition != InitialPositionEnum.EOS)
                {
                    ProcessNewOrExpandedFiles(filePath, relativeFilePath, fileSize);
                    continue;
                }

                switch (this.InitialPosition)
                {
                case InitialPositionEnum.EOS:
                    _logFiles[relativeFilePath] = CreateLogSourceInfo(filePath, fi.Length);
                    break;

                case InitialPositionEnum.BOS:
                    //Process all files
                    _logFiles[relativeFilePath] = CreateLogSourceInfo(filePath, 0);
                    if (this.bookmarkOnBufferFlush)
                    {
                        BookmarkManager.RegisterBookmark(this.GetBookmarkName(filePath), 0, (pos) => this.SaveBookmark());
                    }
                    AddToBuffer(relativeFilePath);
                    break;

                case InitialPositionEnum.Bookmark:
                    _logFiles[relativeFilePath] = CreateLogSourceInfo(filePath, fi.Length);
                    if (this.bookmarkOnBufferFlush)
                    {
                        BookmarkManager.RegisterBookmark(this.GetBookmarkName(filePath), fi.Length, (pos) => this.SaveBookmark());
                    }
                    break;

                case InitialPositionEnum.Timestamp:
                    if (fi.LastWriteTimeUtc > this.InitialPositionTimestamp)
                    {
                        _logFiles[relativeFilePath] = CreateLogSourceInfo(filePath, 0);
                        if (this.bookmarkOnBufferFlush)
                        {
                            BookmarkManager.RegisterBookmark(this.GetBookmarkName(filePath), 0, (pos) => this.SaveBookmark());
                        }
                        AddToBuffer(relativeFilePath);
                    }
                    else
                    {
                        _logFiles[relativeFilePath] = CreateLogSourceInfo(filePath, fi.Length);
                        if (this.bookmarkOnBufferFlush)
                        {
                            BookmarkManager.RegisterBookmark(this.GetBookmarkName(filePath), fi.Length, (pos) => this.SaveBookmark());
                        }
                    }
                    break;

                default:
                    throw new NotImplementedException($"Initial Position {this.InitialPosition} is not supported");
                }
            }
        }
        protected virtual (long recordsRead, long bytesRead) ParseLogFile(string relativeFilePath, string fullPath)
        {
            long recordsRead = 0;
            long bytesRead   = 0;

            int bookmarkId;

            if (!_logFiles.TryGetValue(relativeFilePath, out TContext sourceInfo))
            {
                sourceInfo = CreateLogSourceInfo(fullPath, 0);
                _logFiles.Add(relativeFilePath, sourceInfo);
                bookmarkId = this.bookmarkOnBufferFlush ? BookmarkManager.RegisterBookmark(this.GetBookmarkName(fullPath), 0, (pos) => this.SaveBookmark()).Id : 0;
            }
            else
            {
                bookmarkId = BookmarkManager.GetBookmarkId(this.GetBookmarkName(fullPath));
            }

            try
            {
                using (var fs = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                    using (var sr = CreateStreamReader(fs, _encoding))
                    {
                        var records = _recordParser.ParseRecords(sr, sourceInfo);
                        foreach (var record in records)
                        {
                            ILogEnvelope envelope = (ILogEnvelope)record;
                            if (envelope != null &&
                                record.Timestamp > (this.InitialPositionTimestamp ?? DateTime.MinValue) &&
                                envelope.LineNumber > _skipLines)
                            {
                                record.BookmarkId = bookmarkId;
                                _recordSubject.OnNext(record);
                                recordsRead++;
                            }

                            if (!_started)
                            {
                                break;
                            }
                        }

                        //Need to grab the position before disposing the reader because disposing the reader will dispose the stream
                        bytesRead           = fs.Position - sourceInfo.Position;
                        sourceInfo.Position = fs.Position;
                        sourceInfo.ConsecutiveIOExceptionCount = 0;
                    }
            }
            catch (IOException ex)
            {
                //Add it back to buffer for processing
                AddToBuffer(relativeFilePath);
                sourceInfo.ConsecutiveIOExceptionCount++;
                if (sourceInfo.ConsecutiveIOExceptionCount >= this.NumberOfConsecutiveIOExceptionsToLogError)
                {
                    _logger?.LogError(ex.ToMinimized());
                }
            }
            catch (Exception ex)
            {
                _logger?.LogError(ex.ToMinimized());
            }
            return(recordsRead, bytesRead);
        }