Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core ILoggerProvider for database

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.

like image 707
lucas Avatar asked Jul 27 '16 15:07

lucas


1 Answers

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()
            {
            }
        }
    }
}
like image 53
lucas Avatar answered Sep 22 '22 10:09

lucas