This commit is contained in:
2024-04-25 22:05:17 +02:00
parent 44468ae920
commit 6c48b43a20
41 changed files with 2276 additions and 2515 deletions

View File

@@ -2,40 +2,34 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace EonaCat.Logger.EonaCatCoreLogger.Extensions
namespace EonaCat.Logger.EonaCatCoreLogger.Extensions;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
/// <summary>
/// Extensions for adding the <see cref="FileLoggerProvider" /> to the <see cref="ILoggingBuilder" />
/// </summary>
public static class FileLoggerFactoryExtensions
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
private static ILoggingBuilder AddEonaCatFileLogger(this ILoggingBuilder builder)
{
builder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
return builder;
}
/// <summary>
/// Extensions for adding the <see cref="FileLoggerProvider" /> to the <see cref="ILoggingBuilder" />
/// Adds the EonaCat File Logger named 'EonaCatFileLogger' to the factory.
/// </summary>
public static class FileLoggerFactoryExtensions
/// <param name="builder">The <see cref="ILoggingBuilder" /> to use.</param>
/// <param name="filenamePrefix">Sets the filename prefix to use for log files (optional)</param>
/// <param name="fileLoggerOptions">the options for the fileLogger that needs to be used (optional)</param>
public static ILoggingBuilder AddEonaCatFileLogger(this ILoggingBuilder builder, string filenamePrefix = null,
FileLoggerOptions fileLoggerOptions = null)
{
private static ILoggingBuilder AddEonaCatFileLogger(this ILoggingBuilder builder)
{
builder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
return builder;
}
if (fileLoggerOptions == null) fileLoggerOptions = new FileLoggerOptions();
/// <summary>
/// Adds the EonaCat File Logger named 'EonaCatFileLogger' to the factory.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="filenamePrefix">Sets the filename prefix to use for log files (optional)</param>
/// <param name="fileLoggerOptions">the options for the fileLogger that needs to be used (optional)</param>
public static ILoggingBuilder AddEonaCatFileLogger(this ILoggingBuilder builder, string filenamePrefix = null, FileLoggerOptions fileLoggerOptions = null)
{
if (fileLoggerOptions == null)
{
fileLoggerOptions = new FileLoggerOptions();
}
if (!string.IsNullOrWhiteSpace(filenamePrefix))
{
fileLoggerOptions.FileNamePrefix = filenamePrefix;
}
builder.AddEonaCatFileLogger(options =>
if (!string.IsNullOrWhiteSpace(filenamePrefix)) fileLoggerOptions.FileNamePrefix = filenamePrefix;
builder.AddEonaCatFileLogger(options =>
{
options.FileNamePrefix = fileLoggerOptions.FileNamePrefix;
options.FlushPeriod = fileLoggerOptions.FlushPeriod;
@@ -49,26 +43,22 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Extensions
options.MaxRolloverFiles = fileLoggerOptions.MaxRolloverFiles;
options.UseLocalTime = fileLoggerOptions.UseLocalTime;
}
);
return builder;
}
);
return builder;
}
/// <summary>
/// Adds the EonaCat File Logger named 'EonaCatFileLogger' to the factory.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder" /> to use.</param>
/// <param name="configure">Configure an instance of the <see cref="FileLoggerOptions" /> to set logging options</param>
public static ILoggingBuilder AddEonaCatFileLogger(this ILoggingBuilder builder,
Action<FileLoggerOptions> configure)
{
if (configure == null) throw new ArgumentNullException(nameof(configure));
builder.AddEonaCatFileLogger();
builder.Services.Configure(configure);
/// <summary>
/// Adds the EonaCat File Logger named 'EonaCatFileLogger' to the factory.
/// </summary>
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
/// <param name="configure">Configure an instance of the <see cref="FileLoggerOptions" /> to set logging options</param>
public static ILoggingBuilder AddEonaCatFileLogger(this ILoggingBuilder builder, Action<FileLoggerOptions> configure)
{
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
builder.AddEonaCatFileLogger();
builder.Services.Configure(configure);
return builder;
}
return builder;
}
}

View File

