Extensible_Portfolio_Site/ExtensiblePortfolioSite/Logging.cs

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);
}
}
}