如何在.NET 6中在自定义日志记录提供程序中使用数据库上下文

huangapple go评论88阅读模式
英文:

How to use db context in custom logger provider .NET 6

问题

我无法将数据库上下文添加到Program.cs中的自定义日志记录提供程序中。

我尝试以下代码:

builder.Services.AddScoped<EFDataContext>();
var app = builder.Build();
builder.Logging.AddProvider(new CustomLoggerProvider(app.Services.GetRequiredService<EFDataContext>()));

但是出现错误(无法使用单例模式进行dbContext,因为存在选项):'无法从根提供程序解析作用域服务 'eSchronisko.Server.Domain.EFDataContext'。'。

英文:

I can`t add database context into custom logger provider in Program.cs

I try code below:

builder.Services.AddScoped&lt;EFDataContext&gt;();
var app = builder.Build();
builder.Logging.AddProvider(new CustomLoggerProvider(app.Services.GetRequiredService&lt;EFDataContext&gt;()));

but get an error (and can`t use singleton for dbContext due to options): 'Cannot resolve scoped service 'eSchronisko.Server.Domain.EFDataContext' from root provider.'.

答案1

得分: 1

  1. 我建议不要重新发明轮子,而是使用已存在的库,该库提供了将日志记录到数据库的功能。例如,Serilog,它有多个适配器允许将日志写入不同的数据库。

  2. 如果你仍然考虑重新发明轮子出于某种原因 - 最好查看扩展一些现有的日志记录库,而不是从头开始。

  3. 如果你不想在现有库的基础上构建 - 我强烈建议防止多次构建应用程序,并通过以下方法利用日志记录器提供程序中的依赖注入(参见自定义日志记录器文档):

    public class CustomLoggerProvider : ILoggerProvider
    {
    	private readonly IServiceScopeFactory _scopeFactory;
    
    	public CustomLoggerProvider(IServiceScopeFactory scopeFactory)
    	{
    		_scopeFactory = scopeFactory;
    	}
    
    	public void Dispose(){} // 待办事项
    
    	public ILogger CreateLogger(string categoryName) => new CustomLogger(categoryName, _scopeFactory);
    }
    
    public sealed class CustomLogger : ILogger
    {
    	private readonly IServiceScopeFactory _scopeFactory;
    
    	public CustomLogger(string category, IServiceScopeFactory scopeFactory)
    	{
    		_scopeFactory = scopeFactory;
    	}
    
    	public IDisposable? BeginScope<TState>(TState state) where TState : notnull => default!;
    
    	public bool IsEnabled(LogLevel logLevel) => true; // 待办事项
    
    	public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
    	{
    		using var serviceScope = _scopeFactory.CreateScope();
    		var provider = serviceScope.ServiceProvider;
            // 解析作用域服务,例如数据库上下文: 
    		var someService = provider.GetRequiredService<SomeService>(); 
    		// 使用 someService...
    	}
    }
    

    并进行注册:

    builder.Services.TryAddEnumerable(new ServiceDescriptor(
        typeof(ILoggerProvider),
        typeof(CustomLoggerProvider),
        ServiceLifetime.Singleton
    ));
    
  4. 解决初始问题 - 要从根作用域访问上下文,你需要更改上下文的生命周期范围(默认为 Scoped,尽管个人在一般情况下不建议更改它),或者手动创建一个作用域 - 请参阅此答案

英文:
  1. I would recommend to skip reinventing the wheel and use an existing library which provides logging into database. For example Serilog which has several sinks allowing to write to different databases.

  2. If you still considering reinventing the wheel for some reason - still better look into extending some existing logger library then starting from scratch.

  3. If you do not want to build on top of the existing library - I strongly recommend prevent building app several times and leverage DI in the logger provider via the following approach (see the custom logger docs):

    public class CustomLoggerProvider : ILoggerProvider
    {
    	private readonly IServiceScopeFactory _scopeFactory;
    
    	public CustomLoggerProvider(IServiceScopeFactory scopeFactory)
    	{
    		_scopeFactory = scopeFactory;
    	}
    
    	public void Dispose(){} // todo
    
    	public ILogger CreateLogger(string categoryName) =&gt; new CustomLogger(categoryName, _scopeFactory);
    }
    
    public sealed class CustomLogger : ILogger
    {
    	private readonly IServiceScopeFactory _scopeFactory;
    
    	public CustomLogger(string category, IServiceScopeFactory scopeFactory)
    	{
    		_scopeFactory = scopeFactory;
    	}
    
    	public IDisposable? BeginScope&lt;TState&gt;(TState state) where TState : notnull =&gt; default!;
    
    	public bool IsEnabled(LogLevel logLevel) =&gt; true; // todo
    
    	public void Log&lt;TState&gt;(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func&lt;TState, Exception?, string&gt; formatter)
    	{
    		using var serviceScope = _scopeFactory.CreateScope();
    		var provider = serviceScope.ServiceProvider;
            // resolve scoped service, for example db context: 
    		var someService = provider.GetRequiredService&lt;SomeService&gt;(); 
    		// use someService...
    	}
    }
    

    and registration:

    builder.Services.TryAddEnumerable(new ServiceDescriptor(
        typeof(ILoggerProvider),
        typeof(CustomLoggerProvider),
        ServiceLifetime.Singleton
    ));
    
  4. Addressing the initial problem - to access context from the root scope you need either change the context lifetime scope (default - Scoped, though personally I would not recommend changing it in general case) or create a scope manually - see this answer.

huangapple
  • 本文由 发表于 2023年1月9日 02:13:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/75050254.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定