@@ -2,108 +2,93 @@
using System.IO;
using EonaCat.Logger.EonaCatCoreLogger.Internal;
namespace EonaCat.Logger.EonaCatCoreLogger
namespace EonaCat.Logger.EonaCatCoreLogger;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
/// <summary>
/// Options for file logging.
/// </summary>
public class FileLoggerOptions : BatchingLoggerOptions
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
private int _fileSizeLimit = 200 * 1024 * 1024;
private int _maxRolloverFiles = 10;
private int _retainedFileCountLimit = 50;
public static string DefaultPath =>
AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
/// <summary>
/// Options for file logging.
/// Gets or sets a strictly positive value representing the maximum log size in bytes or null for no limit.
/// Once the log is full, no more messages will be appended.
/// Defaults to <c>200MB</c>.
/// </summary>
public class FileLoggerOptions : BatchingLoggerOptions
public int FileSizeLimit
{
private int _fileSizeLimit = 200 * 1024 * 1024;
private int _retainedFileCountLimit = 50;
private int _maxRolloverFiles = 10;
private int _maxTries = 3;
get => _fileSizeLimit;
public static string DefaultPath => AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
/// <summary>
/// Gets or sets a strictly positive value representing the maximum log size in bytes or null for no limit.
/// Once the log is full, no more messages will be appended.
/// Defaults to <c>200MB</c>.
/// </summary>
public int FileSizeLimit
set
{
get => _fileSizeLimit;
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FileSizeLimit)} must be positive.");
}
_fileSizeLimit = value;
}
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FileSizeLimit)} must be positive.");
_fileSizeLimit = value;
}
/// <summary>
/// Gets or sets a strictly positive value representing the maximum retained file count or null for no limit.
/// Defaults to <c>50</c>.
/// </summary>
public int RetainedFileCountLimit
{
get => _retainedFileCountLimit;
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(RetainedFileCountLimit)} must be positive.");
}
_retainedFileCountLimit = value;
}
}
/// <summary>
/// Gets or sets the max times to try to write to the file.
/// Defaults 3.
/// </summary>
public int MaxWriteTries
{
get => _maxTries;
set
{
_maxTries = value;
}
}
/// <summary>
/// Determines if we need to use the local time in the logging or UTC (default:false)
/// </summary>
public bool UseLocalTime { get; set; }
/// <summary>
/// Gets or sets a strictly positive value representing the maximum retained file rollovers or null for no limit.
/// Defaults to <c>10</c>.
/// </summary>
public int MaxRolloverFiles
{
get => _maxRolloverFiles;
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxRolloverFiles)} must be positive.");
}
_maxRolloverFiles = value;
}
}
/// <summary>
/// Gets or sets the filename prefix to use for log files.
/// Defaults to <c>EonaCat_</c>.
/// </summary>
public string FileNamePrefix { get; set; } = "EonaCat";
/// <summary>
/// The directory in which log files will be written, relative to the app process.
/// Default to <c>executablePath\logs</c>
/// </summary>
/// <returns></returns>
public string LogDirectory { get; set; } = Path.Combine(DefaultPath, "logs");
}
/// <summary>
/// Gets or sets a strictly positive value representing the maximum retained file count or null for no limit.
/// Defaults to <c>50</c>.
/// </summary>
public int RetainedFileCountLimit
{
get => _retainedFileCountLimit;
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value),
$"{nameof(RetainedFileCountLimit)} must be positive.");
_retainedFileCountLimit = value;
}
}
/// <summary>
/// Gets or sets the max times to try to write to the file.
/// Defaults 3.
/// </summary>
public int MaxWriteTries { get; set; } = 3;
/// <summary>
/// Determines if we need to use the local time in the logging or UTC (default:false)
/// </summary>
public bool UseLocalTime { get; set; }
/// <summary>
/// Gets or sets a strictly positive value representing the maximum retained file rollovers or null for no limit.
/// Defaults to <c>10</c>.
/// </summary>
public int MaxRolloverFiles
{
get => _maxRolloverFiles;
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxRolloverFiles)} must be positive.");
_maxRolloverFiles = value;
}
}
/// <summary>
/// Gets or sets the filename prefix to use for log files.
/// Defaults to <c>EonaCat_</c>.
/// </summary>
public string FileNamePrefix { get; set; } = "EonaCat";
/// <summary>
/// The directory in which log files will be written, relative to the app process.
/// Default to <c>executablePath\logs</c>
/// </summary>
/// <returns></returns>
public string LogDirectory { get; set; } = Path.Combine(DefaultPath, "logs");
}

View File

