286 lines
9.1 KiB
C#
286 lines
9.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|