I am in the middle of studying the ASP.NET Core, and I have implemented logging with a file system successfully, but how about implementing logging feature with a database solution. How to pass EF context to my 'LoggerDatabaseProvider
', and still have those two decoupled? The code below should clear some upfront questions:
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFramework()
.AddEntityFrameworkSqlServer()
.AddDbContext<WorldContext>();
services.AddTransient<WorldContextSeedData>();
services.AddScoped<IWorldRepository, WorldRepository>();
//this one adds service with Dependency Injection that calls 'MyLogger' constructor with two parameters, instead the default one parametereless constructor.
services.AddScoped<ILogger, LoggerFileProvider.Logger>(provider => new LoggerFileProvider.Logger("CustomErrors", Startup.Configuration["Data:LogFilePath"]));
//services.AddScoped<ILogger, LoggerDatabaseProvider.Logger>(provider => new LoggerDatabaseProvider.Logger("CustomErrors"));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, WorldContextSeedData seeder)
{
string basePath = Startup.Configuration["Data:LogFilePath"];
loggerFactory.AddProvider(new LoggerFileProvider(basePath));
loggerFactory.AddProvider(new LoggerDatabaseProvider());
app.UseStaticFiles();
app.UseMvc(config =>
{
config.MapRoute(
name: "Default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "App", action = "index" }
);
});
seeder.EnsureSeedData();
}
Here is my successfully implemented logger with text file:
public class LoggerFileProvider : ILoggerProvider
{
private readonly string _logFilePath;
public LoggerFileProvider(string logFilePath)
{
_logFilePath = logFilePath;
}
public ILogger CreateLogger(string categoryName)
{
return new Logger(categoryName, _logFilePath);
}
public void Dispose()
{
}
public class Logger : ILogger
{
private readonly string _categoryName;
private readonly string _path;
public Logger(string categoryName, string logFilePath)
{
_path = logFilePath;
_categoryName = categoryName;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
try
{
RecordMsg(logLevel, eventId, state, exception, formatter);
}
catch (Exception ex)
{
//this is being used in case of error 'the process cannot access the file because it is being used by another process', could not find a better way to resolve the issue
RecordMsg(logLevel, eventId, state, exception, formatter);
}
}
private void RecordMsg<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
string msg = $"{logLevel} :: {_categoryName} :: {formatter(state, exception)} :: username :: {DateTime.Now}";
using (var writer = File.AppendText(_path))
{
writer.WriteLine(msg);
}
}
public IDisposable BeginScope<TState>(TState state)
{
return new NoopDisposable();
}
private class NoopDisposable : IDisposable
{
public void Dispose()
{
}
}
}
}
But how about the very similar implementation, but with database solution? My WorldContext
is utilized by EF to communicate with database, but if it would be easier I could utilize my repository. I would like to keep the context/repository decoupled from 'LoggerDatabaseProvider
' that I am about to implement, so I could utilize it in other projects.
Per specification, here is how I managed to implemented it, just in case someone would look for similar solution.
'Startup.cs':
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFramework()
.AddEntityFrameworkSqlServer()
.AddDbContext<WorldContext>();
services.AddLogging();
services.AddTransient<WorldContextSeedData>();
services.AddScoped<IMailService, MailServiceDebug>();
services.AddScoped<IWorldRepository, WorldRepository>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, WorldContextSeedData seeder, IWorldRepository worldRepo)
{
string basePath = Startup.Configuration["Data:LogFilePath"];
loggerFactory.AddProvider(new LoggerFileProvider(basePath));
loggerFactory.AddProvider(new LoggerDatabaseProvider(worldRepo));
app.UseStaticFiles();
app.UseMvc(config =>
{
config.MapRoute(
name: "Default",
template: "{controller}/{action}/{id?}",
defaults: new { controller = "App", action = "index" }
);
});
seeder.EnsureSeedData();
}
Custom 'LoggerFileProvider.cs':
public class LoggerFileProvider : ILoggerProvider
{
private readonly string _logFilePath;
public LoggerFileProvider(string logFilePath)
{
_logFilePath = logFilePath;
}
public ILogger CreateLogger(string categoryName)
{
return new Logger(categoryName, _logFilePath);
}
public void Dispose()
{
}
public class Logger : ILogger
{
private readonly string _categoryName;
private readonly string _path;
public Logger(string categoryName, string logFilePath)
{
_path = logFilePath;
_categoryName = categoryName;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
try
{
RecordMsg(logLevel, eventId, state, exception, formatter);
}
catch (Exception ex)
{
//this is being used in case of error 'the process cannot access the file because it is being used by another process', could not find a better way to resolve the issue
RecordMsg(logLevel, eventId, state, exception, formatter);
}
}
private void RecordMsg<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
string msg = $"{logLevel} :: {_categoryName} :: {formatter(state, exception)} :: username :: {DateTime.Now}";
using (var writer = File.AppendText(_path))
{
writer.WriteLine(msg);
}
}
public IDisposable BeginScope<TState>(TState state)
{
return new NoopDisposable();
}
private class NoopDisposable : IDisposable
{
public void Dispose()
{
}
}
}
}
Custom 'LoggerDatabaseProvider.cs':
public class LoggerDatabaseProvider : ILoggerProvider
{
private IWorldRepository _repo;
public LoggerDatabaseProvider(IWorldRepository repo)
{
_repo = repo;
}
public ILogger CreateLogger(string categoryName)
{
return new Logger(categoryName, _repo);
}
public void Dispose()
{
}
public class Logger : ILogger
{
private readonly string _categoryName;
private readonly IWorldRepository _repo;
public Logger(string categoryName, IWorldRepository repo)
{
_repo = repo;
_categoryName = categoryName;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (logLevel == LogLevel.Critical || logLevel == LogLevel.Error || logLevel == LogLevel.Warning)
RecordMsg(logLevel, eventId, state, exception, formatter);
}
private void RecordMsg<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_repo.Log(new Log
{
LogLevel = logLevel.ToString(),
CategoryName = _categoryName,
Msg = formatter(state, exception),
User = "username",
Timestamp = DateTime.Now
});
}
public IDisposable BeginScope<TState>(TState state)
{
return new NoopDisposable();
}
private class NoopDisposable : IDisposable
{
public void Dispose()
{
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With