private void Add(string line, LevelFlags level, int numberOfLinesRead, DateTime?timestamp) { if (_properties.GetValue(LogFileProperties.StartTimestamp) == null) { _properties.SetValue(LogFileProperties.StartTimestamp, timestamp); } if (timestamp != null) { _properties.SetValue(LogFileProperties.EndTimestamp, timestamp); } var duration = timestamp - _properties.GetValue(LogFileProperties.StartTimestamp); _properties.SetValue(LogFileProperties.Duration, duration); lock (_syncRoot) { int lineIndex = _entries.Count; var logLine = new LogLine(lineIndex, lineIndex, line, level, timestamp); var translated = Translate(logLine); _entries.Add(translated); _maxCharactersPerLine = Math.Max(_maxCharactersPerLine, translated.Message?.Length ?? 0); if (timestamp != null) { UpdateLastModifiedIfNecessary(timestamp.Value); } } Listeners.OnRead(numberOfLinesRead); }
private bool TryAddLogEntry(List <LogLine> logEntry) { if (_indices.Count > 0 && logEntry.Count > 0 && _indices[_indices.Count - 1] == logEntry[logEntry.Count - 1].LineIndex) { return(true); } if (_logEntryFilter.PassesFilter(logEntry)) { lock (_indices) { if (logEntry.Count > 0) { foreach (LogLine line in logEntry) { _indices.Add(line.LineIndex); _logEntryIndices[line.LineIndex] = _currentLogEntryIndex; _maxCharactersPerLine = Math.Max(_maxCharactersPerLine, line.Message?.Length ?? 0); } ++_currentLogEntryIndex; } } Listeners.OnRead(_indices.Count); return(true); } return(false); }
private bool TryAddLogEntry(IReadOnlyList <IReadOnlyLogEntry> logEntry) { if (_indices.Count > 0 && logEntry.Count > 0 && _indices[_indices.Count - 1] == logEntry[logEntry.Count - 1].Index) { return(true); } if (_logEntryFilter.PassesFilter(logEntry)) { lock (_indices) { if (logEntry.Count > 0) { foreach (var line in logEntry) { _indices.Add((int)line.Index); _logEntryIndices[(int)line.Index] = _currentLogEntryIndex; _maxCharactersPerLine = Math.Max(_maxCharactersPerLine, line.RawContent?.Length ?? 0); } ++_currentLogEntryIndex; } } Listeners.OnRead(_indices.Count); return(true); } return(false); }
private bool Process(IReadOnlyList <LogSourceModification> pendingModifications) { if (pendingModifications.Count == 0) { return(false); } foreach (var modification in pendingModifications) { if (modification.IsReset()) { _count = 0; Listeners.Reset(); _propertiesBuffer.SetToDefault(_adornedProperties); SynchronizeProperties(); } else if (modification.IsRemoved(out var removedSection)) { _count = (int)removedSection.Index; SynchronizeProperties(); Listeners.Remove((int)removedSection.Index, removedSection.Count); } else if (modification.IsAppended(out var appendedSection)) { Process(appendedSection); _count += appendedSection.Count; SynchronizeProperties(); Listeners.OnRead(_count); } } return(true); }
/// <summary> /// Processes all newly arrived log entries. /// </summary> /// <param name="token"></param> private void ProcessNewLogEntries(CancellationToken token) { if (!_fullSourceSection.IsEndOfSection(_currentSourceIndex)) { int remaining = _fullSourceSection.Index + _fullSourceSection.Count - _currentSourceIndex; int nextCount = Math.Min(remaining, BatchSize); var nextSection = new LogSourceSection(_currentSourceIndex, nextCount); _source.GetEntries(nextSection, _array); for (int i = 0; i < nextCount; ++i) { if (token.IsCancellationRequested) { break; } var logEntry = _array[i]; if (Log.IsDebugEnabled) { Log.DebugFormat("Processing: LineIndex={0}, OriginalLineIndex={1}, LogEntryIndex={2}, Message={3}", logEntry.Index, logEntry.OriginalIndex, logEntry.LogEntryIndex, logEntry.RawContent); } if (_lastLogBuffer.Count == 0 || _lastLogBuffer[0].LogEntryIndex == logEntry.LogEntryIndex) { TryAddLogLine(logEntry); } else if (logEntry.LogEntryIndex != _lastLogBuffer[0].LogEntryIndex) { TryAddLogEntry(_lastLogBuffer); _lastLogBuffer.Clear(); TryAddLogLine(logEntry); } } _currentSourceIndex += nextCount; } // Now that we've processes all newly added log entries, we can check if we're at the end just yet... if (_fullSourceSection.IsEndOfSection(_currentSourceIndex)) { TryAddLogEntry(_lastLogBuffer); UpdateProperties(); //< we need to update our own properties after we've added the last entry, but before we notify listeners... Listeners.OnRead(_indices.Count); if (_properties.GetValue(Core.Properties.PercentageProcessed) == Percentage.HundredPercent) { Listeners.Flush(); } } else { UpdateProperties(); } }
private void Clear() { _fullSourceSection = new LogFileSection(); lock (_indices) { _indices.Clear(); _logEntryIndices.Clear(); _currentLogEntryIndex = 0; } Listeners.OnRead(-1); }
/// <inheritdoc /> protected override TimeSpan RunOnce(CancellationToken token) { bool performedWork = false; // Every Process() invocation locks the sync root until // the changes have been processed. The goal is to minimize // total process time and to prevent locking for too long. // The following number has been empirically determined // via testing and it felt alright :P const int maxLineCount = 10000; if (_pendingModifications.TryDequeueUpTo(maxLineCount, out var modifications)) { foreach (var modification in modifications) { if (modification.IsReset()) { Clear(); } else if (modification.IsRemoved(out var removedSection)) { Remove(removedSection); } else if (modification.IsAppended(out var appendedSection)) { Append(appendedSection); } performedWork = true; } } UpdateProperties(); if (_indices.Count != _currentSourceIndex) { Log.ErrorFormat("Inconsistency detected: We have {0} indices for {1} lines", _indices.Count, _currentSourceIndex); } Listeners.OnRead((int)_currentSourceIndex); if (_properties.GetValue(Core.Properties.PercentageProcessed) == Percentage.HundredPercent) { Listeners.Flush(); } if (performedWork) { return(TimeSpan.Zero); } return(_maximumWaitTime); }
private void Clear() { _fullSourceSection = new LogFileSection(0, 0); _currentSourceIndex = 0; _currentLogEntry = new LogEntryInfo(-1, 0); lock (_syncRoot) { _indices.Clear(); } Listeners.OnRead(-1); }
protected override TimeSpan RunOnce(CancellationToken token) { var pendingSections = _pendingSections.DequeueAll(); if (!Process(pendingSections)) { SynchronizeProperties(); Listeners.OnRead(_count); } return(_maximumWaitTime); }
private void Add(string line, int numberOfLinesRead) { lock (_syncRoot) { _entries.Add(new LogEntry { RawContent = line }); } Listeners.OnRead(numberOfLinesRead); }
private void Add(string line, LevelFlags level, int numberOfLinesRead, DateTime?timestamp) { if (_properties.GetValue(LogFileProperties.StartTimestamp) == null) { _properties.SetValue(LogFileProperties.StartTimestamp, timestamp); } if (timestamp != null) { _properties.SetValue(LogFileProperties.EndTimestamp, timestamp); } lock (_syncRoot) { int lineIndex = _entries.Count; var logLine = new LogLine(lineIndex, lineIndex, line, level, timestamp); var translated = Translate(logLine); _entries.Add(translated); _maxCharactersPerLine = Math.Max(_maxCharactersPerLine, translated.Message?.Length ?? 0); if (timestamp != null) { var lastModified = _properties.GetValue(LogFileProperties.LastModified); var difference = timestamp - lastModified; if (difference >= TimeSpan.FromSeconds(10)) { // I've had this issue occur on one system and I can't really explain it. // For some reason, new FileInfo(...).LastWriteTime will not give correct // results when we can see that the bloody file is being written to as we speak. // As a work around, we'll just use the timestamp of the log file as lastModifed // if that happens to be newer. // // This might be related to files being consumed from a network share. Anyways, // this quick fix should improve user feedback... if (!_loggedTimestampWarning) { Log.InfoFormat( "FileInfo.LastWriteTime results in a time stamp that is less than the parsed timestamp (LastWriteTime={0}, Parsed={1}), using parsed instead", lastModified, timestamp ); _loggedTimestampWarning = true; } _properties.SetValue(LogFileProperties.LastModified, timestamp.Value); } } } Listeners.OnRead(numberOfLinesRead); }
private void AppendSection(LogSourceSection section) { _count = (int)(section.Index + section.Count); try { _source.GetEntries(section, _fetchBuffer); OnSectionAppended(section, _fetchBuffer, _count); } catch (Exception e) { Log.WarnFormat("Caught unexpected exception: {0}", e); } SynchronizeProperties(); Listeners.OnRead(_count); }
/// <inheritdoc /> protected override TimeSpan RunOnce(CancellationToken token) { bool performedWork = false; while (_pendingModifications.TryDequeue(out var nextSection) && !token.IsCancellationRequested) { if (nextSection.IsReset) { Clear(); } else if (nextSection.IsInvalidate) { Invalidate(nextSection); } else { Append(nextSection); } performedWork = true; } // Now we can perform a block-copy of all properties. _source.GetValues(_properties); _maxCharactersPerLine = _source.MaxCharactersPerLine; if (_indices.Count != _currentSourceIndex) { Log.ErrorFormat("Inconsistency detected: We have {0} indices for {1} lines", _indices.Count, _currentSourceIndex); } Listeners.OnRead((int)_currentSourceIndex); if (_source.EndOfSourceReached && _fullSourceSection.IsEndOfSection(_currentSourceIndex)) { SetEndOfSourceReached(); } if (performedWork) { return(TimeSpan.Zero); } return(_maximumWaitTime); }
private void NotifyListeners(IEnumerable <LogFileSection> changes) { foreach (var section in changes) { if (section.IsInvalidate) { Listeners.Invalidate((int)section.Index, section.Count); } else if (section.IsReset) { Listeners.Reset(); } else { Listeners.OnRead((int)(section.Index + section.Count)); } } }
private void NotifyListeners(IEnumerable <LogSourceModification> changes) { foreach (var section in changes) { if (section.IsRemoved(out var removedSection)) { Listeners.Remove((int)removedSection.Index, removedSection.Count); } else if (section.IsReset()) { Listeners.Reset(); } else if (section.IsAppended(out var appendedSection)) { Listeners.OnRead((int)(appendedSection.Index + appendedSection.Count)); } } }
private void ProcessPendingSections(out bool workDone) { workDone = false; while (_pendingSections.TryDequeue(out var pair)) { // We may still have pending sections from a log file we've just removed as listener. // If that's the case, then throw away that section and go look for the next... if (!Equals(pair.Key, _finalLogSource)) { continue; } var modification = pair.Value; if (modification.IsReset()) { Listeners.Reset(); _count = 0; _maxCharactersInLine = 0; } else if (modification.IsRemoved(out var removedSection)) { Listeners.Remove((int)removedSection.Index, removedSection.Count); _count = (int)removedSection.Index; // TODO: What about max width? } else if (modification.IsAppended(out var appendedSection)) { Listeners.OnRead(appendedSection.LastIndex); _count = (int)(appendedSection.Index + appendedSection.Count); UpdateMaxWidth(appendedSection, pair.Key); } workDone = true; } if (!workDone) { Listeners.OnRead(_count); } }
private void Clear(ILogFile logFile) { var numRemoved = 0; lock (_syncRoot) { for (var i = _indices.Count - 1; i >= 0; --i) { var index = _indices[i]; var indexLogFile = _sources[index.LogFileIndex]; if (indexLogFile == logFile) { _indices.RemoveAt(i); ++numRemoved; } } } if (numRemoved > 0) { Listeners.Reset(); Listeners.OnRead(_indices.Count); } }
/// <inheritdoc /> protected override TimeSpan RunOnce(CancellationToken token) { var lastCount = _fullSourceSection.Count; bool performedWork = false; LogFileSection section; while (_pendingModifications.TryDequeue(out section) && !token.IsCancellationRequested) { if (section.IsReset) { Clear(); } else if (section.IsInvalidate) { Invalidate(section); } else { _fullSourceSection = LogFileSection.MinimumBoundingLine(_fullSourceSection, section); } performedWork = true; } if (!_fullSourceSection.IsEndOfSection(_currentSourceIndex)) { var remaining = Math.Min(_fullSourceSection.Count - _currentSourceIndex, MaximumBatchSize); var buffer = new LogLine[remaining]; _source.GetSection(new LogFileSection(_currentSourceIndex, remaining), buffer); LogLineIndex?resetIndex = null; lock (_syncRoot) { for (var i = 0; i < remaining; ++i) { var line = buffer[i]; if (_currentLogEntry.EntryIndex.IsInvalid || line.Level != LevelFlags.None || _currentLogEntryLevel == LevelFlags.None) { _currentLogEntry = _currentLogEntry.NextEntry(line.LineIndex); _currentLogEntryLevel = line.Level; } else if (_currentLogEntry.FirstLineIndex < lastCount && resetIndex == null) { var index = _currentLogEntry.FirstLineIndex; resetIndex = index; _currentLogEntryLevel = _source.GetLine((int)index).Level; } _indices.Add(_currentLogEntry); } } if (resetIndex != null) { var resetCount = lastCount - resetIndex.Value; if (resetCount > 0) { Listeners.Invalidate((int)resetIndex.Value, resetCount); } } _currentSourceIndex += remaining; } // Now we can perform a block-copy of all properties. _source.GetValues(_properties); _maxCharactersPerLine = _source.MaxCharactersPerLine; if (_indices.Count != _currentSourceIndex) { Log.ErrorFormat("Inconsistency detected: We have {0} indices for {1} lines", _indices.Count, _currentSourceIndex); } Listeners.OnRead((int)_currentSourceIndex); if (_source.EndOfSourceReached && _fullSourceSection.IsEndOfSection(_currentSourceIndex)) { SetEndOfSourceReached(); } if (performedWork) { return(TimeSpan.Zero); } return(_maximumWaitTime); }
/// <inheritdoc /> protected override TimeSpan RunOnce(CancellationToken token) { bool read = false; try { if (!File.Exists(_fileName)) { SetDoesNotExist(); } else { var info = new FileInfo(_fileName); var fileSize = info.Length; _localProperties.SetValue(Core.Properties.LastModified, info.LastWriteTimeUtc); _localProperties.SetValue(Core.Properties.Created, info.CreationTimeUtc); _localProperties.SetValue(Core.Properties.Size, Size.FromBytes(fileSize)); UpdatePercentageProcessed(_lastStreamPosition, fileSize, allow100Percent: true); SynchronizePropertiesWithUser(); using (var stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { using (var reader = new StreamReaderEx(stream, _encoding)) { // We change the error flag explicitly AFTER opening // the stream because that operation might throw if we're // not allowed to access the file (in which case a different // error must be set). _localProperties.SetValue(Core.Properties.EmptyReason, null); if (stream.Length >= _lastStreamPosition) { stream.Position = _lastStreamPosition; } else { OnReset(stream, out _numberOfLinesRead, out _lastStreamPosition); } int numProcessed = 0; string currentLine; while ((currentLine = reader.ReadLine()) != null) { token.ThrowIfCancellationRequested(); bool lastLineHadNewline = _lastLineHadNewline; var trimmedLine = currentLine.TrimNewlineEnd(out _lastLineHadNewline); var entryCount = _entries.Count; if (entryCount > 0 && !lastLineHadNewline) { // We need to remove the last line and create a new line // that consists of the entire content. RemoveLast(); trimmedLine = _untrimmedLastLine + trimmedLine; _untrimmedLastLine = _untrimmedLastLine + currentLine; } else { _untrimmedLastLine = currentLine; ++_numberOfLinesRead; read = true; } Add(trimmedLine, _numberOfLinesRead); if (++numProcessed % 1000 == 0) { // Here's the deal: Since we're processing the file in chunks, we advance the underlying // stream faster than we're actually consuming lines. This means that it's quite likely // that at the end of the file, we have moved the stream to the end, but have not quite // yet processed the underlying buffer from StreamReaderEx. The percentage processed // should be accurate enough so that if it is at 100%, then no more log entries are added. // We can only guarantee that when we have processed all lines and therefore we reserve // setting the percentage to 100% ONLY when we can read no more lines // (See the SetEndOfSourceReached() call below, outside the loop). UpdatePercentageProcessed(stream.Position, fileSize, allow100Percent: false); SynchronizePropertiesWithUser(); } } _lastStreamPosition = stream.Position; _localProperties.SetValue(TextProperties.LineCount, _entries.Count); _localProperties.SetValue(Core.Properties.LogEntryCount, _entries.Count); } } Listeners.OnRead(_numberOfLinesRead); SetEndOfSourceReached(); } } catch (FileNotFoundException e) { SetError(_sourceDoesNotExist); Log.Debug(e); } catch (DirectoryNotFoundException e) { SetError(_sourceDoesNotExist); Log.Debug(e); } catch (OperationCanceledException e) { Log.Debug(e); } catch (UnauthorizedAccessException e) { SetError(_sourceCannotBeAccessed); Log.Debug(e); } catch (IOException e) { SetError(_sourceCannotBeAccessed); Log.Debug(e); } catch (Exception e) { Log.Debug(e); } if (read) { return(TimeSpan.Zero); } return(TimeSpan.FromMilliseconds(100)); }
protected override TimeSpan RunOnce(CancellationToken token) { try { var info = new FileInfo(_fileName); _fileSize = Size.FromBytes(info.Length); if (info.Exists) { var builder = new SQLiteConnectionStringBuilder { DataSource = _fileName, Version = 3 }; using (var connection = new SQLiteConnection(builder.ToString())) { connection.Open(); int lineCount = GetNumberOfLogEntries(connection); if (lineCount < _lineCount) { // We'll assume that the table was cleared... Listeners.Flush(); _lineCount = 0; lock (_syncRoot) { _lines.Clear(); } var lines = ReadLogLines(connection, 0, lineCount); lock (_syncRoot) { _lines.AddRange(lines); _lineCount = lineCount; } Listeners.OnRead(_lineCount); } else if (lineCount > _lineCount) { var remaining = lineCount - _lineCount; var lines = ReadLogLines(connection, _lineCount, remaining); lock (_syncRoot) { _lines.AddRange(lines); _lineCount = lineCount; } Listeners.OnRead(_lineCount); } else { // We'll just assume that nothing relevant has been modified. } } _exists = true; } else { Clear(); _exists = false; } } catch (IOException e) { Log.DebugFormat("Caught exception while trying to open the database: {0}", e); // It's unlikely, but possible that the file happened to be deleted in between File.Exists // and actually opening the connection. But we should actually behave as if we've taken the else // branch above... _exists = false; } catch (Exception e) { // It's considered bad form to let exception bubble through to the task scheduler, and therefore // we 'll log the exception (which hopefully doesn't happen every time) ourselves... Log.ErrorFormat("Caught unexpected exception: {0}", e); } return(TimeSpan.FromMilliseconds(500)); }
/// <inheritdoc /> protected override TimeSpan RunOnce(CancellationToken token) { PendingModification modification; while (_pendingModifications.TryDequeue(out modification)) { if (token.IsCancellationRequested) { return(TimeSpan.Zero); } _totalLineCount = CalculateTotalLogLineCount(); _progress = Percentage.Of(_indices.Count, _totalLineCount); if (modification.Section.IsReset) { Clear(modification.LogFile); } else if (modification.Section.IsInvalidate) { throw new NotImplementedException(); } else { for (var i = 0; i < modification.Section.Count; ++i) { var sourceIndex = modification.Section.Index + i; var newLogLine = modification.LogFile.GetLine((int)sourceIndex); if (newLogLine.Timestamp != null) { // We need to find out where this new entry (or entries) is/are to be inserted. var insertionIndex = _indices.Count; byte logFileIndex; _logFileIndices.TryGetValue(modification.LogFile, out logFileIndex); for (var n = _indices.Count - 1; n >= 0; --n) { var idx = _indices[n]; var logFile = _sources[idx.LogFileIndex]; var entry = logFile.GetLine(idx.SourceLineIndex); if (entry.Timestamp <= newLogLine.Timestamp) { insertionIndex = n + 1; break; } if (entry.Timestamp > newLogLine.Timestamp) { insertionIndex = n; } } var mergedLogEntryIndex = GetMergedLogEntryIndex(modification.LogFile, insertionIndex, newLogLine); var index = new Index((int)sourceIndex, mergedLogEntryIndex, newLogLine.LogEntryIndex, logFileIndex); if (insertionIndex < _indices.Count) { InvalidateOnward(insertionIndex, modification.LogFile, newLogLine); } lock (_syncRoot) { _indices.Insert(insertionIndex, index); _maxCharactersPerLine = Math.Max(_maxCharactersPerLine, newLogLine.Message?.Length ?? 0); } Listeners.OnRead(_indices.Count); // There's no need to frantically update every time, but every // once in a while would be nice... if (i % 100 == 0) { UpdateProperties(); } } } } } UpdateProperties(); Listeners.OnRead(_indices.Count); SetEndOfSourceReached(); return(_maximumWaitTime); }
/// <inheritdoc /> protected override TimeSpan RunOnce(CancellationToken token) { bool performedWork = false; LogFileSection section; while (_pendingModifications.TryDequeue(out section) && !token.IsCancellationRequested) { if (section.IsReset) { Clear(); _lastLogEntry.Clear(); _currentSourceIndex = 0; } else if (section.IsInvalidate) { LogLineIndex startIndex = section.Index; _fullSourceSection = new LogFileSection(0, (int)startIndex); if (_currentSourceIndex > _fullSourceSection.LastIndex) { _currentSourceIndex = (int)section.Index; } Invalidate(_currentSourceIndex); RemoveInvalidatedLines(_lastLogEntry, _currentSourceIndex); } else { _fullSourceSection = LogFileSection.MinimumBoundingLine(_fullSourceSection, section); } performedWork = true; } if (!_fullSourceSection.IsEndOfSection(_currentSourceIndex)) { int remaining = _fullSourceSection.Index + _fullSourceSection.Count - _currentSourceIndex; int nextCount = Math.Min(remaining, BatchSize); var nextSection = new LogFileSection(_currentSourceIndex, nextCount); _source.GetSection(nextSection, _buffer); for (int i = 0; i < nextCount; ++i) { if (token.IsCancellationRequested) { break; } LogLine line = _buffer[i]; if (_lastLogEntry.Count == 0 || _lastLogEntry[0].LogEntryIndex == line.LogEntryIndex) { TryAddLogLine(line); } else if (line.LogEntryIndex != _lastLogEntry[0].LogEntryIndex) { TryAddLogEntry(_lastLogEntry); _lastLogEntry.Clear(); TryAddLogLine(line); } } _currentSourceIndex += nextCount; } if (_fullSourceSection.IsEndOfSection(_currentSourceIndex)) { TryAddLogEntry(_lastLogEntry); Listeners.OnRead(_indices.Count); if (_source.EndOfSourceReached) { SetEndOfSourceReached(); } } if (performedWork) { return(TimeSpan.Zero); } return(_maximumWaitTime); }
/// <inheritdoc /> protected override TimeSpan RunOnce(CancellationToken token) { bool read = false; try { if (!File.Exists(_fileName)) { SetDoesNotExist(); } else { var info = new FileInfo(_fileName); _properties.SetValue(LogFileProperties.LastModified, info.LastWriteTime); _properties.SetValue(LogFileProperties.Created, info.CreationTime); _properties.SetValue(LogFileProperties.Size, Size.FromBytes(info.Length)); using (var stream = new FileStream(_fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { var format = _properties.GetValue(LogFileProperties.Format); if (format == null) { format = TryFindFormat(stream); _properties.SetValue(LogFileProperties.Format, format); } var encoding = format?.Encoding ?? _encoding; _properties.SetValue(LogFileProperties.Encoding, encoding); using (var reader = new StreamReaderEx(stream, encoding)) { // We change the error flag explicitly AFTER opening // the stream because that operation might throw if we're // not allowed to access the file (in which case a different // error must be set). _properties.SetValue(LogFileProperties.EmptyReason, ErrorFlags.None); if (stream.Length >= _lastPosition) { stream.Position = _lastPosition; } else { OnReset(stream, out _numberOfLinesRead, out _lastPosition); } string currentLine; while ((currentLine = reader.ReadLine()) != null) { token.ThrowIfCancellationRequested(); ResetEndOfSourceReached(); LevelFlags level = LogLine.DetermineLevelFromLine(currentLine); bool lastLineHadNewline = _lastLineHadNewline; var trimmedLine = currentLine.TrimNewlineEnd(out _lastLineHadNewline); var entryCount = _entries.Count; if (entryCount > 0 && !lastLineHadNewline) { // We need to remove the last line and create a new line // that consists of the entire content. RemoveLast(); trimmedLine = _untrimmedLastLine + trimmedLine; _untrimmedLastLine = _untrimmedLastLine + currentLine; } else { _untrimmedLastLine = currentLine; ++_numberOfLinesRead; read = true; } var timestamp = ParseTimestamp(trimmedLine); Add(trimmedLine, level, _numberOfLinesRead, timestamp); } _lastPosition = stream.Position; } } Listeners.OnRead(_numberOfLinesRead); SetEndOfSourceReached(); } } catch (FileNotFoundException e) { SetError(ErrorFlags.SourceDoesNotExist); Log.Debug(e); } catch (DirectoryNotFoundException e) { SetError(ErrorFlags.SourceDoesNotExist); Log.Debug(e); } catch (OperationCanceledException e) { Log.Debug(e); } catch (UnauthorizedAccessException e) { SetError(ErrorFlags.SourceCannotBeAccessed); Log.Debug(e); } catch (IOException e) { SetError(ErrorFlags.SourceCannotBeAccessed); Log.Debug(e); } catch (Exception e) { Log.Debug(e); } if (read) { return(TimeSpan.Zero); } return(TimeSpan.FromMilliseconds(100)); }