Initial version
This commit is contained in:
25
EonaCat.Logger.sln
Normal file
25
EonaCat.Logger.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.3.32811.315
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EonaCat.Logger", "EonaCat.Logger\EonaCat.Logger.csproj", "{DCD1D32E-0F24-4D0F-A6B6-59941C0F9BB7}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{DCD1D32E-0F24-4D0F-A6B6-59941C0F9BB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{DCD1D32E-0F24-4D0F-A6B6-59941C0F9BB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{DCD1D32E-0F24-4D0F-A6B6-59941C0F9BB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{DCD1D32E-0F24-4D0F-A6B6-59941C0F9BB7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {B01183F3-D85E-45FB-9749-DA281F465A0F}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
13
EonaCat.Logger/Constants.cs
Normal file
13
EonaCat.Logger/Constants.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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 Constants
|
||||||
|
{
|
||||||
|
public static class DateTimeFormats
|
||||||
|
{
|
||||||
|
public static string LOGGING { get; } = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
EonaCat.Logger/DllInfo.cs
Normal file
26
EonaCat.Logger/DllInfo.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
public static class DllInfo
|
||||||
|
{
|
||||||
|
public const string NAME = "EonaCatLogger";
|
||||||
|
public const string VERSION = "1.0.0";
|
||||||
|
|
||||||
|
static DllInfo()
|
||||||
|
{
|
||||||
|
bool isDebug = false;
|
||||||
|
#if DEBUG
|
||||||
|
isDebug = true;
|
||||||
|
#endif
|
||||||
|
VERSION_NAME = isDebug ? "DEBUG" : "RELEASE";
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string VERSION_NAME { get; }
|
||||||
|
|
||||||
|
public static string ApplicationName { get; internal set; } = "EonaCatLogger";
|
||||||
|
|
||||||
|
public static ELogType LogLevel { get; internal set; } = ELogType.INFO;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
EonaCat.Logger/Enums.cs
Normal file
17
EonaCat.Logger/Enums.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
NONE = 0,
|
||||||
|
INFO = 1,
|
||||||
|
WARNING = 2,
|
||||||
|
ERROR = 3,
|
||||||
|
TRAFFIC = 4,
|
||||||
|
DEBUG = 5,
|
||||||
|
CRITICAL = 6,
|
||||||
|
TRACE = 7
|
||||||
|
}
|
||||||
|
}
|
||||||
64
EonaCat.Logger/EonaCat.Logger.csproj
Normal file
64
EonaCat.Logger/EonaCat.Logger.csproj
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>
|
||||||
|
netstandard2.0;
|
||||||
|
netstandard2.1;
|
||||||
|
net5.0;
|
||||||
|
net6.0;
|
||||||
|
</TargetFrameworks>
|
||||||
|
<ApplicationIcon>icon.ico</ApplicationIcon>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
<Authors>EonaCat (Jeroen Saey)</Authors>
|
||||||
|
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||||
|
<Company>EonaCat (Jeroen Saey)</Company>
|
||||||
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
|
<PackageProjectUrl>https://www.nuget.org/packages/EonaCat.Logger/</PackageProjectUrl>
|
||||||
|
<Description>EonaCat.Logger is a logging library created for .NET Standard.</Description>
|
||||||
|
<PackageReleaseNotes>Public release version</PackageReleaseNotes>
|
||||||
|
<Copyright>EonaCat (Jeroen Saey)</Copyright>
|
||||||
|
<PackageTags>EonaCat, Logger, .NET Standard, EonaCatLogger, Log, Writer Jeroen, Saey</PackageTags>
|
||||||
|
<PackageIconUrl />
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
<GenerateDocumentationFile>True</GenerateDocumentationFile>
|
||||||
|
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||||
|
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="icon.png" />
|
||||||
|
<None Include="..\LICENSE">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
<None Include="..\README.md">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
<None Include="icon.png">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath></PackagePath>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="LICENSE.md">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
<None Update="LICENSE.x">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
<None Update="README.md">
|
||||||
|
<Pack>True</Pack>
|
||||||
|
<PackagePath>\</PackagePath>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extensions for adding the <see cref="FileLoggerProvider" /> to the <see cref="ILoggingBuilder" />
|
||||||
|
/// </summary>
|
||||||
|
public static class FileLoggerFactoryExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a file logger named 'File' to the factory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
|
||||||
|
public static ILoggingBuilder AddFile(this ILoggingBuilder builder)
|
||||||
|
{
|
||||||
|
builder.Services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a file logger named 'File' to the factory.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="builder">The <see cref="ILoggingBuilder"/> to use.</param>
|
||||||
|
/// <param name="filenamePrefix">Sets the filename prefix to use for log files</param>
|
||||||
|
public static ILoggingBuilder AddFile(this ILoggingBuilder builder, string filenamePrefix)
|
||||||
|
{
|
||||||
|
builder.AddFile(options => options.FileNamePrefix = filenamePrefix);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a file logger named 'File' 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 AddFile(this ILoggingBuilder builder, Action<FileLoggerOptions> configure)
|
||||||
|
{
|
||||||
|
if (configure == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(configure));
|
||||||
|
}
|
||||||
|
builder.AddFile();
|
||||||
|
builder.Services.Configure(configure);
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs
Normal file
89
EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using EonaCat.Logger.Internal;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Options for file logging.
|
||||||
|
/// </summary>
|
||||||
|
public class FileLoggerOptions : BatchingLoggerOptions
|
||||||
|
{
|
||||||
|
private int _fileSizeLimit = 200 * 1024 * 1024;
|
||||||
|
private int _retainedFileCountLimit = 50;
|
||||||
|
private int _maxRolloverFiles = 10;
|
||||||
|
|
||||||
|
public static string DefaultPath => AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a strictly positive value representing the maximum log size in bytes or null for no limit.
|
||||||
|
/// Once the log is full, no more messages will be appended.
|
||||||
|
/// Defaults to <c>200MB</c>.
|
||||||
|
/// </summary>
|
||||||
|
public int FileSizeLimit
|
||||||
|
{
|
||||||
|
get => _fileSizeLimit;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
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 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
138
EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
Normal file
138
EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using EonaCat.Logger.Internal;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="ILoggerProvider" /> that writes logs to a file
|
||||||
|
/// </summary>
|
||||||
|
[ProviderAlias("File")]
|
||||||
|
public class FileLoggerProvider : BatchingLoggerProvider
|
||||||
|
{
|
||||||
|
private readonly string _path;
|
||||||
|
private readonly string _fileNamePrefix;
|
||||||
|
private readonly int _maxFileSize;
|
||||||
|
private readonly int _maxRetainedFiles;
|
||||||
|
private readonly int _maxRolloverFiles;
|
||||||
|
private int _rollOverCount = 0;
|
||||||
|
|
||||||
|
public string LogFile { get; private set; }
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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)
|
||||||
|
{
|
||||||
|
fileInfo.CopyTo(LogFile.Replace(".log", $"_{++_rollOverCount}.log"));
|
||||||
|
File.WriteAllText(LogFile, string.Empty);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool areFilesDeleted = false;
|
||||||
|
for (int i = 0; i < _rollOverCount; i++)
|
||||||
|
{
|
||||||
|
File.Delete(LogFile.Replace(".log", $"_{i}.log"));
|
||||||
|
areFilesDeleted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (areFilesDeleted)
|
||||||
|
{
|
||||||
|
File.Move(LogFile.Replace(".log", $"_{_rollOverCount}.log"), LogFile.Replace(".log", $"_1.log"));
|
||||||
|
_rollOverCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using (StreamWriter streamWriter = File.AppendText(LogFile))
|
||||||
|
{
|
||||||
|
foreach (LogMessage item in group)
|
||||||
|
{
|
||||||
|
await streamWriter.WriteAsync(item.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
Normal file
64
EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.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
|
||||||
|
{
|
||||||
|
private readonly BatchingLoggerProvider _provider;
|
||||||
|
private readonly string _category;
|
||||||
|
|
||||||
|
public BatchingLogger(BatchingLoggerProvider loggerProvider, string categoryName)
|
||||||
|
{
|
||||||
|
_provider = loggerProvider;
|
||||||
|
_category = categoryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDisposable BeginScope<TState>(TState state)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsEnabled(LogLevel logLevel)
|
||||||
|
{
|
||||||
|
if (logLevel == LogLevel.None)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log<TState>(DateTimeOffset timestamp, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
if (!IsEnabled(logLevel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
builder.Append(timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff zzz"));
|
||||||
|
builder.Append(" [");
|
||||||
|
builder.Append(logLevel.ToString());
|
||||||
|
builder.Append("] ");
|
||||||
|
builder.Append(_category);
|
||||||
|
builder.Append(": ");
|
||||||
|
builder.AppendLine(formatter(state, exception));
|
||||||
|
|
||||||
|
if (exception != null)
|
||||||
|
{
|
||||||
|
builder.AppendLine(exception.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
_provider.AddMessage(timestamp, builder.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
|
||||||
|
{
|
||||||
|
Log(DateTimeOffset.Now, logLevel, eventId, state, exception, formatter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.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
|
||||||
|
{
|
||||||
|
private int _batchSize = 32;
|
||||||
|
private int _backgroundQueueSize;
|
||||||
|
private TimeSpan _flushPeriod = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the period after which logs will be flushed to the store.
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan FlushPeriod
|
||||||
|
{
|
||||||
|
get => _flushPeriod;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FlushPeriod)} must be positive.");
|
||||||
|
}
|
||||||
|
_flushPeriod = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum size of the background log message queue or less than 1 for no limit.
|
||||||
|
/// After maximum queue size is reached log event sink would start blocking.
|
||||||
|
/// Defaults to <c>0</c>.
|
||||||
|
/// </summary>
|
||||||
|
public int BackgroundQueueSize
|
||||||
|
{
|
||||||
|
get => _backgroundQueueSize;
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_backgroundQueueSize = 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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.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
|
||||||
|
{
|
||||||
|
private readonly List<LogMessage> _currentBatch = new List<LogMessage>();
|
||||||
|
private readonly TimeSpan _interval;
|
||||||
|
private readonly int _queueSize;
|
||||||
|
private readonly int _batchSize;
|
||||||
|
|
||||||
|
private BlockingCollection<LogMessage> _messageQueue;
|
||||||
|
private Task _outputTask;
|
||||||
|
private CancellationTokenSource _cancellationTokenSource;
|
||||||
|
|
||||||
|
protected BatchingLoggerProvider(IOptions<BatchingLoggerOptions> options)
|
||||||
|
{
|
||||||
|
BatchingLoggerOptions loggerOptions = options.Value;
|
||||||
|
|
||||||
|
if (loggerOptions.BatchSize <= 0)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(loggerOptions.BatchSize), $"{nameof(loggerOptions.BatchSize)} must be a positive number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loggerOptions.FlushPeriod <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod), $"{nameof(loggerOptions.FlushPeriod)} must be longer than zero.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_interval = loggerOptions.FlushPeriod;
|
||||||
|
_batchSize = loggerOptions.BatchSize;
|
||||||
|
_queueSize = loggerOptions.BackgroundQueueSize;
|
||||||
|
|
||||||
|
Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Task WriteMessagesAsync(IEnumerable<LogMessage> messages, CancellationToken token);
|
||||||
|
|
||||||
|
private async Task ProcessLogQueue(object state)
|
||||||
|
{
|
||||||
|
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
int limit = _batchSize <= 0 ? int.MaxValue : _batchSize;
|
||||||
|
|
||||||
|
while (limit > 0 && _messageQueue.TryTake(out LogMessage message))
|
||||||
|
{
|
||||||
|
_currentBatch.Add(message);
|
||||||
|
limit--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_currentBatch.Count > 0)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await WriteMessagesAsync(_currentBatch, _cancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentBatch.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
await IntervalAsync(_interval, _cancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Task IntervalAsync(TimeSpan interval, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.Delay(interval, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddMessage(DateTimeOffset timestamp, string message)
|
||||||
|
{
|
||||||
|
if (!_messageQueue.IsAddingCompleted)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_messageQueue.Add(new LogMessage { Message = message, Timestamp = timestamp }, _cancellationTokenSource.Token);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
//cancellation token canceled or CompleteAdding called
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Start()
|
||||||
|
{
|
||||||
|
_messageQueue = _queueSize == 0 ?
|
||||||
|
new BlockingCollection<LogMessage>(new ConcurrentQueue<LogMessage>()) :
|
||||||
|
new BlockingCollection<LogMessage>(new ConcurrentQueue<LogMessage>(), _queueSize);
|
||||||
|
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
_outputTask = Task.Factory.StartNew(
|
||||||
|
ProcessLogQueue,
|
||||||
|
null,
|
||||||
|
TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Stop()
|
||||||
|
{
|
||||||
|
_cancellationTokenSource.Cancel();
|
||||||
|
_messageQueue.CompleteAdding();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_outputTask.Wait(_interval);
|
||||||
|
}
|
||||||
|
catch (TaskCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (AggregateException exception) when (exception.InnerExceptions.Count == 1 && exception.InnerExceptions[0] is TaskCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
return new BatchingLogger(this, categoryName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
EonaCat.Logger/EonaCatCoreLogger/Internal/LogMessage.cs
Normal file
13
EonaCat.Logger/EonaCatCoreLogger/Internal/LogMessage.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace EonaCat.Logger.Internal
|
||||||
|
{
|
||||||
|
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||||
|
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
||||||
|
|
||||||
|
public struct LogMessage
|
||||||
|
{
|
||||||
|
public DateTimeOffset Timestamp { get; set; }
|
||||||
|
public string Message { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
21
EonaCat.Logger/EonaCatCoreLogger/Models/EonaCatLogMessage.cs
Normal file
21
EonaCat.Logger/EonaCatCoreLogger/Models/EonaCatLogMessage.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using EonaCat.Logger;
|
||||||
|
using EonaCat.Logger.Helpers;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace EonaCatLogger.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
|
||||||
|
{
|
||||||
|
public DateTime DateTime { get; internal set; }
|
||||||
|
public string Message { get; internal set; }
|
||||||
|
public ELogType LogType { get; internal set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"[{EnumHelper<ELogType>.ToString(LogType)}] [{DateTime.ToString(Constants.DateTimeFormats.LOGGING)}] {Message}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
261
EonaCat.Logger/Extensions/OffsetStream.cs
Normal file
261
EonaCat.Logger/Extensions/OffsetStream.cs
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace EonaCat.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
|
||||||
|
{
|
||||||
|
private const int BUFFERSIZE = 4096;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
long previousPosition = Position1;
|
||||||
|
Position1 = 0;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
CopyTo(stream, bufferSize);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Position1 = previousPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
EonaCat.Logger/Helpers/EnumHelper.cs
Normal file
53
EonaCat.Logger/Helpers/EnumHelper.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
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
|
||||||
|
{
|
||||||
|
static EnumHelper()
|
||||||
|
{
|
||||||
|
string[] names = Enum.GetNames(typeof(T));
|
||||||
|
T[] values = (T[])Enum.GetValues(typeof(T));
|
||||||
|
|
||||||
|
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; private set; }
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
276
EonaCat.Logger/Managers/LogManager.cs
Normal file
276
EonaCat.Logger/Managers/LogManager.cs
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
using EonaCat.Extensions;
|
||||||
|
using EonaCat.Logger.Extensions;
|
||||||
|
using EonaCat.Logger.Helpers;
|
||||||
|
using EonaCatLogger.EonaCatCoreLogger.Models;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
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 : IDisposable
|
||||||
|
{
|
||||||
|
private readonly object _batton = new object();
|
||||||
|
private DateTime _logDate;
|
||||||
|
|
||||||
|
private ILoggerProvider LoggerProvider { get; set; }
|
||||||
|
private ILoggerFactory LoggerFactory { get; set; }
|
||||||
|
private ILogger Logger { get; set; }
|
||||||
|
public string CurrentLogFile => LoggerProvider != null ? ((FileLoggerProvider)LoggerProvider).LogFile : string.Empty;
|
||||||
|
|
||||||
|
public bool IsRunning { get; private set; }
|
||||||
|
|
||||||
|
public StreamWriter Output { get; private set; }
|
||||||
|
public string LogFolderPath { get; set; } = "logs";
|
||||||
|
public FileLoggerOptions FileLoggerOptions { get; private set; }
|
||||||
|
public int FileSizeLimit { get; set; } = 200 * 1024 * 1024;
|
||||||
|
public string CategoryName { get; set; }
|
||||||
|
private LogManager Instance { get; set; }
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
StopLogging();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task DownloadLogAsync(HttpResponse response, string logFile, long limit)
|
||||||
|
{
|
||||||
|
using (FileStream fileStream = new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
|
||||||
|
{
|
||||||
|
response.ContentType = "text/plain";
|
||||||
|
response.Headers.Add("Content-Disposition", "attachment;filename=" + Path.GetFileName(logFile));
|
||||||
|
|
||||||
|
if (limit > fileStream.Length)
|
||||||
|
{
|
||||||
|
limit = fileStream.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (OffsetStream offsetStream = new OffsetStream(fileStream, 0, limit))
|
||||||
|
{
|
||||||
|
using (Stream stream = response.Body)
|
||||||
|
{
|
||||||
|
offsetStream.CopyTo(stream);
|
||||||
|
|
||||||
|
if (fileStream.Length > limit)
|
||||||
|
{
|
||||||
|
byte[] buffer = Encoding.UTF8.GetBytes("####___EonaCatLogger_TRUNCATED___####");
|
||||||
|
await stream.WriteAsync(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartNewLog()
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
|
||||||
|
if (IsRunning && now.Date > _logDate.Date)
|
||||||
|
{
|
||||||
|
StopLogging();
|
||||||
|
}
|
||||||
|
IsRunning = true;
|
||||||
|
|
||||||
|
IServiceCollection serviceCollection = new ServiceCollection();
|
||||||
|
CreateDefaultFileLoggerOptions();
|
||||||
|
|
||||||
|
serviceCollection.AddLogging(builder => builder.AddFile(configuration =>
|
||||||
|
{
|
||||||
|
configuration.BackgroundQueueSize = FileLoggerOptions.BackgroundQueueSize;
|
||||||
|
configuration.BatchSize = FileLoggerOptions.BatchSize;
|
||||||
|
configuration.FileNamePrefix = FileLoggerOptions.FileNamePrefix;
|
||||||
|
configuration.FileSizeLimit = FileLoggerOptions.FileSizeLimit;
|
||||||
|
configuration.FlushPeriod = FileLoggerOptions.FlushPeriod;
|
||||||
|
configuration.IsEnabled = FileLoggerOptions.IsEnabled;
|
||||||
|
configuration.LogDirectory = FileLoggerOptions.LogDirectory;
|
||||||
|
configuration.RetainedFileCountLimit = FileLoggerOptions.RetainedFileCountLimit;
|
||||||
|
}));
|
||||||
|
|
||||||
|
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||||
|
LoggerProvider = serviceProvider.GetService<ILoggerProvider>();
|
||||||
|
LoggerFactory = serviceProvider.GetService<ILoggerFactory>();
|
||||||
|
CategoryName = CategoryName ?? string.Empty;
|
||||||
|
Logger = LoggerFactory.CreateLogger(CategoryName);
|
||||||
|
|
||||||
|
if (!Directory.Exists(FileLoggerOptions.LogDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(FileLoggerOptions.LogDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logDate = now;
|
||||||
|
|
||||||
|
Write(now, "EonaCatLogger started.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateDefaultFileLoggerOptions()
|
||||||
|
{
|
||||||
|
if (FileLoggerOptions == null)
|
||||||
|
{
|
||||||
|
FileLoggerOptions = new FileLoggerOptions
|
||||||
|
{
|
||||||
|
LogDirectory = LogFolderPath,
|
||||||
|
FileSizeLimit = FileSizeLimit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteToFile(EonaCatLogMessage EonaCatLogMessage)
|
||||||
|
{
|
||||||
|
if (EonaCatLogMessage.LogType == ELogType.CRITICAL)
|
||||||
|
{
|
||||||
|
Instance.Logger.LogCritical(EonaCatLogMessage.Message);
|
||||||
|
}
|
||||||
|
else if (EonaCatLogMessage.LogType == ELogType.DEBUG)
|
||||||
|
{
|
||||||
|
Instance.Logger.LogDebug(EonaCatLogMessage.Message);
|
||||||
|
}
|
||||||
|
else if (EonaCatLogMessage.LogType == ELogType.ERROR)
|
||||||
|
{
|
||||||
|
Instance.Logger.LogError(EonaCatLogMessage.Message);
|
||||||
|
}
|
||||||
|
else if (EonaCatLogMessage.LogType == ELogType.INFO)
|
||||||
|
{
|
||||||
|
Instance.Logger.LogInformation(EonaCatLogMessage.Message);
|
||||||
|
}
|
||||||
|
else if (EonaCatLogMessage.LogType == ELogType.TRACE)
|
||||||
|
{
|
||||||
|
Instance.Logger.LogTrace(EonaCatLogMessage.Message);
|
||||||
|
}
|
||||||
|
else if (EonaCatLogMessage.LogType == ELogType.TRAFFIC)
|
||||||
|
{
|
||||||
|
Instance.Logger.LogTrace($"[TRAFFIC] {EonaCatLogMessage.Message}");
|
||||||
|
}
|
||||||
|
else if (EonaCatLogMessage.LogType == ELogType.WARNING)
|
||||||
|
{
|
||||||
|
Instance.Logger.LogWarning(EonaCatLogMessage.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Write(DateTime dateTime, string message, ELogType logType = ELogType.INFO)
|
||||||
|
{
|
||||||
|
var EonaCatMessage = new EonaCatLogMessage { DateTime = dateTime, Message = message, LogType = logType };
|
||||||
|
WriteToFile(EonaCatMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private LogManager()
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogManager(FileLoggerOptions fileLoggerOptions)
|
||||||
|
{
|
||||||
|
FileLoggerOptions = fileLoggerOptions;
|
||||||
|
SetupLogManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupLogManager()
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.ProcessExit += ProcessExit;
|
||||||
|
|
||||||
|
Instance = this;
|
||||||
|
|
||||||
|
_logDate = DateTime.Now;
|
||||||
|
|
||||||
|
StartNewLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessExit(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
Instance?.StopLogging();
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopLogging()
|
||||||
|
{
|
||||||
|
Write(DateTime.Now, "EonaCatLogger stopped.");
|
||||||
|
IsRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LogManager(string logFolder = null, bool defaultPrefix = true)
|
||||||
|
{
|
||||||
|
CreateDefaultFileLoggerOptions();
|
||||||
|
if (logFolder != null)
|
||||||
|
{
|
||||||
|
FileLoggerOptions.LogDirectory = logFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultPrefix)
|
||||||
|
{
|
||||||
|
FileLoggerOptions.FileNamePrefix = "EonaCat";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FileLoggerOptions.FileNamePrefix = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetupLogManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(Exception exception)
|
||||||
|
{
|
||||||
|
Write(exception.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(string message, ELogType logType = ELogType.INFO, ELogType? logLevel = null)
|
||||||
|
{
|
||||||
|
lock (_batton)
|
||||||
|
{
|
||||||
|
if (logLevel == null)
|
||||||
|
{
|
||||||
|
logLevel = DllInfo.LogLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (logType == ELogType.CRITICAL || logLevel.GetValueOrDefault() <= logType)
|
||||||
|
{
|
||||||
|
DateTime now = DateTime.Now;
|
||||||
|
|
||||||
|
if (!IsRunning)
|
||||||
|
{
|
||||||
|
StartNewLog();
|
||||||
|
}
|
||||||
|
|
||||||
|
Write(now, $"{DllInfo.ApplicationName}: {message}", logType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DeleteCurrentLogFile()
|
||||||
|
{
|
||||||
|
lock (this)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(CurrentLogFile))
|
||||||
|
{
|
||||||
|
File.Delete(CurrentLogFile);
|
||||||
|
}
|
||||||
|
StartNewLog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
EonaCat.Logger/icon.ico
Normal file
BIN
EonaCat.Logger/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 248 KiB |
BIN
EonaCat.Logger/icon.png
Normal file
BIN
EonaCat.Logger/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 88 KiB |
Reference in New Issue
Block a user