-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLogger.cs
More file actions
99 lines (87 loc) · 3.6 KB
/
Logger.cs
File metadata and controls
99 lines (87 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
using System;
using System.IO;
using System.Text;
namespace MWBToggle;
/// <summary>
/// Tiny append-only log at %LOCALAPPDATA%\MWBToggle\mwbtoggle.log.
/// Caps at ~100 KB by truncating to the last ~50 KB when it grows too large.
/// Never throws — every log call is best-effort. Written for LTR support triage.
///
/// Backed by a long-lived StreamWriter so steady-state logging is one append —
/// no Dir.Exists + FileInfo stat + open/close per line. The directory is ensured
/// once on first write and cached; Truncate is amortized by only stat-ing every
/// N writes instead of every write.
/// </summary>
internal static class Logger
{
private static readonly object _gate = new();
private const long MaxBytes = 100 * 1024;
private const long KeepBytes = 50 * 1024;
private const int TruncateCheckInterval = 64;
internal static string LogPath { get; } = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MWBToggle", "mwbtoggle.log");
private static StreamWriter? _writer;
private static bool _dirEnsured;
private static int _writesSinceTruncateCheck;
internal static void Info(string msg) => Write("INFO", msg);
internal static void Warn(string msg) => Write("WARN", msg);
internal static void Error(string msg) => Write("ERROR", msg);
private static void Write(string level, string msg)
{
try
{
lock (_gate)
{
if (!_dirEnsured)
{
var dir = Path.GetDirectoryName(LogPath);
if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir);
_dirEnsured = true;
}
// Amortize the truncate check — the path that actually trims runs at
// most every N writes, not every write. The first write of the session
// always checks so a stale oversized log from a crashed prior session
// gets trimmed immediately.
if (_writesSinceTruncateCheck == 0 || _writesSinceTruncateCheck >= TruncateCheckInterval)
{
MaybeTruncate();
_writesSinceTruncateCheck = 0;
}
_writesSinceTruncateCheck++;
_writer ??= new StreamWriter(
new FileStream(LogPath, FileMode.Append, FileAccess.Write, FileShare.Read),
Encoding.UTF8) { AutoFlush = true };
_writer.Write($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] {msg}{Environment.NewLine}");
}
}
catch
{
// Logging must never break the app. Drop the writer so the next write
// re-opens (covers the rare case where the FileStream went bad).
try { _writer?.Dispose(); } catch { }
_writer = null;
}
}
private static void MaybeTruncate()
{
try
{
var fi = new FileInfo(LogPath);
if (!fi.Exists || fi.Length < MaxBytes) return;
// Release the long-lived writer so we can open the file exclusively.
try { _writer?.Dispose(); } catch { }
_writer = null;
using var fs = new FileStream(LogPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
fs.Seek(-KeepBytes, SeekOrigin.End);
var tail = new byte[KeepBytes];
int read = fs.Read(tail, 0, tail.Length);
fs.SetLength(0);
fs.Write(tail, 0, read);
}
catch
{
// Leave the file as-is if truncation fails.
}
}
}