diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj
index d564952..30763dd 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.7.4
- 1.7.4
+ 1.7.5
+ 1.7.5
README.md
True
LICENSE
@@ -25,7 +25,7 @@
- 1.7.4+{chash:10}.{c:ymd}
+ 1.7.5+{chash:10}.{c:ymd}
true
true
v[0-9]*
diff --git a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
index e899156..5455694 100644
--- a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
+++ b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
@@ -1,12 +1,10 @@
using EonaCat.Logger;
using EonaCat.Logger.EonaCatCoreLogger;
using EonaCat.Logger.EonaCatCoreLogger.Internal;
-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;
using System.IO.Compression;
@@ -20,350 +18,150 @@ using System.Threading.Tasks;
[ProviderAlias("EonaCatFileLogger")]
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 const int BufferSize = 64 * 1024;
+ private const int ChannelCapacity = 8192;
+ private const int FlushThreshold = 48 * 1024;
- private readonly byte[] _encryptionKey;
- private readonly byte[] _encryptionIV;
- private readonly bool _isEncryptionEnabled;
-
- private const int MaxQueueSize = 500;
- private const int BufferSize = 16 * 1024;
- private const int MaxStringBuilderPool = 4;
- private const int MaxStringBuilderCapacity = 512;
- private const int InitialStringBuilderCapacity = 256;
- private const int MaxSanitizedCacheSize = 256;
-
- private static readonly Encoding Utf8 = new UTF8Encoding(false);
-
- private readonly LoggerScopedContext _context = new();
-
- private readonly ConcurrentDictionary _files = new(1, 4, StringComparer.Ordinal);
- private readonly ConcurrentQueue _compressionQueue = new();
- private readonly SemaphoreSlim _compressionSemaphore = new(1, 1);
- private readonly CancellationTokenSource _compressionCts = new();
- private Task _compressionWorker;
-
- private static readonly object _sanitizedCacheLock = new object();
- private static readonly Dictionary _sanitizedCache = new(StringComparer.Ordinal);
- private static readonly Queue _sanitizedCacheOrder = new();
+ private static readonly UTF8Encoding Utf8 = new(false);
private readonly Channel _channel;
- private readonly Task _backgroundWorker;
+ private readonly Thread _writerThread;
- private readonly ConcurrentBag _sbPool = new();
- private int _sbPoolCount;
+ private readonly string _filePath;
+ private readonly int _maxFileSize;
+ private readonly bool _encryptionEnabled;
- private Timer _flushTimer;
- private readonly TimeSpan _flushInterval = TimeSpan.FromMilliseconds(500);
-
- private int _disposed;
- private Aes _aes;
-
- private readonly ThreadLocal _encryptor = new ThreadLocal(() => null, trackAllValues: true);
- private readonly object _aesLock = new object();
+ private readonly Aes _aes;
+ private readonly ICryptoTransform _encryptor;
public bool IncludeCorrelationId { get; }
public bool EnableCategoryRouting { get; }
+
+ private FileStream _stream;
+ private byte[] _buffer;
+ private int _position;
+ private long _size;
+
+ public string LogFile => _filePath;
+ private volatile bool _running = true;
+ private readonly int _maxRolloverFiles;
+
public ELogType MinimumLogLevel { get; set; }
-
- public event EventHandler OnError;
- public event EventHandler OnRollOver;
-
- public string LogFile => _files.TryGetValue(string.Empty, out var state) ? state.FilePath : null;
+ private readonly LoggerScopedContext _context = new();
public FileLoggerProvider(IOptions options) : base(options)
{
- var o = options.Value ?? throw new ArgumentNullException(nameof(options));
+ var o = options.Value;
+
+ _filePath = Path.Combine(o.LogDirectory,
+ $"{o.FileNamePrefix}_{Environment.MachineName}_{DateTime.UtcNow:yyyyMMdd}.log");
+
+ Directory.CreateDirectory(o.LogDirectory);
- _path = EnsureWritableDirectory(o.LogDirectory);
- _fileNamePrefix = o.FileNamePrefix;
_maxFileSize = o.FileSizeLimit;
- _maxRetainedFiles = o.RetainedFileCountLimit;
_maxRolloverFiles = o.MaxRolloverFiles;
- IncludeCorrelationId = o.IncludeCorrelationId;
- EnableCategoryRouting = o.EnableCategoryRouting;
- MinimumLogLevel = o.MinimumLogLevel;
- _encryptionKey = o.EncryptionKey;
- _encryptionIV = o.EncryptionIV;
- _isEncryptionEnabled = _encryptionKey != null && _encryptionIV != null;
+ _encryptionEnabled = o.EncryptionKey != null && o.EncryptionIV != null;
- if (_isEncryptionEnabled)
+ if (_encryptionEnabled)
{
_aes = Aes.Create();
- _aes.Key = _encryptionKey;
- _aes.IV = _encryptionIV;
+ _aes.Key = o.EncryptionKey;
+ _aes.IV = o.EncryptionIV;
+ _encryptor = _aes.CreateEncryptor();
}
- var defaultState = CreateFileState(DateTime.UtcNow.Date, string.Empty);
- _files[string.Empty] = defaultState;
+ IncludeCorrelationId = o.IncludeCorrelationId;
+ EnableCategoryRouting = o.EnableCategoryRouting;
- var channelOptions = new BoundedChannelOptions(MaxQueueSize)
- {
- SingleReader = true,
- SingleWriter = false,
- FullMode = BoundedChannelFullMode.Wait,
- AllowSynchronousContinuations = false
- };
- _channel = Channel.CreateBounded(channelOptions);
+ _stream = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, 1, FileOptions.WriteThrough | FileOptions.SequentialScan);
+ _size = _stream.Length;
+ _buffer = ArrayPool.Shared.Rent(BufferSize);
- _backgroundWorker = Task.Run(ProcessQueueAsync);
- _flushTimer = new Timer(FlushTimerCallback, null, _flushInterval, _flushInterval);
- _compressionWorker = Task.Run(() => CompressionWorkerAsync(_compressionCts.Token));
- }
-
- private string EnsureWritableDirectory(string path)
- {
- string fallback = Path.Combine(Path.GetTempPath(), "EonaCatFallbackLogs");
- foreach (var dir in new[] { path, fallback })
- {
- try
+ _channel = Channel.CreateBounded(
+ new BoundedChannelOptions(ChannelCapacity)
{
- Directory.CreateDirectory(dir);
- string testFile = Path.Combine(dir, $"write_test_{Guid.NewGuid()}.tmp");
- File.WriteAllText(testFile, "test");
- File.Delete(testFile);
- return dir;
- }
- catch { }
- }
- Directory.CreateDirectory(fallback);
- return fallback;
- }
+ SingleReader = true,
+ SingleWriter = false,
+ FullMode = BoundedChannelFullMode.Wait
+ });
- private void FlushTimerCallback(object state)
- {
- if (_disposed == 1)
+ _writerThread = new Thread(WriterLoop)
{
- return;
- }
+ IsBackground = true,
+ Priority = ThreadPriority.AboveNormal
+ };
- // Synchronous flush on timer to avoid task buildup
- foreach (var f in _files.Values)
- {
- FlushBufferSync(f);
- }
+ _writerThread.Start();
}
internal override Task WriteMessagesAsync(IReadOnlyList messages, CancellationToken token)
{
for (int i = 0; i < messages.Count; i++)
{
- var msg = messages[i];
- if (msg.Level >= MinimumLogLevel)
+ var message = messages[i];
+ if (message.Level >= MinimumLogLevel)
{
- // TryWrite waits until it can write
- _channel.Writer.TryWrite(msg);
+ while (!_channel.Writer.TryWrite(message))
+ {
+ Thread.SpinWait(1);
+ }
}
}
return Task.CompletedTask;
}
- private async Task ProcessQueueAsync()
+ private void WriterLoop()
{
- try
+ var reader = _channel.Reader;
+ var spin = new SpinWait();
+
+ while (_running || reader.Count > 0)
{
- await foreach (var msg in _channel.Reader.ReadAllAsync())
+ while (reader.TryRead(out var message))
{
- await ProcessSingleMessageAsync(msg);
+ WriteMessage(message);
}
+
+ FlushIfNeeded();
+
+ spin.SpinOnce();
}
- catch (OperationCanceledException)
- {
- // Expected on shutdown
- }
+
+ FlushFinal();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private async Task ProcessSingleMessageAsync(LogMessage msg)
+ private void FlushIfNeeded()
{
- var key = EnableCategoryRouting ? GetOrCreateSanitizedCategory(msg.Category) : string.Empty;
-
- if (!_files.TryGetValue(key, out var state))
+ if (_position >= FlushThreshold)
{
- state = CreateFileState(DateTime.UtcNow.Date, key);
- _files[key] = state;
+ FlushInternal();
}
+ }
- if (!TryRecover(state))
+ private void FlushInternal()
+ {
+ if (_position == 0)
{
return;
}
- await state.WriteLock.WaitAsync();
- try
- {
- await WriteMessageAsync(state, msg);
- }
- finally
- {
- state.WriteLock.Release();
- }
+ _stream.Write(_buffer, 0, _position);
+ _position = 0;
}
- private async Task WriteMessageAsync(FileState state, LogMessage msg)
+ private void FlushFinal()
{
- var sb = RentStringBuilder();
-
- try
- {
- BuildMessageInto(sb, msg);
- var text = sb.ToString();
-
- // Size the array precisely
- int maxByteCount = Utf8.GetMaxByteCount(text.Length);
- byte[] rentedBytes = ArrayPool.Shared.Rent(maxByteCount);
-
- try
- {
- int actualByteCount = Utf8.GetBytes(text, 0, text.Length, rentedBytes, 0);
-
- byte[] finalBytes = rentedBytes;
- int finalLength = actualByteCount;
-
- if (_isEncryptionEnabled)
- {
- byte[] encrypted = EncryptFast(rentedBytes, actualByteCount);
- finalBytes = encrypted;
- finalLength = encrypted.Length;
- ArrayPool.Shared.Return(rentedBytes, clearArray: true);
- rentedBytes = null;
- }
-
- if (state.Buffer == null)
- {
- state.Buffer = ArrayPool.Shared.Rent(BufferSize);
- }
-
- // Flush if needed
- if (state.BufferPosition + finalLength > BufferSize)
- {
- await FlushBufferAsync(state).ConfigureAwait(false);
- }
-
- // Handle file size limit
- if (_maxFileSize > 0 && state.Size + finalLength > _maxFileSize)
- {
- await FlushBufferAsync(state).ConfigureAwait(false);
- RollOverAndCompressOldest(state, string.Empty);
- }
-
- // Write to buffer
- Buffer.BlockCopy(finalBytes, 0, state.Buffer, state.BufferPosition, finalLength);
- state.BufferPosition += finalLength;
- state.Size += finalLength;
- }
- finally
- {
- if (rentedBytes != null)
- {
- ArrayPool.Shared.Return(rentedBytes, clearArray: true);
- }
- }
-
- }
- finally
- {
- ReturnStringBuilder(sb);
- }
+ FlushInternal();
+ _stream.Flush(true);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private byte[] EncryptFast(byte[] plainBytes, int length)
+ private void WriteMessage(LogMessage msg)
{
- var encryptor = _encryptor.Value;
- if (encryptor == null)
- {
- lock (_aesLock)
- {
- encryptor = _aes?.CreateEncryptor();
- _encryptor.Value = encryptor;
- }
- }
- return encryptor.TransformFinalBlock(plainBytes, 0, length);
- }
-
- private void FlushBufferSync(FileState state)
- {
- if (state.BufferPosition == 0 || state.Stream == null)
- {
- return;
- }
-
- try
- {
- state.Stream.Write(state.Buffer, 0, state.BufferPosition);
- state.Stream.Flush();
- }
- catch (Exception ex)
- {
- HandleWriteFailure(state, ex);
- }
- finally
- {
- state.BufferPosition = 0;
- }
- }
-
- private async Task FlushBufferAsync(FileState state)
- {
- if (state.BufferPosition == 0 || state.Stream == null)
- {
- return;
- }
-
- try
- {
- await state.Stream.WriteAsync(state.Buffer, 0, state.BufferPosition).ConfigureAwait(false);
- await state.Stream.FlushAsync().ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- HandleWriteFailure(state, ex);
- }
- finally
- {
- state.BufferPosition = 0;
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private StringBuilder RentStringBuilder()
- {
- if (_sbPool.TryTake(out var sb))
- {
- sb.Clear();
- Interlocked.Decrement(ref _sbPoolCount);
- return sb;
- }
- return new StringBuilder(InitialStringBuilderCapacity);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void ReturnStringBuilder(StringBuilder sb)
- {
- if (sb.Capacity > MaxStringBuilderCapacity)
- {
- return; // Don't pool oversized builders
- }
-
- if (_sbPoolCount < MaxStringBuilderPool)
- {
- _sbPool.Add(sb);
- Interlocked.Increment(ref _sbPoolCount);
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void BuildMessageInto(StringBuilder sb, LogMessage msg)
- {
- sb.Clear();
- sb.Append(msg.Message);
-
+ StringBuilder sb = new StringBuilder();
+
if (IncludeCorrelationId)
{
var ctx = _context.GetAll();
@@ -398,332 +196,140 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
}
}
- sb.Append(Environment.NewLine);
- }
-
- private void HandleWriteFailure(FileState state, Exception ex)
- {
- state.IsFaulted = true;
- state.LastFailureUtc = DateTime.UtcNow;
-
- state.Stream?.Dispose();
- state.Stream = null;
-
- try
+ string correlationId = null;
+ if (IncludeCorrelationId)
{
- string fallbackFile = Path.Combine(_path, Path.GetFileName(state.FilePath));
- state.Stream = new FileStream(
- fallbackFile,
- FileMode.Append,
- FileAccess.Write,
- FileShare.ReadWrite | FileShare.Delete,
- 4096,
- FileOptions.Asynchronous | FileOptions.SequentialScan);
- state.FilePath = fallbackFile;
- state.Size = GetFileSize(fallbackFile);
- state.IsFaulted = false;
-
- OnError?.Invoke(this, new ErrorMessage { Exception = ex, Message = $"Switched to fallback path: {fallbackFile}" });
+ correlationId = _context.Get("CorrelationId") ?? Guid.NewGuid().ToString();
+ _context.Set("CorrelationId", correlationId);
}
- catch (Exception fallbackEx)
+
+ string text = sb.ToString() + ' ' + msg.Message + Environment.NewLine;
+
+ int max = Utf8.GetMaxByteCount(text.Length);
+ byte[] temp = ArrayPool.Shared.Rent(max);
+
+ int bytes = Utf8.GetBytes(text, 0, text.Length, temp, 0);
+
+ byte[] final = temp;
+ int length = bytes;
+
+ if (_encryptionEnabled)
{
- OnError?.Invoke(this, new ErrorMessage { Exception = fallbackEx, Message = "Failed to recover logging using fallback path" });
+ final = _encryptor.TransformFinalBlock(temp, 0, bytes);
+ length = final.Length;
+ ArrayPool.Shared.Return(temp, true);
+ temp = null;
+ }
+
+ if (_position + length > BufferSize)
+ {
+ FlushInternal();
+ }
+
+ Buffer.BlockCopy(final, 0, _buffer, _position, length);
+ _position += length;
+ _size += length;
+
+ if (_maxFileSize > 0 && _size >= _maxFileSize)
+ {
+ RollFile();
+ }
+
+ if (temp != null)
+ {
+ ArrayPool.Shared.Return(temp, true);
}
}
- private bool TryRecover(FileState state)
+ private readonly object _rollLock = new();
+
+ private void RollFile()
{
- if (!state.IsFaulted)
+ lock (_rollLock)
{
- return true;
- }
+ FlushInternal();
- if (DateTime.UtcNow - state.LastFailureUtc < TimeSpan.FromSeconds(60))
- {
- return false;
- }
+ _stream?.Dispose();
+ _stream = null;
- try
- {
- state.Stream = new FileStream(
- state.FilePath,
- FileMode.Append,
- FileAccess.Write,
- FileShare.ReadWrite | FileShare.Delete,
- 4096,
- FileOptions.Asynchronous | FileOptions.SequentialScan);
- state.Size = GetFileSize(state.FilePath);
- state.IsFaulted = false;
- return true;
+ RotateCompressedFiles();
+
+ string tempArchive = _filePath + ".rolling";
+
+ File.Move(_filePath, tempArchive);
+
+ CompressToIndex(tempArchive, 1);
+
+ _stream = new FileStream(_filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.SequentialScan);
+ _size = 0;
}
- catch { return false; }
}
-
- private FileState CreateFileState(DateTime date, string category)
+ private void RotateCompressedFiles()
{
- var path = GetFullName(date, category);
- try
+ int maxFiles = _maxRolloverFiles;
+
+ string directory = Path.GetDirectoryName(_filePath)!;
+ string nameWithoutExt = Path.GetFileNameWithoutExtension(_filePath);
+ string extension = Path.GetExtension(_filePath); // .log
+
+ for (int i = maxFiles; i >= 1; i--)
{
- return new FileState
+ string current = Path.Combine(directory, $"{nameWithoutExt}_{i}{extension}.gz");
+
+ if (!File.Exists(current))
{
- FilePath = path,
- Date = date,
- Size = GetFileSize(path),
- Buffer = ArrayPool.Shared.Rent(BufferSize),
- Stream = new FileStream(
- path,
- FileMode.Append,
- FileAccess.Write,
- FileShare.ReadWrite | FileShare.Delete,
- 4096,
- FileOptions.Asynchronous | FileOptions.SequentialScan),
- WriteLock = new SemaphoreSlim(1, 1)
- };
- }
- catch (Exception ex)
- {
- OnError?.Invoke(this, new ErrorMessage { Exception = ex, Message = $"Failed to create log file: {path}" });
- return new FileState
- {
- FilePath = path,
- Date = date,
- Buffer = ArrayPool.Shared.Rent(BufferSize),
- IsFaulted = true,
- WriteLock = new SemaphoreSlim(1, 1)
- };
- }
- }
-
- private string GetFullName(DateTime date, string category)
- {
- var datePart = date.ToString("yyyyMMdd");
- var machine = Environment.MachineName;
-
- if (!EnableCategoryRouting || string.IsNullOrWhiteSpace(category))
- {
- return Path.Combine(_path, $"{_fileNamePrefix}_{machine}_{datePart}.log");
- }
-
- var safeCategory = GetOrCreateSanitizedCategory(category);
- return Path.Combine(_path, $"{_fileNamePrefix}_{machine}_{safeCategory}_{datePart}.log");
- }
-
- private static string GetOrCreateSanitizedCategory(string category)
- {
- if (string.IsNullOrEmpty(category))
- {
- return string.Empty;
- }
-
- lock (_sanitizedCacheLock)
- {
- if (_sanitizedCache.TryGetValue(category, out var cached))
- {
- return cached;
+ continue;
}
- var sanitized = SanitizeCategory(category);
-
- // Simple LRU: evict oldest if cache exceeds max size
- if (_sanitizedCache.Count >= MaxSanitizedCacheSize)
+ if (i == maxFiles)
{
- var oldestKey = _sanitizedCacheOrder.Dequeue();
- _sanitizedCache.Remove(oldestKey);
+ File.Delete(current);
}
-
- _sanitizedCache[category] = sanitized;
- _sanitizedCacheOrder.Enqueue(category);
- return sanitized;
- }
- }
-
- private static string SanitizeCategory(string category)
- {
- if (string.IsNullOrEmpty(category))
- {
- return category;
- }
-
- var chars = category.ToCharArray();
- var invalid = Path.GetInvalidFileNameChars();
- bool modified = false;
-
- for (int i = 0; i < chars.Length; i++)
- {
- if (Array.IndexOf(invalid, chars[i]) >= 0 || chars[i] == '.')
+ else
{
- chars[i] = '_';
- modified = true;
- }
- }
+ string next = Path.Combine(directory, $"{nameWithoutExt}_{i + 1}{extension}.gz");
- return modified ? new string(chars) : category;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static long GetFileSize(string path) => File.Exists(path) ? new FileInfo(path).Length : 0;
-
- private void RollOverAndCompressOldest(FileState state, string category)
- {
- if (state.Stream != null)
- {
- state.Stream.Flush();
- state.Stream.Dispose();
- state.Stream = null;
- }
-
- var dir = Path.GetDirectoryName(state.FilePath);
- var name = Path.GetFileNameWithoutExtension(state.FilePath);
- var ext = Path.GetExtension(state.FilePath);
-
- for (int i = _maxRolloverFiles - 1; i >= 1; i--)
- {
- var src = Path.Combine(dir, $"{name}_{i}{ext}");
- var dst = Path.Combine(dir, $"{name}_{i + 1}{ext}");
- try
- {
- if (File.Exists(dst))
+ if (File.Exists(next))
{
- File.Delete(dst);
+ File.Delete(next);
}
- if (File.Exists(src))
- {
- File.Move(src, dst);
- }
- }
- catch
- {
- // Ignore rollover failures
- }
- }
-
- var rolledFile = Path.Combine(dir, $"{name}_1{ext}");
- try
- {
- if (File.Exists(state.FilePath))
- {
- File.Move(state.FilePath, rolledFile);
- }
-
- OnRollOver?.Invoke(this, rolledFile);
- }
- catch
- {
- // Ignore rollover failures
- }
-
- state.Size = 0;
- state.Stream = new FileStream(
- state.FilePath,
- FileMode.Create,
- FileAccess.Write,
- FileShare.ReadWrite | FileShare.Delete,
- 4096,
- FileOptions.Asynchronous | FileOptions.SequentialScan);
-
- var oldestFile = Path.Combine(dir, $"{name}_{_maxRolloverFiles}{ext}");
- if (File.Exists(oldestFile))
- {
- _compressionQueue.Enqueue(oldestFile);
- }
- }
-
- private async Task CompressionWorkerAsync(CancellationToken token)
- {
- while (!token.IsCancellationRequested)
- {
- try
- {
- await Task.Delay(1000, token).ConfigureAwait(false);
-
- while (_compressionQueue.TryDequeue(out var file))
- {
- await _compressionSemaphore.WaitAsync(token);
- try
- {
- await CompressOldLogFileAsync(file);
- }
- catch
- {
- // Silently fail compression
- }
- finally
- {
- _compressionSemaphore.Release();
- }
- }
- }
- catch (OperationCanceledException)
- {
- break;
+ File.Move(current, next);
}
}
}
- private async Task CompressOldLogFileAsync(string filePath)
+
+ private void CompressToIndex(string sourceFile, int index)
{
- if (!File.Exists(filePath) || filePath.EndsWith(".gz"))
+ string directory = Path.GetDirectoryName(_filePath)!;
+ string nameWithoutExtension = Path.GetFileNameWithoutExtension(_filePath);
+ string extension = Path.GetExtension(_filePath); // .log
+
+ // destination uses the same "_index" before .log + add .gz
+ string destination = Path.Combine(directory, $"{nameWithoutExtension}_{index}{extension}.gz");
+
+ using (var input = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.Read))
+ using (var output = new FileStream(destination, FileMode.Create, FileAccess.Write, FileShare.None))
+ using (var gzip = new GZipStream(output, CompressionLevel.Fastest))
{
- return;
+ input.CopyTo(gzip);
}
- var compressedFile = filePath + ".gz";
-
- try
- {
- using (var original = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 8192, FileOptions.Asynchronous | FileOptions.SequentialScan))
- using (var compressed = new FileStream(compressedFile, FileMode.Create, FileAccess.Write, FileShare.None, 8192, FileOptions.Asynchronous | FileOptions.SequentialScan))
- using (var gzip = new GZipStream(compressed, CompressionLevel.Fastest)) // Fastest compression for lower memory usage
- {
- await original.CopyToAsync(gzip, 8192).ConfigureAwait(false);
- }
-
- File.Delete(filePath);
- }
- catch
- {
- // Do nothing
- }
+ File.Delete(sourceFile);
}
- protected override async Task OnShutdownFlushAsync()
+
+ protected override Task OnShutdownFlushAsync()
{
- if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
- {
- return;
- }
-
- _flushTimer?.Dispose();
- _flushTimer = null;
-
- try { _compressionCts.Cancel(); } catch { }
+ _running = false;
_channel.Writer.Complete();
+ _writerThread.Join();
- try
- {
- await _backgroundWorker.ConfigureAwait(false);
- }
- catch
- {
- // Ignore
- }
-
- foreach (var state in _files.Values)
- {
- try { await FlushBufferAsync(state).ConfigureAwait(false); } catch { }
- try { state.Dispose(); } catch { }
- }
-
- _files.Clear();
-
- while (_sbPool.TryTake(out _)) { }
- Interlocked.Exchange(ref _sbPoolCount, 0);
-
- _encryptor.Dispose();
+ ArrayPool.Shared.Return(_buffer, true);
+ _stream.Dispose();
_aes?.Dispose();
- _compressionCts.Dispose();
- _compressionSemaphore.Dispose();
+ return Task.CompletedTask;
}
public new void Dispose()
@@ -731,32 +337,4 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
OnShutdownFlushAsync().GetAwaiter().GetResult();
base.Dispose();
}
-
- private sealed class FileState : IDisposable
- {
- public string FilePath;
- public long Size;
- public DateTime Date;
- public byte[] Buffer;
- public int BufferPosition;
- public FileStream Stream;
- public SemaphoreSlim WriteLock;
- public bool IsFaulted;
- public DateTime LastFailureUtc;
-
- public void Dispose()
- {
- Stream?.Dispose();
- Stream = null;
-
- if (Buffer != null)
- {
- ArrayPool.Shared.Return(Buffer, clearArray: true);
- Buffer = null;
- }
-
- WriteLock?.Dispose();
- WriteLock = null;
- }
- }
}
diff --git a/Testers/EonaCat.Logger.Test.Web/Logger.cs b/Testers/EonaCat.Logger.Test.Web/Logger.cs
deleted file mode 100644
index 804babc..0000000
--- a/Testers/EonaCat.Logger.Test.Web/Logger.cs
+++ /dev/null
@@ -1,153 +0,0 @@
-using System.IO.Compression;
-using EonaCat.Logger.EonaCatCoreLogger;
-using EonaCat.Logger.Extensions;
-using EonaCat.Logger.Managers;
-
-namespace EonaCat.Logger.Test.Web;
-
-public class Logger
-{
- private LogManager _logManager;
- public LoggerSettings LoggerSettings { get; }
- public bool UseLocalTime { get; set; }
- public string LogFolder { get; set; } = Path.Combine(FileLoggerOptions.DefaultPath, "logs");
- public string CurrentLogFile => _logManager.CurrentLogFile;
- public bool IsDisabled { get; set; }
-
- ///
- /// Logger
- ///
- ///
- ///
- ///
- ///
- public Logger(string name = "EonaCatTestLogger", List typesToLog = null, bool useLocalTime = false, int maxFileSize = 20_000_000)
- {
- UseLocalTime = useLocalTime;
-
- LoggerSettings = new LoggerSettings
- {
- Id = name,
- TypesToLog = typesToLog,
- UseLocalTime = UseLocalTime,
- FileLoggerOptions =
- {
- LogDirectory = LogFolder,
- FileSizeLimit = maxFileSize,
- UseLocalTime = UseLocalTime,
- },
- };
- }
-
- public void DeleteCurrentLogFile()
- {
- if (IsDisabled)
- {
- return;
- }
-
- _logManager.DeleteCurrentLogFile();
- }
-
- private string ConvertToAbsolutePath(string path)
- {
- return Path.IsPathRooted(path) ? path : Path.Combine(LogFolder, path);
- }
-
- public async Task DownloadLogAsync(HttpContext context, string logName, long limit)
- {
- var logFileName = logName + ".log";
-
- await using var fileStream = new FileStream(Path.Combine(ConvertToAbsolutePath(LogFolder), logFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 64 * 1024, true);
-
- var response = context.Response;
-
- response.ContentType = "text/plain";
- response.Headers.ContentDisposition = "attachment;filename=" + logFileName;
-
- if (limit > fileStream.Length || limit < 1)
- {
- limit = fileStream.Length;
- }
-
- var oFS = new OffsetStream(fileStream, 0, limit);
- var request = context.Request;
- Stream stream;
-
- string acceptEncoding = request.Headers["Accept-Encoding"];
- if (string.IsNullOrEmpty(acceptEncoding))
- {
- stream = response.Body;
- }
- else
- {
- var acceptEncodingParts = acceptEncoding.Split(new[] { ',' },
- StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
-
- if (acceptEncodingParts.Contains("br"))
- {
- response.Headers.ContentEncoding = "br";
- stream = new BrotliStream(response.Body, CompressionMode.Compress);
- }
- else if (acceptEncodingParts.Contains("gzip"))
- {
- response.Headers.ContentEncoding = "gzip";
- stream = new GZipStream(response.Body, CompressionMode.Compress);
- }
- else if (acceptEncodingParts.Contains("deflate"))
- {
- response.Headers.ContentEncoding = "deflate";
- stream = new DeflateStream(response.Body, CompressionMode.Compress);
- }
- else
- {
- stream = response.Body;
- }
- }
-
- await using (stream)
- {
- await oFS.CopyToAsync(stream).ConfigureAwait(false);
-
- if (fileStream.Length > limit)
- {
- await stream.WriteAsync("\r\n####___TRUNCATED___####"u8.ToArray()).ConfigureAwait(false);
- }
- }
- }
-
- public async Task LogAsync(string message, ELogType logType = ELogType.INFO, bool writeToConsole = true)
- {
- if (IsDisabled)
- {
- return;
- }
-
- InitLogger();
- await _logManager.WriteAsync(message, logType, writeToConsole).ConfigureAwait(false);
- }
-
- private void InitLogger()
- {
- if (_logManager == null)
- {
- // Initialize LogManager
- _logManager = new LogManager(LoggerSettings);
- _logManager.Settings.TypesToLog.Clear();
- }
- }
-
- public async Task LogAsync(Exception exception, string message = "", bool writeToConsole = true)
- {
- if (IsDisabled)
- {
- return;
- }
-
- InitLogger();
- if (LoggerSettings.TypesToLog.Contains(ELogType.ERROR))
- {
- await _logManager.WriteAsync(exception, message, writeToConsole: writeToConsole).ConfigureAwait(false);
- }
- }
-}
\ No newline at end of file