/// <summary> /// When overriden, return the header fields for the log file with a given <paramref name="context"/>, /// or 'null' if none is found /// </summary> protected virtual async Task <string[]> TryGetHeaderFields(DelimitedTextLogContext context, CancellationToken stopToken) { var position = 0; long lineNumber = 0; using (var stream = new FileStream(context.FilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) using (var reader = new LineReader(stream, _encoding, _bufferSize)) { while (position < context.Position) { stopToken.ThrowIfCancellationRequested(); var(line, consumed) = await reader.ReadAsync(stopToken); if (line is null) { break; } lineNumber++; position += consumed; if (IsHeaders(line, lineNumber)) { return(ParseHeadersLine(line)); } } } return(null); }
protected override async Task <string[]> TryGetHeaderFields(DelimitedTextLogContext context, CancellationToken stopToken) { if (_defaultHeaders is not null) { return(ParseHeadersLine(_defaultHeaders)); } return(await base.TryGetHeaderFields(context, stopToken)); }
protected override W3SVCRecord CreateRecord(DelimitedTextLogContext context, Dictionary <string, string> data) { var timestamp = DateTime.UtcNow; if (data.TryGetValue("date", out var date) && data.TryGetValue("time", out var time)) { // date and time field is UTC-based: https://docs.microsoft.com/en-us/windows/win32/http/w3c-logging timestamp = DateTime.Parse(date + "T" + time + "Z", null, DateTimeStyles.RoundtripKind); } return(new W3SVCRecord(timestamp, data)); }
protected override KeyValueLogRecord CreateRecord(DelimitedTextLogContext context, Dictionary <string, string> data) { var timestamp = DateTime.Now; if (data.TryGetValue("TimestampUtc", out var timestampText)) { timestamp = DateTime.Parse(timestampText, null, DateTimeStyles.AssumeUniversal); } else if (data.TryGetValue("Timestamp", out timestampText)) { timestamp = DateTime.Parse(timestampText, null, DateTimeStyles.AssumeLocal); } return(new KeyValueLogRecord(timestamp, data)); }
/// <inheritdoc/> public async Task ParseRecordsAsync(DelimitedTextLogContext context, IList <IEnvelope <TData> > output, int recordCount, CancellationToken stopToken = default) { if (context.Fields is null) { context.Fields = await TryGetHeaderFields(context, stopToken); } 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); context.Position += consumed; if (line is null) { break; } if (ShouldStopAndRollback(line, context)) { context.Position -= consumed; return; } context.LineNumber++; if (IsHeaders(line, context.LineNumber)) { context.Fields = ParseHeadersLine(line); continue; } else if (IsComment(line)) { continue; } try { // 'ParseDataFragments' and 'CreateRecord' might throw error, so we need to catch it and skip the record var fragments = ParseDataFragments(line); if (context.Fields is null) { _logger.LogWarning("Unknown field mapping, skipping line {0}", context.LineNumber); continue; } var dict = new Dictionary <string, string>(); for (var i = 0; i < context.Fields.Length; i++) { if (i >= fragments.Length) { break; } var(key, val) = KeyValueSelector(context.Fields[i], fragments[i]); dict[key] = val; } var record = CreateRecord(context, dict); var envelope = new LogEnvelope <TData>(record, record.Timestamp, line, context.FilePath, context.Position, context.LineNumber); output.Add(envelope); count++; } catch (Exception ex) { _logger.LogError(ex, "Error processing record '{0}'", line); continue; } } } } }
/// <summary> /// When immplemented, create a record based on the parsed key-value pairs. /// </summary> protected abstract TData CreateRecord(DelimitedTextLogContext context, Dictionary <string, string> data);
/// <summary> /// When implemented, returns true iff the line indicates that the reader should stop and rewind. /// </summary> protected virtual bool ShouldStopAndRollback(string line, DelimitedTextLogContext context) => false;