Severity: Warning
File: src/Servy.Core/Logging/EventLogLogger.cs
Lines: 73-85
Code:
public IServyLogger CreateScoped(string prefix)
{
return new EventLogLogger(
_source,
_currentLogLevel,
_isEventLogEnabled,
prefix
);
}
Explanation:
CreateScoped instantiates a brand-new EventLogLogger, which in its constructor calls InitializeEventLog() (line 53-56) and allocates a separate System.Diagnostics.EventLog handle. Every call site that builds a scoped logger therefore opens its own native EventLog handle — and most call sites simply use the result locally and let it go out of scope without ever calling Dispose().
EventLog implements IDisposable and holds an unmanaged event-source handle plus an internal listener thread under the hood. Without explicit disposal these accumulate until finalization (or service shutdown), which is observable as RPC handle pressure under sustained scoped-logger churn. It also defeats the implicit assumption that scoped loggers are cheap.
Suggested fix:
Have CreateScoped return a lightweight wrapper that delegates to the parent EventLog instance and only changes the prefix, instead of creating a new EventLog handle:
private sealed class ScopedEventLogLogger : IServyLogger
{
private readonly EventLogLogger _parent;
public string? Prefix { get; }
public ScopedEventLogLogger(EventLogLogger parent, string prefix) { _parent = parent; Prefix = prefix; }
public void Info(string m) => _parent.Info(Format(m));
// ... etc, with Format prepending Prefix
public void Dispose() { /* no-op; parent owns the EventLog */ }
public IServyLogger CreateScoped(string p) => new ScopedEventLogLogger(_parent, p);
}
public IServyLogger CreateScoped(string prefix) => new ScopedEventLogLogger(this, prefix);
Side benefit: avoids the duplicated EventLog.SourceExists round-trip on each scope creation.
Severity: Warning
File:
src/Servy.Core/Logging/EventLogLogger.csLines: 73-85
Code:
Explanation:
CreateScopedinstantiates a brand-newEventLogLogger, which in its constructor callsInitializeEventLog()(line 53-56) and allocates a separateSystem.Diagnostics.EventLoghandle. Every call site that builds a scoped logger therefore opens its own native EventLog handle — and most call sites simply use the result locally and let it go out of scope without ever callingDispose().EventLogimplementsIDisposableand holds an unmanaged event-source handle plus an internal listener thread under the hood. Without explicit disposal these accumulate until finalization (or service shutdown), which is observable as RPC handle pressure under sustained scoped-logger churn. It also defeats the implicit assumption that scoped loggers are cheap.Suggested fix:
Have
CreateScopedreturn a lightweight wrapper that delegates to the parent EventLog instance and only changes the prefix, instead of creating a new EventLog handle:Side benefit: avoids the duplicated
EventLog.SourceExistsround-trip on each scope creation.