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

@@ -1,13 +1,11 @@
namespace EonaCat.Logger namespace EonaCat.Logger;
{ // This file is part of the EonaCat project(s) which is released under the Apache License.
// 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.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public static class Constants public static class Constants
{
public static class DateTimeFormats
{ {
public static class DateTimeFormats public static string LOGGING { get; } = "yyyy-MM-dd HH:mm:ss";
{
public static string LOGGING { get; } = "yyyy-MM-dd HH:mm:ss";
}
} }
} }

View File

@@ -1,24 +1,22 @@
namespace EonaCat.Logger namespace EonaCat.Logger;
// 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 static class DllInfo
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. public const string NAME = "EonaCatLogger";
// See the LICENSE file or go to https://EonaCat.com/License for full license details. public const string VERSION = "1.1.0";
public static class DllInfo static DllInfo()
{ {
public const string NAME = "EonaCatLogger"; var isDebug = false;
public const string VERSION = "1.1.0";
static DllInfo()
{
bool isDebug = false;
#if DEBUG #if DEBUG
isDebug = true; isDebug = true;
#endif #endif
VersionName = isDebug ? "DEBUG" : "RELEASE"; VersionName = isDebug ? "DEBUG" : "RELEASE";
}
internal static string VersionName { get; }
public static string ApplicationName { get; internal set; } = "EonaCatLogger";
} }
internal static string VersionName { get; }
public static string ApplicationName { get; internal set; } = "EonaCatLogger";
} }

View File

@@ -1,188 +1,192 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
namespace EonaCat.Logger namespace EonaCat.Logger;
// 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 enum ELogType
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. NONE = 0,
// See the LICENSE file or go to https://EonaCat.com/License for full license details. INFO = 1,
WARNING = 2,
ERROR = 3,
TRAFFIC = 4,
DEBUG = 5,
CRITICAL = 6,
TRACE = 7
}
public enum ELogType public static class LogTypeConverter
{
public static ELogType FromLogLevel(this LogLevel logLevel)
{ {
NONE = 0, switch (logLevel)
INFO = 1,
WARNING = 2,
ERROR = 3,
TRAFFIC = 4,
DEBUG = 5,
CRITICAL = 6,
TRACE = 7
}
public static class LogTypeConverter
{
public static ELogType FromLogLevel(this LogLevel logLevel)
{ {
switch (logLevel) case LogLevel.None:
{ return ELogType.NONE;
case LogLevel.None: case LogLevel.Error:
return ELogType.NONE; return ELogType.ERROR;
case LogLevel.Error: case LogLevel.Debug:
return ELogType.ERROR; return ELogType.DEBUG;
case LogLevel.Debug: case LogLevel.Critical:
return ELogType.DEBUG; return ELogType.CRITICAL;
case LogLevel.Critical: case LogLevel.Warning:
return ELogType.CRITICAL; return ELogType.WARNING;
case LogLevel.Warning: case LogLevel.Trace:
return ELogType.WARNING; return ELogType.TRACE;
case LogLevel.Trace: case LogLevel.Information:
return ELogType.TRACE; return ELogType.INFO;
case LogLevel.Information: default:
return ELogType.INFO; return ELogType.TRAFFIC;
default:
return ELogType.TRAFFIC;
}
}
public static LogLevel ToLogLevel(this ELogType logLevel)
{
switch (logLevel)
{
case ELogType.NONE:
return LogLevel.None;
case ELogType.ERROR:
return LogLevel.Error;
case ELogType.DEBUG:
return LogLevel.Debug;
case ELogType.CRITICAL:
return LogLevel.Critical;
case ELogType.WARNING:
return LogLevel.Warning;
case ELogType.TRACE:
return LogLevel.Trace;
case ELogType.TRAFFIC:
return LogLevel.Trace;
case ELogType.INFO:
return LogLevel.Information;
default:
return LogLevel.Information;
}
}
public static string ToString(this ELogType logLevel)
{
switch (logLevel)
{
case ELogType.NONE:
return "NONE";
case ELogType.ERROR:
return "ERROR";
case ELogType.DEBUG:
return "DEBUG";
case ELogType.CRITICAL:
return "CRITICAL";
case ELogType.WARNING:
return "WARNING";
case ELogType.TRACE:
return "TRACE";
case ELogType.TRAFFIC:
return "TRAFFIC";
case ELogType.INFO:
return "INFO";
default:
return "INFO";
}
}
public static ELogType FromSeverity(this ESeverity logLevel)
{
switch (logLevel)
{
case ESeverity.Debug:
return ELogType.DEBUG;
case ESeverity.Warn:
return ELogType.WARNING;
case ESeverity.Emergency:
return ELogType.TRACE;
case ESeverity.Critical:
return ELogType.CRITICAL;
case ESeverity.Alert:
return ELogType.TRAFFIC;
case ESeverity.Error:
return ELogType.ERROR;
default:
return ELogType.INFO;
}
}
public static int ToGrayLogLevel(this ELogType logLevel)
{
// Loglevel to GELF format
switch (logLevel.ToString())
{
case "TRAFFIC": return 7;
case "TRACE": return 7;
case "DEBUG": return 7;
case "INFO": return 6;
case "WARNING": return 4;
case "ERROR": return 3;
case "CRITICAL": return 2;
default: return 6; // Default to INFO
}
}
public static ESeverity ToSeverity(this ELogType logLevel)
{
switch (logLevel)
{
case ELogType.DEBUG:
return ESeverity.Debug;
case ELogType.WARNING:
return ESeverity.Warn;
case ELogType.CRITICAL:
return ESeverity.Critical;
case ELogType.TRACE:
return ESeverity.Emergency;
case ELogType.ERROR:
return ESeverity.Error;
case ELogType.TRAFFIC:
return ESeverity.Alert;
default:
return ESeverity.Info;
}
} }
} }
/// <summary> public static LogLevel ToLogLevel(this ELogType logLevel)
/// Message severity.
/// </summary>
public enum ESeverity
{ {
/// <summary> switch (logLevel)
/// Debug messages. {
/// </summary> case ELogType.NONE:
Debug = 0, return LogLevel.None;
/// <summary> case ELogType.ERROR:
/// Informational messages. return LogLevel.Error;
/// </summary> case ELogType.DEBUG:
Info = 1, return LogLevel.Debug;
/// <summary> case ELogType.CRITICAL:
/// Warning messages. return LogLevel.Critical;
/// </summary> case ELogType.WARNING:
Warn = 2, return LogLevel.Warning;
/// <summary> case ELogType.TRACE:
/// Error messages. return LogLevel.Trace;
/// </summary> case ELogType.TRAFFIC:
Error = 3, return LogLevel.Trace;
/// <summary> case ELogType.INFO:
/// Alert messages. return LogLevel.Information;
/// </summary> default:
Alert = 4, return LogLevel.Information;
/// <summary> }
/// Critical messages. }
/// </summary>
Critical = 5, public static string ToString(this ELogType logLevel)
/// <summary> {
/// Emergency messages. switch (logLevel)
/// </summary> {
Emergency = 6 case ELogType.NONE:
return "NONE";
case ELogType.ERROR:
return "ERROR";
case ELogType.DEBUG:
return "DEBUG";
case ELogType.CRITICAL:
return "CRITICAL";
case ELogType.WARNING:
return "WARNING";
case ELogType.TRACE:
return "TRACE";
case ELogType.TRAFFIC:
return "TRAFFIC";
case ELogType.INFO:
return "INFO";
default:
return "INFO";
}
}
public static ELogType FromSeverity(this ESeverity logLevel)
{
switch (logLevel)
{
case ESeverity.Debug:
return ELogType.DEBUG;
case ESeverity.Warn:
return ELogType.WARNING;
case ESeverity.Emergency:
return ELogType.TRACE;
case ESeverity.Critical:
return ELogType.CRITICAL;
case ESeverity.Alert:
return ELogType.TRAFFIC;
case ESeverity.Error:
return ELogType.ERROR;
default:
return ELogType.INFO;
}
}
public static int ToGrayLogLevel(this ELogType logLevel)
{
// Loglevel to GELF format
switch (logLevel.ToString())
{
case "TRAFFIC": return 7;
case "TRACE": return 7;
case "DEBUG": return 7;
case "INFO": return 6;
case "WARNING": return 4;
case "ERROR": return 3;
case "CRITICAL": return 2;
default: return 6; // Default to INFO
}
}
public static ESeverity ToSeverity(this ELogType logLevel)
{
switch (logLevel)
{
case ELogType.DEBUG:
return ESeverity.Debug;
case ELogType.WARNING:
return ESeverity.Warn;
case ELogType.CRITICAL:
return ESeverity.Critical;
case ELogType.TRACE:
return ESeverity.Emergency;
case ELogType.ERROR:
return ESeverity.Error;
case ELogType.TRAFFIC:
return ESeverity.Alert;
default:
return ESeverity.Info;
}
} }
} }
/// <summary>
/// Message severity.
/// </summary>
public enum ESeverity
{
/// <summary>
/// Debug messages.
/// </summary>
Debug = 0,
/// <summary>
/// Informational messages.
/// </summary>
Info = 1,
/// <summary>
/// Warning messages.
/// </summary>
Warn = 2,
/// <summary>
/// Error messages.
/// </summary>
Error = 3,
/// <summary>
/// Alert messages.
/// </summary>
Alert = 4,
/// <summary>
/// Critical messages.
/// </summary>
Critical = 5,
/// <summary>
/// Emergency messages.
/// </summary>
Emergency = 6
}

View File

@@ -3,7 +3,7 @@
<TargetFrameworks>.netstandard2.1; net6.0; net7.0; net8.0; net4.8;</TargetFrameworks> <TargetFrameworks>.netstandard2.1; net6.0; net7.0; net8.0; net4.8;</TargetFrameworks>
<ApplicationIcon>icon.ico</ApplicationIcon> <ApplicationIcon>icon.ico</ApplicationIcon>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Version>1.2.5</Version> <FileVersion>1.2.5</FileVersion>
<Authors>EonaCat (Jeroen Saey)</Authors> <Authors>EonaCat (Jeroen Saey)</Authors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Company>EonaCat (Jeroen Saey)</Company> <Company>EonaCat (Jeroen Saey)</Company>
@@ -24,7 +24,7 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<EVRevisionFormat>1.0.0.0+{chash:10}.{c:ymd}</EVRevisionFormat> <EVRevisionFormat>1.2.5+{chash:10}.{c:ymd}</EVRevisionFormat>
<EVDefault>true</EVDefault> <EVDefault>true</EVDefault>
<EVInfo>true</EVInfo> <EVInfo>true</EVInfo>
<EVTagMatch>v[0-9]*</EVTagMatch> <EVTagMatch>v[0-9]*</EVTagMatch>

View File

@@ -2,40 +2,34 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; 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. private static ILoggingBuilder AddEonaCatFileLogger(this ILoggingBuilder builder)
// See the LICENSE file or go to https://EonaCat.com/License for full license details. {
builder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
return builder;
}
/// <summary> /// <summary>
/// Extensions for adding the <see cref="FileLoggerProvider" /> to the <see cref="ILoggingBuilder" /> /// Adds the EonaCat File Logger named 'EonaCatFileLogger' to the factory.
/// </summary> /// </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) if (fileLoggerOptions == null) fileLoggerOptions = new FileLoggerOptions();
{
builder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
return builder;
}
/// <summary> if (!string.IsNullOrWhiteSpace(filenamePrefix)) fileLoggerOptions.FileNamePrefix = filenamePrefix;
/// Adds the EonaCat File Logger named 'EonaCatFileLogger' to the factory. builder.AddEonaCatFileLogger(options =>
/// </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 =>
{ {
options.FileNamePrefix = fileLoggerOptions.FileNamePrefix; options.FileNamePrefix = fileLoggerOptions.FileNamePrefix;
options.FlushPeriod = fileLoggerOptions.FlushPeriod; options.FlushPeriod = fileLoggerOptions.FlushPeriod;
@@ -49,26 +43,22 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Extensions
options.MaxRolloverFiles = fileLoggerOptions.MaxRolloverFiles; options.MaxRolloverFiles = fileLoggerOptions.MaxRolloverFiles;
options.UseLocalTime = fileLoggerOptions.UseLocalTime; options.UseLocalTime = fileLoggerOptions.UseLocalTime;
} }
);
return builder;
}
); /// <summary>
return builder; /// 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> return builder;
/// 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;
}
} }
} }

View File

