diff --git a/Testers/EonaCat.Logger.Test.Web/Logger.cs b/Testers/EonaCat.Logger.Test.Web/Logger.cs new file mode 100644 index 0000000..d527a26 --- /dev/null +++ b/Testers/EonaCat.Logger.Test.Web/Logger.cs @@ -0,0 +1,163 @@ +using EonaCat.Logger.Managers; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc; +using System.Net; + +namespace EonaCat.Logger.Test.Web +{ + public static class Logger + { + private static readonly LogManager LogManager = new LogManager(new LoggerSettings + { + Id = "EonaCatDnsLogger", + MaxLogType = ELogType.TRACE, + FileLoggerOptions = + { + LogDirectory = "Logs", + FileSizeLimit = 20_000_000, // 20 MB + } + }); + + public static string LogFolder => "Logs"; + public static string CurrentLogFile => LogManager.CurrentLogFile; + public static bool IsDisabled { get; set; } + + public static void DeleteCurrentLogFile() + { + if (IsDisabled) + return; + + LogManager.DeleteCurrentLogFile(); + } + + internal static IActionResult DownloadLogAsync(HttpResponse response, string logFile) + { + if (IsDisabled) + { + response.StatusCode = (int)HttpStatusCode.BadRequest; + return null; + } + + // Check if the file exists + if (!File.Exists(logFile)) + { + response.StatusCode = (int)HttpStatusCode.BadRequest; + return null; + } + + // Create a cancellation token source to handle cancellation + var cancellationTokenSource = new CancellationTokenSource(); + + // Start streaming the file content to the response + Task.Run(() => StreamFileContentAsync(response, logFile, response.Body, cancellationTokenSource.Token), cancellationTokenSource.Token); + + // Return an empty result to indicate that streaming has started + return new EmptyResult(); + } + + private static void DisableResponseBuffering(HttpResponse response) + { + var httpContext = response.HttpContext; + var responseBodyFeature = httpContext.Features.Get(); + if (responseBodyFeature != null) + { + responseBodyFeature.DisableBuffering(); + } + } + + private static async Task StreamFileContentAsync(HttpResponse response, string filePath, Stream outputStream, CancellationToken cancellationToken) + { + using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 4096, FileOptions.Asynchronous | FileOptions.SequentialScan)) + { + try + { + // Clear the response first + response.Clear(); + + // Disable response buffering + DisableResponseBuffering(response); + + // Set the response headers + response.Headers.Add("Content-Disposition", $"attachment; filename={Path.GetFileName(filePath)}"); + response.Headers.Add("Content-Type", "application/octet-stream"); + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) > 0) + { + if (cancellationToken.IsCancellationRequested) + { + break; + } + + // Check if the response is completed + if (response.HasStarted) + { + // The response is already completed, so we can't write further + break; + } + + try + { + await outputStream.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + } + catch (Exception) + { + // Do nothing, handle any specific error if needed + } + + // Check if the file has been modified + if (File.GetLastWriteTimeUtc(filePath) > File.GetLastWriteTimeUtc(fileStream.Name)) + { + // Close the existing stream and reopen the file with updated contents + fileStream.Close(); + await StreamFileContentAsync(response, filePath, outputStream, cancellationToken).ConfigureAwait(false); + return; + } + } + } + catch (OperationCanceledException) + { + // The operation was canceled, clean up if necessary + fileStream.Close(); + } + finally + { + + try + { + // Flush and close the output stream + await outputStream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + catch (ObjectDisposedException e) + { + Log(e, "StreamFileContentAsync"); + } + finally + { + outputStream?.Close(); + } + } + } + } + + + + + public static void Log(string message, ELogType logType = ELogType.INFO, bool writeToConsole = true) + { + if (IsDisabled) + return; + + LogManager.Write(message, logType, writeToConsole); + } + + public static void Log(Exception exception, string message = "", bool writeToConsole = true) + { + if (IsDisabled) + return; + + LogManager.Write(exception, module: message, writeToConsole: writeToConsole); + } + } +} diff --git a/Testers/EonaCat.Logger.Test.Web/Program.cs b/Testers/EonaCat.Logger.Test.Web/Program.cs index 354605c..d684959 100644 --- a/Testers/EonaCat.Logger.Test.Web/Program.cs +++ b/Testers/EonaCat.Logger.Test.Web/Program.cs @@ -2,6 +2,7 @@ using EonaCat.Logger; using EonaCat.Logger.EonaCatCoreLogger; using EonaCat.Logger.EonaCatCoreLogger.Extensions; using EonaCat.Logger.Managers; +using EonaCat.Logger.Test.Web; using EonaCat.Web.RateLimiter; using EonaCat.Web.RateLimiter.Endpoints.Extensions; using EonaCat.Web.Tracer.Extensions; @@ -112,6 +113,7 @@ void RunLoggingExceptionTests() } } +Task.Run(RunWebLoggerTests); Task.Run(RunWebLoggingTests); Task.Run(RunLoggingTests); Task.Run(RunLoggingExceptionTests); @@ -174,4 +176,21 @@ void RunLoggingTests() } } +void RunWebLoggerTests() +{ + for (int i = 0; i < 9000000; i++) + { + Logger.Log($"test via logger {i} INFO", ELogType.INFO); + Logger.Log($"test via logger {i} CRITICAL", ELogType.CRITICAL); + Logger.Log($"test via logger {i} DEBUG", ELogType.DEBUG); + Logger.Log($"test via logger {i} ERROR", ELogType.ERROR); + Logger.Log($"test via logger {i} TRACE", ELogType.TRACE); + Logger.Log($"test via logger {i} TRAFFIC", ELogType.TRAFFIC); + Logger.Log($"test via logger {i} WARNING", ELogType.WARNING); + Logger.Log($"test via logger {i} NONE", ELogType.NONE); + Console.WriteLine($"Logger: {i}"); + Task.Delay(1); + } +} + app.Run();