231 lines
7.8 KiB
C#
231 lines
7.8 KiB
C#
using Microsoft.Extensions.FileSystemGlobbing.Internal.PathSegments;
|
|
|
|
using System.Collections.Concurrent;
|
|
using System.Net.Sockets;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Text;
|
|
|
|
namespace ExtensiblePortfolioSite
|
|
{
|
|
public sealed class LoggerProvider : ILoggerProvider
|
|
{
|
|
private sealed class Logger : ILogger, IDisposable
|
|
{
|
|
// can't see what the scopes are event used for.
|
|
// 0 restraints on the genericType so what am i supposed to do with them.
|
|
// log their type? serialize the scope object with every message?
|
|
// it seems a bit rediculus.
|
|
|
|
private sealed class Scope : IDisposable
|
|
{
|
|
private readonly Logger Parent;
|
|
public readonly String Identifier;
|
|
|
|
public Scope(Logger Parent, String Identifier)
|
|
{
|
|
this.Parent = Parent;
|
|
this.Identifier = Identifier;
|
|
this.Parent.ScopeStack.AddLast(this);
|
|
}
|
|
|
|
public void Dispose() => this.Parent.ScopeStack.Remove(this);
|
|
}
|
|
|
|
private readonly LinkedList<Scope> ScopeStack = new();
|
|
private readonly LoggerProvider Provider;
|
|
public readonly String Category;
|
|
|
|
public Logger(LoggerProvider Provider, String Category)
|
|
{
|
|
this.Provider = Provider;
|
|
this.Category = Category;
|
|
}
|
|
|
|
|
|
public IDisposable BeginScope<TState>(TState state) => new Scope(this, ScopeToString(state));
|
|
public bool IsEnabled(LogLevel logLevel) => true;
|
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) =>
|
|
Provider.WriteLog(this, logLevel, eventId, formatter(state, exception));
|
|
|
|
public void WriteScope(StringBuilder MessageBuilder)
|
|
{
|
|
LinkedListNode<Scope>? Curr = ScopeStack.First;
|
|
|
|
while (Curr != null)
|
|
{
|
|
MessageBuilder.Append(">");
|
|
MessageBuilder.Append(Curr.Value.Identifier);
|
|
Curr = Curr.Next;
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
lock (ScopeStack)
|
|
{
|
|
LinkedListNode<Scope>? Curr = ScopeStack.First;
|
|
while (Curr != null)
|
|
{
|
|
LinkedListNode<Scope>? next = Curr.Next;
|
|
Curr.Value.Dispose();
|
|
Curr = next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#region Static
|
|
private static readonly Encoding ENCODING = Encoding.Unicode;
|
|
private static readonly Queue<StringBuilder> SBCache = new();
|
|
private static StringBuilder GetStringBuilder()
|
|
{
|
|
lock (SBCache)
|
|
if (SBCache.Count > 1)
|
|
return SBCache.Dequeue();
|
|
|
|
return new StringBuilder(1024);
|
|
}
|
|
private static void ReclaimStringBuilder(StringBuilder sb)
|
|
{
|
|
if (sb.Capacity > 1024)
|
|
return;
|
|
sb.Clear();
|
|
lock (SBCache)
|
|
SBCache.Enqueue(sb);
|
|
}
|
|
|
|
private static String ScopeToString<TState>(TState Obj)
|
|
{
|
|
#pragma warning disable IDE0046 // Convert to conditional expression
|
|
|
|
if (Obj == null)
|
|
return "null";
|
|
|
|
if (Obj is Thread thread)
|
|
return $"{thread.ManagedThreadId}:{thread.Name}";
|
|
|
|
if (Obj is Socket socket)
|
|
return socket.RemoteEndPoint?.ToString() ?? "null";
|
|
|
|
return Obj.ToString() ?? "null";
|
|
#pragma warning restore IDE0046 // Convert to conditional expression
|
|
}
|
|
#endregion Static
|
|
|
|
|
|
private Boolean Running;
|
|
private readonly Thread LogWriter;
|
|
private readonly TextWriter COut;
|
|
private readonly StreamWriter? FOut;
|
|
public LoggerProvider(String FileOutput)
|
|
{
|
|
this.COut = Console.Out;
|
|
|
|
String Dir = Path.GetDirectoryName(FileOutput)!;
|
|
if(!Directory.Exists(Dir)) Directory.CreateDirectory(Dir);
|
|
this.FOut = new StreamWriter(new FileStream(FileOutput, FileMode.Append, FileAccess.Write, FileShare.Read, 1024, false), ENCODING, 1024, false);
|
|
|
|
this.LogWriter = new Thread(LogWriter_thread)
|
|
{
|
|
Name = "Log Writer",
|
|
Priority = ThreadPriority.BelowNormal,
|
|
IsBackground = true,
|
|
};
|
|
this.Running = true;
|
|
this.LogWriter.Start();
|
|
}
|
|
public LoggerProvider()
|
|
{
|
|
this.FOut = null;
|
|
this.COut = Console.Out;
|
|
|
|
this.LogWriter = new Thread(LogWriter_thread)
|
|
{
|
|
Name = "Log Writer",
|
|
Priority = ThreadPriority.BelowNormal,
|
|
IsBackground = true,
|
|
};
|
|
this.Running = true;
|
|
this.LogWriter.Start();
|
|
}
|
|
|
|
|
|
private readonly SortedDictionary<String, Logger> LoggerLookup = new();
|
|
public ILogger CreateLogger(string categoryName)
|
|
{
|
|
// optimize string matching by enforcing it into the strings table
|
|
categoryName = string.IsInterned(categoryName) ?? string.Intern(categoryName);
|
|
|
|
if (LoggerLookup.TryGetValue(categoryName, out Logger? logger))
|
|
return logger;
|
|
|
|
lock (LoggerLookup)
|
|
{
|
|
if (LoggerLookup.TryGetValue(categoryName, out logger))
|
|
return logger;
|
|
|
|
logger = new Logger(this, categoryName);
|
|
LoggerLookup.Add(categoryName, logger);
|
|
return logger;
|
|
}
|
|
}
|
|
public void Dispose()
|
|
{
|
|
this.Running = false;
|
|
lock (LoggerLookup)
|
|
foreach (Logger logger in LoggerLookup.Values)
|
|
logger.Dispose();
|
|
|
|
this.LogWriter.Join();
|
|
}
|
|
|
|
|
|
|
|
private readonly ConcurrentQueue<String> LogQueue = new();
|
|
private void WriteLog(Logger logger, LogLevel logLevel, EventId eventId, String Message)
|
|
{
|
|
StringBuilder MessageBuilder = GetStringBuilder();
|
|
{
|
|
MessageBuilder.Append('[');
|
|
MessageBuilder.Append(DateTime.Now.ToShortTimeString());
|
|
MessageBuilder.Append('\\');
|
|
MessageBuilder.Append(logLevel.ToString());
|
|
MessageBuilder.Append('\\');
|
|
MessageBuilder.Append(logger.Category);
|
|
MessageBuilder.Append(']');
|
|
MessageBuilder.Append('{');
|
|
MessageBuilder.Append(eventId.Id);
|
|
MessageBuilder.Append(':');
|
|
MessageBuilder.Append(eventId.Name);
|
|
logger.WriteScope(MessageBuilder);
|
|
MessageBuilder.Append("}::");
|
|
MessageBuilder.Append(Message);
|
|
|
|
LogQueue.Enqueue(MessageBuilder.ToString());
|
|
}
|
|
ReclaimStringBuilder(MessageBuilder);
|
|
}
|
|
private void LogWriter_thread()
|
|
{
|
|
[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
|
|
async void WriteLog(String Log)
|
|
{
|
|
Task COutWrite_task = COut.WriteLineAsync(Log);
|
|
if (FOut != null)
|
|
await FOut.WriteLineAsync(Log);
|
|
await COutWrite_task;
|
|
}
|
|
|
|
while (this.Running)
|
|
{
|
|
while (LogQueue.TryDequeue(out String? Log))
|
|
WriteLog(Log);
|
|
Thread.Sleep(1000);
|
|
}
|
|
lock (LogQueue)
|
|
while (LogQueue.TryDequeue(out String? Log))
|
|
WriteLog(Log);
|
|
}
|
|
}
|
|
}
|