我正在尝试创建一个与EntityFrameworkCore绑定的自定义SeriLog接收器。我找到了一个名为Serilog.Sinks.EntityFrameworkCore的现有版本,但它使用了自己的DbContext,因此我需要能够使用现有的DbContext。
因此,我基本上创建了自己的可与DbContext一起使用的代码版本。但是,每次调用Emit方法并尝试加载DbContext时,都会出现以下错误:
Cannot resolve scoped service ... from root provider
我还看到了有关此问题的其他帖子,涉及范围服务和中间件。但是,我不相信自己拥有的就是中间件。
简而言之,这是我代码的核心部分(同样,其中大部分是从前面提到的Git Repo中复制的。)
startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<EligibilityDbContext>(opts => opts.UseSqlServer(Configuration.GetConnectionString("EligibilityDbConnection")));
}
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
SystemModelBuilder modelBuilder,
ILoggerFactory loggerFactory)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.EntityFrameworkSink(app.ApplicationServices.GetService<EligibilityDbContext>)
.CreateLogger();
loggerFactory.AddSeriLog();
}
EntityFrameworkSinkExtensions.cs
public static class EntityFrameworkSinkExtensions
{
public static LoggerConfiguration EntityFrameworkSink(
this LoggerSinkConfiguration loggerConfiguration,
Func<EligibilityDbContext> dbContextProvider,
IFormatProvider formatProvider = null)
{
return loggerConfiguration.Sink(new EntityFrameworkSink(dbContextProvider, formatProvider));
}
}
EntityFrameworkSink.cs
public class EntityFrameworkSink : ILogEventSink
{
private readonly IFormatProvider _formatProvider;
private readonly Func<EligibilityDbContext> _dbContextProvider;
private readonly JsonFormatter _jsonFormatter;
static readonly object _lock = new object();
public EntityFrameworkSink(Func<EligibilityDbContext> dbContextProvider, IFormatProvider formatProvider)
{
_formatProvider = formatProvider;
_dbContextProvider = dbContextProvider ?? throw new ArgumentNullException(nameof(dbContextProvider));
_jsonFormatter = new JsonFormatter(formatProvider: formatProvider);
}
public void Emit(LogEvent logEvent)
{
lock (_lock)
{
if (logEvent == null)
{
return;
}
try
{
var record = ConvertLogEventToLogRecord(logEvent);
//! This is the line causing the problems!
DbContext context = _dbContextProvider.Invoke();
if (context != null)
{
context.Set<LogRecord>().Add(this.ConvertLogEventToLogRecord(logEvent));
context.SaveChanges();
}
}
catch(Exception ex)
{
// ignored
}
}
}
private LogRecord ConvertLogEventToLogRecord(LogEvent logEvent)
{
if (logEvent == null)
return null;
string json = this.ConvertLogEventToJson(logEvent);
JObject jObject = JObject.Parse(json);
JToken properties = jObject["Properties"];
return new LogRecord
{
Exception = logEvent.Exception?.ToString(),
Level = logEvent.Level.ToString(),
LogEvent = json,
Message = logEvent.RenderMessage(this._formatProvider),
MessageTemplate = logEvent.MessageTemplate?.ToString(),
TimeStamp = logEvent.Timestamp.DateTime.ToUniversalTime(),
EventId = (int?)properties["EventId"]?["Id"],
SourceContext = (string)properties["SourceContext"],
ActionId = (string)properties["ActionId"],
ActionName = (string)properties["ActionName"],
RequestId = (string)properties["RequestId"],
RequestPath = (string)properties["RequestPath"]
};
}
private string ConvertLogEventToJson(LogEvent logEvent)
{
if (logEvent == null)
{
return null;
}
StringBuilder sb = new StringBuilder();
using (StringWriter writer = new StringWriter(sb))
{
this._jsonFormatter.Format(logEvent, writer);
}
return sb.ToString();
}
}
该错误发生在DbContext context = _dbContextProvider.Invoke();
行上的EntityFrameworkSink.cs中
关于为什么会引发错误以及如何使其正常工作的任何想法?
更新
根据Eric的评论,我更新了startup.cs代码,如下所示:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, SystemModelBuilder modelBuilder, ILoggerFactory loggerFactory, IServiceProvider provider)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.EntityFrameworkSink(provider.GetService<EligibilityDbContext>)
.CreateLogger();
}
现在我得到了错误:Cannot access a disposed object. Object name: IServiceProvider
要回答的问题
因此,我将涛州的答案标记为答案。但是,不是他所说的,而是他提供的代码实际上提供了答案。我认为EmitBatchAsync
并不能真正解决我的问题-但是,我在其他地方遇到了其他一些评论,等等,表明这可能有助于提高性能。
实际解决问题的方法是遵循他的代码示例。在启动时,他正在传递app.ApplicationServices
。然后,在实际的Sink实现中,他创建了一个用于解析dbContext实例的范围:
using(var context = service.CreateScope().ServiceProvider.GetRequiredService<EligibilityDbContext>())
{
}
这实际上解决了我遇到的所有错误,并使它按预期的方式工作。谢谢
答案 0 :(得分:2)
调用app.ApplicationServices.GetService<EligibilityDbContext>
时,您是在应用容器中直接解析scoped service,这是不允许的。如果将EligibilityDbContext作为参数添加到Configure方法中,它将生成一个作用域并将上下文注入您的方法中。
public void Configure(IApplicationBuilder app, ..., EligibilityDbContext context)
{
// ... use context
}
答案 1 :(得分:0)
对于将Serilog
与EF Core
一起使用,您可能需要实现PeriodicBatchingSink
而不是ILogEventSink
。
请按照以下步骤操作:
Serilog.Sinks.PeriodicBatching
EntityFrameworkCoreSinkExtensions
public static class EntityFrameworkCoreSinkExtensions
{
public static LoggerConfiguration EntityFrameworkCoreSink(
this LoggerSinkConfiguration loggerConfiguration,
IServiceProvider serviceProvider,
IFormatProvider formatProvider = null)
{
return loggerConfiguration.Sink(new EntityFrameworkCoreSink(serviceProvider, formatProvider, 10 , TimeSpan.FromSeconds(10)));
}
}
EntityFrameworkCoreSink
public class EntityFrameworkCoreSink : PeriodicBatchingSink
{
private readonly IFormatProvider _formatProvider;
private readonly IServiceProvider _serviceProvider;
private readonly JsonFormatter _jsonFormatter;
static readonly object _lock = new object();
public EntityFrameworkCoreSink(IServiceProvider serviceProvider, IFormatProvider formatProvider, int batchSizeLimit, TimeSpan period):base(batchSizeLimit, period)
{
this._formatProvider = formatProvider;
this._serviceProvider = serviceProvider;
this._jsonFormatter = new JsonFormatter(formatProvider: formatProvider);
}
protected override async Task EmitBatchAsync(IEnumerable<LogEvent> events)
{
using (var context = _serviceProvider.CreateScope().ServiceProvider.GetRequiredService<ApplicationDbContext>())
{
if (context != null)
{
foreach (var logEvent in events)
{
var log = this.ConvertLogEventToLogRecord(logEvent);
await context.AddAsync(log);
}
await context.SaveChangesAsync();
}
}
}
private LogRecord ConvertLogEventToLogRecord(LogEvent logEvent)
{
if (logEvent == null)
{
return null;
}
string json = this.ConvertLogEventToJson(logEvent);
JObject jObject = JObject.Parse(json);
JToken properties = jObject["Properties"];
return new LogRecord
{
Exception = logEvent.Exception?.ToString(),
Level = logEvent.Level.ToString(),
LogEvent = json,
Message = this._formatProvider == null ? null : logEvent.RenderMessage(this._formatProvider),
MessageTemplate = logEvent.MessageTemplate?.ToString(),
TimeStamp = logEvent.Timestamp.DateTime.ToUniversalTime(),
EventId = (int?)properties["EventId"]?["Id"],
SourceContext = (string)properties["SourceContext"],
ActionId = (string)properties["ActionId"],
ActionName = (string)properties["ActionName"],
RequestId = (string)properties["RequestId"],
RequestPath = (string)properties["RequestPath"]
};
}
private string ConvertLogEventToJson(LogEvent logEvent)
{
if (logEvent == null)
{
return null;
}
StringBuilder sb = new StringBuilder();
using (StringWriter writer = new StringWriter(sb))
{
this._jsonFormatter.Format(logEvent, writer);
}
return sb.ToString();
}
}
Startup
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.EntityFrameworkCoreSink(app.ApplicationServices)
.CreateLogger();
loggerFactory.AddSerilog();
源代码:StartupEFCore