From bb767e89f711b9582d6994a1ca327af55d1004e2 Mon Sep 17 00:00:00 2001 From: EonaCat Date: Mon, 16 Feb 2026 06:21:59 +0100 Subject: [PATCH] Updated --- EonaCat.Logger/EonaCat.Logger.csproj | 6 +- .../EonaCatCoreLogger/FileLoggerProvider.cs | 778 ++++-------------- Testers/EonaCat.Logger.Test.Web/Logger.cs | 153 ---- 3 files changed, 181 insertions(+), 756 deletions(-) delete mode 100644 Testers/EonaCat.Logger.Test.Web/Logger.cs 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