using System.Diagnostics; using System.Runtime.Versioning; using Serilog.Core; using Serilog.Events; namespace Mbproxy.Diagnostics; /// /// Serilog sink that writes events at level Error and above to the Windows Event Log /// under source mbproxy. /// /// This sink is only active when the service is running as a Windows Service /// ( /// returns true). Under dotnet run / test / interactive launch, the sink is /// a no-op so that the Event Log source registration (which requires admin rights) is not /// required in development. /// /// The Event Log source mbproxy must be created by install.ps1 before /// the service starts. The bridge does NOT attempt to create the source at runtime — the /// service account may not hold the required admin rights. /// /// Messages are capped at 32 KB (the Windows Event Log single-entry limit). /// [SupportedOSPlatform("windows")] internal sealed class EventLogBridge : ILogEventSink { private const string Source = "mbproxy"; private const string LogName = "Application"; private const int MaxMessageBytes = 32 * 1024; // 32 KB Event Log limit private readonly bool _enabled; // Cache the source-exists check at construction so Emit doesn't hit the registry on // every Error+ log line. A missing source after start requires a service restart to // pick up; in practice install.ps1 registers it once at install. private readonly bool _sourceExists; public EventLogBridge(bool enabled) { _enabled = enabled; if (_enabled) { try { _sourceExists = EventLog.SourceExists(Source); } catch { _sourceExists = false; } } } /// public void Emit(LogEvent logEvent) { if (!_enabled) return; if (logEvent.Level < LogEventLevel.Error) return; // Cached at construction — silently swallow if the source isn't registered. // The service account may not be able to create it and we must not crash the logger. if (!_sourceExists) return; string message = logEvent.RenderMessage(); // Append exception detail when present. if (logEvent.Exception is not null) { message += Environment.NewLine + logEvent.Exception; } // Truncate to the Event Log single-entry limit. if (message.Length * 2 > MaxMessageBytes) // rough UTF-16 upper bound { int charLimit = MaxMessageBytes / 2 - 3; message = message[..charLimit] + "..."; } var type = logEvent.Level switch { LogEventLevel.Fatal => EventLogEntryType.Error, LogEventLevel.Error => EventLogEntryType.Error, LogEventLevel.Warning => EventLogEntryType.Warning, _ => EventLogEntryType.Information, }; try { EventLog.WriteEntry(Source, message, type); } catch { // Swallow: if the Event Log write fails (e.g., source not registered, // quota exceeded) we must not crash the application or recurse. } } }