@@ -2,108 +2,93 @@
using System.IO; using System.IO;
using EonaCat.Logger.EonaCatCoreLogger.Internal; 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. private int _fileSizeLimit = 200 * 1024 * 1024;
// See the LICENSE file or go to https://EonaCat.com/License for full license details. private int _maxRolloverFiles = 10;
private int _retainedFileCountLimit = 50;
public static string DefaultPath =>
AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
/// <summary> /// <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> /// </summary>
public class FileLoggerOptions : BatchingLoggerOptions public int FileSizeLimit
{ {
private int _fileSizeLimit = 200 * 1024 * 1024; get => _fileSizeLimit;
private int _retainedFileCountLimit = 50;
private int _maxRolloverFiles = 10;
private int _maxTries = 3;
public static string DefaultPath => AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory; set
/// <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
{ {
get => _fileSizeLimit; if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FileSizeLimit)} must be positive.");
set _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.Logging;
using Microsoft.Extensions.Options; 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. private static readonly object WriteLock = new();
// See the LICENSE file or go to https://EonaCat.com/License for full license details. 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> /// <summary>
/// An <see cref="ILoggerProvider" /> that writes logs to a file /// Creates an instance of the <see cref="FileLoggerProvider" />
/// </summary> /// </summary>
[ProviderAlias("EonaCatFileLogger")] /// <param name="options">The options object controlling the logger</param>
public class FileLoggerProvider : BatchingLoggerProvider public FileLoggerProvider(IOptions<FileLoggerOptions> options) : base(options)
{ {
private readonly string _path; var loggerOptions = options.Value;
private readonly string _fileNamePrefix; _path = loggerOptions.LogDirectory;
private readonly int _maxFileSize; _fileNamePrefix = loggerOptions.FileNamePrefix;
private readonly int _maxRetainedFiles; _maxFileSize = loggerOptions.FileSizeLimit;
private readonly int _maxRolloverFiles; _maxRetainedFiles = loggerOptions.RetainedFileCountLimit;
private readonly int _maxTries; _maxRolloverFiles = loggerOptions.MaxRolloverFiles;
private int _rollOverCount = 0; _maxTries = loggerOptions.MaxWriteTries;
private static readonly object WriteLock = new object(); }
private static readonly object RollOverLock = new object();
private string _logFile;
private bool _rollingOver;
/// <summary> /// <summary>
/// The file to which log messages should be appended. /// The file to which log messages should be appended.
/// </summary> /// </summary>
public string LogFile public string LogFile
{
get => _logFile;
set
{ {
get _logFile = value;
if (!string.IsNullOrEmpty(_logFile))
{ {
return _logFile; var dir = Path.GetDirectoryName(_logFile);
} if (!string.IsNullOrEmpty(dir))
set if (!Directory.Exists(dir))
{ Directory.CreateDirectory(dir);
_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();
}
} }
} }
} }
/// <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 EonaCat.Logger.Managers;
using Microsoft.Extensions.Logging; 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. private readonly string _category;
// See the LICENSE file or go to https://EonaCat.com/License for full license details. private readonly BatchingLoggerProvider _provider;
private LoggerSettings _loggerSettings;
public class BatchingLogger : ILogger public BatchingLogger(BatchingLoggerProvider loggerProvider, string categoryName, LoggerSettings loggerSettings)
{ {
private LoggerSettings _loggerSettings; _loggerSettings = loggerSettings;
private readonly BatchingLoggerProvider _provider; _provider = loggerProvider;
private readonly string _category; _category = categoryName;
private DateTimeOffset CurrentDateTimeOffset => _loggerSettings.UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow; }
private DateTime CurrentDateTme => _loggerSettings.UseLocalTime ? DateTime.Now : DateTime.UtcNow;
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; DateTime = timestamp.DateTime,
_provider = loggerProvider; Message = message,
_category = categoryName; LogType = logLevel.FromLogLevel()
} };
public IDisposable BeginScope<TState>(TState state) currentMessage.Origin = string.IsNullOrWhiteSpace(_loggerSettings.LogOrigin)
{ ? "BatchingLogger"
return null; : _loggerSettings.LogOrigin;
} _loggerSettings?.OnLogEvent(currentMessage);
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);
}
} }
} }

View File

@@ -1,48 +1,35 @@
using System; 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. private TimeSpan _flushPeriod = TimeSpan.FromMilliseconds(100);
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
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; get => _flushPeriod;
private TimeSpan _flushPeriod = TimeSpan.FromMilliseconds(100);
/// <summary> set
/// Gets or sets the period after which logs will be flushed to the store.
/// </summary>
public TimeSpan FlushPeriod
{ {
get => _flushPeriod; if (value <= TimeSpan.Zero)
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FlushPeriod)} must be positive.");
set _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.Logging;
using Microsoft.Extensions.Options; 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. private readonly int _batchSize;
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
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; var loggerOptions = options.Value;
protected DateTime CurrentDateTme => UseLocalTime ? DateTime.Now : DateTime.UtcNow;
private readonly List<LogMessage> _currentBatch = new List<LogMessage>(); if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
private readonly TimeSpan _interval; throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod),
private readonly int _batchSize; $"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");
private ConcurrentQueue<LogMessage> _messageQueue; if (options.Value is FileLoggerOptions fileLoggerOptions) UseLocalTime = fileLoggerOptions.UseLocalTime;
private Task _outputTask; _batchSize = loggerOptions.BatchSize;
private CancellationTokenSource _cancellationTokenSource;
private LoggerSettings _loggerSettings;
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) _loggerSettings = new LoggerSettings();
{ _loggerSettings.UseLocalTime = UseLocalTime;
throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod), $"{nameof(loggerOptions.FlushPeriod)} must be longer than zero."); return _loggerSettings;
}
if (options.Value is FileLoggerOptions fileLoggerOptions)
{
UseLocalTime = fileLoggerOptions.UseLocalTime;
}
_interval = loggerOptions.FlushPeriod;
_batchSize = loggerOptions.BatchSize;
Start();
} }
protected LoggerSettings LoggerSettings set => _loggerSettings = value;
{ }
get
{
if (_loggerSettings != null) return _loggerSettings;
_loggerSettings = new LoggerSettings(); public bool IsStarted { get; set; }
_loggerSettings.UseLocalTime = UseLocalTime;
return _loggerSettings; 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; if (_currentBatch.Count > 0)
} try
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))
{ {
_currentBatch.Add(message); await WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token).ConfigureAwait(false);
limit--; _currentBatch.Clear();
}
catch
{
// ignored
} }
if (_currentBatch.Count > 0) Thread.Sleep(10);
{
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);
} }
} }
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; 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.
// 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.
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public struct LogMessage public struct LogMessage
{ {
public DateTimeOffset Timestamp { get; set; } public DateTimeOffset Timestamp { get; set; }
public string Message { get; set; } public string Message { get; set; }
}
} }

View File

@@ -1,21 +1,20 @@
using System; using System;
using EonaCat.Logger.Helpers; 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. public DateTime DateTime { get; set; }
// See the LICENSE file or go to https://EonaCat.com/License for full license details. 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; } return
public string Message { get; set; } $"[{DateTime.ToString(Constants.DateTimeFormats.LOGGING)}] [{EnumHelper<ELogType>.ToString(LogType)}] {Message}";
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}";
}
} }
} }

View File

@@ -1,9 +1,10 @@
using System; using System;
namespace EonaCat.Logger.Exceptions namespace EonaCat.Logger.Exceptions;
public class EonaCatLoggerAssertionException : Exception
{ {
public class EonaCatLoggerAssertionException : Exception public EonaCatLoggerAssertionException(string message) : base(message)
{ {
public EonaCatLoggerAssertionException(string message) : base(message) { }
} }
} }

View File

@@ -1,12 +1,11 @@
using System; using System;
namespace EonaCat.Logger.Extensions namespace EonaCat.Logger.Extensions;
public static class DateTimeExtensions
{ {
public static class DateTimeExtensions public static long ToUnixTimestamp(this DateTime dateTime)
{ {
public static long ToUnixTimestamp(this DateTime dateTime) return (long)(dateTime.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
{
return (long)(dateTime.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
}
} }
} }

View File

@@ -3,70 +3,73 @@ using System.Collections;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
namespace EonaCat.Logger.Extensions namespace EonaCat.Logger.Extensions;
public static class ExceptionExtensions
{ {
public static class ExceptionExtensions public static string FormatExceptionToMessage(this Exception exception, string module = null, string method = null)
{ {
public static string FormatExceptionToMessage(this Exception exception, string module = null, string method = null) if (exception == null)
{ return string.Empty;
if (exception == null)
return string.Empty;
var st = new StackTrace(exception, true); var st = new StackTrace(exception, true);
var frame = st.GetFrame(0); var frame = st.GetFrame(0);
int fileLine = frame.GetFileLineNumber(); var fileLine = frame.GetFileLineNumber();
string filename = frame.GetFileName(); var filename = frame.GetFileName();
StringBuilder sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine(); sb.AppendLine();
sb.AppendLine("--- Exception details provided by EonaCatLogger ---"); sb.AppendLine("--- Exception details provided by EonaCatLogger ---");
if (!string.IsNullOrEmpty(module)) if (!string.IsNullOrEmpty(module))
sb.AppendLine(" Module : " + module); sb.AppendLine(" Module : " + module);
if (!string.IsNullOrEmpty(method)) if (!string.IsNullOrEmpty(method))
sb.AppendLine(" Method : " + method); sb.AppendLine(" Method : " + method);
sb.Append(" Type : ").AppendLine(exception.GetType().ToString()); sb.Append(" Type : ").AppendLine(exception.GetType().ToString());
sb.Append(" Data : ").AppendLine(exception.Data != null && exception.Data.Count > 0 ? FormatExceptionData(exception.Data) : "(none)"); sb.Append(" Data : ").AppendLine(exception.Data != null && exception.Data.Count > 0
sb.Append(" Inner : ").AppendLine(exception.InnerException != null ? FormatInnerException(exception.InnerException) : "(null)"); ? FormatExceptionData(exception.Data)
sb.Append(" Message : ").AppendLine(exception.Message); : "(none)");
sb.Append(" Source : ").AppendLine(exception.Source); sb.Append(" Inner : ").AppendLine(exception.InnerException != null
sb.Append(" StackTrace : ").AppendLine(exception.StackTrace); ? FormatInnerException(exception.InnerException)
sb.Append(" Line : ").AppendLine(fileLine.ToString()); : "(null)");
sb.Append(" File : ").AppendLine(filename); sb.Append(" Message : ").AppendLine(exception.Message);
sb.Append(" ToString : ").AppendLine(exception.ToString()); sb.Append(" Source : ").AppendLine(exception.Source);
sb.AppendLine("---"); sb.Append(" StackTrace : ").AppendLine(exception.StackTrace);
sb.Append(" Line : ").AppendLine(fileLine.ToString());
sb.Append(" File : ").AppendLine(filename);
sb.Append(" ToString : ").AppendLine(exception.ToString());
sb.AppendLine("---");
return sb.ToString(); return sb.ToString();
} }
private static string FormatExceptionData(IDictionary data) private static string FormatExceptionData(IDictionary data)
{ {
StringBuilder sb = new StringBuilder(); var sb = new StringBuilder();
foreach (DictionaryEntry entry in data) foreach (DictionaryEntry entry in data)
{ sb.Append(" | ")
sb.Append(" | ") .Append(entry.Key)
.Append(entry.Key) .Append(": ")
.Append(": ") .AppendLine(entry.Value.ToString());
.AppendLine(entry.Value.ToString());
}
return sb.ToString(); return sb.ToString();
} }
private static string FormatInnerException(Exception innerException) private static string FormatInnerException(Exception innerException)
{ {
StringBuilder sb = new StringBuilder(); var sb = new StringBuilder();
sb.AppendLine(innerException.GetType().ToString()) sb.AppendLine(innerException.GetType().ToString())
.AppendLine(" Message : " + innerException.Message) .AppendLine(" Message : " + innerException.Message)
.AppendLine(" Source : " + innerException.Source) .AppendLine(" Source : " + innerException.Source)
.AppendLine(" StackTrace : " + innerException.StackTrace) .AppendLine(" StackTrace : " + innerException.StackTrace)
.AppendLine(" ToString : " + innerException.ToString()) .AppendLine(" ToString : " + innerException)
.Append(" Data : ") .Append(" Data : ")
.AppendLine(innerException.Data != null && innerException.Data.Count > 0 ? FormatExceptionData(innerException.Data) : "(none)"); .AppendLine(innerException.Data != null && innerException.Data.Count > 0
? FormatExceptionData(innerException.Data)
: "(none)");
return sb.ToString(); return sb.ToString();
}
} }
} }

View File

@@ -1,261 +1,194 @@
using System; using System;
using System.IO; using System.IO;
namespace EonaCat.Logger.Extensions namespace EonaCat.Logger.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.
public class OffsetStream : Stream
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. private const int BufferSize = 4096;
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public class OffsetStream : Stream public OffsetStream(Stream stream, long offset = 0, long length = 0, bool readOnly = false, bool ownStream = false)
{ {
private const int BufferSize = 4096; if (stream.CanSeek)
public OffsetStream(Stream stream, long offset = 0, long length = 0, bool readOnly = false, bool ownStream = false)
{ {
if (stream.CanSeek) if (offset > stream.Length) throw new EndOfStreamException();
{
if (offset > stream.Length)
{
throw new EndOfStreamException();
}
BaseStreamOffset = offset;
if (length > stream.Length - offset)
{
throw new EndOfStreamException();
}
if (length == 0)
{
Length1 = stream.Length - offset;
}
else
{
Length1 = length;
}
}
else
{
BaseStreamOffset = 0;
Length1 = length;
}
BaseStream = stream;
ReadOnly = readOnly;
OwnStream = ownStream;
}
protected override void Dispose(bool disposing)
{
if (Disposed)
{
return;
}
if (disposing)
{
if (OwnStream & (BaseStream != null))
{
BaseStream.Dispose();
}
}
Disposed = true;
base.Dispose(disposing);
}
public override bool CanRead => BaseStream.CanRead;
public override bool CanSeek => BaseStream.CanSeek;
public override bool CanWrite => BaseStream.CanWrite && !ReadOnly;
public override long Length => Length1;
public override long Position
{
get => Position1;
set
{
if (value > Length1)
{
throw new EndOfStreamException();
}
if (!BaseStream.CanSeek)
{
throw new NotSupportedException("Cannot seek stream.");
}
Position1 = value;
}
}
public override void Flush()
{
if (ReadOnly)
{
throw new IOException("OffsetStream is read only.");
}
BaseStream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (count < 1)
{
throw new ArgumentOutOfRangeException("Count cannot be less than 1.");
}
if (Position1 >= Length1)
{
return 0;
}
if (count > Length1 - Position1)
{
count = Convert.ToInt32(Length1 - Position1);
}
if (BaseStream.CanSeek)
{
BaseStream.Position = BaseStreamOffset + Position1;
}
int bytesRead = BaseStream.Read(buffer, offset, count);
Position1 += bytesRead;
return bytesRead;
}
public override long Seek(long offset, SeekOrigin origin)
{
if (!BaseStream.CanSeek)
{
throw new IOException("Stream is not seekable.");
}
long pos;
switch (origin)
{
case SeekOrigin.Begin:
pos = offset;
break;
case SeekOrigin.Current:
pos = Position1 + offset;
break;
case SeekOrigin.End:
pos = Length1 + offset;
break;
default:
pos = 0;
break;
}
if (pos < 0 || pos >= Length1)
{
throw new EndOfStreamException("OffsetStream reached begining/end of stream.");
}
Position1 = pos;
return pos;
}
public override void SetLength(long value)
{
if (ReadOnly)
{
throw new IOException("OffsetStream is read only.");
}
BaseStream.SetLength(BaseStreamOffset + value);
Length1 = value;
}
public override void Write(byte[] buffer, int offset, int count)
{
if (ReadOnly)
{
throw new IOException("OffsetStream is read only.");
}
if (count < 1)
{
return;
}
long pos = Position1 + count;
if (pos > Length1)
{
throw new EndOfStreamException("OffsetStream reached end of stream.");
}
if (BaseStream.CanSeek)
{
BaseStream.Position = BaseStreamOffset + Position1;
}
BaseStream.Write(buffer, offset, count);
Position1 = pos;
}
public long BaseStreamOffset { get; private set; }
public Stream BaseStream { get; }
public long Length1 { get; set; }
public long Position1 { get; set; }
public bool ReadOnly { get; }
public bool Disposed { get; set; }
public bool OwnStream { get; }
public void Reset(long offset, long length, long position)
{
BaseStreamOffset = offset; BaseStreamOffset = offset;
if (length > stream.Length - offset) throw new EndOfStreamException();
if (length == 0)
Length1 = stream.Length - offset;
else
Length1 = length;
}
else
{
BaseStreamOffset = 0;
Length1 = length; Length1 = length;
Position1 = position;
} }
public void WriteTo(Stream stream) BaseStream = stream;
ReadOnly = readOnly;
OwnStream = ownStream;
}
public override bool CanRead => BaseStream.CanRead;
public override bool CanSeek => BaseStream.CanSeek;
public override bool CanWrite => BaseStream.CanWrite && !ReadOnly;
public override long Length => Length1;
public override long Position
{
get => Position1;
set
{ {
WriteTo(stream, BufferSize); if (value > Length1) throw new EndOfStreamException();
if (!BaseStream.CanSeek) throw new NotSupportedException("Cannot seek stream.");
Position1 = value;
}
}
public long BaseStreamOffset { get; private set; }
public Stream BaseStream { get; }
public long Length1 { get; set; }
public long Position1 { get; set; }
public bool ReadOnly { get; }
public bool Disposed { get; set; }
public bool OwnStream { get; }
protected override void Dispose(bool disposing)
{
if (Disposed) return;
if (disposing)
if (OwnStream & (BaseStream != null))
BaseStream.Dispose();
Disposed = true;
base.Dispose(disposing);
}
public override void Flush()
{
if (ReadOnly) throw new IOException("OffsetStream is read only.");
BaseStream.Flush();
}
public override int Read(byte[] buffer, int offset, int count)
{
if (count < 1) throw new ArgumentOutOfRangeException("Count cannot be less than 1.");
if (Position1 >= Length1) return 0;
if (count > Length1 - Position1) count = Convert.ToInt32(Length1 - Position1);
if (BaseStream.CanSeek) BaseStream.Position = BaseStreamOffset + Position1;
var bytesRead = BaseStream.Read(buffer, offset, count);
Position1 += bytesRead;
return bytesRead;
}
public override long Seek(long offset, SeekOrigin origin)
{
if (!BaseStream.CanSeek) throw new IOException("Stream is not seekable.");
long pos;
switch (origin)
{
case SeekOrigin.Begin:
pos = offset;
break;
case SeekOrigin.Current:
pos = Position1 + offset;
break;
case SeekOrigin.End:
pos = Length1 + offset;
break;
default:
pos = 0;
break;
} }
public void WriteTo(Stream stream, int bufferSize) if (pos < 0 || pos >= Length1) throw new EndOfStreamException("OffsetStream reached begining/end of stream.");
Position1 = pos;
return pos;
}
public override void SetLength(long value)
{
if (ReadOnly) throw new IOException("OffsetStream is read only.");
BaseStream.SetLength(BaseStreamOffset + value);
Length1 = value;
}
public override void Write(byte[] buffer, int offset, int count)
{
if (ReadOnly) throw new IOException("OffsetStream is read only.");
if (count < 1) return;
var pos = Position1 + count;
if (pos > Length1) throw new EndOfStreamException("OffsetStream reached end of stream.");
if (BaseStream.CanSeek) BaseStream.Position = BaseStreamOffset + Position1;
BaseStream.Write(buffer, offset, count);
Position1 = pos;
}
public void Reset(long offset, long length, long position)
{
BaseStreamOffset = offset;
Length1 = length;
Position1 = position;
}
public void WriteTo(Stream stream)
{
WriteTo(stream, BufferSize);
}
public void WriteTo(Stream stream, int bufferSize)
{
if (!BaseStream.CanSeek) throw new IOException("Stream is not seekable.");
if (Length1 < bufferSize) bufferSize = Convert.ToInt32(Length1);
var previousPosition = Position1;
Position1 = 0;
try
{ {
if (!BaseStream.CanSeek) CopyTo(stream, bufferSize);
{ }
throw new IOException("Stream is not seekable."); finally
} {
Position1 = previousPosition;
if (Length1 < bufferSize)
{
bufferSize = Convert.ToInt32(Length1);
}
long previousPosition = Position1;
Position1 = 0;
try
{
CopyTo(stream, bufferSize);
}
finally
{
Position1 = previousPosition;
}
} }
} }
} }

