Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.57.8] - 2026-08-06

### Fixed
- **The log file can no longer grow big enough to be unusable.** SysManager keeps a diary of what it does, and that file is what you attach when reporting a problem. It had no size limit — one day's file was allowed to reach a gigabyte, which is far past what you could upload anywhere. Each file is now capped at 10 MB and a fresh one is started when it fills, keeping at most two weeks of them, so the diary stays small enough to send and takes a predictable amount of disk.
- **The Volume Control tab no longer floods that log.** If an app's audio could not be read, the tab wrote the same complaint twenty times a second for as long as it stayed open — filling the file with one repeated line and pushing out everything actually worth reading. It is now noted once, and again only if something genuinely changes.

## [1.57.7] - 2026-08-06

### Fixed
Expand Down
57 changes: 57 additions & 0 deletions SysManager/SysManager.Tests/LogServiceSinkScrubbingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,61 @@ public void Sink_IsSafeUnderConcurrentWrites()
// Every line is individually intact — no two events rendered into each other.
Assert.All(lines, line => Assert.Matches(@"\[INF\] Line \d+ at ", line));
}

// ── The log file is bounded ──────────────────────────────────────────────
// The sink passed no fileSizeLimitBytes and no rollOnFileSizeLimit, so it took Serilog's
// defaults: 1 GB per file with rolling OFF. The only bound on the folder was the 14-FILE count,
// so one daily file could grow to a gigabyte. Debug is a real volume tier here (290 Log.Debug
// call sites) and the documented support path is "attach the log" — an unattachable file breaks
// the evidence trail exactly when it is needed.

[Fact]
public void TheSizeLimit_IsSmallEnoughToAttachToABugReport()
{
// 25 MB is GitHub's per-file attachment ceiling. A log the user cannot upload is the failure
// this bound exists to prevent, so the intent is pinned rather than just the number.
Assert.True(LogService.MaxLogFileBytes <= 25L * 1024 * 1024,
$"A {LogService.MaxLogFileBytes / 1024 / 1024} MB log file is too large to attach to an issue.");
Assert.True(LogService.MaxLogFileBytes >= 1024 * 1024,
"Too small to hold useful context for a crash report.");
}

[Fact]
public void TheWholeLogFolder_IsBounded()
{
// The point of the pair: per-file ceiling × retained count is a predictable worst case,
// which is what the 14-file-count-alone version never gave.
var worstCase = LogService.MaxLogFileBytes * LogService.RetainedFileCount;
Assert.True(worstCase <= 250L * 1024 * 1024,
$"Worst-case log folder is {worstCase / 1024 / 1024} MB — too much for a low-end laptop.");
}

[Fact]
public void TheSink_KeepsEveryFileUnderTheLimit()
{
// Asserting the constants alone would pass even if the sink never received them — the defect
// was a missing ARGUMENT, not a wrong number. So drive the real sink hard and check the files
// it actually produced.
//
// RollingInterval.Infinite so the only thing that could create a second file is the size
// limit: with a daily interval a date change could produce one and this would pass for the
// wrong reason.
var sinkType = typeof(LogService).GetNestedType("UserPathScrubbingSink", BindingFlags.NonPublic)!;
var path = Path.Combine(_dir, "roll.log");
var sink = (ILogEventSink)Activator.CreateInstance(
sinkType, [path, RollingInterval.Infinite, LogService.RetainedFileCount])!;

using (var logger = new LoggerConfiguration().MinimumLevel.Debug().WriteTo.Sink(sink).CreateLogger())
{
var filler = new string('x', 1024); // ~1 KB per line
for (int i = 0; i < 2000; i++) logger.Information("{Index} {Filler}", i, filler);
}
(sink as IDisposable)?.Dispose();

var written = Directory.GetFiles(_dir, "roll*.log");
Assert.NotEmpty(written);
Assert.All(written, f =>
Assert.True(new FileInfo(f).Length <= LogService.MaxLogFileBytes + 64 * 1024,
$"{Path.GetFileName(f)} is {new FileInfo(f).Length} bytes, over the {LogService.MaxLogFileBytes}-byte limit."));
}
}
22 changes: 21 additions & 1 deletion SysManager/SysManager/Services/AudioMixerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,12 +287,29 @@ public float GetPeak(string sessionId)
{
if (meter.GetPeakValue(out float value) == 0 && value > peak) peak = value;
}
catch (COMException ex) { Log.Debug("GetPeak failed: {Error}", ex.Message); }
catch (COMException ex)
{
// Log once per session-handle generation, not per call. GetPeak is driven by the
// Volume Control tab's 50 ms peak-meter timer, so one bad audio session wrote
// ~20 identical Debug lines a second for as long as the tab stayed open — the
// log's single biggest flood path. The message is identical every tick, so
// repeating it adds nothing; ReleaseGroups resets the flag, meaning a genuinely
// new failure after a device change is still reported. Mirrors the existing
// one-shot probe pattern (_routingProbed / _routingSupported).
if (!_peakFailureLogged)
{
_peakFailureLogged = true;
Log.Debug("GetPeak failed: {Error}", ex.Message);
}
}
}
return peak;
}
}