@@ -8,244 +8,207 @@ using EonaCat.Logger.EonaCatCoreLogger.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace EonaCat.Logger.EonaCatCoreLogger
namespace EonaCat.Logger.EonaCatCoreLogger;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
/// <summary>
/// An <see cref="ILoggerProvider" /> that writes logs to a file
/// </summary>
[ProviderAlias("EonaCatFileLogger")]
public class FileLoggerProvider : BatchingLoggerProvider
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
private static readonly object WriteLock = new();
private static readonly object RollOverLock = new();
private readonly string _fileNamePrefix;
private readonly int _maxFileSize;
private readonly int _maxRetainedFiles;
private readonly int _maxRolloverFiles;
private readonly int _maxTries;
private readonly string _path;
private string _logFile;
private bool _rollingOver;
private int _rollOverCount;
/// <summary>
/// An <see cref="ILoggerProvider" /> that writes logs to a file
/// Creates an instance of the <see cref="FileLoggerProvider" />
/// </summary>
[ProviderAlias("EonaCatFileLogger")]
public class FileLoggerProvider : BatchingLoggerProvider
/// <param name="options">The options object controlling the logger</param>
public FileLoggerProvider(IOptions<FileLoggerOptions> options) : base(options)
{
private readonly string _path;
private readonly string _fileNamePrefix;
private readonly int _maxFileSize;
private readonly int _maxRetainedFiles;
private readonly int _maxRolloverFiles;
private readonly int _maxTries;
private int _rollOverCount = 0;
private static readonly object WriteLock = new object();
private static readonly object RollOverLock = new object();
private string _logFile;
private bool _rollingOver;
var loggerOptions = options.Value;
_path = loggerOptions.LogDirectory;
_fileNamePrefix = loggerOptions.FileNamePrefix;
_maxFileSize = loggerOptions.FileSizeLimit;
_maxRetainedFiles = loggerOptions.RetainedFileCountLimit;
_maxRolloverFiles = loggerOptions.MaxRolloverFiles;
_maxTries = loggerOptions.MaxWriteTries;
}
/// <summary>
/// The file to which log messages should be appended.
/// </summary>
public string LogFile
/// <summary>
/// The file to which log messages should be appended.
/// </summary>
public string LogFile
{
get => _logFile;
set
{
get
_logFile = value;
if (!string.IsNullOrEmpty(_logFile))
{
return _logFile;
}
set
{
_logFile = value;
if (!string.IsNullOrEmpty(_logFile))
{
string dir = Path.GetDirectoryName(_logFile);
if (!string.IsNullOrEmpty(dir))
{
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
}
}
}
}
/// <summary>
/// Creates an instance of the <see cref="FileLoggerProvider" />
/// </summary>
/// <param name="options">The options object controlling the logger</param>
public FileLoggerProvider(IOptions<FileLoggerOptions> options) : base(options)
{
FileLoggerOptions loggerOptions = options.Value;
_path = loggerOptions.LogDirectory;
_fileNamePrefix = loggerOptions.FileNamePrefix;
_maxFileSize = loggerOptions.FileSizeLimit;
_maxRetainedFiles = loggerOptions.RetainedFileCountLimit;
_maxRolloverFiles = loggerOptions.MaxRolloverFiles;
_maxTries = loggerOptions.MaxWriteTries;
}
/// <inheritdoc />
protected override async Task WriteMessagesAsync(IEnumerable<LogMessage> messages, CancellationToken cancellationToken)
{
Directory.CreateDirectory(_path);
foreach (IGrouping<(int Year, int Month, int Day), LogMessage> group in messages.GroupBy(GetGrouping))
{
LogFile = GetFullName(group.Key);
FileInfo fileInfo = new FileInfo(LogFile);
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);
}
lock (WriteLock)
{
int tries = 0;
bool completed = false;
while (!completed)
{
try
{
System.IO.StreamWriter file = new System.IO.StreamWriter(LogFile, true);
foreach (LogMessage item in group)
{
file.Write(item.Message);
}
file.Close();
completed = true;
}
catch (Exception exc)
{
tries++;
Task.Delay(100);
if (tries >= _maxTries)
throw;
}
}
}
DeleteOldLogFiles();
}
}
private string GetFullName((int Year, int Month, int Day) group)
{
bool hasPrefix = !string.IsNullOrWhiteSpace(_fileNamePrefix);
if (hasPrefix)
{
return Path.Combine(_path, $"{_fileNamePrefix}_{group.Year:0000}{group.Month:00}{group.Day:00}.log");
}
else
{
return Path.Combine(_path, $"{group.Year:0000}{group.Month:00}{group.Day:00}.log");
}
}
private (int Year, int Month, int Day) GetGrouping(LogMessage message)
{
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);
var destination = new FileInfo(copyToPath);
destination.CreationTime = origin.CreationTime;
destination.LastWriteTime = origin.LastWriteTime;
destination.LastAccessTime = origin.LastAccessTime;
}
/// <summary>
/// Rollover logFiles
/// </summary>
protected 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;
}
}
}
/// <summary>
/// Deletes old log files, keeping a number of files defined by <see cref="FileLoggerOptions.RetainedFileCountLimit" />
/// </summary>
protected void DeleteOldLogFiles()
{
if (_maxRetainedFiles > 0)
{
bool hasPrefix = !string.IsNullOrWhiteSpace(_fileNamePrefix);
IEnumerable<FileInfo> files = null;
if (hasPrefix)
{
files = new DirectoryInfo(_path).GetFiles(_fileNamePrefix + "*");
}
else
{
files = new DirectoryInfo(_path).GetFiles("*");
}
files = files.OrderByDescending(file => file.Name).Skip(_maxRetainedFiles);
foreach (FileInfo item in files)
{
item.Delete();
}
var dir = Path.GetDirectoryName(_logFile);
if (!string.IsNullOrEmpty(dir))
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
}
}
}
/// <inheritdoc />
protected override async Task WriteMessagesAsync(IEnumerable<LogMessage> messages,
CancellationToken cancellationToken)
{
Directory.CreateDirectory(_path);
foreach (var group in messages.GroupBy(GetGrouping))
{
LogFile = GetFullName(group.Key);
var fileInfo = new FileInfo(LogFile);
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);
lock (WriteLock)
{
var tries = 0;
var completed = false;
while (!completed)
try
{
var file = new StreamWriter(LogFile, true);
foreach (var item in group) file.Write(item.Message);
file.Close();
completed = true;
}
catch (Exception exc)
{
tries++;
Task.Delay(100);
if (tries >= _maxTries)
throw;
}
}
DeleteOldLogFiles();
}
}
private string GetFullName((int Year, int Month, int Day) group)
{
var hasPrefix = !string.IsNullOrWhiteSpace(_fileNamePrefix);
if (hasPrefix)
return Path.Combine(_path, $"{_fileNamePrefix}_{group.Year:0000}{group.Month:00}{group.Day:00}.log");
return Path.Combine(_path, $"{group.Year:0000}{group.Month:00}{group.Day:00}.log");
}
private (int Year, int Month, int Day) GetGrouping(LogMessage message)
{
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);
var destination = new FileInfo(copyToPath);
destination.CreationTime = origin.CreationTime;
destination.LastWriteTime = origin.LastWriteTime;
destination.LastAccessTime = origin.LastAccessTime;
}
/// <summary>
/// Rollover logFiles
/// </summary>
protected void MoveRolloverLogFiles()
{
if (_maxRolloverFiles > 0 && _rollOverCount >= 0)
if (_rollOverCount >= _maxRolloverFiles)
{
var maxRollover = _rollOverCount;
var 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 (var 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;
}
}
/// <summary>
/// Deletes old log files, keeping a number of files defined by <see cref="FileLoggerOptions.RetainedFileCountLimit" />
/// </summary>
protected void DeleteOldLogFiles()
{
if (_maxRetainedFiles > 0)
{
var hasPrefix = !string.IsNullOrWhiteSpace(_fileNamePrefix);
IEnumerable<FileInfo> files = null;
if (hasPrefix)
files = new DirectoryInfo(_path).GetFiles(_fileNamePrefix + "*");
else
files = new DirectoryInfo(_path).GetFiles("*");
files = files.OrderByDescending(file => file.Name).Skip(_maxRetainedFiles);
foreach (var item in files) item.Delete();
}
}
}

