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.LogStack.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(); } } } }