From 715205760dd3f78b9f7056f92f1d102cc6f736c6 Mon Sep 17 00:00:00 2001 From: Pultak <pultak5@gmail.com> Date: Sat, 2 Apr 2022 15:07:38 +0200 Subject: [PATCH 1/3] re #9439 Implementation of console and rotating file logger --- ld_client/LDClient/Program.cs | 12 +- ld_client/LDClient/utils/Logger.cs | 225 +++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 ld_client/LDClient/utils/Logger.cs diff --git a/ld_client/LDClient/Program.cs b/ld_client/LDClient/Program.cs index 22069e5..2d372d9 100644 --- a/ld_client/LDClient/Program.cs +++ b/ld_client/LDClient/Program.cs @@ -1,11 +1,19 @@ -using System; +using LDClient; +using System; class Program { + public static LDClient.ConfigLoader Config { get; set; } + // Main Method static public void Main() { + Config = new LDClient.ConfigLoader(); - Console.WriteLine("Main Method"); + while (true) { + Logger.Current.Info("Ok"); + Logger.Current.Debug("Debug"); + Logger.Current.Error("Error"); + } } } \ No newline at end of file diff --git a/ld_client/LDClient/utils/Logger.cs b/ld_client/LDClient/utils/Logger.cs new file mode 100644 index 0000000..aaf0ea1 --- /dev/null +++ b/ld_client/LDClient/utils/Logger.cs @@ -0,0 +1,225 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LDClient { + enum LogVerbosity { + None = 0, + Exceptions, + Full + } + + public enum LogType { + Info = 0, + Debug, + Error + } + + enum LogFlow { + Console = 0, + File + } + + + public abstract class Logger : IDisposable { + + private LogVerbosity _verbosity; + private LogFlow _logFlow; + + private Queue<Action> _queue = new Queue<Action>(); + private ManualResetEvent _hasNewItems = new ManualResetEvent(false); + private ManualResetEvent _terminate = new ManualResetEvent(false); + private ManualResetEvent _waiting = new ManualResetEvent(false); + private Thread _loggingThread; + + private static readonly Lazy<Logger> _lazyLog = new Lazy<Logger>(() + => { + switch (Program.Config.LogFlowType) { + case LogFlow.File: + return new FileLogger(); + case LogFlow.Console: + default: + return new ConsoleLogger(); + + } + } + ); + + public static Logger Current => _lazyLog.Value; + + protected Logger() { + _verbosity = (LogVerbosity)Program.Config.LogVerbosityType; + _logFlow = (LogFlow)Program.Config.LogFlowType; + _loggingThread = new Thread(new ThreadStart(ProcessQueue)) { IsBackground = true }; + _loggingThread.Start(); + } + + public void Info(string message) { + Log(message, LogType.Info); + } + + public void Debug(string message) { + Log(message, LogType.Debug); + } + + public void Error(string message) { + Log(message, LogType.Error); + } + + public void Error(Exception e) { + if (_verbosity != LogVerbosity.None) { + Log(UnwrapExceptionMessages(e), LogType.Error); + } + } + + public override string ToString() => $"Logger settings: [Type: {this.GetType().Name}, Verbosity: {_verbosity}, "; + + protected abstract void CreateLog(string message); + + public void Flush() => _waiting.WaitOne(); + + public void Dispose() { + _terminate.Set(); + _loggingThread.Join(); + } + + protected virtual string ComposeLogRow(string message, LogType logType) => + $"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff", CultureInfo.InvariantCulture)} - {logType}] - {message}"; + + protected virtual string UnwrapExceptionMessages(Exception ex) { + if (ex == null) + return string.Empty; + + return $"{ex}, Inner exception: {UnwrapExceptionMessages(ex.InnerException)} "; + } + + private void ProcessQueue() { + while (true) { + _waiting.Set(); + int i = WaitHandle.WaitAny(new WaitHandle[] { _hasNewItems, _terminate }); + if (i == 1) return; + _hasNewItems.Reset(); + _waiting.Reset(); + + Queue<Action> queueCopy; + lock (_queue) { + queueCopy = new Queue<Action>(_queue); + _queue.Clear(); + } + + foreach (var log in queueCopy) { + log(); + } + } + } + + private void Log(string message, LogType logType) { + if (string.IsNullOrEmpty(message)) + return; + + var logRow = ComposeLogRow(message, logType); + System.Diagnostics.Debug.WriteLine(logRow); + + if (_verbosity == LogVerbosity.Full) { + lock (_queue) + _queue.Enqueue(() => CreateLog(logRow)); + + _hasNewItems.Set(); + } + } + } + + class ConsoleLogger : Logger { + protected override void CreateLog(string message) { + Console.WriteLine(message); + } + } + + + class FileLogger : Logger { + + private const string LogFolderName = "logs"; + private const string LogFileName = "app_info.log"; + private readonly int _logChunkSize = Program.Config.LogChunkSize; + private readonly int _logChunkMaxCount = Program.Config.LogChunkMaxCount; + private readonly int _logArchiveMaxCount = Program.Config.LogArchiveMaxCount; + private readonly int _logCleanupPeriod = Program.Config.LogCleanupPeriod; + + private readonly string logFolderPath = Path.Combine(Path.GetTempPath(), $"ldClient\\{LogFolderName}"); + + private bool _logDirExists = false; + + protected override void CreateLog(string message) { + + if (!_logDirExists) { + _logDirExists = Directory.Exists(logFolderPath); + if (!_logDirExists) { + Directory.CreateDirectory(logFolderPath); + _logDirExists = true; + } + } + + var logFilePath = Path.Combine(logFolderPath, LogFileName); + + Rotate(logFilePath); + + using (var sw = File.AppendText(logFilePath)) { + sw.WriteLine(message); + } + } + + private void Rotate(string filePath) { + if (!File.Exists(filePath)) + return; + + var fileInfo = new FileInfo(filePath); + if (fileInfo.Length < _logChunkSize) + return; + + var fileTime = DateTime.Now.ToString("dd-MM-yyyy,hh-mm-ss,fff"); + var rotatedPath = filePath.Replace(".log", $".{fileTime}"); + File.Move(filePath, rotatedPath); + + var folderPath = Path.GetDirectoryName(rotatedPath); + var logFolderContent = new DirectoryInfo(folderPath).GetFileSystemInfos(); + + var chunks = logFolderContent.Where(x => !x.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase)); + + if (chunks.Count() <= _logChunkMaxCount) + return; + + var archiveFolderInfo = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(rotatedPath), $"{LogFolderName}_{fileTime}")); + + foreach (var chunk in chunks) { + var destination = Path.Combine(archiveFolderInfo.FullName, chunk.Name); + Directory.Move(chunk.FullName, destination); + } + + ZipFile.CreateFromDirectory(archiveFolderInfo.FullName, Path.Combine(folderPath, $"{LogFolderName}_{fileTime}.zip")); + Directory.Delete(archiveFolderInfo.FullName, true); + + var archives = logFolderContent.Where(x => x.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase)).ToArray(); + + if (archives.Count() <= _logArchiveMaxCount) + return; + + var oldestArchive = archives.OrderBy(x => x.CreationTime).First(); + var cleanupDate = oldestArchive.CreationTime.AddDays(_logCleanupPeriod); + if (DateTime.Compare(cleanupDate, DateTime.Now) <= 0) { + foreach (var file in logFolderContent) { + file.Delete(); + } + } else + File.Delete(oldestArchive.FullName); + + } + + public override string ToString() => $"{base.ToString()}, Chunk Size: {_logChunkSize}, Max chunk count: {_logChunkMaxCount}, Max log archive count: {_logArchiveMaxCount}, Cleanup period: {_logCleanupPeriod} days]"; + } + + +} -- GitLab From d0cf94768067a25abd98e78c3775d418c154774e Mon Sep 17 00:00:00 2001 From: Pultak <pultak5@gmail.com> Date: Sat, 2 Apr 2022 01:27:27 +0200 Subject: [PATCH 2/3] re #9440 initial config manager definition --- ld_client/LDClient/LDClient.csproj | 13 ++++- ld_client/LDClient/appsettings.json | 17 ++++++ ld_client/LDClient/utils/ConfigLoader.cs | 72 ++++++++++++++++++++++++ ld_client/LauterbachDebuggerClient.sln | 2 +- 4 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 ld_client/LDClient/appsettings.json create mode 100644 ld_client/LDClient/utils/ConfigLoader.cs diff --git a/ld_client/LDClient/LDClient.csproj b/ld_client/LDClient/LDClient.csproj index 9b3c77f..3db4860 100644 --- a/ld_client/LDClient/LDClient.csproj +++ b/ld_client/LDClient/LDClient.csproj @@ -7,6 +7,16 @@ <Nullable>enable</Nullable> </PropertyGroup> + <ItemGroup> + <None Remove="appsettings.json" /> + </ItemGroup> + + <ItemGroup> + <EmbeddedResource Include="appsettings.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </EmbeddedResource> + </ItemGroup> + <ItemGroup> <Reference Include="t32apinet"> <HintPath>..\dotnet\t32apinet\bin\Release\t32apinet.dll</HintPath> @@ -14,7 +24,8 @@ </ItemGroup> <ItemGroup> - <Folder Include="NewFolder\" /> + <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="6.0.0" /> + <PackageReference Include="System.Configuration.ConfigurationManager" Version="6.0.0" /> </ItemGroup> </Project> diff --git a/ld_client/LDClient/appsettings.json b/ld_client/LDClient/appsettings.json new file mode 100644 index 0000000..505db61 --- /dev/null +++ b/ld_client/LDClient/appsettings.json @@ -0,0 +1,17 @@ +{ + "Logging": { + "ColorConsole": { + "LogLevels": { + "Information": "DarkGreen", + "Warning": "Cyan", + "Error": "Red" + } + }, + "LogChunkSize": 1000, + "LogChunkMaxCount": 1, + "LogArchiveMaxCount": 1, + "LogCleanupPeriod": 1000, + "LogVerbosityType": 2, + "LogFlowType": 1 + } +} \ No newline at end of file diff --git a/ld_client/LDClient/utils/ConfigLoader.cs b/ld_client/LDClient/utils/ConfigLoader.cs new file mode 100644 index 0000000..d6502ac --- /dev/null +++ b/ld_client/LDClient/utils/ConfigLoader.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration.Json; + +namespace LDClient { + internal class ConfigurationLoader { + + private readonly string LOGGING_SECTION = "Logging"; + + + public int LogChunkSize { get; set; } + public int LogChunkMaxCount { get; set; } + public int LogArchiveMaxCount { get; set; } + + public int LogCleanupPeriod { get; set; } + + private LogVerbosity _logVerbosity = LogVerbosity.Full; + public LogVerbosity LogVerbosityType { + get { + return _logVerbosity; + } + set + { _logVerbosity = value; + } + } + + private LogFlow _logFlowType = LogFlow.Console; + public LogFlow LogFlowType { + get { + return _logFlowType; + } + set { + _logFlowType = value; + } + } + + public ConfigurationLoader() { + var configuration = new ConfigurationBuilder() + .AddJsonFile("appsettings.json") + .Build(); + ReadAllSettings(configuration); + } + + void ReadAllSettings(IConfigurationRoot configuration) { + + try { + var logging = configuration.GetSection(LOGGING_SECTION); + //TODO: Exception handling + LogChunkSize = Int32.Parse(logging["LogChunkSize"]); + LogChunkMaxCount = Int32.Parse(logging["LogChunkMaxCount"]); + LogArchiveMaxCount = Int32.Parse(logging["LogArchiveMaxCount"]); + LogCleanupPeriod = Int32.Parse(logging["LogCleanupPeriod"]); + LogFlowType = (LogFlow)Int32.Parse(logging["LogFlowType"]); + LogVerbosityType = (LogVerbosity)Int32.Parse(logging["LogVerbosityType"]); + + + Console.WriteLine("Configuration successfully loaded!"); + } catch (FormatException e) { + //Console.WriteLine("Error reading app settings"); + //TODO: remove stacktrace print in production + Console.WriteLine("Error during reading of configuration occured!" + e); + throw new IOException("Reading of configuration file failed! " + e); + } + } + + } +} diff --git a/ld_client/LauterbachDebuggerClient.sln b/ld_client/LauterbachDebuggerClient.sln index d62277c..6e2f143 100644 --- a/ld_client/LauterbachDebuggerClient.sln +++ b/ld_client/LauterbachDebuggerClient.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 17.1.32210.238 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LDClient", "LDClient\LDClient.csproj", "{26616C14-A61B-4611-8CD0-D29837FA34E7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LDClientTests", "LDClientTests\LDClientTests.csproj", "{5A1DB888-70E5-41E8-A900-FC22B51727F8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LDClientTests", "LDClientTests\LDClientTests.csproj", "{5A1DB888-70E5-41E8-A900-FC22B51727F8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution -- GitLab From 74bd1e40820cf9bf2c808f4946a0dec8f2013d0c Mon Sep 17 00:00:00 2001 From: Pultak <pultak5@gmail.com> Date: Sat, 2 Apr 2022 18:19:27 +0200 Subject: [PATCH 3/3] re #9439 logger code refactoring --- ld_client/LDClient/Program.cs | 19 +- ld_client/LDClient/appsettings.json | 8 +- ld_client/LDClient/utils/ConfigLoader.cs | 55 ++--- ld_client/LDClient/utils/Logger.cs | 225 ------------------ ld_client/LDClient/utils/loggers/ALogger.cs | 109 +++++++++ .../LDClient/utils/loggers/ConsoleLogger.cs | 7 + .../LDClient/utils/loggers/FileLogger.cs | 95 ++++++++ ld_client/LDClient/utils/loggers/LogFlow.cs | 6 + ld_client/LDClient/utils/loggers/LogType.cs | 7 + .../LDClient/utils/loggers/LogVerbosity.cs | 7 + 10 files changed, 261 insertions(+), 277 deletions(-) delete mode 100644 ld_client/LDClient/utils/Logger.cs create mode 100644 ld_client/LDClient/utils/loggers/ALogger.cs create mode 100644 ld_client/LDClient/utils/loggers/ConsoleLogger.cs create mode 100644 ld_client/LDClient/utils/loggers/FileLogger.cs create mode 100644 ld_client/LDClient/utils/loggers/LogFlow.cs create mode 100644 ld_client/LDClient/utils/loggers/LogType.cs create mode 100644 ld_client/LDClient/utils/loggers/LogVerbosity.cs diff --git a/ld_client/LDClient/Program.cs b/ld_client/LDClient/Program.cs index 2d372d9..4030157 100644 --- a/ld_client/LDClient/Program.cs +++ b/ld_client/LDClient/Program.cs @@ -1,19 +1,20 @@ -using LDClient; -using System; +using LDClient.utils; +using LDClient.utils.loggers; +namespace LDClient; -class Program { +internal class Program { - public static LDClient.ConfigLoader Config { get; set; } + public static ConfigLoader Config { get; set; } = null!; // Main Method - static public void Main() { - Config = new LDClient.ConfigLoader(); + public static void Main() { + Config = new ConfigLoader(); while (true) { - Logger.Current.Info("Ok"); - Logger.Current.Debug("Debug"); - Logger.Current.Error("Error"); + ALogger.Current.Info("Ok"); + ALogger.Current.Debug("Debug"); + ALogger.Current.Error("Error"); } } } \ No newline at end of file diff --git a/ld_client/LDClient/appsettings.json b/ld_client/LDClient/appsettings.json index 505db61..01b0de4 100644 --- a/ld_client/LDClient/appsettings.json +++ b/ld_client/LDClient/appsettings.json @@ -7,10 +7,10 @@ "Error": "Red" } }, - "LogChunkSize": 1000, - "LogChunkMaxCount": 1, - "LogArchiveMaxCount": 1, - "LogCleanupPeriod": 1000, + "LogChunkSize": 2097152, + "LogChunkMaxCount": 2, + "LogArchiveMaxCount": 10, + "LogCleanupPeriod": 10, "LogVerbosityType": 2, "LogFlowType": 1 } diff --git a/ld_client/LDClient/utils/ConfigLoader.cs b/ld_client/LDClient/utils/ConfigLoader.cs index d6502ac..8005130 100644 --- a/ld_client/LDClient/utils/ConfigLoader.cs +++ b/ld_client/LDClient/utils/ConfigLoader.cs @@ -1,16 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using LDClient.utils.loggers; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Configuration.Json; -namespace LDClient { - internal class ConfigurationLoader { - - private readonly string LOGGING_SECTION = "Logging"; +namespace LDClient.utils { + internal class ConfigLoader { + private const string LoggingSection = "Logging"; public int LogChunkSize { get; set; } @@ -19,51 +12,35 @@ namespace LDClient { public int LogCleanupPeriod { get; set; } - private LogVerbosity _logVerbosity = LogVerbosity.Full; - public LogVerbosity LogVerbosityType { - get { - return _logVerbosity; - } - set - { _logVerbosity = value; - } - } + public LogVerbosity LogVerbosityType { get; set; } = LogVerbosity.Full; - private LogFlow _logFlowType = LogFlow.Console; - public LogFlow LogFlowType { - get { - return _logFlowType; - } - set { - _logFlowType = value; - } - } + public LogFlow LogFlowType { get; set; } = LogFlow.Console; - public ConfigurationLoader() { + public ConfigLoader() { var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); ReadAllSettings(configuration); } - void ReadAllSettings(IConfigurationRoot configuration) { + private void ReadAllSettings(IConfigurationRoot configuration) { try { - var logging = configuration.GetSection(LOGGING_SECTION); + var logging = configuration.GetSection(LoggingSection); //TODO: Exception handling - LogChunkSize = Int32.Parse(logging["LogChunkSize"]); - LogChunkMaxCount = Int32.Parse(logging["LogChunkMaxCount"]); - LogArchiveMaxCount = Int32.Parse(logging["LogArchiveMaxCount"]); - LogCleanupPeriod = Int32.Parse(logging["LogCleanupPeriod"]); - LogFlowType = (LogFlow)Int32.Parse(logging["LogFlowType"]); - LogVerbosityType = (LogVerbosity)Int32.Parse(logging["LogVerbosityType"]); + LogChunkSize = int.Parse(logging["LogChunkSize"]); + LogChunkMaxCount = int.Parse(logging["LogChunkMaxCount"]); + LogArchiveMaxCount = int.Parse(logging["LogArchiveMaxCount"]); + LogCleanupPeriod = int.Parse(logging["LogCleanupPeriod"]); + LogFlowType = (LogFlow)int.Parse(logging["LogFlowType"]); + LogVerbosityType = (LogVerbosity)int.Parse(logging["LogVerbosityType"]); Console.WriteLine("Configuration successfully loaded!"); } catch (FormatException e) { //Console.WriteLine("Error reading app settings"); //TODO: remove stacktrace print in production - Console.WriteLine("Error during reading of configuration occured!" + e); + Console.WriteLine("Error during reading of configuration occurred!" + e); throw new IOException("Reading of configuration file failed! " + e); } } diff --git a/ld_client/LDClient/utils/Logger.cs b/ld_client/LDClient/utils/Logger.cs deleted file mode 100644 index aaf0ea1..0000000 --- a/ld_client/LDClient/utils/Logger.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO.Compression; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace LDClient { - enum LogVerbosity { - None = 0, - Exceptions, - Full - } - - public enum LogType { - Info = 0, - Debug, - Error - } - - enum LogFlow { - Console = 0, - File - } - - - public abstract class Logger : IDisposable { - - private LogVerbosity _verbosity; - private LogFlow _logFlow; - - private Queue<Action> _queue = new Queue<Action>(); - private ManualResetEvent _hasNewItems = new ManualResetEvent(false); - private ManualResetEvent _terminate = new ManualResetEvent(false); - private ManualResetEvent _waiting = new ManualResetEvent(false); - private Thread _loggingThread; - - private static readonly Lazy<Logger> _lazyLog = new Lazy<Logger>(() - => { - switch (Program.Config.LogFlowType) { - case LogFlow.File: - return new FileLogger(); - case LogFlow.Console: - default: - return new ConsoleLogger(); - - } - } - ); - - public static Logger Current => _lazyLog.Value; - - protected Logger() { - _verbosity = (LogVerbosity)Program.Config.LogVerbosityType; - _logFlow = (LogFlow)Program.Config.LogFlowType; - _loggingThread = new Thread(new ThreadStart(ProcessQueue)) { IsBackground = true }; - _loggingThread.Start(); - } - - public void Info(string message) { - Log(message, LogType.Info); - } - - public void Debug(string message) { - Log(message, LogType.Debug); - } - - public void Error(string message) { - Log(message, LogType.Error); - } - - public void Error(Exception e) { - if (_verbosity != LogVerbosity.None) { - Log(UnwrapExceptionMessages(e), LogType.Error); - } - } - - public override string ToString() => $"Logger settings: [Type: {this.GetType().Name}, Verbosity: {_verbosity}, "; - - protected abstract void CreateLog(string message); - - public void Flush() => _waiting.WaitOne(); - - public void Dispose() { - _terminate.Set(); - _loggingThread.Join(); - } - - protected virtual string ComposeLogRow(string message, LogType logType) => - $"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff", CultureInfo.InvariantCulture)} - {logType}] - {message}"; - - protected virtual string UnwrapExceptionMessages(Exception ex) { - if (ex == null) - return string.Empty; - - return $"{ex}, Inner exception: {UnwrapExceptionMessages(ex.InnerException)} "; - } - - private void ProcessQueue() { - while (true) { - _waiting.Set(); - int i = WaitHandle.WaitAny(new WaitHandle[] { _hasNewItems, _terminate }); - if (i == 1) return; - _hasNewItems.Reset(); - _waiting.Reset(); - - Queue<Action> queueCopy; - lock (_queue) { - queueCopy = new Queue<Action>(_queue); - _queue.Clear(); - } - - foreach (var log in queueCopy) { - log(); - } - } - } - - private void Log(string message, LogType logType) { - if (string.IsNullOrEmpty(message)) - return; - - var logRow = ComposeLogRow(message, logType); - System.Diagnostics.Debug.WriteLine(logRow); - - if (_verbosity == LogVerbosity.Full) { - lock (_queue) - _queue.Enqueue(() => CreateLog(logRow)); - - _hasNewItems.Set(); - } - } - } - - class ConsoleLogger : Logger { - protected override void CreateLog(string message) { - Console.WriteLine(message); - } - } - - - class FileLogger : Logger { - - private const string LogFolderName = "logs"; - private const string LogFileName = "app_info.log"; - private readonly int _logChunkSize = Program.Config.LogChunkSize; - private readonly int _logChunkMaxCount = Program.Config.LogChunkMaxCount; - private readonly int _logArchiveMaxCount = Program.Config.LogArchiveMaxCount; - private readonly int _logCleanupPeriod = Program.Config.LogCleanupPeriod; - - private readonly string logFolderPath = Path.Combine(Path.GetTempPath(), $"ldClient\\{LogFolderName}"); - - private bool _logDirExists = false; - - protected override void CreateLog(string message) { - - if (!_logDirExists) { - _logDirExists = Directory.Exists(logFolderPath); - if (!_logDirExists) { - Directory.CreateDirectory(logFolderPath); - _logDirExists = true; - } - } - - var logFilePath = Path.Combine(logFolderPath, LogFileName); - - Rotate(logFilePath); - - using (var sw = File.AppendText(logFilePath)) { - sw.WriteLine(message); - } - } - - private void Rotate(string filePath) { - if (!File.Exists(filePath)) - return; - - var fileInfo = new FileInfo(filePath); - if (fileInfo.Length < _logChunkSize) - return; - - var fileTime = DateTime.Now.ToString("dd-MM-yyyy,hh-mm-ss,fff"); - var rotatedPath = filePath.Replace(".log", $".{fileTime}"); - File.Move(filePath, rotatedPath); - - var folderPath = Path.GetDirectoryName(rotatedPath); - var logFolderContent = new DirectoryInfo(folderPath).GetFileSystemInfos(); - - var chunks = logFolderContent.Where(x => !x.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase)); - - if (chunks.Count() <= _logChunkMaxCount) - return; - - var archiveFolderInfo = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(rotatedPath), $"{LogFolderName}_{fileTime}")); - - foreach (var chunk in chunks) { - var destination = Path.Combine(archiveFolderInfo.FullName, chunk.Name); - Directory.Move(chunk.FullName, destination); - } - - ZipFile.CreateFromDirectory(archiveFolderInfo.FullName, Path.Combine(folderPath, $"{LogFolderName}_{fileTime}.zip")); - Directory.Delete(archiveFolderInfo.FullName, true); - - var archives = logFolderContent.Where(x => x.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase)).ToArray(); - - if (archives.Count() <= _logArchiveMaxCount) - return; - - var oldestArchive = archives.OrderBy(x => x.CreationTime).First(); - var cleanupDate = oldestArchive.CreationTime.AddDays(_logCleanupPeriod); - if (DateTime.Compare(cleanupDate, DateTime.Now) <= 0) { - foreach (var file in logFolderContent) { - file.Delete(); - } - } else - File.Delete(oldestArchive.FullName); - - } - - public override string ToString() => $"{base.ToString()}, Chunk Size: {_logChunkSize}, Max chunk count: {_logChunkMaxCount}, Max log archive count: {_logArchiveMaxCount}, Cleanup period: {_logCleanupPeriod} days]"; - } - - -} diff --git a/ld_client/LDClient/utils/loggers/ALogger.cs b/ld_client/LDClient/utils/loggers/ALogger.cs new file mode 100644 index 0000000..260a03d --- /dev/null +++ b/ld_client/LDClient/utils/loggers/ALogger.cs @@ -0,0 +1,109 @@ +using System.Globalization; + +namespace LDClient.utils.loggers { + + public abstract class ALogger : IDisposable { + + private readonly LogVerbosity _verbosity; + private readonly LogFlow _logFlow; + + private readonly Queue<Action> _queue = new(); + private readonly ManualResetEvent _hasNewItems = new(false); + private readonly ManualResetEvent _terminate = new(false); + private readonly ManualResetEvent _waiting = new(false); + private readonly Thread _loggingThread; + + private static readonly Lazy<ALogger> LazyLog = new(() + => { + switch (Program.Config.LogFlowType) { + case LogFlow.File: + return new FileLogger(); + case LogFlow.Console: + default: + return new ConsoleLogger(); + + } + } + ); + + public static ALogger Current => LazyLog.Value; + + protected ALogger() { + _verbosity = Program.Config.LogVerbosityType; + _logFlow = Program.Config.LogFlowType; + _loggingThread = new Thread(ProcessQueue) { IsBackground = true }; + _loggingThread.Start(); + } + + public void Info(string message) { + Log(message, LogType.Info); + } + + public void Debug(string message) { + Log(message, LogType.Debug); + } + + public void Error(string message) { + Log(message, LogType.Error); + } + + public void Error(Exception e) { + if (_verbosity != LogVerbosity.None) { + Log(UnwrapExceptionMessages(e), LogType.Error); + } + } + + public override string ToString() => $"Logger settings: [Type: {this.GetType().Name}, Verbosity: {_verbosity}, "; + + protected abstract void CreateLog(string message); + + public void Flush() => _waiting.WaitOne(); + + public void Dispose() { + _terminate.Set(); + _loggingThread.Join(); + } + + protected virtual string ComposeLogRow(string message, LogType logType) => + $"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff", CultureInfo.InvariantCulture)} - {logType}] - {message}"; + + protected virtual string UnwrapExceptionMessages(Exception? ex) => + ex == null ? string.Empty : $"{ex}, Inner exception: {UnwrapExceptionMessages(ex.InnerException)} "; + + + private void ProcessQueue() { + while (true) { + _waiting.Set(); + var i = WaitHandle.WaitAny(new WaitHandle[] { _hasNewItems, _terminate }); + if (i == 1) return; + _hasNewItems.Reset(); + _waiting.Reset(); + + Queue<Action> queueCopy; + lock (_queue) { + queueCopy = new Queue<Action>(_queue); + _queue.Clear(); + } + + foreach (var log in queueCopy) { + log(); + } + } + } + + private void Log(string message, LogType logType) { + if (string.IsNullOrEmpty(message)) + return; + + var logRow = ComposeLogRow(message, logType); + System.Diagnostics.Debug.WriteLine(logRow); + + if (_verbosity == LogVerbosity.Full) { + lock (_queue) + _queue.Enqueue(() => CreateLog(logRow)); + + _hasNewItems.Set(); + } + } + } +} diff --git a/ld_client/LDClient/utils/loggers/ConsoleLogger.cs b/ld_client/LDClient/utils/loggers/ConsoleLogger.cs new file mode 100644 index 0000000..b108ed6 --- /dev/null +++ b/ld_client/LDClient/utils/loggers/ConsoleLogger.cs @@ -0,0 +1,7 @@ +namespace LDClient.utils.loggers { + public class ConsoleLogger : ALogger { + protected override void CreateLog(string message) { + Console.WriteLine(message); + } + } +} diff --git a/ld_client/LDClient/utils/loggers/FileLogger.cs b/ld_client/LDClient/utils/loggers/FileLogger.cs new file mode 100644 index 0000000..d63ab05 --- /dev/null +++ b/ld_client/LDClient/utils/loggers/FileLogger.cs @@ -0,0 +1,95 @@ +using System.IO.Compression; + +namespace LDClient.utils.loggers; + +public class FileLogger : ALogger { + + private const string LogFolderName = "logs"; + private const string LogFileName = "app_info.log"; + private readonly int _logChunkSize = Program.Config.LogChunkSize; + private readonly int _logChunkMaxCount = Program.Config.LogChunkMaxCount; + private readonly int _logArchiveMaxCount = Program.Config.LogArchiveMaxCount; + private readonly int _logCleanupPeriod = Program.Config.LogCleanupPeriod; + + private const string LogFolderPath = $"ldClient\\{LogFolderName}"; + + private bool _logDirExists; + + protected override void CreateLog(string message) { + + if (!_logDirExists) { + _logDirExists = Directory.Exists(LogFolderPath); + if (!_logDirExists) { + Directory.CreateDirectory(LogFolderPath); + _logDirExists = true; + } + } + + var logFilePath = Path.Combine(LogFolderPath, LogFileName); + + Rotate(logFilePath); + + using var sw = File.AppendText(logFilePath); + sw.WriteLine(message); + } + + private void Rotate(string filePath) { + if (!File.Exists(filePath)) { + return; + } + + var fileInfo = new FileInfo(filePath); + if (fileInfo.Length < _logChunkSize) { + return; + } + var fileTime = DateTime.Now.ToString("dd-MM-yyyy,hh-mm-ss,fff"); + var rotatedPath = filePath.Replace(".log", $".{fileTime}"); + File.Move(filePath, rotatedPath); + + var folderPath = Path.GetDirectoryName(rotatedPath); + var logFolderContent = new DirectoryInfo(folderPath ?? string.Empty).GetFileSystemInfos(); + + var chunks = logFolderContent.Where(x => + !x.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase)); + + if (chunks.Count() <= _logChunkMaxCount) { + return; + } + + Archive(chunks, rotatedPath, fileTime, folderPath); + DeleteOldArchives(logFolderContent); + } + + private void Archive(IEnumerable<FileSystemInfo> chunks, string rotatedPath, string fileTime, string? folderPath) { + + var archiveFolderInfo = Directory.CreateDirectory(Path.Combine(Path.GetDirectoryName(rotatedPath) ?? LogFolderPath, $"{LogFolderName}_{fileTime}")); + + foreach (var chunk in chunks) { + var destination = Path.Combine(archiveFolderInfo.FullName, chunk.Name); + Directory.Move(chunk.FullName, destination); + } + + ZipFile.CreateFromDirectory(archiveFolderInfo.FullName, Path.Combine(folderPath ?? LogFolderPath, $"{LogFolderName}_{fileTime}.zip")); + Directory.Delete(archiveFolderInfo.FullName, true); + } + + private void DeleteOldArchives(FileSystemInfo[] logFolderContent) { + + var archives = logFolderContent.Where(x => x.Extension.Equals(".zip", StringComparison.OrdinalIgnoreCase)).ToArray(); + + if (archives.Length <= _logArchiveMaxCount) + return; + + var oldestArchive = archives.OrderBy(x => x.CreationTime).First(); + var cleanupDate = oldestArchive.CreationTime.AddDays(_logCleanupPeriod); + if (DateTime.Compare(cleanupDate, DateTime.Now) <= 0) { + foreach (var file in logFolderContent) { + file.Delete(); + } + } else { + File.Delete(oldestArchive.FullName); + } + } + + public override string ToString() => $"{base.ToString()}, Chunk Size: {_logChunkSize}, Max chunk count: {_logChunkMaxCount}, Max log archive count: {_logArchiveMaxCount}, Cleanup period: {_logCleanupPeriod} days]"; +} \ No newline at end of file diff --git a/ld_client/LDClient/utils/loggers/LogFlow.cs b/ld_client/LDClient/utils/loggers/LogFlow.cs new file mode 100644 index 0000000..5041b12 --- /dev/null +++ b/ld_client/LDClient/utils/loggers/LogFlow.cs @@ -0,0 +1,6 @@ +namespace LDClient.utils.loggers { + public enum LogFlow { + Console = 0, + File + } +} diff --git a/ld_client/LDClient/utils/loggers/LogType.cs b/ld_client/LDClient/utils/loggers/LogType.cs new file mode 100644 index 0000000..370057f --- /dev/null +++ b/ld_client/LDClient/utils/loggers/LogType.cs @@ -0,0 +1,7 @@ +namespace LDClient.utils.loggers { + public enum LogType { + Info = 0, + Debug, + Error + } +} diff --git a/ld_client/LDClient/utils/loggers/LogVerbosity.cs b/ld_client/LDClient/utils/loggers/LogVerbosity.cs new file mode 100644 index 0000000..03c292b --- /dev/null +++ b/ld_client/LDClient/utils/loggers/LogVerbosity.cs @@ -0,0 +1,7 @@ +namespace LDClient.utils.loggers { + public enum LogVerbosity { + None = 0, + Exceptions, + Full + } +} -- GitLab