[REFACTOR] Refactored logging & added support for Microsoft.Extensions.Logging. Added test WebApi project

This commit is contained in:
max 2024-11-17 22:51:54 +01:00
parent 58739c2aea
commit 737cbcfd11
31 changed files with 398 additions and 97 deletions

View File

@ -7,14 +7,23 @@
using DotBased.Logging.Serilog;
using DotBased.Logging;
using DotBased.Utilities;
using Serilog;
using ILogger = Serilog.ILogger;
LogService.Initialize(options =>
{
options
.AddSeverityFilter("Program", LogSeverity.Verbose)
.AddSeverityFilter("DotBased.dll", LogSeverity.Verbose);
});
var serilogLogger = SetupSerilog();
LogService.AddLogAdapter(new BasedSerilogAdapter(serilogLogger));
var logger = LogService.RegisterLogger(typeof(Program));
var logger = LogService.RegisterLogger<Program>();
logger.Information("Whoah... Hi!");
logger.Information("Whoah... Hi!, {Param}", "Test!");
var cult = Culture.GetSystemCultures();
Console.ReadKey(); // Hold console app open.
return;

View File

@ -18,21 +18,23 @@ public class BasedServerAuthenticationStateProvider : ServerAuthenticationStateP
_config = configuration;
_localStorage = localStorage;
_securityService = securityService;
_logger = LogService.RegisterLogger(typeof(BasedServerAuthenticationStateProvider));
_logger = LogService.RegisterLogger<BasedServerAuthenticationStateProvider>();
}
private BasedAuthConfiguration _config;
private readonly ProtectedLocalStorage _localStorage;
private readonly SecurityService _securityService;
private ILogger _logger;
private readonly ILogger _logger;
private readonly AuthenticationState _anonState = new(new ClaimsPrincipal());
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
_logger.Debug("Getting authentication state...");
var sessionIdResult = await _localStorage.GetAsync<string>(BasedAuthDefaults.StorageKey);
if (!sessionIdResult.Success || sessionIdResult.Value == null)
return _anonState;
_logger.Debug("Found state [{State}], getting session from {Service}", sessionIdResult.Value, nameof(SecurityService));
var stateResult = await _securityService.GetAuthenticationStateFromSessionAsync(sessionIdResult.Value);
return stateResult is { Success: true, Value: not null } ? stateResult.Value : _anonState;
}

View File

@ -18,6 +18,7 @@ public static class DotBasedAuthDependencyInjection
var Configuration = new BasedAuthConfiguration();
configurationAction?.Invoke(Configuration);
services.AddSingleton<BasedAuthConfiguration>(Configuration);
if (Configuration.AuthDataRepositoryType == null)
throw new ArgumentNullException(nameof(Configuration.AuthDataRepositoryType), $"No '{nameof(IAuthDataRepository)}' configured!");

View File

@ -34,11 +34,11 @@ public class MemoryAuthDataRepository : IAuthDataRepository
public async Task<Result<UserModel>> GetUserAsync(string id, string email, string username)
{
UserModel? userModel = null;
if (!id.IsNullOrWhiteSpace())
if (!id.IsNullOrEmpty())
userModel = MemoryData.users.FirstOrDefault(u => u.Id.Equals(id, StringComparison.OrdinalIgnoreCase));
if (!email.IsNullOrWhiteSpace())
if (!email.IsNullOrEmpty())
userModel = MemoryData.users.FirstOrDefault(u => u.Email.Equals(email, StringComparison.OrdinalIgnoreCase));
if (!username.IsNullOrWhiteSpace())
if (!username.IsNullOrEmpty())
userModel = MemoryData.users.FirstOrDefault(u => u.UserName.Equals(username, StringComparison.OrdinalIgnoreCase));
return userModel != null ? Result<UserModel>.Ok(userModel) : Result<UserModel>.Failed("No user found!");
}

View File

