diff --git a/EonaCat.Logger/Enums.cs b/EonaCat.Logger/Enums.cs index cfe343a..2c159ab 100644 --- a/EonaCat.Logger/Enums.cs +++ b/EonaCat.Logger/Enums.cs @@ -67,6 +67,31 @@ namespace EonaCat.Logger } } + public static string ToString(this ELogType logLevel) + { + switch (logLevel) + { + case ELogType.NONE: + return "NONE"; + case ELogType.ERROR: + return "ERROR"; + case ELogType.DEBUG: + return "DEBUG"; + case ELogType.CRITICAL: + return "CRITICAL"; + case ELogType.WARNING: + return "WARNING"; + case ELogType.TRACE: + return "TRACE"; + case ELogType.TRAFFIC: + return "TRAFFIC"; + case ELogType.INFO: + return "INFO"; + default: + return "INFO"; + } + } + public static ELogType FromSeverity(this ESeverity logLevel) { switch (logLevel) @@ -88,6 +113,22 @@ namespace EonaCat.Logger } } + public static int ToGrayLogLevel(this ELogType logLevel) + { + // Loglevel to GELF format + switch (logLevel.ToString()) + { + case "TRAFFIC": return 7; + case "TRACE": return 7; + case "DEBUG": return 7; + case "INFO": return 6; + case "WARNING": return 4; + case "ERROR": return 3; + case "CRITICAL": return 2; + default: return 6; // Default to INFO + } + } + public static ESeverity ToSeverity(this ELogType logLevel) { switch (logLevel) diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj index e0a6d53..41da821 100644 --- a/EonaCat.Logger/EonaCat.Logger.csproj +++ b/EonaCat.Logger/EonaCat.Logger.csproj @@ -7,7 +7,7 @@ net7.0; icon.ico - 1.2.0 + 1.2.1 EonaCat (Jeroen Saey) true EonaCat (Jeroen Saey) @@ -43,6 +43,7 @@ + diff --git a/EonaCat.Logger/Extensions/DateTimeExtensions.cs b/EonaCat.Logger/Extensions/DateTimeExtensions.cs new file mode 100644 index 0000000..f23fcfe --- /dev/null +++ b/EonaCat.Logger/Extensions/DateTimeExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace EonaCat.Logger.Extensions +{ + public static class DateTimeExtensions + { + public static long ToUnixTimestamp(this DateTime dateTime) + { + return (long)(dateTime.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; + } + } +} diff --git a/EonaCat.Logger/GrayLog/GrayLogServer.cs b/EonaCat.Logger/GrayLog/GrayLogServer.cs new file mode 100644 index 0000000..6f63a74 --- /dev/null +++ b/EonaCat.Logger/GrayLog/GrayLogServer.cs @@ -0,0 +1,91 @@ +using System; +using System.Net.Sockets; + +namespace EonaCat.Logger.GrayLog +{ + // 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. + + /// + /// Syslog server. + /// + public class GrayLogServer + { + /// + /// Hostname. + /// + public string Hostname + { + get + { + return _Hostname; + } + + set + { + if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(Hostname)); + _Hostname = value; + + SetUdp(); + } + } + + /// + /// UDP port. + /// + public int Port + { + get + { + return _Port; + } + set + { + if (value < 0) throw new ArgumentException("Port must be zero or greater."); + _Port = value; + + SetUdp(); + } + } + + /// + /// IP:port of the server. + /// + public string IpPort + { + get + { + return _Hostname + ":" + _Port; + } + } + + internal readonly object SendLock = new object(); + internal UdpClient Udp = null; + private string _Hostname = "127.0.0.1"; + private int _Port = 12201; + + /// + /// Instantiate the object. + /// + public GrayLogServer() + { + } + + /// + /// Instantiate the object. + /// + /// Hostname. + /// Port. + public GrayLogServer(string hostname = "127.0.0.1", int port = 12201) + { + Hostname = hostname; + Port = port; + } + + private void SetUdp() + { + Udp = null; + Udp = new UdpClient(_Hostname, _Port); + } + } +} diff --git a/EonaCat.Logger/Managers/ColorSchema.cs b/EonaCat.Logger/Managers/ColorSchema.cs index f40c19e..9e7b1b0 100644 --- a/EonaCat.Logger/Managers/ColorSchema.cs +++ b/EonaCat.Logger/Managers/ColorSchema.cs @@ -2,6 +2,9 @@ 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. + /// /// Colors to use when writing to the console. /// diff --git a/EonaCat.Logger/Managers/ILogManager.cs b/EonaCat.Logger/Managers/ILogManager.cs index 6427feb..bff79b7 100644 --- a/EonaCat.Logger/Managers/ILogManager.cs +++ b/EonaCat.Logger/Managers/ILogManager.cs @@ -3,11 +3,14 @@ using System.Threading.Tasks; namespace EonaCat.Logger.Managers { + // This file is part of the EonaCat project(s) which is released under the Apache License. + // See the LICENSE file or go to https://EonaCat.com/License for full license details. + public interface ILogManager { - void Write(string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null); + void Write(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"); void Write(Exception exception, string module = null, string method = null, bool criticalException = false, - bool? writeToConsole = null); + 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"); } } \ No newline at end of file diff --git a/EonaCat.Logger/Managers/LogHelper.cs b/EonaCat.Logger/Managers/LogHelper.cs index 59f60c9..3fd3901 100644 --- a/EonaCat.Logger/Managers/LogHelper.cs +++ b/EonaCat.Logger/Managers/LogHelper.cs @@ -2,10 +2,18 @@ using System.Collections.Generic; using System.Linq; using System.Net; +using System.Net.Http; using System.Text; +using System.Threading.Tasks; +using EonaCat.Json; +using EonaCat.Logger.Extensions; +using EonaCat.Logger.GrayLog; 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 { internal static class LogHelper @@ -43,7 +51,7 @@ namespace EonaCat.Logger.Managers return sb.ToString(); } - + internal static void SendToConsole(LoggerSettings settings, ELogType logType, string message, bool writeToConsole) { @@ -119,9 +127,104 @@ namespace EonaCat.Logger.Managers logger.Log(logLevel, message); } - internal static void SendToSysLogServers(LoggerSettings settings, string message) + public static async Task SendToSplunkServersAsync(LoggerSettings settings, string logType, string message, bool sendToSplunkServer) { - if (settings == null || !settings.SendToSyslogServers || string.IsNullOrWhiteSpace(message)) + if (settings == null || !sendToSplunkServer || string.IsNullOrWhiteSpace(message)) + return; + + if (settings.SplunkServers == null) + { + settings.SplunkServers = new List(); + } + + foreach (var splunkServer in settings.SplunkServers) + { + if (!splunkServer.HasHecUrl) + { + Console.WriteLine("Splunk server HecUrl not specified, skipping splunkServer"); + continue; + } + + if (!splunkServer.HasHecToken) + { + Console.WriteLine($"Splunk server HecToken not specified, skipping splunkServer '{splunkServer.SplunkHecUrl}'"); + continue; + } + + await Task.Run(async () => + { + try + { + using (var httpClient = new HttpClient()) + { + var payload = new + { + log_message = message, + sourcetype = logType + }; + + var jsonPayload = JsonHelper.ToJson(payload); + var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json"); + + httpClient.DefaultRequestHeaders.Add("Authorization", $"Splunk {splunkServer.SplunkHecToken}"); + + var response = await httpClient.PostAsync(splunkServer.SplunkHecUrl, content); + + if (!response.IsSuccessStatusCode) + { + Console.WriteLine($"Failed to send log to Splunk '{splunkServer.SplunkHecUrl}'. Status code: {response.StatusCode}"); + } + } + } + catch (Exception exception) + { + Console.WriteLine($"Error while logging to Splunk server '{splunkServer.SplunkHecUrl}': {exception.Message}"); + } + }); + } + } + + 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; + + if (settings.GrayLogServers == null || !settings.GrayLogServers.Any()) + { + settings.GrayLogServers = new List { new GrayLogServer("127.0.0.1", 12201) }; + } + + foreach (var grayLogServer in settings.GrayLogServers) + { + await Task.Run(async () => + { + 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, new IPEndPoint(IPAddress.Parse(grayLogServer.Hostname), grayLogServer.Port)); + } + catch (Exception exception) + { + Console.WriteLine($"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; if (settings.SysLogServers == null || !settings.SysLogServers.Any()) @@ -133,28 +236,36 @@ namespace EonaCat.Logger.Managers foreach (SyslogServer server in settings.SysLogServers) { - if (string.IsNullOrWhiteSpace(server.Hostname)) - { - Console.WriteLine("Server hostname not specified, skipping syslog server"); - continue; - } - if (server.Port < 0) - { - Console.WriteLine("Server port must be zero or greater, skipping syslog server"); - continue; - } - - lock (server.SendLock) + await Task.Run(() => { try { - server.Udp.Send(data, data.Length); + if (string.IsNullOrWhiteSpace(server.Hostname)) + { + Console.WriteLine("Server hostname not specified, skipping SysLog Server"); + return; + } + + if (server.Port < 0) + { + Console.WriteLine("Server port must be zero or greater, skipping SysLog Server"); + return; + } + + try + { + server.Udp.Send(data, data.Length); + } + catch (Exception exception) + { + Console.WriteLine($"Error while logging to SysLog Server '{server.Hostname}': {exception.Message}"); + } } - catch + catch (Exception exception) { - // Do nothing + Console.WriteLine($"Error while logging to SysLog Server '{server.Hostname}': {exception.Message}"); } - } + }); } } } diff --git a/EonaCat.Logger/Managers/LogManager.cs b/EonaCat.Logger/Managers/LogManager.cs index c614b95..be506ac 100644 --- a/EonaCat.Logger/Managers/LogManager.cs +++ b/EonaCat.Logger/Managers/LogManager.cs @@ -13,6 +13,9 @@ using EonaCat.Logger.Extensions; 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 partial class LogManager : ILogManager, IDisposable { private DateTime CurrentDateTme => Settings.UseLocalTime ? DateTime.Now : DateTime.UtcNow; @@ -98,7 +101,7 @@ namespace EonaCat.Logger.Managers Logger = LoggerFactory.CreateLogger(Settings.Id); } - private void InternalWriteAsync(DateTime dateTime, string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null) + private void 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; @@ -113,7 +116,32 @@ namespace EonaCat.Logger.Managers Task.Run(() => { LogHelper.SendToFile(Logger, Settings, logType, message); }); - Task.Run(() => { LogHelper.SendToSysLogServers(Settings, messageWithHeader); }); + sendToSyslogServers ??= Settings.SendToSyslogServers; + if (sendToSyslogServers.Value) + { + Task.Run(async () => + { + await LogHelper.SendToSysLogServersAsync(Settings, messageWithHeader, true); + }); + } + + sendToSplunkServers ??= Settings.SendToSplunkServers; + if (sendToSplunkServers.Value) + { + Task.Run(async () => + { + await LogHelper.SendToSplunkServersAsync(Settings, customSplunkSourceType ?? logType.ToString(), messageWithHeader, true); + }); + } + + sendToGrayLogServers ??= Settings.SendToGrayLogServers; + if (sendToGrayLogServers.Value) + { + Task.Run(async () => + { + await LogHelper.SendToGrayLogServersAsync(Settings, messageWithHeader, logType, grayLogFacility, grayLogSource, true, grayLogVersion); + }); + } var logMessage = new EonaCatLogMessage { @@ -203,15 +231,15 @@ namespace EonaCat.Logger.Managers GC.SuppressFinalize(this); } - public void Write(Exception exception, string module = null, string method = null, bool criticalException = false, bool? writeToConsole = null) + public void Write(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; - Write(exception.FormatExceptionToMessage(module, method), criticalException ? ELogType.CRITICAL : ELogType.ERROR, writeToConsole); + Write(exception.FormatExceptionToMessage(module, method), criticalException ? ELogType.CRITICAL : ELogType.ERROR, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion); } - public void Write(string message, ELogType logType = ELogType.INFO, bool? writeToConsole = null) + public void Write(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; @@ -219,7 +247,7 @@ namespace EonaCat.Logger.Managers if (!IsRunning) StartNewLogAsync().ConfigureAwait(false); - InternalWriteAsync(CurrentDateTme, message, logType, writeToConsole); + InternalWriteAsync(CurrentDateTme, message, logType, writeToConsole, sendToSysLogServers, sendToSplunkServers, customSplunkSourceType, sendToGrayLogServers, grayLogFacility, grayLogSource, grayLogVersion); } } } diff --git a/EonaCat.Logger/Managers/LoggerSettings.cs b/EonaCat.Logger/Managers/LoggerSettings.cs index ce36a74..12e3ff7 100644 --- a/EonaCat.Logger/Managers/LoggerSettings.cs +++ b/EonaCat.Logger/Managers/LoggerSettings.cs @@ -2,10 +2,14 @@ using System.Collections.Generic; using EonaCat.Logger.EonaCatCoreLogger; using EonaCat.Logger.EonaCatCoreLogger.Models; +using EonaCat.Logger.GrayLog; using EonaCat.Logger.Syslog; 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. + /// /// Logger settings. /// @@ -113,6 +117,14 @@ namespace EonaCat.Logger.Managers public List SysLogServers { get; set; } + public bool SendToSplunkServers { get; set; } + + public bool SendToGrayLogServers { get; set; } + + public List SplunkServers { get; set; } + + public List GrayLogServers { get; set; } + /// /// Determines if the fileLogging is enabled /// diff --git a/EonaCat.Logger/Splunk/SplunkServer.cs b/EonaCat.Logger/Splunk/SplunkServer.cs new file mode 100644 index 0000000..6be8765 --- /dev/null +++ b/EonaCat.Logger/Splunk/SplunkServer.cs @@ -0,0 +1,79 @@ +using System; + +namespace EonaCat.Logger.SplunkServer +{ + // 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. + + /// + /// Splunk Server. + /// + public class SplunkServer + { + /// + /// SplunkHecUrl + /// + public string SplunkHecUrl + { + get + { + return _splunkHecUrl; + } + + set + { + if (!string.IsNullOrWhiteSpace(_splunkHecUrl) && !_splunkHecUrl.ToLower().Contains("http")) + { + value = $"http://{value}"; + } + _splunkHecUrl = value; + } + } + + public bool IsHttpsHecUrl => HasHecUrl && _splunkHecUrl.ToLower().StartsWith("https"); + + /// + /// SplunkHecToken + /// + public string SplunkHecToken + { + get + { + return _splunkHecToken; + } + set + { + _splunkHecToken = value; + } + } + + internal readonly object SendLock = new object(); + private string _splunkHecUrl = "https://127.0.0.1:8088"; + private string _splunkHecToken = "splunk-hec-token"; + private string _hostName; + + public bool HasHecToken => !string.IsNullOrWhiteSpace(SplunkHecToken) && SplunkHecToken != "splunk-hec-token"; + + public bool HasHecUrl => !string.IsNullOrWhiteSpace(SplunkHecUrl); + + public bool IsLocalHost => HasHecUrl && (SplunkHecUrl.ToLower().Contains("127.0.0.1") || SplunkHecUrl.ToLower().Contains("localhost")); + + /// + /// Instantiate the object. + /// + public SplunkServer() + { + } + + /// + /// Instantiate the object. + /// + /// splunkHecUrl. + /// splunkHecToken. + public SplunkServer(string splunkHecUrl, string splunkHecToken) + { + SplunkHecUrl = splunkHecUrl; + SplunkHecToken = splunkHecToken; + } + } +} diff --git a/EonaCat.Logger/Syslog/SyslogServer.cs b/EonaCat.Logger/Syslog/SyslogServer.cs index f682932..cf15696 100644 --- a/EonaCat.Logger/Syslog/SyslogServer.cs +++ b/EonaCat.Logger/Syslog/SyslogServer.cs @@ -5,6 +5,9 @@ using System.Text; namespace EonaCat.Logger.Syslog { + // 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. + /// /// Syslog server. /// @@ -58,7 +61,6 @@ namespace EonaCat.Logger.Syslog } } - internal readonly object SendLock = new object(); internal UdpClient Udp = null; private string _Hostname = "127.0.0.1"; private int _Port = 514;