I would like to better identify the processes making calls to the logger. My old .Net framework implementation allowed me to create a logger per class (or pass in an existing logger to ride that existing one) via log4net. That option is no longer viable for us.
I have created a customer logger implementation for this process.
#static main inline code:
ILogger<LoggerTesting.LoggerTest> logger = loggerFactory.CreateLogger<LoggerTesting.LoggerTest>();
LoggerTesting.LoggerTest lt = new LoggerTesting.LoggerTest(logger);
await lt.GenerateLogEntry("Test");
internal class LoggerTest
{
ILogger _logger;
public LoggerTest(ILogger<LoggerTest> logger)
{
_logger = logger;
}
public async Task GenerateLogEntry(string message)
{
_logger.LogInformation(message);
_logger.LogWarning(message);
_logger.LogError(message);
_logger.LogCritical(message);
_logger.LogDebug(message);
}
}
UPDATE to reflect Mushrooms recommendation. The sample code I used didn't do anything with the original categoryName. Now I'm getting what's expected.
public class DbLogger : ILogger
{
/// <summary>
/// Instance of <see cref="DbLoggerProvider" />.
/// </summary>
private readonly DbLoggerProvider _dbLoggerProvider;
private readonly string _appName;
/// <summary>
/// Creates a new instance of <see cref="FileLogger" />.
/// </summary>
/// <param name="fileLoggerProvider">Instance of <see cref="FileLoggerProvider" />.</param>
public DbLogger(DbLoggerProvider dbLoggerProvider, string appName)
{
_dbLoggerProvider = dbLoggerProvider;
_appName = appName ?? "Default";
System.Diagnostics.Debug.WriteLine($"Created instance of {appName}");
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
/// <summary>
/// Whether to log the entry.
/// </summary>
/// <param name="logLevel"></param>
/// <returns></returns>
public bool IsEnabled(LogLevel logLevel)
{
return logLevel != LogLevel.None;
}
/// <summary>
/// Used to log the entry.
/// </summary>
/// <typeparam name="TState"></typeparam>
/// <param name="logLevel">An instance of <see cref="LogLevel"/>.</param>
/// <param name="eventId">The event's ID. An instance of <see cref="EventId"/>.</param>
/// <param name="state">The event's state.</param>
/// <param name="exception">The event's exception. An instance of <see cref="Exception" /></param>
/// <param name="formatter">A delegate that formats </param>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (!IsEnabled(logLevel))
{
// Don't log the entry if it's not enabled.
return;
}
var threadId = Thread.CurrentThread.ManagedThreadId; // Get the current thread ID to use in the log file.
// Store record.
using (var connection = new SqlConnection(_dbLoggerProvider.Options.ConnectionString))
{
connection.Open();
using (var command = new SqlCommand())
{
command.Connection = connection;
command.CommandType = System.Data.CommandType.Text;
command.CommandText = $@"INSERT INTO {_dbLoggerProvider.Options.LogTable}
(
Application
,LogLevel
,ThreadId
,EventId
,EventName
,Message
,ExceptionMessage
,ExceptionStackTrace
,ExceptionSource
) VALUES(
@Application
,@LogLevel
,@ThreadId
,@EventId
,@EventName
,@Message
,@ExceptionMessage
,@ExceptionStackTrace
,@ExceptionSource
)";
var values = new JObject();
command.Parameters.Add(new SqlParameter("@Application", _appName));
command.Parameters.Add(new SqlParameter("@LogLevel", logLevel.ToString()));
command.Parameters.Add(new SqlParameter("@ThreadId", threadId));
command.Parameters.Add(new SqlParameter("@EventId", eventId.Id));
command.Parameters.Add(new SqlParameter("@EventName", !string.IsNullOrWhiteSpace(eventId.Name) ? eventId.Name : (object)DBNull.Value));
command.Parameters.Add(new SqlParameter("@Message", !string.IsNullOrWhiteSpace(formatter(state, exception)) ? formatter(state, exception) : (object)DBNull.Value));
if (exception != null)
{
command.Parameters.Add(new SqlParameter("@ExceptionMessage", !string.IsNullOrWhiteSpace(exception.Message) ? exception?.Message : (object)DBNull.Value));
command.Parameters.Add(new SqlParameter("@ExceptionStackTrace", !string.IsNullOrWhiteSpace(exception.StackTrace) ? exception?.StackTrace : (object)DBNull.Value));
command.Parameters.Add(new SqlParameter("@ExceptionSource", !string.IsNullOrWhiteSpace(exception.Source) ? exception?.Source : (object)DBNull.Value));
}
else
{
command.Parameters.Add(new SqlParameter("@ExceptionMessage", (object)DBNull.Value));
command.Parameters.Add(new SqlParameter("@ExceptionStackTrace", (object)DBNull.Value));
command.Parameters.Add(new SqlParameter("@ExceptionSource", (object)DBNull.Value));
}
command.ExecuteNonQuery();
}
connection.Close();
}
}
}
[ProviderAlias("Database")]
public class DbLoggerProvider : ILoggerProvider
{
public readonly DbLoggerOptions Options;
public DbLoggerProvider(IOptions<DbLoggerOptions> _options)
{
Options = _options.Value; // Stores all the options.
}
/// <summary>
/// Creates a new instance of the db logger.
/// </summary>
/// <param name="categoryName"></param>
/// <returns></returns>
public ILogger CreateLogger(string categoryName)
{
return new DbLogger(this, categoryName);
}
public void Dispose()
{
}
}
I would think that somewhere I could see LoggerTesting.LoggerTest somewhere in the logger application since this is the logger type created for this purpose but that doesn't seem to be the case.
Is there a specific way to retrieve the derived type from within my custom implementation of?
The goal is to replace ApplicationNameHere in the code with the LoggerTesting.LoggerTest value.
Please note I'm just experimenting with this code. It's very simple and crude, so there is no optimization or validation in place.
You also need to implement ILoggerProvider
which has the method CreateLogger(string categoryName): ILogger
.
The categoryName
is the name of the class which the logger should be created for, so exactly what you want. Now, you just have to return a new instance of your ILogger
implementation and pass it the categoryName
as a parameter.
public class DbLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new DbLogger(categoryName); // pass the category name here
}
public void Dispose()
{
}
}
In your ILogger
implementation you can store this as a readonly
field and use within your ILogger
implementation when needed.
public class DbLogger : ILogger
{
private readonly string _categoryName;
public DbLogger(string categoryName)
{
_categoryName = categoryName;
}
// Omitted: Some implementations of other necessary methods defined in ILogger...
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
// use the _categoryName here now
Console.WriteLine($"Logged with category {_categoryName}");
}
You then need to also register your logging provider with DI using
builder.Services.AddSingleton<ILoggerProvider, DbLoggerProvider>();