View File

@@ -4,68 +4,67 @@ using EonaCat.Logger.Extensions;
using EonaCat.Logger.Managers;
using Microsoft.Extensions.Logging;
namespace EonaCat.Logger.EonaCatCoreLogger.Internal
namespace EonaCat.Logger.EonaCatCoreLogger.Internal;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public class BatchingLogger : ILogger
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
private readonly string _category;
private readonly BatchingLoggerProvider _provider;
private LoggerSettings _loggerSettings;
public class BatchingLogger : ILogger
public BatchingLogger(BatchingLoggerProvider loggerProvider, string categoryName, LoggerSettings loggerSettings)
{
private LoggerSettings _loggerSettings;
private readonly BatchingLoggerProvider _provider;
private readonly string _category;
private DateTimeOffset CurrentDateTimeOffset => _loggerSettings.UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
private DateTime CurrentDateTme => _loggerSettings.UseLocalTime ? DateTime.Now : DateTime.UtcNow;
_loggerSettings = loggerSettings;
_provider = loggerProvider;
_category = categoryName;
}
public BatchingLogger(BatchingLoggerProvider loggerProvider, string categoryName, LoggerSettings loggerSettings)
private DateTimeOffset CurrentDateTimeOffset =>
_loggerSettings.UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
private DateTime CurrentDateTme => _loggerSettings.UseLocalTime ? DateTime.Now : DateTime.UtcNow;
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel != LogLevel.None;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception,
Func<TState, Exception, string> formatter)
{
Log(CurrentDateTimeOffset, logLevel, eventId, state, exception, formatter);
}
public void Log<TState>(DateTimeOffset timestamp, LogLevel logLevel, EventId eventId, TState state,
Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel)) return;
if (_loggerSettings == null) _loggerSettings = new LoggerSettings();
var message = LogHelper.FormatMessageWithHeader(_loggerSettings, logLevel.FromLogLevel(),
formatter(state, exception), timestamp.DateTime) + Environment.NewLine;
if (exception != null) message = exception.FormatExceptionToMessage() + Environment.NewLine;
_provider.AddMessage(timestamp, message);
var currentMessage = new EonaCatLogMessage
{
_loggerSettings = loggerSettings;
_provider = loggerProvider;
_category = categoryName;
}
DateTime = timestamp.DateTime,
Message = message,
LogType = logLevel.FromLogLevel()
};
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel != LogLevel.None;
}
public void Log<TState>(DateTimeOffset timestamp, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel)) return;
if (_loggerSettings == null)
{
_loggerSettings = new LoggerSettings();
}
var message = LogHelper.FormatMessageWithHeader(_loggerSettings, logLevel.FromLogLevel(), formatter(state, exception), timestamp.DateTime) + Environment.NewLine;
if (exception != null)
{
message = exception.FormatExceptionToMessage() + Environment.NewLine;
}
_provider.AddMessage(timestamp, message);
var currentMessage = new EonaCatLogMessage
{
DateTime = timestamp.DateTime,
Message = message,
LogType = logLevel.FromLogLevel()
};
if (_loggerSettings == null) return;
currentMessage.Origin = string.IsNullOrWhiteSpace(_loggerSettings.LogOrigin) ? "BatchingLogger" : _loggerSettings.LogOrigin;
_loggerSettings?.OnLogEvent(currentMessage);
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
Log(CurrentDateTimeOffset, logLevel, eventId, state, exception, formatter);
}
currentMessage.Origin = string.IsNullOrWhiteSpace(_loggerSettings.LogOrigin)
? "BatchingLogger"
: _loggerSettings.LogOrigin;
_loggerSettings?.OnLogEvent(currentMessage);
}
}

