private LogEnvelope <IDictionary <string, string> > CreateRecord(RegexLogContext context)
        {
            var rawRecord = context.RecordBuilder.Length == 0 ? null : context.RecordBuilder.ToString();

            if (rawRecord is null)
            {
                return(null);
            }

            var fields = _extractionRegex is null
                    ? new Dictionary <string, string>()
                    : Utility.ExtractFields(_extractionRegex, rawRecord);

            var timestamp = GetTimestamp(fields, context.MatchedLineTimestamp);

            return(new LogEnvelope <IDictionary <string, string> >(fields,
                                                                   timestamp,
                                                                   rawRecord,
                                                                   context.FilePath,
                                                                   context.Position,
                                                                   context.MatchedLineNumber));
        }
        public async Task ParseRecordsAsync(RegexLogContext context, IList <IEnvelope <IDictionary <string, string> > > output,
                                            int recordCount, CancellationToken stopToken = default)
        {
            var count = 0;

            using (var stream = new FileStream(context.FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
            {
                stream.Position = context.Position;
                using (var reader = new LineReader(stream, _encoding, _bufferSize))
                {
                    while (count < recordCount)
                    {
                        stopToken.ThrowIfCancellationRequested();
                        var(line, consumed) = await reader.ReadAsync(stopToken);

                        _logger.LogTrace("File: '{0}', line: '{1}', bytes: {2}", context.FilePath, line, consumed);

                        if (line is null)
                        {
                            // end-of-file
                            var record = CreateRecord(context);
                            if (record is not null)
                            {
                                output.Add(record);
                            }

                            context.RecordBuilder.Clear();
                            context.MatchedLineNumber    = -1;
                            context.MatchedLineTimestamp = null;
                            break;
                        }

                        context.LineNumber++;
                        context.Position += consumed;

                        var match = _patternRegex.Match(line);
                        if (match.Success)
                        {
                            _logger.LogDebug("Regex matched.");
                            // this is start of a new record, get the last record
                            var record = CreateRecord(context);
                            if (record is not null)
                            {
                                output.Add(record);
                                count++;
                            }
                            // remember the new record's first line
                            context.RecordBuilder.Clear();
                            context.RecordBuilder.Append(line);
                            context.MatchedLineNumber = context.LineNumber;
                            // if the matched line has a 'Timestamp' match group, use it as the record's timestamp
                            context.MatchedLineTimestamp = GetTimeStamp(match);
                        }
                        else
                        {
                            _logger.LogDebug("Regex NOT matched.");
                            // line is not the beginning of a new record
                            if (context.RecordBuilder.Length != 0)
                            {
                                // some lines have been added to the new record
                                context.RecordBuilder.AppendLine();
                                context.RecordBuilder.Append(line);
                            }
                            else if (_removeUnmatchedRecord)
                            {
                                _logger.LogWarning("Line discarded: {0}", line);
                            }
                            else
                            {
                                // start a new record
                                // TODO figure out what's the use case scenario for this
                                context.RecordBuilder.Append(line);
                                context.MatchedLineNumber = context.LineNumber;
                            }
                        }
                    }
                }
            }
        }