Initial version
This commit is contained in:
285
EonaCat.LogStack/EonaCatLoggerCore/Flows/ConsoleFlow.cs
Normal file
285
EonaCat.LogStack/EonaCatLoggerCore/Flows/ConsoleFlow.cs
Normal file
@@ -0,0 +1,285 @@
|
||||
using EonaCat.LogStack.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace EonaCat.LogStack.Flows
|
||||
{
|
||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
||||
|
||||
/// <summary>
|
||||
/// console flow with color support and minimal allocations
|
||||
/// Uses a ColorSchema for configurable colors
|
||||
/// </summary>
|
||||
public sealed class ConsoleFlow : FlowBase
|
||||
{
|
||||
private readonly bool _useColors;
|
||||
private readonly TimestampMode _timestampMode;
|
||||
private readonly StringBuilder _buffer = new(1024);
|
||||
private readonly object _consoleLock = new();
|
||||
private readonly ColorSchema _colors;
|
||||
|
||||
private readonly string _template;
|
||||
private List<Action<LogEvent, StringBuilder>> _compiledTemplate;
|
||||
|
||||
public ConsoleFlow(
|
||||
LogLevel minimumLevel = LogLevel.Trace,
|
||||
bool useColors = true,
|
||||
TimestampMode timestampMode = TimestampMode.Local,
|
||||
ColorSchema? colorSchema = null,
|
||||
string template = "[{ts}] [{tz}] [Host: {host}] [Category: {category}] [Thread: {thread}] [{logtype}] {message}{props}")
|
||||
: base("Console", minimumLevel)
|
||||
{
|
||||
_useColors = useColors;
|
||||
_timestampMode = timestampMode;
|
||||
_colors = colorSchema ?? new ColorSchema();
|
||||
_template = template ?? throw new ArgumentNullException(nameof(template));
|
||||
|
||||
CompileTemplate(_template);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override Task<WriteResult> BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!IsEnabled || !IsLogLevelEnabled(logEvent))
|
||||
{
|
||||
return Task.FromResult(WriteResult.LevelFiltered);
|
||||
}
|
||||
|
||||
WriteToConsole(logEvent);
|
||||
Interlocked.Increment(ref BlastedCount);
|
||||
return Task.FromResult(WriteResult.Success);
|
||||
}
|
||||
|
||||
public override Task<WriteResult> BlastBatchAsync(ReadOnlyMemory<LogEvent> logEvents, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
return Task.FromResult(WriteResult.FlowDisabled);
|
||||
}
|
||||
|
||||
foreach (var logEvent in logEvents.Span)
|
||||
{
|
||||
if (logEvent.Level >= MinimumLevel)
|
||||
{
|
||||
WriteToConsole(logEvent);
|
||||
Interlocked.Increment(ref BlastedCount);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(WriteResult.Success);
|
||||
}
|
||||
|
||||
private void WriteToConsole(LogEvent logEvent)
|
||||
{
|
||||
lock (_consoleLock)
|
||||
{
|
||||
_buffer.Clear();
|
||||
|
||||
foreach (var action in _compiledTemplate)
|
||||
{
|
||||
action(logEvent, _buffer);
|
||||
}
|
||||
|
||||
if (_useColors && TryGetColor(logEvent.Level, out var color))
|
||||
{
|
||||
Console.ForegroundColor = color.Foreground;
|
||||
}
|
||||
|
||||
Console.WriteLine(_buffer.ToString());
|
||||
|
||||
if (logEvent.Exception != null)
|
||||
{
|
||||
if (_useColors)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.DarkRed;
|
||||
}
|
||||
|
||||
Console.WriteLine(logEvent.Exception.ToString());
|
||||
|
||||
if (_useColors)
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
|
||||
if (_useColors)
|
||||
{
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CompileTemplate(string template)
|
||||
{
|
||||
_compiledTemplate = new List<Action<LogEvent, StringBuilder>>();
|
||||
int pos = 0;
|
||||
|
||||
while (pos < template.Length)
|
||||
{
|
||||
int open = template.IndexOf('{', pos);
|
||||
if (open < 0)
|
||||
{
|
||||
string lit = template.Substring(pos);
|
||||
_compiledTemplate.Add((_, sb) => sb.Append(lit));
|
||||
break;
|
||||
}
|
||||
|
||||
if (open > pos)
|
||||
{
|
||||
string lit = template.Substring(pos, open - pos);
|
||||
_compiledTemplate.Add((_, sb) => sb.Append(lit));
|
||||
}
|
||||
|
||||
int close = template.IndexOf('}', open);
|
||||
if (close < 0)
|
||||
{
|
||||
string lit = template.Substring(open);
|
||||
_compiledTemplate.Add((_, sb) => sb.Append(lit));
|
||||
break;
|
||||
}
|
||||
|
||||
string token = template.Substring(open + 1, close - open - 1);
|
||||
_compiledTemplate.Add(ResolveToken(token));
|
||||
pos = close + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private Action<LogEvent, StringBuilder> ResolveToken(string token)
|
||||
{
|
||||
switch (token.ToLowerInvariant())
|
||||
{
|
||||
case "ts":
|
||||
return (log, sb) =>
|
||||
sb.Append(LogEvent.GetDateTime(log.Timestamp)
|
||||
.ToString("yyyy-MM-dd HH:mm:ss.fff"));
|
||||
|
||||
case "tz":
|
||||
return (_, sb) =>
|
||||
sb.Append(_timestampMode == TimestampMode.Local
|
||||
? TimeZoneInfo.Local.StandardName
|
||||
: "UTC");
|
||||
|
||||
case "host":
|
||||
return (_, sb) => sb.Append(Environment.MachineName);
|
||||
|
||||
case "category":
|
||||
return (log, sb) =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(log.Category))
|
||||
{
|
||||
sb.Append(log.Category);
|
||||
}
|
||||
};
|
||||
|
||||
case "thread":
|
||||
return (_, sb) => sb.Append(Thread.CurrentThread.ManagedThreadId);
|
||||
|
||||
case "pid":
|
||||
return (_, sb) => sb.Append(Process.GetCurrentProcess().Id);
|
||||
|
||||
case "message":
|
||||
return (log, sb) => sb.Append(log.Message);
|
||||
|
||||
case "props":
|
||||
return AppendProperties;
|
||||
|
||||
case "newline":
|
||||
return (_, sb) => sb.AppendLine();
|
||||
|
||||
case "logtype":
|
||||
return (log, sb) =>
|
||||
{
|
||||
var levelText = GetLevelText(log.Level);
|
||||
|
||||
if (_useColors && TryGetColor(log.Level, out var color))
|
||||
{
|
||||
Console.ForegroundColor = color.Foreground;
|
||||
Console.BackgroundColor = color.Background;
|
||||
|
||||
Console.Write(sb.ToString());
|
||||
Console.Write(levelText);
|
||||
|
||||
Console.ResetColor();
|
||||
sb.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(levelText);
|
||||
}
|
||||
};
|
||||
|
||||
default:
|
||||
return (_, sb) => sb.Append('{').Append(token).Append('}');
|
||||
}
|
||||
}
|
||||
|
||||
private void AppendProperties(LogEvent log, StringBuilder sb)
|
||||
{
|
||||
if (log.Properties.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
sb.Append(" {");
|
||||
|
||||
bool first = true;
|
||||
foreach (var prop in log.Properties)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
|
||||
sb.Append(prop.Key);
|
||||
sb.Append('=');
|
||||
sb.Append(prop.Value?.ToString() ?? "null");
|
||||
|
||||
first = false;
|
||||
}
|
||||
|
||||
sb.Append('}');
|
||||
}
|
||||
|
||||
private bool TryGetColor(LogLevel level, out ColorScheme color)
|
||||
{
|
||||
color = level switch
|
||||
{
|
||||
LogLevel.Trace => _colors.Trace,
|
||||
LogLevel.Debug => _colors.Debug,
|
||||
LogLevel.Information => _colors.Info,
|
||||
LogLevel.Warning => _colors.Warning,
|
||||
LogLevel.Error => _colors.Error,
|
||||
LogLevel.Critical => _colors.Critical,
|
||||
_ => _colors.Info
|
||||
};
|
||||
return color != null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string GetLevelText(LogLevel level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
LogLevel.Trace => "TRACE",
|
||||
LogLevel.Debug => "DEBUG",
|
||||
LogLevel.Information => "INFO",
|
||||
LogLevel.Warning => "WARN",
|
||||
LogLevel.Error => "ERROR",
|
||||
LogLevel.Critical => "CRITICAL",
|
||||
_ => "???"
|
||||
};
|
||||
}
|
||||
|
||||
public override Task FlushAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Console auto-flushes
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user