2021-11-08 12:18:49 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
2022-05-17 12:57:51 -04:00
using System.Reflection ;
2021-11-08 12:18:49 -05:00
using System.Text ;
using System.Text.RegularExpressions ;
2022-03-24 16:35:00 -04:00
using Microsoft.Extensions.Logging ;
2021-11-08 12:18:49 -05:00
namespace EpicGames.Core
{
/// <summary>
/// Confidence of a matched log event being the correct derivation
/// </summary>
public enum LogEventPriority
{
None ,
Lowest ,
Low ,
BelowNormal ,
Normal ,
AboveNormal ,
High ,
Highest ,
}
public class LogEventMatch
{
public LogEventPriority Priority { get ; }
public List < LogEvent > Events { get ; }
2022-03-24 16:35:00 -04:00
public LogEventMatch ( LogEventPriority priority , LogEvent logEvent )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
Priority = priority ;
Events = new List < LogEvent > { logEvent } ;
2021-11-08 12:18:49 -05:00
}
2022-03-24 16:35:00 -04:00
public LogEventMatch ( LogEventPriority priority , IEnumerable < LogEvent > events )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
Priority = priority ;
Events = events . ToList ( ) ;
2021-11-08 12:18:49 -05:00
}
}
/// <summary>
/// Interface for a class which matches error strings
/// </summary>
public interface ILogEventMatcher
{
/// <summary>
/// Attempt to match events from the given input buffer
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="cursor">The input buffer</param>
2021-11-08 12:18:49 -05:00
/// <returns>Information about the error that was matched, or null if an error was not matched</returns>
2022-03-24 16:35:00 -04:00
LogEventMatch ? Match ( ILogCursor cursor ) ;
2021-11-08 12:18:49 -05:00
}
/// <summary>
/// Turns raw text output into structured logging events
/// </summary>
public class LogEventParser : IDisposable
{
/// <summary>
/// List of event matchers for this parser
/// </summary>
public List < ILogEventMatcher > Matchers { get ; } = new List < ILogEventMatcher > ( ) ;
/// <summary>
/// List of patterns to ignore
/// </summary>
public List < Regex > IgnorePatterns { get ; } = new List < Regex > ( ) ;
/// <summary>
/// Buffer of input lines
/// </summary>
2022-03-24 16:35:00 -04:00
readonly LogBuffer _buffer ;
2021-11-08 12:18:49 -05:00
/// <summary>
/// Buffer for holding partial line data
/// </summary>
2022-03-24 16:35:00 -04:00
readonly MemoryStream _partialLine = new MemoryStream ( ) ;
2021-11-08 12:18:49 -05:00
/// <summary>
/// Whether matching is currently enabled
/// </summary>
2022-03-24 16:35:00 -04:00
int _matchingEnabled ;
2021-11-08 12:18:49 -05:00
/// <summary>
/// The inner logger
/// </summary>
2022-05-17 12:57:51 -04:00
ILogger _logger ;
/// <summary>
/// Public accessor for the logger
/// </summary>
public ILogger Logger
{
get = > _logger ;
set = > _logger = value ;
}
2021-11-08 12:18:49 -05:00
/// <summary>
/// Constructor
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="logger">The logger to receive parsed output messages</param>
public LogEventParser ( ILogger logger )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
_logger = logger ;
_buffer = new LogBuffer ( 50 ) ;
2021-11-08 12:18:49 -05:00
}
/// <inheritdoc/>
public void Dispose ( ) = > Flush ( ) ;
2022-05-17 12:57:51 -04:00
/// <summary>
/// Enumerate all the types that implement <see cref="ILogEventMatcher"/> in the given assembly, and create instances of them
/// </summary>
/// <param name="assembly">The assembly to enumerate matchers from</param>
public void AddMatchersFromAssembly ( Assembly assembly )
{
foreach ( Type type in assembly . GetTypes ( ) )
{
if ( type . IsClass & & typeof ( ILogEventMatcher ) . IsAssignableFrom ( type ) )
{
2022-05-31 15:32:26 -04:00
_logger . LogDebug ( "Adding event matcher: {Type}" , type . Name ) ;
2022-05-17 12:57:51 -04:00
ILogEventMatcher matcher = ( ILogEventMatcher ) Activator . CreateInstance ( type ) ! ;
Matchers . Add ( matcher ) ;
}
}
}
2021-11-08 12:18:49 -05:00
/// <summary>
/// Writes a line to the event filter
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="line">The line to output</param>
public void WriteLine ( string line )
2021-11-08 12:18:49 -05:00
{
2022-05-17 12:57:51 -04:00
if ( line . Length > 0 & & line [ 0 ] = = '{' )
{
byte [ ] data = Encoding . UTF8 . GetBytes ( line ) ;
2022-05-23 09:58:48 -04:00
try
2022-05-17 12:57:51 -04:00
{
2022-05-23 09:58:48 -04:00
JsonLogEvent jsonEvent ;
if ( JsonLogEvent . TryParse ( data , out jsonEvent ) )
{
ProcessData ( true ) ;
_logger . Log ( jsonEvent . Level , jsonEvent . EventId , jsonEvent , null , JsonLogEvent . Format ) ;
return ;
}
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Exception while parsing log event" ) ;
2022-05-17 12:57:51 -04:00
}
}
_buffer . AddLine ( StringUtils . ParseEscapeCodes ( line ) ) ;
2021-11-08 12:18:49 -05:00
ProcessData ( false ) ;
}
/// <summary>
/// Writes data to the log parser
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="data">Data to write</param>
public void WriteData ( ReadOnlyMemory < byte > data )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
int baseIdx = 0 ;
int scanIdx = 0 ;
ReadOnlySpan < byte > span = data . Span ;
2021-11-08 12:18:49 -05:00
// Handle a partially existing line
2022-03-24 16:35:00 -04:00
if ( _partialLine . Length > 0 )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
for ( ; scanIdx < span . Length ; scanIdx + + )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
if ( span [ scanIdx ] = = '\n' )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
_partialLine . Write ( span . Slice ( baseIdx , scanIdx - baseIdx ) ) ;
2021-11-08 12:18:49 -05:00
FlushPartialLine ( ) ;
2022-03-24 16:35:00 -04:00
baseIdx = + + scanIdx ;
2021-11-08 12:18:49 -05:00
break ;
}
}
}
// Handle any complete lines
2022-03-24 16:35:00 -04:00
for ( ; scanIdx < span . Length ; scanIdx + + )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
if ( span [ scanIdx ] = = '\n' )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
AddLine ( data . Slice ( baseIdx , scanIdx - baseIdx ) ) ;
baseIdx = scanIdx + 1 ;
2021-11-08 12:18:49 -05:00
}
}
// Add the rest of the text to the partial line buffer
2022-03-24 16:35:00 -04:00
_partialLine . Write ( span . Slice ( baseIdx ) ) ;
2021-11-08 12:18:49 -05:00
// Process the new data
ProcessData ( false ) ;
}
/// <summary>
/// Flushes the current contents of the parser
/// </summary>
public void Flush ( )
{
// If there's a partially written line, write that out first
2022-03-24 16:35:00 -04:00
if ( _partialLine . Length > 0 )
2021-11-08 12:18:49 -05:00
{
FlushPartialLine ( ) ;
}
// Process any remaining data
ProcessData ( true ) ;
}
/// <summary>
/// Adds a raw utf-8 string to the buffer
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="data">The string data</param>
private void AddLine ( ReadOnlyMemory < byte > data )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
if ( data . Length > 0 & & data . Span [ data . Length - 1 ] = = '\r' )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
data = data . Slice ( 0 , data . Length - 1 ) ;
2021-11-08 12:18:49 -05:00
}
2022-03-24 16:35:00 -04:00
if ( data . Length > 0 & & data . Span [ 0 ] = = '{' )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
JsonLogEvent jsonEvent ;
if ( JsonLogEvent . TryParse ( data , out jsonEvent ) )
2021-11-08 12:18:49 -05:00
{
ProcessData ( true ) ;
2022-03-24 16:35:00 -04:00
_logger . Log ( jsonEvent . Level , jsonEvent . EventId , jsonEvent , null , JsonLogEvent . Format ) ;
2021-11-08 12:18:49 -05:00
return ;
}
}
2022-03-24 16:35:00 -04:00
_buffer . AddLine ( StringUtils . ParseEscapeCodes ( Encoding . UTF8 . GetString ( data . Span ) ) ) ;
2021-11-08 12:18:49 -05:00
}
/// <summary>
/// Writes the current partial line data, with the given data appended to it, then clear the buffer
/// </summary>
private void FlushPartialLine ( )
{
2022-03-24 16:35:00 -04:00
AddLine ( _partialLine . ToArray ( ) ) ;
_partialLine . Position = 0 ;
_partialLine . SetLength ( 0 ) ;
2021-11-08 12:18:49 -05:00
}
/// <summary>
/// Process any data in the buffer
/// </summary>
/// <param name="bFlush">Whether we've reached the end of the stream</param>
void ProcessData ( bool bFlush )
{
2022-03-24 16:35:00 -04:00
while ( _buffer . Length > 0 )
2021-11-08 12:18:49 -05:00
{
// Try to match an event
2022-03-24 16:35:00 -04:00
List < LogEvent > ? events = null ;
if ( Regex . IsMatch ( _buffer [ 0 ] , "<-- Suspend Log Parsing -->" , RegexOptions . IgnoreCase ) )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
_matchingEnabled - - ;
2021-11-08 12:18:49 -05:00
}
2022-03-24 16:35:00 -04:00
else if ( Regex . IsMatch ( _buffer [ 0 ] , "<-- Resume Log Parsing -->" , RegexOptions . IgnoreCase ) )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
_matchingEnabled + + ;
2021-11-08 12:18:49 -05:00
}
2022-03-24 16:35:00 -04:00
else if ( _matchingEnabled > = 0 )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
events = MatchEvent ( ) ;
2021-11-08 12:18:49 -05:00
}
// Bail out if we need more data
2022-03-24 16:35:00 -04:00
if ( _buffer . Length < 1024 & & ! bFlush & & _buffer . NeedMoreData )
2021-11-08 12:18:49 -05:00
{
break ;
}
// If we did match something, check if it's not negated by an ignore pattern. We typically have relatively few errors and many more ignore patterns than matchers, so it's quicker
// to check them in response to an identified error than to treat them as matchers of their own.
2022-03-24 16:35:00 -04:00
if ( events ! = null )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
foreach ( Regex ignorePattern in IgnorePatterns )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
if ( ignorePattern . IsMatch ( _buffer [ 0 ] ) )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
events = null ;
2021-11-08 12:18:49 -05:00
break ;
}
}
}
// Report the error to the listeners
2022-03-24 16:35:00 -04:00
if ( events ! = null )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
WriteEvents ( events ) ;
_buffer . Advance ( events . Count ) ;
2021-11-08 12:18:49 -05:00
}
else
{
2022-03-24 16:35:00 -04:00
_logger . Log ( LogLevel . Information , KnownLogEvents . None , _buffer [ 0 ] ! , null , ( state , exception ) = > state ) ;
_buffer . MoveNext ( ) ;
2021-11-08 12:18:49 -05:00
}
}
}
/// <summary>
/// Try to match an event from the current buffer
/// </summary>
/// <returns>The matched event</returns>
private List < LogEvent > ? MatchEvent ( )
{
2022-03-24 16:35:00 -04:00
LogEventMatch ? currentMatch = null ;
foreach ( ILogEventMatcher matcher in Matchers )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
LogEventMatch ? match = matcher . Match ( _buffer ) ;
if ( match ! = null )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
if ( currentMatch = = null | | match . Priority > currentMatch . Priority )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
currentMatch = match ;
2021-11-08 12:18:49 -05:00
}
}
}
2022-03-24 16:35:00 -04:00
return currentMatch ? . Events ;
2021-11-08 12:18:49 -05:00
}
/// <summary>
/// Writes an event to the log
/// </summary>
/// <param name="Event">The event to write</param>
2022-03-24 16:35:00 -04:00
protected virtual void WriteEvents ( List < LogEvent > logEvents )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
foreach ( LogEvent logEvent in logEvents )
2021-11-08 12:18:49 -05:00
{
2022-03-24 16:35:00 -04:00
_logger . Log ( logEvent . Level , logEvent . Id , logEvent , null , ( state , exception ) = > state . ToString ( ) ) ;
2021-11-08 12:18:49 -05:00
}
}
}
}