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;