diff --git a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs index af3b882..3c8bd05 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs @@ -256,13 +256,13 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable const int maxBatch = 5000; int batchCount = 0; - // Rent a buffer - byte[] combined = ArrayPool.Shared.Rent(BufferSize); + // Start with a reasonably sized buffer from the pool + int estimatedSize = 1024 * 64; // 64KB starting buffer + byte[] combined = ArrayPool.Shared.Rent(estimatedSize); int pos = 0; while (queue.TryDequeue(out var msg) && batchCount < maxBatch) { - // Rotate file if date changed var msgDate = msg.Timestamp.UtcDateTime.Date; if (state.Date != msgDate) { @@ -270,66 +270,68 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable RotateByDate(state, msgDate, string.Empty); } - var msgText = BuildMessage(msg); - int byteCount = Utf8.GetByteCount(msgText); + var messageString = BuildMessage(msg); + int requiredLength = Utf8.GetByteCount(messageString); - // Flush buffer if message won't fit - if (pos + byteCount > combined.Length) + // Grow buffer if needed + if (pos + requiredLength > combined.Length) { - await FlushBufferAsync(state).ConfigureAwait(false); - pos = 0; + int newSize = Math.Max(combined.Length * 2, pos + requiredLength); + byte[] newBuffer = ArrayPool.Shared.Rent(newSize); + Array.Copy(combined, 0, newBuffer, 0, pos); + ArrayPool.Shared.Return(combined); + combined = newBuffer; } - Utf8.GetBytes(msgText, 0, msgText.Length, combined, pos); - pos += byteCount; + // Write directly into combined buffer + pos += Utf8.GetBytes(messageString, 0, messageString.Length, combined, pos); batchCount++; } if (pos == 0) { ArrayPool.Shared.Return(combined); - return; + return; // nothing to write } + // If encryption is enabled, encrypt into a new array byte[] dataToWrite; - if (_isEncryptionEnabled) { - // Encrypt creates a new array, old buffer cleared immediately dataToWrite = Encrypt(combined.AsSpan(0, pos).ToArray()); - Array.Clear(combined, 0, pos); } else { + // No encryption: just use the pooled buffer slice directly dataToWrite = combined; } // Flush buffer if needed - if (state.BufferPosition + dataToWrite.Length > BufferSize) + if (state.BufferPosition + pos > BufferSize) { await FlushBufferAsync(state).ConfigureAwait(false); } // Rollover if file exceeds max size - if (_maxFileSize > 0 && state.Size + dataToWrite.Length > _maxFileSize) + if (_maxFileSize > 0 && state.Size + pos > _maxFileSize) { await FlushBufferAsync(state).ConfigureAwait(false); RollOverAndCompressOldest(state, string.Empty); } - // Copy into buffer - Array.Copy(dataToWrite, 0, state.Buffer, state.BufferPosition, dataToWrite.Length); - state.BufferPosition += dataToWrite.Length; - state.Size += dataToWrite.Length; + // Copy directly into the file buffer + Array.Copy(dataToWrite, 0, state.Buffer, state.BufferPosition, pos); + state.BufferPosition += pos; + state.Size += pos; + // Clear sensitive data + Array.Clear(dataToWrite, 0, pos); + + // Return buffer if unencrypted if (!_isEncryptionEnabled) { - // return pooled buffer if not encrypted ArrayPool.Shared.Return(dataToWrite); } - - // Clear encrypted array immediately - Array.Clear(dataToWrite, 0, dataToWrite.Length); } private async Task FlushBufferAsync(FileState state, CancellationToken token = default)