View File

@@ -1,48 +1,35 @@
using System;
namespace EonaCat.Logger.EonaCatCoreLogger.Internal
namespace EonaCat.Logger.EonaCatCoreLogger.Internal;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public class BatchingLoggerOptions
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
private TimeSpan _flushPeriod = TimeSpan.FromMilliseconds(100);
public class BatchingLoggerOptions
/// <summary>
/// Gets or sets the period after which logs will be flushed to the store.
/// </summary>
public TimeSpan FlushPeriod
{
private int _batchSize = 0;
private TimeSpan _flushPeriod = TimeSpan.FromMilliseconds(100);
get => _flushPeriod;
/// <summary>
/// Gets or sets the period after which logs will be flushed to the store.
/// </summary>
public TimeSpan FlushPeriod
set
{
get => _flushPeriod;
set
{
if (value <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FlushPeriod)} must be positive.");
}
_flushPeriod = value;
}
if (value <= TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FlushPeriod)} must be positive.");
_flushPeriod = value;
}
/// <summary>
/// Gets or sets a maximum number of events to include in a single batch or less than 1 for no limit.
/// </summary>
public int BatchSize
{
get => _batchSize;
set
{
_batchSize = value;
}
}
/// <summary>
/// Gets or sets value indicating if logger accepts and queues writes.
/// </summary>
public bool IsEnabled { get; set; }
}
/// <summary>
/// Gets or sets a maximum number of events to include in a single batch or less than 1 for no limit.
/// </summary>
public int BatchSize { get; set; } = 0;
/// <summary>
/// Gets or sets value indicating if logger accepts and queues writes.
/// </summary>
public bool IsEnabled { get; set; }
}

