From 98afcd379040ed7dea1f0b037bfe028c35b46400 Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Thu, 26 Sep 2024 07:03:16 +0200 Subject: [PATCH 1/4] Added a event thats gets fired when the logLevel is disabled --- EonaCat.Logger/EonaCat.Logger.csproj | 4 +- .../Extensions/ExceptionExtensions.cs | 15 +++++-- EonaCat.Logger/Managers/LogHelper.cs | 24 +++++++++-- EonaCat.Logger/Managers/LogManager.cs | 41 ++++++++++++++++--- README.md | 11 +++++ 5 files changed, 80 insertions(+), 15 deletions(-) diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj index ad0f40d..2275da1 100644 --- a/EonaCat.Logger/EonaCat.Logger.csproj +++ b/EonaCat.Logger/EonaCat.Logger.csproj @@ -3,7 +3,7 @@ .netstandard2.1; net6.0; net7.0; net8.0; net4.8; icon.ico latest - 1.3.2 + 1.3.3 EonaCat (Jeroen Saey) true EonaCat (Jeroen Saey) @@ -24,7 +24,7 @@ - 1.3.2+{chash:10}.{c:ymd} + 1.3.3+{chash:10}.{c:ymd} true true v[0-9]* diff --git a/EonaCat.Logger/Extensions/ExceptionExtensions.cs b/EonaCat.Logger/Extensions/ExceptionExtensions.cs index 3076f27..c254e77 100644 --- a/EonaCat.Logger/Extensions/ExceptionExtensions.cs +++ b/EonaCat.Logger/Extensions/ExceptionExtensions.cs @@ -63,10 +63,17 @@ public static class ExceptionExtensions foreach (DictionaryEntry entry in data) { - sb.Append(" | ") - .Append(entry.Key) - .Append(": ") - .AppendLine(entry.Value.ToString()); + if (entry.Key != null) + { + sb.Append(" | ") + .Append(entry.Key); + } + + if (entry.Value != null) + { + sb.Append(": ") + .AppendLine(entry.Value.ToString()); + } } return sb.ToString(); diff --git a/EonaCat.Logger/Managers/LogHelper.cs b/EonaCat.Logger/Managers/LogHelper.cs index 8393f25..a318e3e 100644 --- a/EonaCat.Logger/Managers/LogHelper.cs +++ b/EonaCat.Logger/Managers/LogHelper.cs @@ -23,7 +23,9 @@ public class ErrorMessage internal static class LogHelper { - internal static event EventHandler OnException; + internal static event EventHandler OnException; + + internal static event EventHandler OnLogLevelDisabled; internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string currentMessage, DateTime dateTime, string category = null) @@ -131,11 +133,27 @@ internal static class LogHelper { Log(logger, logLevel, message); } + } + + private bool IsLogLevelEnabled(LoggerSettings loggerSettings, ELogType logType) + { + if (loggerSettings == null) + { + OnLogLevelDisabled?.Invoke(this, new ErrorMessage { Message = "Settings is null." }); + return false; + } + + var isEnabled = loggerSettings.MaxLogType != ELogType.NONE && logType <= loggerSettings.MaxLogType; + if (!isEnabled) + { + OnLogLevelDisabled?.Invoke(this, new ErrorMessage { Message = $"Logtype '{logType}' is not enabled, cannot log message" }); + } + return isEnabled; } - private static bool IsLogLevelEnabled(LoggerSettings settings, ELogType logType) + public static void SetLogLevel(LoggerSettings settings, ELogType logType) { - return settings.MaxLogType != ELogType.NONE && logType <= settings.MaxLogType; + settings.MaxLogType = logType; } private static void Log(ILogger logger, LogLevel logLevel, string message) diff --git a/EonaCat.Logger/Managers/LogManager.cs b/EonaCat.Logger/Managers/LogManager.cs index 3590fb4..e91790c 100644 --- a/EonaCat.Logger/Managers/LogManager.cs +++ b/EonaCat.Logger/Managers/LogManager.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -57,8 +56,14 @@ namespace EonaCat.Logger.Managers SetupFileLogger(settings); SetupLogManager(); LogHelper.OnException += LogHelper_OnException; - } - + LogHelper.OnLogLevelDisabled += LogHelper_OnLogLevelDisabled; + } + + private void LogHelper_OnLogLevelDisabled(object sender, ErrorMessage e) + { + OnLogLevelDisabled?.Invoke(sender, e); + } + private DateTime CurrentDateTime => Settings.UseLocalTime ? DateTime.Now : DateTime.UtcNow; public ILoggerProvider LoggerProvider { get; private set; } public ILoggerFactory LoggerFactory { get; private set; } @@ -111,8 +116,16 @@ namespace EonaCat.Logger.Managers customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion); } + /// + /// Gets fired when an exception occurs during logging + /// public event EventHandler OnException; + /// + /// Gets fired when the log level is disabled and the user tries to log a message + /// + public event EventHandler OnLogLevelDisabled; + private static LoggerSettings CreateDefaultSettings() { var settings = new LoggerSettings @@ -194,7 +207,7 @@ namespace EonaCat.Logger.Managers string grayLogSource = null, string grayLogVersion = "1.1") { if (string.IsNullOrEmpty(message) || logType == ELogType.NONE || - (int)logType > (int)Settings.MaxLogType) + !IsLogLevelEnabled(logType)) { return; } @@ -254,8 +267,24 @@ namespace EonaCat.Logger.Managers }; Settings.OnLogEvent(logMessage); - } - + } + + private bool IsLogLevelEnabled(ELogType logType) + { + if (Settings == null) + { + OnLogLevelDisabled?.Invoke(this, new ErrorMessage { Message = "Settings is null." }); + return false; + } + + var isEnabled = Settings.MaxLogType != ELogType.NONE && logType <= Settings.MaxLogType; + if (!isEnabled) + { + OnLogLevelDisabled?.Invoke(this, new ErrorMessage { Message = $"Logtype '{logType}' is not enabled, cannot log message" }); + } + return isEnabled; + } + public void Reset() { Settings.ResetLogEvent(); diff --git a/README.md b/README.md index 7385add..9a993e8 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,7 @@ namespace EonaCat.Logger.Advanced // Create and configure a LogManager for logging. _logManager = new LogManager(new LoggerSettings { RemoveMessagePrefix = true }); _logManager.OnException += _logManager_OnException; + _logManager.OnLogLevelDisabled += _logManager_OnLogLevelDisabled; _logManager.Settings.FileLoggerOptions.FileNamePrefix = "advanced"; _logManager.Settings.UseLocalTime = true; _logManager.Settings.FileLoggerOptions.UseLocalTime = true; @@ -106,6 +107,16 @@ namespace EonaCat.Logger.Advanced // Event handler for LogManager exceptions, writes messages to the console. private static void _logManager_OnException(object? sender, ErrorMessage e) + { + Console.WriteLine(e.Message); + if (e.Exception != null) + { + Console.WriteLine(e.Exception); + } + } + + // Event handler for LogManager loglevel disabled notifications, writes messages to the console. + private static void _logManager_OnLogLevelDisabled(object? sender, ErrorMessage e) { Console.WriteLine(e.Message); if (e.Exception != null) From 44e4da0a2448f77f6d1d2416f2e1b0d3eab9f485 Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Thu, 26 Sep 2024 07:04:42 +0200 Subject: [PATCH 2/4] Made the logLevel event static --- EonaCat.Logger/Managers/LogHelper.cs | 618 +++++++++++++-------------- 1 file changed, 309 insertions(+), 309 deletions(-) diff --git a/EonaCat.Logger/Managers/LogHelper.cs b/EonaCat.Logger/Managers/LogHelper.cs index a318e3e..48a4d32 100644 --- a/EonaCat.Logger/Managers/LogHelper.cs +++ b/EonaCat.Logger/Managers/LogHelper.cs @@ -1,324 +1,324 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Text; -using System.Threading.Tasks; -using EonaCat.Json; -using EonaCat.Logger.Extensions; -using EonaCat.Logger.GrayLog; -using EonaCat.Logger.Splunk.Models; -using EonaCat.Logger.Syslog; -using Microsoft.Extensions.Logging; - -// This file is part of the EonaCat project(s) which is released under the Apache License. -// See the LICENSE file or go to https://EonaCat.com/License for full license details. - -namespace EonaCat.Logger.Managers; - -public class ErrorMessage -{ - public Exception Exception { get; set; } - public string Message { get; set; } -} - -internal static class LogHelper -{ +using System; +using System.Collections.Generic; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using EonaCat.Json; +using EonaCat.Logger.Extensions; +using EonaCat.Logger.GrayLog; +using EonaCat.Logger.Splunk.Models; +using EonaCat.Logger.Syslog; +using Microsoft.Extensions.Logging; + +// This file is part of the EonaCat project(s) which is released under the Apache License. +// See the LICENSE file or go to https://EonaCat.com/License for full license details. + +namespace EonaCat.Logger.Managers; + +public class ErrorMessage +{ + public Exception Exception { get; set; } + public string Message { get; set; } +} + +internal static class LogHelper +{ internal static event EventHandler OnException; - - internal static event EventHandler OnLogLevelDisabled; - - internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string currentMessage, - DateTime dateTime, string category = null) - { - if (string.IsNullOrWhiteSpace(currentMessage)) - { - return currentMessage; - } - - if (string.IsNullOrWhiteSpace(category)) - { - category = "General"; - } - - var sb = new StringBuilder(settings?.HeaderFormat ?? "[EonaCatLogger]"); - - sb.Replace("{ts}", - dateTime.ToString(settings?.TimestampFormat ?? "yyyy-MM-dd HH:mm:ss") + " " + - (settings?.UseLocalTime ?? false ? "[LOCAL]" : "[UTC]")) - .Replace("{host}", $"[Host:{Dns.GetHostName()}]") - .Replace("{category}", $"[Category:{category}]") - .Replace("{thread}", $"[ThreadId:{Environment.CurrentManagedThreadId}]") - .Replace("{sev}", $"[{logType}]"); - - if (!settings?.RemoveMessagePrefix ?? (false && !currentMessage.Contains("[EonaCatLogger]"))) - { - sb.Insert(0, "[EonaCatLogger] "); - } - - sb.Append(" ").Append(currentMessage); - - return sb.ToString(); - } - - internal static void SendToConsole(LoggerSettings settings, ELogType logType, string message, bool writeToConsole) - { - if (settings == null || !writeToConsole || string.IsNullOrWhiteSpace(message)) - { - return; - } - - if (settings.EnableColors && settings.Colors != null) - { - var prevForeground = Console.ForegroundColor; - var prevBackground = Console.BackgroundColor; - - ConsoleColor foregroundColor; - ConsoleColor backgroundColor; - switch (logType) - { - case ELogType.DEBUG: - foregroundColor = settings.Colors.Debug.Foreground; - backgroundColor = settings.Colors.Debug.Background; - break; - case ELogType.INFO: - foregroundColor = settings.Colors.Info.Foreground; - backgroundColor = settings.Colors.Info.Background; - break; - case ELogType.WARNING: - foregroundColor = settings.Colors.Warning.Foreground; - backgroundColor = settings.Colors.Warning.Background; - break; - case ELogType.ERROR: - foregroundColor = settings.Colors.Error.Foreground; - backgroundColor = settings.Colors.Error.Background; - break; - case ELogType.TRAFFIC: - foregroundColor = settings.Colors.Traffic.Foreground; - backgroundColor = settings.Colors.Traffic.Background; - break; - case ELogType.CRITICAL: - foregroundColor = settings.Colors.Critical.Foreground; - backgroundColor = settings.Colors.Critical.Background; - break; - case ELogType.TRACE: - foregroundColor = settings.Colors.Trace.Foreground; - backgroundColor = settings.Colors.Trace.Background; - break; - default: - return; - } - - Console.ForegroundColor = foregroundColor; - Console.BackgroundColor = backgroundColor; - Console.WriteLine(message); - Console.ForegroundColor = prevForeground; - Console.BackgroundColor = prevBackground; - } - else - { - Console.WriteLine(message); - } - } - - internal static void SendToFile(ILogger logger, LoggerSettings settings, ELogType logType, string message) - { - if (logger == null || settings == null || !settings.EnableFileLogging || - string.IsNullOrWhiteSpace(message)) - { - return; - } - - var logLevel = logType.ToLogLevel(); - if (IsLogLevelEnabled(settings, logType)) - { - Log(logger, logLevel, message); - } + + internal static event EventHandler OnLogLevelDisabled; + + internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string currentMessage, + DateTime dateTime, string category = null) + { + if (string.IsNullOrWhiteSpace(currentMessage)) + { + return currentMessage; + } + + if (string.IsNullOrWhiteSpace(category)) + { + category = "General"; + } + + var sb = new StringBuilder(settings?.HeaderFormat ?? "[EonaCatLogger]"); + + sb.Replace("{ts}", + dateTime.ToString(settings?.TimestampFormat ?? "yyyy-MM-dd HH:mm:ss") + " " + + (settings?.UseLocalTime ?? false ? "[LOCAL]" : "[UTC]")) + .Replace("{host}", $"[Host:{Dns.GetHostName()}]") + .Replace("{category}", $"[Category:{category}]") + .Replace("{thread}", $"[ThreadId:{Environment.CurrentManagedThreadId}]") + .Replace("{sev}", $"[{logType}]"); + + if (!settings?.RemoveMessagePrefix ?? (false && !currentMessage.Contains("[EonaCatLogger]"))) + { + sb.Insert(0, "[EonaCatLogger] "); + } + + sb.Append(" ").Append(currentMessage); + + return sb.ToString(); } - private bool IsLogLevelEnabled(LoggerSettings loggerSettings, ELogType logType) + internal static void SendToConsole(LoggerSettings settings, ELogType logType, string message, bool writeToConsole) + { + if (settings == null || !writeToConsole || string.IsNullOrWhiteSpace(message)) + { + return; + } + + if (settings.EnableColors && settings.Colors != null) + { + var prevForeground = Console.ForegroundColor; + var prevBackground = Console.BackgroundColor; + + ConsoleColor foregroundColor; + ConsoleColor backgroundColor; + switch (logType) + { + case ELogType.DEBUG: + foregroundColor = settings.Colors.Debug.Foreground; + backgroundColor = settings.Colors.Debug.Background; + break; + case ELogType.INFO: + foregroundColor = settings.Colors.Info.Foreground; + backgroundColor = settings.Colors.Info.Background; + break; + case ELogType.WARNING: + foregroundColor = settings.Colors.Warning.Foreground; + backgroundColor = settings.Colors.Warning.Background; + break; + case ELogType.ERROR: + foregroundColor = settings.Colors.Error.Foreground; + backgroundColor = settings.Colors.Error.Background; + break; + case ELogType.TRAFFIC: + foregroundColor = settings.Colors.Traffic.Foreground; + backgroundColor = settings.Colors.Traffic.Background; + break; + case ELogType.CRITICAL: + foregroundColor = settings.Colors.Critical.Foreground; + backgroundColor = settings.Colors.Critical.Background; + break; + case ELogType.TRACE: + foregroundColor = settings.Colors.Trace.Foreground; + backgroundColor = settings.Colors.Trace.Background; + break; + default: + return; + } + + Console.ForegroundColor = foregroundColor; + Console.BackgroundColor = backgroundColor; + Console.WriteLine(message); + Console.ForegroundColor = prevForeground; + Console.BackgroundColor = prevBackground; + } + else + { + Console.WriteLine(message); + } + } + + internal static void SendToFile(ILogger logger, LoggerSettings settings, ELogType logType, string message) + { + if (logger == null || settings == null || !settings.EnableFileLogging || + string.IsNullOrWhiteSpace(message)) + { + return; + } + + var logLevel = logType.ToLogLevel(); + if (IsLogLevelEnabled(settings, logType)) + { + Log(logger, logLevel, message); + } + } + + private static bool IsLogLevelEnabled(LoggerSettings loggerSettings, ELogType logType) { if (loggerSettings == null) { - OnLogLevelDisabled?.Invoke(this, new ErrorMessage { Message = "Settings is null." }); + OnLogLevelDisabled?.Invoke(null, new ErrorMessage { Message = "Settings is null." }); return false; } var isEnabled = loggerSettings.MaxLogType != ELogType.NONE && logType <= loggerSettings.MaxLogType; if (!isEnabled) { - OnLogLevelDisabled?.Invoke(this, new ErrorMessage { Message = $"Logtype '{logType}' is not enabled, cannot log message" }); + OnLogLevelDisabled?.Invoke(null, new ErrorMessage { Message = $"Logtype '{logType}' is not enabled, cannot log message" }); } return isEnabled; - } - - public static void SetLogLevel(LoggerSettings settings, ELogType logType) - { - settings.MaxLogType = logType; - } - - private static void Log(ILogger logger, LogLevel logLevel, string message) - { - logger.Log(logLevel, message); - } - - public static async Task SendToSplunkServersAsync(LoggerSettings settings, SplunkPayload splunkPayload, - bool sendToSplunkServer) - { - if (settings == null || !sendToSplunkServer || splunkPayload == null) - { - return; - } - - if (settings.SplunkServers == null) - { - settings.SplunkServers = new List(); - } - - foreach (var splunkServer in settings.SplunkServers) - { - if (!splunkServer.HasHecUrl || !splunkServer.HasHecToken) - { - OnException?.Invoke(null, - new ErrorMessage - { - Message = - $"Splunk server HecUrl or HecToken not specified, skipping splunkServer '{splunkServer.SplunkHecUrl}'" - }); - continue; - } - - try - { - var response = await splunkServer.SendAsync(splunkPayload); - - if (!response.IsSuccessStatusCode) - { - OnException?.Invoke(null, - new ErrorMessage - { - Message = - $"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}" - }); - } - } - catch (Exception exception) - { - OnException?.Invoke(null, - new ErrorMessage - { - Exception = exception, - Message = - $"Error while logging to Splunk Server '{splunkServer.SplunkHecUrl}': {exception.Message}" - }); - } - } - } - - public static async Task SendToSplunkServersAsync(LoggerSettings settings, string logType, string message, - bool sendToSplunkServer) - { - if (settings == null || !sendToSplunkServer || string.IsNullOrWhiteSpace(message)) - { - return; - } - - var splunkPayload = new SplunkPayload - { - Host = Environment.MachineName, - EventData = message, - SourceType = logType - }; - - await SendToSplunkServersAsync(settings, splunkPayload, sendToSplunkServer); - } - - internal static async Task SendToGrayLogServersAsync(LoggerSettings settings, string message, ELogType logLevel, - string facility, string source, bool sendToGrayLogServer, string version = "1.1") - { - if (settings == null || !sendToGrayLogServer || string.IsNullOrWhiteSpace(message)) - { - return; - } - - foreach (var grayLogServer in settings.GrayLogServers ?? new List { new("127.0.0.1") }) - { - try - { - var gelfMessage = new - { - version, - host = Environment.MachineName, - short_message = message, - level = logLevel.ToGrayLogLevel(), - facility, - source, - timestamp = DateTime.UtcNow.ToUnixTimestamp() - }; - - var messageBytes = Encoding.UTF8.GetBytes(JsonHelper.ToJson(gelfMessage)); - await grayLogServer.Udp.SendAsync(messageBytes, messageBytes.Length); - } - catch (Exception exception) - { - OnException?.Invoke(null, - new ErrorMessage - { - Exception = exception, - Message = - $"Error while logging to GrayLog Server '{grayLogServer.Hostname}': {exception.Message}" - }); - } - } - } - - internal static async Task SendToSysLogServersAsync(LoggerSettings settings, string message, - bool sendToSyslogServers) - { - if (settings == null || !sendToSyslogServers || string.IsNullOrWhiteSpace(message)) - { - return; - } - - foreach (var server in settings.SysLogServers ?? new List { new("127.0.0.1") }) - { - try - { - if (string.IsNullOrWhiteSpace(server.Hostname)) - { - OnException?.Invoke(null, - new ErrorMessage { Message = "Server hostname not specified, skipping SysLog Server" }); - continue; - } - - if (server.Port < 0) - { - OnException?.Invoke(null, - new ErrorMessage { Message = "Server port must be zero or greater, skipping SysLog Server" }); - continue; - } - - var data = Encoding.UTF8.GetBytes(message); - await server.Udp.SendAsync(data, data.Length); - } - catch (Exception exception) - { - OnException?.Invoke(null, - new ErrorMessage - { - Exception = exception, - Message = $"Error while logging to SysLog Server '{server.Hostname}': {exception.Message}" - }); - } - } - } - - internal static string GetStartupMessage() - { - return $"{DllInfo.ApplicationName} started."; - } - - internal static string GetStopMessage() - { - return $"{DllInfo.ApplicationName} stopped."; - } + } + + public static void SetLogLevel(LoggerSettings settings, ELogType logType) + { + settings.MaxLogType = logType; + } + + private static void Log(ILogger logger, LogLevel logLevel, string message) + { + logger.Log(logLevel, message); + } + + public static async Task SendToSplunkServersAsync(LoggerSettings settings, SplunkPayload splunkPayload, + bool sendToSplunkServer) + { + if (settings == null || !sendToSplunkServer || splunkPayload == null) + { + return; + } + + if (settings.SplunkServers == null) + { + settings.SplunkServers = new List(); + } + + foreach (var splunkServer in settings.SplunkServers) + { + if (!splunkServer.HasHecUrl || !splunkServer.HasHecToken) + { + OnException?.Invoke(null, + new ErrorMessage + { + Message = + $"Splunk server HecUrl or HecToken not specified, skipping splunkServer '{splunkServer.SplunkHecUrl}'" + }); + continue; + } + + try + { + var response = await splunkServer.SendAsync(splunkPayload); + + if (!response.IsSuccessStatusCode) + { + OnException?.Invoke(null, + new ErrorMessage + { + Message = + $"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}" + }); + } + } + catch (Exception exception) + { + OnException?.Invoke(null, + new ErrorMessage + { + Exception = exception, + Message = + $"Error while logging to Splunk Server '{splunkServer.SplunkHecUrl}': {exception.Message}" + }); + } + } + } + + public static async Task SendToSplunkServersAsync(LoggerSettings settings, string logType, string message, + bool sendToSplunkServer) + { + if (settings == null || !sendToSplunkServer || string.IsNullOrWhiteSpace(message)) + { + return; + } + + var splunkPayload = new SplunkPayload + { + Host = Environment.MachineName, + EventData = message, + SourceType = logType + }; + + await SendToSplunkServersAsync(settings, splunkPayload, sendToSplunkServer); + } + + internal static async Task SendToGrayLogServersAsync(LoggerSettings settings, string message, ELogType logLevel, + string facility, string source, bool sendToGrayLogServer, string version = "1.1") + { + if (settings == null || !sendToGrayLogServer || string.IsNullOrWhiteSpace(message)) + { + return; + } + + foreach (var grayLogServer in settings.GrayLogServers ?? new List { new("127.0.0.1") }) + { + try + { + var gelfMessage = new + { + version, + host = Environment.MachineName, + short_message = message, + level = logLevel.ToGrayLogLevel(), + facility, + source, + timestamp = DateTime.UtcNow.ToUnixTimestamp() + }; + + var messageBytes = Encoding.UTF8.GetBytes(JsonHelper.ToJson(gelfMessage)); + await grayLogServer.Udp.SendAsync(messageBytes, messageBytes.Length); + } + catch (Exception exception) + { + OnException?.Invoke(null, + new ErrorMessage + { + Exception = exception, + Message = + $"Error while logging to GrayLog Server '{grayLogServer.Hostname}': {exception.Message}" + }); + } + } + } + + internal static async Task SendToSysLogServersAsync(LoggerSettings settings, string message, + bool sendToSyslogServers) + { + if (settings == null || !sendToSyslogServers || string.IsNullOrWhiteSpace(message)) + { + return; + } + + foreach (var server in settings.SysLogServers ?? new List { new("127.0.0.1") }) + { + try + { + if (string.IsNullOrWhiteSpace(server.Hostname)) + { + OnException?.Invoke(null, + new ErrorMessage { Message = "Server hostname not specified, skipping SysLog Server" }); + continue; + } + + if (server.Port < 0) + { + OnException?.Invoke(null, + new ErrorMessage { Message = "Server port must be zero or greater, skipping SysLog Server" }); + continue; + } + + var data = Encoding.UTF8.GetBytes(message); + await server.Udp.SendAsync(data, data.Length); + } + catch (Exception exception) + { + OnException?.Invoke(null, + new ErrorMessage + { + Exception = exception, + Message = $"Error while logging to SysLog Server '{server.Hostname}': {exception.Message}" + }); + } + } + } + + internal static string GetStartupMessage() + { + return $"{DllInfo.ApplicationName} started."; + } + + internal static string GetStopMessage() + { + return $"{DllInfo.ApplicationName} stopped."; + } } \ No newline at end of file From 8d902100de61239332c7b96a7c171f045358526f Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Mon, 14 Oct 2024 15:43:29 +0200 Subject: [PATCH 3/4] Updated --- EonaCat.Logger/EonaCat.Logger.csproj | 4 +- EonaCat.Logger/Managers/LogHelper.cs | 153 +++--- EonaCat.Logger/Managers/LogManager.cs | 643 +++++++++++++------------- 3 files changed, 373 insertions(+), 427 deletions(-) diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj index 2275da1..c1921d8 100644 --- a/EonaCat.Logger/EonaCat.Logger.csproj +++ b/EonaCat.Logger/EonaCat.Logger.csproj @@ -3,7 +3,7 @@ .netstandard2.1; net6.0; net7.0; net8.0; net4.8; icon.ico latest - 1.3.3 + 1.3.4 EonaCat (Jeroen Saey) true EonaCat (Jeroen Saey) @@ -24,7 +24,7 @@ - 1.3.3+{chash:10}.{c:ymd} + 1.3.4+{chash:10}.{c:ymd} true true v[0-9]* diff --git a/EonaCat.Logger/Managers/LogHelper.cs b/EonaCat.Logger/Managers/LogHelper.cs index 48a4d32..8cbced9 100644 --- a/EonaCat.Logger/Managers/LogHelper.cs +++ b/EonaCat.Logger/Managers/LogHelper.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; @@ -27,89 +28,49 @@ internal static class LogHelper internal static event EventHandler OnLogLevelDisabled; + private static readonly string MachineName = Environment.MachineName; + private static readonly string HostName = Dns.GetHostName(); + internal static string FormatMessageWithHeader(LoggerSettings settings, ELogType logType, string currentMessage, DateTime dateTime, string category = null) { - if (string.IsNullOrWhiteSpace(currentMessage)) - { - return currentMessage; - } - - if (string.IsNullOrWhiteSpace(category)) - { - category = "General"; - } + if (string.IsNullOrWhiteSpace(currentMessage)) return currentMessage; + category ??= "General"; var sb = new StringBuilder(settings?.HeaderFormat ?? "[EonaCatLogger]"); + var timestamp = dateTime.ToString(settings?.TimestampFormat ?? "yyyy-MM-dd HH:mm:ss"); + var timeLabel = settings?.UseLocalTime ?? false ? "[LOCAL]" : "[UTC]"; - sb.Replace("{ts}", - dateTime.ToString(settings?.TimestampFormat ?? "yyyy-MM-dd HH:mm:ss") + " " + - (settings?.UseLocalTime ?? false ? "[LOCAL]" : "[UTC]")) - .Replace("{host}", $"[Host:{Dns.GetHostName()}]") + sb.Replace("{ts}", $"{timestamp} {timeLabel}") + .Replace("{host}", $"[Host:{HostName}]") .Replace("{category}", $"[Category:{category}]") .Replace("{thread}", $"[ThreadId:{Environment.CurrentManagedThreadId}]") .Replace("{sev}", $"[{logType}]"); - if (!settings?.RemoveMessagePrefix ?? (false && !currentMessage.Contains("[EonaCatLogger]"))) + if (!(settings?.RemoveMessagePrefix ?? false) && !currentMessage.Contains("[EonaCatLogger]")) { sb.Insert(0, "[EonaCatLogger] "); } sb.Append(" ").Append(currentMessage); - return sb.ToString(); } internal static void SendToConsole(LoggerSettings settings, ELogType logType, string message, bool writeToConsole) { - if (settings == null || !writeToConsole || string.IsNullOrWhiteSpace(message)) - { - return; - } + if (settings == null || !writeToConsole || string.IsNullOrWhiteSpace(message)) return; if (settings.EnableColors && settings.Colors != null) { var prevForeground = Console.ForegroundColor; var prevBackground = Console.BackgroundColor; - ConsoleColor foregroundColor; - ConsoleColor backgroundColor; - switch (logType) + if (TryGetLogColor(settings.Colors, logType, out var foregroundColor, out var backgroundColor)) { - case ELogType.DEBUG: - foregroundColor = settings.Colors.Debug.Foreground; - backgroundColor = settings.Colors.Debug.Background; - break; - case ELogType.INFO: - foregroundColor = settings.Colors.Info.Foreground; - backgroundColor = settings.Colors.Info.Background; - break; - case ELogType.WARNING: - foregroundColor = settings.Colors.Warning.Foreground; - backgroundColor = settings.Colors.Warning.Background; - break; - case ELogType.ERROR: - foregroundColor = settings.Colors.Error.Foreground; - backgroundColor = settings.Colors.Error.Background; - break; - case ELogType.TRAFFIC: - foregroundColor = settings.Colors.Traffic.Foreground; - backgroundColor = settings.Colors.Traffic.Background; - break; - case ELogType.CRITICAL: - foregroundColor = settings.Colors.Critical.Foreground; - backgroundColor = settings.Colors.Critical.Background; - break; - case ELogType.TRACE: - foregroundColor = settings.Colors.Trace.Foreground; - backgroundColor = settings.Colors.Trace.Background; - break; - default: - return; + Console.ForegroundColor = foregroundColor; + Console.BackgroundColor = backgroundColor; } - Console.ForegroundColor = foregroundColor; - Console.BackgroundColor = backgroundColor; Console.WriteLine(message); Console.ForegroundColor = prevForeground; Console.BackgroundColor = prevBackground; @@ -120,21 +81,40 @@ internal static class LogHelper } } - internal static void SendToFile(ILogger logger, LoggerSettings settings, ELogType logType, string message) + private static bool TryGetLogColor(ColorSchema colors, ELogType logType, out ConsoleColor foreground, out ConsoleColor background) { - if (logger == null || settings == null || !settings.EnableFileLogging || - string.IsNullOrWhiteSpace(message)) - { - return; - } + foreground = default; + background = default; - var logLevel = logType.ToLogLevel(); - if (IsLogLevelEnabled(settings, logType)) + return logType switch { - Log(logger, logLevel, message); - } + ELogType.DEBUG => TrySetColors(colors.Debug, out foreground, out background), + ELogType.INFO => TrySetColors(colors.Info, out foreground, out background), + ELogType.WARNING => TrySetColors(colors.Warning, out foreground, out background), + ELogType.ERROR => TrySetColors(colors.Error, out foreground, out background), + ELogType.TRAFFIC => TrySetColors(colors.Traffic, out foreground, out background), + ELogType.CRITICAL => TrySetColors(colors.Critical, out foreground, out background), + ELogType.TRACE => TrySetColors(colors.Trace, out foreground, out background), + _ => false, + }; } + private static bool TrySetColors(ColorScheme color, out ConsoleColor foreground, out ConsoleColor background) + { + foreground = color.Foreground; + background = color.Background; + return true; + } + + internal static void SendToFile(ILogger logger, LoggerSettings settings, ELogType logType, string message) + { + if (logger == null || settings == null || !settings.EnableFileLogging || string.IsNullOrWhiteSpace(message)) return; + + if (IsLogLevelEnabled(settings, logType)) + { + Log(logger, logType.ToLogLevel(), message); + } + } private static bool IsLogLevelEnabled(LoggerSettings loggerSettings, ELogType logType) { if (loggerSettings == null) @@ -146,8 +126,9 @@ internal static class LogHelper var isEnabled = loggerSettings.MaxLogType != ELogType.NONE && logType <= loggerSettings.MaxLogType; if (!isEnabled) { - OnLogLevelDisabled?.Invoke(null, new ErrorMessage { Message = $"Logtype '{logType}' is not enabled, cannot log message" }); + OnLogLevelDisabled?.Invoke(null, new ErrorMessage { Message = $"Logtype '{logType}' is not enabled." }); } + return isEnabled; } @@ -161,55 +142,29 @@ internal static class LogHelper logger.Log(logLevel, message); } - public static async Task SendToSplunkServersAsync(LoggerSettings settings, SplunkPayload splunkPayload, - bool sendToSplunkServer) + public static async Task SendToSplunkServersAsync(LoggerSettings settings, SplunkPayload splunkPayload, bool sendToSplunkServer) { - if (settings == null || !sendToSplunkServer || splunkPayload == null) - { - return; - } + if (settings == null || !sendToSplunkServer || splunkPayload == null) return; - if (settings.SplunkServers == null) - { - settings.SplunkServers = new List(); - } - - foreach (var splunkServer in settings.SplunkServers) + foreach (var splunkServer in settings.SplunkServers ?? Enumerable.Empty()) { if (!splunkServer.HasHecUrl || !splunkServer.HasHecToken) { - OnException?.Invoke(null, - new ErrorMessage - { - Message = - $"Splunk server HecUrl or HecToken not specified, skipping splunkServer '{splunkServer.SplunkHecUrl}'" - }); + OnException?.Invoke(null, new ErrorMessage { Message = $"Invalid Splunk server configuration for '{splunkServer.SplunkHecUrl}'" }); continue; } try { var response = await splunkServer.SendAsync(splunkPayload); - if (!response.IsSuccessStatusCode) { - OnException?.Invoke(null, - new ErrorMessage - { - Message = - $"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}" - }); + OnException?.Invoke(null, new ErrorMessage { Message = $"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}" }); } } - catch (Exception exception) + catch (Exception ex) { - OnException?.Invoke(null, - new ErrorMessage - { - Exception = exception, - Message = - $"Error while logging to Splunk Server '{splunkServer.SplunkHecUrl}': {exception.Message}" - }); + OnException?.Invoke(null, new ErrorMessage { Exception = ex, Message = $"Error logging to Splunk '{splunkServer.SplunkHecUrl}': {ex.Message}" }); } } } @@ -247,7 +202,7 @@ internal static class LogHelper var gelfMessage = new { version, - host = Environment.MachineName, + host = MachineName, short_message = message, level = logLevel.ToGrayLogLevel(), facility, diff --git a/EonaCat.Logger/Managers/LogManager.cs b/EonaCat.Logger/Managers/LogManager.cs index e91790c..b953922 100644 --- a/EonaCat.Logger/Managers/LogManager.cs +++ b/EonaCat.Logger/Managers/LogManager.cs @@ -1,272 +1,263 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using EonaCat.Logger.EonaCatCoreLogger; -using EonaCat.Logger.EonaCatCoreLogger.Extensions; -using EonaCat.Logger.EonaCatCoreLogger.Models; -using EonaCat.Logger.Extensions; -using EonaCat.Logger.Syslog; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; - -namespace EonaCat.Logger.Managers -{ - // This file is part of the EonaCat project(s) which is released under the Apache License. - // See the LICENSE file or go to https://EonaCat.com/License for full license details. - - public class LogManager : ILogManager, IDisposable - { - private static readonly Lazy _instance = new(() => new LogManager(CreateDefaultSettings())); - private readonly CancellationTokenSource _tokenSource = new(); - private DateTime _logDate; - private bool _isDisposing; - private string _category; - - public LogManager(LoggerSettings settings, string serverIp, int serverPort) - { - if (string.IsNullOrEmpty(serverIp)) - { - throw new ArgumentNullException(nameof(serverIp)); - } - - if (serverPort < 0) - { - throw new ArgumentException("Server port must be zero or greater."); - } - - settings.SysLogServers = new List - { - new(serverIp, serverPort) - }; - - Settings = settings; - SetupLogManager(); - } - - public LogManager(LoggerSettings settings, string category = null) - { - _category = category; - if (string.IsNullOrWhiteSpace(category)) - { - _category = "General"; - } - Settings = settings; - SetupFileLogger(settings); - SetupLogManager(); - LogHelper.OnException += LogHelper_OnException; - LogHelper.OnLogLevelDisabled += LogHelper_OnLogLevelDisabled; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using EonaCat.Logger.EonaCatCoreLogger; +using EonaCat.Logger.EonaCatCoreLogger.Extensions; +using EonaCat.Logger.EonaCatCoreLogger.Models; +using EonaCat.Logger.Extensions; +using EonaCat.Logger.Syslog; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace EonaCat.Logger.Managers +{ + // This file is part of the EonaCat project(s) which is released under the Apache License. + // See the LICENSE file or go to https://EonaCat.com/License for full license details. + + public class LogManager : ILogManager, IDisposable + { + private static readonly Lazy _instance = new(() => new LogManager(CreateDefaultSettings())); + private readonly CancellationTokenSource _tokenSource = new(); + private DateTime _logDate; + private bool _isDisposing; + private string _category; + + public LogManager(LoggerSettings settings, string serverIp, int serverPort) + { + if (string.IsNullOrEmpty(serverIp)) + { + throw new ArgumentNullException(nameof(serverIp)); + } + + if (serverPort < 0) + { + throw new ArgumentException("Server port must be zero or greater."); + } + + settings.SysLogServers = new List + { + new(serverIp, serverPort) + }; + + Settings = settings; + SetupLogManager(); } - private void LogHelper_OnLogLevelDisabled(object sender, ErrorMessage e) - { - OnLogLevelDisabled?.Invoke(sender, e); + public LogManager(LoggerSettings settings, string category = null) + { + _category = category; + if (string.IsNullOrWhiteSpace(category)) + { + _category = "General"; + } + Settings = settings; + SetupFileLogger(settings); + SetupLogManager(); + LogHelper.OnException += LogHelper_OnException; + LogHelper.OnLogLevelDisabled += LogHelper_OnLogLevelDisabled; + } + + private void LogHelper_OnLogLevelDisabled(object sender, ErrorMessage e) + { + OnLogLevelDisabled?.Invoke(sender, e); + } + + private DateTime CurrentDateTime => Settings.UseLocalTime ? DateTime.Now : DateTime.UtcNow; + public ILoggerProvider LoggerProvider { get; private set; } + public ILoggerFactory LoggerFactory { get; private set; } + public ILogger Logger { get; private set; } + + public string CurrentLogFile => LoggerProvider is FileLoggerProvider fileLoggerProvider + ? fileLoggerProvider.LogFile + : string.Empty; + + public bool IsRunning { get; private set; } + + public static LogManager Instance => _instance.Value; + + public LoggerSettings Settings { get; set; } = CreateDefaultSettings(); + + public void Dispose() + { + DisposeAsync(true).GetAwaiter().GetResult(); + GC.SuppressFinalize(this); + } + + public async Task WriteAsync(Exception exception, string module = null, string method = null, + bool criticalException = false, + bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, + string customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, + string grayLogSource = null, string grayLogVersion = "1.1") + { + if (exception == null) + { + return; + } + + await WriteAsync(exception.FormatExceptionToMessage(module, method), + criticalException ? ELogType.CRITICAL : ELogType.ERROR, writeToConsole, sendToSysLogServers, + sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, + grayLogVersion); + } + + public async Task WriteAsync(string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null, + bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string customSplunkSourceType = null, + bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, + string grayLogVersion = "1.1") + { + if (logType == ELogType.NONE) + { + return; + } + + await InternalWriteAsync(CurrentDateTime, message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, + customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion); } - private DateTime CurrentDateTime => Settings.UseLocalTime ? DateTime.Now : DateTime.UtcNow; - public ILoggerProvider LoggerProvider { get; private set; } - public ILoggerFactory LoggerFactory { get; private set; } - public ILogger Logger { get; private set; } - - public string CurrentLogFile => LoggerProvider is FileLoggerProvider fileLoggerProvider - ? fileLoggerProvider.LogFile - : string.Empty; - - public bool IsRunning { get; private set; } - - public static LogManager Instance => _instance.Value; - - public LoggerSettings Settings { get; set; } = CreateDefaultSettings(); - - public void Dispose() - { - DisposeAsync(true).GetAwaiter().GetResult(); - GC.SuppressFinalize(this); - } - - public async Task WriteAsync(Exception exception, string module = null, string method = null, - bool criticalException = false, - bool? writeToConsole = null, bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, - string customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, - string grayLogSource = null, string grayLogVersion = "1.1") - { - if (exception == null) - { - return; - } - - await WriteAsync(exception.FormatExceptionToMessage(module, method), - criticalException ? ELogType.CRITICAL : ELogType.ERROR, writeToConsole, sendToSysLogServers, - sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, - grayLogVersion); - } - - public async Task WriteAsync(string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null, - bool? sendToSysLogServers = null, bool? sendToSplunkServers = null, string customSplunkSourceType = null, - bool? sendToGrayLogServers = null, string grayLogFacility = null, string grayLogSource = null, - string grayLogVersion = "1.1") - { - if (logType == ELogType.NONE) - { - return; - } - - await InternalWriteAsync(CurrentDateTime, message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, - customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion); - } - /// /// Gets fired when an exception occurs during logging - /// - public event EventHandler OnException; - + /// + public event EventHandler OnException; + /// /// Gets fired when the log level is disabled and the user tries to log a message - /// - public event EventHandler OnLogLevelDisabled; - - private static LoggerSettings CreateDefaultSettings() - { - var settings = new LoggerSettings - { - Id = "EonaCatLogger", - MaxLogType = ELogType.INFO - }; - return settings; - } - - protected virtual async Task DisposeAsync(bool disposing) - { - if (disposing) - { - _isDisposing = true; - await StopLoggingAsync(); - await Task.Delay(100); - } - } - - public async Task StartNewLogAsync() - { - if (_tokenSource.IsCancellationRequested) - { - return; - } - - if (IsRunning && CurrentDateTime.Date > _logDate.Date) - { - await StopLoggingAsync(); - } - - IsRunning = true; - - CreateLogger(); - - Directory.CreateDirectory(Settings.FileLoggerOptions.LogDirectory); - - _logDate = CurrentDateTime; - } - - private void CreateLogger() - { - // Dispose of previous ServiceProvider if it exists - LoggerProvider?.Dispose(); - LoggerFactory?.Dispose(); - - IServiceCollection serviceCollection = new ServiceCollection(); - serviceCollection.AddLogging(builder => builder.SetMinimumLevel(Settings.MaxLogType.ToLogLevel()) - .AddEonaCatFileLogger(configuration => - { - var fileLoggerOptions = Settings.FileLoggerOptions; - configuration.MaxWriteTries = fileLoggerOptions.MaxWriteTries; - configuration.RetainedFileCountLimit = fileLoggerOptions.RetainedFileCountLimit; - configuration.FlushPeriod = fileLoggerOptions.FlushPeriod; - configuration.IsEnabled = fileLoggerOptions.IsEnabled; - configuration.BatchSize = fileLoggerOptions.BatchSize; - configuration.FileSizeLimit = fileLoggerOptions.FileSizeLimit; - configuration.LogDirectory = fileLoggerOptions.LogDirectory; - configuration.FileNamePrefix = fileLoggerOptions.FileNamePrefix; - configuration.MaxRolloverFiles = fileLoggerOptions.MaxRolloverFiles; - configuration.UseLocalTime = Settings.UseLocalTime; - configuration.UseMask = Settings.UseMask; - configuration.Mask = fileLoggerOptions.Mask; - configuration.UseDefaultMasking = Settings.UseDefaultMasking; - configuration.MaskedKeywords = fileLoggerOptions.MaskedKeywords; - })); - - var serviceProvider = serviceCollection.BuildServiceProvider(); - LoggerProvider = serviceProvider.GetService(); - LoggerFactory = serviceProvider.GetService(); - Logger = LoggerFactory.CreateLogger(Settings.Id); - LogHelper.SendToFile(Logger, Settings, ELogType.INFO, LogHelper.GetStartupMessage()); - } - - private async Task InternalWriteAsync(DateTime dateTime, string message, ELogType logType = ELogType.INFO, - bool? writeToConsole = null, bool? sendToSyslogServers = null, bool? sendToSplunkServers = null, - string customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, - string grayLogSource = null, string grayLogVersion = "1.1") - { - if (string.IsNullOrEmpty(message) || logType == ELogType.NONE || - !IsLogLevelEnabled(logType)) - { - return; - } - - if (_isDisposing) - { - return; - } - - if (!IsRunning) - { - await StartNewLogAsync(); - } - - var messageWithHeader = LogHelper.FormatMessageWithHeader(Settings, logType, message, dateTime, _category); - var writeToConsoleValue = writeToConsole ?? Settings.EnableConsole; - var sendToSyslogServersValue = sendToSyslogServers ?? Settings.SendToSyslogServers; - var sendToSplunkServersValue = sendToSplunkServers ?? Settings.SendToSplunkServers; - var sendToGrayLogServersValue = sendToGrayLogServers ?? Settings.SendToGrayLogServers; - - LogHelper.SendToFile(Logger, Settings, logType, message); - - if (writeToConsoleValue) - { - LogHelper.SendToConsole(Settings, logType, messageWithHeader, true); - } - - var tasks = new List(); - if (sendToSyslogServersValue || sendToSplunkServersValue || sendToGrayLogServersValue) - { - if (sendToSyslogServersValue) - { - tasks.Add(LogHelper.SendToSysLogServersAsync(Settings, messageWithHeader, true)); - } - - if (sendToSplunkServersValue) - { - tasks.Add(LogHelper.SendToSplunkServersAsync(Settings, customSplunkSourceType ?? logType.ToString(), - messageWithHeader, true)); - } - - if (sendToGrayLogServersValue) - { - tasks.Add(LogHelper.SendToGrayLogServersAsync(Settings, messageWithHeader, logType, grayLogFacility, - grayLogSource, true, grayLogVersion)); - } - - await Task.WhenAll(tasks); - } - - var logMessage = new EonaCatLogMessage - { - DateTime = dateTime, - Message = message, - LogType = logType, - Origin = string.IsNullOrWhiteSpace(Settings.LogOrigin) ? "LogManager" : Settings.LogOrigin - }; - - Settings.OnLogEvent(logMessage); + /// + public event EventHandler OnLogLevelDisabled; + + private static LoggerSettings CreateDefaultSettings() + { + var settings = new LoggerSettings + { + Id = "EonaCatLogger", + MaxLogType = ELogType.INFO + }; + return settings; + } + + protected virtual async Task DisposeAsync(bool disposing) + { + if (disposing) + { + _isDisposing = true; + await StopLoggingAsync(); + await Task.Delay(100); + } + } + + public async Task StartNewLogAsync() + { + if (_tokenSource.IsCancellationRequested) + { + return; + } + + if (IsRunning && CurrentDateTime.Date > _logDate.Date) + { + await StopLoggingAsync(); + } + + IsRunning = true; + + CreateLogger(); + + Directory.CreateDirectory(Settings.FileLoggerOptions.LogDirectory); + + _logDate = CurrentDateTime; + } + + private void CreateLogger() + { + // Dispose of previous ServiceProvider if it exists + LoggerProvider?.Dispose(); + LoggerFactory?.Dispose(); + + IServiceCollection serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(builder => builder.SetMinimumLevel(Settings.MaxLogType.ToLogLevel()) + .AddEonaCatFileLogger(configuration => + { + var fileLoggerOptions = Settings.FileLoggerOptions; + configuration.MaxWriteTries = fileLoggerOptions.MaxWriteTries; + configuration.RetainedFileCountLimit = fileLoggerOptions.RetainedFileCountLimit; + configuration.FlushPeriod = fileLoggerOptions.FlushPeriod; + configuration.IsEnabled = fileLoggerOptions.IsEnabled; + configuration.BatchSize = fileLoggerOptions.BatchSize; + configuration.FileSizeLimit = fileLoggerOptions.FileSizeLimit; + configuration.LogDirectory = fileLoggerOptions.LogDirectory; + configuration.FileNamePrefix = fileLoggerOptions.FileNamePrefix; + configuration.MaxRolloverFiles = fileLoggerOptions.MaxRolloverFiles; + configuration.UseLocalTime = Settings.UseLocalTime; + configuration.UseMask = Settings.UseMask; + configuration.Mask = fileLoggerOptions.Mask; + configuration.UseDefaultMasking = Settings.UseDefaultMasking; + configuration.MaskedKeywords = fileLoggerOptions.MaskedKeywords; + })); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + LoggerProvider = serviceProvider.GetService(); + LoggerFactory = serviceProvider.GetService(); + Logger = LoggerFactory.CreateLogger(Settings.Id); + LogHelper.SendToFile(Logger, Settings, ELogType.INFO, LogHelper.GetStartupMessage()); + } + + private async Task InternalWriteAsync(DateTime dateTime, string message, ELogType logType = ELogType.INFO, + bool? writeToConsole = null, bool? sendToSyslogServers = null, bool? sendToSplunkServers = null, + string customSplunkSourceType = null, bool? sendToGrayLogServers = null, string grayLogFacility = null, + string grayLogSource = null, string grayLogVersion = "1.1") + { + if (string.IsNullOrWhiteSpace(message) || logType == ELogType.NONE || !IsLogLevelEnabled(logType) || _isDisposing) + { + return; + } + + if (!IsRunning) + { + await StartNewLogAsync().ConfigureAwait(false); + } + + var messageWithHeader = LogHelper.FormatMessageWithHeader(Settings, logType, message, dateTime, _category); + var writeToConsoleValue = writeToConsole.GetValueOrDefault(Settings.EnableConsole); + var sendToSyslogServersValue = sendToSyslogServers.GetValueOrDefault(Settings.SendToSyslogServers); + var sendToSplunkServersValue = sendToSplunkServers.GetValueOrDefault(Settings.SendToSplunkServers); + var sendToGrayLogServersValue = sendToGrayLogServers.GetValueOrDefault(Settings.SendToGrayLogServers); + + LogHelper.SendToFile(Logger, Settings, logType, message); + + if (writeToConsoleValue) + { + LogHelper.SendToConsole(Settings, logType, messageWithHeader, true); + } + + var tasks = new List(3); + if (sendToSyslogServersValue) + { + tasks.Add(LogHelper.SendToSysLogServersAsync(Settings, messageWithHeader, true)); + } + if (sendToSplunkServersValue) + { + tasks.Add(LogHelper.SendToSplunkServersAsync(Settings, customSplunkSourceType ?? logType.ToString(), + messageWithHeader, true)); + } + if (sendToGrayLogServersValue) + { + tasks.Add(LogHelper.SendToGrayLogServersAsync(Settings, messageWithHeader, logType, grayLogFacility, + grayLogSource, true, grayLogVersion)); + } + + if (tasks.Count > 0) + { + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + var logMessage = new EonaCatLogMessage + { + DateTime = dateTime, + Message = message, + LogType = logType, + Origin = string.IsNullOrEmpty(Settings.LogOrigin) ? "LogManager" : Settings.LogOrigin + }; + Settings.OnLogEvent(logMessage); } private bool IsLogLevelEnabled(ELogType logType) @@ -285,69 +276,69 @@ namespace EonaCat.Logger.Managers return isEnabled; } - public void Reset() - { - Settings.ResetLogEvent(); - } - - private void LogHelper_OnException(object sender, ErrorMessage e) - { - OnException?.Invoke(sender, e); - } - - private void SetupFileLogger(LoggerSettings settings = null, string logFolder = null, bool defaultPrefix = true) - { - if (settings == null) - { - settings = Settings; - } - - if (!settings.EnableFileLogging) - { - return; - } - - if (logFolder != null) - { - settings.FileLoggerOptions.LogDirectory = logFolder; - } - - if (string.IsNullOrWhiteSpace(settings.FileLoggerOptions.FileNamePrefix)) - { - settings.FileLoggerOptions.FileNamePrefix = defaultPrefix ? "EonaCat" : string.Empty; - } - } - - private void SetupLogManager() - { - AppDomain.CurrentDomain.ProcessExit += ProcessExit; - _logDate = CurrentDateTime; - } - - private void ProcessExit(object sender, EventArgs e) - { - Dispose(); - } - - private Task StopLoggingAsync() - { - WriteStopMessage(); - IsRunning = false; - return Task.CompletedTask; - } - - private void WriteStopMessage() - { - var stopMessage = $"{DllInfo.ApplicationName} stopped.{Environment.NewLine}"; - LogHelper.SendToFile(Logger, Settings, ELogType.INFO, stopMessage); - } - - public void DeleteCurrentLogFile() - { - if (!string.IsNullOrEmpty(CurrentLogFile)) - { - File.Delete(CurrentLogFile); - } - } - } + public void Reset() + { + Settings.ResetLogEvent(); + } + + private void LogHelper_OnException(object sender, ErrorMessage e) + { + OnException?.Invoke(sender, e); + } + + private void SetupFileLogger(LoggerSettings settings = null, string logFolder = null, bool defaultPrefix = true) + { + if (settings == null) + { + settings = Settings; + } + + if (!settings.EnableFileLogging) + { + return; + } + + if (logFolder != null) + { + settings.FileLoggerOptions.LogDirectory = logFolder; + } + + if (string.IsNullOrWhiteSpace(settings.FileLoggerOptions.FileNamePrefix)) + { + settings.FileLoggerOptions.FileNamePrefix = defaultPrefix ? "EonaCat" : string.Empty; + } + } + + private void SetupLogManager() + { + AppDomain.CurrentDomain.ProcessExit += ProcessExit; + _logDate = CurrentDateTime; + } + + private void ProcessExit(object sender, EventArgs e) + { + Dispose(); + } + + private Task StopLoggingAsync() + { + WriteStopMessage(); + IsRunning = false; + return Task.CompletedTask; + } + + private void WriteStopMessage() + { + var stopMessage = $"{DllInfo.ApplicationName} stopped.{Environment.NewLine}"; + LogHelper.SendToFile(Logger, Settings, ELogType.INFO, stopMessage); + } + + public void DeleteCurrentLogFile() + { + if (!string.IsNullOrEmpty(CurrentLogFile)) + { + File.Delete(CurrentLogFile); + } + } + } } \ No newline at end of file From b18f2916296177e03fc3c3c7b3187a3e85a93125 Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Thu, 13 Feb 2025 15:59:51 +0100 Subject: [PATCH 4/4] Updated --- EonaCat.Logger/EonaCat.Logger.csproj | 4 +- EonaCat.Logger/GrayLog/GrayLogServer.cs | 6 +- EonaCat.Logger/Managers/LogHelper.cs | 239 ++++++++++++++++-------- EonaCat.Logger/Syslog/SyslogServer.cs | 6 +- 4 files changed, 173 insertions(+), 82 deletions(-) diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj index c1921d8..360b06a 100644 --- a/EonaCat.Logger/EonaCat.Logger.csproj +++ b/EonaCat.Logger/EonaCat.Logger.csproj @@ -3,7 +3,7 @@ .netstandard2.1; net6.0; net7.0; net8.0; net4.8; icon.ico latest - 1.3.4 + 1.3.5 EonaCat (Jeroen Saey) true EonaCat (Jeroen Saey) @@ -24,7 +24,7 @@ - 1.3.4+{chash:10}.{c:ymd} + 1.3.5+{chash:10}.{c:ymd} true true v[0-9]* diff --git a/EonaCat.Logger/GrayLog/GrayLogServer.cs b/EonaCat.Logger/GrayLog/GrayLogServer.cs index 697a42c..3f134ce 100644 --- a/EonaCat.Logger/GrayLog/GrayLogServer.cs +++ b/EonaCat.Logger/GrayLog/GrayLogServer.cs @@ -75,8 +75,10 @@ public class GrayLogServer /// /// IP:port of the server. /// - public string IpPort => _Hostname + ":" + _Port; - + public string IpPort => _Hostname + ":" + _Port; + + public bool SupportsTcp { get; set; } + private void SetUdp() { Udp = null; diff --git a/EonaCat.Logger/Managers/LogHelper.cs b/EonaCat.Logger/Managers/LogHelper.cs index 8cbced9..fa23b5d 100644 --- a/EonaCat.Logger/Managers/LogHelper.cs +++ b/EonaCat.Logger/Managers/LogHelper.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Sockets; using System.Text; using System.Threading.Tasks; using EonaCat.Json; @@ -144,33 +145,36 @@ internal static class LogHelper public static async Task SendToSplunkServersAsync(LoggerSettings settings, SplunkPayload splunkPayload, bool sendToSplunkServer) { - if (settings == null || !sendToSplunkServer || splunkPayload == null) return; - - foreach (var splunkServer in settings.SplunkServers ?? Enumerable.Empty()) + if (settings == null || !sendToSplunkServer || splunkPayload == null) { - if (!splunkServer.HasHecUrl || !splunkServer.HasHecToken) - { - OnException?.Invoke(null, new ErrorMessage { Message = $"Invalid Splunk server configuration for '{splunkServer.SplunkHecUrl}'" }); - continue; - } - - try - { - var response = await splunkServer.SendAsync(splunkPayload); - if (!response.IsSuccessStatusCode) - { - OnException?.Invoke(null, new ErrorMessage { Message = $"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}" }); - } - } - catch (Exception ex) - { - OnException?.Invoke(null, new ErrorMessage { Exception = ex, Message = $"Error logging to Splunk '{splunkServer.SplunkHecUrl}': {ex.Message}" }); - } + return; } + + var tasks = settings.SplunkServers? + .Where(splunkServer => splunkServer.HasHecUrl && splunkServer.HasHecToken) + .Select(async splunkServer => + { + try + { + var response = await splunkServer.SendAsync(splunkPayload); + if (!response.IsSuccessStatusCode) + { + LogError($"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}"); + } + } + catch (Exception ex) + { + LogError($"Error logging to Splunk '{splunkServer.SplunkHecUrl}': {ex.Message}", ex); + } + }) ?? new List(); + + await Task.WhenAll(tasks); } - public static async Task SendToSplunkServersAsync(LoggerSettings settings, string logType, string message, - bool sendToSplunkServer) + /// + /// Overload for sending a simple log message to Splunk. + /// + public static async Task SendToSplunkServersAsync(LoggerSettings settings, string logType, string message, bool sendToSplunkServer) { if (settings == null || !sendToSplunkServer || string.IsNullOrWhiteSpace(message)) { @@ -187,86 +191,169 @@ internal static class LogHelper await SendToSplunkServersAsync(settings, splunkPayload, sendToSplunkServer); } + /// + /// Logs an error using the OnException event. + /// + private static void LogError(string message, Exception ex = null) + { + OnException?.Invoke(null, new ErrorMessage { Message = message, Exception = ex }); + } + + internal static async Task SendToGrayLogServersAsync(LoggerSettings settings, string message, ELogType logLevel, - string facility, string source, bool sendToGrayLogServer, string version = "1.1") + string facility, string source, bool sendToGrayLogServer, string version = "1.1") { if (settings == null || !sendToGrayLogServer || string.IsNullOrWhiteSpace(message)) { return; } - foreach (var grayLogServer in settings.GrayLogServers ?? new List { new("127.0.0.1") }) - { - try - { - var gelfMessage = new - { - version, - host = MachineName, - short_message = message, - level = logLevel.ToGrayLogLevel(), - facility, - source, - timestamp = DateTime.UtcNow.ToUnixTimestamp() - }; + const int MaxUdpPacketSize = 4096; - var messageBytes = Encoding.UTF8.GetBytes(JsonHelper.ToJson(gelfMessage)); - await grayLogServer.Udp.SendAsync(messageBytes, messageBytes.Length); - } - catch (Exception exception) + var gelfMessage = new + { + version, + host = MachineName, + short_message = message, + level = logLevel.ToGrayLogLevel(), + facility, + source, + timestamp = DateTime.UtcNow.ToUnixTimestamp() + }; + + var messageBytes = Encoding.UTF8.GetBytes(JsonHelper.ToJson(gelfMessage)); + + var tasks = settings.GrayLogServers? + .Where(server => !string.IsNullOrWhiteSpace(server.Hostname) && server.Port >= 0) + .Select(async grayLogServer => { - OnException?.Invoke(null, - new ErrorMessage + try + { + if (messageBytes.Length <= MaxUdpPacketSize) { - Exception = exception, - Message = - $"Error while logging to GrayLog Server '{grayLogServer.Hostname}': {exception.Message}" + // Send via UDP (single packet) + await grayLogServer.Udp.SendAsync(messageBytes, messageBytes.Length); + } + else if (grayLogServer.SupportsTcp) + { + // Send via TCP if supported + await SendViaTcpAsync(grayLogServer, messageBytes); + } + else + { + // Chunk large messages for UDP + await SendUdpInChunksAsync(grayLogServer, messageBytes, MaxUdpPacketSize); + } + } + catch (Exception ex) + { + OnException?.Invoke(null, new ErrorMessage + { + Exception = ex, + Message = $"Error logging to GrayLog Server '{grayLogServer.Hostname}': {ex.Message}" }); - } + } + }) ?? new List(); + + await Task.WhenAll(tasks); + } + + + /// + /// Sends a message via TCP to a GrayLog server. + /// + private static async Task SendViaTcpAsync(GrayLogServer server, byte[] data) + { + using var tcpClient = new TcpClient(); + await tcpClient.ConnectAsync(server.Hostname, server.Port); + using var stream = tcpClient.GetStream(); + await stream.WriteAsync(data, 0, data.Length); + await stream.FlushAsync(); + } + + /// + /// Sends large messages in chunks over UDP. + /// + private static async Task SendUdpInChunksAsync(GrayLogServer server, byte[] data, int chunkSize) + { + for (int i = 0; i < data.Length; i += chunkSize) + { + var chunk = data.Skip(i).Take(chunkSize).ToArray(); + await server.Udp.SendAsync(chunk, chunk.Length); } } - internal static async Task SendToSysLogServersAsync(LoggerSettings settings, string message, - bool sendToSyslogServers) + internal static async Task SendToSysLogServersAsync(LoggerSettings settings, string message, bool sendToSyslogServers) { if (settings == null || !sendToSyslogServers || string.IsNullOrWhiteSpace(message)) { return; } - foreach (var server in settings.SysLogServers ?? new List { new("127.0.0.1") }) - { - try - { - if (string.IsNullOrWhiteSpace(server.Hostname)) - { - OnException?.Invoke(null, - new ErrorMessage { Message = "Server hostname not specified, skipping SysLog Server" }); - continue; - } + const int MaxUdpPacketSize = 4096; - if (server.Port < 0) - { - OnException?.Invoke(null, - new ErrorMessage { Message = "Server port must be zero or greater, skipping SysLog Server" }); - continue; - } - - var data = Encoding.UTF8.GetBytes(message); - await server.Udp.SendAsync(data, data.Length); - } - catch (Exception exception) + var tasks = settings.SysLogServers? + .Where(server => !string.IsNullOrWhiteSpace(server.Hostname) && server.Port >= 0) + .Select(async server => { - OnException?.Invoke(null, - new ErrorMessage + try + { + var data = Encoding.UTF8.GetBytes(message); + + if (data.Length <= MaxUdpPacketSize) { - Exception = exception, - Message = $"Error while logging to SysLog Server '{server.Hostname}': {exception.Message}" + // Send via UDP (single packet) + await server.Udp.SendAsync(data, data.Length); + } + else if (server.SupportsTcp) + { + // Send via TCP if supported + await SendViaTcpAsync(server, data); + } + else + { + // Chunk large messages for UDP + await SendUdpInChunksAsync(server, data, MaxUdpPacketSize); + } + } + catch (Exception ex) + { + OnException?.Invoke(null, new ErrorMessage + { + Exception = ex, + Message = $"Error logging to SysLog Server '{server.Hostname}': {ex.Message}" }); - } + } + }) ?? new List(); + + await Task.WhenAll(tasks); + } + + /// + /// Sends a message via TCP to a syslog server. + /// + private static async Task SendViaTcpAsync(SyslogServer server, byte[] data) + { + using var tcpClient = new TcpClient(); + await tcpClient.ConnectAsync(server.Hostname, server.Port); + using var stream = tcpClient.GetStream(); + await stream.WriteAsync(data, 0, data.Length); + await stream.FlushAsync(); + } + + /// + /// Sends large messages in chunks over UDP. + /// + private static async Task SendUdpInChunksAsync(SyslogServer server, byte[] data, int chunkSize) + { + for (int i = 0; i < data.Length; i += chunkSize) + { + var chunk = data.Skip(i).Take(chunkSize).ToArray(); + await server.Udp.SendAsync(chunk, chunk.Length); } } + internal static string GetStartupMessage() { return $"{DllInfo.ApplicationName} started."; diff --git a/EonaCat.Logger/Syslog/SyslogServer.cs b/EonaCat.Logger/Syslog/SyslogServer.cs index e725a9c..824ff1b 100644 --- a/EonaCat.Logger/Syslog/SyslogServer.cs +++ b/EonaCat.Logger/Syslog/SyslogServer.cs @@ -75,8 +75,10 @@ public class SyslogServer /// /// IP:port of the server. /// - public string IpPort => _Hostname + ":" + _Port; - + public string IpPort => _Hostname + ":" + _Port; + + public bool SupportsTcp { get; set; } + private void SetUdp() { Udp = null;