using System.Collections.Concurrent; using Microsoft.Extensions.Options; using ROLAC.API.Entities.Logging; using MsLogLevel = Microsoft.Extensions.Logging.LogLevel; namespace ROLAC.API.Services.Logging; /// /// A singleton that turns framework/app log events into /// rows enqueued onto . It depends only on /// singletons (queue, options, IHttpContextAccessor) and NEVER touches a DbContext — that is /// what makes the enqueue-only design safe from the singleton logging pipeline. /// [ProviderAlias("Database")] public sealed class DbLoggerProvider : ILoggerProvider { private readonly SystemLogQueue _queue; private readonly DatabaseLoggerOptions _options; private readonly IHttpContextAccessor _http; private readonly ConcurrentDictionary _loggers = new(); public DbLoggerProvider( SystemLogQueue queue, IOptions options, IHttpContextAccessor http) { _queue = queue; _options = options.Value; _http = http; } public ILogger CreateLogger(string categoryName) => _loggers.GetOrAdd(categoryName, name => new DbLogger(name, _queue, _options, _http)); public void Dispose() => _loggers.Clear(); } /// The per-category logger. Drops events below the floor or in excluded categories. internal sealed class DbLogger : ILogger { private readonly string _category; private readonly SystemLogQueue _queue; private readonly DatabaseLoggerOptions _options; private readonly IHttpContextAccessor _http; private readonly bool _excluded; public DbLogger( string category, SystemLogQueue queue, DatabaseLoggerOptions options, IHttpContextAccessor http) { _category = category; _queue = queue; _options = options; _http = http; _excluded = options.ExcludedCategories.Any(prefix => category.StartsWith(prefix, StringComparison.Ordinal)); } public IDisposable? BeginScope(TState state) where TState : notnull => null; public bool IsEnabled(MsLogLevel logLevel) => !_excluded && logLevel != MsLogLevel.None && logLevel >= _options.MinimumLevel; public void Log( MsLogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { if (!IsEnabled(logLevel)) return; var context = _http.HttpContext; var log = new SystemLog { Timestamp = DateTimeOffset.UtcNow, Level = LogLevelMap.FromMs(logLevel), Category = _category, EventId = eventId.Id == 0 ? null : eventId.Id, Message = formatter(state, exception), Exception = exception?.ToString(), RequestPath = context?.Request.Path.Value, HttpMethod = context?.Request.Method, UserId = context?.User.FindFirst("sub")?.Value, IpAddress = context?.Connection.RemoteIpAddress?.ToString(), CorrelationId = context?.TraceIdentifier, }; _queue.TryEnqueue(log); } }