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 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 state) => new Scope(this, ScopeToString(state)); public bool IsEnabled(LogLevel logLevel) => true; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) => Provider.WriteLog(this, logLevel, eventId, formatter(state, exception)); public void WriteScope(StringBuilder MessageBuilder) { LinkedListNode? Curr = ScopeStack.First; while (Curr != null) { MessageBuilder.Append(">"); MessageBuilder.Append(Curr.Value.Identifier); Curr = Curr.Next; } } public void Dispose() { lock (ScopeStack) { LinkedListNode? Curr = ScopeStack.First; while (Curr != null) { LinkedListNode? next = Curr.Next; Curr.Value.Dispose(); Curr = next; } } } } #region Static private static readonly Encoding ENCODING = Encoding.Unicode; private static readonly Queue 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 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 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 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); } } }