diff --git a/EonaCat.Logger/EonaCat.Logger.csproj b/EonaCat.Logger/EonaCat.Logger.csproj index 90777fd..490031b 100644 --- a/EonaCat.Logger/EonaCat.Logger.csproj +++ b/EonaCat.Logger/EonaCat.Logger.csproj @@ -7,7 +7,7 @@ net7.0; icon.ico - 1.1.5 + 1.1.6 EonaCat (Jeroen Saey) true EonaCat (Jeroen Saey) @@ -43,6 +43,7 @@ + diff --git a/EonaCat.Logger/EonaCat.Logger.csproj.DotSettings b/EonaCat.Logger/EonaCat.Logger.csproj.DotSettings new file mode 100644 index 0000000..16c7a3d --- /dev/null +++ b/EonaCat.Logger/EonaCat.Logger.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp110 \ No newline at end of file diff --git a/EonaCat.Logger/EonaCatCoreLogger/Extensions/FileLoggerFactoryExtensions.cs b/EonaCat.Logger/EonaCatCoreLogger/Extensions/FileLoggerFactoryExtensions.cs index 87191d9..baf295e 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/Extensions/FileLoggerFactoryExtensions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/Extensions/FileLoggerFactoryExtensions.cs @@ -47,6 +47,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Extensions options.FileSizeLimit = fileLoggerOptions.FileSizeLimit; options.IsEnabled = fileLoggerOptions.IsEnabled; options.MaxRolloverFiles = fileLoggerOptions.MaxRolloverFiles; + options.UseLocalTime = fileLoggerOptions.UseLocalTime; } ); diff --git a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs index 11e80f8..862fb5b 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/FileLoggerOptions.cs @@ -70,6 +70,11 @@ namespace EonaCat.Logger.EonaCatCoreLogger } } + /// + /// Determines if we need to use the local time in the logging or UTC (default:false) + /// + public bool UseLocalTime { get; set; } + /// /// Gets or sets a strictly positive value representing the maximum retained file rollovers or null for no limit. /// Defaults to 10. diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs index 2291646..184962d 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLogger.cs @@ -14,8 +14,10 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal private LoggerSettings _loggerSettings; private readonly BatchingLoggerProvider _provider; private readonly string _category; + private DateTimeOffset CurrentDateTimeOffset => _loggerSettings.UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow; + private DateTime CurrentDateTme => _loggerSettings.UseLocalTime ? DateTime.Now : DateTime.UtcNow; - public BatchingLogger(BatchingLoggerProvider loggerProvider, string categoryName, LoggerSettings loggerSettings = null) + public BatchingLogger(BatchingLoggerProvider loggerProvider, string categoryName, LoggerSettings loggerSettings) { _loggerSettings = loggerSettings; _provider = loggerProvider; @@ -41,7 +43,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal _loggerSettings = new LoggerSettings(); } - var message = LogHelper.FormatMessageWithHeader(_loggerSettings, logLevel.FromLogLevel(), formatter(state, exception), DateTime.Now) + Environment.NewLine; + var message = LogHelper.FormatMessageWithHeader(_loggerSettings, logLevel.FromLogLevel(), formatter(state, exception), timestamp.DateTime) + Environment.NewLine; if (exception != null) { message = exception.FormatExceptionToMessage() + Environment.NewLine; @@ -63,7 +65,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { - Log(DateTimeOffset.Now, logLevel, eventId, state, exception, formatter); + Log(CurrentDateTimeOffset, logLevel, eventId, state, exception, formatter); } } } \ No newline at end of file diff --git a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs index 142de19..6c2d677 100644 --- a/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs +++ b/EonaCat.Logger/EonaCatCoreLogger/Internal/BatchingLoggerProvider.cs @@ -14,6 +14,9 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal public abstract class BatchingLoggerProvider : ILoggerProvider { + protected DateTimeOffset CurrentDateTimeOffset => UseLocalTime ? DateTimeOffset.Now : DateTimeOffset.UtcNow; + protected DateTime CurrentDateTme => UseLocalTime ? DateTime.Now : DateTime.UtcNow; + private readonly List _currentBatch = new List(); private readonly TimeSpan _interval; private readonly int _batchSize; @@ -21,6 +24,9 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal private ConcurrentQueue _messageQueue; private Task _outputTask; private CancellationTokenSource _cancellationTokenSource; + private LoggerSettings _loggerSettings; + + protected bool UseLocalTime { get; set; } protected BatchingLoggerProvider(IOptions options) { @@ -31,18 +37,37 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal throw new ArgumentOutOfRangeException(nameof(loggerOptions.FlushPeriod), $"{nameof(loggerOptions.FlushPeriod)} must be longer than zero."); } + if (options is FileLoggerOptions fileLoggerOptions) + { + UseLocalTime = fileLoggerOptions.UseLocalTime; + } + _interval = loggerOptions.FlushPeriod; _batchSize = loggerOptions.BatchSize; Start(); } + protected LoggerSettings LoggerSettings + { + get + { + if (_loggerSettings != null) return _loggerSettings; + + _loggerSettings = new LoggerSettings(); + _loggerSettings.UseLocalTime = UseLocalTime; + return _loggerSettings; + } + + set => _loggerSettings = value; + } + protected abstract Task WriteMessagesAsync(IEnumerable messages, CancellationToken token); private async Task ProcessLogQueueAsync(object state) { var startupMessage = $"{DllInfo.ApplicationName} started.{Environment.NewLine}"; - startupMessage = LogHelper.FormatMessageWithHeader(new LoggerSettings(), ELogType.INFO, startupMessage, DateTime.Now); + startupMessage = LogHelper.FormatMessageWithHeader(LoggerSettings, ELogType.INFO, startupMessage, CurrentDateTme); AddMessage(DateTimeOffset.Now, startupMessage); @@ -77,7 +102,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal await IntervalAsync(_interval, _cancellationTokenSource.Token).ConfigureAwait(false); } - await WriteMessagesAsync(new List { new LogMessage { Message = $"[{DllInfo.ApplicationName}] {DllInfo.ApplicationName} stopped.{Environment.NewLine}", Timestamp = DateTimeOffset.Now } }, _cancellationTokenSource.Token).ConfigureAwait(false); + await WriteMessagesAsync(new List { new LogMessage { Message = $"[{DllInfo.ApplicationName}] {DllInfo.ApplicationName} stopped.{Environment.NewLine}", Timestamp = CurrentDateTimeOffset } }, _cancellationTokenSource.Token).ConfigureAwait(false); } protected virtual Task IntervalAsync(TimeSpan interval, CancellationToken cancellationToken) @@ -132,7 +157,7 @@ namespace EonaCat.Logger.EonaCatCoreLogger.Internal public ILogger CreateLogger(string categoryName) { - return new BatchingLogger(this, categoryName); + return new BatchingLogger(this, categoryName, LoggerSettings); } } } \ No newline at end of file diff --git a/EonaCat.Logger/Managers/LogHelper.cs b/EonaCat.Logger/Managers/LogHelper.cs index e6992c6..59f60c9 100644 --- a/EonaCat.Logger/Managers/LogHelper.cs +++ b/EonaCat.Logger/Managers/LogHelper.cs @@ -22,7 +22,7 @@ namespace EonaCat.Logger.Managers if (settings != null) { if (sb.ToString().Contains("{ts}")) - sb.Replace("{ts}", dateTime.ToString(settings.TimestampFormat)); + sb.Replace("{ts}", dateTime.ToString(settings.TimestampFormat) + " " + (settings.UseLocalTime ? "[LOCAL]" : "[UTC]")); if (sb.ToString().Contains("{host}")) sb.Replace("{host}", $"[Host:{Dns.GetHostName()}]"); diff --git a/EonaCat.Logger/Managers/LogManager.cs b/EonaCat.Logger/Managers/LogManager.cs index b15dcad..c614b95 100644 --- a/EonaCat.Logger/Managers/LogManager.cs +++ b/EonaCat.Logger/Managers/LogManager.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.IO; -using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using EonaCat.Logger.EonaCatCoreLogger; @@ -16,6 +15,7 @@ namespace EonaCat.Logger.Managers { public partial class LogManager : ILogManager, IDisposable { + private DateTime CurrentDateTme => Settings.UseLocalTime ? DateTime.Now : DateTime.UtcNow; private DateTime _logDate; public ILoggerProvider LoggerProvider { get; private set; } public ILoggerFactory LoggerFactory { get; private set; } @@ -30,7 +30,7 @@ namespace EonaCat.Logger.Managers public static LogManager Instance => InstanceInit(); - public LoggerSettings Settings { get; } = CreateDefaultSettings(); + public LoggerSettings Settings { get; set; } = CreateDefaultSettings(); private static LogManager InstanceInit() { @@ -62,9 +62,7 @@ namespace EonaCat.Logger.Managers if (_tokenSource.IsCancellationRequested) return; - DateTime now = DateTime.UtcNow; - - if (IsRunning && now.Date > _logDate.Date) + if (IsRunning && CurrentDateTme.Date > _logDate.Date) await StopLoggingAsync().ConfigureAwait(false); IsRunning = true; @@ -73,7 +71,7 @@ namespace EonaCat.Logger.Managers Directory.CreateDirectory(Settings.FileLoggerOptions.LogDirectory); - _logDate = now; + _logDate = CurrentDateTme; } private void CreateLogger() @@ -91,6 +89,7 @@ namespace EonaCat.Logger.Managers configuration.LogDirectory = fileLoggerOptions.LogDirectory; configuration.FileNamePrefix = fileLoggerOptions.FileNamePrefix; configuration.MaxRolloverFiles = fileLoggerOptions.MaxRolloverFiles; + configuration.UseLocalTime = Settings.UseLocalTime; })); var serviceProvider = serviceCollection.BuildServiceProvider(); @@ -172,7 +171,7 @@ namespace EonaCat.Logger.Managers { AppDomain.CurrentDomain.ProcessExit += ProcessExit; Console.CancelKeyPress += Console_CancelKeyPress; - _logDate = DateTime.UtcNow; + _logDate = CurrentDateTme; } private void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e) @@ -188,7 +187,7 @@ namespace EonaCat.Logger.Managers private Task StopLoggingAsync() { IsRunning = false; - InternalWriteAsync(DateTime.UtcNow, $"{DllInfo.ApplicationName} stopped."); + InternalWriteAsync(CurrentDateTme, $"{DllInfo.ApplicationName} stopped."); return Task.Delay(500); } @@ -217,12 +216,10 @@ namespace EonaCat.Logger.Managers if (logType == ELogType.NONE) return; - var now = DateTime.UtcNow; - if (!IsRunning) StartNewLogAsync().ConfigureAwait(false); - InternalWriteAsync(now, message, logType, writeToConsole); + InternalWriteAsync(CurrentDateTme, message, logType, writeToConsole); } } } diff --git a/EonaCat.Logger/Managers/LoggerSettings.cs b/EonaCat.Logger/Managers/LoggerSettings.cs index 2d43dc5..ce36a74 100644 --- a/EonaCat.Logger/Managers/LoggerSettings.cs +++ b/EonaCat.Logger/Managers/LoggerSettings.cs @@ -14,6 +14,11 @@ namespace EonaCat.Logger.Managers public event LogDelegate OnLog; public delegate void LogDelegate(EonaCatLogMessage message); + /// + /// Determines if we need to use the local time in the logging or UTC (default:false) + /// + public bool UseLocalTime { get; set; } + public string Id { get; set; } = "EonaCatLogger"; /// @@ -135,7 +140,7 @@ namespace EonaCat.Logger.Managers } } - private FileLoggerOptions CreateDefaultFileLoggerOptions() + private static FileLoggerOptions CreateDefaultFileLoggerOptions() { return new FileLoggerOptions(); } diff --git a/Testers/EonaCat.Logger.Test.Web/Logger.cs b/Testers/EonaCat.Logger.Test.Web/Logger.cs index d527a26..dedaa7c 100644 --- a/Testers/EonaCat.Logger.Test.Web/Logger.cs +++ b/Testers/EonaCat.Logger.Test.Web/Logger.cs @@ -1,7 +1,6 @@ using EonaCat.Logger.Managers; -using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Mvc; -using System.Net; +using System.IO.Compression; +using EonaCat.Logger.Extensions; namespace EonaCat.Logger.Test.Web { @@ -30,114 +29,64 @@ namespace EonaCat.Logger.Test.Web LogManager.DeleteCurrentLogFile(); } - internal static IActionResult DownloadLogAsync(HttpResponse response, string logFile) + private static string ConvertToAbsolutePath(string path) { - 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(); + return Path.IsPathRooted(path) ? path : Path.Combine(LogFolder, path); } - private static void DisableResponseBuffering(HttpResponse response) + public static async Task DownloadLogAsync(HttpContext context, string logName, long limit) { - var httpContext = response.HttpContext; - var responseBodyFeature = httpContext.Features.Get(); - if (responseBodyFeature != null) + var logFileName = logName + ".log"; + + await using var fS = new FileStream(Path.Combine(ConvertToAbsolutePath(LogFolder), logFileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 64 * 1024, true); + var response = context.Response; + + response.ContentType = "text/plain"; + response.Headers.ContentDisposition = "attachment;filename=" + logFileName; + + if ((limit > fS.Length) || (limit < 1)) + limit = fS.Length; + + var oFS = new OffsetStream(fS, 0, limit); + var request = context.Request; + Stream s; + + string acceptEncoding = request.Headers["Accept-Encoding"]; + if (string.IsNullOrEmpty(acceptEncoding)) { - responseBodyFeature.DisableBuffering(); + s = response.Body; } - } - - 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)) + else { - try + string[] acceptEncodingParts = acceptEncoding.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + if (acceptEncodingParts.Contains("br")) { - // 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; - } - } + response.Headers.ContentEncoding = "br"; + s = new BrotliStream(response.Body, CompressionMode.Compress); } - catch (OperationCanceledException) + else if (acceptEncodingParts.Contains("gzip")) { - // The operation was canceled, clean up if necessary - fileStream.Close(); + response.Headers.ContentEncoding = "gzip"; + s = new GZipStream(response.Body, CompressionMode.Compress); } - finally + else if (acceptEncodingParts.Contains("deflate")) { - - try - { - // Flush and close the output stream - await outputStream.FlushAsync(cancellationToken).ConfigureAwait(false); - } - catch (ObjectDisposedException e) - { - Log(e, "StreamFileContentAsync"); - } - finally - { - outputStream?.Close(); - } + response.Headers.ContentEncoding = "deflate"; + s = new DeflateStream(response.Body, CompressionMode.Compress); } + else + { + s = response.Body; + } + } + + await using (s) + { + await oFS.CopyToAsync(s).ConfigureAwait(false); + + if (fS.Length > limit) + await s.WriteAsync("\r\n####__EONACATLOGGER_TRUNCATED___####"u8.ToArray()).ConfigureAwait(false); } }