I configured my project to use Serilog for logging using dependecy injection. I use the following schema in the classes constructor:
namespace FlickPopper.API.Controllers {
public class ValuesController : Controller {
private ILogger<ValuesController> _logger;
public MyClass(ILogger<ValuesController> logger) {
_logger = logger;
}
}
}
In this way, serilog creates the logger calling to Log.ForContext<classname>
My settings are:
"Serilog": {
"WriteTo": [
{
"Name": "RollingFile",
"Args": {
"pathFormat": "Logs\\log-{Date}.txt",
"outputTemplate": "{Timestamp:yyyy-MM-dd HH:mm:ss} [{Level:u3}] [{SourceContext}] {Message}{NewLine}{Exception}"
}
}
],
"Enrich": [ "FromLogContext" ]
}
So, the logs seems like this:
2018-01-26 22:20:08 [INF] [FlickPopper.API.Controllers.ValuesController] Get all values
It's any way to format the SourceContext property to show only the assembly name in the logs, somthing like this?
2018-01-26 22:20:08 [INF] [FlickPopper.API] Get all values
When you use injected ILogger<T>
, under the hood Serilog logger is created by SerilogLoggerProvider that implements ILoggerProvider
interface. This interface has the only method:
public interface ILoggerProvider : IDisposable
{
ILogger CreateLogger(string categoryName);
}
The passed categoryName
is used as value for {SourceContext}
property in message format. And ASP.NET Core passes it as fully-qualified name (e.g. FlickPopper.API.Controllers.ValuesController
).
So this string value should be fixed not in Serilog code or configuration, but in ASP.NET Core logging infrastructure.
The responsible class for creation of that value in first place is Logger<T>
class. Instances of Logger<T>
are instantiated when you inject ILogger<T>
into your classes. Here is source code of its constructor:
public class Logger<T> : ILogger<T>
{
public Logger(ILoggerFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
_logger = factory.CreateLogger(TypeNameHelper.GetTypeDisplayName(typeof(T)));
}
// ...
}
That call to TypeNameHelper.GetTypeDisplayName(typeof(T))
returns fully-qualified name that then is passed to ILoggerFactory
and eventually to SerilogLoggerProvider
.
So if you want to change that behavior and adjust the categoryName
passed to ILoggerFactory
, you should have your own implementation of ILogger<T>
that makes required call to ILoggerFactory.CreateLogger()
. It's not that difficult, because Logger<T>
class is very thin and is based on Microsoft.Extensions.Logging.Logger
implementation. Here is the class that is a copy of Logger<T>
except for one line producing logger category name:
public class LoggerEx<T> : ILogger<T>
{
private readonly ILogger _logger;
public LoggerEx(ILoggerFactory factory)
{
if (factory == null)
{
throw new ArgumentNullException(nameof(factory));
}
_logger = factory.CreateLogger(typeof(T).Assembly.GetName().Name);
}
IDisposable ILogger.BeginScope<TState>(TState state)
{
return _logger.BeginScope(state);
}
bool ILogger.IsEnabled(LogLevel logLevel)
{
return _logger.IsEnabled(logLevel);
}
void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
_logger.Log(logLevel, eventId, state, exception, formatter);
}
}
You should also replace the standard implementation of ILogger<T>
with this one in your services registration:
services.AddSingleton(typeof(ILogger<>), typeof(LoggerEx<>));
Now the instances of LoggerEx<T>
will be injected to the controllers and {SourceContext}
will have a value you built:
2018-01-27 09:54:21 [INF] [TestProject.TestApplication] Hello!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With