View File

@@ -7,161 +7,151 @@ using EonaCat.Logger.Managers;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace EonaCat.Logger.EonaCatCoreLogger.Internal
namespace EonaCat.Logger.EonaCatCoreLogger.Internal;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
private readonly int _batchSize;
public abstract class BatchingLoggerProvider : ILoggerProvider, IDisposable
private readonly List<LogMessage> _currentBatch = new();
private CancellationTokenSource _cancellationTokenSource;
private LoggerSettings _loggerSettings;
private ConcurrentQueue<LogMessage> _messageQueue;
private Task _outputTask;
protected BatchingLoggerProvider(IOptions<BatchingLoggerOptions> options)
{
protected DateTimeOffset CurrentDateTimeOffset => UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
protected DateTime CurrentDateTme => UseLocalTime ? DateTime.Now : DateTime.UtcNow;
var loggerOptions = options.Value;
private readonly List<LogMessage> _currentBatch = new List<LogMessage>();
private readonly TimeSpan _interval;
private readonly int _batchSize;
if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod),
$"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");
private ConcurrentQueue<LogMessage> _messageQueue;
private Task _outputTask;
private CancellationTokenSource _cancellationTokenSource;
private LoggerSettings _loggerSettings;
if (options.Value is FileLoggerOptions fileLoggerOptions) UseLocalTime = fileLoggerOptions.UseLocalTime;
_batchSize = loggerOptions.BatchSize;
protected bool UseLocalTime { get; set; }
Start();
}
protected BatchingLoggerProvider(IOptions<BatchingLoggerOptions> options)
protected DateTimeOffset CurrentDateTimeOffset => UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow;
protected DateTime CurrentDateTme => UseLocalTime ? DateTime.Now : DateTime.UtcNow;
protected bool UseLocalTime { get; set; }
protected LoggerSettings LoggerSettings
{
get
{
BatchingLoggerOptions loggerOptions = options.Value;
if (_loggerSettings != null) return _loggerSettings;
if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
{
throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod), $"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");
}
if (options.Value is FileLoggerOptions fileLoggerOptions)
{
UseLocalTime = fileLoggerOptions.UseLocalTime;
}
_interval = loggerOptions.FlushPeriod;
_batchSize = loggerOptions.BatchSize;
Start();
_loggerSettings = new LoggerSettings();
_loggerSettings.UseLocalTime = UseLocalTime;
return _loggerSettings;
}
protected LoggerSettings LoggerSettings
{
get
{
if (_loggerSettings != null) return _loggerSettings;
set => _loggerSettings = value;
}
_loggerSettings = new LoggerSettings();
_loggerSettings.UseLocalTime = UseLocalTime;
return _loggerSettings;
public bool IsStarted { get; set; }
public void Dispose()
{
while (!_messageQueue.IsEmpty) Task.Delay(10);
StopAsync().GetAwaiter().GetResult();
GC.SuppressFinalize(this);
}
public ILogger CreateLogger(string categoryName)
{
return new BatchingLogger(this, categoryName, LoggerSettings);
}
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;
while (limit > 0 && _messageQueue.TryDequeue(out var message))
{
_currentBatch.Add(message);
limit--;
}
set => _loggerSettings = value;
}
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)
{
int limit = _batchSize <= 0 ? int.MaxValue : _batchSize;
while (limit > 0 && _messageQueue.TryDequeue(out LogMessage message))
if (_currentBatch.Count > 0)
try
{
_currentBatch.Add(message);
limit--;
await WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token).ConfigureAwait(false);
_currentBatch.Clear();
}
catch
{
// ignored
}
if (_currentBatch.Count > 0)
{
bool isBatchWritten = false;
try
{
await WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token).ConfigureAwait(false);
isBatchWritten = true;
}
catch
{
// ignored
}
if (isBatchWritten)
{
_currentBatch.Clear();
}
}
await IntervalAsync(_interval, _cancellationTokenSource.Token).ConfigureAwait(false);
}
await WriteMessagesAsync(new List<LogMessage> { new LogMessage { Message = $"[{DllInfo.ApplicationName}] {DllInfo.ApplicationName} stopped.{Environment.NewLine}", Timestamp = CurrentDateTimeOffset } }, _cancellationTokenSource.Token).ConfigureAwait(false);
}
protected virtual Task IntervalAsync(TimeSpan interval, CancellationToken cancellationToken)
{
return Task.Delay(interval, cancellationToken);
}
internal void AddMessage(DateTimeOffset timestamp, string message)
{
_messageQueue.Enqueue(new LogMessage { Message = message, Timestamp = timestamp });
}
private void Start()
{
_messageQueue = new ConcurrentQueue<LogMessage>();
_cancellationTokenSource = new CancellationTokenSource();
_outputTask = Task.Factory.StartNew(
ProcessLogQueueAsync,
null,
TaskCreationOptions.LongRunning);
}
private async Task StopAsync()
{
_cancellationTokenSource.Cancel();
try
{
await _outputTask.ConfigureAwait(false);
}
catch (TaskCanceledException)
{
}
catch (AggregateException exception) when (exception.InnerExceptions.Count == 1 && exception.InnerExceptions[0] is TaskCanceledException)
{
}
}
public void Dispose()
{
while (!_messageQueue.IsEmpty)
{
_messageQueue.TryDequeue(out _);
}
StopAsync().GetAwaiter().GetResult();
GC.SuppressFinalize(this);
}
~BatchingLoggerProvider()
{
Dispose();
}
public ILogger CreateLogger(string categoryName)
{
return new BatchingLogger(this, categoryName, LoggerSettings);
Thread.Sleep(10);
}
}
internal void AddMessage(DateTimeOffset timestamp, string message)
{
_messageQueue.Enqueue(new LogMessage { Message = message, Timestamp = timestamp });
}
private void Start()
{
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);
});
}
private async Task StopAsync()
{
_cancellationTokenSource.Cancel();
try
{
while (_outputTask.Status != TaskStatus.RanToCompletion && _outputTask.Status != TaskStatus.Canceled)
{
await _outputTask.ConfigureAwait(false);
await Task.Delay(100);
}
}
catch (TaskCanceledException)
{
}
catch (AggregateException exception) when (exception.InnerExceptions.Count == 1 &&
exception.InnerExceptions[0] is TaskCanceledException)
{
}
}
~BatchingLoggerProvider()
{
Dispose();
}
}

