Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.Core/DefaultConsoleLogger.cs
2025-05-18 13:04:45 +08:00

207 lines
5.0 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Extensions.Logging;
namespace EpicGames.Core
{
/// <summary>
/// Simple implementation of <see cref="ILogger"/> which formats output to the console similar to Serilog's 'Code' style.
/// </summary>
public class DefaultConsoleLogger : ILogger
{
sealed class Disposable : IDisposable
{
public void Dispose() { }
}
const string AnsiReset = "\x1b[0m";
const string AnsiTime = "\x1b[38;5;0246m";
const string AnsiTrace = "\x1b[37m";
const string AnsiDebug = "\x1b[37m";
const string AnsiInformation = "\x1b[37;1m";
const string AnsiWarning = "\x1b[38;5;0229m";
const string AnsiError = "\x1b[38;5;0197m\x1b[48;5;0238m";
const string AnsiNull = "\x1b[38;5;0038m";
const string AnsiBool = "\x1b[38;5;0038m";
const string AnsiNumber = "\x1b[38;5;151m";
const string AnsiString = "\x1b[38;5;0216m";
readonly LogLevel _minLevel;
/// <summary>
/// Constructor
/// </summary>
/// <param name="minLevel">Minimum level for messages to be output</param>
public DefaultConsoleLogger(LogLevel minLevel = LogLevel.Debug)
{
_minLevel = minLevel;
}
/// <inheritdoc/>
public bool IsEnabled(LogLevel logLevel) => logLevel >= _minLevel;
/// <inheritdoc/>
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => new Disposable();
/// <inheritdoc/>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
using StringWriter writer = new StringWriter();
Write<TState>(logLevel, state, exception, formatter, writer);
Console.WriteLine(writer.ToString());
}
static void Write<TState>(LogLevel logLevel, TState state, Exception? exception, Func<TState, Exception?, string> formatter, TextWriter textWriter)
{
DateTime time = DateTime.Now;
textWriter.Write(AnsiTime);
textWriter.Write($"[{time:HH:mm:ss} ");
switch (logLevel)
{
case LogLevel.Trace:
textWriter.Write(AnsiTrace);
textWriter.Write("trc");
break;
case LogLevel.Debug:
textWriter.Write(AnsiDebug);
textWriter.Write("dbg");
break;
case LogLevel.Information:
textWriter.Write(AnsiInformation);
textWriter.Write("inf" + AnsiTime);
break;
case LogLevel.Warning:
textWriter.Write(AnsiWarning);
textWriter.Write("wrn" + AnsiTime);
break;
case LogLevel.Error:
textWriter.Write(AnsiError);
textWriter.Write("err");
break;
case LogLevel.Critical:
textWriter.Write(AnsiError);
textWriter.Write("ctl");
break;
}
textWriter.Write(AnsiReset);
textWriter.Write(AnsiTime);
textWriter.Write("] ");
textWriter.Write(AnsiReset);
const string OriginalFormatKeyName = "{OriginalFormat}";
if (state is IEnumerable<KeyValuePair<string, object>> enumerable)
{
string? originalFormat = enumerable.FirstOrDefault(x => x.Key.Equals(OriginalFormatKeyName, StringComparison.OrdinalIgnoreCase)).Value as string;
if (originalFormat != null)
{
ReadOnlySpan<char> fmt = originalFormat.AsSpan();
while (fmt.Length > 0)
{
fmt = WriteToken(fmt, enumerable, textWriter);
}
return;
}
}
textWriter.Write(formatter(state, exception));
}
static ReadOnlySpan<char> WriteToken(ReadOnlySpan<char> fmt, IEnumerable<KeyValuePair<string, object>> properties, TextWriter writer)
{
int idx = fmt.IndexOf('{');
if (idx == -1 || idx + 1 == fmt.Length)
{
writer.Write(fmt);
return [];
}
if (fmt[idx + 1] == '{')
{
writer.Write(fmt.Slice(0, idx + 1));
return fmt.Slice(idx + 2);
}
writer.Write(fmt.Slice(0, idx));
fmt = fmt.Slice(idx);
int endIdx = fmt.IndexOf('}');
if (endIdx == -1)
{
writer.Write(fmt);
return [];
}
ReadOnlySpan<char> name = fmt.Slice(1, endIdx - 1);
ReadOnlySpan<char> spec = [];
int specIdx = name.IndexOfAny(':', ',');
if (specIdx != -1)
{
spec = name.Slice(specIdx);
name = name.Slice(0, specIdx);
}
object? obj = GetPropertyValue(properties, name);
string? color = obj switch
{
null => AnsiNull,
bool _ => AnsiBool,
byte _ => AnsiNumber,
sbyte _ => AnsiNumber,
short _ => AnsiNumber,
ushort _ => AnsiNumber,
int _ => AnsiNumber,
ulong _ => AnsiNumber,
float _ => AnsiNumber,
double _ => AnsiNumber,
string _ => AnsiString,
_ => null
};
if (color != null)
{
writer.Write(color);
}
if (spec.Length > 0)
{
writer.Write($"{{0{spec}}}", obj);
}
else
{
writer.Write(obj);
}
if (color != null)
{
writer.Write(AnsiReset);
}
return fmt.Slice(endIdx + 1);
}
static object? GetPropertyValue(IEnumerable<KeyValuePair<string, object>> enumerable, ReadOnlySpan<char> name)
{
foreach (KeyValuePair<string, object> item in enumerable)
{
if (name.Equals(item.Key, StringComparison.Ordinal))
{
return item.Value;
}
}
return null;
}
}
}