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); } } }