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

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

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

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

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

    最佳答案

    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) { }
            }
        }
    
    您将在 ILogger 的实现中使用此类小号 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
            }
        }
    
    这在我的 ASP.NET Core 应用程序中运行良好,尽管您可以通过批量插入日志来调整它的性能。我确实调用 SaveChanges()对于每个可能非常慢的条目。
    在您的情况下,您可以使用异步函数委托(delegate)创建线程:new Thread(async() => await WriteQueue())并使用 _context.SaveChangesAsync() .
    编辑(根据评论):
    public class RequestLoggerConfiguration
    {
        public LogLevel LogLevel { get; set; } = LogLevel.Error;
        public int Buffer { get; set; } = 1024;
    }
    

    关于c# - 在 asp.net 核心应用程序中应该如何实现对数据库的异步日志记录?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59919244/

    相关文章:

    asp.net-core - Visual Studio 2015 中支持 ASP.NET Core?

    c# - 在 .NET Core(.NET Standard 1.4 和 .NET Framework 4.6.1)中对 System.Net.Http 使用 await/async 时出现错误?

    c# - 如何在树状结构中列出程序集(包括引用的程序集)中的所有属性?

    java - 领域驱动设计实体和值对象

    asp.net-core - ASP.NET Core - MVC - View 中的模型变量 - 空异常

    asp.net - 如何在 asp.net 核心应用程序中运行 ssis 包?

    javascript - 重试失败的异步/ promise 功能?

    objective-c - 在继续之前等待 bool 值发生变化

    c# - 如何将编译器检查的属性名称/表达式树传递给自定义属性

    c# - 我如何知道我从流中获得的图像格式?