This commit is contained in:
2025-07-25 21:30:24 +02:00
parent 182129d9fa
commit ec32464c5b
7 changed files with 549 additions and 622 deletions

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Net.Sockets; using System.Net.Sockets;
namespace EonaCat.Logger.Servers.GrayLog; namespace EonaCat.Logger.Servers.GrayLog;
// This file is part of the EonaCat project(s) which is released under the Apache License. // 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. // See the LICENSE file or go to https://EonaCat.com/License for full license details.

View File

@@ -1,155 +1,159 @@
using EonaCat.Json; using System;
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.
/// <summary>
/// Splunk Server.
/// </summary>
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
using System.Threading.Tasks; 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"));
/// <summary> /// <summary>
/// Splunk Server /// Splunk Server.
/// </summary> /// </summary>
/// <param name="splunkHecUrl"></param> public class Splunk : IDisposable
/// <param name="splunkHecToken"></param>
/// <param name="httpClientHandler"></param>
/// <param name="nickName"></param>
/// <param name="typesToLog"></param>
/// <exception cref="ArgumentNullException"></exception>
public Splunk(string splunkHecUrl, string splunkHecToken, HttpClientHandler httpClientHandler = null, string nickName = null, List<ELogType> typesToLog = null)
{ {
SplunkHecUrl = splunkHecUrl ?? throw new ArgumentNullException(nameof(splunkHecUrl)); private string _splunkHecUrl = "https://127.0.0.1:8088/services/collector/event";
SplunkHecToken = splunkHecToken ?? throw new ArgumentNullException(nameof(splunkHecToken)); private HttpClient _httpClient;
private HttpClientHandler _httpClientHandler;
public event EventHandler<Exception> OnException;
Nickname = nickName ?? $"{splunkHecUrl}_{splunkHecToken}"; public string SplunkHecToken { get; set; } = "splunk-hec-token";
SplunkClientHandler = httpClientHandler ?? new HttpClientHandler(); public string Nickname { get; set; }
TypesToLog = typesToLog; public List<ELogType> TypesToLog { get; set; }
CreateHttpClient(); public string SplunkHecUrl
}
public string SplunkHecUrl
{
get => _splunkHecUrl;
set
{ {
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<ELogType> 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<HttpResponseMessage> SendAsync(SplunkPayload splunkPayload, bool disableSplunkSSL = false)
{
if (splunkPayload == null)
{
return null;
} }
var eventObject = new public bool HasHecToken => !string.IsNullOrWhiteSpace(SplunkHecToken) && SplunkHecToken != "splunk-hec-token";
{ public bool HasHecUrl => !string.IsNullOrWhiteSpace(_splunkHecUrl);
@event = splunkPayload.EventData, public bool IsHttpsHecUrl => _splunkHecUrl.StartsWith("https", StringComparison.OrdinalIgnoreCase);
sourcetype = splunkPayload.SourceType, public bool IsLocalHost => _splunkHecUrl.ToLower().Contains("127.0.0.1") || _splunkHecUrl.ToLower().Contains("localhost");
host = splunkPayload.Host
};
if (disableSplunkSSL) public Splunk(string splunkHecUrl, string splunkHecToken, HttpClientHandler handler = null, string nickName = null, List<ELogType> typesToLog = null)
{
DisableSSLValidation();
}
if (HttpClient == 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(); CreateHttpClient();
} }
var eventJson = JsonHelper.ToJson(eventObject); private void CreateHttpClient()
if (!HasHecToken)
{ {
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(); CreateHttpClient();
} }
using var content = new StringContent(eventJson, Encoding.UTF8, "application/json"); public void DisableSSLValidation()
return await HttpClient.PostAsync("/services/collector/event", content);
}
internal void DisposeHttpClient()
{
if (HttpClient != null)
{ {
HttpClient.Dispose(); DisposeHttpClient();
HttpClient = null; _httpClientHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (_, _, _, _) => true
};
CreateHttpClient();
} }
if (SplunkClientHandler != null) public async Task<HttpResponseMessage> SendAsync(SplunkPayload payload, bool disableSplunkSSL = false)
{ {
SplunkClientHandler.Dispose(); try
SplunkClientHandler = null; {
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();
}
}

View File

@@ -1,194 +1,191 @@
using System; using System;
using System.Collections.Generic; using System.Buffers;
using System.Linq; using System.Collections.Generic;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace EonaCat.Logger.Servers.Syslog; 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. // This file is part of the EonaCat project(s) released under the Apache License.
public class Syslog : IDisposable
/// <summary> {
/// Syslog server. private const int MaxUdpPacketSize = 4096;
/// </summary> private string _hostname = "127.0.0.1";
public class Syslog : IDisposable private int _port = 514;
{ private UdpClient _udp;
const int MaxUdpPacketSize = 4096; public event EventHandler<Exception> OnException;
private string _Hostname = "127.0.0.1";
private int _Port = 514; public bool IsConnected { get; private set; }
public bool IsConnected { get; private set; } public string Nickname { get; set; }
public string IpPort => $"{_hostname}:{_port}";
internal UdpClient Udp; public bool SupportsTcp { get; set; }
public List<ELogType> TypesToLog { get; set; }
public Syslog() { } public bool ConvertToRfc5424 { get; set; }
public bool ConvertToRfc3164 { get; set; }
/// <summary>
/// Syslog server public Syslog() { }
/// </summary>
/// <param name="hostname"></param> public Syslog(string hostname = "127.0.0.1", int port = 514, string nickName = null, List<ELogType> typesToLog = null, bool convertToRfc5424 = false, bool convertToRfc3164 = false)
/// <param name="port"></param> {
/// <param name="nickName"></param> Hostname = hostname;
/// <param name="typesToLog"></param> Port = port;
/// <param name="convertToRfc5424"></param> Nickname = nickName ?? IpPort;
/// <param name="convertToRfc3164"></param> TypesToLog = typesToLog;
public Syslog(string hostname = "127.0.0.1", int port = 514, string nickName = null, List<ELogType> typesToLog = null, bool convertToRfc5424 = false, bool convertToRfc3164 = false) ConvertToRfc5424 = convertToRfc5424;
{ ConvertToRfc3164 = convertToRfc3164;
Hostname = hostname; }
Port = port;
Nickname = nickName ?? IpPort; public string Hostname
TypesToLog = typesToLog; {
ConvertToRfc5424 = convertToRfc5424; get => _hostname;
ConvertToRfc3164 = convertToRfc3164; set
}
public string Hostname
{
get => _Hostname;
set
{
if (string.IsNullOrEmpty(value))
{ {
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; _udp = null;
SetUdp(); }
}
} public async Task WriteAsync(string message)
{
public int Port if (string.IsNullOrWhiteSpace(message))
{ return;
get => _Port;
set var data = Encoding.UTF8.GetBytes(message);
{ await SendAsync(data);
if (value < 0) }
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<ELogType> 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) int offset = 0;
await Udp?.SendAsync(data, data.Length); byte[] buffer = ArrayPool<byte>.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<byte>.Shared.Return(buffer);
}
} }
else if (SupportsTcp)
public void Dispose()
{ {
// Send via TCP if supported DisposeUdp();
await SendViaTcpAsync(this, data); GC.SuppressFinalize(this);
} }
else
~Syslog()
{ {
// Chunk large messages for UDP Dispose();
await SendUdpInChunksAsync(this, data, MaxUdpPacketSize);
} }
} }
}
/// <summary>
/// Sends a message via TCP to a syslog server.
/// </summary>
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();
}
/// <summary>
/// Sends large messages in chunks over UDP.
/// </summary>
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();
}
}

