diff --git a/EonaCat.Logger/Servers/GrayLog/Graylog.cs b/EonaCat.Logger/Servers/GrayLog/Graylog.cs
index 39b5b76..9673a92 100644
--- a/EonaCat.Logger/Servers/GrayLog/Graylog.cs
+++ b/EonaCat.Logger/Servers/GrayLog/Graylog.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Net.Sockets;
namespace EonaCat.Logger.Servers.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.
diff --git a/EonaCat.Logger/Servers/Splunk/Splunk.cs b/EonaCat.Logger/Servers/Splunk/Splunk.cs
index 8e332d4..297c3a0 100644
--- a/EonaCat.Logger/Servers/Splunk/Splunk.cs
+++ b/EonaCat.Logger/Servers/Splunk/Splunk.cs
@@ -1,155 +1,159 @@
-using EonaCat.Json;
-
-namespace EonaCat.Logger.Servers.Splunk;
-
-using EonaCat.Logger.Servers.Splunk.Models;
-// 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.
-///
-using System;
+using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
+using EonaCat.Json;
+using EonaCat.Logger.Servers.Splunk.Models;
-public class Splunk : IDisposable
+namespace EonaCat.Logger.Servers.Splunk
{
- internal readonly object SendLock = new();
- private string _splunkHecUrl = "https://127.0.0.1:8088/services/collector/event";
-
- public string SplunkHecToken { get; set; } = "splunk-hec-token";
- public HttpClientHandler SplunkClientHandler { get; private set; }
- public HttpClient HttpClient { get; private set; }
- public string Nickname { get; set; }
-
- public bool HasHecToken => !string.IsNullOrWhiteSpace(SplunkHecToken) && SplunkHecToken != "splunk-hec-token";
- public bool HasHecUrl => !string.IsNullOrWhiteSpace(SplunkHecUrl);
- public bool IsHttpsHecUrl => HasHecUrl && _splunkHecUrl.ToLower().StartsWith("https");
- public bool IsLocalHost => HasHecUrl && (_splunkHecUrl.ToLower().Contains("127.0.0.1") || _splunkHecUrl.ToLower().Contains("localhost"));
-
///
- /// Splunk Server
+ /// Splunk Server.
///
- ///
- ///
- ///
- ///
- ///
- ///
- public Splunk(string splunkHecUrl, string splunkHecToken, HttpClientHandler httpClientHandler = null, string nickName = null, List typesToLog = null)
+ public class Splunk : IDisposable
{
- SplunkHecUrl = splunkHecUrl ?? throw new ArgumentNullException(nameof(splunkHecUrl));
- SplunkHecToken = splunkHecToken ?? throw new ArgumentNullException(nameof(splunkHecToken));
+ private string _splunkHecUrl = "https://127.0.0.1:8088/services/collector/event";
+ private HttpClient _httpClient;
+ private HttpClientHandler _httpClientHandler;
+ public event EventHandler OnException;
- Nickname = nickName ?? $"{splunkHecUrl}_{splunkHecToken}";
- SplunkClientHandler = httpClientHandler ?? new HttpClientHandler();
- TypesToLog = typesToLog;
+ public string SplunkHecToken { get; set; } = "splunk-hec-token";
+ public string Nickname { get; set; }
+ public List TypesToLog { get; set; }
- CreateHttpClient();
- }
-
- public string SplunkHecUrl
- {
- get => _splunkHecUrl;
- set
+ public string SplunkHecUrl
{
- if (!string.IsNullOrWhiteSpace(value) && !value.ToLower().Contains("http"))
+ get => _splunkHecUrl;
+ set
{
- value = $"http://{value}";
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentNullException(nameof(SplunkHecUrl));
+
+ _splunkHecUrl = value.StartsWith("http", StringComparison.OrdinalIgnoreCase)
+ ? value
+ : $"http://{value}";
+
+ RecreateHttpClient();
}
-
- _splunkHecUrl = value;
- CreateHttpClient();
- }
- }
-
- public List TypesToLog { get; set; }
-
- private void CreateHttpClient()
- {
- DisposeHttpClient();
-
- SplunkClientHandler ??= new HttpClientHandler();
- HttpClient = new HttpClient(SplunkClientHandler)
- {
- BaseAddress = new Uri(SplunkHecUrl)
- };
-
- HttpClient.DefaultRequestHeaders.Add("Authorization", $"Splunk {SplunkHecToken}");
- }
-
- public void DisableSSLValidation()
- {
- DisposeHttpClient();
-
- SplunkClientHandler = new HttpClientHandler
- {
- ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
- };
-
- CreateHttpClient();
- }
-
- public async Task SendAsync(SplunkPayload splunkPayload, bool disableSplunkSSL = false)
- {
- if (splunkPayload == null)
- {
- return null;
}
- var eventObject = new
- {
- @event = splunkPayload.EventData,
- sourcetype = splunkPayload.SourceType,
- host = splunkPayload.Host
- };
+ public bool HasHecToken => !string.IsNullOrWhiteSpace(SplunkHecToken) && SplunkHecToken != "splunk-hec-token";
+ public bool HasHecUrl => !string.IsNullOrWhiteSpace(_splunkHecUrl);
+ public bool IsHttpsHecUrl => _splunkHecUrl.StartsWith("https", StringComparison.OrdinalIgnoreCase);
+ public bool IsLocalHost => _splunkHecUrl.ToLower().Contains("127.0.0.1") || _splunkHecUrl.ToLower().Contains("localhost");
- if (disableSplunkSSL)
- {
- DisableSSLValidation();
- }
-
- if (HttpClient == null)
+ public Splunk(string splunkHecUrl, string splunkHecToken, HttpClientHandler handler = null, string nickName = null, List typesToLog = null)
{
+ SplunkHecToken = splunkHecToken ?? throw new ArgumentNullException(nameof(splunkHecToken));
+ SplunkHecUrl = splunkHecUrl ?? throw new ArgumentNullException(nameof(splunkHecUrl));
+ Nickname = nickName ?? $"{splunkHecUrl}_{splunkHecToken}";
+ TypesToLog = typesToLog;
+ _httpClientHandler = handler ?? new HttpClientHandler();
CreateHttpClient();
}
- var eventJson = JsonHelper.ToJson(eventObject);
- if (!HasHecToken)
+ private void CreateHttpClient()
{
+ DisposeHttpClient();
+
+ _httpClientHandler ??= new HttpClientHandler();
+ _httpClient = new HttpClient(_httpClientHandler)
+ {
+ BaseAddress = new Uri(SplunkHecUrl)
+ };
+
+ if (!_httpClient.DefaultRequestHeaders.Contains("Authorization"))
+ {
+ _httpClient.DefaultRequestHeaders.Add("Authorization", $"Splunk {SplunkHecToken}");
+ }
+ }
+
+ private void RecreateHttpClient()
+ {
+ DisposeHttpClient();
+ _httpClientHandler = new HttpClientHandler();
CreateHttpClient();
}
- using var content = new StringContent(eventJson, Encoding.UTF8, "application/json");
- return await HttpClient.PostAsync("/services/collector/event", content);
- }
-
- internal void DisposeHttpClient()
- {
- if (HttpClient != null)
+ public void DisableSSLValidation()
{
- HttpClient.Dispose();
- HttpClient = null;
+ DisposeHttpClient();
+ _httpClientHandler = new HttpClientHandler
+ {
+ ServerCertificateCustomValidationCallback = (_, _, _, _) => true
+ };
+ CreateHttpClient();
}
- if (SplunkClientHandler != null)
+ public async Task SendAsync(SplunkPayload payload, bool disableSplunkSSL = false)
{
- SplunkClientHandler.Dispose();
- SplunkClientHandler = null;
+ try
+ {
+ if (payload == null)
+ return null;
+
+ if (!HasHecToken || !HasHecUrl)
+ return null;
+
+ if (disableSplunkSSL)
+ DisableSSLValidation();
+
+ _httpClient ??= new HttpClient(_httpClientHandler ?? new HttpClientHandler())
+ {
+ BaseAddress = new Uri(SplunkHecUrl)
+ };
+
+ var eventObject = new
+ {
+ @event = payload.EventData,
+ sourcetype = payload.SourceType,
+ host = payload.Host
+ };
+
+ var json = JsonHelper.ToJson(eventObject);
+
+ var request = new HttpRequestMessage(HttpMethod.Post, "services/collector/event")
+ {
+ Content = new StringContent(json, Encoding.UTF8, "application/json")
+ };
+
+ return await _httpClient.SendAsync(request);
+ }
+ catch (Exception ex)
+ {
+ OnException?.Invoke(this, ex);
+ return null;
+ }
+ }
+
+ internal void DisposeHttpClient()
+ {
+ try
+ {
+ _httpClient?.Dispose();
+ _httpClientHandler?.Dispose();
+ }
+ catch
+ {
+ // Silent fail
+ }
+ finally
+ {
+ _httpClient = null;
+ _httpClientHandler = null;
+ }
+ }
+
+ public void Dispose()
+ {
+ DisposeHttpClient();
+ GC.SuppressFinalize(this);
+ }
+
+ ~Splunk()
+ {
+ Dispose();
}
}
-
- public void Dispose()
- {
- DisposeHttpClient();
- GC.SuppressFinalize(this);
- }
-
- ~Splunk()
- {
- Dispose();
- }
-}
\ No newline at end of file
+}
diff --git a/EonaCat.Logger/Servers/Syslog/Syslog.cs b/EonaCat.Logger/Servers/Syslog/Syslog.cs
index e896071..f29c5e7 100644
--- a/EonaCat.Logger/Servers/Syslog/Syslog.cs
+++ b/EonaCat.Logger/Servers/Syslog/Syslog.cs
@@ -1,194 +1,191 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace EonaCat.Logger.Servers.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.
-///
-public class Syslog : IDisposable
-{
- const int MaxUdpPacketSize = 4096;
- private string _Hostname = "127.0.0.1";
- private int _Port = 514;
- public bool IsConnected { get; private set; }
-
- internal UdpClient Udp;
-
- public Syslog() { }
-
- ///
- /// Syslog server
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- public Syslog(string hostname = "127.0.0.1", int port = 514, string nickName = null, List typesToLog = null, bool convertToRfc5424 = false, bool convertToRfc3164 = false)
- {
- Hostname = hostname;
- Port = port;
- Nickname = nickName ?? IpPort;
- TypesToLog = typesToLog;
- ConvertToRfc5424 = convertToRfc5424;
- ConvertToRfc3164 = convertToRfc3164;
- }
-
- public string Hostname
- {
- get => _Hostname;
- set
- {
- if (string.IsNullOrEmpty(value))
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EonaCat.Logger.Servers.Syslog
+{
+ // This file is part of the EonaCat project(s) released under the Apache License.
+ public class Syslog : IDisposable
+ {
+ private const int MaxUdpPacketSize = 4096;
+ private string _hostname = "127.0.0.1";
+ private int _port = 514;
+ private UdpClient _udp;
+ public event EventHandler OnException;
+
+ public bool IsConnected { get; private set; }
+ public string Nickname { get; set; }
+ public string IpPort => $"{_hostname}:{_port}";
+ public bool SupportsTcp { get; set; }
+ public List TypesToLog { get; set; }
+ public bool ConvertToRfc5424 { get; set; }
+ public bool ConvertToRfc3164 { get; set; }
+
+ public Syslog() { }
+
+ public Syslog(string hostname = "127.0.0.1", int port = 514, string nickName = null, List typesToLog = null, bool convertToRfc5424 = false, bool convertToRfc3164 = false)
+ {
+ Hostname = hostname;
+ Port = port;
+ Nickname = nickName ?? IpPort;
+ TypesToLog = typesToLog;
+ ConvertToRfc5424 = convertToRfc5424;
+ ConvertToRfc3164 = convertToRfc3164;
+ }
+
+ public string Hostname
+ {
+ get => _hostname;
+ set
{
- throw new ArgumentNullException(nameof(Hostname));
+ if (string.IsNullOrWhiteSpace(value))
+ throw new ArgumentNullException(nameof(Hostname));
+
+ _hostname = value;
+ SetUdp();
+ }
+ }
+
+ public int Port
+ {
+ get => _port;
+ set
+ {
+ if (value < 0)
+ throw new ArgumentException("Port must be zero or greater.");
+
+ _port = value;
+ SetUdp();
+ }
+ }
+
+ internal void SetUdp()
+ {
+ try
+ {
+ DisposeUdp();
+ _udp = new UdpClient(_hostname, _port);
+ IsConnected = true;
+ }
+ catch
+ {
+ IsConnected = false;
+ }
+ }
+
+ internal void DisposeUdp()
+ {
+ IsConnected = false;
+
+ try
+ {
+ _udp?.Close();
+ _udp?.Dispose();
+ }
+ catch
+ {
+ // Swallow cleanup exceptions
}
- _Hostname = value;
- SetUdp();
- }
- }
-
- public int Port
- {
- get => _Port;
- set
- {
- if (value < 0)
+ _udp = null;
+ }
+
+ public async Task WriteAsync(string message)
+ {
+ if (string.IsNullOrWhiteSpace(message))
+ return;
+
+ var data = Encoding.UTF8.GetBytes(message);
+ await SendAsync(data);
+ }
+
+ public async Task WriteAsync(byte[] data)
+ {
+ if (data is { Length: > 0 })
+ await SendAsync(data);
+ }
+
+ private async Task SendAsync(byte[] data)
+ {
+ if (_udp == null)
+ return;
+
+ if (data.Length <= MaxUdpPacketSize)
{
- throw new ArgumentException("Port must be zero or greater.");
+ try
+ {
+ await _udp.SendAsync(data, data.Length);
+ }
+ catch (Exception ex)
+ {
+ OnException?.Invoke(this, ex);
+ IsConnected = false;
+ }
+ }
+ else if (SupportsTcp)
+ {
+ await SendViaTcpAsync(data);
+ }
+ else
+ {
+ await SendUdpInChunksAsync(data, MaxUdpPacketSize);
}
-
- _Port = value;
- SetUdp();
- }
- }
-
- public string IpPort => _Hostname + ":" + _Port;
- public bool SupportsTcp { get; set; }
- public string Nickname { get; set; }
- public List TypesToLog { get; set; }
- public bool ConvertToRfc5424 { get; set; }
- public bool ConvertToRfc3164 { get; set; }
-
- internal void SetUdp()
- {
- try
- {
- DisposeUdp();
- Udp = new UdpClient(_Hostname, _Port);
- IsConnected = true;
- }
- catch
- {
- IsConnected = false;
- }
- }
-
- internal void DisposeUdp()
- {
- if (Udp != null)
- {
- IsConnected = false;
-
- try
- {
- Udp?.Close();
- Udp?.Dispose();
- }
- catch
- {
- // Do nothing
- }
- Udp = null;
- }
- }
-
- public void Dispose()
- {
- DisposeUdp();
- GC.SuppressFinalize(this);
- }
-
- public async Task WriteAsync(string message)
- {
- var data = Encoding.UTF8.GetBytes(message);
- await SendAsync(data);
- }
-
- public async Task WriteAsync(byte[] data)
- {
- await SendAsync(data);
- }
-
- private async Task SendAsync(byte[] data)
- {
- if (data == null)
- {
- return;
}
- if (Udp == null)
+ private async Task SendViaTcpAsync(byte[] data)
{
- return;
+ try
+ {
+ using var tcpClient = new TcpClient();
+ await tcpClient.ConnectAsync(_hostname, _port);
+
+ var stream = tcpClient.GetStream();
+ await stream.WriteAsync(data, 0, data.Length);
+ await stream.FlushAsync();
+ }
+ catch
+ {
+ // TCP failure fallback is silent here
+ }
}
- if (data.Length <= MaxUdpPacketSize)
+ private async Task SendUdpInChunksAsync(byte[] data, int chunkSize)
{
- // Send via UDP (single packet)
- await Udp?.SendAsync(data, data.Length);
+ int offset = 0;
+ byte[] buffer = ArrayPool.Shared.Rent(chunkSize);
+
+ try
+ {
+ while (offset < data.Length)
+ {
+ int size = Math.Min(chunkSize, data.Length - offset);
+ Buffer.BlockCopy(data, offset, buffer, 0, size);
+ await _udp.SendAsync(buffer, size);
+ offset += size;
+ }
+ }
+ catch
+ {
+ IsConnected = false;
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
+ }
}
- else if (SupportsTcp)
+
+ public void Dispose()
{
- // Send via TCP if supported
- await SendViaTcpAsync(this, data);
+ DisposeUdp();
+ GC.SuppressFinalize(this);
}
- else
+
+ ~Syslog()
{
- // Chunk large messages for UDP
- await SendUdpInChunksAsync(this, data, MaxUdpPacketSize);
+ Dispose();
}
}
-
- ///
- /// Sends a message via TCP to a syslog server.
- ///
- private static async Task SendViaTcpAsync(Servers.Syslog.Syslog server, byte[] data)
- {
- if (server == null)
- {
- return;
- }
-
- using var tcpClient = new System.Net.Sockets.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(Servers.Syslog.Syslog 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);
- }
- }
-
- ~Syslog()
- {
- Dispose();
- }
-}
\ No newline at end of file
+}
diff --git a/EonaCat.Logger/Servers/Tcp/Tcp.cs b/EonaCat.Logger/Servers/Tcp/Tcp.cs
index 8c5f59d..6a1f851 100644
--- a/EonaCat.Logger/Servers/Tcp/Tcp.cs
+++ b/EonaCat.Logger/Servers/Tcp/Tcp.cs
@@ -2,35 +2,30 @@
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
+
namespace EonaCat.Logger.Servers.Tcp
{
// 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 Tcp : IDisposable
{
- private string _Hostname = "127.0.0.1";
- private int _Port = 514;
- internal TcpClient _tcp;
+ private string _hostname = "127.0.0.1";
+ private int _port = 514;
+ private TcpClient _tcp;
+ private readonly SemaphoreSlim _sendLock = new(1, 1);
+ public event EventHandler OnException;
public bool IsConnected { get; private set; }
public string Nickname { get; set; }
- public string IpPort => _Hostname + ":" + _Port;
+ public string IpPort => $"{_hostname}:{_port}";
+ public List TypesToLog { get; set; }
- ///
- /// Tcp Server
- ///
- ///
- ///
- ///
- ///
- ///
- ///
public Tcp(string hostname, int port, string nickname = null, List typesToLog = null)
{
- _Hostname = hostname ?? throw new ArgumentNullException(nameof(hostname));
- _Port = port >= 0 ? port : throw new ArgumentException("Port must be zero or greater.");
-
+ _hostname = hostname ?? throw new ArgumentNullException(nameof(hostname));
+ _port = port >= 0 ? port : throw new ArgumentException("Port must be zero or greater.");
Nickname = nickname ?? IpPort;
TypesToLog = typesToLog;
@@ -39,42 +34,36 @@ namespace EonaCat.Logger.Servers.Tcp
public string Hostname
{
- get => _Hostname;
+ get => _hostname;
set
{
- if (string.IsNullOrEmpty(value))
- {
+ if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException(nameof(Hostname));
- }
- _Hostname = value;
+ _hostname = value;
SetTcp();
}
}
public int Port
{
- get => _Port;
+ get => _port;
set
{
if (value < 0)
- {
throw new ArgumentException("Port must be zero or greater.");
- }
- _Port = value;
+ _port = value;
SetTcp();
}
}
- public List TypesToLog { get; set; }
-
internal void SetTcp()
{
try
{
DisposeTcp();
- _tcp = new TcpClient(_Hostname, _Port);
+ _tcp = new TcpClient(_hostname, _port);
IsConnected = true;
}
catch
@@ -85,80 +74,67 @@ namespace EonaCat.Logger.Servers.Tcp
internal void DisposeTcp()
{
- if (_tcp != null)
- {
- IsConnected = false;
+ IsConnected = false;
- try
- {
- _tcp.Close();
- _tcp.Dispose();
- _tcp = null;
- }
- catch
- {
- // Do nothing
- }
+ try
+ {
+ _tcp?.Close();
+ _tcp?.Dispose();
+ }
+ catch
+ {
+ // Silent fail
+ }
+
+ _tcp = null;
+ }
+
+ public async Task WriteAsync(byte[] data)
+ {
+ if (data == null || data.Length == 0) return;
+ await InternalWriteAsync(data);
+ }
+
+ public async Task WriteAsync(string data)
+ {
+ if (string.IsNullOrWhiteSpace(data)) return;
+ var bytes = Encoding.UTF8.GetBytes(data);
+ await InternalWriteAsync(bytes);
+ }
+
+ private async Task InternalWriteAsync(byte[] data)
+ {
+ await _sendLock.WaitAsync();
+ try
+ {
+ if (!IsConnected || _tcp == null)
+ SetTcp();
+
+ if (!IsConnected || _tcp == null)
+ return;
+
+ var stream = _tcp.GetStream();
+ await stream.WriteAsync(data, 0, data.Length);
+ await stream.FlushAsync();
+ }
+ catch (Exception ex)
+ {
+ OnException?.Invoke(this, ex);
+ IsConnected = false;
+ }
+ finally
+ {
+ _sendLock.Release();
}
}
public void Dispose()
{
DisposeTcp();
+ _sendLock.Dispose();
GC.SuppressFinalize(this);
}
- public async Task WriteAsync(byte[] data)
- {
- if (_tcp == null)
- {
- return;
- }
-
- if (data == null)
- {
- return;
- }
-
- if (!IsConnected)
- {
- SetTcp();
- }
-
- if (IsConnected)
- {
- using var stream = _tcp.GetStream();
- await stream.WriteAsync(data, 0, data.Length);
- await stream.FlushAsync();
- }
- }
-
- public async Task WriteAsync(string data)
- {
- if (_tcp == null)
- {
- return;
- }
-
- if (string.IsNullOrEmpty(data))
- {
- return;
- }
-
- if (!IsConnected)
- {
- SetTcp();
- }
-
- if (IsConnected)
- {
- var sendData = Encoding.UTF8.GetBytes(data);
- using var stream = _tcp.GetStream();
- await stream.WriteAsync(sendData, 0, sendData.Length);
- await stream.FlushAsync();
- }
- }
-
~Tcp()
{
Dispose();
diff --git a/EonaCat.Logger/Servers/Udp/Udp.cs b/EonaCat.Logger/Servers/Udp/Udp.cs
index a5a0ca2..9f175cc 100644
--- a/EonaCat.Logger/Servers/Udp/Udp.cs
+++ b/EonaCat.Logger/Servers/Udp/Udp.cs
@@ -1,36 +1,33 @@
using System;
+using System.Buffers;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
+
namespace EonaCat.Logger.Servers.Udp
{
// 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 Udp : IDisposable
{
- const int MaxUdpPacketSize = 4096;
- private string _Hostname = "127.0.0.1";
- private int _Port = 514;
- internal UdpClient _udp;
+ private const int MaxUdpPacketSize = 4096;
+ private string _hostname = "127.0.0.1";
+ private int _port = 514;
+ private UdpClient _udp;
+ private readonly SemaphoreSlim _sendLock = new(1, 1);
+ public event EventHandler OnException;
public bool IsConnected { get; private set; }
public string Nickname { get; set; }
- public string IpPort => _Hostname + ":" + _Port;
+ public string IpPort => $"{_hostname}:{_port}";
+ public List TypesToLog { get; set; }
- ///
- /// Udp Server
- ///
- ///
- ///
- ///
- ///
- ///
- ///
public Udp(string hostname, int port, string nickname = null, List typesToLog = null)
{
- _Hostname = hostname ?? throw new ArgumentNullException(nameof(hostname));
- _Port = port >= 0 ? port : throw new ArgumentException("Port must be zero or greater.");
+ _hostname = hostname ?? throw new ArgumentNullException(nameof(hostname));
+ _port = port >= 0 ? port : throw new ArgumentException("Port must be zero or greater.");
TypesToLog = typesToLog;
Nickname = nickname ?? IpPort;
SetUdp();
@@ -38,42 +35,39 @@ namespace EonaCat.Logger.Servers.Udp
public string Hostname
{
- get => _Hostname;
+ get => _hostname;
set
{
- if (string.IsNullOrEmpty(value))
- {
+ if (string.IsNullOrWhiteSpace(value))
throw new ArgumentNullException(nameof(Hostname));
- }
- _Hostname = value;
+ _hostname = value;
SetUdp();
}
}
public int Port
{
- get => _Port;
+ get => _port;
set
{
if (value < 0)
- {
throw new ArgumentException("Port must be zero or greater.");
- }
- _Port = value;
+ _port = value;
SetUdp();
}
}
- public List TypesToLog { get; set; }
-
internal void SetUdp()
{
try
{
DisposeUdp();
- _udp = new UdpClient(_Hostname, _Port);
+ _udp = new UdpClient(_hostname, _port)
+ {
+ DontFragment = false
+ };
IsConnected = true;
}
catch
@@ -84,92 +78,88 @@ namespace EonaCat.Logger.Servers.Udp
internal void DisposeUdp()
{
- if (_udp != null)
+ IsConnected = false;
+
+ try
+ {
+ _udp?.Close();
+ _udp?.Dispose();
+ }
+ catch
+ {
+ // Silently ignore
+ }
+
+ _udp = null;
+ }
+
+ public async Task WriteAsync(byte[] data, bool dontFragment = false)
+ {
+ if (data == null || data.Length == 0)
+ return;
+
+ await _sendLock.WaitAsync();
+ try
+ {
+ if (!IsConnected)
+ SetUdp();
+
+ if (!IsConnected || _udp == null)
+ return;
+
+ _udp.DontFragment = dontFragment;
+
+ await SendChunksAsync(data);
+ }
+ finally
+ {
+ _sendLock.Release();
+ }
+ }
+
+ public async Task WriteAsync(string data, bool dontFragment = false)
+ {
+ if (string.IsNullOrWhiteSpace(data))
+ return;
+
+ var bytes = Encoding.UTF8.GetBytes(data);
+ await WriteAsync(bytes, dontFragment);
+ }
+
+ private async Task SendChunksAsync(byte[] data)
+ {
+ int offset = 0;
+ int length = data.Length;
+ byte[] buffer = ArrayPool.Shared.Rent(MaxUdpPacketSize);
+
+ try
+ {
+ while (offset < length)
+ {
+ int chunkSize = Math.Min(MaxUdpPacketSize, length - offset);
+ Buffer.BlockCopy(data, offset, buffer, 0, chunkSize);
+ await _udp.SendAsync(buffer, chunkSize);
+ offset += chunkSize;
+ }
+ }
+ catch (Exception ex)
{
IsConnected = false;
-
- try
- {
- _udp?.Close();
- _udp.Dispose();
- }
- catch
- {
- // Do nothing
- }
- _udp = null;
+ OnException?.Invoke(this, ex);
+ }
+ finally
+ {
+ ArrayPool.Shared.Return(buffer);
}
}
public void Dispose()
{
DisposeUdp();
+ _sendLock.Dispose();
GC.SuppressFinalize(this);
}
- public async Task WriteAsync(byte[] data, bool dontFragment = false)
- {
- if (_udp == null || data == null)
- {
- return;
- }
-
- if (!IsConnected)
- {
- SetUdp();
- }
-
- if (IsConnected)
- {
- _udp.DontFragment = dontFragment;
-
- int maxChunkSize = MaxUdpPacketSize;
- int offset = 0;
-
- while (offset < data.Length)
- {
- int chunkSize = Math.Min(maxChunkSize, data.Length - offset);
- byte[] chunk = new byte[chunkSize];
- Array.Copy(data, offset, chunk, 0, chunkSize);
-
- await _udp?.SendAsync(chunk, chunk.Length);
- offset += chunkSize;
- }
- }
- }
-
- public async Task WriteAsync(string data, bool dontFragment = false)
- {
- if (_udp == null || string.IsNullOrEmpty(data))
- {
- return;
- }
-
- if (!IsConnected)
- {
- SetUdp();
- }
-
- if (IsConnected)
- {
- var sendData = Encoding.UTF8.GetBytes(data);
- _udp.DontFragment = dontFragment;
-
- int maxChunkSize = MaxUdpPacketSize;
- int offset = 0;
-
- while (offset < sendData.Length)
- {
- int chunkSize = Math.Min(maxChunkSize, sendData.Length - offset);
- byte[] chunk = new byte[chunkSize];
- Array.Copy(sendData, offset, chunk, 0, chunkSize);
-
- await _udp?.SendAsync(chunk, chunk.Length);
- offset += chunkSize;
- }
- }
- }
-
~Udp()
{
Dispose();
diff --git a/EonaCat.Logger/Servers/Zabbix/API/ZabbixApi.cs b/EonaCat.Logger/Servers/Zabbix/API/ZabbixApi.cs
index 297c811..429114e 100644
--- a/EonaCat.Logger/Servers/Zabbix/API/ZabbixApi.cs
+++ b/EonaCat.Logger/Servers/Zabbix/API/ZabbixApi.cs
@@ -5,144 +5,107 @@ using System.IO;
using System.Net;
using System.Text;
-namespace EonaCat.Logger.Servers.Zabbix.API;
-// 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 ZabbixApi
+namespace EonaCat.Logger.Servers.Zabbix.API
{
///
- /// Zabbix Api
+ /// Zabbix API client for authentication and communication with Zabbix server.
///
- ///
- ///
- ///
- ///
- public ZabbixApi(string user, string password, string zabbixURL, bool basicAuth)
+ public class ZabbixApi
{
- _user = user;
- _password = password;
- _zabbixURL = zabbixURL;
+ private readonly string _user;
+ private readonly string _password;
+ private readonly string _zabbixUrl;
+ private readonly string _basicAuth;
+ private string _auth;
+ public event EventHandler OnException;
- if (basicAuth)
+ public bool IsLoggedIn { get; private set; }
+
+ public ZabbixApi(string user, string password, string zabbixUrl, bool useBasicAuth = false)
{
- _basicAuth = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(_user + ":" + _password));
+ _user = user ?? throw new ArgumentNullException(nameof(user));
+ _password = password ?? throw new ArgumentNullException(nameof(password));
+ _zabbixUrl = zabbixUrl ?? throw new ArgumentNullException(nameof(zabbixUrl));
+
+ _basicAuth = useBasicAuth
+ ? Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes($"{_user}:{_password}"))
+ : null;
}
- _auth = null;
- }
-
- public ZabbixApi(string user, string password, string zabbixURL) : this(user, password, zabbixURL, false)
- {
-
- }
-
- private readonly string _user;
- private readonly string _password;
- private readonly string _zabbixURL;
- private string _auth;
- private readonly string _basicAuth = null;
-
- ///
- /// Determines if the user is logged in
- ///
- public bool IsLoggedIn { get; set; }
-
- ///
- /// Logs in the user
- ///
- public void Login()
- {
- dynamic authentication = new ExpandoObject();
- authentication.user = _user;
- authentication.password = _password;
-
- ZabbixApiResponse zabbixApiResponse = ResponseAsObject("user.login", authentication);
-
- _auth = zabbixApiResponse.Result;
- IsLoggedIn = !string.IsNullOrEmpty(_auth);
- }
-
- ///
- /// Logs out the user
- ///
- ///
- public bool Logout()
- {
- ZabbixApiResponse zbxResponse = ResponseAsObject("user.logout", new string[] { });
- var result = zbxResponse.Result;
- return result;
- }
-
- ///
- /// Generic method to send a request to the Zabbix API
- ///
- ///
- ///
- ///
- public string ResponseAsJson(string method, object parameters)
- {
- ZabbixApiRequest zbxRequest = new ZabbixApiRequest("2.0", method, 1, _auth, parameters);
- string jsonParameters = JsonHelper.ToJson(zbxRequest);
- return SendRequest(jsonParameters);
- }
-
- ///
- /// Generic method to send a request to the Zabbix API
- ///
- ///
- ///
- ///
- public ZabbixApiResponse ResponseAsObject(string method, object parameters)
- {
- ZabbixApiRequest zbxRequest = new ZabbixApiRequest("2.0", method, 1, _auth, parameters);
- string jsonParameters = JsonHelper.ToJson(zbxRequest);
- return CreateResponse(SendRequest(jsonParameters));
- }
-
- ///
- /// Creates a ZabbixApiResponse object from a JSON string
- ///
- ///
- ///
- private ZabbixApiResponse CreateResponse(string json)
- {
- ZabbixApiResponse zbxResponse = JsonHelper.ToObject(json);
- return zbxResponse;
- }
-
- ///
- /// Sends a request to the Zabbix API
- ///
- ///
- ///
- private string SendRequest(string jsonParams)
- {
- WebRequest request = WebRequest.Create(_zabbixURL);
-
- if (_basicAuth != null)
+ ///
+ /// Login and retrieve authentication token from Zabbix API.
+ ///
+ public void Login()
{
- request.Headers.Add("Authorization", "Basic " + _basicAuth);
+ dynamic authParams = new ExpandoObject();
+ authParams.user = _user;
+ authParams.password = _password;
+
+ var response = ResponseAsObject("user.login", authParams);
+ _auth = response?.Result;
+ IsLoggedIn = !string.IsNullOrEmpty(_auth);
}
- request.ContentType = "application/json-rpc";
- request.Method = "POST";
- string jsonResult;
-
- using (var streamWriter = new StreamWriter(request.GetRequestStream()))
+ ///
+ /// Logout from Zabbix API.
+ ///
+ public bool Logout()
{
- streamWriter.Write(jsonParams);
- streamWriter.Flush();
- streamWriter.Close();
+ var response = ResponseAsObject("user.logout", Array.Empty());
+ return response?.Result ?? false;
}
- WebResponse response = request.GetResponse();
- using (var streamReader = new StreamReader(response.GetResponseStream()))
+ ///
+ /// Send a Zabbix API request and return JSON response as string.
+ ///
+ public string ResponseAsJson(string method, object parameters)
{
- jsonResult = streamReader.ReadToEnd();
+ var request = CreateRequest(method, parameters);
+ var jsonParams = JsonHelper.ToJson(request);
+ return SendRequest(jsonParams);
}
- return jsonResult;
+ ///
+ /// Send a Zabbix API request and return deserialized response.
+ ///
+ public ZabbixApiResponse ResponseAsObject(string method, object parameters)
+ {
+ var request = CreateRequest(method, parameters);
+ var jsonParams = JsonHelper.ToJson(request);
+ var responseJson = SendRequest(jsonParams);
+ return JsonHelper.ToObject(responseJson);
+ }
+
+ private ZabbixApiRequest CreateRequest(string method, object parameters) =>
+ new("2.0", method, 1, _auth, parameters);
+
+ private string SendRequest(string jsonParams)
+ {
+ try
+ {
+ var request = WebRequest.Create(_zabbixUrl);
+ request.Method = "POST";
+ request.ContentType = "application/json-rpc";
+
+ if (!string.IsNullOrEmpty(_basicAuth))
+ {
+ request.Headers.Add("Authorization", $"Basic {_basicAuth}");
+ }
+
+ using (var writer = new StreamWriter(request.GetRequestStream()))
+ {
+ writer.Write(jsonParams);
+ }
+
+ using var response = request.GetResponse();
+ using var reader = new StreamReader(response.GetResponseStream());
+ return reader.ReadToEnd();
+ }
+ catch (Exception ex)
+ {
+ OnException?.Invoke(this, ex);
+ return null;
+ }
+ }
}
-
}
diff --git a/Testers/EonaCat.Logger.Test.Web/Program.cs b/Testers/EonaCat.Logger.Test.Web/Program.cs
index cbb5f8a..86179cf 100644
--- a/Testers/EonaCat.Logger.Test.Web/Program.cs
+++ b/Testers/EonaCat.Logger.Test.Web/Program.cs
@@ -7,10 +7,6 @@ using EonaCat.Logger.Test.Web;
using EonaCat.Versioning.Helpers;
using EonaCat.Web.RateLimiter;
using EonaCat.Web.RateLimiter.Endpoints.Extensions;
-using EonaCat.Web.Tracer.Extensions;
-using EonaCat.Web.Tracer.Models;
-using Microsoft.Extensions.Logging;
-using System.Runtime.Versioning;
var builder = WebApplication.CreateBuilder(args);
int onLogCounter = 0;