1a2856526a
Comments described the *history* of how the code arrived (phase numbers, wave IDs, review IDs, dated TODOs) instead of what it does today. That scaffolding rotted as the codebase evolved. Cleaned 60 source files + .gitignore; behaviour unchanged (387/387 tests still pass). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
91 lines
3.3 KiB
C#
91 lines
3.3 KiB
C#
using System.Diagnostics;
|
|
using System.Runtime.Versioning;
|
|
using Serilog.Core;
|
|
using Serilog.Events;
|
|
|
|
namespace Mbproxy.Diagnostics;
|
|
|
|
/// <summary>
|
|
/// Serilog sink that writes events at level Error and above to the Windows Event Log
|
|
/// under source <c>mbproxy</c>.
|
|
///
|
|
/// <para>This sink is only active when the service is running as a Windows Service
|
|
/// (<see cref="Microsoft.Extensions.Hosting.WindowsServices.WindowsServiceHelpers.IsWindowsService"/>
|
|
/// returns <c>true</c>). Under <c>dotnet run</c> / 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.</para>
|
|
///
|
|
/// <para>The Event Log source <c>mbproxy</c> must be created by <c>install.ps1</c> 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.</para>
|
|
///
|
|
/// <para>Messages are capped at 32 KB (the Windows Event Log single-entry limit).</para>
|
|
/// </summary>
|
|
[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; }
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc/>
|
|
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.
|
|
}
|
|
}
|
|
}
|