diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj
index 0f884e9..8f17c31 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.5
EonaCat (Jeroen Saey)
true
EonaCat (Jeroen Saey)
@@ -24,7 +24,7 @@
- 1.3.2+{chash:10}.{c:ymd}
+ 1.3.5+{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/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 8393f25..fa23b5d 100644
--- a/EonaCat.Logger/Managers/LogHelper.cs
+++ b/EonaCat.Logger/Managers/LogHelper.cs
@@ -1,306 +1,366 @@
-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 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);
- }
- }
-
- private static bool IsLogLevelEnabled(LoggerSettings settings, ELogType logType)
- {
- return settings.MaxLogType != ELogType.NONE && logType <= settings.MaxLogType;
- }
-
- 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.";
- }
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+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;
+
+ 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;
+
+ 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}", $"{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]"))
+ {
+ 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;
+
+ if (TryGetLogColor(settings.Colors, logType, out var foregroundColor, out var backgroundColor))
+ {
+ Console.ForegroundColor = foregroundColor;
+ Console.BackgroundColor = backgroundColor;
+ }
+
+ Console.WriteLine(message);
+ Console.ForegroundColor = prevForeground;
+ Console.BackgroundColor = prevBackground;
+ }
+ else
+ {
+ Console.WriteLine(message);
+ }
+ }
+
+ private static bool TryGetLogColor(ColorSchema colors, ELogType logType, out ConsoleColor foreground, out ConsoleColor background)
+ {
+ foreground = default;
+ background = default;
+
+ return logType switch
+ {
+ 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)
+ {
+ OnLogLevelDisabled?.Invoke(null, new ErrorMessage { Message = "Settings is null." });
+ return false;
+ }
+
+ var isEnabled = loggerSettings.MaxLogType != ELogType.NONE && logType <= loggerSettings.MaxLogType;
+ if (!isEnabled)
+ {
+ OnLogLevelDisabled?.Invoke(null, new ErrorMessage { Message = $"Logtype '{logType}' is not enabled." });
+ }
+
+ 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;
+ }
+
+ 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);
+ }
+
+ ///
+ /// 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))
+ {
+ return;
+ }
+
+ var splunkPayload = new SplunkPayload
+ {
+ Host = Environment.MachineName,
+ EventData = message,
+ SourceType = logType
+ };
+
+ 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")
+ {
+ if (settings == null || !sendToGrayLogServer || string.IsNullOrWhiteSpace(message))
+ {
+ return;
+ }
+
+ const int MaxUdpPacketSize = 4096;
+
+ 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 =>
+ {
+ try
+ {
+ if (messageBytes.Length <= MaxUdpPacketSize)
+ {
+ // 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)
+ {
+ if (settings == null || !sendToSyslogServers || string.IsNullOrWhiteSpace(message))
+ {
+ return;
+ }
+
+ const int MaxUdpPacketSize = 4096;
+
+ var tasks = settings.SysLogServers?
+ .Where(server => !string.IsNullOrWhiteSpace(server.Hostname) && server.Port >= 0)
+ .Select(async server =>
+ {
+ try
+ {
+ var data = Encoding.UTF8.GetBytes(message);
+
+ if (data.Length <= MaxUdpPacketSize)
+ {
+ // 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.";
+ }
+
+ internal static string GetStopMessage()
+ {
+ return $"{DllInfo.ApplicationName} stopped.";
+ }
}
\ No newline at end of file
diff --git a/EonaCat.Logger/Managers/LogManager.cs b/EonaCat.Logger/Managers/LogManager.cs
index 3590fb4..b953922 100644
--- a/EonaCat.Logger/Managers/LogManager.cs
+++ b/EonaCat.Logger/Managers/LogManager.cs
@@ -1,324 +1,344 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-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;
- }
-
- 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);
- }
-
- public event EventHandler OnException;
-
- 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 ||
- (int)logType > (int)Settings.MaxLogType)
- {
- 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 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);
- }
- }
- }
+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;
+ }
+
+ 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);
+ }
+
+ ///
+ /// 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
+ {
+ 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)
+ {
+ 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();
+ }
+
+ 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
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;
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)