View File

@@ -1,91 +1,77 @@
using System; using System;
using System.Net.Sockets; using System.Net.Sockets;
namespace EonaCat.Logger.GrayLog namespace EonaCat.Logger.GrayLog;
// 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>
/// Syslog server.
/// </summary>
public class GrayLogServer
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. internal readonly object SendLock = new();
// See the LICENSE file or go to https://EonaCat.com/License for full license details. private string _Hostname = "127.0.0.1";
private int _Port = 12201;
internal UdpClient Udp;
/// <summary> /// <summary>
/// Syslog server. /// Instantiate the object.
/// </summary> /// </summary>
public class GrayLogServer public GrayLogServer()
{ {
/// <summary> }
/// Hostname.
/// </summary> /// <summary>
public string Hostname /// Instantiate the object.
/// </summary>
/// <param name="hostname">Hostname.</param>
/// <param name="port">Port.</param>
public GrayLogServer(string hostname = "127.0.0.1", int port = 12201)
{
Hostname = hostname;
Port = port;
}
/// <summary>
/// Hostname.
/// </summary>
public string Hostname
{
get => _Hostname;
set
{ {
get if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(Hostname));
{ _Hostname = value;
return _Hostname;
}
set SetUdp();
{
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(Hostname));
_Hostname = value;
SetUdp();
}
}
/// <summary>
/// UDP port.
/// </summary>
public int Port
{
get
{
return _Port;
}
set
{
if (value < 0) throw new ArgumentException("Port must be zero or greater.");
_Port = value;
SetUdp();
}
}
/// <summary>
/// IP:port of the server.
/// </summary>
public string IpPort
{
get
{
return _Hostname + ":" + _Port;
}
}
internal readonly object SendLock = new object();
internal UdpClient Udp = null;
private string _Hostname = "127.0.0.1";
private int _Port = 12201;
/// <summary>
/// Instantiate the object.
/// </summary>
public GrayLogServer()
{
}
/// <summary>
/// Instantiate the object.
/// </summary>
/// <param name="hostname">Hostname.</param>
/// <param name="port">Port.</param>
public GrayLogServer(string hostname = "127.0.0.1", int port = 12201)
{
Hostname = hostname;
Port = port;
}
private void SetUdp()
{
Udp = null;
Udp = new UdpClient(_Hostname, _Port);
} }
} }
/// <summary>
/// UDP port.
/// </summary>
public int Port
{
get => _Port;
set
{
if (value < 0) throw new ArgumentException("Port must be zero or greater.");
_Port = value;
SetUdp();
}
}
/// <summary>
/// IP:port of the server.
/// </summary>
public string IpPort => _Hostname + ":" + _Port;
private void SetUdp()
{
Udp = null;
Udp = new UdpClient(_Hostname, _Port);
}
} }

View File

@@ -2,134 +2,131 @@
using System.Drawing; using System.Drawing;
using System.Globalization; using System.Globalization;
namespace EonaCat.Logger.Helpers namespace EonaCat.Logger.Helpers;
public static class ColorHelper
{ {
public static class ColorHelper public static string ColorToHexString(Color c)
{ {
public static string ColorToHexString(Color c) return "#" + c.R.ToString("X2") + c.G.ToString("X2") + c.B.ToString("X2");
}
public static string ColorToRGBString(Color c)
{
return "RGB(" + c.R + "," + c.G + "," + c.B + ")";
}
public static Color ConsoleColorToColor(this ConsoleColor consoleColor)
{
switch (consoleColor)
{ {
return "#" + c.R.ToString("X2") + c.G.ToString("X2") + c.B.ToString("X2"); case ConsoleColor.Black:
return Color.Black;
case ConsoleColor.DarkBlue:
return HexStringToColor("#000080");
case ConsoleColor.DarkGreen:
return HexStringToColor("#008000");
case ConsoleColor.DarkCyan:
return HexStringToColor("#008080");
case ConsoleColor.DarkRed:
return HexStringToColor("#800000");
case ConsoleColor.DarkMagenta:
return HexStringToColor("#800080");
case ConsoleColor.DarkYellow:
return HexStringToColor("#808000");
case ConsoleColor.Gray:
return HexStringToColor("#C0C0C0");
case ConsoleColor.DarkGray:
return HexStringToColor("#808080");
case ConsoleColor.Blue:
return Color.Blue;
case ConsoleColor.Green:
return Color.Lime;
case ConsoleColor.Cyan:
return Color.Cyan;
case ConsoleColor.Red:
return Color.Red;
case ConsoleColor.Magenta:
return Color.Magenta;
case ConsoleColor.Yellow:
return Color.Yellow;
case ConsoleColor.White:
return Color.White;
default:
throw new NotSupportedException();
} }
}
public static string ColorToRGBString(Color c) public static Color HexStringToColor(string htmlColor, bool requireHexSpecified = false, int defaultAlpha = 0xFF)
{
return Color.FromArgb(HexColorToArgb(htmlColor, requireHexSpecified, defaultAlpha));
}
public static int HexColorToArgb(string htmlColor, bool requireHexSpecified = false, int defaultAlpha = 0xFF)
{
if (string.IsNullOrEmpty(htmlColor)) throw new ArgumentNullException(nameof(htmlColor));
if (!htmlColor.StartsWith("#") && requireHexSpecified)
throw new ArgumentException($"Provided parameter '{htmlColor}' is not valid");
htmlColor = htmlColor.TrimStart('#');
var symbolCount = htmlColor.Length;
var value = int.Parse(htmlColor, NumberStyles.HexNumber);
switch (symbolCount)
{ {
return "RGB(" + c.R.ToString() + "," + c.G.ToString() + "," + c.B.ToString() + ")"; case 3: // RGB short hand
}
public static Color ConsoleColorToColor(this ConsoleColor consoleColor)
{
switch (consoleColor)
{ {
case ConsoleColor.Black: return (defaultAlpha << 24)
return Color.Black; | (value & 0xF)
| ((value & 0xF) << 4)
case ConsoleColor.DarkBlue: | ((value & 0xF0) << 4)
return HexStringToColor("#000080"); | ((value & 0xF0) << 8)
| ((value & 0xF00) << 8)
case ConsoleColor.DarkGreen: | ((value & 0xF00) << 12)
return HexStringToColor("#008000"); ;
case ConsoleColor.DarkCyan:
return HexStringToColor("#008080");
case ConsoleColor.DarkRed:
return HexStringToColor("#800000");
case ConsoleColor.DarkMagenta:
return HexStringToColor("#800080");
case ConsoleColor.DarkYellow:
return HexStringToColor("#808000");
case ConsoleColor.Gray:
return HexStringToColor("#C0C0C0");
case ConsoleColor.DarkGray:
return HexStringToColor("#808080");
case ConsoleColor.Blue:
return Color.Blue;
case ConsoleColor.Green:
return Color.Lime;
case ConsoleColor.Cyan:
return Color.Cyan;
case ConsoleColor.Red:
return Color.Red;
case ConsoleColor.Magenta:
return Color.Magenta;
case ConsoleColor.Yellow:
return Color.Yellow;
case ConsoleColor.White:
return Color.White;
default:
throw new NotSupportedException();
} }
} case 4: // RGBA short hand
public static Color HexStringToColor(string htmlColor, bool requireHexSpecified = false, int defaultAlpha = 0xFF) => Color.FromArgb(HexColorToArgb(htmlColor, requireHexSpecified, defaultAlpha));
public static int HexColorToArgb(string htmlColor, bool requireHexSpecified = false, int defaultAlpha = 0xFF)
{
if (string.IsNullOrEmpty(htmlColor))
{ {
throw new ArgumentNullException(nameof(htmlColor)); // Inline alpha swap
return ((value & 0xF) << 24)
| ((value & 0xF) << 28)
| ((value & 0xF0) >> 4)
| (value & 0xF0)
| (value & 0xF00)
| ((value & 0xF00) << 4)
| ((value & 0xF000) << 4)
| ((value & 0xF000) << 8)
;
} }
case 6: // RGB complete definition
if (!htmlColor.StartsWith("#") && requireHexSpecified)
{ {
throw new ArgumentException($"Provided parameter '{htmlColor}' is not valid"); return (defaultAlpha << 24) | value;
} }
case 8: // RGBA complete definition
htmlColor = htmlColor.TrimStart('#');
var symbolCount = htmlColor.Length;
var value = int.Parse(htmlColor, NumberStyles.HexNumber);
switch (symbolCount)
{ {
case 3: // RGB short hand // Alpha swap
{ return ((value & 0xFF) << 24) | (value >> 8);
return defaultAlpha << 24
| value & 0xF
| (value & 0xF) << 4
| (value & 0xF0) << 4
| (value & 0xF0) << 8
| (value & 0xF00) << 8
| (value & 0xF00) << 12
;
}
case 4: // RGBA short hand
{
// Inline alpha swap
return (value & 0xF) << 24
| (value & 0xF) << 28
| (value & 0xF0) >> 4
| value & 0xF0
| value & 0xF00
| (value & 0xF00) << 4
| (value & 0xF000) << 4
| (value & 0xF000) << 8
;
}
case 6: // RGB complete definition
{
return defaultAlpha << 24 | value;
}
case 8: // RGBA complete definition
{
// Alpha swap
return (value & 0xFF) << 24 | value >> 8;
}
default:
throw new FormatException("Invalid HTML Color");
} }
default:
throw new FormatException("Invalid HTML Color");
} }
} }
} }

View File

