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?