Logger.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. using System.Collections.Concurrent;
  2. using System.Diagnostics;
  3. namespace MeloongCore;
  4. /// <summary>
  5. /// 日志等级。
  6. /// </summary>
  7. public enum LogLevel { Trace, Info, Warn, Error }
  8. /// <summary>
  9. /// 输出日志时执行的错误汇报行为。
  10. /// </summary>
  11. public enum LogBehavior {
  12. /// <summary>
  13. /// 无提示。
  14. /// </summary>
  15. None,
  16. /// <summary>
  17. /// 只在调试状态下给出错误提示信息。
  18. /// </summary>
  19. ToastIfDebug,
  20. /// <summary>
  21. /// 给出错误提示信息。
  22. /// </summary>
  23. Toast,
  24. /// <summary>
  25. /// 弹出错误弹窗,不要求反馈。
  26. /// </summary>
  27. Alert,
  28. /// <summary>
  29. /// 弹出错误弹窗,要求提交反馈。
  30. /// </summary>
  31. AlertThenFeedback,
  32. /// <summary>
  33. /// 弹出系统原生样式的错误弹窗,要求提交反馈,然后使程序崩溃。
  34. /// </summary>
  35. AlertThenCrash
  36. }
  37. public static class Logger {
  38. public static BaseLogger Instance { get; set; } = new();
  39. // 转发给实例的方法包装
  40. public static void Log(string message, LogLevel level = LogLevel.Info, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "")
  41. => Instance.Log(message, level, behavior, filePath);
  42. public static void Trace(string message, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "")
  43. => Log(message, LogLevel.Trace, behavior, filePath);
  44. public static void Info(string message, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "")
  45. => Log(message, LogLevel.Info, behavior, filePath);
  46. public static void Warn(string message, LogBehavior behavior = LogBehavior.ToastIfDebug, [CallerFilePath] string filePath = "")
  47. => Log(message, LogLevel.Warn, behavior, filePath);
  48. public static void Error(string message, LogBehavior behavior = LogBehavior.AlertThenFeedback, [CallerFilePath] string filePath = "")
  49. => Log(message, LogLevel.Error, behavior, filePath);
  50. public static void Log(LogLevel level, [InterpolatedStringHandlerArgument("level")] ref LogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "") {
  51. if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), level, behavior, filePath); }
  52. public static void Trace(ref TraceLogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "") {
  53. if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), LogLevel.Trace, behavior, filePath); }
  54. public static void Info(ref InfoLogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "") {
  55. if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), LogLevel.Info, behavior, filePath); }
  56. public static void Warn(ref WarnLogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.ToastIfDebug, [CallerFilePath] string filePath = "") {
  57. if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), LogLevel.Warn, behavior, filePath); }
  58. public static void Error(ref ErrorLogInterpolatedStringHandler handler, LogBehavior behavior = LogBehavior.AlertThenFeedback, [CallerFilePath] string filePath = "") {
  59. if (handler.IsEnabled) Instance.Log(handler.ToStringAndClear(), LogLevel.Error, behavior, filePath); }
  60. public static void Log(Exception ex, string? message = null, LogLevel level = LogLevel.Warn, LogBehavior behavior = LogBehavior.ToastIfDebug, [CallerFilePath] string filePath = "")
  61. => Instance.Log(ex, message, level, behavior, filePath);
  62. public static void Trace(Exception ex, string? message = null, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "")
  63. => Log(ex, message, LogLevel.Trace, behavior, filePath);
  64. public static void Info(Exception ex, string? message = null, LogBehavior behavior = LogBehavior.None, [CallerFilePath] string filePath = "")
  65. => Log(ex, message, LogLevel.Info, behavior, filePath);
  66. public static void Warn(Exception ex, string? message = null, LogBehavior behavior = LogBehavior.ToastIfDebug, [CallerFilePath] string filePath = "")
  67. => Log(ex, message, LogLevel.Warn, behavior, filePath);
  68. public static void Error(Exception ex, string? message = null, LogBehavior behavior = LogBehavior.AlertThenFeedback, [CallerFilePath] string filePath = "")
  69. => Log(ex, message, LogLevel.Error, behavior, filePath);
  70. /// <summary>
  71. /// 在不调用 Logger 的情况下,将信息直接发送到输出窗口。
  72. /// </summary>
  73. [Conditional("DEBUG")]
  74. public static void SendToDebug(string message, LogLevel level = LogLevel.Error, [CallerFilePath] string filePath = "")
  75. => Debug.WriteLine($"{BaseLogger.GetLogPrefix(level, filePath)}{message}");
  76. /// <summary>
  77. /// 在不调用 Logger 的情况下,将信息直接发送到输出窗口。
  78. /// </summary>
  79. [Conditional("DEBUG")]
  80. public static void SendToDebug(Exception ex, string? message = null, LogLevel level = LogLevel.Error, [CallerFilePath] string filePath = "")
  81. => Debug.WriteLine($"{BaseLogger.GetLogPrefix(level, filePath)}{(message is null ? "" : $"{message}:")}{ex.GetDisplay(true)}");
  82. }
  83. /// <summary>
  84. /// 最基础的日志实现。
  85. /// 仅将日志输出到 <see cref="Debug"/>,不实现 <see cref="LogBehavior"/>。
  86. /// </summary>
  87. public class BaseLogger {
  88. /// <summary>
  89. /// 最低日志输出等级,低于此等级的日志将被忽略。
  90. /// </summary>
  91. public LogLevel MinLevel { get; set; } = LogLevel.Trace;
  92. // 核心方法
  93. public virtual void Log(string message, LogLevel level, LogBehavior behavior, string filePath) {
  94. if (MinLevel > level) return;
  95. try {
  96. var formattedMessage = Format(message, level, filePath, null);
  97. Output(formattedMessage, level);
  98. HandleBehavior(message, formattedMessage, behavior, null);
  99. } catch (Exception logEx) {
  100. Logger.SendToDebug(logEx, "打印日志失败");
  101. }
  102. }
  103. public virtual void Log(Exception ex, string? message, LogLevel level, LogBehavior behavior, string filePath) {
  104. if (MinLevel > level) return;
  105. if (ex is ThreadInterruptedException) {
  106. Thread.CurrentThread.Interrupt();
  107. return;
  108. }
  109. try {
  110. var formattedMessage = (message is null ? "" : $"{message}:") + ex.GetDisplay(true);
  111. formattedMessage = Format(formattedMessage, level, filePath, ex);
  112. Output(formattedMessage, level);
  113. HandleBehavior(message, formattedMessage, behavior, ex);
  114. } catch (Exception logEx) {
  115. Logger.SendToDebug(logEx, "打印日志失败");
  116. }
  117. }
  118. /// <summary>
  119. /// 格式化日志文本。
  120. /// </summary>
  121. protected virtual string Format(string text, LogLevel level, string filePath, Exception? ex) {
  122. string prefix = GetLogPrefix(level, filePath);
  123. return text
  124. .ReplaceLineEndings("\n", mergeMultiple: true).Split(['\n'], StringSplitOptions.RemoveEmptyEntries)
  125. .Select(t => prefix + t)
  126. .Join("\r\n");
  127. }
  128. public static string GetLogPrefix(LogLevel level, string filePath)
  129. => $"{DateTime.Now:HH':'mm':'ss'.'fff} {level.ToString().First()} {(Thread.CurrentThread.Name is null ? "" : $"<{Thread.CurrentThread.Name}> ")}[{Path.GetFileName(filePath).BeforeFirst(".")}] ";
  130. /// <summary>
  131. /// 输出格式化后的日志文本。
  132. /// </summary>
  133. protected virtual void Output(string formattedMessage, LogLevel level) {
  134. Debug.WriteLine(formattedMessage);
  135. }
  136. /// <summary>
  137. /// 在调用 Log 方法的线程执行 <see cref="LogBehavior"/>。
  138. /// </summary>
  139. protected virtual void HandleBehavior(string? rawMessage, string formattedMessage, LogBehavior behavior, Exception? ex) {
  140. }
  141. }
  142. /// <summary>
  143. /// 用于 <see cref="Logger.Log(LogLevel, ref LogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。
  144. /// 仅当日志等级满足要求时才格式化字符串,避免不必要的字符串拼接开销。
  145. /// </summary>
  146. [InterpolatedStringHandler]
  147. public ref struct LogInterpolatedStringHandler {
  148. private StringBuilder? _sb;
  149. /// <summary>当前日志等级是否满足输出要求。</summary>
  150. public readonly bool IsEnabled;
  151. public LogInterpolatedStringHandler(int literalLength, int formattedCount, LogLevel level, out bool isEnabled) {
  152. IsEnabled = isEnabled = Logger.Instance.MinLevel <= level;
  153. _sb = IsEnabled ? new StringBuilder(literalLength) : null;
  154. }
  155. /// <summary>追加字面量字符串片段。</summary>
  156. public void AppendLiteral(string value) => _sb?.Append(value);
  157. /// <summary>追加格式化值。</summary>
  158. public void AppendFormatted<T>(T value) => _sb?.Append(value?.ToString());
  159. /// <summary>追加带格式字符串的格式化值。</summary>
  160. public void AppendFormatted<T>(T value, string? format)
  161. => _sb?.Append(value is IFormattable formattable ? formattable.ToString(format, null) : value?.ToString());
  162. /// <summary>追加带对齐宽度的格式化值。</summary>
  163. public void AppendFormatted<T>(T value, int alignment) {
  164. if (_sb is null) return;
  165. var s = value?.ToString() ?? string.Empty;
  166. _sb.Append(alignment > 0 ? s.PadLeft(alignment) : alignment < 0 ? s.PadRight(-alignment) : s);
  167. }
  168. /// <summary>追加带对齐宽度和格式字符串的格式化值。</summary>
  169. public void AppendFormatted<T>(T value, int alignment, string? format) {
  170. if (_sb is null) return;
  171. var s = value is IFormattable formattable ? formattable.ToString(format, null) : value?.ToString() ?? string.Empty;
  172. _sb.Append(alignment > 0 ? s.PadLeft(alignment) : alignment < 0 ? s.PadRight(-alignment) : s);
  173. }
  174. /// <summary>追加字符串值。</summary>
  175. public void AppendFormatted(string? value) => _sb?.Append(value);
  176. /// <summary>追加带对齐宽度和格式字符串的对象值。</summary>
  177. public void AppendFormatted(object? value, int alignment = 0, string? format = null) {
  178. if (_sb is null) return;
  179. var s = value is IFormattable formattable ? formattable.ToString(format, null) : value?.ToString() ?? string.Empty;
  180. _sb.Append(alignment > 0 ? s.PadLeft(alignment) : alignment < 0 ? s.PadRight(-alignment) : s);
  181. }
  182. internal string ToStringAndClear() {
  183. var result = _sb?.ToString() ?? string.Empty;
  184. _sb = null;
  185. return result;
  186. }
  187. }
  188. /// <summary>用于 <see cref="Logger.Trace(ref TraceLogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。</summary>
  189. [InterpolatedStringHandler]
  190. public ref struct TraceLogInterpolatedStringHandler {
  191. private LogInterpolatedStringHandler _inner;
  192. /// <inheritdoc cref="LogInterpolatedStringHandler.IsEnabled"/>
  193. public bool IsEnabled => _inner.IsEnabled;
  194. public TraceLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled)
  195. => _inner = new(literalLength, formattedCount, LogLevel.Trace, out isEnabled);
  196. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendLiteral"/>
  197. public void AppendLiteral(string value) => _inner.AppendLiteral(value);
  198. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T)"/>
  199. public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
  200. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, string?)"/>
  201. public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
  202. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int)"/>
  203. public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
  204. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int, string?)"/>
  205. public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
  206. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(string?)"/>
  207. public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
  208. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(object?, int, string?)"/>
  209. public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
  210. internal string ToStringAndClear() => _inner.ToStringAndClear();
  211. }
  212. /// <summary>用于 <see cref="Logger.Info(ref InfoLogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。</summary>
  213. [InterpolatedStringHandler]
  214. public ref struct InfoLogInterpolatedStringHandler {
  215. private LogInterpolatedStringHandler _inner;
  216. /// <inheritdoc cref="LogInterpolatedStringHandler.IsEnabled"/>
  217. public bool IsEnabled => _inner.IsEnabled;
  218. public InfoLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled)
  219. => _inner = new(literalLength, formattedCount, LogLevel.Info, out isEnabled);
  220. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendLiteral"/>
  221. public void AppendLiteral(string value) => _inner.AppendLiteral(value);
  222. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T)"/>
  223. public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
  224. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, string?)"/>
  225. public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
  226. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int)"/>
  227. public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
  228. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int, string?)"/>
  229. public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
  230. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(string?)"/>
  231. public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
  232. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(object?, int, string?)"/>
  233. public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
  234. internal string ToStringAndClear() => _inner.ToStringAndClear();
  235. }
  236. /// <summary>用于 <see cref="Logger.Warn(ref WarnLogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。</summary>
  237. [InterpolatedStringHandler]
  238. public ref struct WarnLogInterpolatedStringHandler {
  239. private LogInterpolatedStringHandler _inner;
  240. /// <inheritdoc cref="LogInterpolatedStringHandler.IsEnabled"/>
  241. public bool IsEnabled => _inner.IsEnabled;
  242. public WarnLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled)
  243. => _inner = new(literalLength, formattedCount, LogLevel.Warn, out isEnabled);
  244. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendLiteral"/>
  245. public void AppendLiteral(string value) => _inner.AppendLiteral(value);
  246. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T)"/>
  247. public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
  248. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, string?)"/>
  249. public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
  250. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int)"/>
  251. public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
  252. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int, string?)"/>
  253. public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
  254. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(string?)"/>
  255. public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
  256. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(object?, int, string?)"/>
  257. public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
  258. internal string ToStringAndClear() => _inner.ToStringAndClear();
  259. }
  260. /// <summary>用于 <see cref="Logger.Error(ref ErrorLogInterpolatedStringHandler, LogBehavior, string)"/> 的内插字符串处理器。</summary>
  261. [InterpolatedStringHandler]
  262. public ref struct ErrorLogInterpolatedStringHandler {
  263. private LogInterpolatedStringHandler _inner;
  264. /// <inheritdoc cref="LogInterpolatedStringHandler.IsEnabled"/>
  265. public bool IsEnabled => _inner.IsEnabled;
  266. public ErrorLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled)
  267. => _inner = new(literalLength, formattedCount, LogLevel.Error, out isEnabled);
  268. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendLiteral"/>
  269. public void AppendLiteral(string value) => _inner.AppendLiteral(value);
  270. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T)"/>
  271. public void AppendFormatted<T>(T value) => _inner.AppendFormatted(value);
  272. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, string?)"/>
  273. public void AppendFormatted<T>(T value, string? format) => _inner.AppendFormatted(value, format);
  274. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int)"/>
  275. public void AppendFormatted<T>(T value, int alignment) => _inner.AppendFormatted(value, alignment);
  276. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted{T}(T, int, string?)"/>
  277. public void AppendFormatted<T>(T value, int alignment, string? format) => _inner.AppendFormatted(value, alignment, format);
  278. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(string?)"/>
  279. public void AppendFormatted(string? value) => _inner.AppendFormatted(value);
  280. /// <inheritdoc cref="LogInterpolatedStringHandler.AppendFormatted(object?, int, string?)"/>
  281. public void AppendFormatted(object? value, int alignment = 0, string? format = null) => _inner.AppendFormatted(value, alignment, format);
  282. internal string ToStringAndClear() => _inner.ToStringAndClear();
  283. }
  284. /// <summary>
  285. /// 将日志输出到 <see cref="Debug"/> 并写入指定的文件夹,最多保留 5 个文件。
  286. /// 不实现 <see cref="LogBehavior"/>。
  287. /// </summary>
  288. public class FileLogger : BaseLogger, IDisposable {
  289. /// <inheritdoc/>
  290. protected override void Output(string formattedText, LogLevel level) {
  291. base.Output(formattedText, level);
  292. if (!writerAvailable && queue.Count >= 100) return; // 在 writer 就绪前,最多缓存 100 条日志
  293. queue.Enqueue(formattedText);
  294. queuedEvent.Set();
  295. }
  296. private readonly ConcurrentQueue<string> queue = new();
  297. private readonly AutoResetEvent queuedEvent = new(false);
  298. private StreamWriter? writer;
  299. private volatile bool writerAvailable = false;
  300. /// <summary>
  301. /// 将日志立即写入文件。
  302. /// 不会抛出异常。
  303. /// </summary>
  304. public void Flush() {
  305. try {
  306. if (writer is null || !writerAvailable) return;
  307. lock (flushLock)
  308. while (queue.TryDequeue(out string line)) writer.WriteLine(line); // writer 指定了 AutoFlush
  309. } catch (Exception logEx) {
  310. Logger.SendToDebug(logEx, "写入日志失败");
  311. }
  312. }
  313. private readonly object flushLock = new();
  314. /// <summary>
  315. /// 初始化此 Logger。
  316. /// 会在新线程中,将之前的日志文件依次后移,最多保留 5 个文件。
  317. /// </summary>
  318. public FileLogger(string logFolder, string fileNamePrefix = "Log", string fileNameSuffix = ".txt") {
  319. var thread = new Thread(() => {
  320. // 轮转日志文件,将 Log1.txt 留空
  321. Logger.Info("日志初始化开始");
  322. try {
  323. for (int i = 4; i >= 1; i--) {
  324. string newerFile = Path.Combine(logFolder, $"{fileNamePrefix}{i + 1}{fileNameSuffix}");
  325. string olderFile = Path.Combine(logFolder, $"{fileNamePrefix}{i}{fileNameSuffix}");
  326. if (!FileUtils.Exists(olderFile)) continue;
  327. FileUtils.Copy(olderFile, newerFile);
  328. FileUtils.Delete(olderFile);
  329. }
  330. } catch (IOException ex) {
  331. Logger.Warn(ex, "可能同时开启了多个程序,这或许会导致未知问题", LogBehavior.Toast);
  332. } catch (Exception ex) {
  333. Logger.Warn(ex, "整理日志文件失败");
  334. }
  335. // 写入新日志文件
  336. try {
  337. writer = new StreamWriter(PathUtils.WithLongPath(Path.Combine(logFolder, $"{fileNamePrefix}1{fileNameSuffix}")), append: true) { AutoFlush = true };
  338. writerAvailable = true;
  339. Logger.Info("日志初始化成功");
  340. while (writerAvailable) {
  341. queuedEvent.WaitOne();
  342. Flush();
  343. }
  344. } catch (Exception ex) {
  345. Logger.Warn(ex, "写入日志文件失败", LogBehavior.Toast);
  346. writerAvailable = false;
  347. }
  348. }) { Name = nameof(FileLogger), Priority = ThreadPriority.Lowest, IsBackground = true };
  349. thread.Start();
  350. }
  351. public void Dispose() {
  352. writerAvailable = false;
  353. if (writer is not null) ((IDisposable) writer).Dispose();
  354. queuedEvent.Dispose();
  355. GC.SuppressFinalize(this);
  356. }
  357. }