diff --git a/Benchmarks/ClonerBenchmarks.cs b/Benchmarks/ClonerBenchmarks.cs index fc2f8d03..6f5a3f15 100644 --- a/Benchmarks/ClonerBenchmarks.cs +++ b/Benchmarks/ClonerBenchmarks.cs @@ -20,7 +20,7 @@ public class ClonerBenchmarks public void Setup() { var jsonUtil = new JsonUtil(); - var importer = new ImporterUtil(new MockLogger(), new FileUtil(new MockLogger()), + var importer = new ImporterUtil(new MockLogger(), new FileUtil(), jsonUtil); var loadTask = importer.LoadRecursiveAsync("./Assets/database/templates/"); loadTask.Wait(); diff --git a/Benchmarks/Mock/MockLogger.cs b/Benchmarks/Mock/MockLogger.cs index 1907879d..25a6ee1d 100644 --- a/Benchmarks/Mock/MockLogger.cs +++ b/Benchmarks/Mock/MockLogger.cs @@ -41,6 +41,17 @@ public class MockLogger : ISptLogger Console.WriteLine(data); } + public void Log( + LogLevel level, + string data, + LogTextColor? textColor = null, + LogBackgroundColor? backgroundColor = null, + Exception? ex = null + ) + { + throw new NotImplementedException(); + } + public void WriteToLogFile(string body, LogLevel level = LogLevel.Info) { throw new NotImplementedException(); @@ -51,6 +62,11 @@ public class MockLogger : ISptLogger return false; } + public void DumpAndStop() + { + throw new NotImplementedException(); + } + public void LogWithColor( string data, Exception? ex = null, diff --git a/Libraries/SPTarkov.Server.Core/Controllers/ClientLogController.cs b/Libraries/SPTarkov.Server.Core/Controllers/ClientLogController.cs index ca1542ec..f041c221 100644 --- a/Libraries/SPTarkov.Server.Core/Controllers/ClientLogController.cs +++ b/Libraries/SPTarkov.Server.Core/Controllers/ClientLogController.cs @@ -22,27 +22,6 @@ public class ClientLogController( var color = logRequest.Color ?? LogTextColor.White; var backgroundColor = logRequest.BackgroundColor ?? LogBackgroundColor.Default; - switch (logRequest.Level) - { - case LogLevel.Error: - _logger.Error(message); - break; - case LogLevel.Warn: - _logger.Warning(message); - break; - case LogLevel.Success: - case LogLevel.Info: - _logger.Info(message); - break; - case LogLevel.Custom: - _logger.LogWithColor(message, color, backgroundColor); - break; - case LogLevel.Debug: - _logger.Debug(message); - break; - default: - _logger.Info(message); - break; - } + _logger.Log(logRequest.Level ?? LogLevel.Info, message, color, backgroundColor); } } diff --git a/Libraries/SPTarkov.Server.Core/Models/Spt/Logging/LogLevel.cs b/Libraries/SPTarkov.Server.Core/Models/Spt/Logging/LogLevel.cs index 6be6a5ec..d88d7e7e 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Spt/Logging/LogLevel.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Spt/Logging/LogLevel.cs @@ -2,12 +2,11 @@ public enum LogLevel { + // The order are very important for the logger to calculate properly the logging level, do not change! Fatal, Error, Warn, - Success, Info, - Custom, Debug, Trace } diff --git a/Libraries/SPTarkov.Server.Core/Models/Utils/ISptLogger.cs b/Libraries/SPTarkov.Server.Core/Models/Utils/ISptLogger.cs index e0b4b892..453508c5 100644 --- a/Libraries/SPTarkov.Server.Core/Models/Utils/ISptLogger.cs +++ b/Libraries/SPTarkov.Server.Core/Models/Utils/ISptLogger.cs @@ -12,6 +12,7 @@ public interface ISptLogger void Info(string data, Exception? ex = null); void Debug(string data, Exception? ex = null); void Critical(string data, Exception? ex = null); - void WriteToLogFile(string body, LogLevel level = LogLevel.Info); + void Log(LogLevel level, string data, LogTextColor? textColor = null, LogBackgroundColor? backgroundColor = null, Exception? ex = null); bool IsLogEnabled(LogLevel level); + void DumpAndStop(); } diff --git a/Libraries/SPTarkov.Server.Core/Routers/ItemEventRouter.cs b/Libraries/SPTarkov.Server.Core/Routers/ItemEventRouter.cs index 30593387..bcac3883 100644 --- a/Libraries/SPTarkov.Server.Core/Routers/ItemEventRouter.cs +++ b/Libraries/SPTarkov.Server.Core/Routers/ItemEventRouter.cs @@ -6,42 +6,21 @@ using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Services; using SPTarkov.Server.Core.Utils; using SPTarkov.Server.Core.Utils.Cloners; +using SPTarkov.Server.Core.Utils.Logger; using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; namespace SPTarkov.Server.Core.Routers; [Injectable] -public class ItemEventRouter +public class ItemEventRouter(ISptLogger logger, + ISptLogger fileLogger, + JsonUtil jsonUtil, + ProfileHelper profileHelper, + LocalisationService localisationService, + EventOutputHolder eventOutputHolder, + IEnumerable itemEventRouters, + ICloner cloner) { - protected ICloner _cloner; - protected EventOutputHolder _eventOutputHolder; - protected HttpResponseUtil _httpResponseUtil; - protected List _itemEventRouters; - protected JsonUtil _jsonUtil; - protected LocalisationService _localisationService; - protected ISptLogger _logger; - protected ProfileHelper _profileHelper; - - public ItemEventRouter( - ISptLogger logger, - HttpResponseUtil httpResponseUtil, - JsonUtil jsonUtil, - ProfileHelper profileHelper, - LocalisationService localisationService, - EventOutputHolder eventOutputHolder, - IEnumerable itemEventRouters, - ICloner cloner - ) - { - _logger = logger; - _httpResponseUtil = httpResponseUtil; - _jsonUtil = jsonUtil; - _profileHelper = profileHelper; - _localisationService = localisationService; - _eventOutputHolder = eventOutputHolder; - _itemEventRouters = itemEventRouters.ToList(); - _cloner = cloner; - } /// /// Handles ItemEventRouter Requests and processes them. @@ -51,24 +30,24 @@ public class ItemEventRouter /// Item response public ItemEventRouterResponse HandleEvents(ItemEventRouterRequest info, string sessionID) { - var output = _eventOutputHolder.GetOutput(sessionID); + var output = eventOutputHolder.GetOutput(sessionID); foreach (var body in info.Data) { - var pmcData = _profileHelper.GetPmcProfile(sessionID); + var pmcData = profileHelper.GetPmcProfile(sessionID); - var eventRouter = _itemEventRouters.FirstOrDefault(r => r.CanHandle(body.Action)); + var eventRouter = itemEventRouters.FirstOrDefault(r => r.CanHandle(body.Action)); if (eventRouter is null) { - _logger.Error(_localisationService.GetText("event-unhandled_event", body.Action)); - _logger.WriteToLogFile(_jsonUtil.Serialize(info.Data)); + logger.Error(localisationService.GetText("event-unhandled_event", body.Action)); + fileLogger.Info(jsonUtil.Serialize(info.Data)); continue; } - if (_logger.IsLogEnabled(LogLevel.Debug)) + if (logger.IsLogEnabled(LogLevel.Debug)) { - _logger.Debug($"event: {body.Action}"); + logger.Debug($"event: {body.Action}"); } eventRouter.HandleItemEvent(body.Action, pmcData, body, sessionID, output); @@ -78,11 +57,11 @@ public class ItemEventRouter } } - _eventOutputHolder.UpdateOutputProperties(sessionID); + eventOutputHolder.UpdateOutputProperties(sessionID); // Clone output before resetting the output object ready for use next time - var outputClone = _cloner.Clone(output); - _eventOutputHolder.ResetOutput(sessionID); + var outputClone = cloner.Clone(output); + eventOutputHolder.ResetOutput(sessionID); return outputClone; } diff --git a/Libraries/SPTarkov.Server.Core/Servers/SaveServer.cs b/Libraries/SPTarkov.Server.Core/Servers/SaveServer.cs index 70d7341f..de833a5b 100644 --- a/Libraries/SPTarkov.Server.Core/Servers/SaveServer.cs +++ b/Libraries/SPTarkov.Server.Core/Servers/SaveServer.cs @@ -273,7 +273,11 @@ public class SaveServer( if (profiles.ContainsKey(sessionID)) { profiles.TryRemove(sessionID, out _); - _fileUtil.DeleteFile(file); + if (!_fileUtil.DeleteFile(file)) + { + _logger.Error($"Unable to delete file, not found: {file}"); + } + } return !_fileUtil.FileExists(file); diff --git a/Libraries/SPTarkov.Server.Core/Services/BackupService.cs b/Libraries/SPTarkov.Server.Core/Services/BackupService.cs index 2981e088..8c3b43f5 100644 --- a/Libraries/SPTarkov.Server.Core/Services/BackupService.cs +++ b/Libraries/SPTarkov.Server.Core/Services/BackupService.cs @@ -123,7 +123,10 @@ public class BackupService // Create absolute path to file var relativeSourceFilePath = Path.Combine(_profileDir, profileFileName); var absoluteDestinationFilePath = Path.Combine(targetDir, profileFileName); - _fileUtil.CopyFile(relativeSourceFilePath, absoluteDestinationFilePath); + if (!_fileUtil.CopyFile(relativeSourceFilePath, absoluteDestinationFilePath)) + { + _logger.Error($"Source file not found: {relativeSourceFilePath}. Cannot copy to: {absoluteDestinationFilePath}"); + } } // Write a copy of active mods. diff --git a/Libraries/SPTarkov.Server.Core/Utils/FileUtil.cs b/Libraries/SPTarkov.Server.Core/Utils/FileUtil.cs index d2c78772..07643d67 100644 --- a/Libraries/SPTarkov.Server.Core/Utils/FileUtil.cs +++ b/Libraries/SPTarkov.Server.Core/Utils/FileUtil.cs @@ -1,11 +1,9 @@ using SPTarkov.Common.Annotations; -using SPTarkov.Server.Core.Models.Utils; namespace SPTarkov.Server.Core.Utils; [Injectable] -public class FileUtil( - ISptLogger _logger) +public class FileUtil() { protected const string _modBasePath = "user/mods/"; @@ -69,6 +67,11 @@ public class FileUtil( public void WriteFile(string filePath, string fileContent) { + if (!DirectoryExists(Path.GetDirectoryName(filePath))) + { + CreateDirectory(Path.GetDirectoryName(filePath)); + } + if (!FileExists(filePath)) { CreateFile(filePath); @@ -93,16 +96,15 @@ public class FileUtil( stream.Close(); } - public void DeleteFile(string filePath) + public bool DeleteFile(string filePath) { if (!FileExists(filePath)) { - _logger.Error($"Unable to delete file, not found: {filePath}"); - - return; + return false; } File.Delete(filePath); + return true; } /// @@ -111,12 +113,12 @@ public class FileUtil( /// Source file to copy from /// /// Should destination file be overwritten - public void CopyFile(string copyFromPath, string destinationFilePath, bool overwrite = false) + public bool CopyFile(string copyFromPath, string destinationFilePath, bool overwrite = false) { // Check it exists first if (!FileExists(copyFromPath)) { - _logger.Error($"Source file not found: {copyFromPath}. Cannot copy to: {destinationFilePath}"); + return false; } @@ -125,6 +127,7 @@ public class FileUtil( // Copy the file File.Copy(copyFromPath, destinationFilePath, overwrite); + return true; } /// diff --git a/Libraries/SPTarkov.Server.Core/Utils/Json/Converters/BaseSptLoggerReferenceConverter.cs b/Libraries/SPTarkov.Server.Core/Utils/Json/Converters/BaseSptLoggerReferenceConverter.cs new file mode 100644 index 00000000..e75dcad1 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Json/Converters/BaseSptLoggerReferenceConverter.cs @@ -0,0 +1,33 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using SPTarkov.Server.Core.Utils.Logger; + +namespace SPTarkov.Server.Core.Utils.Json.Converters; + +public class BaseSptLoggerReferenceConverter : JsonConverter +{ + public override BaseSptLoggerReference? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + using (var jsonDocument = JsonDocument.ParseValue(ref reader)) + { + if (!jsonDocument.RootElement.TryGetProperty("type", out var typeElement)) + { + throw new Exception("One of the loggers doesnt have a type property defined."); + } + + switch (typeElement.GetString()) + { + case "File": + return jsonDocument.Deserialize(); + case "Console": + return jsonDocument.Deserialize(); + default: + throw new Exception($"The logger type '{typeElement.GetString()}' does not exist."); + } + } + } + + public override void Write(Utf8JsonWriter writer, BaseSptLoggerReference value, JsonSerializerOptions options) + { + } +} diff --git a/Libraries/SPTarkov.Server.Core/Utils/JsonUtil.cs b/Libraries/SPTarkov.Server.Core/Utils/JsonUtil.cs index 0ff79460..ccd1fba7 100644 --- a/Libraries/SPTarkov.Server.Core/Utils/JsonUtil.cs +++ b/Libraries/SPTarkov.Server.Core/Utils/JsonUtil.cs @@ -24,6 +24,7 @@ public class JsonUtil Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, Converters = { + new BaseSptLoggerReferenceConverter(), new ListOrTConverterFactory(), new DictionaryOrListConverter(), new EftEnumConverter(), diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/FileLogger.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/FileLogger.cs index d3a00f84..2c675371 100644 --- a/Libraries/SPTarkov.Server.Core/Utils/Logger/FileLogger.cs +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/FileLogger.cs @@ -1,6 +1,5 @@ namespace SPTarkov.Server.Core.Utils.Logger; -// This is a dummy class to use for SourceContext in Serilog, do not remove! public class FileLogger { } diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/Handlers/BaseLogHandler.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/Handlers/BaseLogHandler.cs new file mode 100644 index 00000000..862dc4c8 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/Handlers/BaseLogHandler.cs @@ -0,0 +1,28 @@ +namespace SPTarkov.Server.Core.Utils.Logger.Handlers; + +public abstract class BaseLogHandler : ILogHandler +{ + public abstract LoggerType LoggerType + { + get; + } + + public abstract void Log(SptLogMessage message, BaseSptLoggerReference reference); + + protected string FormatMessage(string processedMessage, SptLogMessage message, BaseSptLoggerReference reference) + { + var formattedMessage = reference.Format.Replace("%date%", message.LogTime.ToString("yyyy-MM-dd")) + .Replace("%time%", message.LogTime.ToString("HH:mm:ss.fff")) + .Replace("%message%", processedMessage) + .Replace("%loggerShort%", message.Logger.Split('.').Last()) + .Replace("%logger%", message.Logger) + .Replace("%tid%", message.threadId.ToString()) + .Replace("%tname%", message.threadName) + .Replace("%level%", Enum.GetName(message.LogLevel)); + if (message.Exception != null) + { + formattedMessage += $"\n{message.Exception.Message}\n{message.Exception.StackTrace}"; + } + return formattedMessage; + } +} diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/Handlers/ConsoleLogHandler.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/Handlers/ConsoleLogHandler.cs new file mode 100644 index 00000000..1ee8b122 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/Handlers/ConsoleLogHandler.cs @@ -0,0 +1,37 @@ +using SPTarkov.Common.Annotations; +using SPTarkov.Server.Core.Models.Logging; + +namespace SPTarkov.Server.Core.Utils.Logger.Handlers; + +[Injectable(InjectionType.Singleton)] +public class ConsoleLogHandler : BaseLogHandler +{ + public override LoggerType LoggerType => LoggerType.Console; + + public override void Log(SptLogMessage message, BaseSptLoggerReference reference) + { + Console.WriteLine(FormatMessage(GetColorizedText(message.Message, message.TextColor, message.BackgroundColor), message, reference)); + } + + private string GetColorizedText( + string data, + LogTextColor? textColor = null, + LogBackgroundColor? backgroundColor = null + ) + { + var colorString = string.Empty; + if (textColor != null) + { + colorString += ((int) textColor.Value).ToString(); + } + + if (backgroundColor != null) + { + colorString += string.IsNullOrEmpty(colorString) + ? ((int) backgroundColor.Value).ToString() + : $";{((int) backgroundColor.Value).ToString()}"; + } + + return $"\x1b[{colorString}m{data}\x1b[0m"; + } +} diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/Handlers/FileLogHandler.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/Handlers/FileLogHandler.cs new file mode 100644 index 00000000..be6e980b --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/Handlers/FileLogHandler.cs @@ -0,0 +1,35 @@ +using System.Collections.Concurrent; +using System.Text; +using SPTarkov.Common.Annotations; + +namespace SPTarkov.Server.Core.Utils.Logger.Handlers; + +[Injectable(InjectionType.Singleton)] +public class FileLogHandler : BaseLogHandler +{ + private static ConcurrentDictionary _fileLocks = new(); + + public override LoggerType LoggerType => LoggerType.File; + + public override void Log(SptLogMessage message, BaseSptLoggerReference reference) + { + var config = reference as FileSptLoggerReference; + + if (!_fileLocks.TryGetValue(config.FilePath, out var lockObject)) + { + lockObject = new object(); + while (!_fileLocks.TryAdd(config.FilePath, lockObject)) ; + } + + lock (lockObject) + { + if (!Directory.Exists(Path.GetDirectoryName(config.FilePath))) + { + Directory.CreateDirectory(Path.GetDirectoryName(config.FilePath)); + } + + // The AppendAllText will create the file as long as the directory exists + File.AppendAllText(config.FilePath, FormatMessage(message.Message + "\n", message, reference)); + } + } +} diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/ILogHandler.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/ILogHandler.cs new file mode 100644 index 00000000..43ae28b6 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/ILogHandler.cs @@ -0,0 +1,11 @@ +namespace SPTarkov.Server.Core.Utils.Logger; + +public interface ILogHandler +{ + LoggerType LoggerType + { + get; + } + + void Log(SptLogMessage message, BaseSptLoggerReference reference); +} diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/LoggerType.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/LoggerType.cs new file mode 100644 index 00000000..932611e5 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/LoggerType.cs @@ -0,0 +1,2 @@ +namespace SPTarkov.Server.Core.Utils.Logger; + diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogMessage.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogMessage.cs new file mode 100644 index 00000000..fee64c17 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogMessage.cs @@ -0,0 +1,16 @@ +using SPTarkov.Server.Core.Models.Logging; +using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; + +namespace SPTarkov.Server.Core.Utils.Logger; + +public record SptLogMessage( + string Logger, + DateTime LogTime, + LogLevel LogLevel, + int threadId, + string? threadName, + string Message, + Exception? Exception = null, + LogTextColor? TextColor = null, + LogBackgroundColor? BackgroundColor = null +); diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogger.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogger.cs new file mode 100644 index 00000000..81017122 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogger.cs @@ -0,0 +1,197 @@ +using SPTarkov.Common.Annotations; +using SPTarkov.Server.Core.Models.Logging; +using SPTarkov.Server.Core.Models.Utils; +using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; + +namespace SPTarkov.Server.Core.Utils.Logger; + +[Injectable(InjectableTypeOverride = typeof(ISptLogger<>))] +public class SptLogger : ISptLogger, IDisposable +{ + private string _category; + private readonly SptLoggerQueueManager _loggerQueueManager; + + private const string ConfigurationPath = "./sptLogger.json"; + private readonly SptLoggerConfiguration _config; + + ~SptLogger() + { + _loggerQueueManager.DumpAndStop(); + } + + public SptLogger(FileUtil fileUtil, JsonUtil jsonUtil, SptLoggerQueueManager loggerQueueManager) + { + _category = typeof(T).FullName; + _loggerQueueManager = loggerQueueManager; + + if (fileUtil.FileExists(ConfigurationPath)) + { + _config = jsonUtil.DeserializeFromFile(ConfigurationPath); + } + else + { + throw new Exception($"Unable to find SPTLogger file '{ConfigurationPath}'"); + } + + if (_config == null) + { + throw new Exception( + "The configuration path was loaded but it contained invalid or incorrect configuration."); + } + + _loggerQueueManager.Initialize(_config); + } + + public void OverrideCategory(string category) + { + _category = category; + } + + public void LogWithColor( + string data, + LogTextColor? textColor = null, + LogBackgroundColor? backgroundColor = null, + Exception? ex = null + ) + { + _loggerQueueManager.EnqueueMessage( + new SptLogMessage( + _category, + DateTime.UtcNow, + LogLevel.Info, + Environment.CurrentManagedThreadId, + Thread.CurrentThread.Name, + data, + ex, + textColor, + backgroundColor) + ); + } + + public void Success(string data, Exception? ex = null) + { + _loggerQueueManager.EnqueueMessage( + new SptLogMessage( + _category, + DateTime.UtcNow, + LogLevel.Info, + Environment.CurrentManagedThreadId, + Thread.CurrentThread.Name, + data, + ex, + LogTextColor.Green) + ); + } + + public void Error(string data, Exception? ex = null) + { + _loggerQueueManager.EnqueueMessage( + new SptLogMessage( + _category, + DateTime.UtcNow, + LogLevel.Error, + Environment.CurrentManagedThreadId, + Thread.CurrentThread.Name, + data, + ex, + LogTextColor.Red) + ); + } + + public void Warning(string data, Exception? ex = null) + { + _loggerQueueManager.EnqueueMessage( + new SptLogMessage( + _category, + DateTime.UtcNow, + LogLevel.Warn, + Environment.CurrentManagedThreadId, + Thread.CurrentThread.Name, + data, + ex, + LogTextColor.Yellow) + ); + } + + public void Info(string data, Exception? ex = null) + { + _loggerQueueManager.EnqueueMessage( + new SptLogMessage( + _category, + DateTime.UtcNow, + LogLevel.Info, + Environment.CurrentManagedThreadId, + Thread.CurrentThread.Name, + data, + ex) + ); + } + + public void Debug(string data, Exception? ex = null) + { + _loggerQueueManager.EnqueueMessage( + new SptLogMessage( + _category, + DateTime.UtcNow, + LogLevel.Debug, + Environment.CurrentManagedThreadId, + Thread.CurrentThread.Name, + data, + ex, + LogTextColor.Gray) + ); + } + + public void Critical(string data, Exception? ex = null) + { + _loggerQueueManager.EnqueueMessage( + new SptLogMessage( + _category, + DateTime.UtcNow, + LogLevel.Fatal, + Environment.CurrentManagedThreadId, + Thread.CurrentThread.Name, + data, + ex, + LogTextColor.Black, + LogBackgroundColor.Red) + ); + } + + public void Log( + LogLevel level, + string data, + LogTextColor? textColor = null, + LogBackgroundColor? backgroundColor = null, + Exception? ex = null + ) + { + _loggerQueueManager.EnqueueMessage( + new SptLogMessage( + _category, + DateTime.UtcNow, + level, + Environment.CurrentManagedThreadId, + Thread.CurrentThread.Name, + data, + ex, + textColor, + backgroundColor) + ); + } + + public bool IsLogEnabled(LogLevel level) + { + return _config.Loggers.Any(l => l.LogLevel.CanLog(level)); + } + + public void DumpAndStop() + { + _loggerQueueManager.DumpAndStop(); + } + + public void Dispose() + { + _loggerQueueManager.DumpAndStop(); + } +} diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLoggerConfiguration.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLoggerConfiguration.cs new file mode 100644 index 00000000..ae453784 --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLoggerConfiguration.cs @@ -0,0 +1,183 @@ +using System.Collections.Concurrent; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; +using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; + +namespace SPTarkov.Server.Core.Utils.Logger; + +public class SptLoggerConfiguration +{ + [JsonPropertyName("loggers")] + public List Loggers + { + get; + set; + } + + [JsonPropertyName("poolingTimeMs")] + public uint PoolingTimeMs + { + get; + set; + } = 500; +} + +public abstract class BaseSptLoggerReference +{ + [JsonPropertyName("type")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public LoggerType Type + { + get; + set; + } + + [JsonPropertyName("filters")] + public List Filters + { + get; + set; + } + + [JsonPropertyName("logLevel")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public LogLevel LogLevel + { + get; + set; + } + + [JsonPropertyName("format")] + public string Format + { + get; + set; + } +} + +public class SptLoggerFilter +{ + [JsonPropertyName("type")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public SptLoggerFilterType Type + { + get; + set; + } + + [JsonPropertyName("name")] + public string Name + { + get; + set; + } + + [JsonPropertyName("matchingType")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public MatchingType MatchingType + { + get; + set; + } + + protected bool Equals(SptLoggerFilter other) + { + return Type == other.Type && Name == other.Name && MatchingType == other.MatchingType; + } + + public override bool Equals(object? obj) + { + if (obj is null) + { + return false; + } + + if (ReferenceEquals(this, obj)) + { + return true; + } + + if (obj.GetType() != GetType()) + { + return false; + } + + return Equals((SptLoggerFilter) obj); + } + + public override int GetHashCode() + { + return HashCode.Combine((int) Type, Name, (int) MatchingType); + } +} + +public class FileSptLoggerReference : BaseSptLoggerReference +{ + [JsonPropertyName("filePath")] + public string FilePath + { + get; + set; + } +} + +public class ConsoleSptLoggerReference : BaseSptLoggerReference +{ +} + +public enum LoggerType +{ + File, + Console +} + + +public enum MatchingType +{ + Literal, + Regex +} + +public enum SptLoggerFilterType +{ + Exclude, + Include +} + +public static class SptLoggerFilterExtensions +{ + private static ConcurrentDictionary _cachedRegexes = new(); + + public static bool Match(this SptLoggerFilter filter, SptLogMessage message) + { + switch (filter.MatchingType) + { + case MatchingType.Literal: + if (filter.Name != message.Logger) + { + return false; + } + break; + case MatchingType.Regex: + if (!_cachedRegexes.TryGetValue(filter, out var regex)) + { + regex = new Regex(filter.Name); + while(!_cachedRegexes.TryAdd(filter, regex)); + } + + if (!regex.IsMatch(message.Logger)) + { + return false; + } + break; + + } + + return true; + } + + public static bool CanLog(this LogLevel logLevel, LogLevel messageLevel) + { + return logLevel >= messageLevel; + } +} diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLoggerQueueManager.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLoggerQueueManager.cs new file mode 100644 index 00000000..c0b8732c --- /dev/null +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLoggerQueueManager.cs @@ -0,0 +1,127 @@ +using SPTarkov.Common.Annotations; + +namespace SPTarkov.Server.Core.Utils.Logger; + +[Injectable(InjectionType.Singleton)] +public class SptLoggerQueueManager(IEnumerable logHandlers) +{ + private readonly Dictionary> _resolvedMessageLoggerTypes = new(); + private readonly object _resolvedMessageLoggerTypesLock = new(); + private Thread? _loggerTask; + private readonly object LoggerTaskLock = new(); + private readonly CancellationTokenSource _loggerCancellationTokens = new(); + private readonly Queue _messageQueue = new(); + private readonly object _messageQueueLock = new(); + private Dictionary? _logHandlers; + private SptLoggerConfiguration _config; + + public void Initialize(SptLoggerConfiguration config) + { + _config = config; + + if (_logHandlers == null) + { + _logHandlers = logHandlers.ToDictionary(lh => lh.LoggerType, lh => lh); + } + + lock (LoggerTaskLock) + { + if (_loggerTask == null) + { + _loggerTask = new Thread(LoggerWorkerThread); + _loggerTask.IsBackground = true; + _loggerTask.Start(); + } + } + } + + private void LoggerWorkerThread() + { + while (!_loggerCancellationTokens.IsCancellationRequested) + { + lock (_messageQueueLock) + { + if (_messageQueue.Count != 0) + { + while (_messageQueue.TryDequeue(out var message)) + { + LogMessage(message); + } + } + } + + Thread.Sleep((int) _config.PoolingTimeMs); + } + + lock (_messageQueueLock) + { + // make sure after cancellation that no messages are outstanding + if (_messageQueue.Count != 0) + { + while (_messageQueue.TryDequeue(out var message)) + { + LogMessage(message); + } + } + } + } + + private void LogMessage(SptLogMessage message) + { + List messageLoggers; + lock (_resolvedMessageLoggerTypesLock) + { + if (!_resolvedMessageLoggerTypes.TryGetValue(message.Logger, out messageLoggers)) + { + messageLoggers = _config.Loggers.Where(logger => + { + var excludeFilters = logger.Filters?.Where(filter => filter.Type == SptLoggerFilterType.Exclude); + var includeFilters = logger.Filters?.Where(filter => filter.Type == SptLoggerFilterType.Include); + var passed = true; + if (excludeFilters?.Any() ?? false) + { + passed = !excludeFilters.Any(filter => filter.Match(message)); + } + + if (includeFilters?.Any() ?? false) + { + passed = includeFilters.Any(filter => filter.Match(message)); + } + + return passed; + }).ToList(); + _resolvedMessageLoggerTypes.Add(message.Logger, messageLoggers); + } + } + + if (messageLoggers.Count != 0) + { + messageLoggers.ForEach(logger => + { + if (logger.LogLevel.CanLog(message.LogLevel) && + (_logHandlers?.TryGetValue(logger.Type, out var handler) ?? false)) + { + handler.Log(message, logger); + } + }); + } + } + + public void EnqueueMessage(SptLogMessage message) + { + lock (_messageQueueLock) + { + _messageQueue.Enqueue(message); + } + } + + public void DumpAndStop() + { + _loggerCancellationTokens.Cancel(); + while (_loggerTask.IsAlive) + { + // waiting for logger to finish avoiding the application to close + Thread.Sleep(100); + } + } +} diff --git a/SPTarkov.Server/Logger/AbstractFormatter.cs b/SPTarkov.Server/Logger/AbstractFormatter.cs deleted file mode 100644 index 0bfa9665..00000000 --- a/SPTarkov.Server/Logger/AbstractFormatter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Text; -using Serilog.Events; -using Serilog.Formatting; - -namespace SPTarkov.Server.Logger; - -public abstract class AbstractFormatter : ITextFormatter -{ - public void Format(LogEvent logEvent, TextWriter output) - { - Console.OutputEncoding = Encoding.UTF8; - var newLine = Environment.NewLine; - var timestamp = logEvent.Timestamp.ToString("HH:mm:ss.fff"); - var logLevel = logEvent.Level.ToString().ToUpper().Substring(0, 4); - var message = logEvent.RenderMessage(); - var exception = logEvent.Exception != null ? $"{newLine}{logEvent.Exception}{newLine}{logEvent.Exception.StackTrace}" : ""; - var sourceContext = logEvent.Properties["SourceContext"].ToString().Replace("\"", ""); - var logMessage = ProcessText(GetFormattedText(timestamp, logLevel, sourceContext, $"{message}{exception}")); - output.WriteLine(logMessage); - } - - protected abstract string ProcessText(string text); - - protected virtual string GetFormattedText(string timestamp, string logLevel, string sourceContext, string message) - { - return $"[{timestamp} {logLevel}][{sourceContext}] {message}"; - } -} diff --git a/SPTarkov.Server/Logger/ConsoleFormatter.cs b/SPTarkov.Server/Logger/ConsoleFormatter.cs deleted file mode 100644 index 77d0bb6b..00000000 --- a/SPTarkov.Server/Logger/ConsoleFormatter.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace SPTarkov.Server.Logger; - -public class ConsoleFormatter : AbstractFormatter -{ - public static ConsoleFormatter Default - { - get; - } = new(); - - protected override string ProcessText(string text) - { - return text; - } - - protected override string GetFormattedText(string timestamp, string logLevel, string sourceContext, string message) - { - return message; - } -} diff --git a/SPTarkov.Server/Logger/FileFormatter.cs b/SPTarkov.Server/Logger/FileFormatter.cs deleted file mode 100644 index 55d2a4c2..00000000 --- a/SPTarkov.Server/Logger/FileFormatter.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Text.RegularExpressions; - -namespace SPTarkov.Server.Logger; - -public class FileFormatter : AbstractFormatter -{ - public static FileFormatter Default - { - get; - } = new(); - - protected override string ProcessText(string message) - { - foreach (Match match in Regex.Matches(message, @"\x1b\[[0-9]{1,2}(;[0-1]{1,2}){0,1}m")) - { - message = message.Replace(match.Value, ""); - } - - return message.Replace("\"", ""); - } -} diff --git a/SPTarkov.Server/Logger/SptLoggerExtensions.cs b/SPTarkov.Server/Logger/SptLoggerExtensions.cs new file mode 100644 index 00000000..92e7dba3 --- /dev/null +++ b/SPTarkov.Server/Logger/SptLoggerExtensions.cs @@ -0,0 +1,31 @@ +using SPTarkov.Server.Core.Utils; +using SPTarkov.Server.Core.Utils.Logger; + +namespace SPTarkov.Server.Logger; + +public static class SptLoggerExtensions +{ + + public static IHostBuilder UseSptLogger(this IHostBuilder builder) + { + if (builder == null) throw new ArgumentNullException(nameof(builder)); + + builder.ConfigureServices((_, collection) => + { + collection.AddSptLogger(); + }); + + return builder; + } + + public static IServiceCollection AddSptLogger(this IServiceCollection collection) + { + if (collection == null) throw new ArgumentNullException(nameof(collection)); + + collection.AddSingleton(sp => + new SptLoggerProvider(sp.GetService(), sp.GetService(), sp.GetService())); + + return collection; + } + +} diff --git a/SPTarkov.Server/Logger/SptLoggerProvider.cs b/SPTarkov.Server/Logger/SptLoggerProvider.cs new file mode 100644 index 00000000..7cd29988 --- /dev/null +++ b/SPTarkov.Server/Logger/SptLoggerProvider.cs @@ -0,0 +1,25 @@ +using SPTarkov.Common.Annotations; +using SPTarkov.Server.Core.Utils; +using SPTarkov.Server.Core.Utils.Logger; + +namespace SPTarkov.Server.Logger; + +[Injectable] +public class SptLoggerProvider(JsonUtil jsonUtil, FileUtil fileUtil, SptLoggerQueueManager queueManager) : ILoggerProvider, ILoggerFactory +{ + private List loggerProviders = new(); + + public void Dispose() + { + } + + public void AddProvider(ILoggerProvider provider) + { + loggerProviders?.Add(provider); + } + + public ILogger CreateLogger(string categoryName) + { + return new SptLoggerWrapper(categoryName, jsonUtil, fileUtil, queueManager); + } +} diff --git a/SPTarkov.Server/Logger/SptLoggerWrapper.cs b/SPTarkov.Server/Logger/SptLoggerWrapper.cs new file mode 100644 index 00000000..ed3d8d98 --- /dev/null +++ b/SPTarkov.Server/Logger/SptLoggerWrapper.cs @@ -0,0 +1,80 @@ +using SPTarkov.Server.Core.Utils; +using SPTarkov.Server.Core.Utils.Logger; +using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; + +namespace SPTarkov.Server.Logger; + +public class SptLoggerWrapper : ILogger +{ + private readonly SptLogger _logger; + + public SptLoggerWrapper(string category, JsonUtil jsonUtil, FileUtil fileUtil, SptLoggerQueueManager queueManager) + { + _logger = new SptLogger(fileUtil, jsonUtil, queueManager); + _logger.OverrideCategory(category); + } + + public IDisposable? BeginScope(TState state) where TState : notnull + { + return null; + } + + public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel) + { + return _logger.IsLogEnabled(ConvertLogLevel(logLevel)); + } + + public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + var level = ConvertLogLevel(logLevel); + switch (level) + { + case LogLevel.Fatal: + _logger.Critical(formatter(state, exception), exception); + break; + case LogLevel.Error: + _logger.Error(formatter(state, exception), exception); + break; + case LogLevel.Warn: + _logger.Warning(formatter(state, exception), exception); + break; + case LogLevel.Info: + _logger.Info(formatter(state, exception), exception); + break; + case LogLevel.Debug: + case LogLevel.Trace: + _logger.Debug(formatter(state, exception), exception); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + protected Microsoft.Extensions.Logging.LogLevel ConvertLogLevel(LogLevel level) + { + return level switch + { + LogLevel.Trace => Microsoft.Extensions.Logging.LogLevel.Trace, + LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug, + LogLevel.Info => Microsoft.Extensions.Logging.LogLevel.Information, + LogLevel.Warn => Microsoft.Extensions.Logging.LogLevel.Warning, + LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error, + LogLevel.Fatal => Microsoft.Extensions.Logging.LogLevel.Critical, + _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) + }; + } + + protected LogLevel ConvertLogLevel(Microsoft.Extensions.Logging.LogLevel level) + { + return level switch + { + Microsoft.Extensions.Logging.LogLevel.Trace => LogLevel.Trace, + Microsoft.Extensions.Logging.LogLevel.Debug => LogLevel.Debug, + Microsoft.Extensions.Logging.LogLevel.Information => LogLevel.Info, + Microsoft.Extensions.Logging.LogLevel.Warning => LogLevel.Warn, + Microsoft.Extensions.Logging.LogLevel.Error => LogLevel.Error, + Microsoft.Extensions.Logging.LogLevel.Critical => LogLevel.Fatal, + _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) + }; + } +} diff --git a/SPTarkov.Server/Logger/WebApplicationLogger.cs b/SPTarkov.Server/Logger/WebApplicationLogger.cs deleted file mode 100644 index bc6265e1..00000000 --- a/SPTarkov.Server/Logger/WebApplicationLogger.cs +++ /dev/null @@ -1,119 +0,0 @@ -using SPTarkov.Common.Annotations; -using SPTarkov.Server.Core.Models.Logging; -using SPTarkov.Server.Core.Models.Utils; -using SPTarkov.Server.Core.Utils.Logger; -using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel; - -namespace SPTarkov.Server.Logger; - -[Injectable] -public class SptWebApplicationLogger : ISptLogger -{ - private static ILogger? _fileLogger; - - private readonly ILogger _logger; - - public SptWebApplicationLogger(ILoggerFactory provider) - { - _logger = provider.CreateLogger(typeof(T).FullName ?? "SPT Logger Default Name"); - if (_fileLogger == null) - { - _fileLogger = provider.CreateLogger(typeof(FileLogger).FullName ?? "SPT Logger Default Name"); - } - } - - public void LogWithColor( - string data, - LogTextColor? textColor = null, - LogBackgroundColor? backgroundColor = null, - Exception? ex = null - ) - { - if (textColor != null || backgroundColor != null) - { - _logger.LogInformation(ex, GetColorizedText(data, textColor, backgroundColor)); - } - else - { - _logger.LogInformation(ex, data); - } - } - - public void Success(string data, Exception? ex = null) - { - _logger.LogInformation(ex, GetColorizedText(data, LogTextColor.Green)); - } - - public void Error(string data, Exception? ex = null) - { - _logger.LogError(ex, GetColorizedText(data, LogTextColor.Red)); - } - - public void Warning(string data, Exception? ex = null) - { - _logger.LogWarning(ex, GetColorizedText(data, LogTextColor.Yellow)); - } - - public void Info(string data, Exception? ex = null) - { - _logger.LogInformation(ex, data); - } - - public void Debug(string data, Exception? ex = null) - { - _logger.LogDebug(ex, data); - } - - public void Critical(string data, Exception? ex = null) - { - _logger.LogCritical(ex, GetColorizedText(data, LogTextColor.Black, LogBackgroundColor.Red)); - } - - public void WriteToLogFile(string body, LogLevel level = LogLevel.Info) - { - _fileLogger?.Log(ConvertLogLevel(level), body); - } - - public bool IsLogEnabled(LogLevel level) - { - return _logger.IsEnabled(ConvertLogLevel(level)); - } - - private string GetColorizedText( - string data, - LogTextColor? textColor = null, - LogBackgroundColor? backgroundColor = null - ) - { - var colorString = string.Empty; - if (textColor != null) - { - colorString += ((int) textColor.Value).ToString(); - } - - if (backgroundColor != null) - { - colorString += string.IsNullOrEmpty(colorString) - ? ((int) backgroundColor.Value).ToString() - : $";{((int) backgroundColor.Value).ToString()}"; - } - - return $"\x1b[{colorString}m{data}\x1b[0m"; - } - - protected Microsoft.Extensions.Logging.LogLevel ConvertLogLevel(LogLevel level) - { - return level switch - { - LogLevel.Trace => Microsoft.Extensions.Logging.LogLevel.Trace, - LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug, - LogLevel.Success - or LogLevel.Info - or LogLevel.Custom => Microsoft.Extensions.Logging.LogLevel.Information, - LogLevel.Warn => Microsoft.Extensions.Logging.LogLevel.Warning, - LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error, - LogLevel.Fatal => Microsoft.Extensions.Logging.LogLevel.Critical, - _ => throw new ArgumentOutOfRangeException(nameof(level), level, null) - }; - } -} diff --git a/SPTarkov.Server/Program.cs b/SPTarkov.Server/Program.cs index 5528dcfb..1155478e 100644 --- a/SPTarkov.Server/Program.cs +++ b/SPTarkov.Server/Program.cs @@ -1,7 +1,4 @@ using System.Runtime; -using Serilog; -using Serilog.Exceptions; -using Serilog.Settings.Configuration; using SPTarkov.Common.Semver; using SPTarkov.Common.Semver.Implementations; using SPTarkov.DI; @@ -11,6 +8,7 @@ using SPTarkov.Server.Core.Models.External; using SPTarkov.Server.Core.Models.Spt.Mod; using SPTarkov.Server.Core.Models.Utils; using SPTarkov.Server.Core.Utils; +using SPTarkov.Server.Core.Utils.Logger; using SPTarkov.Server.Logger; using SPTarkov.Server.Modding; @@ -45,6 +43,7 @@ public static class Program var serviceProvider = builder.Services.BuildServiceProvider(); var logger = serviceProvider.GetService().CreateLogger("Server"); + try { var watermark = serviceProvider.GetService(); @@ -89,6 +88,10 @@ public static class Program Console.WriteLine(ex); logger.LogCritical(ex, "Critical exception, stopping server..."); } + finally + { + serviceProvider.GetService>()?.DumpAndStop(); + } } private static WebApplicationBuilder CreateNewHostBuilder(string[]? args = null) @@ -96,22 +99,7 @@ public static class Program var builder = WebApplication.CreateBuilder(args); builder.Logging.ClearProviders(); builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()); - - if (ProgramStatics.DEBUG()) - { - builder.Configuration.AddJsonFile("./appsettings.Development.json", true, true); - } - else - { - builder.Configuration.AddJsonFile("./appsettings.json", true, true); - } - - builder.Host.UseSerilog((context, provider, logger) => - { - logger - .ReadFrom.Configuration(context.Configuration) - .ReadFrom.Services(provider); - }); + builder.Host.UseSptLogger(); return builder; } @@ -130,7 +118,7 @@ public static class Program DependencyInjectionRegistrator.RegisterSptComponents(typeof(Program).Assembly, typeof(App).Assembly, builder.Services); // register the mod validator components var provider = builder.Services - .AddScoped(typeof(ISptLogger), typeof(SptWebApplicationLogger)) + .AddScoped(typeof(ISptLogger), typeof(SptLogger)) .AddScoped(typeof(ISemVer), typeof(SemanticVersioningSemVer)) .AddSingleton() .AddSingleton() diff --git a/SPTarkov.Server/SPTarkov.Server.csproj b/SPTarkov.Server/SPTarkov.Server.csproj index 21b5bbac..f6188e94 100644 --- a/SPTarkov.Server/SPTarkov.Server.csproj +++ b/SPTarkov.Server/SPTarkov.Server.csproj @@ -29,12 +29,6 @@ - - - - - - @@ -44,12 +38,9 @@ - + Always - - - Always - + diff --git a/SPTarkov.Server/appsettings.Development.json b/SPTarkov.Server/appsettings.Development.json deleted file mode 100644 index 7301b71c..00000000 --- a/SPTarkov.Server/appsettings.Development.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "Serilog": { - "Using": [ - "Serilog.Sinks.Console", - "Serilog.Sinks.File", - "Serilog.Sinks.Async", - "Serilog.Settings.Configuration", - "Serilog.Expressions", - "Serilog.Exceptions", - "Serilog.Enrichers.Context", - "Serilog.Enrichers.Thread" - ], - "MinimumLevel": { - "Default": "Verbose", - "Override": { - "Microsoft": "Verbose" - } - }, - "WriteTo": [ - { - "Name": "Async", - "Args": { - "configure": [ - { - "Name": "Logger", - "Args": { - "configureLogger": { - "Filter": [ - { - "Name": "ByExcluding", - "Args": { - "expression": "StartsWith(SourceContext, 'SPTarkov.Server.Core.Servers.Http.RequestLogger')" - } - }, - { - "Name": "ByExcluding", - "Args": { - "expression": "SourceContext like 'Microsoft%'" - } - } - ], - "WriteTo": [ - { - "Name": "Console", - "Args": { - "formatter": "SPTarkov.Server.Logger.ConsoleFormatter::Default, SPTarkov.Server" - } - }, - { - "Name": "File", - "Args": { - "path": "./user/logs/spt/spt.txt", - "rollingInterval": "Day", - "fileSizeLimitBytes": "20971520", - "rollOnFileSizeLimit": true, - "formatter": "SPTarkov.Server.Logger.FileFormatter::Default, SPTarkov.Server" - } - } - ] - } - } - }, - { - "Name": "Logger", - "Args": { - "configureLogger": { - "Filter": [ - { - "Name": "ByIncludingOnly", - "Args": { - "expression": "StartsWith(SourceContext, 'SPTarkov.Server.Core.Servers.Http.RequestLogger')" - } - } - ], - "WriteTo": [ - { - "Name": "File", - "Args": { - "formatter": "SPTarkov.Server.Logger.FileFormatter::Default, SPTarkov.Server", - "path": "./user/logs/requests/requests.txt", - "rollingInterval": "Day", - "fileSizeLimitBytes": "20971520", - "rollOnFileSizeLimit": true - } - } - ] - } - } - }, - { - "Name": "Logger", - "Args": { - "configureLogger": { - "Filter": [ - { - "Name": "ByIncludingOnly", - "Args": { - "expression": "SourceContext like 'Microsoft%'" - } - } - ], - "WriteTo": [ - { - "Name": "File", - "Args": { - "path": "./user/logs/kestrel/kestrel.txt", - "rollingInterval": "Day", - "fileSizeLimitBytes": "20971520", - "rollOnFileSizeLimit": true, - "outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}" - } - } - ] - } - } - } - ] - } - } - ], - "Enrich": [ - "FromLogContext", - "WithExceptionDetails", - "WithThreadId" - ] - } -} diff --git a/SPTarkov.Server/appsettings.json b/SPTarkov.Server/appsettings.json deleted file mode 100644 index f1c901a8..00000000 --- a/SPTarkov.Server/appsettings.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "Serilog": { - "Using": [ - "Serilog.Sinks.Console", - "Serilog.Sinks.File", - "Serilog.Sinks.Async", - "Serilog.Settings.Configuration", - "Serilog.Expressions", - "Serilog.Exceptions", - "Serilog.Enrichers.Context", - "Serilog.Enrichers.Thread" - ], - "MinimumLevel": { - "Default": "Information", - "Override": { - "Microsoft": "Information" - } - }, - "WriteTo": [ - { - "Name": "Async", - "Args": { - "configure": [ - { - "Name": "Logger", - "Args": { - "configureLogger": { - "Filter": [ - { - "Name": "ByExcluding", - "Args": { - "expression": "StartsWith(SourceContext, 'SPTarkov.Server.Core.Servers.Http.RequestLogger')" - } - }, - { - "Name": "ByExcluding", - "Args": { - "expression": "SourceContext like 'Microsoft%'" - } - } - ], - "WriteTo": [ - { - "Name": "Console", - "Args": { - "formatter": "SPTarkov.Server.Logger.ConsoleFormatter::Default, SPTarkov.Server" - } - }, - { - "Name": "File", - "Args": { - "path": "./user/logs/spt/spt.txt", - "rollingInterval": "Day", - "fileSizeLimitBytes": "20971520", - "rollOnFileSizeLimit": true, - "formatter": "SPTarkov.Server.Logger.FileFormatter::Default, SPTarkov.Server" - } - } - ] - } - } - } - ] - } - } - ], - "Enrich": [ - "FromLogContext", - "WithExceptionDetails", - "WithThreadId" - ] - } -} diff --git a/SPTarkov.Server/sptLogger.json b/SPTarkov.Server/sptLogger.json new file mode 100644 index 00000000..d44cf6c1 --- /dev/null +++ b/SPTarkov.Server/sptLogger.json @@ -0,0 +1,57 @@ +{ + "loggers": [ + { + "type": "File", + "logLevel": "Trace", + "format": "[%date% %time%][%level%][%logger%] %message%", + "filePath": "./user/logs/spt/spt.txt", + "filters": [ + { + "type": "Exclude", + "name": ".*RequestLogger", + "matchingType": "Regex" + }, + { + "type": "Exclude", + "name": "Microsoft\\.AspNetCore\\.Server\\.Kestrel.*", + "matchingType": "Regex" + } + ] + }, + { + "type": "File", + "logLevel": "Trace", + "format": "[%date% %time%][%level%][%logger%] %message%", + "filePath": "./user/logs/requests/requests.txt", + "filters": [ + { + "type": "Include", + "name": ".*RequestLogger", + "matchingType": "Regex" + } + ] + }, + { + "type": "Console", + "logLevel": "Trace", + "format": "%message%", + "filters": [ + { + "type": "Exclude", + "name": "Microsoft.*", + "matchingType": "Regex" + }, + { + "type": "Exclude", + "name": ".*FileLogger", + "matchingType": "Regex" + }, + { + "type": "Exclude", + "name": ".*RequestLogger", + "matchingType": "Regex" + } + ] + } + ] +} diff --git a/Tools/HideoutCraftQuestIdGenerator/SptBasicLogger.cs b/Tools/HideoutCraftQuestIdGenerator/SptBasicLogger.cs index a49385ba..b027dddd 100644 --- a/Tools/HideoutCraftQuestIdGenerator/SptBasicLogger.cs +++ b/Tools/HideoutCraftQuestIdGenerator/SptBasicLogger.cs @@ -51,13 +51,24 @@ public class SptBasicLogger : ISptLogger Console.WriteLine($"{categoryName}: {data}"); } - public void WriteToLogFile(string body, LogLevel level = LogLevel.Info) + public void Log( + LogLevel level, + string data, + LogTextColor? textColor = null, + LogBackgroundColor? backgroundColor = null, + Exception? ex = null + ) { - Console.WriteLine($"{categoryName}: {body}"); + throw new NotImplementedException(); } public bool IsLogEnabled(LogLevel level) { return true; } + + public void DumpAndStop() + { + throw new NotImplementedException(); + } } diff --git a/Tools/ItemTplGenerator/SptBasicLogger.cs b/Tools/ItemTplGenerator/SptBasicLogger.cs index 774a8947..5e032590 100644 --- a/Tools/ItemTplGenerator/SptBasicLogger.cs +++ b/Tools/ItemTplGenerator/SptBasicLogger.cs @@ -51,6 +51,17 @@ public class SptBasicLogger : ISptLogger Console.WriteLine($"{categoryName}: {data}"); } + public void Log( + LogLevel level, + string data, + LogTextColor? textColor = null, + LogBackgroundColor? backgroundColor = null, + Exception? ex = null + ) + { + throw new NotImplementedException(); + } + public void WriteToLogFile(string body, LogLevel level = LogLevel.Info) { Console.WriteLine($"{categoryName}: {body}"); @@ -60,4 +71,9 @@ public class SptBasicLogger : ISptLogger { return true; } + + public void DumpAndStop() + { + throw new NotImplementedException(); + } } diff --git a/UnitTests/Mock/MockLogger.cs b/UnitTests/Mock/MockLogger.cs index a5fe96f5..0015dc59 100644 --- a/UnitTests/Mock/MockLogger.cs +++ b/UnitTests/Mock/MockLogger.cs @@ -41,6 +41,17 @@ public class MockLogger : ISptLogger Console.WriteLine(data); } + public void Log( + LogLevel level, + string data, + LogTextColor? textColor = null, + LogBackgroundColor? backgroundColor = null, + Exception? ex = null + ) + { + throw new NotImplementedException(); + } + public void WriteToLogFile(string body, LogLevel level = LogLevel.Info) { throw new NotImplementedException(); @@ -51,6 +62,11 @@ public class MockLogger : ISptLogger return true; } + public void DumpAndStop() + { + throw new NotImplementedException(); + } + public void LogWithColor( string data, Exception? ex = null, diff --git a/UnitTests/Tests/Test.cs b/UnitTests/Tests/Test.cs index d50e4716..989b5e08 100644 --- a/UnitTests/Tests/Test.cs +++ b/UnitTests/Tests/Test.cs @@ -12,7 +12,7 @@ public class Test [TestInitialize] public void Setup() { - var importer = new ImporterUtil(new MockLogger(), new FileUtil(new MockLogger()), new JsonUtil()); + var importer = new ImporterUtil(new MockLogger(), new FileUtil(), new JsonUtil()); var loadTask = importer.LoadRecursiveAsync("./TestAssets/"); loadTask.Wait(); _templates = loadTask.Result; diff --git a/UnitTests/Tests/Utils/ClonerTest.cs b/UnitTests/Tests/Utils/ClonerTest.cs index 4c5479bd..6fb3979e 100644 --- a/UnitTests/Tests/Utils/ClonerTest.cs +++ b/UnitTests/Tests/Utils/ClonerTest.cs @@ -20,7 +20,7 @@ public class ClonerTest public void Setup() { _jsonUtil = new JsonUtil(); - var importer = new ImporterUtil(new MockLogger(), new FileUtil(new MockLogger()), _jsonUtil); + var importer = new ImporterUtil(new MockLogger(), new FileUtil(), _jsonUtil); var loadTask = importer.LoadRecursiveAsync("./TestAssets/"); loadTask.Wait(); _templates = loadTask.Result;