View File

@@ -2,35 +2,30 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace EonaCat.Logger.Servers.Tcp namespace EonaCat.Logger.Servers.Tcp
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. // 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. // See the LICENSE file or go to https://EonaCat.com/License for full license details.
public class Tcp : IDisposable public class Tcp : IDisposable
{ {
private string _Hostname = "127.0.0.1"; private string _hostname = "127.0.0.1";
private int _Port = 514; private int _port = 514;
internal TcpClient _tcp; private TcpClient _tcp;
private readonly SemaphoreSlim _sendLock = new(1, 1);
public event EventHandler<Exception> OnException;
public bool IsConnected { get; private set; } public bool IsConnected { get; private set; }
public string Nickname { get; set; } public string Nickname { get; set; }
public string IpPort => _Hostname + ":" + _Port; public string IpPort => $"{_hostname}:{_port}";
public List<ELogType> TypesToLog { get; set; }
/// <summary>
/// Tcp Server
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="nickname"></param>
/// <param name="typesToLog"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public Tcp(string hostname, int port, string nickname = null, List<ELogType> typesToLog = null) public Tcp(string hostname, int port, string nickname = null, List<ELogType> typesToLog = null)
{ {
_Hostname = hostname ?? throw new ArgumentNullException(nameof(hostname)); _hostname = hostname ?? throw new ArgumentNullException(nameof(hostname));
_Port = port >= 0 ? port : throw new ArgumentException("Port must be zero or greater."); _port = port >= 0 ? port : throw new ArgumentException("Port must be zero or greater.");
Nickname = nickname ?? IpPort; Nickname = nickname ?? IpPort;
TypesToLog = typesToLog; TypesToLog = typesToLog;
@@ -39,42 +34,36 @@ namespace EonaCat.Logger.Servers.Tcp
public string Hostname public string Hostname
{ {
get => _Hostname; get => _hostname;
set set
{ {
if (string.IsNullOrEmpty(value)) if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentNullException(nameof(Hostname)); throw new ArgumentNullException(nameof(Hostname));
}
_Hostname = value; _hostname = value;
SetTcp(); SetTcp();
} }
} }
public int Port public int Port
{ {
get => _Port; get => _port;
set set
{ {
if (value < 0) if (value < 0)
{
throw new ArgumentException("Port must be zero or greater."); throw new ArgumentException("Port must be zero or greater.");
}
_Port = value; _port = value;
SetTcp(); SetTcp();
} }
} }
public List<ELogType> TypesToLog { get; set; }
internal void SetTcp() internal void SetTcp()
{ {
try try
{ {
DisposeTcp(); DisposeTcp();
_tcp = new TcpClient(_Hostname, _Port); _tcp = new TcpClient(_hostname, _port);
IsConnected = true; IsConnected = true;
} }
catch catch
@@ -85,80 +74,67 @@ namespace EonaCat.Logger.Servers.Tcp
internal void DisposeTcp() internal void DisposeTcp()
{ {
if (_tcp != null) IsConnected = false;
{
IsConnected = false;
try try
{ {
_tcp.Close(); _tcp?.Close();
_tcp.Dispose(); _tcp?.Dispose();
_tcp = null; }
} catch
catch {
{ // Silent fail
// Do nothing }
}
_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() public void Dispose()
{ {
DisposeTcp(); DisposeTcp();
_sendLock.Dispose();
GC.SuppressFinalize(this); 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() ~Tcp()
{ {
Dispose(); Dispose();

View File

@@ -1,36 +1,33 @@
using System; using System;
using System.Buffers;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace EonaCat.Logger.Servers.Udp namespace EonaCat.Logger.Servers.Udp
{ {
// This file is part of the EonaCat project(s) which is released under the Apache License. // 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. // See the LICENSE file or go to https://EonaCat.com/License for full license details.
public class Udp : IDisposable public class Udp : IDisposable
{ {
const int MaxUdpPacketSize = 4096; private const int MaxUdpPacketSize = 4096;
private string _Hostname = "127.0.0.1"; private string _hostname = "127.0.0.1";
private int _Port = 514; private int _port = 514;
internal UdpClient _udp; private UdpClient _udp;
private readonly SemaphoreSlim _sendLock = new(1, 1);
public event EventHandler<Exception> OnException;
public bool IsConnected { get; private set; } public bool IsConnected { get; private set; }
public string Nickname { get; set; } public string Nickname { get; set; }
public string IpPort => _Hostname + ":" + _Port; public string IpPort => $"{_hostname}:{_port}";
public List<ELogType> TypesToLog { get; set; }
/// <summary>
/// Udp Server
/// </summary>
/// <param name="hostname"></param>
/// <param name="port"></param>
/// <param name="nickname"></param>
/// <param name="typesToLog"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public Udp(string hostname, int port, string nickname = null, List<ELogType> typesToLog = null) public Udp(string hostname, int port, string nickname = null, List<ELogType> typesToLog = null)
{ {
_Hostname = hostname ?? throw new ArgumentNullException(nameof(hostname)); _hostname = hostname ?? throw new ArgumentNullException(nameof(hostname));
_Port = port >= 0 ? port : throw new ArgumentException("Port must be zero or greater."); _port = port >= 0 ? port : throw new ArgumentException("Port must be zero or greater.");
TypesToLog = typesToLog; TypesToLog = typesToLog;
Nickname = nickname ?? IpPort; Nickname = nickname ?? IpPort;
SetUdp(); SetUdp();
@@ -38,42 +35,39 @@ namespace EonaCat.Logger.Servers.Udp
public string Hostname public string Hostname
{ {
get => _Hostname; get => _hostname;
set set
{ {
if (string.IsNullOrEmpty(value)) if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentNullException(nameof(Hostname)); throw new ArgumentNullException(nameof(Hostname));
}
_Hostname = value; _hostname = value;
SetUdp(); SetUdp();
} }
} }
public int Port public int Port
{ {
get => _Port; get => _port;
set set
{ {
if (value < 0) if (value < 0)
{
throw new ArgumentException("Port must be zero or greater."); throw new ArgumentException("Port must be zero or greater.");
}
_Port = value; _port = value;
SetUdp(); SetUdp();
} }
} }
public List<ELogType> TypesToLog { get; set; }
internal void SetUdp() internal void SetUdp()
{ {
try try
{ {
DisposeUdp(); DisposeUdp();
_udp = new UdpClient(_Hostname, _Port); _udp = new UdpClient(_hostname, _port)
{
DontFragment = false
};
IsConnected = true; IsConnected = true;
} }
catch catch
@@ -84,92 +78,88 @@ namespace EonaCat.Logger.Servers.Udp
internal void DisposeUdp() 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<byte>.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; IsConnected = false;
OnException?.Invoke(this, ex);
try }
{ finally
_udp?.Close(); {
_udp.Dispose(); ArrayPool<byte>.Shared.Return(buffer);
}
catch
{
// Do nothing
}
_udp = null;
} }
} }
public void Dispose() public void Dispose()
{ {
DisposeUdp(); DisposeUdp();
_sendLock.Dispose();
GC.SuppressFinalize(this); 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() ~Udp()
{ {
Dispose(); Dispose();

View File

@@ -5,144 +5,107 @@ using System.IO;
using System.Net; using System.Net;
using System.Text; using System.Text;
namespace EonaCat.Logger.Servers.Zabbix.API; 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
{ {
/// <summary> /// <summary>
/// Zabbix Api /// Zabbix API client for authentication and communication with Zabbix server.
/// </summary> /// </summary>
/// <param name="user"></param> public class ZabbixApi
/// <param name="password"></param>
/// <param name="zabbixURL"></param>
/// <param name="basicAuth"></param>
public ZabbixApi(string user, string password, string zabbixURL, bool basicAuth)
{ {
_user = user; private readonly string _user;
_password = password; private readonly string _password;
_zabbixURL = zabbixURL; private readonly string _zabbixUrl;
private readonly string _basicAuth;
private string _auth;
public event EventHandler<Exception> 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; /// <summary>
} /// Login and retrieve authentication token from Zabbix API.
/// </summary>
public ZabbixApi(string user, string password, string zabbixURL) : this(user, password, zabbixURL, false) public void Login()
{
}
private readonly string _user;
private readonly string _password;
private readonly string _zabbixURL;
private string _auth;
private readonly string _basicAuth = null;
/// <summary>
/// Determines if the user is logged in
/// </summary>
public bool IsLoggedIn { get; set; }
/// <summary>
/// Logs in the user
/// </summary>
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);
}
/// <summary>
/// Logs out the user
/// </summary>
/// <returns></returns>
public bool Logout()
{
ZabbixApiResponse zbxResponse = ResponseAsObject("user.logout", new string[] { });
var result = zbxResponse.Result;
return result;
}
/// <summary>
/// Generic method to send a request to the Zabbix API
/// </summary>
/// <param name="method"></param>
/// <param name="parameters"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Generic method to send a request to the Zabbix API
/// </summary>
/// <param name="method"></param>
/// <param name="parameters"></param>
/// <returns></returns>
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));
}
/// <summary>
/// Creates a ZabbixApiResponse object from a JSON string
/// </summary>
/// <param name="json"></param>
/// <returns></returns>
private ZabbixApiResponse CreateResponse(string json)
{
ZabbixApiResponse zbxResponse = JsonHelper.ToObject<ZabbixApiResponse>(json);
return zbxResponse;
}
/// <summary>
/// Sends a request to the Zabbix API
/// </summary>
/// <param name="jsonParams"></param>
/// <returns></returns>
private string SendRequest(string jsonParams)
{
WebRequest request = WebRequest.Create(_zabbixURL);
if (_basicAuth != null)
{ {
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"; /// <summary>
request.Method = "POST"; /// Logout from Zabbix API.
string jsonResult; /// </summary>
public bool Logout()
using (var streamWriter = new StreamWriter(request.GetRequestStream()))
{ {
streamWriter.Write(jsonParams); var response = ResponseAsObject("user.logout", Array.Empty<string>());
streamWriter.Flush(); return response?.Result ?? false;
streamWriter.Close();
} }
WebResponse response = request.GetResponse(); /// <summary>
using (var streamReader = new StreamReader(response.GetResponseStream())) /// Send a Zabbix API request and return JSON response as string.
/// </summary>
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; /// <summary>
/// Send a Zabbix API request and return deserialized response.
/// </summary>
public ZabbixApiResponse ResponseAsObject(string method, object parameters)
{
var request = CreateRequest(method, parameters);
var jsonParams = JsonHelper.ToJson(request);
var responseJson = SendRequest(jsonParams);
return JsonHelper.ToObject<ZabbixApiResponse>(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;
}
}
} }
} }

View File

@@ -7,10 +7,6 @@ using EonaCat.Logger.Test.Web;
using EonaCat.Versioning.Helpers; using EonaCat.Versioning.Helpers;
using EonaCat.Web.RateLimiter; using EonaCat.Web.RateLimiter;
using EonaCat.Web.RateLimiter.Endpoints.Extensions; 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); var builder = WebApplication.CreateBuilder(args);
int onLogCounter = 0; int onLogCounter = 0;