@ -16,7 +16,7 @@ public class SecurityService
_authDataRepository = authDataRepository;
_dataCache = dataCache;
_localStorage = localStorage;
_logger = LogService.RegisterLogger(typeof(SecurityService));
_logger = LogService.RegisterLogger<SecurityService>();
}
private readonly IAuthDataRepository _authDataRepository;
@ -26,7 +26,7 @@ public class SecurityService
public async Task<Result<AuthenticationState>> GetAuthenticationStateFromSessionAsync(string id)
{
if (id.IsNullOrWhiteSpace())
if (id.IsNullOrEmpty())
return Result<AuthenticationState>.Failed("No valid id!");
AuthenticationStateModel? authStateModel = null;
var stateCache = _dataCache.RequestSessionState(id);
@ -75,13 +75,13 @@ public class SecurityService
{
UserModel? user = null;
Result<UserModel> usrResult;
if (!login.UserName.IsNullOrWhiteSpace())
if (!login.UserName.IsNullOrEmpty())
{
usrResult = await _authDataRepository.GetUserAsync(string.Empty, string.Empty, login.UserName);
if (usrResult is { Success: true, Value: not null })
user = usrResult.Value;
}
else if (!login.Email.IsNullOrWhiteSpace())
else if (!login.Email.IsNullOrEmpty())
{
usrResult = await _authDataRepository.GetUserAsync(string.Empty, login.Email, string.Empty);
if (usrResult is { Success: true, Value: not null })
@ -114,7 +114,7 @@ public class SecurityService
{
try
{
if (state.IsNullOrWhiteSpace())
if (state.IsNullOrEmpty())
return Result.Failed($"Argument {nameof(state)} is empty!");
var stateResult = await _authDataRepository.GetAuthenticationStateAsync(state);

View File

@ -0,0 +1,63 @@
using Microsoft.Extensions.Logging;
namespace DotBased.Logging.MEL;
public class BasedLogger : Microsoft.Extensions.Logging.ILogger
{
public BasedLogger(ILogger logger)
{
basedLogger = logger;
}
private readonly ILogger basedLogger;
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
return;
var severity = ConvertLogLevelToSeverity(logLevel);
var capsule = ConstructCapsule(severity, eventId, state, exception, formatter);
basedLogger.Log(capsule);
}
private LogCapsule ConstructCapsule<TState>(LogSeverity severity, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
//TODO: Extract parameters & format
if (state is IEnumerable<KeyValuePair<string, object>> stateEnum)
{
foreach (var prop in stateEnum)
{
}
}
return new LogCapsule()
{
Exception = exception,
Message = formatter.Invoke(state, exception),
Parameters = [],
Severity = severity,
TimeStamp = DateTime.Now,
Logger = basedLogger as LoggerBase ?? throw new NullReferenceException(nameof(basedLogger))
};
}
private LogSeverity ConvertLogLevelToSeverity(LogLevel level)
{
return level switch
{
LogLevel.Trace => LogSeverity.Trace,
LogLevel.Debug => LogSeverity.Debug,
LogLevel.Information => LogSeverity.Info,
LogLevel.Warning => LogSeverity.Warning,
LogLevel.Error => LogSeverity.Error,
LogLevel.Critical => LogSeverity.Fatal,
LogLevel.None => LogSeverity.Ignore,
_ => LogSeverity.Verbose
};
}
public bool IsEnabled(LogLevel logLevel) => logLevel != LogLevel.None;
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default;
}

View File

@ -0,0 +1,23 @@
using Microsoft.Extensions.Logging;
namespace DotBased.Logging.MEL;
public class BasedLoggerProvider : ILoggerProvider
{
public BasedLoggerProvider(LogOptions options)
{
Options = options;
}
private readonly LogOptions Options;
public Microsoft.Extensions.Logging.ILogger CreateLogger(string categoryName)
{
return new BasedLogger(Options.LoggerBuilder.Invoke(new LoggerInformation(typeof(BasedLoggerProvider)), categoryName));
}
public void Dispose()
{
}
}

View File

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>12</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotBased\DotBased.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,14 @@
using Microsoft.Extensions.Logging;
namespace DotBased.Logging.MEL;
public static class LoggerBuilderExtensions
{
public static ILoggingBuilder AddDotBased(this ILoggingBuilder builder, LogOptions options)
{
if (builder == null)
throw new ArgumentNullException(nameof(builder));
builder.AddProvider(new BasedLoggerProvider(options));
return builder;
}
}

View File

@ -7,7 +7,7 @@ public static class BasedSerilog
/// <summary>
/// Default output template with the extra properties that can be used for serilog sinks.
/// </summary>
public const string OutputTemplate = "[{Timestamp:HH:mm:ss} - {Caller}->{Assembly}] | {Level:u3}] {Message:lj}{NewLine}{Exception}";
public const string OutputTemplate = "[{Timestamp:HH:mm:ss} {Level:u3} - {LoggerName}]{NewLine} {Message:lj}{NewLine}{Exception}";
public static LoggerConfiguration UseBasedExtension(this LoggerConfiguration loggerConfiguration)
{
@ -20,8 +20,10 @@ public static class BasedSerilog
/// </summary>
public static class ExtraProperties
{
public const string LoggerName = "LoggerName";
public const string AssemblyProp = "Assembly";
public const string SourceProp = "Source";
public const string FullNameProp = "FullName";
public const string NamespaceProp = "Namespace";
public const string CallerProp = "Caller";
}
}

View File

@ -16,9 +16,11 @@ public class BasedSerilogAdapter(global::Serilog.ILogger serilogLogger) : LogAda
if (capsule == null)
return;
var logger = serilogLogger
.ForContext(BasedSerilog.ExtraProperties.AssemblyProp, capsule.Logger.Caller.AssemblyName)
.ForContext(BasedSerilog.ExtraProperties.SourceProp, capsule.Logger.Caller.Source)
.ForContext(BasedSerilog.ExtraProperties.CallerProp, capsule.Logger.Caller.Name);
.ForContext(BasedSerilog.ExtraProperties.LoggerName, capsule.Logger.Name)
.ForContext(BasedSerilog.ExtraProperties.AssemblyProp, capsule.Logger.LoggerInformation.AssemblyName)
.ForContext(BasedSerilog.ExtraProperties.FullNameProp, capsule.Logger.LoggerInformation.TypeFullName)
.ForContext(BasedSerilog.ExtraProperties.NamespaceProp, capsule.Logger.LoggerInformation.TypeNamespace)
.ForContext(BasedSerilog.ExtraProperties.CallerProp, capsule.Logger.LoggerInformation.TypeName);
var template = _messageTemplateParser.Parse(capsule.Message);
IEnumerable<LogEventProperty>? properties = null;

View File

@ -15,7 +15,7 @@ public class BasedSerilogEnricher : ILogEventEnricher
sourcePropValue = appValue.ToString().Replace("\"", "");
var assemblyProperty = propertyFactory.CreateProperty(BasedSerilog.ExtraProperties.AssemblyProp, asmPropValue);
var sourceProperty = propertyFactory.CreateProperty(BasedSerilog.ExtraProperties.SourceProp, sourcePropValue);
var sourceProperty = propertyFactory.CreateProperty(BasedSerilog.ExtraProperties.FullNameProp, sourcePropValue);
var callerProperty = propertyFactory.CreateProperty(BasedSerilog.ExtraProperties.CallerProp, sourcePropValue);
logEvent.AddPropertyIfAbsent(assemblyProperty);

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>default</LangVersion>
</PropertyGroup>

View File

@ -10,6 +10,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{2156
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotBased.ASP.Auth", "DotBased.ASP.Auth\DotBased.ASP.Auth.csproj", "{CBD4111D-F1CA-466A-AC73-9EAB7F235B3D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DotBased.Logging.MEL", "DotBased.Logging.MEL\DotBased.Logging.MEL.csproj", "{D4D9B584-A524-4CBB-9B61-9CD65ED4AF0D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{DBDB4538-85D4-45AC-9E0A-A684467AEABA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestWebApi", "TestWebApi\TestWebApi.csproj", "{BADA4BAF-142B-47A8-95FC-B25E1D3D0020}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -32,9 +38,19 @@ Global
{CBD4111D-F1CA-466A-AC73-9EAB7F235B3D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBD4111D-F1CA-466A-AC73-9EAB7F235B3D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBD4111D-F1CA-466A-AC73-9EAB7F235B3D}.Release|Any CPU.Build.0 = Release|Any CPU
{D4D9B584-A524-4CBB-9B61-9CD65ED4AF0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D4D9B584-A524-4CBB-9B61-9CD65ED4AF0D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D4D9B584-A524-4CBB-9B61-9CD65ED4AF0D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D4D9B584-A524-4CBB-9B61-9CD65ED4AF0D}.Release|Any CPU.Build.0 = Release|Any CPU
{BADA4BAF-142B-47A8-95FC-B25E1D3D0020}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BADA4BAF-142B-47A8-95FC-B25E1D3D0020}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BADA4BAF-142B-47A8-95FC-B25E1D3D0020}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BADA4BAF-142B-47A8-95FC-B25E1D3D0020}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{EBBDAF9A-BFC7-4BDC-8C51-0501B59A1DDC} = {2156FB93-C252-4B33-8A0C-73C82FABB163}
{CBD4111D-F1CA-466A-AC73-9EAB7F235B3D} = {2156FB93-C252-4B33-8A0C-73C82FABB163}
{D4D9B584-A524-4CBB-9B61-9CD65ED4AF0D} = {2156FB93-C252-4B33-8A0C-73C82FABB163}
{BADA4BAF-142B-47A8-95FC-B25E1D3D0020} = {DBDB4538-85D4-45AC-9E0A-A684467AEABA}
EndGlobalSection
EndGlobal

View File

@ -8,7 +8,7 @@ namespace DotBased.Collections;
/// </summary>
public class InstanceContainer : IDisposable
{
private readonly ILogger _log = LogService.RegisterLogger(typeof(InstanceContainer));
private readonly ILogger _log = LogService.RegisterLogger<InstanceContainer>();
private readonly Dictionary<string, InstanceNode> _tCollection = new Dictionary<string, InstanceNode>();
/// <summary>
@ -40,7 +40,7 @@ public class InstanceContainer : IDisposable
case null:
break;
case IDisposable instance when dispose:
_log.Debug("Disposing disposable object... (InstanceContainer)");
_log.Debug("Disposing disposable object...");
instance.Dispose();
break;
}

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>default</LangVersion>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -5,5 +5,5 @@ namespace DotBased.Extensions;
/// </summary>
public static class StringExtensions
{
public static bool IsNullOrWhiteSpace(this string s) => string.IsNullOrWhiteSpace(s);
public static bool IsNullOrEmpty(this string s) => string.IsNullOrWhiteSpace(s);
}

View File

@ -5,6 +5,7 @@ namespace DotBased.Logging;
/// </summary>
public interface ILogger
{
public void Log(LogCapsule capsule);
public void Verbose(string message, params object?[]? parameters);
public void Trace(string message, params object?[]? parameters);
@ -18,4 +19,6 @@ public interface ILogger
public void Error(Exception exception, string message, params object?[]? parameters);
public void Fatal(Exception exception, string message, params object?[]? parameters);
public string Name { get; }
}

View File

@ -1,7 +1,7 @@
namespace DotBased.Logging;
/// <summary>
/// The base for creating log adpaters.
/// The base for creating log adapters.
/// </summary>
public abstract class LogAdapterBase
{
@ -19,7 +19,7 @@ public abstract class LogAdapterBase
public string AdapterName { get; }
/// <summary>
/// Handle the incomming <see cref="LogCapsule"/> that the <see cref="LogProcessor"/> sends.
/// Handle the incoming <see cref="LogCapsule"/> that the <see cref="LogProcessor"/> sends.
/// </summary>
/// <param name="processor">The log processor that has processed this log</param>
/// <param name="capsule">The log capsule, which contains the log information</param>

View File

@ -1,3 +1,6 @@
using System.Collections.ObjectModel;
using DotBased.Extensions;
namespace DotBased.Logging;
/// <summary>
@ -5,14 +8,35 @@ namespace DotBased.Logging;
/// </summary>
public class LogOptions
{
public readonly SeverityFilterCollection SeverityFilters = [];
/// <summary>
/// The severty the logger will log
/// The severity the logger will log
/// </summary>
public LogSeverity Severity { get; set; } = LogSeverity.Trace;
/// <summary>
/// The function that will build and return the <see cref="LoggerBase"/> when calling <see cref="LogService.RegisterLogger"/>, so a custom logger based on the <see cref="LoggerBase"/> can be used.
/// The function that will build and return the <see cref="ILogger"/>.
/// </summary>
public Func<CallerInformation, Action<LogCapsule>, LoggerBase> LoggerBuilder { get; set; } =
(identifier, sendEvent) => new Logger(identifier, ref sendEvent);
public Func<LoggerInformation, string, ILogger> LoggerBuilder { get; set; } =
(identifier, loggerName) => new Logger(identifier, loggerName);
public LogOptions AddSeverityFilter(string filter, LogSeverity logSeverity)
{
if (filter.IsNullOrEmpty())
return this;
SeverityFilters.Add(new SeverityFilter() { Filter = filter, Severity = logSeverity });
return this;
}
}
public struct SeverityFilter
{
public string Filter { get; set; }
public LogSeverity Severity { get; set; }
}
public class SeverityFilterCollection : KeyedCollection<string, SeverityFilter>
{
protected override string GetKeyForItem(SeverityFilter item) => item.Filter;
}

View File

@ -8,7 +8,7 @@ public class LogProcessor : IDisposable
public LogProcessor()
{
_processorQueue = new Queue<LogCapsule>();
IncommingLogHandlerEvent = IncommingLogHandler;
IncomingLogHandlerEvent = IncomingLogHandler;
_processorThread = new Thread(ProcessLog)
{
IsBackground = true,
@ -16,7 +16,7 @@ public class LogProcessor : IDisposable
};
_processorThread.Start();
}
public readonly Action<LogCapsule> IncommingLogHandlerEvent;
public readonly Action<LogCapsule> IncomingLogHandlerEvent;
public event EventHandler<LogCapsule>? LogProcessed;
private readonly Queue<LogCapsule> _processorQueue;
private readonly Thread _processorThread;
@ -41,10 +41,10 @@ public class LogProcessor : IDisposable
Stop();
}
private void IncommingLogHandler(LogCapsule e)
private void IncomingLogHandler(LogCapsule e)
{
_processorQueue.Enqueue(e);
// Check is the thread is running, if not wake up the thread.
// Check if the thread is running, if not wake up the thread.
if (!_threadSuspendEvent.WaitOne(0))
_threadSuspendEvent.Set();
}
@ -63,7 +63,9 @@ public class LogProcessor : IDisposable
if (_processorQueue.Count != 0)
{
var capsule = _processorQueue.Dequeue();
if (LogService.ShouldLog(LogService.Options.Severity, capsule.Severity))
if (!LogService.CanLog(LogService.Options.Severity, capsule.Severity))
continue;
if (LogService.FilterSeverityLog(capsule))
LogProcessed?.Invoke(this, capsule);
}
else

View File

@ -1,5 +1,6 @@
using System.Collections.ObjectModel;
using System.Reflection;
using DotBased.Extensions;
namespace DotBased.Logging;
@ -8,52 +9,43 @@ namespace DotBased.Logging;
/// </summary>
public static class LogService
{
// TODO: Future: add middlewares and changeable log processor
static LogService()
{
Options = new LogOptions();
LoggerSendEvent = LogProcessor.IncommingLogHandlerEvent;
LoggerSendEvent = LogProcessor.IncomingLogHandlerEvent;
}
public static bool ShouldLog(LogSeverity maxSeverity, LogSeverity severity) => maxSeverity <= severity;
public static bool CanLog(LogSeverity maxSeverity, LogSeverity severity) => maxSeverity <= severity;
public static LogOptions Options { get; private set; }
public static LogProcessor LogProcessor { get; private set; } = new LogProcessor();
private static HashSet<LogAdapterBase> Adapters { get; } = new HashSet<LogAdapterBase>();
private static HashSet<LoggerBase> Loggers { get; } = new HashSet<LoggerBase>();
private static HashSet<LogAdapterBase> Adapters { get; } = [];
private static HashSet<ILogger> Loggers { get; } = [];
/// <summary>
/// Action for internal communication between loggers and processor
/// </summary>
private static readonly Action<LogCapsule> LoggerSendEvent;
internal static readonly Action<LogCapsule> LoggerSendEvent;
/// <summary>
/// Register a logger that will be used in a class and will live as long as the class.
/// This will get the calling assembly and will pass that through ther log adapters.
/// </summary>
/// <example>
/// <code>
/// public class Program
/// {
/// public Program
/// {
/// logger = LogService.RegisterLogger(nameof(Program));
/// }
/// private ILogger logger;
/// }
/// </code>
/// </example>
/// <param name="callerType">The type that called the function</param>
/// <returns>The configured <see cref="ILogger"/> implementation that will be configuered in the <see cref="LogOptions.LoggerBuilder"/> at the <see cref="LogService"/> class</returns>
public static ILogger RegisterLogger(Type callerType)
public static void Initialize(Action<LogOptions>? options = null)
{
var logger = Options.LoggerBuilder.Invoke(new CallerInformation(callerType), LoggerSendEvent);
Options = new LogOptions();
options?.Invoke(Options);
}
public static ILogger RegisterLogger(Type? callerType, string name = "")
{
var logger = Options.LoggerBuilder.Invoke(new LoggerInformation(callerType), name);
Loggers.Add(logger);
return logger;
}
public static bool UnregisterLogger(LoggerBase logger) => Loggers.Remove(logger);
/// <summary>
/// Register a logger.
/// </summary>
/// <returns>The configured <see cref="ILogger"/> implementation that will be configured in the <see cref="LogOptions.LoggerBuilder"/> at the <see cref="LogService"/> class</returns>
public static ILogger RegisterLogger<T>() => RegisterLogger(typeof(T), string.Empty);
public static ReadOnlyCollection<LoggerBase> GetLoggers => new ReadOnlyCollection<LoggerBase>(Loggers.ToList());
public static ReadOnlyCollection<ILogger> GetLoggers => new ReadOnlyCollection<ILogger>(Loggers.ToList());
/// <summary>
/// Add a log adapter to the service.
@ -69,7 +61,7 @@ public static class LogService
/// Removes the log adapter from the service.
/// </summary>
/// <param name="adapter">The adapter to remove</param>
/// <returns>True if the adapter is succesfully removed otherwise false.</returns>
/// <returns>True if the adapter is successfully removed otherwise false.</returns>
public static bool RemoveLogAdapter(LogAdapterBase adapter)
{
if (!Adapters.Contains(adapter)) return false;
@ -77,28 +69,52 @@ public static class LogService
return Adapters.Remove(adapter);
}
public static ReadOnlyCollection<LogAdapterBase> GetAdapters =>
new ReadOnlyCollection<LogAdapterBase>(Adapters.ToList());
}
public static ReadOnlyCollection<LogAdapterBase> GetAdapters => new ReadOnlyCollection<LogAdapterBase>(Adapters.ToList());
public readonly struct CallerInformation
{
public CallerInformation(Type type)
internal static bool FilterSeverityLog(LogCapsule capsule)
{
Name = type.Name;
Source = type.FullName ?? type.GUID.ToString();
Namespace = type.Namespace ?? string.Empty;
SourceAssembly = type.Assembly;
var asmName = SourceAssembly.GetName();
AssemblyName = asmName.Name ?? "Unknown";
AssemblyFullname = asmName.FullName;
if (Options.SeverityFilters.TryGetValue(capsule.Logger.Name, out var namespaceFilter))
return CanLog(namespaceFilter.Severity, capsule.Severity);
var filterCapsuleNamespace = Options.SeverityFilters.Where(kvp => capsule.Logger.Name.Contains(kvp.Filter)).Select(v => v).ToList();
if (filterCapsuleNamespace.Count == 0) return true;
var filter = filterCapsuleNamespace.FirstOrDefault();
return CanLog(filter.Severity, capsule.Severity);
}
public string Name { get; }
public string Source { get; }
public string Namespace { get; }
public Assembly SourceAssembly { get; }
public string AssemblyName { get; }
public string AssemblyFullname { get; }
}
public readonly struct LoggerInformation
{
public LoggerInformation(Type? type)
{
if (type == null)
return;
TypeName = type.Name;
TypeFullName = type.FullName ?? string.Empty;
TypeNamespace = type.Namespace ?? string.Empty;
var module = type.Module;
ModuleName = module.Name;
ModuleScopeName = module.ScopeName;
ModuleFullyQualifiedName = module.FullyQualifiedName;
var assemblyName = type.Assembly.GetName();
AssemblyName = assemblyName.Name ?? (type.Assembly.GetCustomAttributes(typeof(AssemblyTitleAttribute)).FirstOrDefault() as AssemblyTitleAttribute)?.Title ?? string.Empty;
AssemblyFullname = assemblyName.FullName;
if (TypeFullName.IsNullOrEmpty())
TypeFullName = !TypeNamespace.IsNullOrEmpty() ? $"{TypeNamespace}.{TypeName}" : TypeName;
if (TypeNamespace.IsNullOrEmpty())
TypeNamespace = TypeName;
}
public string TypeName { get; } = string.Empty;
public string TypeFullName { get; } = string.Empty;
public string TypeNamespace { get; } = string.Empty;
public string AssemblyName { get; } = string.Empty;
public string AssemblyFullname { get; } = string.Empty;
public string ModuleName { get; } = string.Empty;
public string ModuleScopeName { get; } = string.Empty;
public string ModuleFullyQualifiedName { get; } = string.Empty;
}

View File

@ -8,5 +8,6 @@ public enum LogSeverity
Info = 3,
Warning = 4,
Error = 5,
Fatal = 6
Fatal = 6,
Ignore = 99
}

View File

@ -3,13 +3,8 @@ namespace DotBased.Logging;
/// <summary>
/// Main logger, this class is the default logger that the <see cref="LogService.RegisterLogger"/> function will return.
/// </summary>
public class Logger(CallerInformation caller, ref Action<LogCapsule> logProcessorHandler) : LoggerBase(caller, ref logProcessorHandler)
public class Logger(LoggerInformation loggerInformation, string name) : LoggerBase(loggerInformation, name)
{
public void Log(LogCapsule capsule)
{
ProcessLog(capsule);
}
public override void Verbose(string message, params object?[]? parameters)
{
Log(new LogCapsule()
@ -96,5 +91,5 @@ public class Logger(CallerInformation caller, ref Action<LogCapsule> logProcesso
});
}
public override int GetHashCode() => HashCode.Combine(Caller.Source, Caller.AssemblyFullname);
public override int GetHashCode() => HashCode.Combine(LoggerInformation.TypeFullName, LoggerInformation.AssemblyFullname);
}

View File

@ -1,15 +1,23 @@
using DotBased.Extensions;
namespace DotBased.Logging;
/// <summary>
/// Base for creating loggers
/// </summary>
/// <param name="caller">The caller information</param>
/// <param name="logProcessorHandler">The handler where the logs can be send to</param>
public abstract class LoggerBase(CallerInformation caller, ref Action<LogCapsule> logProcessorHandler) : ILogger
/// <param name="loggerInformation">The caller information</param>
public abstract class LoggerBase(LoggerInformation loggerInformation, string name) : ILogger
{
public CallerInformation Caller { get; } = caller;
public LoggerInformation LoggerInformation { get; } = loggerInformation;
public string Name { get; } = name.IsNullOrEmpty() ? loggerInformation.TypeNamespace : name;
private readonly Action<LogCapsule> ProcessLog = LogService.LoggerSendEvent;
public void Log(LogCapsule capsule)
{
ProcessLog(capsule);
}
internal readonly Action<LogCapsule> ProcessLog = logProcessorHandler;
public abstract void Verbose(string message, params object?[]? parameters);
public abstract void Trace(string message, params object?[]? parameters);

View File

@ -2,11 +2,11 @@ using DotBased.Extensions;
namespace DotBased.Objects;
public class DbObjectAttribute<TValueType> : ObjectAttribute<TValueType>
public class DbObjectAttribute<TValueType> : ObjectAttribute<TValueType> where TValueType : IConvertible
{
public DbObjectAttribute(string key, TValueType value, string owner) : base(key, value)
{
if (owner.IsNullOrWhiteSpace())
if (owner.IsNullOrEmpty())
throw new ArgumentNullException(nameof(owner), $"The parameter {nameof(owner)} is null or empty! This parameter is required!");
Owner = owner;
}

View File

@ -6,7 +6,7 @@ public class ObjectAttribute<TValueType> : IObjectAttribute<TValueType>
{
protected ObjectAttribute(string key, TValueType value)
{
if (key.IsNullOrWhiteSpace())
if (key.IsNullOrEmpty())
throw new ArgumentNullException(nameof(key), $"The parameter {nameof(key)} is null or empty!");
Key = key;
Value = value;

View File

@ -1,4 +1,5 @@
using System.Globalization;
using DotBased.Logging;
namespace DotBased.Utilities;
@ -6,6 +7,7 @@ public static class Culture
{
private static List<CultureInfo> _sysCultures = new List<CultureInfo>();
private static Dictionary<string, RegionInfo> _regions = new Dictionary<string, RegionInfo>();
private static readonly ILogger _logger = LogService.RegisterLogger(typeof(Culture));
/// <summary>
/// Get all system known cultures.
@ -14,6 +16,7 @@ public static class Culture
/// <returns>The list with <see cref="CultureInfo"/>'s the system knows</returns>
public static IEnumerable<CultureInfo> GetSystemCultures()
{
_logger.Debug("Getting system cultures...");
if (_sysCultures.Count == 0)
_sysCultures = CultureInfo.GetCultures(CultureTypes.AllCultures).ToList();
return _sysCultures;

72
TestWebApi/Program.cs Normal file
View File

@ -0,0 +1,72 @@
using DotBased.Logging;
using DotBased.Logging.MEL;
using DotBased.Logging.Serilog;
using Serilog;
using ILogger = Serilog.ILogger;
var builder = WebApplication.CreateBuilder(args);
LogService.Initialize(options =>
{
options
.AddSeverityFilter("Program", LogSeverity.Verbose);
});
var serilogLogger = SetupSerilog();
LogService.AddLogAdapter(new BasedSerilogAdapter(serilogLogger));
builder.Logging.ClearProviders();
builder.Logging.AddDotBased(LogService.Options);
// Add services to the container.
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi();
app.Run();
ILogger SetupSerilog()
{
var logConfig = new LoggerConfiguration()
.MinimumLevel.Verbose()
.WriteTo.Console(outputTemplate: BasedSerilog.OutputTemplate);
return logConfig.CreateLogger();
}
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.8"/>
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DotBased.Logging.MEL\DotBased.Logging.MEL.csproj" />
<ProjectReference Include="..\DotBased.Logging.Serilog\DotBased.Logging.Serilog.csproj" />
<ProjectReference Include="..\DotBased\DotBased.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
@TestWebApi_HostAddress = http://localhost:5044
GET {{TestWebApi_HostAddress}}/weatherforecast/
Accept: application/json
###