dragon-iso/src/tests/TeamsISO.Engine.Tests/Logging/EngineLoggingTests.cs
Zac Gaetano 1d85396a90 feat(logging): rolling file sink under %LOCALAPPDATA%\\TeamsISO\\Logs
Adds Serilog.Sinks.File to TeamsISO.Engine and a new EngineLogging.CreateDefault() factory that writes to BOTH the existing console sink and a rolling daily file at %LOCALAPPDATA%\\TeamsISO\\Logs\\teamsiso<date>.log. The WPF host (TeamsISO.exe is a WinExe with no console attached at runtime) now uses CreateDefault so support has something to ask for when users file an issue. The Console build keeps using CreateConsole — stdout is the right surface there and shell redirection beats a competing on-disk sink.

Files roll daily, cap at 10 MB before mid-day rollover, and only the most recent 14 are retained. Disk flush interval is 250 ms so a tail -f from another tool sees lines promptly. Path is announced via the first log line on every startup.

Two unit tests gate the wiring: AllLoggers_WriteToFile (verifies both typed and named CreateLogger() reach the file) and LogsAtBelowMinimumLevel_AreSuppressed (regression guard for level filtering). 74/74 unit tests pass (was 72).

Also adds a startup breadcrumb log line in App.OnStartup carrying the build version + PID so we can correlate a user's log file with a specific commit.
2026-05-08 00:47:25 -04:00

54 lines
2.1 KiB
C#

using FluentAssertions;
using Microsoft.Extensions.Logging;
using TeamsISO.Engine.Logging;
namespace TeamsISO.Engine.Tests.Logging;
public class EngineLoggingTests : IDisposable
{
private readonly string _dir;
public EngineLoggingTests()
{
_dir = Path.Combine(Path.GetTempPath(), $"teamsiso-log-{Guid.NewGuid():N}");
}
public void Dispose()
{
try { Directory.Delete(_dir, recursive: true); } catch { /* best-effort */ }
}
[Fact]
public void CreateDefault_AllLoggers_WriteToFile()
{
// Multiple ILoggers from the same factory must all land in the file sink —
// catches regressions in CreateDefault wiring (e.g. if SerilogLoggerFactory
// swaps to Log.Logger silently and our static singleton isn't set).
var factory = EngineLogging.CreateDefault(LogLevel.Information, logDirectoryOverride: _dir);
factory.CreateLogger<EngineLoggingTests>().LogInformation("typed-logger-line");
factory.CreateLogger("Custom.Category").LogInformation("named-logger-line");
factory.Dispose(); // disposes the wrapped Serilog logger -> flush + close file
var logFiles = Directory.GetFiles(_dir, "*.log");
logFiles.Should().HaveCount(1);
var content = File.ReadAllText(logFiles[0]);
content.Should().Contain("typed-logger-line",
because: "logs from a typed CreateLogger<T> must reach the file sink");
content.Should().Contain("named-logger-line",
because: "logs from a named CreateLogger(string) must reach the file sink");
}
[Fact]
public void CreateDefault_LogsAtBelowMinimumLevel_AreSuppressed()
{
var factory = EngineLogging.CreateDefault(LogLevel.Warning, logDirectoryOverride: _dir);
factory.CreateLogger("X").LogInformation("info-should-be-suppressed");
factory.CreateLogger("X").LogWarning("warn-should-appear");
factory.Dispose();
var content = File.ReadAllText(Directory.GetFiles(_dir, "*.log").Single());
content.Should().NotContain("info-should-be-suppressed");
content.Should().Contain("warn-should-appear");
}
}