4
\$\begingroup\$

In the Startup.cs of my ASP.NET Core application I want to configure the LoggingFactory so that it logs into the database. Therefore I create following DBLoggerExtensions class to add a context:

public static class DBLoggerExtensions
{
    public static ILoggerFactory AddContext(this ILoggerFactory factory,Func<string, LogLevel, bool> filter = null)
    {
        factory.AddProvider(new DBLoggerProvider(filter));
        return factory;
    }

    public static ILoggerFactory AddContext(this ILoggerFactory factory, LogLevel minLevel)
    {
        return AddContext(
            factory,
            (_, logLevel) => logLevel >= minLevel);
    }
}

This will be called in the Configure method of the Startup.cs like this:

loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();
        loggerFactory.AddContext(LogLevel.Information);

The AddContext method add my custom LoggerProvider:

public class DBLoggerProvider : ILoggerProvider
{
    private readonly Func<string, LogLevel, bool> _filter;

    public DBLoggerProvider(Func<string, LogLevel, bool> filter)
    {
        _filter = filter;
    }
    public ILogger CreateLogger(string categoryName)
    {
        return new DBLogger(categoryName, _filter);
    }

    public void Dispose()
    {

    }
}

Which logs into the database:

public class DBLogger : ILogger
{
    private string _categoryName;
    private Func<string, LogLevel, bool> _filter;
    private LoggerContext _context;
    private bool _selfException = false;

    public DBLogger(string categoryName, Func<string, LogLevel, bool> filter)
    {
        _categoryName = categoryName;
        _filter = filter;
        _context = new LoggerContext();
    }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        if (!IsEnabled(logLevel))
        {
            return;
        }
        if (_selfException)
        {
            _selfException = false;
            return;
        }
        _selfException = true;
        if (formatter == null)
        {
            throw new ArgumentNullException(nameof(formatter));
        }
        var message = formatter(state, exception);
        if (string.IsNullOrEmpty(message))
        {
            return;
        }

        if (exception != null)
        {
            message += "\n" + exception.ToString();
        }

        try
        {
            var maxMessageLength = GetMaxMessageLength();
            message = maxMessageLength != null && message.Length > maxMessageLength ? message.Substring(0, (int)maxMessageLength) : message;
            _context.EventLogs.Add(new EventLog { Message = message, EventId = eventId.Id, LogLevel = logLevel.ToString(), CreatedTime = DateTime.UtcNow });
            _context.SaveChanges();
            _selfException = false;
        }
        catch (Exception ex)
        {
            var test = ex;
        }
    }

    public bool IsEnabled(LogLevel logLevel)
    {
        return (_filter == null || _filter(_categoryName, logLevel));
    }

    public IDisposable BeginScope<TState>(TState state)
    {
        return null;
    }

    private int? GetMaxMessageLength()
    {
        int? maxLength = null;
        PropertyInfo[] props = typeof(EventLog).GetProperties();
        foreach (PropertyInfo prop in props)
        {
            object[] attrs = prop.GetCustomAttributes(true);
            foreach (object attr in attrs)
            {
                MaxLengthAttribute maxLengthAttr = attr as MaxLengthAttribute;
                if (maxLengthAttr != null && prop.Name.Equals("Message"))
                {
                    maxLength = maxLengthAttr.Length;
                }
            }
        }

        return maxLength;
    }
}

Here is how the DbContext is defined and implemented:

public class LoggerContext : DbContext
{
    public DbSet<EventLog> EventLogs { get; set; }
    public static string ConnectionString { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseOracle(ConnectionString);
        base.OnConfiguring(optionsBuilder);
    }
}

Implementation in ConfigureServices of Startup.cs:

        LoggerContext.ConnectionString = Configuration.GetConnectionString("DevelopmentDatabase");
        services.AddDbContext<LoggerContext>();

Is the Configure method the best place to accomplish this task? Is the usage of the DbContext the best way to accomplish this task?

\$\endgroup\$
  • \$\begingroup\$ Is this for production code or just a learning exercise? For production code I'd probably use a logging framework, such as NLog. Hmm makes me wonder, does NLog support asp.net core? \$\endgroup\$ – Mathieu Guindon Mar 2 '17 at 17:15
  • 2
    \$\begingroup\$ It supposed to be for production code. How does NLog accomplish this? If possible I would like to stick with Microsoft features and use ad little third party packages as possible \$\endgroup\$ – Palmi Mar 2 '17 at 17:21
  • 2
    \$\begingroup\$ I added the reinventing-the-wheel tag, so that reviewers know not to suggest replacing it all with a 3rd-party package. You can see how NLog works on their GitHub project wiki. \$\endgroup\$ – Mathieu Guindon Mar 2 '17 at 17:24
  • 1
    \$\begingroup\$ In production logging should be as non-blocking as possible. Blocking writes to DB are prohibitively slow. So it should, maybe, write to some logging buffer and get to its business. After that, some another dedicated log collector can do what it wants to do with buffered logs in limited thread space. \$\endgroup\$ – iTollu Mar 3 '17 at 6:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.