@@ -1,53 +1,51 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace EonaCat.Logger.Helpers namespace EonaCat.Logger.Helpers;
// 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.
internal static class EnumHelper<T>
where T : struct
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. static EnumHelper()
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
internal static class EnumHelper<T>
where T : struct
{ {
static EnumHelper() var names = Enum.GetNames(typeof(T));
var values = (T[])Enum.GetValues(typeof(T));
Names = new Dictionary<T, string>(names.Length);
Values = new Dictionary<string, T>(names.Length * 2);
for (var i = 0; i < names.Length; i++)
{ {
string[] names = Enum.GetNames(typeof(T)); Names[values[i]] = names[i];
T[] values = (T[])Enum.GetValues(typeof(T)); Values[names[i]] = values[i];
Values[names[i].ToLower()] = values[i];
Names = new Dictionary<T, string>(names.Length);
Values = new Dictionary<string, T>(names.Length * 2);
for (int i = 0; i < names.Length; i++)
{
Names[values[i]] = names[i];
Values[names[i]] = values[i];
Values[names[i].ToLower()] = values[i];
}
}
public static Dictionary<T, string> Names { get; }
public static Dictionary<string, T> Values { get; }
public static string ToString(T value)
{
return Names.TryGetValue(value, out string result) ? result : Convert.ToInt64(value).ToString();
}
public static bool TryParse(string input, bool ignoreCase, out T value)
{
if (string.IsNullOrEmpty(input))
{
value = default;
return false;
}
return Values.TryGetValue(ignoreCase ? input.ToLower() : input, out value);
}
internal static T Parse(string input, bool ignoreCase, T defaultValue)
{
return TryParse(input, ignoreCase, out T result) ? result : defaultValue;
} }
} }
public static Dictionary<T, string> Names { get; }
public static Dictionary<string, T> Values { get; }
public static string ToString(T value)
{
return Names.TryGetValue(value, out var result) ? result : Convert.ToInt64(value).ToString();
}
public static bool TryParse(string input, bool ignoreCase, out T value)
{
if (string.IsNullOrEmpty(input))
{
value = default;
return false;
}
return Values.TryGetValue(ignoreCase ? input.ToLower() : input, out value);
}
internal static T Parse(string input, bool ignoreCase, T defaultValue)
{
return TryParse(input, ignoreCase, out var result) ? result : defaultValue;
}
} }

View File

@@ -1,75 +1,73 @@
using System; using System;
namespace EonaCat.Logger.Managers namespace EonaCat.Logger.Managers;
// 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>
/// Colors to use when writing to the console.
/// </summary>
public class ColorSchema
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. /// <summary>
// See the LICENSE file or go to https://EonaCat.com/License for full license details. /// The color to use for critical messages.
/// </summary>
public ColorScheme Critical = new(ConsoleColor.DarkRed, ConsoleColor.Black);
/// <summary> /// <summary>
/// Colors to use when writing to the console. /// The color to use for debug messages.
/// </summary> /// </summary>
public class ColorSchema public ColorScheme Debug = new(ConsoleColor.Green, ConsoleColor.Black);
{
/// <summary>
/// The color to use for debug messages.
/// </summary>
public ColorScheme Debug = new ColorScheme(ConsoleColor.Green, ConsoleColor.Black);
/// <summary>
/// The color to use for informational messages.
/// </summary>
public ColorScheme Info = new ColorScheme(ConsoleColor.Blue, ConsoleColor.Black);
/// <summary>
/// The color to use for warning messages.
/// </summary>
public ColorScheme Warning = new ColorScheme(ConsoleColor.DarkYellow, ConsoleColor.Black);
/// <summary>
/// The color to use for error messages.
/// </summary>
public ColorScheme Error = new ColorScheme(ConsoleColor.Red, ConsoleColor.Black);
/// <summary>
/// The color to use for alert messages.
/// </summary>
public ColorScheme Traffic = new ColorScheme(ConsoleColor.DarkMagenta, ConsoleColor.Black);
/// <summary>
/// The color to use for critical messages.
/// </summary>
public ColorScheme Critical = new ColorScheme(ConsoleColor.DarkRed, ConsoleColor.Black);
/// <summary>
/// The color to use for emergency messages.
/// </summary>
public ColorScheme Trace = new ColorScheme(ConsoleColor.Cyan, ConsoleColor.Black);
}
/// <summary> /// <summary>
/// Color scheme for logging messages. /// The color to use for error messages.
/// </summary> /// </summary>
public class ColorScheme public ColorScheme Error = new(ConsoleColor.Red, ConsoleColor.Black);
/// <summary>
/// The color to use for informational messages.
/// </summary>
public ColorScheme Info = new(ConsoleColor.Blue, ConsoleColor.Black);
/// <summary>
/// The color to use for emergency messages.
/// </summary>
public ColorScheme Trace = new(ConsoleColor.Cyan, ConsoleColor.Black);
/// <summary>
/// The color to use for alert messages.
/// </summary>
public ColorScheme Traffic = new(ConsoleColor.DarkMagenta, ConsoleColor.Black);
/// <summary>
/// The color to use for warning messages.
/// </summary>
public ColorScheme Warning = new(ConsoleColor.DarkYellow, ConsoleColor.Black);
}
/// <summary>
/// Color scheme for logging messages.
/// </summary>
public class ColorScheme
{
/// <summary>
/// Background color.
/// </summary>
public ConsoleColor Background = Console.BackgroundColor;
/// <summary>
/// Foreground color.
/// </summary>
public ConsoleColor Foreground = Console.ForegroundColor;
/// <summary>
/// Instantiates a new color scheme.
/// </summary>
/// <param name="foreground">Foreground color.</param>
/// <param name="background">Background color.</param>
public ColorScheme(ConsoleColor foreground, ConsoleColor background)
{ {
/// <summary> Foreground = foreground;
/// Foreground color. Background = background;
/// </summary>
public ConsoleColor Foreground = Console.ForegroundColor;
/// <summary>
/// Background color.
/// </summary>
public ConsoleColor Background = Console.BackgroundColor;
/// <summary>
/// Instantiates a new color scheme.
/// </summary>
/// <param name="foreground">Foreground color.</param>
/// <param name="background">Background color.</param>
public ColorScheme(ConsoleColor foreground, ConsoleColor background)
{
Foreground = foreground;
Background = background;
}
} }
} }

View File

@@ -1,16 +1,18 @@
using System; using System;
using System.Threading.Tasks;
namespace EonaCat.Logger.Managers namespace EonaCat.Logger.Managers;
// 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 interface ILogManager
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. void Write(string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null,
// See the LICENSE file or go to https://EonaCat.com/License for full license details. bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null,
bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null,
string grayLogVersion = "1.1");
public interface ILogManager void Write(Exception exception, string module = null, string method = null, bool criticalException = false,
{ bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null,
void Write(string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, string grayLogVersion = "1.1"); string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null,
string grayLogSource = null, string grayLogVersion = "1.1");
void Write(Exception exception, string module = null, string method = null, bool criticalException = false,
bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, string grayLogVersion = "1.1");
}
} }

View File

@@ -13,215 +13,251 @@ using Microsoft.Extensions.Logging;
// This file is part of the EonaCat project(s) which is released under the Apache License. // 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. // See the LICENSE file or go to https://EonaCat.com/License for full license details.
namespace EonaCat.Logger.Managers namespace EonaCat.Logger.Managers;
public class ErrorMessage
{ {
public class ErrorMessage public Exception Exception { get; set; }
public string Message { get; set; }
}
internal static class LogHelper
{
internal static event EventHandler<ErrorMessage> OnException;
internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string currentMessage,
DateTime dateTime)
{ {
public Exception Exception { get; set; } if (string.IsNullOrWhiteSpace(currentMessage))
public string Message { get; set; } return currentMessage;
var sb = new StringBuilder(settings?.HeaderFormat ?? "[EonaCatLogger]");
sb.Replace("{ts}",
dateTime.ToString(settings?.TimestampFormat ?? "yyyy-MM-dd HH:mm:ss") + " " +
(settings?.UseLocalTime ?? false ? "[LOCAL]" : "[UTC]"))
.Replace("{host}", $"[Host:{Dns.GetHostName()}]")
.Replace("{thread}", $"[ThreadId:{Environment.CurrentManagedThreadId}]")
.Replace("{sev}", $"[{logType}]");
if (!settings?.RemoveMessagePrefix ?? (false && !currentMessage.Contains("[EonaCatLogger]")))
sb.Insert(0, "[EonaCatLogger] ");
sb.Append(" ").Append(currentMessage);
return sb.ToString();
} }
internal static class LogHelper internal static void SendToConsole(LoggerSettings settings, ELogType logType, string message, bool writeToConsole)
{ {
internal static event EventHandler<ErrorMessage> OnException; if (settings == null || !writeToConsole || string.IsNullOrWhiteSpace(message))
return;
internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string currentMessage, DateTime dateTime) if (settings.EnableColors && settings.Colors != null)
{ {
if (string.IsNullOrWhiteSpace(currentMessage)) var prevForeground = Console.ForegroundColor;
return currentMessage; var prevBackground = Console.BackgroundColor;
StringBuilder sb = new StringBuilder(settings?.HeaderFormat ?? "[EonaCatLogger]"); ConsoleColor foregroundColor;
ConsoleColor backgroundColor;
sb.Replace("{ts}", dateTime.ToString(settings?.TimestampFormat ?? "yyyy-MM-dd HH:mm:ss") + " " + (settings?.UseLocalTime ?? false ? "[LOCAL]" : "[UTC]")) switch (logType)
.Replace("{host}", $"[Host:{Dns.GetHostName()}]")
.Replace("{thread}", $"[ThreadId:{Environment.CurrentManagedThreadId}]")
.Replace("{sev}", $"[{logType}]");
if (!settings?.RemoveMessagePrefix ?? false && !currentMessage.Contains("[EonaCatLogger]"))
sb.Insert(0, "[EonaCatLogger] ");
sb.Append(" ").Append(currentMessage);
return sb.ToString();
}
internal static void SendToConsole(LoggerSettings settings, ELogType logType, string message, bool writeToConsole)
{
if (settings == null || !writeToConsole || string.IsNullOrWhiteSpace(message))
return;
if (settings.EnableColors && settings.Colors != null)
{ {
ConsoleColor prevForeground = Console.ForegroundColor; case ELogType.DEBUG:
ConsoleColor prevBackground = Console.BackgroundColor; foregroundColor = settings.Colors.Debug.Foreground;
backgroundColor = settings.Colors.Debug.Background;
break;
case ELogType.INFO:
foregroundColor = settings.Colors.Info.Foreground;
backgroundColor = settings.Colors.Info.Background;
break;
case ELogType.WARNING:
foregroundColor = settings.Colors.Warning.Foreground;
backgroundColor = settings.Colors.Warning.Background;
break;
case ELogType.ERROR:
foregroundColor = settings.Colors.Error.Foreground;
backgroundColor = settings.Colors.Error.Background;
break;
case ELogType.TRAFFIC:
foregroundColor = settings.Colors.Traffic.Foreground;
backgroundColor = settings.Colors.Traffic.Background;
break;
case ELogType.CRITICAL:
foregroundColor = settings.Colors.Critical.Foreground;
backgroundColor = settings.Colors.Critical.Background;
break;
case ELogType.TRACE:
foregroundColor = settings.Colors.Trace.Foreground;
backgroundColor = settings.Colors.Trace.Background;
break;
default:
return;
}
ConsoleColor foregroundColor; Console.ForegroundColor = foregroundColor;
ConsoleColor backgroundColor; Console.BackgroundColor = backgroundColor;
switch (logType) Console.WriteLine(message);
Console.ForegroundColor = prevForeground;
Console.BackgroundColor = prevBackground;
}
else
{
Console.WriteLine(message);
}
}
internal static void SendToFile(ILogger logger, LoggerSettings settings, ELogType logType, string message)
{
if (logger == null || settings == null || !settings.EnableFileLogging ||
string.IsNullOrWhiteSpace(message)) return;
var logLevel = logType.ToLogLevel();
if (IsLogLevelEnabled(settings, logLevel)) Log(logger, logLevel, message);
}
public static bool IsLogLevelEnabled(LoggerSettings settings, LogLevel logLevel)
{
return settings.MinLogType.ToLogLevel() <= logLevel;
}
private static void Log(ILogger logger, LogLevel logLevel, string message)
{
logger.Log(logLevel, message);
}
public static async Task SendToSplunkServersAsync(LoggerSettings settings, SplunkPayload splunkPayload,
bool sendToSplunkServer)
{
if (settings == null || !sendToSplunkServer || splunkPayload == null)
return;
if (settings.SplunkServers == null) settings.SplunkServers = new List<SplunkServer.SplunkServer>();
foreach (var splunkServer in settings.SplunkServers)
{
if (!splunkServer.HasHecUrl || !splunkServer.HasHecToken)
{
OnException?.Invoke(null,
new ErrorMessage
{
Message =
$"Splunk server HecUrl or HecToken not specified, skipping splunkServer '{splunkServer.SplunkHecUrl}'"
});
continue;
}
try
{
var response = await splunkServer.SendAsync(splunkPayload);
if (!response.IsSuccessStatusCode)
OnException?.Invoke(null,
new ErrorMessage
{
Message =
$"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}"
});
}
catch (Exception exception)
{
OnException?.Invoke(null,
new ErrorMessage
{
Exception = exception,
Message =
$"Error while logging to Splunk Server '{splunkServer.SplunkHecUrl}': {exception.Message}"
});
}
}
}
public static async Task SendToSplunkServersAsync(LoggerSettings settings, string logType, string message,
bool sendToSplunkServer)
{
if (settings == null || !sendToSplunkServer || string.IsNullOrWhiteSpace(message))
return;
var splunkPayload = new SplunkPayload
{
Host = Environment.MachineName,
EventData = message,
SourceType = logType
};
await SendToSplunkServersAsync(settings, splunkPayload, sendToSplunkServer);
}
internal static async Task SendToGrayLogServersAsync(LoggerSettings settings, string message, ELogType logLevel,
string facility, string source, bool sendToGrayLogServer, string version = "1.1")
{
if (settings == null || !sendToGrayLogServer || string.IsNullOrWhiteSpace(message))
return;
foreach (var grayLogServer in settings.GrayLogServers ?? new List<GrayLogServer> { new("127.0.0.1") })
try
{
var gelfMessage = new
{ {
case ELogType.DEBUG: version,
foregroundColor = settings.Colors.Debug.Foreground; host = Environment.MachineName,
backgroundColor = settings.Colors.Debug.Background; short_message = message,
break; level = logLevel.ToGrayLogLevel(),
case ELogType.INFO: facility,
foregroundColor = settings.Colors.Info.Foreground; source,
backgroundColor = settings.Colors.Info.Background; timestamp = DateTime.UtcNow.ToUnixTimestamp()
break; };
case ELogType.WARNING:
foregroundColor = settings.Colors.Warning.Foreground;
backgroundColor = settings.Colors.Warning.Background;
break;
case ELogType.ERROR:
foregroundColor = settings.Colors.Error.Foreground;
backgroundColor = settings.Colors.Error.Background;
break;
case ELogType.TRAFFIC:
foregroundColor = settings.Colors.Traffic.Foreground;
backgroundColor = settings.Colors.Traffic.Background;
break;
case ELogType.CRITICAL:
foregroundColor = settings.Colors.Critical.Foreground;
backgroundColor = settings.Colors.Critical.Background;
break;
case ELogType.TRACE:
foregroundColor = settings.Colors.Trace.Foreground;
backgroundColor = settings.Colors.Trace.Background;
break;
default:
return;
}
Console.ForegroundColor = foregroundColor; var messageBytes = Encoding.UTF8.GetBytes(JsonHelper.ToJson(gelfMessage));
Console.BackgroundColor = backgroundColor; await grayLogServer.Udp.SendAsync(messageBytes, messageBytes.Length,
Console.WriteLine(message); new IPEndPoint(IPAddress.Parse(grayLogServer.Hostname), grayLogServer.Port));
Console.ForegroundColor = prevForeground;
Console.BackgroundColor = prevBackground;
} }
else catch (Exception exception)
{ {
Console.WriteLine(message); OnException?.Invoke(null,
new ErrorMessage
{
Exception = exception,
Message =
$"Error while logging to GrayLog Server '{grayLogServer.Hostname}': {exception.Message}"
});
} }
} }
internal static async Task SendToFile(ILogger logger, LoggerSettings settings, ELogType logType, string message) internal static async Task SendToSysLogServersAsync(LoggerSettings settings, string message,
{ bool sendToSyslogServers)
if (logger == null || settings == null || !settings.EnableFileLogging || string.IsNullOrWhiteSpace(message)) {
return; if (settings == null || !sendToSyslogServers || string.IsNullOrWhiteSpace(message))
return;
LogLevel logLevel = logType.ToLogLevel(); foreach (var server in settings.SysLogServers ?? new List<SyslogServer> { new("127.0.0.1") })
try
if (logLevel >= settings.MaxLogType.ToLogLevel())
await Task.Run(() => logger.Log(logLevel, message));
}
public static async Task SendToSplunkServersAsync(LoggerSettings settings, SplunkPayload splunkPayload, bool sendToSplunkServer)
{
if (settings == null || !sendToSplunkServer || splunkPayload == null)
return;
if (settings.SplunkServers == null)
{ {
settings.SplunkServers = new List<SplunkServer.SplunkServer>(); if (string.IsNullOrWhiteSpace(server.Hostname))
}
foreach (var splunkServer in settings.SplunkServers)
{
if (!splunkServer.HasHecUrl || !splunkServer.HasHecToken)
{ {
OnException?.Invoke(null, new ErrorMessage { Message = $"Splunk server HecUrl or HecToken not specified, skipping splunkServer '{splunkServer.SplunkHecUrl}'" }); OnException?.Invoke(null,
new ErrorMessage { Message = "Server hostname not specified, skipping SysLog Server" });
continue; continue;
} }
try if (server.Port < 0)
{ {
var response = await splunkServer.SendAsync(splunkPayload); OnException?.Invoke(null,
new ErrorMessage { Message = "Server port must be zero or greater, skipping SysLog Server" });
continue;
}
if (!response.IsSuccessStatusCode) var data = Encoding.UTF8.GetBytes(message);
{ await server.Udp.SendAsync(data, data.Length,
OnException?.Invoke(null, new ErrorMessage { Message = $"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}" }); new IPEndPoint(IPAddress.Parse(server.Hostname), server.Port));
}
}
catch (Exception exception)
{
OnException?.Invoke(null, new ErrorMessage { Exception = exception, Message = $"Error while logging to Splunk Server '{splunkServer.SplunkHecUrl}': {exception.Message}" });
}
} }
} catch (Exception exception)
public static async Task SendToSplunkServersAsync(LoggerSettings settings, string logType, string message, bool sendToSplunkServer)
{
if (settings == null || !sendToSplunkServer || string.IsNullOrWhiteSpace(message))
return;
var splunkPayload = new SplunkPayload
{ {
Host = Environment.MachineName, OnException?.Invoke(null,
EventData = message, new ErrorMessage
SourceType = logType
};
await SendToSplunkServersAsync(settings, splunkPayload, sendToSplunkServer);
}
internal static async Task SendToGrayLogServersAsync(LoggerSettings settings, string message, ELogType logLevel, string facility, string source, bool sendToGrayLogServer, string version = "1.1")
{
if (settings == null || !sendToGrayLogServer || string.IsNullOrWhiteSpace(message))
return;
foreach (var grayLogServer in settings.GrayLogServers ?? new List<GrayLogServer> { new GrayLogServer("127.0.0.1", 12201) })
{
try
{
var gelfMessage = new
{ {
version, Exception = exception,
host = Environment.MachineName, Message = $"Error while logging to SysLog Server '{server.Hostname}': {exception.Message}"
short_message = message, });
level = logLevel.ToGrayLogLevel(),
facility,
source,
timestamp = DateTime.UtcNow.ToUnixTimestamp(),
};
var messageBytes = Encoding.UTF8.GetBytes(JsonHelper.ToJson(gelfMessage));
await grayLogServer.Udp.SendAsync(messageBytes, messageBytes.Length, new IPEndPoint(IPAddress.Parse(grayLogServer.Hostname), grayLogServer.Port));
}
catch (Exception exception)
{
OnException?.Invoke(null, new ErrorMessage { Exception = exception, Message = $"Error while logging to GrayLog Server '{grayLogServer.Hostname}': {exception.Message}" });
}
} }
}
internal static async Task SendToSysLogServersAsync(LoggerSettings settings, string message, bool sendToSyslogServers)
{
if (settings == null || !sendToSyslogServers || string.IsNullOrWhiteSpace(message))
return;
foreach (var server in settings.SysLogServers ?? new List<SyslogServer> { new SyslogServer("127.0.0.1", 514) })
{
try
{
if (string.IsNullOrWhiteSpace(server.Hostname))
{
OnException?.Invoke(null, new ErrorMessage { Message = "Server hostname not specified, skipping SysLog Server" });
continue;
}
if (server.Port < 0)
{
OnException?.Invoke(null, new ErrorMessage { Message = "Server port must be zero or greater, skipping SysLog Server" });
continue;
}
byte[] data = Encoding.UTF8.GetBytes(message);
await server.Udp.SendAsync(data, data.Length, new IPEndPoint(IPAddress.Parse(server.Hostname), server.Port));
}
catch (Exception exception)
{
OnException?.Invoke(null, new ErrorMessage { Exception = exception, Message = $"Error while logging to SysLog Server '{server.Hostname}': {exception.Message}" });
}
}
}
} }
} }

