Updated
This commit is contained in:
@@ -73,34 +73,59 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
||||
protected override async Task WriteMessagesAsync(IEnumerable<LogMessage> messages,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Directory.CreateDirectory(_path);
|
||||
|
||||
foreach (var group in messages.GroupBy(GetGrouping))
|
||||
if (IsWriting)
|
||||
{
|
||||
LogFile = GetFullName(group.Key);
|
||||
var fileInfo = new FileInfo(LogFile);
|
||||
var currentMessages = string.Join(string.Empty, group.Select(item => item.Message));
|
||||
if (!_buffer.TryAdd(LogFile, currentMessages))
|
||||
{
|
||||
_buffer[LogFile] += currentMessages;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await MoveRolloverLogFilesAsync(fileInfo, cancellationToken).ConfigureAwait(false);
|
||||
IsWriting = true;
|
||||
|
||||
if (await TryWriteToFileAsync(cancellationToken))
|
||||
{
|
||||
// Clear buffer on success
|
||||
_buffer.Clear();
|
||||
}
|
||||
else if (await WriteToTempFileAsync(cancellationToken))
|
||||
{
|
||||
// Fallback to temp file
|
||||
}
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(_path);
|
||||
|
||||
DeleteOldLogFiles();
|
||||
foreach (var group in messages.GroupBy(GetGrouping))
|
||||
{
|
||||
LogFile = GetFullName(group.Key);
|
||||
var currentMessages = string.Join(string.Empty, group.Select(item => item.Message));
|
||||
if (!_buffer.TryAdd(LogFile, currentMessages))
|
||||
{
|
||||
_buffer[LogFile] += currentMessages;
|
||||
}
|
||||
|
||||
// Check file size
|
||||
FileInfo fileInfo = new FileInfo(LogFile);
|
||||
if (fileInfo.Exists && fileInfo.Length >= _maxFileSize)
|
||||
{
|
||||
// Roll over the log file
|
||||
await RollOverLogFile().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (await TryWriteToFileAsync(cancellationToken))
|
||||
{
|
||||
// Clear buffer on success
|
||||
_buffer.Clear();
|
||||
}
|
||||
else if (await WriteToTempFileAsync(cancellationToken))
|
||||
{
|
||||
// Fallback to temp file
|
||||
}
|
||||
|
||||
DeleteOldLogFiles();
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
OnError?.Invoke(this, new ErrorMessage { Exception = exception, Message = "Cannot write to file"});
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsWriting = false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsWriting { get; set; }
|
||||
|
||||
private async Task<bool> TryWriteToFileAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_buffer.ContainsKey(LogFile)) return true;
|
||||
@@ -116,10 +141,9 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
||||
{
|
||||
await file.WriteAsync(_buffer[LogFile]).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
completed = true;
|
||||
_buffer.TryRemove(LogFile, out _);
|
||||
return true; // Success
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -128,7 +152,7 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
||||
if (tries >= _maxTries)
|
||||
{
|
||||
OnError?.Invoke(this, new ErrorMessage { Message = "Cannot write to log folder"});
|
||||
return false; // Failure after retries
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -178,104 +202,82 @@ public class FileLoggerProvider : BatchingLoggerProvider
|
||||
return (message.Timestamp.Year, message.Timestamp.Month, message.Timestamp.Day);
|
||||
}
|
||||
|
||||
private static void MoveFile(string copyFromPath, string copyToPath)
|
||||
{
|
||||
var origin = new FileInfo(copyFromPath);
|
||||
origin.MoveTo(copyToPath);
|
||||
private readonly object lockObj = new object();
|
||||
|
||||
var destination = new FileInfo(copyToPath);
|
||||
destination.CreationTime = origin.CreationTime;
|
||||
destination.LastWriteTime = origin.LastWriteTime;
|
||||
destination.LastAccessTime = origin.LastAccessTime;
|
||||
private async Task RollOverLogFile()
|
||||
{
|
||||
lock (lockObj)
|
||||
{
|
||||
try
|
||||
{
|
||||
string directoryPath = Path.GetDirectoryName(LogFile);
|
||||
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(LogFile);
|
||||
string fileExtension = Path.GetExtension(LogFile);
|
||||
|
||||
// Rename existing files
|
||||
for (int i = _maxRolloverFiles - 1; i >= 1; i--)
|
||||
{
|
||||
string currentFilePath = Path.Combine(directoryPath, $"{fileNameWithoutExtension}.{i}{fileExtension}");
|
||||
string newFilePath = Path.Combine(directoryPath, $"{fileNameWithoutExtension}.{(i + 1)}{fileExtension}");
|
||||
|
||||
if (File.Exists(currentFilePath))
|
||||
{
|
||||
if (File.Exists(newFilePath))
|
||||
{
|
||||
File.Delete(newFilePath);
|
||||
}
|
||||
File.Move(currentFilePath, newFilePath);
|
||||
}
|
||||
|
||||
if (i != 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
currentFilePath = Path.Combine(directoryPath, $"{fileNameWithoutExtension}{fileExtension}");
|
||||
newFilePath = Path.Combine(directoryPath, $"{fileNameWithoutExtension}.{i}{fileExtension}");
|
||||
File.Move(currentFilePath, newFilePath);
|
||||
}
|
||||
|
||||
// Rename current log file
|
||||
var rolloverFilePath = Path.Combine(directoryPath, $"{fileNameWithoutExtension}.1{fileExtension}");
|
||||
|
||||
// Write end message and start processing in background
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await WriteEndMessageAsync(rolloverFilePath).ConfigureAwait(false);
|
||||
await ProcessAsync().ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log or handle the exception
|
||||
Console.WriteLine($"Error occurred during log file rollover: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task MoveRolloverLogFilesAsync(FileInfo fileInfo, CancellationToken cancellationToken)
|
||||
private async Task WriteEndMessageAsync(string logFilePath)
|
||||
{
|
||||
var stopMessage = LogHelper.GetStopMessage();
|
||||
stopMessage = LogHelper.FormatMessageWithHeader(LoggerSettings, ELogType.INFO, stopMessage, CurrentDateTme);
|
||||
|
||||
using (var file = new StreamWriter(logFilePath, true))
|
||||
{
|
||||
await file.WriteAsync(stopMessage).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ProcessAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_maxFileSize > 0 && fileInfo.Exists && fileInfo.Length > _maxFileSize)
|
||||
{
|
||||
if (_maxRolloverFiles > 0 && _rollOverCount >= 0)
|
||||
{
|
||||
if (_rollOverCount < _maxRolloverFiles)
|
||||
{
|
||||
var rollOverFile = LogFile.Replace(".log", $"_{++_rollOverCount}.log");
|
||||
if (File.Exists(rollOverFile))
|
||||
{
|
||||
File.Delete(rollOverFile);
|
||||
}
|
||||
fileInfo.CopyTo(rollOverFile);
|
||||
File.WriteAllText(LogFile, string.Empty);
|
||||
}
|
||||
else
|
||||
{
|
||||
lock (_rollOverLock)
|
||||
{
|
||||
_rollingOver = true;
|
||||
MoveRolloverLogFiles();
|
||||
_rollingOver = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (_rollingOver)
|
||||
{
|
||||
await Task.Delay(100, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
await WriteStartMessage().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception exception)
|
||||
catch (Exception ex)
|
||||
{
|
||||
OnError?.Invoke(this, new ErrorMessage { Message = "Cannot rollover files" });
|
||||
}
|
||||
}
|
||||
|
||||
private object _rollOverLock { get; set; } = new object();
|
||||
|
||||
/// <summary>
|
||||
/// Rollover logFiles
|
||||
/// </summary>
|
||||
private void MoveRolloverLogFiles()
|
||||
{
|
||||
if (_maxRolloverFiles > 0 && _rollOverCount >= 0)
|
||||
{
|
||||
if (_rollOverCount >= _maxRolloverFiles)
|
||||
{
|
||||
var maxRollover = _rollOverCount;
|
||||
bool hasPrefix = !string.IsNullOrWhiteSpace(_fileNamePrefix);
|
||||
IEnumerable<FileInfo> files;
|
||||
if (hasPrefix)
|
||||
{
|
||||
files = new DirectoryInfo(_path).GetFiles(_fileNamePrefix + "*").OrderBy(x => x.CreationTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
files = new DirectoryInfo(_path).GetFiles("*").OrderBy(x => x.CreationTime);
|
||||
}
|
||||
|
||||
for (int i = files.Count() - 1; i >= 0; i--)
|
||||
{
|
||||
var currentFile = files.ElementAt(i);
|
||||
if (i == 0)
|
||||
{
|
||||
// Temporary move first file
|
||||
var newFilename2 = Path.GetFileName(currentFile.FullName).Replace($".log", $"_{i + 1}.log");
|
||||
MoveFile(currentFile.FullName, $@"{Path.GetDirectoryName(currentFile.FullName)}\{newFilename2}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == files.Count() - 1)
|
||||
{
|
||||
// Delete the last file
|
||||
File.Delete(currentFile.FullName);
|
||||
continue;
|
||||
}
|
||||
|
||||
var newFilename = Path.GetFileName(currentFile.FullName).Replace($"_{i}.log", $"_{i + 1}.log");
|
||||
MoveFile(currentFile.FullName, $@"{Path.GetDirectoryName(currentFile.FullName)}\{newFilename}");
|
||||
}
|
||||
_rollOverCount = 0;
|
||||
}
|
||||
// Log or handle the exception
|
||||
Console.WriteLine($"Error occurred during log file rollover: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
|
||||
|
||||
private ConcurrentQueue<LogMessage> _messageQueue;
|
||||
private Task _outputTask;
|
||||
private object _writeLock = new object();
|
||||
private bool _isDisposing;
|
||||
|
||||
protected BatchingLoggerProvider(IOptions<BatchingLoggerOptions> options)
|
||||
{
|
||||
@@ -33,7 +35,7 @@ public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
|
||||
if (options.Value is FileLoggerOptions fileLoggerOptions) UseLocalTime = fileLoggerOptions.UseLocalTime;
|
||||
_batchSize = loggerOptions.BatchSize;
|
||||
|
||||
Start();
|
||||
StartAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected DateTimeOffset CurrentDateTimeOffset => UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
|
||||
@@ -59,8 +61,12 @@ public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
while (!_messageQueue.IsEmpty) Task.Delay(10);
|
||||
while (!_messageQueue.IsEmpty)
|
||||
{
|
||||
Task.Delay(10);
|
||||
}
|
||||
|
||||
_isDisposing = true;
|
||||
StopAsync().GetAwaiter().GetResult();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
@@ -71,14 +77,8 @@ public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
|
||||
}
|
||||
|
||||
protected abstract Task WriteMessagesAsync(IEnumerable<LogMessage> messages, CancellationToken token);
|
||||
|
||||
private async Task ProcessLogQueueAsync(object state)
|
||||
{
|
||||
var startupMessage = $"{DllInfo.ApplicationName} started.{Environment.NewLine}";
|
||||
startupMessage =
|
||||
LogHelper.FormatMessageWithHeader(LoggerSettings, ELogType.INFO, startupMessage, CurrentDateTme);
|
||||
AddMessage(DateTimeOffset.Now, startupMessage);
|
||||
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
var limit = _batchSize <= 0 ? int.MaxValue : _batchSize;
|
||||
@@ -89,44 +89,57 @@ public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
|
||||
}
|
||||
|
||||
if (_currentBatch.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
_currentBatch.Clear();
|
||||
if (_isDisposing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_writeLock)
|
||||
{
|
||||
WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
_currentBatch.Clear();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
}
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task WriteStartMessage()
|
||||
{
|
||||
var message = LogHelper.GetStartupMessage();
|
||||
await WriteMessagesAsync(new List<LogMessage> { new LogMessage() { Message = message, Timestamp = CurrentDateTimeOffset } }, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task WriteStopMessage()
|
||||
{
|
||||
var message = LogHelper.GetStopMessage();
|
||||
await WriteMessagesAsync(new List<LogMessage> { new LogMessage() { Message = message, Timestamp = CurrentDateTimeOffset } }, _cancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal void AddMessage(DateTimeOffset timestamp, string message)
|
||||
{
|
||||
_messageQueue.Enqueue(new LogMessage { Message = message, Timestamp = timestamp });
|
||||
}
|
||||
|
||||
private void Start()
|
||||
private Task StartAsync()
|
||||
{
|
||||
IsStarted = true;
|
||||
_messageQueue = new ConcurrentQueue<LogMessage>();
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
_outputTask = Task.Factory.StartNew(
|
||||
ProcessLogQueueAsync,
|
||||
null,
|
||||
TaskCreationOptions.LongRunning).ContinueWith(async x =>
|
||||
{
|
||||
var stopMessage = $"{DllInfo.ApplicationName} stopped.{Environment.NewLine}";
|
||||
stopMessage = LogHelper.FormatMessageWithHeader(LoggerSettings, ELogType.INFO, stopMessage, CurrentDateTme);
|
||||
await WriteMessagesAsync(
|
||||
new List<LogMessage>(new List<LogMessage>
|
||||
{ new() { Message = stopMessage, Timestamp = CurrentDateTme } }),
|
||||
_cancellationTokenSource.Token)
|
||||
.ConfigureAwait(false);
|
||||
});
|
||||
TaskCreationOptions.LongRunning);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task StopAsync()
|
||||
|
||||
Reference in New Issue
Block a user