I'm trying to create a custom SeriLog sink that ties to EntityFrameworkCore. I found an existing one called Serilog.Sinks.EntityFrameworkCore but it used its own DbContext and I need to be able to use an existing DbContext.
So, I basically created my own version of the code that works with my DbContext. However, every time the Emit method gets called and it tries to load the DbContext, I get the following error:
Cannot resolve scoped service ... from root provider
I have seen other posts regarding this issue that involve scoped services and middleware. However, I don't believe that what I've got is middleware.
In a nutshell, here are the core pieces of my code (again, most of which is copied from the previously mentioned 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();
}
}
The error occurs in EntityFrameworkSink.cs on the line DbContext context = _dbContextProvider.Invoke();
Any thoughts on why this is throwing an error and how to get this working?
Update
Based on Eric's comments, I updated my startup.cs code as follows:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, SystemModelBuilder modelBuilder, ILoggerFactory loggerFactory, IServiceProvider provider)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.EntityFrameworkSink(provider.GetService<EligibilityDbContext>)
.CreateLogger();
}
Now I get the error: Cannot access a disposed object. Object name: IServiceProvider
Cannot access a disposed object. Object name: IServiceProvider
Caveat To Answer
So I marked Tao Zhou's answer as the answer. However, it was not what he said but the code he provided that actually provided the answer. I don't believe that EmitBatchAsync
will actually resolve what my issue was -- however, I've run across a couple other comments, etc. elsewhere that indicate that it may help improve performance.
What actually resolved the problem was following his code sample. In startup, he is passing app.ApplicationServices
. Then, in the actual Sink implementation, he created a scope for resolving an instance of the dbContext:
using(var context = service.CreateScope().ServiceProvider.GetRequiredService<EligibilityDbContext>())
{
}
This actually resolved all the errors I was getting and got this working the way I had expected. Thanks
When you call app.ApplicationServices.GetService<EligibilityDbContext>
, you're directly resolving a scoped service from the application container which isn't allowed. If you add EligibilityDbContext as a parameter to the Configure method, it will generate a scope and inject the context into your method.
public void Configure(IApplicationBuilder app, ..., EligibilityDbContext context)
{
// ... use context
}
For using Serilog
with EF Core
, you may need to implement PeriodicBatchingSink
instead of ILogEventSink
.
Follow steps below:
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();
Source Code: StartupEFCore
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.