Skip to content

[Code Quality] Logger.Log — bare 'catch { /* Fail-silent */ }' on the write path swallows every I/O exception with no fallback channel #940

@Christophe-Rogiers

Description

@Christophe-Rogiers

Severity: Info

File: src/Servy.Core/Logging/Logger.cs (lines 289-324)

Code:

private static void Log(LogLevel level, string? message)
{
    if (_writer == null) return;
    if (string.IsNullOrEmpty(message)) return;

    try
    {
        lock (_lock)
        {
            // ...
            _writer.WriteLine(logEntry);
        }
    }
    catch
    {
        // Fail-silent
    }
}

Explanation:
Logger.Initialize already has a thoughtful fallback (lines 113-128): if init fails, the exception is appended to a LoggerInitializationErrors.log sidecar file. The write path on line 320-323 has no such fallback — it just swallows.

In production these write-path failures are exactly the ones an operator most needs to see:

  • IOException because the disk filled up (the rotation engine in RotatingStreamWriter would also be unhappy, but the visible symptom is silent loss).
  • UnauthorizedAccessException if ACLs on %ProgramData%\Servy\logs were ever clobbered (the project itself manipulates ACLs in SecurityHelper.CreateSecureDirectory, so a bug there would be invisible from the logger).
  • ObjectDisposedException from a race between Shutdown() (line 329) and a concurrent Log — the double-checked-locking if (_writer == null) return mitigates but does not eliminate this for callers that already passed the early return.

In every case, the only signal Servy emits is "logs stopped appearing, no obvious reason". Even an event-log fallback or a write to a sidecar (mirroring the init-time LoggerInitializationErrors.log) would be enough.

Suggested fix:
Mirror the initialization fallback path:

catch (Exception ex)
{
    try
    {
        var logDir = Path.Combine(AppConfig.ProgramDataPath, "logs");
        var now = _useLocalTimeForRotation ? DateTime.Now : DateTime.UtcNow;
        File.AppendAllText(Path.Combine(logDir, "LoggerWriteErrors.log"),
            $"[{now:yyyy-MM-dd HH:mm:ss}] Failed to write log entry: {ex.Message}{Environment.NewLine}");
    }
    catch { /* truly fail-silent only as last resort */ }
}

For services that already have access to EventLog (the AppConfig.EventSource is registered at install time), a write to event log on Critical is also defensible.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions