How to Resolve ILogger in Autofac container - c#

I am trying to replace dryIOC container with autofac container for some reason but I don't know the exact syntax how could I resolve ILogger uisng autofac
Here is how I register ILogger in DryIoc
public void Load(IContainer container)
{
container.Register<ILoggerFactory, Log4NetLoggerFactory>();
ConfigureLogger(container);
}
[Localizable(false)]
private void ConfigureLogger(IContainer container)
{
container.Register<ILogger>(
made: Made.Of(
r => typeof(ILoggerFactory).GetMethod(nameof(ILoggerFactory.Create), new[] { typeof(Type) }),
factoryInfo: ServiceInfo.Of<ILoggerFactory>(),
parameters: Parameters.Of.Name("type", r => r.Parent?.ImplementationType ?? typeof(Startup))),
reuse: Reuse.Transient);
}
I have tried something like this, but it does not work with Autofac
container.Register<ILoggerFactory, Log4NetLoggerFactory>();

If you try to resolve an instance of Microsoft.Extensions.Logging.ILogger then you have two options:
Option #1 - Resolve a ILogger<TCategoryName>.
public class Something
{
private readonly ILogger logger;
public Something(ILogger<Something> logger)
{
this.logger = logger;
}
}
Option #2 - Resolve an ILogger instead of ILogger<TCategoryName> automatically.
My nuget package does exactly this using Autofac:
EgonsoftHU.Extensions.Logging.Autofac
It contains an Autofac module. Register it then magic will happen.
public class Something
{
private readonly ILogger logger;
public Something(ILogger logger)
{
this.logger = logger; // It will be an instance of ILogger<Something>.
}
}

Related

Inject different loggers for controllers in ASP.NET MVC

I have defined services in Startup.cs
public void ConfigureServices(IServiceCollection services)
{
string connectionString = Configuration.GetConnectionString("DbConnection");
services.AddDbContext<AppDbContext>(options => options.UseSqlServer(connectionString));
services.AddControllersWithViews();
services.AddScoped<IAdsService, AdsService>();
services.AddScoped<ILogger, ConsoleLogger>();
services.AddScoped<ILogger, FileLogger>();
services.AddScoped<IAdsRepository, AdsRepository>();
}
This is my implementation of Logger:
public class ConsoleLogger: ILogger
{
public void Log(LogData data) => Console.WriteLine(data.ToString());
}
public class FileLogger : ILogger
{
private string Path = "logs";
public void Log(LogData data)
{
if (!Directory.Exists(Path))
{
DirectoryInfo di = Directory.CreateDirectory(Path);
}
File.AppendAllText(Path + "/logs.txt", data.ToString());
}
}
I want to use different loggers for different controllers:
e.g.
private readonly ILogger _logger;
private readonly IAdsService _adsService;
public AdController(IAdsService adsService, ILogger logger)
{
_adsService = adsService;
_logger = logger;
}
However it only takes FileLogger - how to specify my controller to use ConsoleLogger in Startup?
What you are looking for is a feature called context-based injection, which is something that is not easily implemented with MS.DI. That's not to say it's impossible, but depending on your needs, it might require a lot of configuration.
One way to achieve this, though, it by configuring any component that requires an alternative logger explicitly using a lambda using the ActivatorUtilities class. Here's an example:
private static void AddServices(IServiceCollection services)
{
// The default logger
services.AddScoped<ILogger, FileLogger>();
// Additional 'alternative' loggers
services.AddScoped<ConsoleLogger>();
// Configure a component that requires an alternative logger
services.AddTransient<AdController>(c =>
ActivatorUtilities.CreateInstance<AdController>(c,
c.GetRequiredService<ConsoleLogger>()));
}
In this example:
FileLogger is registered as ILogger allowing any 'normal' component that depends on ILogger to get injected with FileLogger.
ConsoleLogger is registered as itself, allowing it to be requested als alternative logger
AdController is registered using the ActivatorUtilities so that ActivatorUtilities is responsible for creating a new AdController where a resolved ConsoleLogger is supplied to ActivatorUtilities. This allows ActivatorUtilities to supply ConsoleLogger to the first constructor parameter that is assignable from ConsoleLogger. This basically means that ConsoleLogger is supplied to the ILogger argument of AdController.
To test this code, try this:
public interface IAdsService { }
public class AdsService : IAdsService { }
public interface ILogger { }
public class ConsoleLogger : ILogger { }
public class FileLogger : ILogger { }
public class AdController
{
public AdController(IAdsService adsService, ILogger logger) => this.Logger = logger;
public ILogger Logger { get; }
}
class Program
{
static void Main(string[] args)
{
var services = new ServiceCollection();
services.AddTransient<IAdsService, AdsService>();
AddServices(services);
var provider = services.BuildServiceProvider();
using (var scope = provider.CreateScope())
{
var controller =
scope.ServiceProvider.GetRequiredService<AdController>();
Console.WriteLine(controller.Logger.GetType().Name);
}
}
}
There are several downsides to this approach, the most important being that this solution might not scale well. There is no good way to take a more convention-based approach where you say "use ConsoleLogger for any component that follows the following definition X". You must specify each component that uses an alternative logger explicitly.
If these limitations cause maintainability issues, try using a different DI Container that natively supports this feature.