View File

@@ -1,7 +1,4 @@
using EonaCat.Logger.Syslog; using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
@@ -10,86 +7,139 @@ using EonaCat.Logger.EonaCatCoreLogger;
using EonaCat.Logger.EonaCatCoreLogger.Extensions; using EonaCat.Logger.EonaCatCoreLogger.Extensions;
using EonaCat.Logger.EonaCatCoreLogger.Models; using EonaCat.Logger.EonaCatCoreLogger.Models;
using EonaCat.Logger.Extensions; using EonaCat.Logger.Extensions;
using EonaCat.Logger.Syslog;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace EonaCat.Logger.Managers namespace EonaCat.Logger.Managers;
// 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 LogManager : ILogManager, IDisposable
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. private static LogManager _instance;
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
public partial class LogManager : ILogManager, IDisposable private readonly CancellationTokenSource _tokenSource = new();
private DateTime _logDate;
public LogManager(LoggerSettings settings, string serverIp, int serverPort)
{ {
public event EventHandler<ErrorMessage> OnException; if (string.IsNullOrEmpty(serverIp))
private DateTime CurrentDateTme => Settings.UseLocalTime ? DateTime.Now : DateTime.UtcNow; throw new ArgumentNullException(nameof(serverIp));
private DateTime _logDate;
public ILoggerProvider LoggerProvider { get; private set; }
public ILoggerFactory LoggerFactory { get; private set; }
public ILogger Logger { get; private set; }
public string CurrentLogFile => LoggerProvider is FileLoggerProvider fileLoggerProvider ? fileLoggerProvider.LogFile : string.Empty;
public bool IsRunning { get; private set; } if (serverPort < 0)
throw new ArgumentException("Server port must be zero or greater.");
private static LogManager _instance; settings.SysLogServers = new List<SyslogServer>
private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource();
public static LogManager Instance => InstanceInit();
public LoggerSettings Settings { get; set; } = CreateDefaultSettings();
private static LogManager InstanceInit()
{ {
if (_instance == null) new(serverIp, serverPort)
{ };
_instance = new LogManager(CreateDefaultSettings());
}
return _instance;
}
private static LoggerSettings CreateDefaultSettings() Settings = settings;
{ SetupLogManager();
var settings = new LoggerSettings(); }
settings.Id = "EonaCatLogger";
settings.MaxLogType = ELogType.TRACE;
return settings;
}
protected virtual async Task DisposeAsync(bool disposing) public LogManager(LoggerSettings settings)
{ {
if (disposing) Settings = settings;
{ SetupFileLogger(settings);
await StopLoggingAsync().ConfigureAwait(false); SetupLogManager();
} LogHelper.OnException += LogHelper_OnException;
} }
public async Task StartNewLogAsync() private DateTime CurrentDateTme => Settings.UseLocalTime ? DateTime.Now : DateTime.UtcNow;
{ public ILoggerProvider LoggerProvider { get; private set; }
if (_tokenSource.IsCancellationRequested) public ILoggerFactory LoggerFactory { get; private set; }
{ public ILogger Logger { get; private set; }
return;
}
if (IsRunning && CurrentDateTme.Date > _logDate.Date) public string CurrentLogFile => LoggerProvider is FileLoggerProvider fileLoggerProvider
{ ? fileLoggerProvider.LogFile
await StopLoggingAsync().ConfigureAwait(false); : string.Empty;
}
IsRunning = true; public bool IsRunning { get; private set; }
CreateLogger(); public static LogManager Instance => InstanceInit();
Directory.CreateDirectory(Settings.FileLoggerOptions.LogDirectory); public LoggerSettings Settings { get; set; } = CreateDefaultSettings();
_logDate = CurrentDateTme; public void Dispose()
} {
DisposeAsync(true).GetAwaiter().GetResult();
GC.SuppressFinalize(this);
}
private void CreateLogger() public void Write(Exception exception, string module = null, string method = null, bool criticalException = false,
{ bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null,
// Dispose of previous ServiceProvider if it exists string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null,
(LoggerProvider as IDisposable)?.Dispose(); string grayLogSource = null, string grayLogVersion = "1.1")
(LoggerFactory as IDisposable)?.Dispose(); {
if (exception == null)
return;
IServiceCollection serviceCollection = new ServiceCollection(); Write(exception.FormatExceptionToMessage(module, method),
serviceCollection.AddLogging(builder => builder.SetMinimumLevel(Settings.MaxLogType.ToLogLevel()).AddEonaCatFileLogger(configuration => criticalException ? ELogType.CRITICAL : ELogType.ERROR, writeToConsole, sendToSysLogServers,
sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource,
grayLogVersion);
}
public void Write(string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null,
bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null,
bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null,
string grayLogVersion = "1.1")
{
if (logType == ELogType.NONE)
return;
if (!IsRunning) StartNewLogAsync().ConfigureAwait(false);
InternalWriteAsync(CurrentDateTme, message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers,
customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion);
}
public event EventHandler<ErrorMessage> OnException;
private static LogManager InstanceInit()
{
if (_instance == null) _instance = new LogManager(CreateDefaultSettings());
return _instance;
}
private static LoggerSettings CreateDefaultSettings()
{
var settings = new LoggerSettings();
settings.Id = "EonaCatLogger";
settings.MinLogType = ELogType.INFO;
return settings;
}
protected virtual async Task DisposeAsync(bool disposing)
{
if (disposing) await StopLoggingAsync().ConfigureAwait(false);
}
public async Task StartNewLogAsync()
{
if (_tokenSource.IsCancellationRequested) return;
if (IsRunning && CurrentDateTme.Date > _logDate.Date) await StopLoggingAsync().ConfigureAwait(false);
IsRunning = true;
CreateLogger();
Directory.CreateDirectory(Settings.FileLoggerOptions.LogDirectory);
_logDate = CurrentDateTme;
}
private void CreateLogger()
{
// Dispose of previous ServiceProvider if it exists
LoggerProvider?.Dispose();
LoggerFactory?.Dispose();
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddLogging(builder => builder.SetMinimumLevel(Settings.MinLogType.ToLogLevel())
.AddEonaCatFileLogger(configuration =>
{ {
var fileLoggerOptions = Settings.FileLoggerOptions; var fileLoggerOptions = Settings.FileLoggerOptions;
configuration.MaxWriteTries = fileLoggerOptions.MaxWriteTries; configuration.MaxWriteTries = fileLoggerOptions.MaxWriteTries;
@@ -104,166 +154,108 @@ namespace EonaCat.Logger.Managers
configuration.UseLocalTime = Settings.UseLocalTime; configuration.UseLocalTime = Settings.UseLocalTime;
})); }));
var serviceProvider = serviceCollection.BuildServiceProvider(); var serviceProvider = serviceCollection.BuildServiceProvider();
LoggerProvider = serviceProvider.GetService<ILoggerProvider>(); LoggerProvider = serviceProvider.GetService<ILoggerProvider>();
LoggerFactory = serviceProvider.GetService<ILoggerFactory>(); LoggerFactory = serviceProvider.GetService<ILoggerFactory>();
Logger = LoggerFactory.CreateLogger(Settings.Id); Logger = LoggerFactory.CreateLogger(Settings.Id);
}
// Dispose of the current ServiceProvider
(serviceProvider as IDisposable)?.Dispose();
}
private async Task InternalWriteAsync(DateTime dateTime, string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null, bool? sendToSyslogServers = null, bool? sendToSplunkServers = null, string customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, string grayLogVersion = "1.1") private async Task InternalWriteAsync(DateTime dateTime, string message, ELogType logType = ELogType.INFO,
{ bool? writeToConsole = null, bool? sendToSyslogServers = null, bool? sendToSplunkServers = null,
if (string.IsNullOrEmpty(message) || logType == ELogType.NONE || (int)logType >= (int)Settings.MaxLogType) string customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null,
string grayLogSource = null, string grayLogVersion = "1.1")
{
if (string.IsNullOrEmpty(message) || logType == ELogType.NONE ||
(int)logType < (int)Settings.MinLogType) return;
var messageWithHeader = LogHelper.FormatMessageWithHeader(Settings, logType, message, dateTime);
var writeToConsoleValue = writeToConsole ?? Settings.EnableConsole;
var sendToSyslogServersValue = sendToSyslogServers ?? Settings.SendToSyslogServers;
var sendToSplunkServersValue = sendToSplunkServers ?? Settings.SendToSplunkServers;
var sendToGrayLogServersValue = sendToGrayLogServers ?? Settings.SendToGrayLogServers;
LogHelper.SendToFile(Logger, Settings, logType, message);
if (writeToConsoleValue) LogHelper.SendToConsole(Settings, logType, messageWithHeader, true);
if (sendToSyslogServersValue || sendToSplunkServersValue || sendToGrayLogServersValue)
await Task.Run(async () =>
{ {
return; if (sendToSyslogServersValue)
} await LogHelper.SendToSysLogServersAsync(Settings, messageWithHeader, true);
string messageWithHeader = LogHelper.FormatMessageWithHeader(Settings, logType, message, dateTime); if (sendToSplunkServersValue)
bool writeToConsoleValue = writeToConsole ?? Settings.EnableConsole; await LogHelper.SendToSplunkServersAsync(Settings, customSplunkSourceType ?? logType.ToString(),
bool sendToSyslogServersValue = sendToSyslogServers ?? Settings.SendToSyslogServers; messageWithHeader, true);
bool sendToSplunkServersValue = sendToSplunkServers ?? Settings.SendToSplunkServers;
bool sendToGrayLogServersValue = sendToGrayLogServers ?? Settings.SendToGrayLogServers;
if (writeToConsoleValue) if (sendToGrayLogServersValue)
{ await LogHelper.SendToGrayLogServersAsync(Settings, messageWithHeader, logType, grayLogFacility,
LogHelper.SendToConsole(Settings, logType, messageWithHeader, true); grayLogSource, true, grayLogVersion);
} });
if (sendToSyslogServersValue || sendToSplunkServersValue || sendToGrayLogServersValue) var logMessage = new EonaCatLogMessage
{
await Task.Run(async () =>
{
if (sendToSyslogServersValue)
{
await LogHelper.SendToSysLogServersAsync(Settings, messageWithHeader, true);
}
if (sendToSplunkServersValue)
{
await LogHelper.SendToSplunkServersAsync(Settings, customSplunkSourceType ?? logType.ToString(), messageWithHeader, true);
}
if (sendToGrayLogServersValue)
{
await LogHelper.SendToGrayLogServersAsync(Settings, messageWithHeader, logType, grayLogFacility, grayLogSource, true, grayLogVersion);
}
});
}
var logMessage = new EonaCatLogMessage
{
DateTime = dateTime,
Message = message,
LogType = logType,
Origin = string.IsNullOrWhiteSpace(Settings.LogOrigin) ? "LogManager" : Settings.LogOrigin
};
Settings.OnLogEvent(logMessage);
}
public void Reset() => Settings.ResetLogEvent();
public LogManager(LoggerSettings settings, string serverIp, int serverPort)
{ {
if (string.IsNullOrEmpty(serverIp)) DateTime = dateTime,
throw new ArgumentNullException(nameof(serverIp)); Message = message,
LogType = logType,
Origin = string.IsNullOrWhiteSpace(Settings.LogOrigin) ? "LogManager" : Settings.LogOrigin
};
if (serverPort < 0) Settings.OnLogEvent(logMessage);
throw new ArgumentException("Server port must be zero or greater."); }
settings.SysLogServers = new List<SyslogServer> public void Reset()
{ {
new SyslogServer(serverIp, serverPort) Settings.ResetLogEvent();
}; }
Settings = settings; private void LogHelper_OnException(object sender, ErrorMessage e)
SetupLogManager(); {
} OnException?.Invoke(sender, e);
}
public LogManager(LoggerSettings settings) private void SetupFileLogger(LoggerSettings settings = null, string logFolder = null, bool defaultPrefix = true)
{ {
Settings = settings; if (settings == null)
SetupFileLogger(settings, null, true); settings = Settings;
SetupLogManager();
LogHelper.OnException += LogHelper_OnException;
}
private void LogHelper_OnException(object sender, ErrorMessage e) if (!settings.EnableFileLogging)
{ return;
OnException?.Invoke(sender, e);
}
private void SetupFileLogger(LoggerSettings settings = null, string logFolder = null, bool defaultPrefix = true) if (logFolder != null)
{ settings.FileLoggerOptions.LogDirectory = logFolder;
if (settings == null)
settings = Settings;
if (!settings.EnableFileLogging) if (string.IsNullOrWhiteSpace(settings.FileLoggerOptions.FileNamePrefix))
return; settings.FileLoggerOptions.FileNamePrefix = defaultPrefix ? "EonaCat" : string.Empty;
}
if (logFolder != null) private void SetupLogManager()
settings.FileLoggerOptions.LogDirectory = logFolder; {
AppDomain.CurrentDomain.ProcessExit += ProcessExit;
Console.CancelKeyPress += Console_CancelKeyPress;
_logDate = CurrentDateTme;
}
if (string.IsNullOrWhiteSpace(settings.FileLoggerOptions.FileNamePrefix)) private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
settings.FileLoggerOptions.FileNamePrefix = defaultPrefix ? "EonaCat" : string.Empty; {
} Dispose();
}
private void SetupLogManager() private void ProcessExit(object sender, EventArgs e)
{ {
AppDomain.CurrentDomain.ProcessExit += ProcessExit; Dispose();
Console.CancelKeyPress += Console_CancelKeyPress; }
_logDate = CurrentDateTme;
}
private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) private Task StopLoggingAsync()
{ {
Dispose(); IsRunning = false;
} return Task.CompletedTask;
}
private void ProcessExit(object sender, EventArgs e) public void DeleteCurrentLogFile()
{ {
Dispose(); if (CurrentLogFile != null)
} File.Delete(CurrentLogFile);
private async Task<Task> StopLoggingAsync()
{
IsRunning = false;
await InternalWriteAsync(CurrentDateTme, $"{DllInfo.ApplicationName} stopped.").ConfigureAwait(false);
return Task.Delay(500);
}
public void DeleteCurrentLogFile()
{
if (CurrentLogFile != null)
File.Delete(CurrentLogFile);
}
public void Dispose()
{
DisposeAsync(true).GetAwaiter().GetResult();
GC.SuppressFinalize(this);
}
public void Write(Exception exception, string module = null, string method = null, bool criticalException = false, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, string grayLogVersion = "1.1")
{
if (exception == null)
return;
Write(exception.FormatExceptionToMessage(module, method), criticalException ? ELogType.CRITICAL : ELogType.ERROR, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion);
}
public void Write(string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string? customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, string grayLogVersion = "1.1")
{
if (logType == ELogType.NONE)
return;
if (!IsRunning)
StartNewLogAsync().ConfigureAwait(false);
InternalWriteAsync(CurrentDateTme, message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion);
}
} }
} }

