Updated
This commit is contained in:
@@ -18,6 +18,7 @@ public class FileLoggerOptions : BatchingLoggerOptions
|
|||||||
private int _maxRolloverFiles = 10;
|
private int _maxRolloverFiles = 10;
|
||||||
private int _retainedFileCountLimit = 50;
|
private int _retainedFileCountLimit = 50;
|
||||||
public bool EnableCategoryRouting { get; set; }
|
public bool EnableCategoryRouting { get; set; }
|
||||||
|
public LogOverflowStrategy OverflowStrategy { get; set; } = LogOverflowStrategy.Wait;
|
||||||
public ELogType MinimumLogLevel { get; set; } = ELogType.INFO;
|
public ELogType MinimumLogLevel { get; set; } = ELogType.INFO;
|
||||||
public string Category { get; set; }
|
public string Category { get; set; }
|
||||||
public byte[] EncryptionKey { get; set; }
|
public byte[] EncryptionKey { get; set; }
|
||||||
|
|||||||
@@ -8,12 +8,20 @@ using System.Buffers;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public enum LogOverflowStrategy
|
||||||
|
{
|
||||||
|
Wait,
|
||||||
|
DropNewest,
|
||||||
|
DropOldest,
|
||||||
|
}
|
||||||
|
|
||||||
[ProviderAlias("EonaCatFileLogger")]
|
[ProviderAlias("EonaCatFileLogger")]
|
||||||
public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
||||||
{
|
{
|
||||||
@@ -24,7 +32,7 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
|
|
||||||
private readonly Channel<LogMessage> _channel;
|
private readonly Channel<LogMessage> _channel;
|
||||||
private readonly Task _writerTask;
|
private readonly Task _writerTask;
|
||||||
private readonly SemaphoreSlim _flushSemaphore = new(1, 1);
|
private int _flushRequested;
|
||||||
|
|
||||||
private string _filePath;
|
private string _filePath;
|
||||||
private readonly int _maxFileSize;
|
private readonly int _maxFileSize;
|
||||||
@@ -39,9 +47,11 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
|
|
||||||
private readonly Aes? _aes;
|
private readonly Aes? _aes;
|
||||||
public event Action<Exception>? OnError;
|
public event Action<Exception>? OnError;
|
||||||
|
public event Action<string>? OnFileRolled;
|
||||||
|
|
||||||
public bool IncludeCorrelationId { get; }
|
public bool IncludeCorrelationId { get; }
|
||||||
public bool EnableCategoryRouting { get; }
|
public bool EnableCategoryRouting { get; }
|
||||||
|
public CompressionLevel CompressionLevel { get; set; } = CompressionLevel.Optimal;
|
||||||
|
|
||||||
public string LogFile => _filePath;
|
public string LogFile => _filePath;
|
||||||
|
|
||||||
@@ -54,6 +64,9 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
|
|
||||||
private static readonly TimeSpan FlushInterval = TimeSpan.FromMilliseconds(500);
|
private static readonly TimeSpan FlushInterval = TimeSpan.FromMilliseconds(500);
|
||||||
private long _lastFlushTicks = DateTime.UtcNow.Ticks;
|
private long _lastFlushTicks = DateTime.UtcNow.Ticks;
|
||||||
|
private DateTime _currentRollDate = DateTime.UtcNow.Date;
|
||||||
|
private readonly Channel<string> _compressQueue = Channel.CreateUnbounded<string>();
|
||||||
|
|
||||||
|
|
||||||
public FileLoggerProvider(IOptions<FileLoggerOptions> options) : base(options)
|
public FileLoggerProvider(IOptions<FileLoggerOptions> options) : base(options)
|
||||||
{
|
{
|
||||||
@@ -96,12 +109,70 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
{
|
{
|
||||||
SingleReader = true,
|
SingleReader = true,
|
||||||
SingleWriter = false,
|
SingleWriter = false,
|
||||||
FullMode = BoundedChannelFullMode.Wait
|
FullMode = o.OverflowStrategy switch
|
||||||
|
{
|
||||||
|
LogOverflowStrategy.DropNewest => BoundedChannelFullMode.DropWrite,
|
||||||
|
LogOverflowStrategy.DropOldest => BoundedChannelFullMode.DropOldest,
|
||||||
|
_ => BoundedChannelFullMode.Wait
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
StartCompressionWorker();
|
||||||
_writerTask = Task.Run(WriterLoopAsync);
|
_writerTask = Task.Run(WriterLoopAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void StartCompressionWorker()
|
||||||
|
{
|
||||||
|
_ = Task.Run(async () =>
|
||||||
|
{
|
||||||
|
await foreach (var path in _compressQueue.Reader.ReadAllAsync())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string dest = GetRotatedGzipPath(path);
|
||||||
|
|
||||||
|
using var input = File.OpenRead(path);
|
||||||
|
using var output = File.Create(dest);
|
||||||
|
using var gzip = new GZipStream(output, CompressionLevel);
|
||||||
|
await input.CopyToAsync(gzip);
|
||||||
|
|
||||||
|
File.Delete(path);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a new rotated .gz filename and shifts older files
|
||||||
|
private string GetRotatedGzipPath(string originalPath)
|
||||||
|
{
|
||||||
|
const int MaxFiles = 5; // maximum number of gz files to keep
|
||||||
|
string dir = Path.GetDirectoryName(originalPath) ?? "";
|
||||||
|
string name = Path.GetFileNameWithoutExtension(originalPath);
|
||||||
|
|
||||||
|
// Shift existing files: log_4.gz → log_5.gz, log_3.gz → log_4.gz, ...
|
||||||
|
for (int i = MaxFiles - 1; i >= 1; i--)
|
||||||
|
{
|
||||||
|
string oldFile = Path.Combine(dir, $"{name}_{i}.gz");
|
||||||
|
if (File.Exists(oldFile))
|
||||||
|
{
|
||||||
|
string newFile = Path.Combine(dir, $"{name}_{i + 1}.gz");
|
||||||
|
|
||||||
|
// Delete the destination if it already exists
|
||||||
|
if (File.Exists(newFile))
|
||||||
|
File.Delete(newFile);
|
||||||
|
|
||||||
|
File.Move(oldFile, newFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New file becomes _1.gz
|
||||||
|
return Path.Combine(dir, $"{name}_1.gz");
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryInitializePath(string directory, string fileName)
|
private bool TryInitializePath(string directory, string fileName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -114,7 +185,8 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
_filePath = fullPath;
|
_filePath = fullPath;
|
||||||
_fileStream = new FileStream(_filePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.SequentialScan | FileOptions.WriteThrough);
|
_fileStream = new FileStream(_filePath, FileMode.Append, FileAccess.Write,
|
||||||
|
FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.SequentialScan | FileOptions.WriteThrough);
|
||||||
|
|
||||||
if (_encryptionEnabled && _aes != null)
|
if (_encryptionEnabled && _aes != null)
|
||||||
{
|
{
|
||||||
@@ -124,11 +196,7 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
_size = _fileStream.Length;
|
_size = _fileStream.Length;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex) { RaiseError(ex); return false; }
|
||||||
{
|
|
||||||
RaiseError(ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override Task WriteMessagesAsync(IReadOnlyList<LogMessage> messages, CancellationToken token)
|
internal override Task WriteMessagesAsync(IReadOnlyList<LogMessage> messages, CancellationToken token)
|
||||||
@@ -146,25 +214,31 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool NeedsTimeRoll()
|
||||||
|
{
|
||||||
|
return DateTime.UtcNow.Date > _currentRollDate;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task WriterLoopAsync()
|
private async Task WriterLoopAsync()
|
||||||
{
|
{
|
||||||
var reader = _channel.Reader;
|
var reader = _channel.Reader;
|
||||||
List<LogMessage> batch = new();
|
var batch = new List<LogMessage>(256);
|
||||||
|
|
||||||
while (_running || reader.Count > 0)
|
while (await reader.WaitToReadAsync())
|
||||||
{
|
{
|
||||||
batch.Clear();
|
batch.Clear();
|
||||||
while (reader.TryRead(out var msg))
|
while (reader.TryRead(out var msg))
|
||||||
{
|
|
||||||
batch.Add(msg);
|
batch.Add(msg);
|
||||||
}
|
|
||||||
|
|
||||||
if (batch.Count > 0)
|
if (batch.Count > 0)
|
||||||
{
|
{
|
||||||
|
if (NeedsTimeRoll())
|
||||||
|
{
|
||||||
|
_currentRollDate = DateTime.UtcNow.Date;
|
||||||
|
await RollFileAsync();
|
||||||
|
}
|
||||||
await WriteBatchAsync(batch);
|
await WriteBatchAsync(batch);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(50); // idle wait
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await FlushFinalAsync();
|
await FlushFinalAsync();
|
||||||
@@ -186,20 +260,31 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
sb.Append(kv.Key).Append('=').Append(kv.Value).Append(' ');
|
sb.Append(kv.Key).Append('=').Append(kv.Value).Append(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.Length--; // trim
|
sb.Length--;
|
||||||
sb.Append(']');
|
sb.Append(']');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sb.Append(' ').Append(msg.Message).AppendLine();
|
sb.Append(' ').Append(msg.Message).AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Directly encode to pooled byte buffer
|
||||||
int maxBytes = Utf8.GetMaxByteCount(sb.Length);
|
int maxBytes = Utf8.GetMaxByteCount(sb.Length);
|
||||||
byte[] rented = ArrayPool<byte>.Shared.Rent(maxBytes);
|
if (maxBytes > _buffer.Length - _position)
|
||||||
int byteCount = Utf8.GetBytes(sb.ToString(), 0, sb.Length, rented, 0);
|
{
|
||||||
WriteToBuffer(rented, byteCount);
|
await FlushInternalAsync();
|
||||||
ArrayPool<byte>.Shared.Return(rented, true);
|
}
|
||||||
|
|
||||||
|
int bytesWritten = Utf8.GetBytes(sb.ToString(), 0, sb.Length, _buffer, _position);
|
||||||
|
_position += bytesWritten;
|
||||||
|
_size += bytesWritten;
|
||||||
|
|
||||||
ReleaseStringBuilder(sb);
|
ReleaseStringBuilder(sb);
|
||||||
|
|
||||||
|
if (_maxFileSize > 0 && _size >= _maxFileSize)
|
||||||
|
{
|
||||||
|
await RollFileAsync();
|
||||||
|
}
|
||||||
|
|
||||||
await FlushIfNeededAsync();
|
await FlushIfNeededAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,31 +293,16 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
long now = DateTime.UtcNow.Ticks;
|
long now = DateTime.UtcNow.Ticks;
|
||||||
if (_position >= FlushThreshold || now - _lastFlushTicks >= FlushInterval.Ticks)
|
if (_position >= FlushThreshold || now - _lastFlushTicks >= FlushInterval.Ticks)
|
||||||
{
|
{
|
||||||
await _flushSemaphore.WaitAsync();
|
if (Interlocked.Exchange(ref _flushRequested, 1) == 0)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await FlushInternalAsync();
|
await FlushInternalAsync();
|
||||||
_lastFlushTicks = now;
|
_lastFlushTicks = now;
|
||||||
}
|
}
|
||||||
finally { _flushSemaphore.Release(); }
|
finally { _flushRequested = 0; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void WriteToBuffer(byte[] data, int length)
|
|
||||||
{
|
|
||||||
if (_position + length > _buffer.Length)
|
|
||||||
{
|
|
||||||
FlushInternalAsync().GetAwaiter().GetResult();
|
|
||||||
}
|
|
||||||
|
|
||||||
Buffer.BlockCopy(data, 0, _buffer, _position, length);
|
|
||||||
_position += length;
|
|
||||||
_size += length;
|
|
||||||
|
|
||||||
if (_maxFileSize > 0 && _size >= _maxFileSize)
|
|
||||||
{
|
|
||||||
Task.Run(RollFileAsync);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task FlushInternalAsync()
|
private async Task FlushInternalAsync()
|
||||||
@@ -244,6 +314,11 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (NeedsReopen())
|
||||||
|
{
|
||||||
|
await ReopenFileAsync();
|
||||||
|
}
|
||||||
|
|
||||||
if (_cryptoStream != null)
|
if (_cryptoStream != null)
|
||||||
{
|
{
|
||||||
await _cryptoStream.WriteAsync(_buffer, 0, _position);
|
await _cryptoStream.WriteAsync(_buffer, 0, _position);
|
||||||
@@ -260,41 +335,114 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
_position = 0;
|
_position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool NeedsReopen()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!File.Exists(_filePath))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var info = new FileInfo(_filePath);
|
||||||
|
return info.Length < _size;
|
||||||
|
}
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReopenFileAsync()
|
||||||
|
{
|
||||||
|
_cryptoStream?.Dispose();
|
||||||
|
_fileStream?.Dispose();
|
||||||
|
|
||||||
|
_fileStream = new FileStream(_filePath, FileMode.Append, FileAccess.Write,
|
||||||
|
FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.SequentialScan | FileOptions.WriteThrough);
|
||||||
|
|
||||||
|
if (_encryptionEnabled && _aes != null)
|
||||||
|
{
|
||||||
|
_cryptoStream = new CryptoStream(_fileStream, _aes.CreateEncryptor(), CryptoStreamMode.Write);
|
||||||
|
}
|
||||||
|
|
||||||
|
_size = _fileStream.Length;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task FlushFinalAsync()
|
private async Task FlushFinalAsync()
|
||||||
{
|
{
|
||||||
await FlushInternalAsync();
|
await FlushInternalAsync();
|
||||||
|
|
||||||
_cryptoStream?.FlushFinalBlock();
|
_cryptoStream?.FlushFinalBlock();
|
||||||
|
|
||||||
await _fileStream.FlushAsync();
|
await _fileStream.FlushAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task RollFileAsync()
|
private async Task RollFileAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _flushSemaphore.WaitAsync();
|
await FlushInternalAsync();
|
||||||
FlushInternalAsync().GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
|
_cryptoStream?.FlushFinalBlock();
|
||||||
_cryptoStream?.Dispose();
|
_cryptoStream?.Dispose();
|
||||||
_fileStream.Dispose();
|
_cryptoStream = null;
|
||||||
|
_fileStream?.Dispose();
|
||||||
|
|
||||||
string tempArchive = _filePath + ".rolling";
|
string directory = Path.GetDirectoryName(_filePath)!;
|
||||||
File.Move(_filePath, tempArchive);
|
string baseName = Path.GetFileNameWithoutExtension(_filePath);
|
||||||
|
string extension = Path.GetExtension(_filePath);
|
||||||
|
|
||||||
// Compression in background
|
// Shift existing rollover files
|
||||||
await Task.Run(() =>
|
for (int i = _maxRolloverFiles - 1; i >= 1; i--)
|
||||||
{
|
{
|
||||||
string dest = _filePath.Replace(".log", "_1.log.gz");
|
string src = Path.Combine(directory, $"{baseName}_{i}{extension}");
|
||||||
using var input = File.OpenRead(tempArchive);
|
string dest = Path.Combine(directory, $"{baseName}_{i + 1}{extension}");
|
||||||
using var output = File.Create(dest);
|
if (File.Exists(dest))
|
||||||
using var gzip = new GZipStream(output, CompressionLevel.Fastest);
|
{
|
||||||
input.CopyTo(gzip);
|
// Compress oldest if it exceeds max
|
||||||
File.Delete(tempArchive);
|
_compressQueue.Writer.TryWrite(dest);
|
||||||
});
|
File.Delete(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(src))
|
||||||
|
{
|
||||||
|
File.Move(src, dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move current log to FORMAT_1.log
|
||||||
|
string firstRollover = Path.Combine(directory, $"{baseName}_1{extension}");
|
||||||
|
if (File.Exists(_filePath))
|
||||||
|
{
|
||||||
|
File.Move(_filePath, firstRollover);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress if we exceed max rollover
|
||||||
|
string oldest = Path.Combine(directory, $"{baseName}_{_maxRolloverFiles + 1}{extension}");
|
||||||
|
if (File.Exists(oldest))
|
||||||
|
{
|
||||||
|
_compressQueue.Writer.TryWrite(oldest);
|
||||||
|
File.Delete(oldest);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recreate active log
|
||||||
|
RecreateLogFile();
|
||||||
|
|
||||||
|
OnFileRolled?.Invoke(firstRollover);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void RecreateLogFile()
|
||||||
|
{
|
||||||
|
_fileStream = new FileStream(_filePath,
|
||||||
|
FileMode.Create,
|
||||||
|
FileAccess.Write,
|
||||||
|
FileShare.ReadWrite | FileShare.Delete,
|
||||||
|
4096,
|
||||||
|
FileOptions.SequentialScan | FileOptions.WriteThrough);
|
||||||
|
|
||||||
_fileStream = new FileStream(_filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.SequentialScan);
|
|
||||||
if (_encryptionEnabled && _aes != null)
|
if (_encryptionEnabled && _aes != null)
|
||||||
{
|
{
|
||||||
_cryptoStream = new CryptoStream(_fileStream, _aes.CreateEncryptor(), CryptoStreamMode.Write);
|
_cryptoStream = new CryptoStream(_fileStream, _aes.CreateEncryptor(), CryptoStreamMode.Write);
|
||||||
@@ -302,18 +450,48 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
|
|
||||||
_size = 0;
|
_size = 0;
|
||||||
}
|
}
|
||||||
finally { _flushSemaphore.Release(); }
|
|
||||||
|
private void CleanupOldRollovers(string directory, string baseName)
|
||||||
|
{
|
||||||
|
if (_maxRolloverFiles <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var rolledLogs = Directory.GetFiles(directory, $"{baseName}_*.log")
|
||||||
|
.Where(file => !file.EndsWith(".gz", StringComparison.OrdinalIgnoreCase))
|
||||||
|
.Select(file => new FileInfo(file))
|
||||||
|
.OrderByDescending(file => file.CreationTimeUtc)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// If too many .log rollovers → compress oldest
|
||||||
|
if (rolledLogs.Count > _maxRolloverFiles)
|
||||||
|
{
|
||||||
|
foreach (var file in rolledLogs.Skip(_maxRolloverFiles))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_compressQueue.Writer.TryWrite(file.FullName);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RaiseError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static StringBuilder AcquireStringBuilder()
|
private static StringBuilder AcquireStringBuilder()
|
||||||
{
|
{
|
||||||
var sb = _cachedStringBuilder;
|
var sb = _cachedStringBuilder;
|
||||||
if (sb == null) { sb = new StringBuilder(256); _cachedStringBuilder = sb; }
|
if (sb == null)
|
||||||
|
{
|
||||||
|
sb = new StringBuilder(256);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
sb.Clear();
|
sb.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_cachedStringBuilder = sb;
|
||||||
return sb;
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +522,15 @@ public sealed class FileLoggerProvider : BatchingLoggerProvider, IDisposable
|
|||||||
protected override async Task OnShutdownFlushAsync()
|
protected override async Task OnShutdownFlushAsync()
|
||||||
{
|
{
|
||||||
_running = false;
|
_running = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
_channel.Writer.Complete();
|
_channel.Writer.Complete();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Channel closed before we could complete, ignore
|
||||||
|
}
|
||||||
await _writerTask;
|
await _writerTask;
|
||||||
|
|
||||||
ArrayPool<byte>.Shared.Return(_buffer, true);
|
ArrayPool<byte>.Shared.Return(_buffer, true);
|
||||||
|
|||||||
Reference in New Issue
Block a user