diff --git a/EonaCat.Logger.sln b/EonaCat.Logger.sln
new file mode 100644
index 0000000..e4aa6d2
--- /dev/null
+++ b/EonaCat.Logger.sln
@@ -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
diff --git a/EonaCat.Logger/Constants.cs b/EonaCat.Logger/Constants.cs
new file mode 100644
index 0000000..e6f01b3
--- /dev/null
+++ b/EonaCat.Logger/Constants.cs
@@ -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";
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/DllInfo.cs b/EonaCat.Logger/DllInfo.cs
new file mode 100644
index 0000000..5c54e2f
--- /dev/null
+++ b/EonaCat.Logger/DllInfo.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/Enums.cs b/EonaCat.Logger/Enums.cs
new file mode 100644
index 0000000..b176f3a
--- /dev/null
+++ b/EonaCat.Logger/Enums.cs
@@ -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
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj
new file mode 100644
index 0000000..7c9e819
--- /dev/null
+++ b/EonaCat.Logger/EonaCat.Logger.csproj
@@ -0,0 +1,64 @@
+
+
+
+
+ netstandard2.0;
+ netstandard2.1;
+ net5.0;
+ net6.0;
+
+ icon.ico
+ 1.0.0
+ EonaCat (Jeroen Saey)
+ true
+ EonaCat (Jeroen Saey)
+ icon.png
+ https://www.nuget.org/packages/EonaCat.Logger/
+ EonaCat.Logger is a logging library created for .NET Standard.
+ Public release version
+ EonaCat (Jeroen Saey)
+ EonaCat, Logger, .NET Standard, EonaCatLogger, Log, Writer Jeroen, Saey
+
+ README.md
+ True
+ LICENSE
+ True
+
+
+
+
+
+ True
+ \
+
+
+ True
+ \
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ \
+
+
+ True
+ \
+
+
+ True
+ \
+
+
+
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Extensions/FileLoggerFactoryExtensions.cs b/EonaCat.Logger/EonaCatCoreLogger/Extensions/FileLoggerFactoryExtensions.cs
new file mode 100644
index 0000000..b6ca567
--- /dev/null
+++ b/EonaCat.Logger/EonaCatCoreLogger/Extensions/FileLoggerFactoryExtensions.cs
@@ -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.
+
+ ///
+ /// Extensions for adding the to the
+ ///
+ public static class FileLoggerFactoryExtensions
+ {
+ ///
+ /// Adds a file logger named 'File' to the factory.
+ ///
+ /// The to use.
+ public static ILoggingBuilder AddFile(this ILoggingBuilder builder)
+ {
+ builder.Services.AddSingleton();
+ return builder;
+ }
+
+ ///
+ /// Adds a file logger named 'File' to the factory.
+ ///
+ /// The to use.
+ /// Sets the filename prefix to use for log files
+ public static ILoggingBuilder AddFile(this ILoggingBuilder builder, string filenamePrefix)
+ {
+ builder.AddFile(options => options.FileNamePrefix = filenamePrefix);
+ return builder;
+ }
+
+ ///
+ /// Adds a file logger named 'File' to the factory.
+ ///
+ /// The to use.
+ /// Configure an instance of the to set logging options
+ public static ILoggingBuilder AddFile(this ILoggingBuilder builder, Action configure)
+ {
+ if (configure == null)
+ {
+ throw new ArgumentNullException(nameof(configure));
+ }
+ builder.AddFile();
+ builder.Services.Configure(configure);
+
+ return builder;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs
new file mode 100644
index 0000000..1321b56
--- /dev/null
+++ b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs
@@ -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.
+
+ ///
+ /// Options for file logging.
+ ///
+ 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;
+
+ ///
+ /// 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 200MB.
+ ///
+ public int FileSizeLimit
+ {
+ get => _fileSizeLimit;
+
+ set
+ {
+ if (value <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FileSizeLimit)} must be positive.");
+ }
+ _fileSizeLimit = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a strictly positive value representing the maximum retained file count or null for no limit.
+ /// Defaults to 50.
+ ///
+ public int RetainedFileCountLimit
+ {
+ get => _retainedFileCountLimit;
+
+ set
+ {
+ if (value <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(RetainedFileCountLimit)} must be positive.");
+ }
+ _retainedFileCountLimit = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a strictly positive value representing the maximum retained file rollovers or null for no limit.
+ /// Defaults to 10.
+ ///
+ public int MaxRolloverFiles
+ {
+ get => _maxRolloverFiles;
+
+ set
+ {
+ if (value <= 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(MaxRolloverFiles)} must be positive.");
+ }
+ _maxRolloverFiles = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the filename prefix to use for log files.
+ /// Defaults to EonaCat_.
+ ///
+ public string FileNamePrefix { get; set; } = "EonaCat";
+
+ ///
+ /// The directory in which log files will be written, relative to the app process.
+ /// Default to executablePath\logs
+ ///
+ ///
+ public string LogDirectory { get; set; } = Path.Combine(DefaultPath, "logs");
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
new file mode 100644
index 0000000..43a54b1
--- /dev/null
+++ b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerProvider.cs
@@ -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.
+
+ ///
+ /// An that writes logs to a file
+ ///
+ [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; }
+
+ ///
+ /// Creates an instance of the
+ ///
+ /// The options object controlling the logger
+ public FileLoggerProvider(IOptions options) : base(options)
+ {
+ FileLoggerOptions loggerOptions = options.Value;
+ _path = loggerOptions.LogDirectory;
+ _fileNamePrefix = loggerOptions.FileNamePrefix;
+ _maxFileSize = loggerOptions.FileSizeLimit;
+ _maxRetainedFiles = loggerOptions.RetainedFileCountLimit;
+ _maxRolloverFiles = loggerOptions.MaxRolloverFiles;
+ }
+
+ ///
+ protected override async Task WriteMessagesAsync(IEnumerable 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);
+ }
+
+ ///
+ /// Deletes old log files, keeping a number of files defined by
+ ///
+ protected void DeleteOldLogFiles()
+ {
+ if (_maxRetainedFiles > 0)
+ {
+ bool hasPrefix = !string.IsNullOrWhiteSpace(_fileNamePrefix);
+ IEnumerable 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();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
new file mode 100644
index 0000000..72f8238
--- /dev/null
+++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs
@@ -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 state)
+ {
+ return null;
+ }
+
+ public bool IsEnabled(LogLevel logLevel)
+ {
+ if (logLevel == LogLevel.None)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public void Log(DateTimeOffset timestamp, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func 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(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter)
+ {
+ Log(DateTimeOffset.Now, logLevel, eventId, state, exception, formatter);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerOptions.cs
new file mode 100644
index 0000000..cb320aa
--- /dev/null
+++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerOptions.cs
@@ -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);
+
+ ///
+ /// Gets or sets the period after which logs will be flushed to the store.
+ ///
+ public TimeSpan FlushPeriod
+ {
+ get => _flushPeriod;
+
+ set
+ {
+ if (value <= TimeSpan.Zero)
+ {
+ throw new ArgumentOutOfRangeException(nameof(value), $"{nameof(FlushPeriod)} must be positive.");
+ }
+ _flushPeriod = value;
+ }
+ }
+
+ ///
+ /// 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 0.
+ ///
+ public int BackgroundQueueSize
+ {
+ get => _backgroundQueueSize;
+
+ set
+ {
+ _backgroundQueueSize = value;
+ }
+ }
+
+ ///
+ /// Gets or sets a maximum number of events to include in a single batch or less than 1 for no limit.
+ ///
+ public int BatchSize
+ {
+ get => _batchSize;
+
+ set
+ {
+ _batchSize = value;
+ }
+ }
+
+ ///
+ /// Gets or sets value indicating if logger accepts and queues writes.
+ ///
+ public bool IsEnabled { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs
new file mode 100644
index 0000000..6eb1561
--- /dev/null
+++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs
@@ -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 _currentBatch = new List();
+ private readonly TimeSpan _interval;
+ private readonly int _queueSize;
+ private readonly int _batchSize;
+
+ private BlockingCollection _messageQueue;
+ private Task _outputTask;
+ private CancellationTokenSource _cancellationTokenSource;
+
+ protected BatchingLoggerProvider(IOptions 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 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(new ConcurrentQueue()) :
+ new BlockingCollection(new ConcurrentQueue(), _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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/LogMessage.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/LogMessage.cs
new file mode 100644
index 0000000..378dd23
--- /dev/null
+++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/LogMessage.cs
@@ -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; }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/EonaCatCoreLogger/Models/EonaCatLogMessage.cs b/EonaCat.Logger/EonaCatCoreLogger/Models/EonaCatLogMessage.cs
new file mode 100644
index 0000000..216c21a
--- /dev/null
+++ b/EonaCat.Logger/EonaCatCoreLogger/Models/EonaCatLogMessage.cs
@@ -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.ToString(LogType)}] [{DateTime.ToString(Constants.DateTimeFormats.LOGGING)}] {Message}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/Extensions/OffsetStream.cs b/EonaCat.Logger/Extensions/OffsetStream.cs
new file mode 100644
index 0000000..791e285
--- /dev/null
+++ b/EonaCat.Logger/Extensions/OffsetStream.cs
@@ -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;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/Helpers/EnumHelper.cs b/EonaCat.Logger/Helpers/EnumHelper.cs
new file mode 100644
index 0000000..38ad567
--- /dev/null
+++ b/EonaCat.Logger/Helpers/EnumHelper.cs
@@ -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
+ where T : struct
+ {
+ static EnumHelper()
+ {
+ string[] names = Enum.GetNames(typeof(T));
+ T[] values = (T[])Enum.GetValues(typeof(T));
+
+ Names = new Dictionary(names.Length);
+ Values = new Dictionary(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 Names { get; }
+
+ public static Dictionary 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;
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/Managers/LogManager.cs b/EonaCat.Logger/Managers/LogManager.cs
new file mode 100644
index 0000000..caf95af
--- /dev/null
+++ b/EonaCat.Logger/Managers/LogManager.cs
@@ -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();
+ LoggerFactory = serviceProvider.GetService();
+ 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();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/EonaCat.Logger/icon.ico b/EonaCat.Logger/icon.ico
new file mode 100644
index 0000000..406f265
Binary files /dev/null and b/EonaCat.Logger/icon.ico differ
diff --git a/EonaCat.Logger/icon.png b/EonaCat.Logger/icon.png
new file mode 100644
index 0000000..0595b89
Binary files /dev/null and b/EonaCat.Logger/icon.png differ
diff --git a/README.md b/README.md
index abce9e7..4422e84 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,5 @@
# EonaCat.Logger
-EonaCat Logger
\ No newline at end of file
+EonaCat Logger
+
+Log application information to log files
\ No newline at end of file