using System.IO.Compression; using NLog; using NLog.Config; using NLog.Layouts; using OliverBooth.Logging; using LogLevel = NLog.LogLevel; namespace OliverBooth.Services; /// /// Represents a class which implements a logging service that supports multiple log targets. /// /// /// This class implements a logging structure similar to that of Minecraft, where historic logs are compressed to a .gz and /// the latest log is found in logs/latest.log. /// internal sealed class LoggingService : BackgroundService { private const string LogFileName = "logs/latest.log"; /// /// Initializes a new instance of the class. /// public LoggingService() { LogFile = new FileInfo(LogFileName); } /// /// Gets or sets the log file. /// /// The log file. public FileInfo LogFile { get; set; } /// /// Archives any existing log files. /// public async Task ArchiveLogFilesAsync(bool archiveToday = true) { var latestFile = new FileInfo(LogFile.FullName); if (!latestFile.Exists) return; DateTime lastWrite = latestFile.LastWriteTime; string lastWriteDate = $"{lastWrite:yyyy-MM-dd}"; var version = 0; string name; if (!archiveToday && lastWrite.Date == DateTime.Today) return; while (File.Exists(name = Path.Combine(LogFile.Directory!.FullName, $"{lastWriteDate}-{++version}.log.gz"))) { // body ignored } await using (FileStream source = latestFile.OpenRead()) { await using FileStream output = File.Create(name); await using var gzip = new GZipStream(output, CompressionMode.Compress); await source.CopyToAsync(gzip); } latestFile.Delete(); } /// protected override Task ExecuteAsync(CancellationToken stoppingToken) { LogFile.Directory?.Create(); LogManager.Setup(builder => builder.SetupExtensions(extensions => { extensions.RegisterLayoutRenderer("TheTime", info => info.TimeStamp.ToString("HH:mm:ss")); extensions.RegisterLayoutRenderer("ServiceName", info => info.LoggerName); })); Layout? layout = Layout.FromString("[${TheTime} ${level:uppercase=true}] [${ServiceName}] ${message}"); var config = new LoggingConfiguration(); var fileLogger = new LogFileTarget("FileLogger", this) { Layout = layout }; var consoleLogger = new ColorfulConsoleTarget("ConsoleLogger") { Layout = layout }; #if DEBUG LogLevel minLevel = LogLevel.Debug; #else LogLevel minLevel = LogLevel.Info; if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("ENABLE_DEBUG_LOGGING"))) minLevel = LogLevel.Debug; #endif config.AddRule(minLevel, LogLevel.Fatal, consoleLogger); config.AddRule(minLevel, LogLevel.Fatal, fileLogger); LogManager.Configuration = config; return ArchiveLogFilesAsync(); } }