如何在asp.net核心应用程序中实现对数据库的异步日志记录?

Мар*_*вич 5 c# logging asynchronous async-await asp.net-core

asp.net 核心(或其他库)中有一个 ILogger,我可以设置我的代码以将日志写入 azure 或数据库或控制台等,但我想知道的是这个 ILogger 是同步的。在 docs.microsoft 我读到这个他们说“记录器应该是同步的,考虑将日志写入某个队列并让后台工作人员将这些日志拉到您的数据库中”。现在,我有几个问题。

  1. 我应该为我自己的异步日志实现而烦恼,还是 ASP.NET Core 已经为我做了?因为对此有很多担忧,而且就时间而言,这不是一件容易的事情。
  2. 如果我不想使用即发即弃的方法并且不想让用户等待每个日志记录任务完成,您将如何实现异步日志记录?在一个单独的方面实现它也很好,不要使代码变脏,将其从横切关注点中解放出来。

也许我在问一个愚蠢的问题或一个广泛的问题,但这对我来说是一个我不太了解的广泛话题。请帮忙。我也想看一些代码示例(一些 github repos 或其他东西)

cmx*_*mxl 3

微软在实现中所做的ConsoleLogger是使用后台线程,它将排队的消息写入控制台。我刚刚实现了类似的东西,但使用了 EF Core 上下文。

原始实现可以在这里找到: https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Logging.Console/src/ConsoleLoggerProcessor.cs

我的实现看起来像这样:

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) { }
        }
    }
Run Code Online (Sandbox Code Playgroud)

ILogger您可以在s函数的实现中使用此类,Log<TState>如下所示:

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
        }
    }
Run Code Online (Sandbox Code Playgroud)

这在我的 ASP.NET Core 应用程序中工作得很好,尽管您可以通过批量插入日志来调整它的性能。我确实要求SaveChanges()每个条目可能会很慢。

在您的情况下,您可以使用异步函数委托创建线程:

new Thread(async() => await WriteQueue())并使用_context.SaveChangesAsync().

编辑(根据评论):

public class RequestLoggerConfiguration
{
    public LogLevel LogLevel { get; set; } = LogLevel.Error;
    public int Buffer { get; set; } = 1024;
}
Run Code Online (Sandbox Code Playgroud)