// Reset by ReleaseGroups, so it is per session-handle generation rather than per process.
private bool _peakFailureLogged;

// ── Output-device enumeration (documented API) ─────────────────────────

/// <inheritdoc/>
Expand Down Expand Up @@ -492,6 +509,9 @@ private void ReleaseGroups()
foreach (var control in controls)
Release(control);
_groups.Clear();
// New handles mean a new chance to succeed — and a failure against them is new information,
// so allow it to be logged once more rather than staying silent for the whole session.
_peakFailureLogged = false;
}

private static void Release(object? comObject)
Expand Down
21 changes: 20 additions & 1 deletion SysManager/SysManager/Services/LogService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ public static partial class LogService
{
public static Logger? Logger { get; private set; }

/// <summary>
/// Per-file ceiling for the rolling log. With <see cref="RetainedFileCount"/> this bounds the
/// whole folder at roughly 140 MB, and keeps any single file small enough to attach to a bug
/// report — which is the documented way a user sends us evidence.
/// </summary>
internal const long MaxLogFileBytes = 10L * 1024 * 1024;

/// <summary>How many rolled files to keep. Combines with <see cref="MaxLogFileBytes"/>.</summary>
internal const int RetainedFileCount = 14;

public static string LogDir { get; } =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "SysManager", "logs");

Expand Down Expand Up @@ -59,7 +69,7 @@ public static void Init()
.WriteTo.Sink(new UserPathScrubbingSink(
Path.Combine(LogDir, "sysmanager-.log"),
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 14))
retainedFileCountLimit: RetainedFileCount))
.CreateLogger();
Log.Logger = Logger;
Logger.Information("SysManager started");
Expand Down Expand Up @@ -98,6 +108,15 @@ public UserPathScrubbingSink(
path,
rollingInterval: rollingInterval,
retainedFileCountLimit: retainedFileCountLimit,
// Bound each file AND roll when it fills. Without both, the sink took Serilog's
// defaults — 1 GB per file with rollOnFileSizeLimit false — so a single daily
// file could grow to a gigabyte with nothing rolling below it, and the only
// bound on the folder was the 14-FILE count. That matters because the whole
// support path is "attach the log" (SUPPORT.md, the bug-report template): a file
// too large to upload breaks the evidence trail exactly when it is needed, and
// Debug is a real volume tier here (290 Log.Debug call sites, some in loops).
fileSizeLimitBytes: MaxLogFileBytes,
rollOnFileSizeLimit: true,
outputTemplate: "{Message:lj}{NewLine}")
.CreateLogger();
}
Expand Down
6 changes: 3 additions & 3 deletions SysManager/SysManager/SysManager.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
<RootNamespace>SysManager</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>NU1603;NU1701</NoWarn>
<Version>1.57.7</Version>
<FileVersion>1.57.7.0</FileVersion>
<AssemblyVersion>1.57.7.0</AssemblyVersion>
<Version>1.57.8</Version>
<FileVersion>1.57.8.0</FileVersion>
<AssemblyVersion>1.57.8.0</AssemblyVersion>
<Product>SysManager</Product>
<Description>SysManager — Windows system monitoring toolkit by laurentiu021. Network, updates, health, logs, safe deep cleanup.</Description>
<PackageProjectUrl>https://github.com/laurentiu021/SystemManager</PackageProjectUrl>
Expand Down
Loading