[backend] Add custom console logger
This commit is contained in:
parent
285c2ba531
commit
bdb2fe39fb
2 changed files with 220 additions and 2 deletions
219
Iceshrimp.Backend/Core/Extensions/ConsoleLoggerExtensions.cs
Normal file
219
Iceshrimp.Backend/Core/Extensions/ConsoleLoggerExtensions.cs
Normal file
|
@ -0,0 +1,219 @@
|
|||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Logging.Console;
|
||||
|
||||
namespace Iceshrimp.Backend.Core.Extensions;
|
||||
|
||||
public static class ConsoleLoggerExtensions {
|
||||
public static ILoggingBuilder AddCustomConsoleFormatter(this ILoggingBuilder builder) =>
|
||||
builder.AddConsole(options => options.FormatterName = "custom")
|
||||
.AddConsoleFormatter<CustomFormatter, ConsoleFormatterOptions>();
|
||||
};
|
||||
|
||||
/*
|
||||
* This is a slightly modified version of Microsoft's SimpleConsoleFormatter.
|
||||
* Changes mainly concern handling of line breaks.
|
||||
* Adapted under MIT from https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Logging.Console/src/SimpleConsoleFormatter.cs
|
||||
*/
|
||||
|
||||
#region Logger implementation
|
||||
|
||||
file static class TextWriterExtensions {
|
||||
public static void WriteColoredMessage(this TextWriter textWriter, string message, ConsoleColor? background,
|
||||
ConsoleColor? foreground) {
|
||||
if (background.HasValue)
|
||||
textWriter.Write(GetBackgroundColorEscapeCode(background.Value));
|
||||
if (foreground.HasValue)
|
||||
textWriter.Write(GetForegroundColorEscapeCode(foreground.Value));
|
||||
|
||||
textWriter.Write(message);
|
||||
|
||||
if (foreground.HasValue)
|
||||
textWriter.Write(DefaultForegroundColor);
|
||||
if (background.HasValue)
|
||||
textWriter.Write(DefaultBackgroundColor);
|
||||
}
|
||||
|
||||
private const string DefaultForegroundColor = "\x1B[39m\x1B[22m"; // reset to default foreground color
|
||||
private const string DefaultBackgroundColor = "\x1B[49m"; // reset to the background color
|
||||
|
||||
private static string GetForegroundColorEscapeCode(ConsoleColor color) {
|
||||
return color switch {
|
||||
ConsoleColor.Black => "\x1B[30m",
|
||||
ConsoleColor.DarkRed => "\x1B[31m",
|
||||
ConsoleColor.DarkGreen => "\x1B[32m",
|
||||
ConsoleColor.DarkYellow => "\x1B[33m",
|
||||
ConsoleColor.DarkBlue => "\x1B[34m",
|
||||
ConsoleColor.DarkMagenta => "\x1B[35m",
|
||||
ConsoleColor.DarkCyan => "\x1B[36m",
|
||||
ConsoleColor.Gray => "\x1B[37m",
|
||||
ConsoleColor.Red => "\x1B[1m\x1B[31m",
|
||||
ConsoleColor.Green => "\x1B[1m\x1B[32m",
|
||||
ConsoleColor.Yellow => "\x1B[1m\x1B[33m",
|
||||
ConsoleColor.Blue => "\x1B[1m\x1B[34m",
|
||||
ConsoleColor.Magenta => "\x1B[1m\x1B[35m",
|
||||
ConsoleColor.Cyan => "\x1B[1m\x1B[36m",
|
||||
ConsoleColor.White => "\x1B[1m\x1B[37m",
|
||||
_ => DefaultForegroundColor // default foreground color
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetBackgroundColorEscapeCode(ConsoleColor color) {
|
||||
return color switch {
|
||||
ConsoleColor.Black => "\x1B[40m",
|
||||
ConsoleColor.DarkRed => "\x1B[41m",
|
||||
ConsoleColor.DarkGreen => "\x1B[42m",
|
||||
ConsoleColor.DarkYellow => "\x1B[43m",
|
||||
ConsoleColor.DarkBlue => "\x1B[44m",
|
||||
ConsoleColor.DarkMagenta => "\x1B[45m",
|
||||
ConsoleColor.DarkCyan => "\x1B[46m",
|
||||
ConsoleColor.Gray => "\x1B[47m",
|
||||
_ => DefaultBackgroundColor // Use default background color
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
file static class ConsoleUtils {
|
||||
private static volatile int _sEmitAnsiColorCodes = -1;
|
||||
|
||||
public static bool EmitAnsiColorCodes {
|
||||
get {
|
||||
var emitAnsiColorCodes = _sEmitAnsiColorCodes;
|
||||
if (emitAnsiColorCodes != -1) {
|
||||
return Convert.ToBoolean(emitAnsiColorCodes);
|
||||
}
|
||||
|
||||
var enabled = !Console.IsOutputRedirected;
|
||||
|
||||
if (enabled) {
|
||||
enabled = Environment.GetEnvironmentVariable("NO_COLOR") is null;
|
||||
}
|
||||
else {
|
||||
var envVar =
|
||||
Environment.GetEnvironmentVariable("DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION");
|
||||
enabled = envVar is not null &&
|
||||
(envVar == "1" || envVar.Equals("true", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
_sEmitAnsiColorCodes = Convert.ToInt32(enabled);
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file sealed class CustomFormatter() : ConsoleFormatter("custom") {
|
||||
private const string LoglevelPadding = ": ";
|
||||
|
||||
private static readonly string MessagePadding =
|
||||
new(' ', GetLogLevelString(LogLevel.Information).Length + LoglevelPadding.Length);
|
||||
|
||||
private static readonly string NewLineWithMessagePadding = Environment.NewLine + MessagePadding;
|
||||
|
||||
public override void Write<TState>(
|
||||
in LogEntry<TState> logEntry,
|
||||
IExternalScopeProvider? scopeProvider,
|
||||
TextWriter textWriter
|
||||
) {
|
||||
var message = logEntry.Formatter(logEntry.State, logEntry.Exception);
|
||||
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
if (logEntry.Exception == null && message == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var logLevel = logEntry.LogLevel;
|
||||
var logLevelColors = GetLogLevelConsoleColors(logLevel);
|
||||
var logLevelString = GetLogLevelString(logLevel);
|
||||
|
||||
textWriter.WriteColoredMessage(logLevelString, logLevelColors.Background, logLevelColors.Foreground);
|
||||
|
||||
CreateDefaultLogMessage(textWriter, logEntry, message);
|
||||
}
|
||||
|
||||
|
||||
private static void CreateDefaultLogMessage<TState>(TextWriter textWriter, in LogEntry<TState> logEntry,
|
||||
string message) {
|
||||
var singleLine = !message.Contains('\n');
|
||||
var eventId = logEntry.EventId.Id;
|
||||
var exception = logEntry.Exception;
|
||||
|
||||
textWriter.Write(LoglevelPadding);
|
||||
textWriter.Write(logEntry.Category);
|
||||
textWriter.Write('[');
|
||||
|
||||
Span<char> span = stackalloc char[10];
|
||||
if (eventId.TryFormat(span, out var charsWritten))
|
||||
textWriter.Write(span[..charsWritten]);
|
||||
else
|
||||
textWriter.Write(eventId.ToString());
|
||||
|
||||
textWriter.Write(']');
|
||||
if (!singleLine) {
|
||||
textWriter.Write(Environment.NewLine);
|
||||
}
|
||||
|
||||
WriteMessage(textWriter, message, singleLine);
|
||||
|
||||
if (exception != null) {
|
||||
WriteMessage(textWriter, exception.ToString(), singleLine);
|
||||
}
|
||||
|
||||
if (singleLine) {
|
||||
textWriter.Write(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteMessage(TextWriter textWriter, string message, bool singleLine) {
|
||||
if (string.IsNullOrEmpty(message)) return;
|
||||
if (singleLine) {
|
||||
textWriter.Write(' ');
|
||||
WriteReplacing(textWriter, Environment.NewLine, " ", message);
|
||||
}
|
||||
else {
|
||||
textWriter.Write(MessagePadding);
|
||||
WriteReplacing(textWriter, Environment.NewLine, NewLineWithMessagePadding, message);
|
||||
textWriter.Write(Environment.NewLine);
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
static void WriteReplacing(TextWriter writer, string oldValue, string newValue, string message) {
|
||||
var newMessage = message.Replace(oldValue, newValue);
|
||||
writer.Write(newMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetLogLevelString(LogLevel logLevel) {
|
||||
return logLevel switch {
|
||||
LogLevel.Trace => "trce",
|
||||
LogLevel.Debug => "dbug",
|
||||
LogLevel.Information => "info",
|
||||
LogLevel.Warning => "warn",
|
||||
LogLevel.Error => "fail",
|
||||
LogLevel.Critical => "crit",
|
||||
LogLevel.None => throw new ArgumentOutOfRangeException(nameof(logLevel)),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(logLevel))
|
||||
};
|
||||
}
|
||||
|
||||
private static ConsoleColors GetLogLevelConsoleColors(LogLevel logLevel) {
|
||||
if (!ConsoleUtils.EmitAnsiColorCodes) {
|
||||
return new ConsoleColors(null, null);
|
||||
}
|
||||
|
||||
return logLevel switch {
|
||||
LogLevel.Trace => new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black),
|
||||
LogLevel.Debug => new ConsoleColors(ConsoleColor.Gray, ConsoleColor.Black),
|
||||
LogLevel.Information => new ConsoleColors(ConsoleColor.DarkGreen, ConsoleColor.Black),
|
||||
LogLevel.Warning => new ConsoleColors(ConsoleColor.Yellow, ConsoleColor.Black),
|
||||
LogLevel.Error => new ConsoleColors(ConsoleColor.Black, ConsoleColor.DarkRed),
|
||||
LogLevel.Critical => new ConsoleColors(ConsoleColor.White, ConsoleColor.DarkRed),
|
||||
_ => new ConsoleColors(null, null)
|
||||
};
|
||||
}
|
||||
|
||||
private readonly struct ConsoleColors(ConsoleColor? foreground, ConsoleColor? background) {
|
||||
public ConsoleColor? Foreground { get; } = foreground;
|
||||
public ConsoleColor? Background { get; } = background;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
|
@ -29,8 +29,7 @@ builder.Services.AddViteServices(options => {
|
|||
options.Server.AutoRun = false; //TODO: Fix script generation on macOS
|
||||
options.Server.UseFullDevUrl = true;
|
||||
});
|
||||
//TODO: single line only if there's no \n in the log msg (otherwise stacktraces don't work)
|
||||
builder.Services.AddLogging(logging => logging.AddSimpleConsole(options => { options.SingleLine = false; }));
|
||||
builder.Services.AddLogging(logging => logging.AddCustomConsoleFormatter());
|
||||
builder.Services.AddDatabaseContext(builder.Configuration); //TODO: maybe use a dbcontext factory?
|
||||
builder.Services.AddRedis(builder.Configuration);
|
||||
builder.Services.AddSlidingWindowRateLimiter();
|
||||
|
|
Loading…
Add table
Reference in a new issue