View File

@@ -5,192 +5,172 @@ using EonaCat.Logger.EonaCatCoreLogger.Models;
using EonaCat.Logger.GrayLog; using EonaCat.Logger.GrayLog;
using EonaCat.Logger.Syslog; using EonaCat.Logger.Syslog;
namespace EonaCat.Logger.Managers namespace EonaCat.Logger.Managers;
// 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>
/// Logger settings.
/// </summary>
public class LoggerSettings
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. public delegate void LogDelegate(EonaCatLogMessage message);
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
private ColorSchema _colors = new();
private bool _enableConsole = true;
private FileLoggerOptions _fileLoggerOptions;
private string _headerFormat = "{ts} {host} {thread} {sev}";
private int _maxMessageLength = 1024;
private string _timestampFormat = "yyyy-MM-dd HH:mm:ss";
/// <summary> /// <summary>
/// Logger settings. /// Determines if we need to use the local time in the logging or UTC (default:false)
/// </summary> /// </summary>
public class LoggerSettings public bool UseLocalTime { get; set; }
public string Id { get; set; } = "EonaCatLogger";
/// <summary>
/// Determines if we need to remove the prefix [EonaCatLogger] from each message (default: false)
/// </summary>
public bool RemoveMessagePrefix { get; set; }
/// <summary>
/// Header format. Provide a string that specifies how the preamble of each message should be structured. You can use
/// variables including:
/// {ts}: UTC timestamp
/// {host}: Hostname
/// {thread}: Thread ID
/// {sev}: Severity
/// Default: {ts} {host} {thread} {sev}
/// A space will be inserted between the header and the message.
/// </summary>
public string HeaderFormat
{ {
public event LogDelegate OnLog; get => _headerFormat;
public delegate void LogDelegate(EonaCatLogMessage message); set
/// <summary>
/// Determines if we need to use the local time in the logging or UTC (default:false)
/// </summary>
public bool UseLocalTime { get; set; }
public string Id { get; set; } = "EonaCatLogger";
/// <summary>
/// Determines if we need to remove the prefix [EonaCatLogger] from each message (default: false)
/// </summary>
public bool RemoveMessagePrefix { get; set; }
/// <summary>
/// Header format. Provide a string that specifies how the preamble of each message should be structured. You can use variables including:
/// {ts}: UTC timestamp
/// {host}: Hostname
/// {thread}: Thread ID
/// {sev}: Severity
/// Default: {ts} {host} {thread} {sev}
/// A space will be inserted between the header and the message.
/// </summary>
public string HeaderFormat
{ {
get if (string.IsNullOrEmpty(value)) _headerFormat = "";
{ else _headerFormat = value;
return _headerFormat;
}
set
{
if (string.IsNullOrEmpty(value)) _headerFormat = "";
else _headerFormat = value;
}
}
/// <summary>
/// Timestamp format.
/// </summary>
public string TimestampFormat
{
get
{
return _timestampFormat;
}
set
{
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(HeaderFormat));
_timestampFormat = value;
}
}
/// <summary>
/// Enable or disable console logging.
/// Settings this to true will first validate if a console exists.
/// If a console is not available, it will be set to false.
/// </summary>
public bool EnableConsole
{
get
{
return _enableConsole;
}
set
{
if (value) _enableConsole = ConsoleExists();
else _enableConsole = false;
}
}
/// <summary>
/// Enable or disable use of color for console messages.
/// </summary>
public bool EnableColors { get; set; } = true;
/// <summary>
/// Colors to use for console messages based on message severity.
/// </summary>
public ColorSchema Colors
{
get
{
return _colors;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(Colors));
}
_colors = value;
}
}
public ELogType MaxLogType { get; set; } = ELogType.TRACE;
public bool SendToSyslogServers { get; set; }
public List<SyslogServer> SysLogServers { get; set; }
public bool SendToSplunkServers { get; set; }
public bool SendToGrayLogServers { get; set; }
public List<SplunkServer.SplunkServer> SplunkServers { get; set; }
public List<GrayLogServer> GrayLogServers { get; set; }
/// <summary>
/// Determines if the fileLogging is enabled
/// </summary>
public bool EnableFileLogging { get; set; } = true;
private FileLoggerOptions _fileLoggerOptions;
/// <summary>
/// FileLogger settings.
/// </summary>
public FileLoggerOptions FileLoggerOptions
{
get
{
if (_fileLoggerOptions == null)
{
_fileLoggerOptions = CreateDefaultFileLoggerOptions();
}
return _fileLoggerOptions;
}
set
{
_fileLoggerOptions = value;
}
}
private static FileLoggerOptions CreateDefaultFileLoggerOptions()
{
return new FileLoggerOptions();
}
/// <summary>
/// Set the origin of where the OnLog event was initiated
/// </summary>
public string LogOrigin { get; set; }
private string _headerFormat = "{ts} {host} {thread} {sev}";
private string _timestampFormat = "yyyy-MM-dd HH:mm:ss";
private bool _enableConsole = true;
private int _maxMessageLength = 1024;
private ColorSchema _colors = new ColorSchema();
private static bool ConsoleExists()
{
try
{
bool test1 = Environment.UserInteractive;
bool test2 = Console.WindowHeight > 0;
return test1 && test2;
}
catch (Exception)
{
return false;
}
}
internal void OnLogEvent(EonaCatLogMessage eonaCatLogMessage)
{
OnLog?.Invoke(eonaCatLogMessage);
}
internal void ResetLogEvent()
{
OnLog = null;
} }
} }
/// <summary>
/// Timestamp format.
/// </summary>
public string TimestampFormat
{
get => _timestampFormat;
set
{
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(HeaderFormat));
_timestampFormat = value;
}
}
/// <summary>
/// Enable or disable console logging.
/// Settings this to true will first validate if a console exists.
/// If a console is not available, it will be set to false.
/// </summary>
public bool EnableConsole
{
get => _enableConsole;
set
{
if (value) _enableConsole = ConsoleExists();
else _enableConsole = false;
}
}
/// <summary>
/// Enable or disable use of color for console messages.
/// </summary>
public bool EnableColors { get; set; } = true;
/// <summary>
/// Colors to use for console messages based on message severity.
/// </summary>
public ColorSchema Colors
{
get => _colors;
set
{
if (value == null) throw new ArgumentNullException(nameof(Colors));
_colors = value;
}
}
public ELogType MinLogType { get; set; } = ELogType.INFO;
public bool SendToSyslogServers { get; set; }
public List<SyslogServer> SysLogServers { get; set; }
public bool SendToSplunkServers { get; set; }
public bool SendToGrayLogServers { get; set; }
public List<SplunkServer.SplunkServer> SplunkServers { get; set; }
public List<GrayLogServer> GrayLogServers { get; set; }
/// <summary>
/// Determines if the fileLogging is enabled
/// </summary>
public bool EnableFileLogging { get; set; } = true;
/// <summary>
/// FileLogger settings.
/// </summary>
public FileLoggerOptions FileLoggerOptions
{
get
{
if (_fileLoggerOptions == null) _fileLoggerOptions = CreateDefaultFileLoggerOptions();
return _fileLoggerOptions;
}
set => _fileLoggerOptions = value;
}
/// <summary>
/// Set the origin of where the OnLog event was initiated
/// </summary>
public string LogOrigin { get; set; }
public event LogDelegate OnLog;
private static FileLoggerOptions CreateDefaultFileLoggerOptions()
{
return new FileLoggerOptions();
}
private static bool ConsoleExists()
{
try
{
var test1 = Environment.UserInteractive;
var test2 = Console.WindowHeight > 0;
return test1 && test2;
}
catch (Exception)
{
return false;
}
}
internal void OnLogEvent(EonaCatLogMessage eonaCatLogMessage)
{
OnLog?.Invoke(eonaCatLogMessage);
}
internal void ResetLogEvent()
{
OnLog = null;
}
} }

