diff --git a/EonaCat.Logger.LogClient/EonaCat.Logger.LogClient.csproj b/EonaCat.Logger.LogClient/EonaCat.Logger.LogClient.csproj
index edc9919..8eb2711 100644
--- a/EonaCat.Logger.LogClient/EonaCat.Logger.LogClient.csproj
+++ b/EonaCat.Logger.LogClient/EonaCat.Logger.LogClient.csproj
@@ -25,7 +25,7 @@
-
+
diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj
index af69bce..e2a2947 100644
--- a/EonaCat.Logger/EonaCat.Logger.csproj
+++ b/EonaCat.Logger/EonaCat.Logger.csproj
@@ -13,8 +13,8 @@
EonaCat (Jeroen Saey)
EonaCat;Logger;EonaCatLogger;Log;Writer;Jeroen;Saey
- 1.6.9
- 1.6.9
+ 1.7.0
+ 1.7.0
README.md
True
LICENSE
@@ -70,11 +70,11 @@
-
-
-
+
+
+
-
+
diff --git a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
index 98da27d..87f2db7 100644
--- a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
+++ b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
@@ -4,6 +4,7 @@ using EonaCat.Logger.Managers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
+using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -13,13 +14,14 @@ using System.Threading;
using System.Threading.Tasks;
[ProviderAlias("EonaCatFileLogger")]
-public sealed class FileLoggerProvider : BatchingLoggerProvider
+public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
{
private readonly string _path;
private readonly string _fileNamePrefix;
private readonly int _maxFileSize;
private readonly int _maxRetainedFiles;
private readonly int _maxRolloverFiles;
+ private bool _disposed;
private readonly LoggerScopedContext _context = new LoggerScopedContext();
private readonly ConcurrentDictionary _files = new();
@@ -39,17 +41,45 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
private readonly Timer _flushTimer;
private readonly TimeSpan _flushInterval = TimeSpan.FromMilliseconds(500);
- private sealed class FileState
+ private sealed class FileState : IDisposable
{
public string FilePath;
public long Size;
public DateTime Date;
- public byte[] Buffer = new byte[BufferSize];
+ public byte[] Buffer = ArrayPool.Shared.Rent(BufferSize);
public int BufferPosition;
public FileStream Stream;
- public SemaphoreSlim WriteLock = new(1, 1); // async safe
+ public SemaphoreSlim WriteLock = new(1, 1);
+
+ public void Dispose()
+ {
+ try
+ {
+ if (Buffer != null)
+ {
+ ArrayPool.Shared.Return(Buffer);
+ Buffer = null;
+ }
+
+ Stream?.Dispose();
+ }
+ catch
+ {
+ // Do nothing
+ }
+
+ try
+ {
+ WriteLock?.Dispose();
+ }
+ catch
+ {
+ // Do nothing
+ }
+ }
}
+
public FileLoggerProvider(IOptions options) : base(options)
{
var o = options.Value ?? throw new ArgumentNullException(nameof(options));
@@ -68,7 +98,42 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
_files[string.Empty] = defaultState;
// Periodic flush
- _flushTimer = new Timer(_ => PeriodicFlushAsync().ConfigureAwait(false), null, _flushInterval, _flushInterval);
+ _flushTimer = new Timer(FlushTimerCallback, null, _flushInterval, _flushInterval);
+ }
+
+ private void FlushTimerCallback(object state)
+ {
+ try
+ {
+ PeriodicFlushAsync().GetAwaiter().GetResult();
+ }
+ catch
+ {
+ // swallow - avoid timer thread crash
+ }
+ }
+
+ private void CleanupUnusedCategories()
+ {
+ var now = DateTime.UtcNow;
+
+ foreach (var kv in _files.ToArray())
+ {
+ var state = kv.Value;
+
+ // Remove file states older than 2 days and empty queues
+ if ((now - state.Date).TotalDays > 2 &&
+ _messageQueues.TryGetValue(kv.Key, out var queue) &&
+ queue.IsEmpty)
+ {
+ if (_files.TryRemove(kv.Key, out var removed))
+ {
+ removed.Dispose();
+ }
+
+ _messageQueues.TryRemove(kv.Key, out _);
+ }
+ }
}
internal override Task WriteMessagesAsync(IReadOnlyList messages, CancellationToken token)
@@ -118,22 +183,15 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
_files[key] = state;
}
- var messagesToWrite = new List();
- while (queue.TryDequeue(out var msg))
- {
- messagesToWrite.Add(msg);
- }
-
- if (messagesToWrite.Count > 0)
+ if (!queue.IsEmpty)
{
+ await state.WriteLock.WaitAsync();
try
{
- await state.WriteLock.WaitAsync();
- WriteBatch(state, messagesToWrite, key);
- }
- catch (Exception ex)
- {
- OnError?.Invoke(this, new ErrorMessage { Exception = ex, Message = ex.Message });
+ while (queue.TryDequeue(out var msg))
+ {
+ WriteBatch(state, new[] { msg }, key);
+ }
}
finally
{
@@ -143,6 +201,7 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
}
DeleteOldLogFiles();
+ CleanupUnusedCategories();
}
private void WriteBatch(FileState state, IEnumerable messages, string categoryKey)
@@ -376,22 +435,33 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
protected override void OnShutdownFlush()
{
_flushTimer?.Dispose();
- PeriodicFlushAsync().GetAwaiter().GetResult();
+
+ try
+ {
+ PeriodicFlushAsync().GetAwaiter().GetResult();
+ }
+ catch
+ {
+ // Do nothing
+ }
foreach (var state in _files.Values)
{
try
{
FlushBufferAsync(state).GetAwaiter().GetResult();
- state.Stream?.Dispose();
}
catch
{
// Do nothing
}
+
+ state.Dispose();
}
_files.Clear();
_messageQueues.Clear();
+
+ base.OnShutdownFlush();
}
}