diff --git a/EonaCat.Logger.Server/EonaCat.Logger.Server.csproj b/EonaCat.Logger.Server/EonaCat.Logger.Server.csproj new file mode 100644 index 0000000..21b0c44 --- /dev/null +++ b/EonaCat.Logger.Server/EonaCat.Logger.Server.csproj @@ -0,0 +1,28 @@ + + + + netstandard2.1 + enable + True + EonaCat.Logger.Server + EonaCat (Jeroen Saey) + EonaCat.Logger.Server is a server for the logging library + EonaCat (Jeroen Saey) + https://www.nuget.org/packages/EonaCat.Logger.Server/ + icon.png + README.md + EonaCat;Logger;EonaCatLogger;server;Log;Writer;Jeroen;Saey + + + + + True + \ + + + True + \ + + + + diff --git a/EonaCat.Logger.Server/Server.cs b/EonaCat.Logger.Server/Server.cs new file mode 100644 index 0000000..bb36864 --- /dev/null +++ b/EonaCat.Logger.Server/Server.cs @@ -0,0 +1,287 @@ +using System.IO; +using System.Net.Sockets; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using System.Threading; +using System; +using System.Linq; + +namespace EonaCat.Logger.Server +{ + // 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 Server + { + private TcpListener _tcpListener; + private UdpClient _udpListener; + private CancellationTokenSource _cts; + private bool _isRunning; + private readonly bool _useUdp; + private const long MaxLogFileSize = 200 * 1024 * 1024; // 200MB log rollover limit + private readonly int _logRetentionDays; // Number of days to retain logs + private readonly long _maxLogDirectorySize; // Maximum allowed size of the logs directory + private const int UdpBufferSize = 65507; // Maximum UDP packet size (65507 bytes for UDP payload) + + /// + /// EonaCat Log Server + /// + /// Determine if we need to start a udp server (default: true) + /// Max log retention days (default: 30) + /// Max log directory size (default: 10GB) + public Server(bool useUdp = true, int logRetentionDays = 30, long maxLogDirectorySize = 10L * 1024 * 1024 * 1024) // Default 10GB max directory size + { + _useUdp = useUdp; + _logRetentionDays = logRetentionDays; + _maxLogDirectorySize = maxLogDirectorySize; + } + + protected virtual Task ProcessLogAsync(string logData) + { + string logsRootDirectory = "logs"; + + // Create root log directory if it doesn't exist + if (!Directory.Exists(logsRootDirectory)) + { + Directory.CreateDirectory(logsRootDirectory); + } + + // Create a daily directory for logs + string dailyLogsDirectory = Path.Combine(logsRootDirectory, DateTime.Now.ToString("yyyyMMdd")); + if (!Directory.Exists(dailyLogsDirectory)) + { + Directory.CreateDirectory(dailyLogsDirectory); + } + + // Base log file name + string baseLogFilePath = Path.Combine(dailyLogsDirectory, "EonaCatLogs"); + string logFilePath = baseLogFilePath + ".log"; + + int fileIndex = 1; + while (File.Exists(logFilePath) && new FileInfo(logFilePath).Length > MaxLogFileSize) + { + logFilePath = baseLogFilePath + $"_{fileIndex}.log"; + fileIndex++; + } + + // After processing log, check directory size and clean up if needed + CleanUpOldLogs(); + + return File.AppendAllTextAsync(logFilePath, logData + Environment.NewLine); + } + + private void CleanUpOldLogs() + { + string logsRootDirectory = "logs"; + if (!Directory.Exists(logsRootDirectory)) + { + return; + } + + // Delete old directories + foreach (var directory in Directory.GetDirectories(logsRootDirectory)) + { + try + { + DirectoryInfo dirInfo = new DirectoryInfo(directory); + if (dirInfo.CreationTime < DateTime.Now.AddDays(-_logRetentionDays)) + { + Console.WriteLine($"Deleting old log directory: {directory}"); + Directory.Delete(directory, true); // Delete directory and its contents + } + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting old directory {directory}: {ex.Message}"); + } + } + + // Ensure total size of log directory doesn't exceed max limit + long totalDirectorySize = GetDirectorySize(logsRootDirectory); + if (totalDirectorySize > _maxLogDirectorySize) + { + Console.WriteLine("Log directory size exceeded limit, cleaning up..."); + + // Delete the oldest directories until the size limit is met + foreach (var directory in Directory.GetDirectories(logsRootDirectory).OrderBy(d => new DirectoryInfo(d).CreationTime)) + { + try + { + DirectoryInfo dirInfo = new DirectoryInfo(directory); + long dirSize = GetDirectorySize(directory); + totalDirectorySize -= dirSize; + + // Delete the directory if the total size exceeds the limit + Directory.Delete(directory, true); + Console.WriteLine($"Deleted directory: {directory}"); + + // Stop deleting if we are under the size limit + if (totalDirectorySize <= _maxLogDirectorySize) + { + break; + } + } + catch (Exception ex) + { + Console.WriteLine($"Error deleting directory {directory}: {ex.Message}"); + } + } + } + } + + private long GetDirectorySize(string directory) + { + long size = 0; + try + { + // Add size of files in the directory + size += Directory.GetFiles(directory).Sum(file => new FileInfo(file).Length); + + // Add size of files in subdirectories + foreach (var subdirectory in Directory.GetDirectories(directory)) + { + size += GetDirectorySize(subdirectory); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error calculating size for directory {directory}: {ex.Message}"); + } + return size; + } + + public async Task Start(IPAddress ipAddress = null, int port = 5555) + { + if (ipAddress == null) + { + ipAddress = IPAddress.Any; + } + + _cts = new CancellationTokenSource(); + _isRunning = true; + + if (_useUdp) + { + _udpListener = new UdpClient(port); + Console.WriteLine($"EonaCat UDP Log Server started on port {port}..."); + await ListenUdpAsync(); + } + else + { + _tcpListener = new TcpListener(ipAddress, port); + _tcpListener.Start(); + Console.WriteLine($"EonaCat TCP Log Server started on port {port}..."); + await ListenTcpAsync(); + } + } + + private async Task ListenTcpAsync() + { + try + { + while (!_cts.Token.IsCancellationRequested) + { + TcpClient client = await _tcpListener.AcceptTcpClientAsync(); + _ = Task.Run(() => HandleTcpClient(client)); + } + } + catch (OperationCanceledException) + { + Console.WriteLine("TCP Server stopping..."); + } + } + + private async Task ListenUdpAsync() + { + try + { + while (!_cts.Token.IsCancellationRequested) + { + // Increased buffer size for UDP + UdpReceiveResult result = await _udpListener.ReceiveAsync(); + string logData = Encoding.UTF8.GetString(result.Buffer); + + // If the received data is too large, process it in chunks + if (result.Buffer.Length > UdpBufferSize) + { + // Handle fragmentation and reassembly (this is a basic placeholder logic) + Console.WriteLine("Received large UDP data. Handling fragmentation."); + await ProcessLargeDataAsync(result.Buffer); + } + else + { + Console.WriteLine($"Received UDP Log: {logData}"); + await ProcessLogAsync(logData); + } + } + } + catch (OperationCanceledException) + { + Console.WriteLine("UDP Server stopping..."); + } + } + + private async Task ProcessLargeDataAsync(byte[] data) + { + // You can implement your own logic here for processing large UDP data, such as fragmentation handling + string largeDataString = Encoding.UTF8.GetString(data); + await ProcessLogAsync(largeDataString); + } + + public void Stop() + { + if (_isRunning) + { + _cts.Cancel(); + + // Proper cleanup of resources + _cts.Dispose(); + if (_useUdp) + { + _udpListener?.Close(); + _udpListener?.Dispose(); + } + else + { + _tcpListener?.Stop(); + _tcpListener?.Server?.Dispose(); // Dispose of the socket (if any) + } + + _isRunning = false; + Console.WriteLine("EonaCat Log Server stopped."); + } + } + + private async Task HandleTcpClient(TcpClient client) + { + try + { + using (NetworkStream stream = client.GetStream()) + using (StreamReader reader = new StreamReader(stream, Encoding.UTF8)) + { + char[] buffer = new char[8192]; // 8KB buffer size for large data + int bytesRead; + StringBuilder logData = new StringBuilder(); + + while ((bytesRead = await reader.ReadAsync(buffer, 0, buffer.Length)) > 0) + { + logData.Append(new string(buffer, 0, bytesRead)); + } + + Console.WriteLine($"Received TCP Log: {logData.ToString()}"); + await ProcessLogAsync(logData.ToString()); + } + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + finally + { + // Ensure client is properly disposed + client.Close(); + client.Dispose(); + } + } + } +} \ No newline at end of file diff --git a/EonaCat.Logger.sln b/EonaCat.Logger.sln index 9375bee..80caf80 100644 --- a/EonaCat.Logger.sln +++ b/EonaCat.Logger.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EonaCat.Logger", "EonaCat.L EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EonaCat.Logger.Test.Web", "Testers\EonaCat.Logger.Test.Web\EonaCat.Logger.Test.Web.csproj", "{DBEEF0B0-68AF-4A98-80B9-17FFCDB96E38}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EonaCat.Logger.Server", "EonaCat.Logger.Server\EonaCat.Logger.Server.csproj", "{5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,18 @@ Global {DBEEF0B0-68AF-4A98-80B9-17FFCDB96E38}.Release|x64.Build.0 = Release|Any CPU {DBEEF0B0-68AF-4A98-80B9-17FFCDB96E38}.Release|x86.ActiveCfg = Release|Any CPU {DBEEF0B0-68AF-4A98-80B9-17FFCDB96E38}.Release|x86.Build.0 = Release|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Debug|x64.ActiveCfg = Debug|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Debug|x64.Build.0 = Debug|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Debug|x86.ActiveCfg = Debug|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Debug|x86.Build.0 = Debug|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Release|Any CPU.Build.0 = Release|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Release|x64.ActiveCfg = Release|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Release|x64.Build.0 = Release|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Release|x86.ActiveCfg = Release|Any CPU + {5A5B4709-C2D9-4AD6-BF7F-A97B8BC98255}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/EonaCat.Logger/Managers/LogManager.cs b/EonaCat.Logger/Managers/LogManager.cs index 113a8ae..dcb6e5c 100644 --- a/EonaCat.Logger/Managers/LogManager.cs +++ b/EonaCat.Logger/Managers/LogManager.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; using EonaCat.Logger.EonaCatCoreLogger;