Sto cercando di creare un sink SeriLog personalizzato che si colleghi a EntityFrameworkCore. Ne ho trovato uno esistente chiamato Serilog.Sinks.EntityFrameworkCore ma utilizzava il suo DbContext e devo essere in grado di utilizzare un DbContext esistente.
Quindi, ho praticamente creato la mia versione del codice che funziona con il mio DbContext. Tuttavia, ogni volta che viene chiamato il metodo Emit e tenta di caricare DbContext, viene visualizzato il seguente errore:
Cannot resolve scoped service ... from root provider
Ho visto altri post su questo problema che riguardano servizi e middleware. Tuttavia, non credo che quello che ho è il middleware.
In poche parole, ecco i pezzi principali del mio codice (di nuovo, la maggior parte dei quali è copiata dal già citato 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();
}
}
L'errore si verifica in EntityFrameworkSink.cs nella riga DbContext context = _dbContextProvider.Invoke();
Qualche idea sul perché questo sta generando un errore e come farlo funzionare?
Aggiornare
Sulla base dei commenti di Eric, ho aggiornato il mio codice startup.cs come segue:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, SystemModelBuilder modelBuilder, ILoggerFactory loggerFactory, IServiceProvider provider)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.EntityFrameworkSink(provider.GetService<EligibilityDbContext>)
.CreateLogger();
}
Ora ottengo l'errore: Cannot access a disposed object. Object name: IServiceProvider
Caveat To Answer
Quindi ho segnato la risposta di Tao Zhou come risposta. Tuttavia, non era quello che ha detto, ma il codice che ha fornito che in realtà ha fornito la risposta. Non credo che EmitBatchAsync
risolverà effettivamente qual è il mio problema, tuttavia ho riscontrato un altro paio di commenti, ecc. Che indicano che potrebbe contribuire a migliorare le prestazioni.
Ciò che effettivamente ha risolto il problema è stato seguendo il suo esempio di codice. app.ApplicationServices
passa app.ApplicationServices
. Quindi, nell'attuale implementazione Sink, ha creato un ambito per la risoluzione di un'istanza di dbContext:
using(var context = service.CreateScope().ServiceProvider.GetRequiredService<EligibilityDbContext>())
{
}
Questo in realtà ha risolto tutti gli errori che stavo ottenendo e ha funzionato come mi aspettavo. Grazie
Per utilizzare Serilog
con EF Core
, potrebbe essere necessario implementare PeriodicBatchingSink
invece di ILogEventSink
.
Segui i passaggi seguenti:
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();
Codice sorgente: StartupEFCore
Quando chiami app.ApplicationServices.GetService<EligibilityDbContext>
, stai risolvendo direttamente un servizio con scope dal contenitore dell'applicazione che non è consentito. Se aggiungi EligibilityDbContext come parametro al metodo Configure, genererà un ambito e inserirà il contesto nel tuo metodo.
public void Configure(IApplicationBuilder app, ..., EligibilityDbContext context)
{
// ... use context
}