Inject Serilog's ILogger interface in ASP .NET Core Web API Controller

All the examples I can find about using Serilog in an ASP .NET Core Web Application use Microsoft's ILogger<T> interface instead of using Serilog's ILogger interface.
How do I make it so that Serilog's ILogger can be injected via constructor, instead?
using Serilog;
public class HomeController : Controller
{
ILogger logger;
public HomeController(ILogger logger)
{
this.logger = logger;
}
public IActionResult Index()
{
this.logger.Information("Index was called");
return View();
}
}
If you prefer ILogger instead of ILogger<HomeController>, you could try to register ILogger.
Here are two options to use Serialog.Information.
Use Log.Logger
Log.Logger.Information("Information Log from Log.Logger");
Register ILogger
//Startup.cs
services.AddSingleton(Log.Logger);
//Use
public class HomeController : Controller
{
private readonly ILogger _logger;
public HomeController(ILogger logger)
{
_logger = logger;
}
public IActionResult Index()
{
_logger.Information("Inform ILog from ILogger");
return View();
}
}
You can install Serilog as the logger under the Microsoft logging framework by including the Serilog.Extensions.Logging package and including the following in your app startup:-
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(x =>
{
x.ClearProviders();
x.AddSerilog(dispose: true);
});
...
Or, as an alternative to injecting, if you just want a reference to the Serilog logger, Serilog.Log has a static method Log to create a logger...
...
using Serilog;
...
namespace Test.Controllers
{
public class TestController : Controller
{
private readonly static ILogger log = Log.ForContext(typeof(TestController));
public TestController()
{
log.Debug("Test");
}

Pass configuration to IHostedService

I'm trying to add a configuration value to an IHostedService but do not know how.
This is what I have at the moment:
Startup
services.Configure<MyOptions>(Configuration.GetSection("MyOptions"));
services.AddHostedService<MyHostedService>();
MyOptions
public class MyOptions
{
public string MyOption{ get; set; }
}
Appsettings
"MyOptions": {
"MyOption": "42"
}
MyHostedService
public MyHostedService(ILogger<MyHostedService> logger)
{
_logger = logger;
// Where are my options?! :(
}
You are almost there. You got only one thing left to do:
Just use constructor dependency injection with IOptions<MyOptions> or related (depending on your scenario) in your IHostedService:
IOptions<TOptions>
IOptionsMonitor<TOptions>
IOptionsSnapshot<TOptions>
public MyHostedService(ILogger<MyHostedService> logger, IOptions<MyOptions> optionsAccessor)
{
_logger = logger;
_options = optionsAccessor.Value;
}
For more details, see Options pattern in ASP.NET Core.

How to get type of dependency owner when using Windsor Castle container?

Consider we have multiple services, each of them uses ILoggerFactory interface creating own instance of logger next way:
public class Foo
{
private readonly ILogger _logger;
public Foo(ILoggerFactory logger)
{
_logger = loggerFactory.CreateForContext(this.GetType());
}
}
Also consider that we use Windsor Castle for dependency injection, so we have a registration like that:
container.Register(Component
.For<ILoggerFactory>()
.LifestyleSingleton()
.Instance(new LoggerFactory());
Of course, we dont want to repeat CreateForContext() in every class.
So, I'm trying to write something like that:
container.Register(Component
.For<ILogger>()
.LifestyleTransient()
.UsingFactoryMethod((kernel,model,context) =>
kernel.Resolve<ILoggerFactory>().CreateForContext(????));
+
public class Foo
{
private readonly ILogger _logger;
public Foo(ILogger logger)
{
_logger = logger;
}
}
But there is a problem - I can't find how to get type of dependency owner.
Is there any way to get it or another way to do same things?
Update
I implemented ISubDependencyResolver, but it doesn't work with late bound components. So, if you use a lot registrations via UsingFactoryMethod for example, it isn't a great solution.

DI ILogger with Serilog on Webapi 2 running Asp.Net Core 2

I am trying to integrate Serilog to my WebApi 2 with Asp.Net Core 2 (With MSSQLServer integration).
I have added the nuget packages and then added the following to my
public async void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
var log = new LoggerConfiguration()
.WriteTo.MSSqlServer(DbGlobals.DevDatabase, "Logs")
.CreateLogger();
loggerFactory.AddSerilog(log);
The in my Program.cs I have the default;
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
From reading the MSDN docs .CreateDefaultBuilder(args) should include logging?
However, when I then DI the ILogger to my controller;
[Route("api/[controller]")]
public class TraderController : Controller
{
private TraderService _service;
private readonly ILogger _logger;
public TraderController(Func<IBemfeitoDataContext> context, ILogger logger)
{
_service = new TraderService(context, logger);
_logger = logger;
}
I am getting the following error;
InvalidOperationException: Unable to resolve service for type 'Microsoft.Extensions.Logging.ILogger' while attempting to activate 'WebApi.Controllers.TraderContactController'.
Do I need to physically setup a singleton for ILogger in the Startup.cs. Can anyone tell me what I am missing here? As I assumed because logging is build into the Core pipeline this would be something it knew how to resolve?
You need to refactor the controller to expect the generic logger ILogger<T>, which is derived from ILogger
[Route("api/[controller]")]
public class TraderController : Controller {
private TraderService _service;
private readonly ILogger _logger;
public TraderController(
Func<IBemfeitoDataContext> context,
ILogger<TraderController> logger //<-- note the change here
) {
_service = new TraderService(context, logger);
_logger = logger;
}
}
ILogger<> is mapped to Logger<> when .AddLogging() is called within CreateDefaultBuilder. ILogger is not registered directly with the service collection.
With that done, the current design choice of coupling the controller to implementation concerns (i.e TraderService) was a cause for some concern.
assuming based on how the current code is being used
public class TraderService : ITraderService {
private readonly ILogger logger;
public TraderService(Func<IBemfeitoDataContext> context, ILogger logger) {
this.logger = logger;
//...
}
//...code removed for brevity
}
the service should also be fixed to depend on ILogger<T>
public class TraderService : ITraderService {
private readonly ILogger logger;
public TraderService(Func<IBemfeitoDataContext> context, ILogger<TraderService> logger) {
this.logger = logger;
//...
}
//...code removed for brevity
}
and the controller should be refactored to depend on the service abstraction and not the implementation.
[Route("api/[controller]")]
public class TraderController : Controller {
private readonly ITraderService service;
private readonly ILogger logger;
public TraderController(ITraderService service, ILogger<TraderController> logger) {
this.service = service;
this.logger = logger;
}
//...
}
All classes involved will get their dependencies explicitly injected when resolved.
This answer assumes that ITraderService and TraderService are registered with the DI container.
Both ILoggerFactory and generic ILogger<T> are injected. That means you could have your controller like this:
[Route("api/[controller]")]
public class TraderController : Controller {
private readonly ITraderService service;
private readonly ILogger logger;
public TraderController(ITraderService service, ILoggerFactory loggerFactory)
{
this.service = service;
this.logger = loggerFactory.CreateLogger<TraderController>();
}
//...
}

Categories

Resources