There's an ILogger in asp.net core (or in other libraries too), and I can setup my code to write logs to azure or database or console etc, but what I'm wondering about is that this ILogger is synchronous. On docs.microsoft I read about this they said "logger should be synchronous, consider writing logs to some queue and have a background worker pulling these logs to your database". Now, I have a couple of questions.
Maybe I'm asking a dumb question or a broad one but this is a broad topic to me that I don't really understand. Please help. Also I would like to see some code examples (some github repos or something)
What Microsoft is doing in their implementation of the ConsoleLogger is using a background thread which will write queued messages to the console.
I just implemented something similar but with an EF Core context instead.
The original implementation can be found here: https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProcessor.cs
My implementation looks something like this:
public class RequestLogProcessor : IDisposable
    {
        private readonly BlockingCollection<RequestLog> _queue;
        private readonly Thread _writingThread;
        private readonly DbContext _context;
        public RequestLogProcessor(DbContext context, RequestLoggerConfiguration configuration)
        {
            _queue = new BlockingCollection<RequestLog>(configuration.Buffer);
            _context = context;
            _writingThread = new Thread(WriteQueue)
            {
                IsBackground = true,
                Name = "RequestLogProcessor Thread"
            };
            _writingThread.Start();
        }
        private void WriteQueue()
        {
            try
            {
                foreach (var message in _queue.GetConsumingEnumerable())
                {
                    WriteMessage(message);
                }
            }
            catch
            {
                try
                {
                    _queue.CompleteAdding();
                }
                catch { }
            }
        }
        public void Enqueue(RequestLog log)
        {
            if (!_queue.IsAddingCompleted)
            {
                try
                {
                    _queue.Add(log);
                    return;
                }
                catch (InvalidOperationException) { }
            }
            try
            {
                WriteMessage(log);
            }
            catch (Exception) { }
        }
        private void WriteMessage(RequestLog log)
        {
            _context.RequestLogs.Add(log);
            _context.SaveChanges();
        }
        public void Dispose()
        {
            _queue.CompleteAdding();
            try
            {
                _writingThread.Join(1500);
            }
            catch (ThreadStateException) { }
        }
    }
You would use this class in your implementation of ILoggers Log<TState> function like so:
public class RequestLogger : ILogger
    {
        private readonly RequestLoggerConfiguration _config;
        private readonly RequestLogProcessor _processor;
        public RequestLogger(
            RequestLoggerConfiguration config,
            RequestLogProcessor processor)
        {
            _config = config;
            _processor = processor;
        }
        public IDisposable BeginScope<TState>(TState state)
            => default;
        public bool IsEnabled(LogLevel logLevel)
            => logLevel <= _config.LogLevel && logLevel != LogLevel.None;
        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
                return;
            if (state is not RequestLog log)
                return;
            log.Exception ??= exception?.ToString();
            log.Level = logLevel.ToString();
            _processor.Enqueue(log); // this is the line
        }
    }
This works perfectly fine in my ASP.NET Core application, although you could tweak it's performance by batch inserting logs. I did call SaveChanges() for each entry which could possibly be pretty slow.
In your case you can just create the Thread with an async function delegate:
new Thread(async() => await WriteQueue()) and use _context.SaveChangesAsync().
EDIT(as per comment):
public class RequestLoggerConfiguration
{
    public LogLevel LogLevel { get; set; } = LogLevel.Error;
    public int Buffer { get; set; } = 1024;
}
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With