View File

@@ -1,13 +1,11 @@
using System;
namespace EonaCat.Logger.EonaCatCoreLogger.Internal
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
namespace EonaCat.Logger.EonaCatCoreLogger.Internal;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public struct LogMessage
{
public DateTimeOffset Timestamp { get; set; }
public string Message { get; set; }
}
public struct LogMessage
{
public DateTimeOffset Timestamp { get; set; }
public string Message { get; set; }
}

View File

@@ -1,21 +1,20 @@
using System;
using EonaCat.Logger.Helpers;
namespace EonaCat.Logger.EonaCatCoreLogger.Models
namespace EonaCat.Logger.EonaCatCoreLogger.Models;
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public class EonaCatLogMessage
{
// This file is part of the EonaCat project(s) which is released under the Apache License.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public DateTime DateTime { get; set; }
public string Message { get; set; }
public ELogType LogType { get; set; }
public string Origin { get; set; }
public class EonaCatLogMessage
public override string ToString()
{
public DateTime DateTime { get; set; }
public string Message { get; set; }
public ELogType LogType { get; set; }
public string Origin { get; set; }
public override string ToString()
{
return $"[{DateTime.ToString(Constants.DateTimeFormats.LOGGING)}] [{EnumHelper<ELogType>.ToString(LogType)}] {Message}";
}
return
$"[{DateTime.ToString(Constants.DateTimeFormats.LOGGING)}] [{EnumHelper<ELogType>.ToString(LogType)}] {Message}";
}
}