(bugfix): fillelogger multithreading issue

This commit is contained in:
Maksym Sadovnychyy 2025-12-04 20:31:18 +01:00
parent 9f9d5fc474
commit 016904f333
4 changed files with 33 additions and 14 deletions

View File

@ -1,14 +1,14 @@
using System;
using System.IO;
using MaksIT.Core.Threading; using MaksIT.Core.Threading;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace MaksIT.Core.Logging; namespace MaksIT.Core.Logging;
public abstract class BaseFileLogger : ILogger, IDisposable { public abstract class BaseFileLogger : ILogger, IDisposable {
private readonly LockManager _lockManager = new LockManager(); private readonly LockManager _lockManager = new LockManager();
private readonly string _folderPath; private readonly string _folderPath;
private readonly TimeSpan _retentionPeriod; 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) { protected BaseFileLogger(string folderPath, TimeSpan retentionPeriod) {
_folderPath = folderPath; _folderPath = folderPath;
@ -22,17 +22,30 @@ public abstract class BaseFileLogger : ILogger, IDisposable {
return logLevel != LogLevel.None; return logLevel != LogLevel.None;
} }
public abstract void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter); public abstract Task Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter);
// Explicit interface implementation for ILogger.Log (void)
void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) {
// Call the async Log and wait synchronously for compatibility
Log(logLevel, eventId, state, exception, formatter).GetAwaiter().GetResult();
}
protected string GenerateLogFileName(string extension) { protected string GenerateLogFileName(string extension) {
return Path.Combine(_folderPath, $"log_{DateTime.UtcNow:yyyy-MM-dd}.{extension}"); return Path.Combine(_folderPath, $"log_{DateTime.UtcNow:yyyy-MM-dd}.{extension}");
} }
protected void AppendToLogFile(string logFileName, string content) { protected Task AppendToLogFileAsync(string logFileName, string content) {
_lockManager.ExecuteWithLockAsync(async () => { bool mutexAcquired = false;
await File.AppendAllTextAsync(logFileName, content); try {
RemoveExpiredLogFiles(Path.GetExtension(logFileName)); mutexAcquired = _fileMutex.WaitOne(10000);
}).Wait(); 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) { private void RemoveExpiredLogFiles(string extension) {
@ -53,5 +66,11 @@ public abstract class BaseFileLogger : ILogger, IDisposable {
public void Dispose() { public void Dispose() {
_lockManager.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();
} }
} }

View File

@ -5,7 +5,7 @@ namespace MaksIT.Core.Logging;
public class FileLogger : BaseFileLogger { public class FileLogger : BaseFileLogger {
public FileLogger(string folderPath, TimeSpan retentionPeriod) : base(folderPath, retentionPeriod) { } public FileLogger(string folderPath, TimeSpan retentionPeriod) : base(folderPath, retentionPeriod) { }
public override void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) { public override async Task Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) {
if (!IsEnabled(logLevel)) if (!IsEnabled(logLevel))
return; return;
@ -13,12 +13,12 @@ public class FileLogger : BaseFileLogger {
if (string.IsNullOrEmpty(message)) if (string.IsNullOrEmpty(message))
return; return;
var logRecord = $"{DateTime.UtcNow.ToString("o")} [{logLevel}] {message}"; var logRecord = $"{DateTime.UtcNow.ToString("o")}" + $" [{logLevel}] {message}";
if (exception != null) { if (exception != null) {
logRecord += Environment.NewLine + exception; logRecord += Environment.NewLine + exception;
} }
var logFileName = GenerateLogFileName("txt"); var logFileName = GenerateLogFileName("txt");
AppendToLogFile(logFileName, logRecord + Environment.NewLine); await AppendToLogFileAsync(logFileName, logRecord + Environment.NewLine);
} }
} }

View File

@ -6,7 +6,7 @@ namespace MaksIT.Core.Logging;
public class JsonFileLogger : BaseFileLogger { public class JsonFileLogger : BaseFileLogger {
public JsonFileLogger(string folderPath, TimeSpan retentionPeriod) : base(folderPath, retentionPeriod) { } public JsonFileLogger(string folderPath, TimeSpan retentionPeriod) : base(folderPath, retentionPeriod) { }
public override void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) { public override async Task Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) {
if (!IsEnabled(logLevel)) if (!IsEnabled(logLevel))
return; return;
@ -18,6 +18,6 @@ public class JsonFileLogger : BaseFileLogger {
}; };
var logFileName = GenerateLogFileName("json"); var logFileName = GenerateLogFileName("json");
AppendToLogFile(logFileName, JsonSerializer.Serialize(logEntry) + Environment.NewLine); await AppendToLogFileAsync(logFileName, JsonSerializer.Serialize(logEntry) + Environment.NewLine);
} }
} }

View File

@ -8,7 +8,7 @@
<!-- NuGet package metadata --> <!-- NuGet package metadata -->
<PackageId>MaksIT.Core</PackageId> <PackageId>MaksIT.Core</PackageId>
<Version>1.5.9</Version> <Version>1.6.0</Version>
<Authors>Maksym Sadovnychyy</Authors> <Authors>Maksym Sadovnychyy</Authors>
<Company>MAKS-IT</Company> <Company>MAKS-IT</Company>
<Product>MaksIT.Core</Product> <Product>MaksIT.Core</Product>