View File

@@ -1,24 +1,19 @@
using System; namespace EonaCat.Logger.Splunk.Models;
using System.Collections.Generic;
using System.Text;
namespace EonaCat.Logger.Splunk.Models public class SplunkPayload
{ {
public class SplunkPayload /// <summary>
{ /// Event data for splunk
/// <summary> /// </summary>
/// Event data for splunk public string EventData { get; set; }
/// </summary>
public string EventData { get; set; }
/// <summary> /// <summary>
/// SourceType for splunk /// SourceType for splunk
/// </summary> /// </summary>
public string SourceType { get; set; } public string SourceType { get; set; }
/// <summary> /// <summary>
/// Host for splunk /// Host for splunk
/// </summary> /// </summary>
public string Host { get; set; } public string Host { get; set; }
}
} }

View File

@@ -1,154 +1,129 @@
using EonaCat.Json; using System;
using EonaCat.Logger.Splunk.Models;
using System;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using EonaCat.Json;
using EonaCat.Logger.Splunk.Models;
namespace EonaCat.Logger.SplunkServer namespace EonaCat.Logger.SplunkServer;
// 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>
/// Splunk Server.
/// </summary>
public class SplunkServer
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. internal readonly object SendLock = new();
// See the LICENSE file or go to https://EonaCat.com/License for full license details. private string _splunkHecUrl = "https://127.0.0.1:8088/services/collector/event";
/// <summary> /// <summary>
/// Splunk Server. /// Instantiate the object.
/// </summary> /// </summary>
public class SplunkServer /// <param name="splunkHecUrl">splunkHecUrl.</param>
/// <param name="splunkHecToken">splunkHecToken.</param>
/// <param name="httpClientHandler">httpClientHandler. (optional)</param>
public SplunkServer(string splunkHecUrl, string splunkHecToken, HttpClientHandler httpClientHandler = null)
{ {
/// <summary> SplunkHecUrl = splunkHecUrl;
/// SplunkHecUrl SplunkHecToken = splunkHecToken;
/// </summary>
public string SplunkHecUrl if (httpClientHandler == null) httpClientHandler = new HttpClientHandler();
SplunkClientHandler = httpClientHandler;
CreateHttpClient();
}
/// <summary>
/// SplunkHecUrl
/// </summary>
public string SplunkHecUrl
{
get => _splunkHecUrl;
set
{ {
get if (!string.IsNullOrWhiteSpace(_splunkHecUrl) && !_splunkHecUrl.ToLower().Contains("http"))
{ value = $"http://{value}";
return _splunkHecUrl; _splunkHecUrl = value;
}
set
{
if (!string.IsNullOrWhiteSpace(_splunkHecUrl) && !_splunkHecUrl.ToLower().Contains("http"))
{
value = $"http://{value}";
}
_splunkHecUrl = value;
}
}
public bool IsHttpsHecUrl => HasHecUrl && _splunkHecUrl.ToLower().StartsWith("https");
/// <summary>
/// SplunkHecToken
/// </summary>
public string SplunkHecToken
{
get
{
return _splunkHecToken;
}
set
{
_splunkHecToken = value;
}
}
public HttpClientHandler SplunkClientHandler { get; internal set; }
public HttpClient HttpClient { get; private set; }
internal readonly object SendLock = new object();
private string _splunkHecUrl = "https://127.0.0.1:8088/services/collector/event";
private string _splunkHecToken = "splunk-hec-token";
/// <summary>
/// Determines if a HEC token is available
/// </summary>
public bool HasHecToken => !string.IsNullOrWhiteSpace(SplunkHecToken) && SplunkHecToken != "splunk-hec-token";
/// <summary>
/// Determines if a HEC url is available
/// </summary>
public bool HasHecUrl => !string.IsNullOrWhiteSpace(SplunkHecUrl);
/// <summary>
/// Determines if the splunk URL is local
/// </summary>
public bool IsLocalHost => HasHecUrl && (SplunkHecUrl.ToLower().Contains("127.0.0.1") || SplunkHecUrl.ToLower().Contains("localhost"));
/// <summary>
/// Instantiate the object.
/// </summary>
/// <param name="splunkHecUrl">splunkHecUrl.</param>
/// <param name="splunkHecToken">splunkHecToken.</param>
/// <param name="httpClientHandler">httpClientHandler. (optional)</param>
public SplunkServer(string splunkHecUrl, string splunkHecToken, HttpClientHandler httpClientHandler = null)
{
SplunkHecUrl = splunkHecUrl;
SplunkHecToken = splunkHecToken;
if (httpClientHandler == null)
{
httpClientHandler = new HttpClientHandler();
}
SplunkClientHandler = httpClientHandler;
CreateHttpClient();
}
private void CreateHttpClient()
{
HttpClient = new HttpClient(SplunkClientHandler);
HttpClient.BaseAddress = new Uri(SplunkHecUrl);
// Add the HEC token to the request headers for authorization
HttpClient.DefaultRequestHeaders.Add("Authorization", $"Splunk {SplunkHecToken}");
}
/// <summary>
/// Disables SSL validation (can be used for insecure https urls)
/// <note>This overwrites your own httpClientHandler</note>
/// </summary>
public void DisableSSLValidation()
{
HttpClientHandler clientHandler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true,
};
SplunkClientHandler = clientHandler;
CreateHttpClient();
}
public async Task<HttpResponseMessage> SendAsync(SplunkPayload splunkPayload)
{
if (splunkPayload == null)
{
return null;
}
// Create an event object with the required fields
var eventObject = new
{
@event = splunkPayload.EventData,
sourcetype = splunkPayload.SourceType,
host = splunkPayload.Host
};
// Serialize the event object to JSON
string eventJson = JsonHelper.ToJson(eventObject);
if (!HasHecToken)
{
CreateHttpClient();
}
// Create an HTTP content with the event data
var content = new StringContent(eventJson, Encoding.UTF8, "application/json");
// Send the event to Splunk
HttpResponseMessage response = await HttpClient.PostAsync("/services/collector/event", content);
return response;
} }
} }
public bool IsHttpsHecUrl => HasHecUrl && _splunkHecUrl.ToLower().StartsWith("https");
/// <summary>
/// SplunkHecToken
/// </summary>
public string SplunkHecToken { get; set; } = "splunk-hec-token";
public HttpClientHandler SplunkClientHandler { get; internal set; }
public HttpClient HttpClient { get; private set; }
/// <summary>
/// Determines if a HEC token is available
/// </summary>
public bool HasHecToken => !string.IsNullOrWhiteSpace(SplunkHecToken) && SplunkHecToken != "splunk-hec-token";
/// <summary>
/// Determines if a HEC url is available
/// </summary>
public bool HasHecUrl => !string.IsNullOrWhiteSpace(SplunkHecUrl);
/// <summary>
/// Determines if the splunk URL is local
/// </summary>
public bool IsLocalHost => HasHecUrl &&
(SplunkHecUrl.ToLower().Contains("127.0.0.1") ||
SplunkHecUrl.ToLower().Contains("localhost"));
private void CreateHttpClient()
{
HttpClient = new HttpClient(SplunkClientHandler);
HttpClient.BaseAddress = new Uri(SplunkHecUrl);
// Add the HEC token to the request headers for authorization
HttpClient.DefaultRequestHeaders.Add("Authorization", $"Splunk {SplunkHecToken}");
}
/// <summary>
/// Disables SSL validation (can be used for insecure https urls)
/// <note>This overwrites your own httpClientHandler</note>
/// </summary>
public void DisableSSLValidation()
{
var clientHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
SplunkClientHandler = clientHandler;
CreateHttpClient();
}
public async Task<HttpResponseMessage> SendAsync(SplunkPayload splunkPayload)
{
if (splunkPayload == null) return null;
// Create an event object with the required fields
var eventObject = new
{
@event = splunkPayload.EventData,
sourcetype = splunkPayload.SourceType,
host = splunkPayload.Host
};
// Serialize the event object to JSON
var eventJson = JsonHelper.ToJson(eventObject);
if (!HasHecToken) CreateHttpClient();
// Create an HTTP content with the event data
var content = new StringContent(eventJson, Encoding.UTF8, "application/json");
// Send the event to Splunk
var response = await HttpClient.PostAsync("/services/collector/event", content);
return response;
}
} }

View File

@@ -1,92 +1,77 @@
using System; using System;
using System.Collections.Generic;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text;
namespace EonaCat.Logger.Syslog namespace EonaCat.Logger.Syslog;
// 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>
/// Syslog server.
/// </summary>
public class SyslogServer
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. private string _Hostname = "127.0.0.1";
// See the LICENSE file or go to https://EonaCat.com/License for full license details. private int _Port = 514;
internal UdpClient Udp;
/// <summary> /// <summary>
/// Syslog server. /// Instantiate the object.
/// </summary> /// </summary>
public class SyslogServer public SyslogServer()
{ {
/// <summary> }
/// Hostname.
/// </summary> /// <summary>
public string Hostname /// Instantiate the object.
/// </summary>
/// <param name="hostname">Hostname.</param>
/// <param name="port">Port.</param>
public SyslogServer(string hostname = "127.0.0.1", int port = 514)
{
Hostname = hostname;
Port = port;
}
/// <summary>
/// Hostname.
/// </summary>
public string Hostname
{
get => _Hostname;
set
{ {
get if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(Hostname));
{ _Hostname = value;
return _Hostname;
}
set SetUdp();
{
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(Hostname));
_Hostname = value;
SetUdp();
}
}
/// <summary>
/// UDP port.
/// </summary>
public int Port
{
get
{
return _Port;
}
set
{
if (value < 0) throw new ArgumentException("Port must be zero or greater.");
_Port = value;
SetUdp();
}
}
/// <summary>
/// IP:port of the server.
/// </summary>
public string IpPort
{
get
{
return _Hostname + ":" + _Port;
}
}
internal UdpClient Udp = null;
private string _Hostname = "127.0.0.1";
private int _Port = 514;
/// <summary>
/// Instantiate the object.
/// </summary>
public SyslogServer()
{
}
/// <summary>
/// Instantiate the object.
/// </summary>
/// <param name="hostname">Hostname.</param>
/// <param name="port">Port.</param>
public SyslogServer(string hostname = "127.0.0.1", int port = 514)
{
Hostname = hostname;
Port = port;
}
private void SetUdp()
{
Udp = null;
Udp = new UdpClient(_Hostname, _Port);
} }
} }
/// <summary>
/// UDP port.
/// </summary>
public int Port
{
get => _Port;
set
{
if (value < 0) throw new ArgumentException("Port must be zero or greater.");
_Port = value;
SetUdp();
}
}
/// <summary>
/// IP:port of the server.
/// </summary>
public string IpPort => _Hostname + ":" + _Port;
private void SetUdp()
{
Udp = null;
Udp = new UdpClient(_Hostname, _Port);
}
} }

View File

@@ -1,14 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\EonaCat.Logger\EonaCat.Logger.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,14 +0,0 @@
using EonaCat.Logger;
using EonaCat.Logger.Managers;
LogManager.Instance.Write("INFO", ELogType.INFO, true);
LogManager.Instance.Write("WARNING", ELogType.WARNING, true);
LogManager.Instance.Write("ERROR", ELogType.ERROR, true);
LogManager.Instance.Write("DEBUG", ELogType.DEBUG, true);
LogManager.Instance.Write("CRITICAL", ELogType.CRITICAL, true);
LogManager.Instance.Write("TRACE", ELogType.TRACE, true);
LogManager.Instance.Write("TRAFFIC", ELogType.TRAFFIC, true);
LogManager.Instance.Write("NONE", ELogType.NONE, true);
Console.WriteLine("Press a key to exit");
Console.ReadKey();

View File

