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.
}
}
}