Updated
This commit is contained in:
@@ -25,7 +25,7 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Net.Http.Json" Version="10.0.1" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EonaCat.Logger\EonaCat.Logger.csproj" />
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
<Copyright>EonaCat (Jeroen Saey)</Copyright>
|
||||
<PackageTags>EonaCat;Logger;EonaCatLogger;Log;Writer;Jeroen;Saey</PackageTags>
|
||||
<PackageIconUrl />
|
||||
<Version>1.6.9</Version>
|
||||
<FileVersion>1.6.9</FileVersion>
|
||||
<Version>1.7.0</Version>
|
||||
<FileVersion>1.7.0</FileVersion>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
@@ -70,11 +70,11 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="EonaCat.Versioning.Helpers" Version="1.0.2" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.3" />
|
||||
<PackageReference Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="10.0.1" />
|
||||
<PackageReference Include="System.Threading.Channels" Version="10.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="LICENSE.md">
|
||||
|
||||
@@ -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<string, FileState> _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<byte>.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<byte>.Shared.Return(Buffer);
|
||||
Buffer = null;
|
||||
}
|
||||
|
||||
Stream?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
WriteLock?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public FileLoggerProvider(IOptions<FileLoggerOptions> 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<LogMessage> messages, CancellationToken token)
|
||||
@@ -118,22 +183,15 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
||||
_files[key] = state;
|
||||
}
|
||||
|
||||
var messagesToWrite = new List<LogMessage>();
|
||||
while (queue.TryDequeue(out var msg))
|
||||
{
|
||||
messagesToWrite.Add(msg);
|
||||
}
|
||||
|
||||
if (messagesToWrite.Count > 0)
|
||||
{
|
||||
try
|
||||
if (!queue.IsEmpty)
|
||||
{
|
||||
await state.WriteLock.WaitAsync();
|
||||
WriteBatch(state, messagesToWrite, key);
|
||||
}
|
||||
catch (Exception ex)
|
||||
try
|
||||
{
|
||||
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<LogMessage> messages, string categoryKey)
|
||||
@@ -376,22 +435,33 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider
|
||||
protected override void OnShutdownFlush()
|
||||
{
|
||||
_flushTimer?.Dispose();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user