【问题标题】:How should async logging to database be implemented in asp.net core application?在 asp.net 核心应用程序中应该如何实现对数据库的异步日志记录?
【发布时间】:2020-05-12 03:02:32
【问题描述】:

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

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

也许我在问一个愚蠢的问题或一个宽泛的问题,但这对我来说是一个宽泛的话题,我并不真正理解。请帮忙。我也想看一些代码示例(一些 github repos 之类的)

【问题讨论】:

  • 我认为你“自己”实现的异步日志记录到数据库将使用_context.AddAsync 和_context.SaveChangesAsync()。我要做的是使用 EF 创建一个日志类,将日志写入类实例,完成后,只需运行上面的异步操作,除非我完全误解了你......
  • ILogger 绝对是同步的。我也在寻找异步解决方案

标签: c# asp.net-core asynchronous logging async-await


【解决方案1】:

Microsoft 在实现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) { }
        }
    }

您将在您的ILoggers Log&lt;TState&gt; 函数的实现中使用此类:

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
        }
    }

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

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

new Thread(async() =&gt; await WriteQueue()) 并使用_context.SaveChangesAsync()。

编辑(根据评论):

public class RequestLoggerConfiguration
{
    public LogLevel LogLevel { get; set; } = LogLevel.Error;
    public int Buffer { get; set; } = 1024;
}

【讨论】:

  • 我在这里试用您的代码。能否提供 RequestLoggerConfiguration 的定义?
  • 你是如何在 RequestLogger 构造函数中传递 RequestLogProcessor 的?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-19
  • 1970-01-01
相关资源
最近更新 更多