@@ -1,118 +1,120 @@
using EonaCat.Logger.Managers; using System.IO.Compression;
using System.IO.Compression; using EonaCat.Logger.EonaCatCoreLogger;
using EonaCat.Logger.Extensions; using EonaCat.Logger.Extensions;
using EonaCat.Logger.Managers;
namespace EonaCat.Logger.Test.Web namespace EonaCat.Logger.Test.Web;
public static class Logger
{ {
public static class Logger private static LogManager LogManager;
public static ELogType MinLogType { get; set; }
public static bool UseLocalTime { get; set; }
public static string LogFolder => Path.Combine(FileLoggerOptions.DefaultPath, "logs");
public static string CurrentLogFile => LogManager.CurrentLogFile;
public static bool IsDisabled { get; set; }
public static void DeleteCurrentLogFile()
{ {
public static ELogType MaxLogType { get; set; } if (IsDisabled)
public static bool UseLocalTime { get; set; } return;
private static LogManager LogManager;
public static string LogFolder => "Logs";
public static string CurrentLogFile => LogManager.CurrentLogFile;
public static bool IsDisabled { get; set; }
public static void DeleteCurrentLogFile() LogManager.DeleteCurrentLogFile();
}
private static string ConvertToAbsolutePath(string path)
{
return Path.IsPathRooted(path) ? path : Path.Combine(LogFolder, path);
}
public static async Task DownloadLogAsync(HttpContext context, string logName, long limit)
{
var logFileName = logName + ".log";
await using var fS = new FileStream(Path.Combine(ConvertToAbsolutePath(LogFolder), logFileName), FileMode.Open,
FileAccess.Read, FileShare.ReadWrite, 64 * 1024, true);
var response = context.Response;
response.ContentType = "text/plain";
response.Headers.ContentDisposition = "attachment;filename=" + logFileName;
if (limit > fS.Length || limit < 1)
limit = fS.Length;
var oFS = new OffsetStream(fS, 0, limit);
var request = context.Request;
Stream stream;
string acceptEncoding = request.Headers["Accept-Encoding"];
if (string.IsNullOrEmpty(acceptEncoding))
{ {
if (IsDisabled) stream = response.Body;
return;
LogManager.DeleteCurrentLogFile();
} }
else
private static string ConvertToAbsolutePath(string path)
{ {
return Path.IsPathRooted(path) ? path : Path.Combine(LogFolder, path); var acceptEncodingParts = acceptEncoding.Split(new[] { ',' },
} StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
public static async Task DownloadLogAsync(HttpContext context, string logName, long limit) if (acceptEncodingParts.Contains("br"))
{
var logFileName = logName + ".log";
await using var fS = new FileStream(Path.Combine(ConvertToAbsolutePath(LogFolder), logFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 64 * 1024, true);
var response = context.Response;
response.ContentType = "text/plain";
response.Headers.ContentDisposition = "attachment;filename=" + logFileName;
if ((limit > fS.Length) || (limit < 1))
limit = fS.Length;
var oFS = new OffsetStream(fS, 0, limit);
var request = context.Request;
Stream stream;
string acceptEncoding = request.Headers["Accept-Encoding"];
if (string.IsNullOrEmpty(acceptEncoding))
{ {
stream = response.Body; 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 else
{ {
string[] acceptEncodingParts = acceptEncoding.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); stream = response.Body;
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 (fS.Length > limit)
await stream.WriteAsync("\r\n####__EONACATLOGGER_TRUNCATED___####"u8.ToArray()).ConfigureAwait(false);
} }
} }
public static void Log(string message, ELogType logType = ELogType.INFO, bool writeToConsole = true) await using (stream)
{ {
if (IsDisabled) await oFS.CopyToAsync(stream).ConfigureAwait(false);
return;
LogManager.Write(message, logType, writeToConsole); if (fS.Length > limit)
} await stream.WriteAsync("\r\n####__EONACATLOGGER_TRUNCATED___####"u8.ToArray()).ConfigureAwait(false);
public static void Log(Exception exception, string message = "", bool writeToConsole = true)
{
if (IsDisabled)
return;
LogManager.Write(exception, module: message, writeToConsole: writeToConsole);
}
public static void Configure()
{
var loggerSettings = new LoggerSettings
{
Id = "EonaCatDnsLogger",
MaxLogType = ELogType.TRACE,
UseLocalTime = UseLocalTime,
FileLoggerOptions =
{
LogDirectory = "Logs",
FileSizeLimit = 20_000_000, // 20 MB,
UseLocalTime = UseLocalTime,
}
};
LogManager = new LogManager(loggerSettings);
} }
} }
public static void Log(string message, ELogType logType = ELogType.INFO, bool writeToConsole = true)
{
if (IsDisabled)
return;
LogManager.Write(message, logType, writeToConsole);
}
public static void Log(Exception exception, string message = "", bool writeToConsole = true)
{
if (IsDisabled)
return;
LogManager.Write(exception, message, writeToConsole: writeToConsole);
}
public static void Configure()
{
var loggerSettings = new LoggerSettings
{
Id = "EonaCatTestLogger",
MinLogType = ELogType.INFO,
UseLocalTime = UseLocalTime,
FileLoggerOptions =
{
LogDirectory = LogFolder,
FileSizeLimit = 20_000_000, // 20 MB,
UseLocalTime = UseLocalTime
}
};
LogManager = new LogManager(loggerSettings);
}
} }

View File

@@ -1,27 +1,26 @@
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Diagnostics;
namespace EonaCat.Logger.Web.Pages namespace EonaCat.Logger.Web.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel
{ {
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] private readonly ILogger<ErrorModel> _logger;
[IgnoreAntiforgeryToken]
public class ErrorModel : PageModel public ErrorModel(ILogger<ErrorModel> logger)
{ {
public string? RequestId { get; set; } _logger = logger;
}
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); public string? RequestId { get; set; }
private readonly ILogger<ErrorModel> _logger; public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public ErrorModel(ILogger<ErrorModel> logger) public void OnGet()
{ {
_logger = logger; RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
} }
} }

View File

@@ -1,19 +1,17 @@
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
namespace EonaCat.Logger.Web.Pages namespace EonaCat.Logger.Web.Pages;
public class IndexModel : PageModel
{ {
public class IndexModel : PageModel private readonly ILogger<IndexModel> _logger;
public IndexModel(ILogger<IndexModel> logger)
{ {
private readonly ILogger<IndexModel> _logger; _logger = logger;
}
public IndexModel(ILogger<IndexModel> logger) public void OnGet()
{ {
_logger = logger;
}
public void OnGet()
{
}
} }
} }

View File

@@ -1,19 +1,17 @@
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace EonaCat.Logger.Web.Pages namespace EonaCat.Logger.Web.Pages;
public class PrivacyModel : PageModel
{ {
public class PrivacyModel : PageModel private readonly ILogger<PrivacyModel> _logger;
public PrivacyModel(ILogger<PrivacyModel> logger)
{ {
private readonly ILogger<PrivacyModel> _logger; _logger = logger;
}
public PrivacyModel(ILogger<PrivacyModel> logger) public void OnGet()
{ {
_logger = logger;
}
public void OnGet()
{
}
} }
} }

View File

@@ -1,51 +1,51 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>@ViewData["Title"] - EonaCat.Logger.Web</title> <title>@ViewData["Title"] - EonaCat.Logger.Web</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" /> <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css"/>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> <link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/EonaCat.Logger.Web.styles.css" asp-append-version="true" /> <link rel="stylesheet" href="~/EonaCat.Logger.Web.styles.css" asp-append-version="true"/>
</head> </head>
<body> <body>
<header> <header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3"> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-page="/Index">EonaCat.Logger.Web</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container"> <div class="container">
&copy; 2022 - EonaCat.Logger.Web - <a asp-area="" asp-page="/Privacy">Privacy</a> <a class="navbar-brand" asp-area="" asp-page="/Index">EonaCat.Logger.Web</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-page="/Privacy">Privacy</a>
</li>
</ul>
</div>
</div> </div>
</footer> </nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<script src="~/lib/jquery/dist/jquery.min.js"></script> <footer class="border-top footer text-muted">
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <div class="container">
<script src="~/js/site.js" asp-append-version="true"></script> &copy; 2022 - EonaCat.Logger.Web - <a asp-area="" asp-page="/Privacy">Privacy</a>
</div>
</footer>
@await RenderSectionAsync("Scripts", required: false) <script src="~/lib/jquery/dist/jquery.min.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", false)
</body> </body>
</html> </html>

View File

@@ -10,11 +10,11 @@ using EonaCat.Web.Tracer.Extensions;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Add services to the container. // Add services to the container.
FileLoggerOptions options = new FileLoggerOptions(); var options = new FileLoggerOptions();
options.MaxRolloverFiles = 5; options.MaxRolloverFiles = 5;
options.FileSizeLimit = 1 * 1024 * 1024 / 4; options.FileSizeLimit = 1 * 1024 * 1024 / 4;
options.UseLocalTime = true; options.UseLocalTime = true;
builder.Logging.AddEonaCatFileLogger(fileLoggerOptions: options, filenamePrefix:"web"); builder.Logging.AddEonaCatFileLogger(fileLoggerOptions: options, filenamePrefix: "web");
builder.Services.AddRazorPages(); builder.Services.AddRazorPages();
@@ -35,6 +35,7 @@ rateOptions.AddDefaultConfiguration(config =>
); );
RateLimiterMiddleware.OnLimitResponseCreated += RateLimiterMiddlewareOnLimitResponseCreatedAsync; RateLimiterMiddleware.OnLimitResponseCreated += RateLimiterMiddlewareOnLimitResponseCreatedAsync;
async void RateLimiterMiddlewareOnLimitResponseCreatedAsync(object? sender, HttpContext httpContext) async void RateLimiterMiddlewareOnLimitResponseCreatedAsync(object? sender, HttpContext httpContext)
{ {
await httpContext.Response.WriteAsync(" THIS IS MY CUSTOM RATE LIMIT MESSAGE").ConfigureAwait(false); await httpContext.Response.WriteAsync(" THIS IS MY CUSTOM RATE LIMIT MESSAGE").ConfigureAwait(false);
@@ -61,7 +62,7 @@ builder.Services.AddMemoryCache();
var app = builder.Build(); var app = builder.Build();
Logger.UseLocalTime = true; Logger.UseLocalTime = true;
Logger.MaxLogType = ELogType.TRACE; Logger.MinLogType = ELogType.TRACE;
Logger.Configure(); Logger.Configure();
// Configure the HTTP request pipeline. // Configure the HTTP request pipeline.
@@ -83,6 +84,7 @@ if (!app.Environment.IsDevelopment())
// await httpContext.Response.WriteAsync("THIS IS MY CUSTOM RATE LIMIT MESSAGE"); // await httpContext.Response.WriteAsync("THIS IS MY CUSTOM RATE LIMIT MESSAGE");
//} //}
WebTracer.OnLog += WebTracer_OnLog; WebTracer.OnLog += WebTracer_OnLog;
void WebTracer_OnLog(object? sender, string e) void WebTracer_OnLog(object? sender, string e)
{ {
Console.WriteLine(e); Console.WriteLine(e);
@@ -103,10 +105,10 @@ void RunLoggingExceptionTests()
var loggerSettings = new LoggerSettings(); var loggerSettings = new LoggerSettings();
loggerSettings.FileLoggerOptions.UseLocalTime = true; loggerSettings.FileLoggerOptions.UseLocalTime = true;
loggerSettings.UseLocalTime = true; loggerSettings.UseLocalTime = true;
loggerSettings.MaxLogType = ELogType.TRACE; loggerSettings.MinLogType = ELogType.INFO;
var logger = new LogManager(loggerSettings); var logger = new LogManager(loggerSettings);
for (int i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
try try
{ {
@@ -117,19 +119,20 @@ void RunLoggingExceptionTests()
logger.Write(exception); logger.Write(exception);
Console.WriteLine($"Normal ExceptionLogged: {i}"); Console.WriteLine($"Normal ExceptionLogged: {i}");
} }
Task.Delay(1); Task.Delay(1);
} }
} }
Task.Run(RunWebLoggerTests); //Task.Run(RunWebLoggerTests);
Task.Run(RunWebLoggingTests); //Task.Run(RunWebLoggingTests);
Task.Run(RunLoggingTests); Task.Run(RunLoggingTests);
Task.Run(RunLoggingExceptionTests); //Task.Run(RunLoggingExceptionTests);
Task.Run(RunWebLoggingExceptionTests); //Task.Run(RunWebLoggingExceptionTests);
void RunWebLoggingExceptionTests() void RunWebLoggingExceptionTests()
{ {
for (int i = 0; i < 10; i++) for (var i = 0; i < 10; i++)
{ {
try try
{ {
@@ -145,18 +148,16 @@ void RunWebLoggingExceptionTests()
app.Logger.LogInformation(exception, "INFORMATION"); app.Logger.LogInformation(exception, "INFORMATION");
Console.WriteLine($"WebExceptionLogged: {i}"); Console.WriteLine($"WebExceptionLogged: {i}");
} }
Task.Delay(1); Task.Delay(1);
} }
} }
void RunWebLoggingTests() void RunWebLoggingTests()
{ {
if (!Directory.Exists(Logger.LogFolder)) if (!Directory.Exists(Logger.LogFolder)) Directory.CreateDirectory(Logger.LogFolder);
{
Directory.CreateDirectory(Logger.LogFolder);
}
for (int i = 0; i < 9000000; i++) for (var i = 0; i < 9000000; i++)
{ {
app.Logger.LogInformation($"web-test {i}"); app.Logger.LogInformation($"web-test {i}");
File.AppendAllText(Path.Combine(Logger.LogFolder, "test.log"), $"WebLogged: {i}{Environment.NewLine}"); File.AppendAllText(Path.Combine(Logger.LogFolder, "test.log"), $"WebLogged: {i}{Environment.NewLine}");
@@ -170,15 +171,15 @@ void RunLoggingTests()
var loggerSettings = new LoggerSettings(); var loggerSettings = new LoggerSettings();
loggerSettings.UseLocalTime = true; loggerSettings.UseLocalTime = true;
loggerSettings.FileLoggerOptions.UseLocalTime = true; loggerSettings.FileLoggerOptions.UseLocalTime = true;
loggerSettings.MaxLogType = ELogType.TRACE; loggerSettings.MinLogType = ELogType.INFO;
loggerSettings.FileLoggerOptions.FileSizeLimit = 1024 * 1024 * 1; loggerSettings.FileLoggerOptions.FileSizeLimit = 1024 * 1024 * 1;
loggerSettings.FileLoggerOptions.FileNamePrefix = "AllTypes"; loggerSettings.FileLoggerOptions.FileNamePrefix = "AllTypes";
loggerSettings.FileLoggerOptions.MaxRolloverFiles = 5; loggerSettings.FileLoggerOptions.MaxRolloverFiles = 5;
var logger = new LogManager(loggerSettings); var logger = new LogManager(loggerSettings);
for (int i = 0; i < 9000000; i++) for (var i = 0; i < 9000000; i++)
{ {
logger.Write($"test to file {i} INFO", ELogType.INFO); logger.Write($"test to file {i} INFO");
logger.Write($"test to file {i} CRITICAL", ELogType.CRITICAL); logger.Write($"test to file {i} CRITICAL", ELogType.CRITICAL);
logger.Write($"test to file {i} DEBUG", ELogType.DEBUG); logger.Write($"test to file {i} DEBUG", ELogType.DEBUG);
logger.Write($"test to file {i} ERROR", ELogType.ERROR); logger.Write($"test to file {i} ERROR", ELogType.ERROR);
@@ -193,9 +194,9 @@ void RunLoggingTests()
void RunWebLoggerTests() void RunWebLoggerTests()
{ {
for (int i = 0; i < 9000000; i++) for (var i = 0; i < 9000000; i++)
{ {
Logger.Log($"test via logger {i} INFO", ELogType.INFO); Logger.Log($"test via logger {i} INFO");
Logger.Log($"test via logger {i} CRITICAL", ELogType.CRITICAL); Logger.Log($"test via logger {i} CRITICAL", ELogType.CRITICAL);
Logger.Log($"test via logger {i} DEBUG", ELogType.DEBUG); Logger.Log($"test via logger {i} DEBUG", ELogType.DEBUG);
Logger.Log($"test via logger {i} ERROR", ELogType.ERROR); Logger.Log($"test via logger {i} ERROR", ELogType.ERROR);