using System.Text; using Microsoft.Extensions.Logging; namespace WxAgent.Service; public static class RuntimeLog { private static readonly object Gate = new(); public static void Append(string path, LogLevel level, string category, string message, Exception? exception = null) { try { var fullPath = Path.GetFullPath(path); var directory = Path.GetDirectoryName(fullPath); if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory); var line = Format(level, category, message, exception); lock (Gate) { using var stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete); using var writer = new StreamWriter(stream, new UTF8Encoding(false)); writer.WriteLine(line); } } catch { // Logging must not prevent the agent from starting or serving requests. } } internal static string Format(LogLevel level, string category, string message, Exception? exception) { var line = $"{DateTimeOffset.Now:O} [{level}] {category}: {message}"; return exception is null ? line : $"{line}{Environment.NewLine}{exception}"; } } internal sealed class FileLoggerProvider(string path) : ILoggerProvider { private readonly string path = Path.GetFullPath(path); public ILogger CreateLogger(string categoryName) => new FileLogger(this, categoryName); public void Dispose() { } private sealed class FileLogger(FileLoggerProvider provider, string categoryName) : ILogger { public IDisposable BeginScope(TState state) where TState : notnull => NoopScope.Instance; public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None; public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { if (!IsEnabled(logLevel)) return; RuntimeLog.Append(provider.path, logLevel, categoryName, formatter(state, exception), exception); } private sealed class NoopScope : IDisposable { public static readonly NoopScope Instance = new(); public void Dispose() { } } } }