I have a console application, and I added logger builder as follows in Program.cs:
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddConsole();
});
ILogger logger = loggerFactory.CreateLogger<Program>();
Now say I have many classes in the project and all want to use logger. How can I make it available to other classes?
For example, I have:
internal class Test
{
public Test()
{
//use logger here
//logger.LogInformation("Calling Test");
}
}
In certain kinds of projects such as Azure Functions, the logger is readily injected in the functions, is there a similar way I can do it in Console application?
To inject stuff, classes need constructors that accept dependencies as their parameters. For instance:
public class Test
{
private ILogger logger;
public Test(ILogger<Test> logger)
{
this.logger = logger;
}
public void DoSomething()
{
//use logger here
logger.LogInformation("Calling Test");
}
}
Related
I need to create a simple .NET Core 5 Console App that uses IOC(Autofac), ILogging(Serilog) and a Message Queue(MQRabbit). I have done this before(exception Microsoft IOC instead of Autofac) in Console Apps that run microservices, this time I however need just a basic console app without hosting a service. How is this done?
I have this code from my microservice :
public static void Main(string[] args) {
Log.Logger = new LoggerConfiguration().DefaultLoggerSetup<Program>();
var serviceName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
var configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var appSettings = configurationBuilder.Get<AppSettings>();
Log.Information("{#serviceName} microservice starting up.", serviceName);
Host.CreateDefaultBuilder(args)
.UseMQ(context => context.UseSettings(appSettings.MQSettings))
.UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration.ReadFrom.Configuration(hostingContext.Configuration))
.ConfigureServices((hostContext, services) =>
{
services
.AddHostedService<MyService>()
.Configure<MQSettings>(configurationBuilder.GetSection("MQSettings"))
.AddTransient<IController, Controller>()
...
})
.Build().Run();
Log.Information("{#serviceName} microservice closing down.", serviceName);
}
But I dont need to host anything in this case, it should be a just basic console app with logging, injection and some I/O on a MQ.
Edit :
This is as far as I manage to get :
public class Program
{
static void Main(string[] args)
{
var serviceName = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
var configurationBuilder = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var appSettings = configurationBuilder.Get<AppSettings>();
CompositionRoot(configurationBuilder).Resolve<ITestController>().PlaceTrestment1OnMQTest();
}
private static IContainer CompositionRoot(IConfigurationRoot configurationRoot)
{
var serilog = new LoggerConfiguration().ReadFrom.Configuration(configurationRoot).CreateLogger();
var loggerFactory = new LoggerFactory().AddSerilog(serilog);
Microsoft.Extensions.Logging.ILogger logger = loggerFactory.CreateLogger("Logger");
var builder = new ContainerBuilder();
builder.Register<Microsoft.Extensions.Logging.ILogger>((c, p) =>
{ return logger; });
//builder.Register<Microsoft.Extensions.Logging.ILogger>((c, p) =>
//{ return new LoggerConfiguration().ReadFrom.Configuration(configurationRoot).CreateLogger(); });
builder.RegisterType<TestController>().As<ITestController>();
return builder.Build();
}
}
}
public class TestController : ITestController
{
private ILogger<TestController> _logger;
public TestController(ILogger<TestController> logger)
{
_logger = logger;
}
public void PlaceTrestment1OnMQTest()
{
}
}
I get the exception when running it :
Cannot resolve parameter 'Microsoft.Extensions.Logging.ILogger1[ConnectorMockTest.BusinessLogicLayer.TestController] logger' of constructor 'Void .ctor(Microsoft.Extensions.Logging.ILogger1[ConnectorMockTest.BusinessLogicLayer.TestController])'.
Registering non-generic ILogger does not automatically register generic ILogger<T> and this is what you are trying to do.
Inject ILogger into TestController constructor instead ILogger and it should work properly, assuming the rest of the code is valid.
I was under the impression that a Service host was mainly used when hosting webservice/webpage or likewise. There is however really nothing wrong to create a service in a "closed" console application that serves the application with Dependency Injection, Configuration and startup routines. Even if the application is just a fire and forget app.
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.
I am working on Azure WebJobs (3.0.6) using dotnet core. I referred Microsoft's Get Started Guide. Per the example I wanted to have a console logging to begin with. The scope of that guide is limited. In my application, I will be using many classes in a different dll. I am not able to figure out how can I add logging in those classes. The sample code is
// The main method
static async Task Main()
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
var host = builder.Build();
using (host)
{
await host.RunAsync();
}
}
// The queue trigger class
public class QueueListenerService
{
public static void QueueListener([QueueTrigger("myqueue")] string message, ILogger logger)
{
logger.LogInformation("The logger works here");
// how can I pass an instance of ILogger in the constructor below
MyWorker myWorker = new MyWorker();
}
}
// MyWorker Class in a different assembly
public class MyWorker
{
public MyWorker(ILogger logger)
{
// I want to use logger here being injected
}
}
I have referred several examples of DI in dotnet core console applications and they use service collection approach. I also check this blog but to me, this is what I have done and yet my ILogger is not being resolved. It ask me to pass an instance of ILogger when I create MyWorker instance
You are close to the solution. The main thing you need to change is to let the service collection create the MyWorker instance for you.
I quickly extended my recent Webjob sample project to include console logging with dependency injection. See this commit for how I added it.
You mainly need to use constructor dependency injection for your QueueListenerService.
builder.ConfigureServices(services =>
{
services.AddScoped<QueueListenerService>();
services.AddScoped<MyWorker>();
});
public class QueueListenerService
{
public QueueListenerService(MyWorker worker){
_Worker = worker;
}
public static void QueueListener([QueueTrigger("myqueue")] string message, ILogger logger)
{
logger.LogInformation("The logger works here");
_Worker.DoStuff()
}
}
I'm working with a project which utilizes Simple Injector as dependency injector. On the other hand, this project uses Microsoft.Extensions.Logging in order to log the events that occurs in certain classes.
My technical issue is pretty simple to explain.
I want to register in my DI the ILogger independently of the class T which is being invoked, but I DO NEED to do it from my ILoggerFactory.CreateLogger<T>() method because this gets the logger configuration using Microsoft.Extensions.Configuration.
I need to use something like this in order to instance my logger:
private Microsoft.Extensions.Logging.ILogger CreateLogger<T>()
{
var factory = this.ResolveService<ILoggerFactory>();
var logger = factory.CreateLogger<T>();
return logger;
}
I could achieve the injection by doing:
Container.Register(typeof(ILogger<>), typeof(Logger<>));
And this allows us to resolve something like:
public class SomeApiController : ApiController
{
public SomeApiController(ILogger<SomeApiController> logger)
{
//logger is well instantiated, but doesn't got the configuration
logger.LogInformation("test log.");
}
}
But as I said, this does it without passing through the configuration obtained from the Microsoft.Extensions.Logging.ILoggerFactory class, so this isn't useful.
Is there a way to register ILogger<T> by using my CreateLogger<T>?
Use the following registrations:
container.RegisterInstance<ILoggerFactory>(loggerFactory);
container.RegisterSingleton(typeof(ILogger<>), typeof(Logger<>));
Or, in case you are integrating Simple Injector into a generic host or ASP.NET Core application, make use of the .AddLogging() extension method to even inject a non-generic ILogger into your application components, as demonstrates in this ASP.NET Core Startup class:
public class Startup
{
...
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging(); // Adds logging to the framework
// AddSimpleInjector enables "cross wiring," which means you can let
// Simple Injector-resolved components to depend on the generic
// ILogger<T> abstraction.
services.AddSimpleInjector(container, options =>
{
options.AddAspNetCore();
// AddLogger allows Simple Injector-resolved components to depend on
// the non-generic Microsoft.Extensions.Logging.ILogger interface.
// Simple Injector will automatically inject the correct ILogger<T>
// for you.
options.AddLogging();
});
}
...
}
For a full example, see the ASP.NET Core and ASP.NET Core MVC Integration Guide.
Letting application components depend on ILogger instead of ILogger<T>, makes your code simpler, easier to test, and less error prone. If you're using Simple Injector without Service Collection integration (as the previous example showed, you can use the following registration to let Simple Injector ensure the correct Logger<T> is still injected whenever an ILogger is injected:
container.RegisterConditional(
typeof(ILogger),
c => typeof(Logger<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
_ => true);
This ensures that every application component gets its own Logger<T> instance, where T is the type of the component the logger is injected into. Take the following class for example that depends on ILogger:
public class ComponentA : IService
{
public ComponentA(ILogger logger) { ... }
}
The above registration will ensure that ComponentA is injected with a Logger<ComponentA>, even though it simply depends on ILogger and not on ILogger<T>.
You can stop reading here if the above suits your needs... or continue reading if you're interested in a more SOLID solution.
A SOLID solution
Instead of letting application components depend on the framework-defined ILogger abstraction, you could also choose to define an application-specific logger abstraction, as prescribed by the Dependency Inversion Principle (DIP).
The DIP states that abstractions should be defined by the application itself—this means you define your own logger abstraction (also see this for an explanation of why you want to do this) and on top of that you build an adapter, much like described here. You can simply derive your generic adapter from the described MicrosoftLoggingAdapter as follows:
public sealed class MicrosoftLoggingAdapter<T> : MicrosoftLoggingAdapter
{
public MicrosoftLoggingAdapter(ILoggerFactory factory)
: base(factory.CreateLogger<T>()) { }
}
Using this generic adapter, you can configure Simple Injector as follows:
container.RegisterInstance<ILoggerFactory>(factory);
container.RegisterConditional(
typeof(MyApplication.Abstractions.ILogger),
c => typeof(MicrosoftLoggingAdapter<>).MakeGenericType(c.Consumer.ImplementationType),
Lifestyle.Singleton,
_ => true);
Based on Steven's solution, I post my answer to help anyone else:
private void RegisterServices()
{
Container.Register(ConfigureLogger, Lifestyle.Singleton);
Container.Register(typeof(ILogger<>), typeof(LoggingAdapter<>));
}
private ILoggerFactory ConfigureLogger()
{
LoggerFactory factory = new LoggerFactory();
var config = new ConfigurationBuilder()
.AddJsonFile("logging.json")
.Build();
//serilog provider configuration
var log = new LoggerConfiguration()
//.ReadFrom.Configuration(config)
.WriteTo
.RollingFile(ConfigSettings.LogsPath)
.CreateLogger();
factory.AddSerilog(log);
return factory;
}
public class LoggingAdapter<T> : ILogger<T>
{
private readonly Microsoft.Extensions.Logging.ILogger adaptee;
public LoggingAdapter(ILoggerFactory factory)
{
adaptee = factory.CreateLogger<T>();
}
public IDisposable BeginScope<TState>(TState state)
{
return adaptee.BeginScope(state);
}
public bool IsEnabled(LogLevel logLevel)
{
return adaptee.IsEnabled(logLevel);
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
adaptee.Log(logLevel, eventId, state, exception, formatter);
}
}
As you can see, my solution is using Serilog as a provider for logging in Microsoft.Extensions.Logging.
Hope it helps!
In asp.net core 1.1 I could inject the IServiceProvider into the logger provider and resolve my logger when CreateLogger was called, but it all changed in asp.net core 2.0
My ILogger implementation needs dependencies injected.
How can I achieve this?
ASP.NET core provides possibility to replace built-in DI container with custom one (see this article for details). You could use this possibility to obtain instance of IServiceProvider earlier for logging bootstrapping while still using standard .Net core DI container.
To do this you should change return value of Startup.ConfigureServices(IServiceCollection services) method from void to IServiceProvider. You can use this possibility to build instance of IServiceProvider in ConfigureServices, use it for logging bootstrapping and then return from the method.
Sample code:
public interface ISomeDependency
{
}
public class SomeDependency : ISomeDependency
{
}
public class CustomLogger : ILogger
{
public CustomLogger(ISomeDependency dependency)
{
}
// ...
}
public class CustomLoggerProvider : ILoggerProvider
{
private readonly IServiceProvider serviceProvider;
public CustomLoggerProvider(IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public ILogger CreateLogger(string categoryName)
{
return serviceProvider.GetRequiredService<ILogger>();
}
// ...
}
public class Startup
{
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
return ConfigureLogging(services);
}
private IServiceProvider ConfigureLogging(IServiceCollection services)
{
services.AddTransient<ISomeDependency, SomeDependency>();
services.AddSingleton<ILogger, CustomLogger>();
IServiceProvider serviceProvider = services.BuildServiceProvider();
var loggerFactory = new LoggerFactory();
loggerFactory.AddProvider(new CustomLoggerProvider(serviceProvider));
return serviceProvider;
}
// ...
}
Starting of with that dependency thing you need in various places
public class SomeDependency : ISomeDependency
{
}
An extension file so we can configure logging on the ServiceCollection as per MSDN
Pretty standard stuff you can find on various sources
public static class ApplicationLoggerFactoryExtensions
{
public static ILoggingBuilder CustomLogger(this ILoggingBuilder builder)
{
builder.Services.AddSingleton<ILoggerProvider, CustomLoggerProvider>();
//Be careful here. Singleton may not be OK for multi tenant applications - You can try and use Transient instead.
return builder;
}
}
The logger provider is the part that gets called AFTER services are built when you are working in your business code and need to log stuff.
So in the context of application the DI is built and available here. And it probably makes sense now why ILoggerProvider exists now.
public class CustomLoggerProvider : ILoggerProvider
{
private ISomeDependency someDependency;
public CustomLoggerProvider(ISomeDependency someDependency)
{
this.someDependency = someDependency;
}
public ILogger CreateLogger(string categoryName)
{
return new CustomeLogger(someDependency);
}
}
The concrete custom logger pretty simple stuff
public class CustomLogger : ILogger
{
public CustomLogger(ISomeDependency dependency)
{
}
}
And in the place where you are configuring your ServiceCollection.. as in the OP's question in Startup.cs
private void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ISomeDependency, SomeDependency>();
//services.AddSingleton<ILogger, CustomLogger>(); <== NO
var loggerFactory = new LoggerFactory(); //??? newer DotNet gives you LoggerFactory in startup this may be unnecessary.
//Add some console printer
services.AddLogging(configure => configure.AddConsole())
.Configure<LoggerFilterOptions>(options => options.MinLevel = LogLevel.Trace);
//Add our custom logger
services.AddLogging(configure => configure.CustomLogger()); // <== our extension helping out!
}
So just a note for usage of ILogger
✘ DO NOT - Do not add any ILogger to your services
The whole point of LoggerFactory and LoggerProvider configuration is to simplify using ILogger
public MyBusinessService(ILogger<BusinessServiceClass> log)
{
log.Information("Please tell all registered loggers I am logging!);
}
In my example it will print out message to console if available and the CustomLogger that took a Dependency we injected. If you register more.. it will go to all of them
If you are configuring logging in program.cs you can create a function to configure logging and get an instance of logging provider like this:
private static void ConfigureApplicationLogging(WebHostBuilderContext context, ILoggingBuilder loggingBuilder)
{
loggingBuilder.AddConfiguration(context.Configuration.GetSection("Logging"));
loggingBuilder.AddDebug();
loggingBuilder.AddConsole();
var serviceProvider = loggingBuilder.Services.BuildServiceProvider();
loggingBuilder.AddProvider(new DoxErrorLoggerProvider(serviceProvider, null));
}
Then in BuildWebHost you will configure logging as follows:
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureLogging(ConfigureApplicationLogging)
.UseNLog()
.UseStartup<Startup>()
.Build();