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