(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 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<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) {
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();
}
}

View File

@ -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<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))
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);
}
}

View File

@ -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<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))
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);
}
}

View File

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