2020-12-21 11:50:46 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
2022-03-24 16:35:00 -04:00
using System.Buffers ;
2020-12-21 11:50:46 -04:00
using System.Collections.Generic ;
using System.Diagnostics ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
using System.Text ;
2021-11-07 23:43:01 -05:00
using System.Text.Json ;
using System.Text.RegularExpressions ;
2022-05-31 14:41:00 -04:00
using System.Threading.Channels ;
using System.Threading.Tasks ;
2022-03-24 16:35:00 -04:00
using JetBrains.Annotations ;
using Microsoft.Extensions.DependencyInjection ;
using Microsoft.Extensions.Logging ;
2020-12-28 14:34:13 -04:00
2020-12-21 23:07:37 -04:00
namespace EpicGames.Core
2020-12-21 11:50:46 -04:00
{
/// <summary>
/// Log Event Type
/// </summary>
public enum LogEventType
{
/// <summary>
/// The log event is a fatal error
/// </summary>
2020-12-28 16:36:30 -04:00
Fatal = LogLevel . Critical ,
2020-12-21 11:50:46 -04:00
/// <summary>
/// The log event is an error
/// </summary>
2020-12-28 16:36:30 -04:00
Error = LogLevel . Error ,
2020-12-21 11:50:46 -04:00
/// <summary>
/// The log event is a warning
/// </summary>
2020-12-28 16:36:30 -04:00
Warning = LogLevel . Warning ,
2020-12-21 11:50:46 -04:00
/// <summary>
/// Output the log event to the console
/// </summary>
2020-12-28 16:36:30 -04:00
Console = LogLevel . Information ,
2020-12-21 11:50:46 -04:00
/// <summary>
/// Output the event to the on-disk log
/// </summary>
2020-12-28 16:36:30 -04:00
Log = LogLevel . Debug ,
2020-12-21 11:50:46 -04:00
/// <summary>
/// The log event should only be displayed if verbose logging is enabled
/// </summary>
2020-12-28 16:36:30 -04:00
Verbose = LogLevel . Trace ,
2020-12-21 11:50:46 -04:00
/// <summary>
/// The log event should only be displayed if very verbose logging is enabled
/// </summary>
2020-12-28 16:36:30 -04:00
VeryVerbose = LogLevel . Trace
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Options for formatting messages
/// </summary>
[Flags]
public enum LogFormatOptions
{
/// <summary>
/// Format normally
/// </summary>
None = 0 ,
/// <summary>
/// Never write a severity prefix. Useful for pre-formatted messages that need to be in a particular format for, eg. the Visual Studio output window
/// </summary>
NoSeverityPrefix = 1 ,
/// <summary>
/// Do not output text to the console
/// </summary>
NoConsoleOutput = 2 ,
}
/// <summary>
/// UAT/UBT Custom log system.
///
/// This lets you use any TraceListeners you want, but you should only call the static
/// methods below, not call Trace.XXX directly, as the static methods
/// This allows the system to enforce the formatting and filtering conventions we desire.
///
/// For posterity, we cannot use the Trace or TraceSource class directly because of our special log requirements:
/// 1. We possibly capture the method name of the logging event. This cannot be done as a macro, so must be done at the top level so we know how many layers of the stack to peel off to get the real function.
/// 2. We have a verbose filter we would like to apply to all logs without having to have each listener filter individually, which would require our string formatting code to run every time.
/// 3. We possibly want to ensure severity prefixes are logged, but Trace.WriteXXX does not allow any severity info to be passed down.
/// </summary>
2022-03-24 16:35:00 -04:00
public static class Log
2020-12-21 11:50:46 -04:00
{
/// <summary>
2022-05-17 12:57:51 -04:00
/// Singleton instance of the default output logger
2020-12-21 11:50:46 -04:00
/// </summary>
2022-05-17 12:57:51 -04:00
private static readonly DefaultLogger DefaultLogger = new DefaultLogger ( ) ;
2020-12-21 11:50:46 -04:00
/// <summary>
2022-05-17 12:57:51 -04:00
/// Logger instance which parses events and forwards them to the main logger.
2020-12-21 11:50:46 -04:00
/// </summary>
2022-05-17 12:57:51 -04:00
private static readonly LegacyEventLogger LegacyLogger = new LegacyEventLogger ( DefaultLogger ) ;
/// <summary>
/// Accessor for the global event parser from legacy events
/// </summary>
public static LogEventParser EventParser = > LegacyLogger . Parser ;
/// <summary>
/// Logger instance
/// </summary>
public static ILogger Logger = > LegacyLogger ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// When true, verbose logging is enabled.
/// </summary>
public static LogEventType OutputLevel
{
2021-10-12 21:21:22 -04:00
get = > ( LogEventType ) DefaultLogger . OutputLevel ;
set = > DefaultLogger . OutputLevel = ( LogLevel ) value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Whether to include timestamps on each line of log output
/// </summary>
public static bool IncludeTimestamps
{
2020-12-28 16:36:30 -04:00
get = > DefaultLogger . IncludeTimestamps ;
set = > DefaultLogger . IncludeTimestamps = value ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
2021-10-12 21:21:22 -04:00
/// When true, warnings and errors will have a WARNING: or ERROR: prefix, respectively.
2020-12-21 11:50:46 -04:00
/// </summary>
2021-10-12 21:21:22 -04:00
public static bool IncludeSeverityPrefix { get ; set ; } = true ;
2020-12-28 16:36:30 -04:00
2020-12-21 11:50:46 -04:00
/// <summary>
/// When true, warnings and errors will have a prefix suitable for display by MSBuild (avoiding error messages showing as (EXEC : Error : ")
/// </summary>
2021-10-12 21:21:22 -04:00
public static bool IncludeProgramNameWithSeverityPrefix { get ; set ; }
2020-12-28 16:36:30 -04:00
2020-12-21 11:50:46 -04:00
/// <summary>
/// When true, will detect warnings and errors and set the console output color to yellow and red.
/// </summary>
public static bool ColorConsoleOutput
{
2020-12-28 16:36:30 -04:00
get = > DefaultLogger . ColorConsoleOutput ;
set = > DefaultLogger . ColorConsoleOutput = value ;
2020-12-21 11:50:46 -04:00
}
2021-08-30 08:44:36 -04:00
/// <summary>
/// When true, a timestamp will be written to the log file when the first listener is added
/// </summary>
public static bool IncludeStartingTimestamp
{
get = > DefaultLogger . IncludeStartingTimestamp ;
set = > DefaultLogger . IncludeStartingTimestamp = value ;
}
2021-11-07 23:43:01 -05:00
/// <summary>
/// When true, create a backup of any log file that would be overwritten by a new log
/// Log.txt will be backed up with its UTC creation time in the name e.g.
/// Log-backup-2021.10.29-19.53.17.txt
/// </summary>
public static bool BackupLogFiles = true ;
/// <summary>
/// The number of backups to be preserved - when there are more than this, the oldest backups will be deleted.
/// Backups will not be deleted if BackupLogFiles is false.
/// </summary>
public static int LogFileBackupCount = 10 ;
2020-12-21 11:50:46 -04:00
/// <summary>
/// Path to the log file being written to. May be null.
/// </summary>
2021-11-07 23:43:01 -05:00
public static FileReference ? OutputFile = > DefaultLogger ? . OutputFile ;
2020-12-21 11:50:46 -04:00
2021-10-12 21:21:22 -04:00
/// <summary>
/// A collection of strings that have been already written once
/// </summary>
2022-03-24 16:35:00 -04:00
private static readonly HashSet < string > s_writeOnceSet = new HashSet < string > ( ) ;
2021-10-12 21:21:22 -04:00
2022-05-17 12:57:51 -04:00
/// <summary>
/// Overrides the logger used for formatting output, after event parsing
/// </summary>
/// <param name="logger"></param>
public static void SetInnerLogger ( ILogger logger )
{
LegacyLogger . SetInnerLogger ( logger ) ;
}
2022-05-31 14:41:00 -04:00
/// <summary>
/// Flush the current log output
/// </summary>
/// <returns></returns>
public static async Task FlushAsync ( )
{
await DefaultLogger . FlushAsync ( ) ;
}
2020-12-21 11:50:46 -04:00
/// <summary>
2021-11-07 23:43:01 -05:00
/// Adds a trace listener that writes to a log file.
/// If Log.DuplicateLogFiles is true, two files will be created - one with the requested name,
/// another with a timestamp appended before any extension.
/// If a StartupTraceListener was in use, this function will copy its captured data to the log file(s)
/// and remove the startup listener from the list of registered listeners.
2020-12-21 11:50:46 -04:00
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="outputFile">The file to write to</param>
2020-12-21 11:50:46 -04:00
/// <returns>The created trace listener</returns>
2022-03-24 16:35:00 -04:00
public static void AddFileWriter ( string name , FileReference outputFile )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
Log . TraceInformation ( $"Log file: {outputFile}" ) ;
2021-11-07 23:43:01 -05:00
2022-03-24 16:35:00 -04:00
if ( Log . BackupLogFiles & & FileReference . Exists ( outputFile ) )
2021-11-07 23:43:01 -05:00
{
// before creating a new backup, cap the number of existing files
2022-03-24 16:35:00 -04:00
string filenameWithoutExtension = outputFile . GetFileNameWithoutExtension ( ) ;
string extension = outputFile . GetExtension ( ) ;
2021-11-07 23:43:01 -05:00
2022-03-24 16:35:00 -04:00
Regex backupForm =
new Regex ( filenameWithoutExtension + @"-backup-\d\d\d\d\.\d\d\.\d\d-\d\d\.\d\d\.\d\d" + extension ) ;
2021-11-07 23:43:01 -05:00
2022-03-24 16:35:00 -04:00
foreach ( FileReference oldBackup in DirectoryReference
. EnumerateFiles ( outputFile . Directory )
2021-11-07 23:43:01 -05:00
// find files that match the way that we name backup files
2022-03-24 16:35:00 -04:00
. Where ( x = > backupForm . IsMatch ( x . GetFileName ( ) ) )
2021-11-07 23:43:01 -05:00
// sort them from newest to oldest
. OrderByDescending ( x = > x . GetFileName ( ) )
// skip the newest ones that are to be kept; -1 because we're about to create another backup.
. Skip ( Log . LogFileBackupCount - 1 ) )
{
2022-03-24 16:35:00 -04:00
Log . TraceLog ( $"Deleting old log file: {oldBackup}" ) ;
FileReference . Delete ( oldBackup ) ;
2021-11-07 23:43:01 -05:00
}
// Ensure that the backup gets a unique name, in the extremely unlikely case that UBT was run twice during
// the same second.
2022-03-24 16:35:00 -04:00
DateTime fileTime = File . GetCreationTimeUtc ( outputFile . FullName ) ;
2021-11-07 23:43:01 -05:00
2022-03-24 16:35:00 -04:00
FileReference backupFile ;
2021-11-07 23:43:01 -05:00
for ( ; ; )
{
2022-03-24 16:35:00 -04:00
string timestamp = $"{fileTime:yyyy.MM.dd-HH.mm.ss}" ;
backupFile = FileReference . Combine ( outputFile . Directory ,
$"{filenameWithoutExtension}-backup-{timestamp}{extension}" ) ;
if ( ! FileReference . Exists ( backupFile ) )
2021-11-07 23:43:01 -05:00
{
break ;
}
2022-03-24 16:35:00 -04:00
fileTime = fileTime . AddSeconds ( 1 ) ;
2021-11-07 23:43:01 -05:00
}
2022-03-24 16:35:00 -04:00
FileReference . Move ( outputFile , backupFile ) ;
2021-11-07 23:43:01 -05:00
}
2022-03-24 16:35:00 -04:00
TextWriterTraceListener firstTextWriter = DefaultLogger . AddFileWriter ( name , outputFile ) ;
2021-11-07 23:43:01 -05:00
// find the StartupTraceListener in the listeners that was added early on
2022-03-24 16:35:00 -04:00
IEnumerable < StartupTraceListener > startupListeners = Trace . Listeners . OfType < StartupTraceListener > ( ) ;
if ( startupListeners . Any ( ) )
2021-11-07 23:43:01 -05:00
{
2022-03-24 16:35:00 -04:00
StartupTraceListener startupListener = startupListeners . First ( ) ;
startupListener . CopyTo ( firstTextWriter ) ;
Trace . Listeners . Remove ( startupListener ) ;
2021-11-07 23:43:01 -05:00
}
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Adds a <see cref="TraceListener"/> to the collection in a safe manner.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="traceListener">The <see cref="TraceListener"/> to add.</param>
public static void AddTraceListener ( TraceListener traceListener )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
DefaultLogger . AddTraceListener ( traceListener ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Removes a <see cref="TraceListener"/> from the collection in a safe manner.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="traceListener">The <see cref="TraceListener"/> to remove.</param>
public static void RemoveTraceListener ( TraceListener traceListener )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
DefaultLogger . RemoveTraceListener ( traceListener ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Determines if a TextWriterTraceListener has been added to the list of trace listeners
/// </summary>
/// <returns>True if a TextWriterTraceListener has been added</returns>
public static bool HasFileWriter ( )
{
2020-12-28 16:36:30 -04:00
return DefaultLogger . HasFileWriter ( ) ;
2020-12-21 11:50:46 -04:00
}
2021-10-12 21:21:22 -04:00
/// <summary>
/// Converts a LogEventType into a log prefix. Only used when bLogSeverity is true.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="severity"></param>
2021-10-12 21:21:22 -04:00
/// <returns></returns>
2022-03-24 16:35:00 -04:00
private static string GetSeverityPrefix ( LogEventType severity )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
switch ( severity )
2021-10-12 21:21:22 -04:00
{
case LogEventType . Fatal :
return "FATAL ERROR: " ;
case LogEventType . Error :
return "ERROR: " ;
case LogEventType . Warning :
return "WARNING: " ;
case LogEventType . Console :
return "" ;
case LogEventType . Verbose :
return "VERBOSE: " ;
default :
return "" ;
}
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Writes a formatted message to the console. All other functions should boil down to calling this method.
/// </summary>
/// <param name="bWriteOnce">If true, this message will be written only once</param>
2022-03-24 16:35:00 -04:00
/// <param name="verbosity">Message verbosity level. We only meaningfully use values up to Verbose</param>
/// <param name="formatOptions">Options for formatting messages</param>
/// <param name="format">Message format string.</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
private static void WriteLinePrivate ( bool bWriteOnce , LogEventType verbosity , LogFormatOptions formatOptions , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( Logger . IsEnabled ( ( LogLevel ) verbosity ) )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
StringBuilder message = new StringBuilder ( ) ;
2021-10-12 21:21:22 -04:00
// Get the severity prefix for this message
2022-03-24 16:35:00 -04:00
if ( IncludeSeverityPrefix & & ( ( formatOptions & LogFormatOptions . NoSeverityPrefix ) = = 0 ) )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
message . Append ( GetSeverityPrefix ( verbosity ) ) ;
if ( message . Length > 0 & & IncludeProgramNameWithSeverityPrefix )
2021-10-12 21:21:22 -04:00
{
// Include the executable name when running inside MSBuild. If unspecified, MSBuild re-formats them with an "EXEC :" prefix.
2022-03-24 16:35:00 -04:00
message . Insert ( 0 , $"{Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly()!.Location)}: " ) ;
2021-10-12 21:21:22 -04:00
}
}
// Append the formatted string
2022-03-24 16:35:00 -04:00
int indentLen = message . Length ;
if ( args . Length = = 0 )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
message . Append ( format ) ;
2021-10-12 21:21:22 -04:00
}
else
{
2022-03-24 16:35:00 -04:00
message . AppendFormat ( format , args ) ;
2021-10-12 21:21:22 -04:00
}
// Replace any Windows \r\n sequences with \n
2022-03-24 16:35:00 -04:00
message . Replace ( "\r\n" , "\n" ) ;
2021-10-12 21:21:22 -04:00
// Remove any trailing whitespace
2022-03-24 16:35:00 -04:00
int trimLen = message . Length ;
while ( trimLen > 0 & & " \t\r\n" . Contains ( message [ trimLen - 1 ] ) )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
trimLen - - ;
2021-10-12 21:21:22 -04:00
}
2022-03-24 16:35:00 -04:00
message . Remove ( trimLen , message . Length - trimLen ) ;
2021-10-12 21:21:22 -04:00
// Update the indent length to include any whitespace at the start of the message
2022-03-24 16:35:00 -04:00
while ( indentLen < message . Length & & message [ indentLen ] = = ' ' )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
indentLen + + ;
2021-10-12 21:21:22 -04:00
}
// If there are multiple lines, insert a prefix at the start of each one
2022-03-24 16:35:00 -04:00
for ( int idx = 0 ; idx < message . Length ; idx + + )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
if ( message [ idx ] = = '\n' )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
message . Insert ( idx + 1 , " " , indentLen ) ;
idx + = indentLen ;
2021-10-12 21:21:22 -04:00
}
}
// if we want this message only written one time, check if it was already written out
2022-03-24 16:35:00 -04:00
if ( bWriteOnce & & ! s_writeOnceSet . Add ( message . ToString ( ) ) )
2021-10-12 21:21:22 -04:00
{
return ;
}
// Forward it on to the internal logger
2022-05-17 12:57:51 -04:00
if ( verbosity < LogEventType . Console )
{
Logger . Log ( ( LogLevel ) verbosity , message . ToString ( ) ) ;
}
else
{
2022-05-17 15:03:25 -04:00
lock ( EventParser )
{
2022-05-23 09:58:48 -04:00
int baseIdx = 0 ;
for ( int idx = 0 ; idx < message . Length ; idx + + )
{
if ( message [ idx ] = = '\n' )
{
EventParser . WriteLine ( message . ToString ( baseIdx , idx - baseIdx ) ) ;
baseIdx = idx + 1 ;
}
}
EventParser . WriteLine ( message . ToString ( baseIdx , message . Length - baseIdx ) ) ;
2022-05-17 15:03:25 -04:00
}
2022-05-17 12:57:51 -04:00
}
2021-10-12 21:21:22 -04:00
}
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Similar to Trace.WriteLineIf
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="condition"></param>
/// <param name="verbosity"></param>
/// <param name="format"></param>
/// <param name="args"></param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void WriteLineIf ( bool condition , LogEventType verbosity , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( condition )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , verbosity , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
}
/// <summary>
/// Similar to Trace.WriteLine
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="verbosity"></param>
/// <param name="format"></param>
/// <param name="args"></param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void WriteLine ( LogEventType verbosity , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , verbosity , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Similar to Trace.WriteLine
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="verbosity"></param>
/// <param name="formatOptions"></param>
/// <param name="format"></param>
/// <param name="args"></param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void WriteLine ( LogEventType verbosity , LogFormatOptions formatOptions , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , verbosity , formatOptions , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Formats an exception for display in the log. The exception message is shown as an error, and the stack trace is included in the log.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="ex">The exception to display</param>
/// <param name="logFileName">The log filename to display, if any</param>
public static void WriteException ( Exception ex , string? logFileName )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
string logSuffix = ( logFileName = = null ) ? "" : String . Format ( "\n(see {0} for full exception trace)" , logFileName ) ;
2020-12-21 11:50:46 -04:00
TraceLog ( "==============================================================================" ) ;
2022-03-24 16:35:00 -04:00
TraceError ( "{0}{1}" , ExceptionUtils . FormatException ( ex ) , logSuffix ) ;
TraceLog ( "\n{0}" , ExceptionUtils . FormatExceptionDetails ( ex ) ) ;
2020-12-21 11:50:46 -04:00
TraceLog ( "==============================================================================" ) ;
}
/// <summary>
/// Writes an error message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceError ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Error , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes an error message to the console, in a format suitable for Visual Studio to parse.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="file">The file containing the error</param>
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceErrorTask ( FileReference file , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Error , LogFormatOptions . NoSeverityPrefix , "{0}: error: {1}" , file , String . Format ( format , args ) ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes an error message to the console, in a format suitable for Visual Studio to parse.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="file">The file containing the error</param>
/// <param name="line">Line number of the error</param>
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceErrorTask ( FileReference file , int line , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Error , LogFormatOptions . NoSeverityPrefix , "{0}({1}): error: {2}" , file , line , String . Format ( format , args ) ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a verbose message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2020-12-21 11:50:46 -04:00
[Conditional("TRACE")]
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceVerbose ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Verbose , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceInformation ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Console , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a warning message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceWarning ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Warning , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
2021-10-12 21:21:22 -04:00
/// Writes a warning message to the console, in a format suitable for Visual Studio to parse.
2020-12-21 11:50:46 -04:00
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="file">The file containing the warning</param>
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceWarningTask ( FileReference file , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Warning , LogFormatOptions . NoSeverityPrefix , "{0}: warning: {1}" , file , String . Format ( format , args ) ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
2021-10-12 21:21:22 -04:00
/// Writes a warning message to the console, in a format suitable for Visual Studio to parse.
2020-12-21 11:50:46 -04:00
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="file">The file containing the warning</param>
/// <param name="line">Line number of the warning</param>
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceWarningTask ( FileReference file , int line , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Warning , LogFormatOptions . NoSeverityPrefix , "{0}({1}): warning: {2}" , file , line , String . Format ( format , args ) ) ;
2020-12-21 11:50:46 -04:00
}
2021-10-12 21:21:22 -04:00
/// <summary>
/// Writes a message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="file">The file containing the message</param>
/// <param name="line">Line number of the message</param>
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
public static void TraceConsoleTask ( FileReference file , int line , string format , params object? [ ] args )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Console , LogFormatOptions . NoSeverityPrefix , "{0}({1}): {2}" , file , line , String . Format ( format , args ) ) ;
2021-10-12 21:21:22 -04:00
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Writes a very verbose message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2020-12-21 11:50:46 -04:00
[Conditional("TRACE")]
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceVeryVerbose ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . VeryVerbose , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a message to the log only.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2020-12-21 11:50:46 -04:00
[Conditional("TRACE")]
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceLog ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( false , LogEventType . Log , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Similar to Trace.WriteLine
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="verbosity"></param>
/// <param name="format"></param>
/// <param name="args"></param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void WriteLineOnce ( LogEventType verbosity , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , verbosity , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Similar to Trace.WriteLine
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="verbosity"></param>
/// <param name="options"></param>
/// <param name="format"></param>
/// <param name="args"></param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void WriteLineOnce ( LogEventType verbosity , LogFormatOptions options , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , verbosity , options , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes an error message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceErrorOnce ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , LogEventType . Error , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a verbose message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2020-12-21 11:50:46 -04:00
[Conditional("TRACE")]
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceVerboseOnce ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , LogEventType . Verbose , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceInformationOnce ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , LogEventType . Console , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a warning message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceWarningOnce ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , LogEventType . Warning , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a warning message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="file">The file containing the error</param>
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceWarningOnce ( FileReference file , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , LogEventType . Warning , LogFormatOptions . NoSeverityPrefix , "{0}: warning: {1}" , file , String . Format ( format , args ) ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a warning message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="file">The file containing the error</param>
/// <param name="line">Line number of the error</param>
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceWarningOnce ( FileReference file , int line , string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , LogEventType . Warning , LogFormatOptions . NoSeverityPrefix , "{0}({1}): warning: {2}" , file , line , String . Format ( format , args ) ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a very verbose message to the console.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2020-12-21 11:50:46 -04:00
[Conditional("TRACE")]
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceVeryVerboseOnce ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , LogEventType . VeryVerbose , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
/// <summary>
/// Writes a message to the log only.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="format">Message format string</param>
/// <param name="args">Optional arguments</param>
2020-12-21 11:50:46 -04:00
[Conditional("TRACE")]
2021-09-22 08:44:41 -04:00
[StringFormatMethod("Format")]
2022-03-24 16:35:00 -04:00
public static void TraceLogOnce ( string format , params object? [ ] args )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
WriteLinePrivate ( true , LogEventType . Log , LogFormatOptions . None , format , args ) ;
2020-12-21 11:50:46 -04:00
}
2020-12-28 16:36:30 -04:00
/// <summary>
/// Enter a scope with the given status message. The message will be written to the console without a newline, allowing it to be updated through subsequent calls to UpdateStatus().
/// The message will be written to the log immediately. If another line is written while in a status scope, the initial status message is flushed to the console first.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="message">The status message</param>
2020-12-28 16:36:30 -04:00
[Conditional("TRACE")]
2022-03-24 16:35:00 -04:00
public static void PushStatus ( string message )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
DefaultLogger . PushStatus ( message ) ;
2020-12-28 16:36:30 -04:00
}
/// <summary>
/// Updates the current status message. This will overwrite the previous status line.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="message">The status message</param>
2020-12-28 16:36:30 -04:00
[Conditional("TRACE")]
2022-03-24 16:35:00 -04:00
public static void UpdateStatus ( string message )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
DefaultLogger . UpdateStatus ( message ) ;
2020-12-28 16:36:30 -04:00
}
/// <summary>
/// Updates the Pops the top status message from the stack. The mess
/// </summary>
/// <param name="Message"></param>
[Conditional("TRACE")]
public static void PopStatus ( )
{
DefaultLogger . PopStatus ( ) ;
}
}
2022-01-06 16:44:09 +00:00
/// <summary>
/// Logger which captures the output for rendering later
/// </summary>
public class CaptureLogger : ILogger
{
class NullScope : IDisposable
{
public void Dispose ( ) { }
}
/// <summary>
/// List of captured events
/// </summary>
public List < LogEvent > Events { get ; } = new List < LogEvent > ( ) ;
/// <summary>
/// Renders the captured events as a single string
/// </summary>
/// <returns>Rendered log text</returns>
public string Render ( ) = > Render ( "\n" ) ;
/// <summary>
/// Renders the captured events as a single string
/// </summary>
/// <returns>Rendered log text</returns>
2022-03-24 16:35:00 -04:00
public string Render ( string newLine ) = > String . Join ( newLine , RenderLines ( ) ) ;
2022-01-06 16:44:09 +00:00
/// <summary>
/// Renders all the captured events
/// </summary>
/// <returns>List of rendered log lines</returns>
public List < string > RenderLines ( ) = > Events . ConvertAll ( x = > x . ToString ( ) ) ;
/// <inheritdoc/>
2022-03-24 16:35:00 -04:00
public IDisposable BeginScope < TState > ( TState state ) = > new NullScope ( ) ;
2022-01-06 16:44:09 +00:00
/// <inheritdoc/>
2022-03-24 16:35:00 -04:00
public bool IsEnabled ( LogLevel logLevel ) = > true ;
2022-01-06 16:44:09 +00:00
/// <inheritdoc/>
2022-03-24 16:35:00 -04:00
public void Log < TState > ( LogLevel logLevel , EventId eventId , TState state , Exception ? exception , Func < TState , Exception ? , string > formatter )
2022-01-06 16:44:09 +00:00
{
2022-03-24 16:35:00 -04:00
Events . Add ( LogEvent . FromState ( logLevel , eventId , state , exception , formatter ) ) ;
2022-01-06 16:44:09 +00:00
}
}
2022-05-17 12:57:51 -04:00
/// <summary>
/// Wrapper around a custom logger interface which flushes the event parser when switching between legacy
/// and native structured logging
/// </summary>
class LegacyEventLogger : ILogger
{
private ILogger _inner ;
private readonly LogEventParser _parser ;
public LogEventParser Parser = > _parser ;
public LegacyEventLogger ( ILogger inner )
{
_inner = inner ;
_parser = new LogEventParser ( inner ) ;
}
public void SetInnerLogger ( ILogger inner )
{
2022-05-17 15:03:25 -04:00
lock ( _parser )
{
_parser . Flush ( ) ;
_inner = inner ;
_parser . Logger = inner ;
}
2022-05-17 12:57:51 -04:00
}
public IDisposable BeginScope < TState > ( TState state ) = > _inner . BeginScope ( state ) ;
public bool IsEnabled ( LogLevel logLevel ) = > _inner . IsEnabled ( logLevel ) ;
public void Log < TState > ( LogLevel logLevel , EventId eventId , TState state , Exception exception , Func < TState , Exception , string > formatter )
{
2022-05-17 15:03:25 -04:00
lock ( _parser )
{
_parser . Flush ( ) ;
}
2022-05-17 12:57:51 -04:00
_inner . Log ( logLevel , eventId , state , exception , formatter ) ;
}
}
2020-12-28 16:36:30 -04:00
/// <summary>
/// Default log output device
/// </summary>
2022-05-31 14:41:00 -04:00
class DefaultLogger : ILogger , IDisposable
2020-12-28 16:36:30 -04:00
{
/// <summary>
/// Temporary status message displayed on the console.
/// </summary>
[DebuggerDisplay("{HeadingText}")]
class StatusMessage
{
/// <summary>
/// The heading for this status message.
/// </summary>
2022-03-24 16:35:00 -04:00
public string _headingText ;
2020-12-28 16:36:30 -04:00
/// <summary>
/// The current status text.
/// </summary>
2022-03-24 16:35:00 -04:00
public string _currentText ;
2020-12-28 16:36:30 -04:00
/// <summary>
/// Whether the heading has been written to the console. Before the first time that lines are output to the log in the midst of a status scope, the heading will be written on a line of its own first.
/// </summary>
2022-03-24 16:35:00 -04:00
public bool _hasFlushedHeadingText ;
2021-11-07 23:43:01 -05:00
/// <summary>
/// Constructor
/// </summary>
2022-03-24 16:35:00 -04:00
public StatusMessage ( string headingText , string currentText )
2021-11-07 23:43:01 -05:00
{
2022-03-24 16:35:00 -04:00
_headingText = headingText ;
_currentText = currentText ;
2021-11-07 23:43:01 -05:00
}
2020-12-28 16:36:30 -04:00
}
/// <summary>
/// Object used for synchronization
/// </summary>
2022-03-24 16:35:00 -04:00
private readonly object _syncObject = new object ( ) ;
2020-12-28 16:36:30 -04:00
/// <summary>
2021-10-12 21:21:22 -04:00
/// Minimum level for outputting messages
2020-12-28 16:36:30 -04:00
/// </summary>
2021-10-12 21:21:22 -04:00
public LogLevel OutputLevel
2020-12-28 16:36:30 -04:00
{
get ; set ;
}
/// <summary>
/// Whether to include timestamps on each line of log output
/// </summary>
public bool IncludeTimestamps
{
get ; set ;
}
/// <summary>
/// When true, will detect warnings and errors and set the console output color to yellow and red.
/// </summary>
public bool ColorConsoleOutput
{
get ; set ;
}
2021-11-07 23:43:01 -05:00
/// <summary>
/// Whether to write JSON to stdout
/// </summary>
public bool WriteJsonToStdOut
{
get ; set ;
}
2021-08-30 08:44:36 -04:00
/// <summary>
/// When true, a timestamp will be written to the log file when the first listener is added
/// </summary>
public bool IncludeStartingTimestamp
{
get ; set ;
}
2022-03-24 16:35:00 -04:00
private bool _includeStartingTimestampWritten = false ;
2021-08-30 08:44:36 -04:00
2020-12-28 16:36:30 -04:00
/// <summary>
/// Path to the log file being written to. May be null.
/// </summary>
2021-11-07 23:43:01 -05:00
public FileReference ? OutputFile
2020-12-28 16:36:30 -04:00
{
get ; private set ;
}
/// <summary>
/// Whether console output is redirected. This prevents writing status updates that rely on moving the cursor.
/// </summary>
2022-03-24 16:35:00 -04:00
private bool AllowStatusUpdates = > ! Console . IsOutputRedirected ;
2020-12-28 16:36:30 -04:00
/// <summary>
/// When configured, this tracks time since initialization to prepend a timestamp to each log.
/// </summary>
2022-03-24 16:35:00 -04:00
private readonly Stopwatch _timer = Stopwatch . StartNew ( ) ;
2020-12-28 16:36:30 -04:00
/// <summary>
/// Stack of status scope information.
/// </summary>
2022-03-24 16:35:00 -04:00
private readonly Stack < StatusMessage > _statusMessageStack = new Stack < StatusMessage > ( ) ;
2020-12-28 16:36:30 -04:00
/// <summary>
/// The currently visible status text
/// </summary>
2022-03-24 16:35:00 -04:00
private string _statusText = "" ;
2020-12-28 16:36:30 -04:00
2022-05-17 12:57:51 -04:00
/// <summary>
/// Parser for transforming legacy log output into structured events
/// </summary>
public LogEventParser EventParser { get ; }
2020-12-28 16:36:30 -04:00
/// <summary>
/// Last time a status message was pushed to the stack
/// </summary>
2022-03-24 16:35:00 -04:00
private readonly Stopwatch _statusTimer = new Stopwatch ( ) ;
2021-11-07 23:43:01 -05:00
2022-05-31 14:41:00 -04:00
/// <summary>
/// Background task for writing to files
/// </summary>
private Task _writeTask ;
/// <summary>
/// Channel for new log events
/// </summary>
private Channel < JsonLogEvent > _eventChannel = Channel . CreateUnbounded < JsonLogEvent > ( ) ;
/// <summary>
/// Output streams for structured log data
/// </summary>
private IReadOnlyList < FileStream > _jsonStreams = Array . Empty < FileStream > ( ) ;
2020-12-28 16:36:30 -04:00
/// <summary>
/// Constructor
/// </summary>
public DefaultLogger ( )
{
2021-10-12 21:21:22 -04:00
OutputLevel = LogLevel . Debug ;
2020-12-28 16:36:30 -04:00
ColorConsoleOutput = true ;
2021-08-30 08:44:36 -04:00
IncludeStartingTimestamp = true ;
2022-05-17 12:57:51 -04:00
EventParser = new LogEventParser ( this ) ;
2021-11-07 23:43:01 -05:00
2022-05-24 15:13:25 -04:00
string? envVar = Environment . GetEnvironmentVariable ( "UE_LOG_JSON_TO_STDOUT" ) ;
2022-03-24 16:35:00 -04:00
if ( envVar ! = null & & Int32 . TryParse ( envVar , out int value ) & & value ! = 0 )
2021-11-07 23:43:01 -05:00
{
WriteJsonToStdOut = true ;
}
2022-05-31 14:41:00 -04:00
_writeTask = Task . Run ( ( ) = > WriteFilesAsync ( ) ) ;
}
/// <inheritdoc/>
public void Dispose ( )
{
_eventChannel . Writer . TryComplete ( ) ;
_writeTask . Wait ( ) ;
}
/// <summary>
/// Flush the stream
/// </summary>
/// <returns></returns>
public async Task FlushAsync ( )
{
lock ( _syncObject )
{
Channel < JsonLogEvent > prevEventChannel = _eventChannel ;
_eventChannel = Channel . CreateUnbounded < JsonLogEvent > ( ) ;
prevEventChannel . Writer . TryComplete ( ) ;
}
await _writeTask ;
_writeTask = Task . Run ( ( ) = > WriteFilesAsync ( ) ) ;
}
/// <summary>
/// Background task to write events to sinks
/// </summary>
async Task WriteFilesAsync ( )
{
byte [ ] newline = new byte [ ] { ( byte ) '\n' } ;
while ( await _eventChannel . Reader . WaitToReadAsync ( ) )
{
IReadOnlyList < FileStream > streams = _jsonStreams ;
JsonLogEvent logEvent ;
while ( _eventChannel . Reader . TryRead ( out logEvent ) )
{
foreach ( FileStream stream in streams )
{
await stream . WriteAsync ( logEvent . Data ) ;
await stream . WriteAsync ( newline ) ;
}
}
foreach ( FileStream stream in streams )
{
await stream . FlushAsync ( ) ;
}
}
2020-12-28 16:36:30 -04:00
}
/// <summary>
/// Adds a trace listener that writes to a log file
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="name">Listener name</param>
/// <param name="outputFile">The file to write to</param>
2020-12-28 16:36:30 -04:00
/// <returns>The created trace listener</returns>
2022-03-24 16:35:00 -04:00
public TextWriterTraceListener AddFileWriter ( string name , FileReference outputFile )
2020-12-28 16:36:30 -04:00
{
try
{
2022-03-24 16:35:00 -04:00
OutputFile = outputFile ;
DirectoryReference . CreateDirectory ( outputFile . Directory ) ;
TextWriterTraceListener logTraceListener = new TextWriterTraceListener ( new StreamWriter ( outputFile . FullName ) , name ) ;
lock ( _syncObject )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
Trace . Listeners . Add ( logTraceListener ) ;
2021-08-30 08:44:36 -04:00
WriteInitialTimestamp ( ) ;
2022-05-31 14:41:00 -04:00
List < FileStream > newJsonStreams = new List < FileStream > ( _jsonStreams ) ;
newJsonStreams . Add ( FileReference . Open ( outputFile . ChangeExtension ( ".json" ) , FileMode . Create , FileAccess . Write , FileShare . Read | FileShare . Delete ) ) ;
_jsonStreams = newJsonStreams ;
2020-12-28 16:36:30 -04:00
}
2022-03-24 16:35:00 -04:00
return logTraceListener ;
2020-12-28 16:36:30 -04:00
}
2022-03-24 16:35:00 -04:00
catch ( Exception ex )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
throw new Exception ( $"Error while creating log file \" { outputFile } \ "" , ex ) ;
2020-12-28 16:36:30 -04:00
}
}
/// <summary>
/// Adds a <see cref="TraceListener"/> to the collection in a safe manner.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="traceListener">The <see cref="TraceListener"/> to add.</param>
public void AddTraceListener ( TraceListener traceListener )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
lock ( _syncObject )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
if ( ! Trace . Listeners . Contains ( traceListener ) )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
Trace . Listeners . Add ( traceListener ) ;
2021-08-30 08:44:36 -04:00
WriteInitialTimestamp ( ) ;
2020-12-28 16:36:30 -04:00
}
}
}
2021-08-30 08:44:36 -04:00
/// <summary>
/// Write a timestamp to the log, once. To be called when a new listener is added.
/// </summary>
private void WriteInitialTimestamp ( )
{
2022-03-24 16:35:00 -04:00
if ( IncludeStartingTimestamp & & ! _includeStartingTimestampWritten )
2021-08-30 08:44:36 -04:00
{
2022-03-24 16:35:00 -04:00
DateTime now = DateTime . Now ;
this . LogDebug ( "{Message}" , $"Log started at {now} ({now.ToUniversalTime():yyyy-MM-ddTHH\\:mm\\:ssZ})" ) ;
_includeStartingTimestampWritten = true ;
2021-08-30 08:44:36 -04:00
}
}
2020-12-28 16:36:30 -04:00
/// <summary>
/// Removes a <see cref="TraceListener"/> from the collection in a safe manner.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="traceListener">The <see cref="TraceListener"/> to remove.</param>
public void RemoveTraceListener ( TraceListener traceListener )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
lock ( _syncObject )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
if ( Trace . Listeners . Contains ( traceListener ) )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
Trace . Listeners . Remove ( traceListener ) ;
2020-12-28 16:36:30 -04:00
}
}
}
/// <summary>
/// Determines if a TextWriterTraceListener has been added to the list of trace listeners
/// </summary>
/// <returns>True if a TextWriterTraceListener has been added</returns>
public static bool HasFileWriter ( )
{
2022-03-24 16:35:00 -04:00
foreach ( TraceListener ? listener in Trace . Listeners )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
if ( listener is TextWriterTraceListener )
2020-12-28 16:36:30 -04:00
{
return true ;
}
}
return false ;
}
2022-03-24 16:35:00 -04:00
public IDisposable BeginScope < TState > ( TState state )
2020-12-28 16:36:30 -04:00
{
throw new NotImplementedException ( ) ;
}
2022-03-24 16:35:00 -04:00
public bool IsEnabled ( LogLevel logLevel )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
return logLevel > = OutputLevel ;
2020-12-28 16:36:30 -04:00
}
2022-05-17 12:57:51 -04:00
public void Log < TState > ( LogLevel logLevel , EventId eventId , TState state , Exception exception , Func < TState , Exception ? , string > formatter )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
string [ ] lines = formatter ( state , exception ) . Split ( '\n' ) ;
lock ( _syncObject )
2020-12-28 16:36:30 -04:00
{
2021-10-12 21:21:22 -04:00
// Output to all the other trace listeners
2022-03-24 16:35:00 -04:00
string timePrefix = String . Format ( "[{0:hh\\:mm\\:ss\\.fff}] " , _timer . Elapsed ) ;
foreach ( TraceListener ? listener in Trace . Listeners )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
if ( listener ! = null )
2021-10-12 21:21:22 -04:00
{
2022-03-24 16:35:00 -04:00
string timePrefixActual =
2021-11-07 23:43:01 -05:00
IncludeTimestamps & &
2022-03-24 16:35:00 -04:00
! ( listener is DefaultTraceListener ) // no timestamps when writing to the Visual Studio debug window
? timePrefix
2021-11-07 23:43:01 -05:00
: String . Empty ;
2022-03-24 16:35:00 -04:00
foreach ( string line in lines )
2021-11-07 23:43:01 -05:00
{
2022-03-24 16:35:00 -04:00
string lineWithTime = timePrefixActual + line ;
listener . WriteLine ( line ) ;
listener . Flush ( ) ;
2021-11-07 23:43:01 -05:00
}
2021-10-12 21:21:22 -04:00
}
2020-12-28 16:36:30 -04:00
}
2021-10-12 21:21:22 -04:00
// Handle the console output separately; we format things differently
2022-03-24 16:35:00 -04:00
if ( logLevel > = LogLevel . Information )
2020-12-28 16:36:30 -04:00
{
2021-10-12 21:21:22 -04:00
FlushStatusHeading ( ) ;
bool bResetConsoleColor = false ;
if ( ColorConsoleOutput )
{
2022-03-24 16:35:00 -04:00
if ( logLevel = = LogLevel . Warning )
2021-10-12 21:21:22 -04:00
{
Console . ForegroundColor = ConsoleColor . Yellow ;
bResetConsoleColor = true ;
}
2022-03-24 16:35:00 -04:00
if ( logLevel > = LogLevel . Error )
2021-10-12 21:21:22 -04:00
{
Console . ForegroundColor = ConsoleColor . Red ;
bResetConsoleColor = true ;
}
}
try
2020-12-28 16:36:30 -04:00
{
2022-05-31 14:41:00 -04:00
JsonLogEvent jsonLogEvent = JsonLogEvent . FromLoggerState ( logLevel , eventId , state , exception , formatter ) ;
_eventChannel . Writer . TryWrite ( jsonLogEvent ) ;
2021-11-07 23:43:01 -05:00
if ( WriteJsonToStdOut )
2020-12-28 16:36:30 -04:00
{
2022-05-19 08:16:07 -04:00
Console . WriteLine ( Encoding . UTF8 . GetString ( jsonLogEvent . Data . Span ) ) ;
2021-11-07 23:43:01 -05:00
}
else
{
2022-03-24 16:35:00 -04:00
foreach ( string line in lines )
2021-11-07 23:43:01 -05:00
{
2022-03-24 16:35:00 -04:00
Console . WriteLine ( line ) ;
2021-11-07 23:43:01 -05:00
}
2021-10-12 21:21:22 -04:00
}
}
catch ( IOException )
{
// Potential file access/sharing issue on std out
// This can occur on some versions of mono (e.g. macOS 6.12.0) if writing to a full pipe
// during IPC when the reader isn't consuming it quick enough
}
finally
{
// make sure we always put the console color back.
if ( bResetConsoleColor )
{
Console . ResetColor ( ) ;
2020-12-28 16:36:30 -04:00
}
}
2022-03-24 16:35:00 -04:00
if ( _statusMessageStack . Count > 0 & & AllowStatusUpdates )
2020-12-28 16:36:30 -04:00
{
2022-03-24 16:35:00 -04:00
SetStatusText ( _statusMessageStack . Peek ( ) . _currentText ) ;
2020-12-28 16:36:30 -04:00
}
}
}
}
2020-12-21 11:50:46 -04:00
/// <summary>
/// Flushes the current status text before writing out a new log line or status message
/// </summary>
2020-12-28 16:36:30 -04:00
void FlushStatusHeading ( )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( _statusMessageStack . Count > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
StatusMessage currentStatus = _statusMessageStack . Peek ( ) ;
if ( currentStatus . _headingText . Length > 0 & & ! currentStatus . _hasFlushedHeadingText & & AllowStatusUpdates )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
SetStatusText ( currentStatus . _headingText ) ;
2020-12-21 11:50:46 -04:00
Console . WriteLine ( ) ;
2022-03-24 16:35:00 -04:00
_statusText = "" ;
currentStatus . _hasFlushedHeadingText = true ;
2020-12-21 11:50:46 -04:00
}
else
{
SetStatusText ( "" ) ;
}
}
}
/// <summary>
/// Enter a scope with the given status message. The message will be written to the console without a newline, allowing it to be updated through subsequent calls to UpdateStatus().
/// The message will be written to the log immediately. If another line is written while in a status scope, the initial status message is flushed to the console first.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="message">The status message</param>
2020-12-21 11:50:46 -04:00
[Conditional("TRACE")]
2022-03-24 16:35:00 -04:00
public void PushStatus ( string message )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
lock ( _syncObject )
2020-12-21 11:50:46 -04:00
{
FlushStatusHeading ( ) ;
2022-03-24 16:35:00 -04:00
StatusMessage newStatusMessage = new StatusMessage ( message , message ) ;
_statusMessageStack . Push ( newStatusMessage ) ;
2020-12-21 11:50:46 -04:00
2022-03-24 16:35:00 -04:00
_statusTimer . Restart ( ) ;
2020-12-21 11:50:46 -04:00
2022-03-24 16:35:00 -04:00
if ( message . Length > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
this . LogDebug ( "{Message}" , message ) ;
SetStatusText ( message ) ;
2020-12-21 11:50:46 -04:00
}
}
}
/// <summary>
/// Updates the current status message. This will overwrite the previous status line.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="message">The status message</param>
2020-12-21 11:50:46 -04:00
[Conditional("TRACE")]
2022-03-24 16:35:00 -04:00
public void UpdateStatus ( string message )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
lock ( _syncObject )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
StatusMessage currentStatusMessage = _statusMessageStack . Peek ( ) ;
currentStatusMessage . _currentText = message ;
2020-12-21 11:50:46 -04:00
2022-03-24 16:35:00 -04:00
if ( AllowStatusUpdates | | _statusTimer . Elapsed . TotalSeconds > 10.0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
SetStatusText ( message ) ;
_statusTimer . Restart ( ) ;
2020-12-21 11:50:46 -04:00
}
}
}
/// <summary>
/// Updates the Pops the top status message from the stack. The mess
/// </summary>
/// <param name="Message"></param>
[Conditional("TRACE")]
2020-12-28 16:36:30 -04:00
public void PopStatus ( )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
lock ( _syncObject )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
StatusMessage currentStatusMessage = _statusMessageStack . Peek ( ) ;
SetStatusText ( currentStatusMessage . _currentText ) ;
2020-12-21 11:50:46 -04:00
2022-03-24 16:35:00 -04:00
if ( _statusText . Length > 0 )
2020-12-21 11:50:46 -04:00
{
Console . WriteLine ( ) ;
2022-03-24 16:35:00 -04:00
_statusText = "" ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
_statusMessageStack . Pop ( ) ;
2020-12-21 11:50:46 -04:00
}
}
/// <summary>
/// Update the status text. For internal use only; does not modify the StatusMessageStack objects.
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="newStatusText">New status text to display</param>
private void SetStatusText ( string newStatusText )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
if ( newStatusText . Length > 0 )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
newStatusText = LogIndent . Current + newStatusText ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
if ( _statusText ! = newStatusText )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
int numCommonChars = 0 ;
while ( numCommonChars < _statusText . Length & & numCommonChars < newStatusText . Length & & _statusText [ numCommonChars ] = = newStatusText [ numCommonChars ] )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
numCommonChars + + ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
if ( ! AllowStatusUpdates & & numCommonChars < _statusText . Length )
2020-12-21 11:50:46 -04:00
{
// Prevent writing backspace characters if the console doesn't support it
Console . WriteLine ( ) ;
2022-03-24 16:35:00 -04:00
_statusText = "" ;
numCommonChars = 0 ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
StringBuilder text = new StringBuilder ( ) ;
text . Append ( '\b' , _statusText . Length - numCommonChars ) ;
text . Append ( newStatusText , numCommonChars , newStatusText . Length - numCommonChars ) ;
if ( newStatusText . Length < _statusText . Length )
2020-12-21 11:50:46 -04:00
{
2022-03-24 16:35:00 -04:00
int numChars = _statusText . Length - newStatusText . Length ;
text . Append ( ' ' , numChars ) ;
text . Append ( '\b' , numChars ) ;
2020-12-21 11:50:46 -04:00
}
2022-03-24 16:35:00 -04:00
Console . Write ( text . ToString ( ) ) ;
2020-12-21 11:50:46 -04:00
2022-03-24 16:35:00 -04:00
_statusText = newStatusText ;
_statusTimer . Restart ( ) ;
2020-12-21 11:50:46 -04:00
}
}
}
2021-01-15 10:49:19 -04:00
/// <summary>
/// Provider for default logger instances
/// </summary>
public class DefaultLoggerProvider : ILoggerProvider
{
/// <inheritdoc/>
2022-03-24 16:35:00 -04:00
public ILogger CreateLogger ( string categoryName )
2021-01-15 10:49:19 -04:00
{
return new DefaultLogger ( ) ;
}
/// <inheritdoc/>
public void Dispose ( )
{
}
}
/// <summary>
/// Extension methods to support the default logger
/// </summary>
public static class DefaultLoggerExtensions
{
/// <summary>
/// Adds a regular Epic logger to the builder
/// </summary>
2022-03-24 16:35:00 -04:00
/// <param name="builder">Logging builder</param>
public static void AddEpicDefault ( this ILoggingBuilder builder )
2021-01-15 10:49:19 -04:00
{
2022-03-24 16:35:00 -04:00
builder . Services . AddSingleton < ILoggerProvider , DefaultLoggerProvider > ( ) ;
2021-01-15 10:49:19 -04:00
}
}
2020-12-21 11:50:46 -04:00
}