Replaced Serilog for custom logger
This commit is contained in:
@@ -20,7 +20,7 @@ public class ClonerBenchmarks
|
||||
public void Setup()
|
||||
{
|
||||
var jsonUtil = new JsonUtil();
|
||||
var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(new MockLogger<FileUtil>()),
|
||||
var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(),
|
||||
jsonUtil);
|
||||
var loadTask = importer.LoadRecursiveAsync<Templates>("./Assets/database/templates/");
|
||||
loadTask.Wait();
|
||||
|
||||
@@ -41,6 +41,17 @@ public class MockLogger<T> : ISptLogger<T>
|
||||
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<T> : ISptLogger<T>
|
||||
return false;
|
||||
}
|
||||
|
||||
public void DumpAndStop()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void LogWithColor(
|
||||
string data,
|
||||
Exception? ex = null,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ public interface ISptLogger<T>
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
protected ICloner _cloner;
|
||||
protected EventOutputHolder _eventOutputHolder;
|
||||
protected HttpResponseUtil _httpResponseUtil;
|
||||
protected List<ItemEventRouterDefinition> _itemEventRouters;
|
||||
protected JsonUtil _jsonUtil;
|
||||
protected LocalisationService _localisationService;
|
||||
protected ISptLogger<ItemEventRouter> _logger;
|
||||
protected ProfileHelper _profileHelper;
|
||||
|
||||
public ItemEventRouter(
|
||||
ISptLogger<ItemEventRouter> logger,
|
||||
HttpResponseUtil httpResponseUtil,
|
||||
public class ItemEventRouter(ISptLogger<ItemEventRouter> logger,
|
||||
ISptLogger<FileLogger> fileLogger,
|
||||
JsonUtil jsonUtil,
|
||||
ProfileHelper profileHelper,
|
||||
LocalisationService localisationService,
|
||||
EventOutputHolder eventOutputHolder,
|
||||
IEnumerable<ItemEventRouterDefinition> itemEventRouters,
|
||||
ICloner cloner
|
||||
)
|
||||
ICloner cloner)
|
||||
{
|
||||
_logger = logger;
|
||||
_httpResponseUtil = httpResponseUtil;
|
||||
_jsonUtil = jsonUtil;
|
||||
_profileHelper = profileHelper;
|
||||
_localisationService = localisationService;
|
||||
_eventOutputHolder = eventOutputHolder;
|
||||
_itemEventRouters = itemEventRouters.ToList();
|
||||
_cloner = cloner;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles ItemEventRouter Requests and processes them.
|
||||
@@ -51,24 +30,24 @@ public class ItemEventRouter
|
||||
/// <returns> Item response </returns>
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using SPTarkov.Common.Annotations;
|
||||
using SPTarkov.Server.Core.Models.Utils;
|
||||
|
||||
namespace SPTarkov.Server.Core.Utils;
|
||||
|
||||
[Injectable]
|
||||
public class FileUtil(
|
||||
ISptLogger<FileUtil> _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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -111,12 +113,12 @@ public class FileUtil(
|
||||
/// <param name="copyFromPath">Source file to copy from</param>
|
||||
/// <param name="destinationFilePath"></param>
|
||||
/// <param name="overwrite">Should destination file be overwritten</param>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+33
@@ -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<BaseSptLoggerReference>
|
||||
{
|
||||
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<FileSptLoggerReference>();
|
||||
case "Console":
|
||||
return jsonDocument.Deserialize<ConsoleSptLoggerReference>();
|
||||
default:
|
||||
throw new Exception($"The logger type '{typeElement.GetString()}' does not exist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, BaseSptLoggerReference value, JsonSerializerOptions options)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ public class JsonUtil
|
||||
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Converters =
|
||||
{
|
||||
new BaseSptLoggerReferenceConverter(),
|
||||
new ListOrTConverterFactory(),
|
||||
new DictionaryOrListConverter(),
|
||||
new EftEnumConverter<SptAirdropTypeEnum>(),
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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<string, object> _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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace SPTarkov.Server.Core.Utils.Logger;
|
||||
|
||||
public interface ILogHandler
|
||||
{
|
||||
LoggerType LoggerType
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
void Log(SptLogMessage message, BaseSptLoggerReference reference);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
namespace SPTarkov.Server.Core.Utils.Logger;
|
||||
|
||||
@@ -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
|
||||
);
|
||||
@@ -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<T> : ISptLogger<T>, 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<SptLoggerConfiguration>(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();
|
||||
}
|
||||
}
|
||||
@@ -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<BaseSptLoggerReference> 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<SptLoggerFilter> 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<SptLoggerFilter, Regex> _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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
using SPTarkov.Common.Annotations;
|
||||
|
||||
namespace SPTarkov.Server.Core.Utils.Logger;
|
||||
|
||||
[Injectable(InjectionType.Singleton)]
|
||||
public class SptLoggerQueueManager(IEnumerable<ILogHandler> logHandlers)
|
||||
{
|
||||
private readonly Dictionary<string, List<BaseSptLoggerReference>> _resolvedMessageLoggerTypes = new();
|
||||
private readonly object _resolvedMessageLoggerTypesLock = new();
|
||||
private Thread? _loggerTask;
|
||||
private readonly object LoggerTaskLock = new();
|
||||
private readonly CancellationTokenSource _loggerCancellationTokens = new();
|
||||
private readonly Queue<SptLogMessage> _messageQueue = new();
|
||||
private readonly object _messageQueueLock = new();
|
||||
private Dictionary<LoggerType, ILogHandler>? _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<BaseSptLoggerReference> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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("\"", "");
|
||||
}
|
||||
}
|
||||
@@ -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<ILoggerFactory>(sp =>
|
||||
new SptLoggerProvider(sp.GetService<JsonUtil>(), sp.GetService<FileUtil>(), sp.GetService<SptLoggerQueueManager>()));
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ILoggerProvider> 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);
|
||||
}
|
||||
}
|
||||
@@ -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<SptLoggerWrapper> _logger;
|
||||
|
||||
public SptLoggerWrapper(string category, JsonUtil jsonUtil, FileUtil fileUtil, SptLoggerQueueManager queueManager)
|
||||
{
|
||||
_logger = new SptLogger<SptLoggerWrapper>(fileUtil, jsonUtil, queueManager);
|
||||
_logger.OverrideCategory(category);
|
||||
}
|
||||
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
|
||||
{
|
||||
return _logger.IsLogEnabled(ConvertLogLevel(logLevel));
|
||||
}
|
||||
|
||||
public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> 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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<T> : ISptLogger<T>
|
||||
{
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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<ILoggerFactory>().CreateLogger("Server");
|
||||
|
||||
try
|
||||
{
|
||||
var watermark = serviceProvider.GetService<Watermark>();
|
||||
@@ -89,6 +88,10 @@ public static class Program
|
||||
Console.WriteLine(ex);
|
||||
logger.LogCritical(ex, "Critical exception, stopping server...");
|
||||
}
|
||||
finally
|
||||
{
|
||||
serviceProvider.GetService<SptLogger<object>>()?.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<ModValidator>), typeof(SptWebApplicationLogger<ModValidator>))
|
||||
.AddScoped(typeof(ISptLogger<ModValidator>), typeof(SptLogger<ModValidator>))
|
||||
.AddScoped(typeof(ISemVer), typeof(SemanticVersioningSemVer))
|
||||
.AddSingleton<ModValidator>()
|
||||
.AddSingleton<ModLoadOrder>()
|
||||
|
||||
@@ -29,12 +29,6 @@
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.1"/>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.1"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.1"/>
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0"/>
|
||||
<PackageReference Include="Serilog.Enrichers.Context" Version="4.6.5"/>
|
||||
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0"/>
|
||||
<PackageReference Include="Serilog.Exceptions" Version="8.4.0"/>
|
||||
<PackageReference Include="Serilog.Expressions" Version="5.0.0"/>
|
||||
<PackageReference Include="Serilog.Sinks.Async" Version="2.1.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -44,12 +38,9 @@
|
||||
</Content>
|
||||
<None Remove="appsettings.json"/>
|
||||
<None Remove="appsettings.Development.json"/>
|
||||
<Content Include="appsettings.json">
|
||||
<None Update="sptLogger.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="appsettings.Development.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -51,13 +51,24 @@ public class SptBasicLogger<T> : ISptLogger<T>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,17 @@ public class SptBasicLogger<T> : ISptLogger<T>
|
||||
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<T> : ISptLogger<T>
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DumpAndStop()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,17 @@ public class MockLogger<T> : ISptLogger<T>
|
||||
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<T> : ISptLogger<T>
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DumpAndStop()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void LogWithColor(
|
||||
string data,
|
||||
Exception? ex = null,
|
||||
|
||||
@@ -12,7 +12,7 @@ public class Test
|
||||
[TestInitialize]
|
||||
public void Setup()
|
||||
{
|
||||
var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(new MockLogger<FileUtil>()), new JsonUtil());
|
||||
var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(), new JsonUtil());
|
||||
var loadTask = importer.LoadRecursiveAsync<Templates>("./TestAssets/");
|
||||
loadTask.Wait();
|
||||
_templates = loadTask.Result;
|
||||
|
||||
@@ -20,7 +20,7 @@ public class ClonerTest
|
||||
public void Setup()
|
||||
{
|
||||
_jsonUtil = new JsonUtil();
|
||||
var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(new MockLogger<FileUtil>()), _jsonUtil);
|
||||
var importer = new ImporterUtil(new MockLogger<ImporterUtil>(), new FileUtil(), _jsonUtil);
|
||||
var loadTask = importer.LoadRecursiveAsync<Templates>("./TestAssets/");
|
||||
loadTask.Wait();
|
||||
_templates = loadTask.Result;
|
||||
|
||||
Reference in New Issue
Block a user