From 016904f333436ac0b87d8b9b600e76177f3fedb7 Mon Sep 17 00:00:00 2001 From: Maksym Sadovnychyy Date: Thu, 4 Dec 2025 20:31:18 +0100 Subject: [PATCH] (bugfix): fillelogger multithreading issue --- src/MaksIT.Core/Logging/BaseFileLogger.cs | 35 +++++++++++++++++------ src/MaksIT.Core/Logging/FileLogger.cs | 6 ++-- src/MaksIT.Core/Logging/JsonFileLogger.cs | 4 +-- src/MaksIT.Core/MaksIT.Core.csproj | 2 +- 4 files changed, 33 insertions(+), 14 deletions(-) diff --git a/src/MaksIT.Core/Logging/BaseFileLogger.cs b/src/MaksIT.Core/Logging/BaseFileLogger.cs index 2e5ba7f..025132c 100644 --- a/src/MaksIT.Core/Logging/BaseFileLogger.cs +++ b/src/MaksIT.Core/Logging/BaseFileLogger.cs @@ -1,14 +1,14 @@ -using System; -using System.IO; using MaksIT.Core.Threading; using Microsoft.Extensions.Logging; + namespace MaksIT.Core.Logging; public abstract class BaseFileLogger : ILogger, IDisposable { private readonly LockManager _lockManager = new LockManager(); private readonly string _folderPath; private readonly TimeSpan _retentionPeriod; + private static readonly Mutex _fileMutex = new Mutex(false, "Global\\MaksITLoggerFileMutex"); // Named mutex for cross-process locking protected BaseFileLogger(string folderPath, TimeSpan retentionPeriod) { _folderPath = folderPath; @@ -22,17 +22,30 @@ public abstract class BaseFileLogger : ILogger, IDisposable { return logLevel != LogLevel.None; } - public abstract void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter); + public abstract Task Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter); + + // Explicit interface implementation for ILogger.Log (void) + void ILogger.Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { + // Call the async Log and wait synchronously for compatibility + Log(logLevel, eventId, state, exception, formatter).GetAwaiter().GetResult(); + } protected string GenerateLogFileName(string extension) { return Path.Combine(_folderPath, $"log_{DateTime.UtcNow:yyyy-MM-dd}.{extension}"); } - protected void AppendToLogFile(string logFileName, string content) { - _lockManager.ExecuteWithLockAsync(async () => { - await File.AppendAllTextAsync(logFileName, content); - RemoveExpiredLogFiles(Path.GetExtension(logFileName)); - }).Wait(); + protected Task AppendToLogFileAsync(string logFileName, string content) { + bool mutexAcquired = false; + try { + mutexAcquired = _fileMutex.WaitOne(10000); + if (!mutexAcquired) throw new IOException("Could not acquire file mutex for logging."); + File.AppendAllText(logFileName, content); // Synchronous write + RemoveExpiredLogFiles(Path.GetExtension(logFileName)); + return Task.CompletedTask; + } + finally { + if (mutexAcquired) _fileMutex.ReleaseMutex(); + } } private void RemoveExpiredLogFiles(string extension) { @@ -53,5 +66,11 @@ public abstract class BaseFileLogger : ILogger, IDisposable { public void Dispose() { _lockManager.Dispose(); + // Do NOT dispose the static mutex here; it should be disposed once per process, not per instance. + } + + // Optionally, add a static method to dispose the mutex at application shutdown: + public static void DisposeMutex() { + _fileMutex?.Dispose(); } } \ No newline at end of file diff --git a/src/MaksIT.Core/Logging/FileLogger.cs b/src/MaksIT.Core/Logging/FileLogger.cs index 50bae3f..c55b4c1 100644 --- a/src/MaksIT.Core/Logging/FileLogger.cs +++ b/src/MaksIT.Core/Logging/FileLogger.cs @@ -5,7 +5,7 @@ namespace MaksIT.Core.Logging; public class FileLogger : BaseFileLogger { public FileLogger(string folderPath, TimeSpan retentionPeriod) : base(folderPath, retentionPeriod) { } - public override void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { + public override async Task Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { if (!IsEnabled(logLevel)) return; @@ -13,12 +13,12 @@ public class FileLogger : BaseFileLogger { if (string.IsNullOrEmpty(message)) return; - var logRecord = $"{DateTime.UtcNow.ToString("o")} [{logLevel}] {message}"; + var logRecord = $"{DateTime.UtcNow.ToString("o")}" + $" [{logLevel}] {message}"; if (exception != null) { logRecord += Environment.NewLine + exception; } var logFileName = GenerateLogFileName("txt"); - AppendToLogFile(logFileName, logRecord + Environment.NewLine); + await AppendToLogFileAsync(logFileName, logRecord + Environment.NewLine); } } diff --git a/src/MaksIT.Core/Logging/JsonFileLogger.cs b/src/MaksIT.Core/Logging/JsonFileLogger.cs index efda238..700764b 100644 --- a/src/MaksIT.Core/Logging/JsonFileLogger.cs +++ b/src/MaksIT.Core/Logging/JsonFileLogger.cs @@ -6,7 +6,7 @@ namespace MaksIT.Core.Logging; public class JsonFileLogger : BaseFileLogger { public JsonFileLogger(string folderPath, TimeSpan retentionPeriod) : base(folderPath, retentionPeriod) { } - public override void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { + public override async Task Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { if (!IsEnabled(logLevel)) return; @@ -18,6 +18,6 @@ public class JsonFileLogger : BaseFileLogger { }; var logFileName = GenerateLogFileName("json"); - AppendToLogFile(logFileName, JsonSerializer.Serialize(logEntry) + Environment.NewLine); + await AppendToLogFileAsync(logFileName, JsonSerializer.Serialize(logEntry) + Environment.NewLine); } } \ No newline at end of file diff --git a/src/MaksIT.Core/MaksIT.Core.csproj b/src/MaksIT.Core/MaksIT.Core.csproj index 676529d..dc355fd 100644 --- a/src/MaksIT.Core/MaksIT.Core.csproj +++ b/src/MaksIT.Core/MaksIT.Core.csproj @@ -8,7 +8,7 @@ MaksIT.Core - 1.5.9 + 1.6.0 Maksym Sadovnychyy MAKS-IT MaksIT.Core