So I've got a basic custom logger, and set up as so:
public static IWebHost CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseIISIntegration()
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConsole();
logging.AddCustomLogger().AddFilter<CustomLoggerProvider>(typeof(CustomLogEntry).ToString(), LogLevel.None); // My custom logger
logging.SetMinimumLevel(LogLevel.Trace);
}).Build();
}
My understanding is that the .AddFilter means that only loggers of
typeof(CustomLogEntry)
should actually log out to my custom logger. That doesn't appear to be the case, though!
The extension to add the actual logger is pretty straightforward:
public static ILoggingBuilder AddCustomLogger(this ILoggingBuilder builder)
{
builder.AddConfiguration();
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, CustomLoggerProvider>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CustomLoggerOptions>, CustomLoggerOptionsSetup>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptionsChangeTokenSource<LoggerOptions>, CustomLoggerProviderOptionsChangeTokenSource<CustomLoggerOptions, CustomLoggerProvider>>());
return builder;
}
My custom logger is pretty straightforward as well:
public class CustomLogger : ILogger
{
public CustomLoggerProvider Provider { get; private set; }
public string Category { get; private set; }
public Logger(CustomLoggerProvider provider, string category)
{
Provider = provider;
Category = category;
}
public IDisposable BeginScope<T>(T item)
{
return Provider.ScopeProvider.Push(item);
}
public bool IsEnabled(LogLevel logLevel)
{
return Provider.IsEnabled(logLevel);
}
public void Log<T>(LogLevel logLevel, EventId eventId, T item, Exception exception, Func<T, Exception, string> formatter)
{
if (IsEnabled(logLevel))
{
// Generate the log entry
CustomLogEntry logEntry = new CustomLogEntry();
// removed for brevity...
// Write the log out to the provider!
Provider.WriteLog(logEntry);
}
}
}
And the provider:
[ProviderAlias("Custom")]
public class CustomLoggerProvider : ILoggerProvider, IDisposable, ISupportExternalScope
{
private ConcurrentQueue<CustomLogEntry> _logQueue = new ConcurrentQueue<CustomLogEntry>();
private readonly IDisposable _settingsChangeToken;
private readonly CustomLogger _customLogger;
private CustomLoggerOptions _settings;
private bool _terminated = false;
public IExternalScopeProvider ScopeProvider { get; set; }
public CustomLoggerProvider(IOptionsMonitor<LoggerOptions> settings, IConfiguration configuration) : this(settings.CurrentValue, configuration)
{
// https://docs.microsoft.com/en-us/aspnet/core/fundamentals/change-tokens
_settingsChangeToken = settings.OnChange(options =>
{
_settings = options;
});
}
public CustomLoggerProvider(CustomLoggerOptions settings, IConfiguration configuration)
{
_settings = settings;
_customLogger = new CustomLogger(this, typeof(CustomLogEntry).ToString());
ProcessQueue();
}
public ILogger CreateLogger(string categoryName)
{
return _customLogger;
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel == LogLevel.Information;
}
public void WriteLog(LogEntry logEntry)
{
_logQueue.Enqueue(logEntry);
}
private void ProcessQueue()
{
Task.Run(async () =>
{
while (!_terminated)
{
try
{
// chew up the queue
if (_logQueue.TryDequeue(out CustomLogEntry logEntry))
{
// Do work, removed for brevity
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
});
}
#region IDisposable Support
private bool disposedValue = false; // To detect redundant calls
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects).
_terminated = true;
}
// TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
// TODO: set large fields to null.
disposedValue = true;
}
}
// This code added to correctly implement the disposable pattern.
void IDisposable.Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
}
#endregion
#region ISupportExternalScope Support
public void SetScopeProvider(IExternalScopeProvider scopeProvider)
{
ScopeProvider = scopeProvider;
}
#endregion
}
So after putting this together and testing it out, I'm seeing every log of LogLevel.Trace or higher being logged to the CustomLogger.
What I'm trying to do, is have only logs of a specific category actually log out to the custom provider. Or in other words, filter which log provider to log out to, based on the category.
Is what I'm doing not possible? Am I wildly misunderstanding how .AddFilter actually works? (probably, lol)
I'm suspecting that my CustomLoggerProvider.IsEnabled somehow needs to check that the incoming log category is correct, but I'm just not seeing how to make that happen.
I'd appreciate any pointers.