Related
I am using the new top-level statements in .NET 6 to create a simple console application, but I don't understand the advantages/disadvantages of using the "Generic Host".
Can you explain?
My code with Generic Host:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Console.WriteLine("Hello, World!");
using var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((_, services) =>
{
services.AddTransient<ITestInterface, TestClass>();
})
.Build();
Test();
Console.ReadKey();
void Test()
{
var testClass = host.Services.GetRequiredService<ITestInterface>();
testClass.TestMethod();
}
versus
using Microsoft.Extensions.DependencyInjection;
Console.WriteLine("Hello, World!");
var services = new ServiceCollection();
services.AddTransient<ITestInterface, TestClass>();
var servicesProvider = services.BuildServiceProvider();
Test();
Console.ReadKey();
void Test()
{
var testClass = servicesProvider.GetRequiredService<ITestInterface>();
testClass.TestMethod();
}
The benefits of using the generic host is that by default a lot of services are already setup for you, see the docs.
The CreateDefaultBuilder method:
Sets the content root to the path returned by GetCurrentDirectory().
Loads host configuration from:
Environment variables prefixed with DOTNET_.
Command-line arguments.
Loads app configuration from:
appsettings.json.
appsettings.{Environment}.json.
Secret Manager when the app runs in the Development environment.
Environment variables.
Command-line arguments.
Adds the following logging providers:
Console
Debug
EventSource
EventLog (only when running on Windows)
Enables scope validation and dependency validation when the environment is Development.
The ConfigureServices method exposes the ability to add services to the Microsoft.Extensions.DependencyInjection.IServiceCollection instance. Later, these services can be made available from dependency injection.
You are not using the generic host correctly. For instance: normally one would add a hosted service so you can use proper DI instead of resolving the required services manually.
An example can be found at the docs
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
If we extend this example with an implementation of Worker that takes in a dependency it will look like this:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddTransient<ITestInterface, TestClass>();
services.AddHostedService<Worker>();
});
}
internal class Worker : IHostedService
{
public Worker(ITestInterface testClass)
{
testClass.Foo();
}
public Task StartAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
}
public interface ITestInterface
{
void Foo();
}
public class TestClass : ITestInterface
{
public void Foo()
{
}
}
Now you see a new instance of Worker is created and an instance of ITestInterface is injected. There is no need to call servicesProvider.GetRequiredService<ITestInterface>(); which is an anti-pattern.
Decision Tree
If you don't need all those additional services you can choose not to use the Generic Host like in your second code example in the question.
If you do want to make use of services like logging, app configuration etc. you should use the Generic Host.
In order to debug a .NET Core app which is failing on startup, I would like to write logs from within the startup.cs file. I have logging setup within the file that can be used in the rest of the app outside the startup.cs file, but not sure how to write logs from within the startup.cs file itself.
.Net Core 3.1
Unfortunately, for ASP.NET Core 3.0, the situation is again a bit different. The default templates use the HostBuilder (instead of the WebHostBuilder) which sets up a new generic host that can host several different applications, not limited to web applications. Part of this new host is also the removal of the second dependency injection container that previously existed for the web host. This ultimately means that you won’t be able to inject any dependencies apart from the IConfiguration into the Startup class. So you won’t be able to log during the ConfigureServices method. You can, however, inject the logger into the Configure method and log there:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
{
logger.LogInformation("Configure called");
// …
}
If you absolutely need to log within ConfigureServices, then you can continue to use the WebHostBuilder which will create the legacy WebHost that can inject the logger into the Startup class. Note that it’s likely that the web host will be removed at some point in the future. So you should try to find a solution that works for you without having to log within ConfigureServices.
.NET Core 2.x
This has changed significantly with the release of ASP.NET Core 2.0. In ASP.NET Core 2.x, logging is created at the host builder. This means that logging is available through DI by default and can be injected into the Startup class:
public class Startup
{
private readonly ILogger<Startup> _logger;
public IConfiguration Configuration { get; }
public Startup(ILogger<Startup> logger, IConfiguration configuration)
{
_logger = logger;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
_logger.LogInformation("ConfigureServices called");
// …
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
_logger.LogInformation("Configure called");
// …
}
}
Option 1: Directly use log (e.g. Serilog) in startup-
public class Startup
{
public Startup(IHostingEnvironment env)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Serilog-{Date}.txt"))
.CreateLogger();
Log.Information("Inside Startup ctor");
....
}
public void ConfigureServices(IServiceCollection services)
{
Log.Information("ConfigureServices");
....
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
Log.Information("Configure");
....
}
Output:
To setup Serilog in asp.net-core application, check out the Serilog.AspNetCore package on GitHub.
Option2: Configure logging in program.cs like this-
var host = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(s => {
s.AddSingleton<IFormatter, LowercaseFormatter>();
})
.ConfigureLogging(f => f.AddConsole(LogLevel.Debug))
.UseStartup<Startup>()
.Build();
host.Run();
User loggerFactory in startup like this-
public class Startup
{
ILogger _logger;
IFormatter _formatter;
public Startup(ILoggerFactory loggerFactory, IFormatter formatter)
{
_logger = loggerFactory.CreateLogger<Startup>();
_formatter = formatter;
}
public void ConfigureServices(IServiceCollection services)
{
_logger.LogDebug($"Total Services Initially: {services.Count}");
// register services
//services.AddSingleton<IFoo, Foo>();
}
public void Configure(IApplicationBuilder app, IFormatter formatter)
{
// note: can request IFormatter here as well as via constructor
_logger.LogDebug("Configure() started...");
app.Run(async (context) => await context.Response.WriteAsync(_formatter.Format("Hi!")));
_logger.LogDebug("Configure() complete.");
}
}
Complete details available on this link
In .NET Core 3.1, you can create a logger directly using LogFactory.
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
});
ILogger logger = loggerFactory.CreateLogger<Startup>();
logger.LogInformation("Example log message");
The official solution is currently to setup a local LoggerFactory like this:
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Information);
builder.AddConsole();
builder.AddEventSourceLogger();
});
var logger = loggerFactory.CreateLogger("Startup");
logger.LogInformation("Hello World");
See also: https://github.com/dotnet/aspnetcore/issues/9337#issuecomment-539859667
For .Net 6
var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build();
var logger = ((IApplicationBuilder)app).ApplicationServices.GetService<ILogger<Program>>();
logger.LogInformation("Some logs");
Or even more easy way:
var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build();
ILogger logger = app.Logger;
Using Rolf's answer, I put this in my Startup constructor:
private readonly ILogger _logger;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Information);
builder.AddConsole();
builder.AddEventSourceLogger();
});
_logger = loggerFactory.CreateLogger<Startup>();
}
public void ConfigureServices(IServiceCollection services)
{
_logger.LogInformation("ConfigureServices...");
// ...and so on...
}
For .NET Core 3.0 the official docs has this to say: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0#create-logs-in-startup
Writing logs before completion of the DI container setup in the Startup.ConfigureServices method is not supported:
Logger injection into the Startup constructor is not supported.
Logger injection into the Startup.ConfigureServices method signature is not supported
But as they say in the docs you can configure a service that depends on ILogger, so if you wrote a class StartupLogger:
public class StartupLogger
{
private readonly ILogger _logger;
public StartupLogger(ILogger<StartupLogger> logger)
{
_logger = logger;
}
public void Log(string message)
{
_logger.LogInformation(message);
}
}
Then in Startup.ConfigureServices add the service, then you need to build the service provider to get access to the DI container:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(provider =>
{
var service = provider.GetRequiredService<ILogger<StartupLogger>>();
return new StartupLogger(service);
});
var logger = services.BuildServiceProvider().GetRequiredService<StartupLogger>();
logger.Log("Startup.ConfigureServices called");
}
Edit: this produces a compiler warning, for the sake of debugging your StartUp class this should be OK but not for production:
Startup.cs(39, 32): [ASP0000] Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
None of the existing answers worked for me. I'm using NLog, and even building a new ServiceCollection, calling .CreateBuilder() on any service collection, creating a logging service ... none of that would write to a log file during ConfigureServices.
The problem is that logging isn't really a thing until after the ServiceCollection is built, and it's not built during ConfigureServices.
Basically, I just want (need) to log what's going on during startup in a configuration extension method, because the only tier I'm having a problem on is PROD, where I can't attach a debugger.
The solution that worked for me was using the old .NET Framework NLog method:
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
Added that right to the extension method class, and I was able to write to a log ("the" log) during ConfigureServices and after.
I have no idea if this is a good idea to actually release into production code (I don't know if the .NET controlled ILogger and this NLog.ILogger will conflict at any point), but I only needed it to see what was going on.
I use a solution avoiding 3rd party loggers implementing a "logger buffer" with ILogger interface.
public class LoggerBuffered : ILogger
{
class Entry
{
public LogLevel _logLevel;
public EventId _eventId;
public string _message;
}
LogLevel _minLogLevel;
List<Entry> _buffer;
public LoggerBuffered(LogLevel minLogLevel)
{
_minLogLevel = minLogLevel;
_buffer = new List<Entry>();
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= _minLogLevel;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (IsEnabled(logLevel)) {
var str = formatter(state, exception);
_buffer.Add(new Entry { _logLevel = logLevel, _eventId = eventId, _message = str });
}
}
public void CopyToLogger (ILogger logger)
{
foreach (var entry in _buffer)
{
logger.Log(entry._logLevel, entry._eventId, entry._message);
}
_buffer.Clear();
}
}
Usage in startup.cs is easy, of course you get log output after call of Configure. But better than nothing. :
public class Startup
{
ILogger _logger;
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
_logger = new LoggerBuffered(LogLevel.Debug);
_logger.LogInformation($"Create Startup {env.ApplicationName} - {env.EnvironmentName}");
}
public void ConfigureServices(IServiceCollection services)
{
_logger.LogInformation("ConfigureServices");
services.AddControllersWithViews();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
(_logger as LoggerBuffered).CopyToLogger(logger);
_logger = logger; // Replace buffered by "real" logger
_logger.LogInformation("Configure");
if (env.IsDevelopment())
Main code:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
CreateDefaultBuilder sets up a default console logger.
... configures the ILoggerFactory to log to the console and debug output
Startup code:
using Microsoft.Extensions.Logging;
...
public class Startup
{
private readonly ILogger _logger;
public Startup(IConfiguration configuration, ILoggerFactory logFactory)
{
_logger = logFactory.CreateLogger<Startup>();
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
_logger.LogInformation("hello stackoverflow");
}
I couldn't get the injection of an ILogger to work, but perhaps that's because it's not a controller. More info welcome!
Refs:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1&tabs=aspnetcore2x
Are you making decisions about which services you are using at runtime that you wish to log? Or are you making decisions about how those services are configured, which you wish to log?
In other words;
public void ConfigureServices(IServiceCollection services){
// Do you really want to log something here?
services.AddRazorPages(options => {
// Or would you be satisfied by logging something here?
});
}
If it is only the latter, you can move the implementation of these lambda functions into an IConfigureOptions<T> service, allowing you to inject other services. Continuing the above example, you could create the following;
public class ConfigureRazorPagesOptions : IConfigureOptions<RazorPagesOptions>
{
private readonly ILogger<ConfigureRazorPagesOptions> logger;
public ConfigureRazorPagesOptions(ILogger<ConfigureRazorPagesOptions> logger)
{
this.logger = logger;
}
public void Configure(RazorPagesOptions options)
{
logger.LogInformation("Now I can log here!");
}
}
public void ConfigureServices(IServiceCollection services){
services.AddRazorPages();
services.AddSingleton<IConfigureOptions<RazorPagesOptions>, ConfigureRazorPagesOptions>();
}
If your .ConfigureServices method is getting too complicated, you might want to create such services. However, that's a lot of boilerplate to add for each options type. There is also an equivalent shorthand, to inject other services into a configuration lamda;
services.AddOptions<RazorPagesOptions>()
.Configure<ILogger<RazorPagesOptions>>((options, logger) => {
logger.LogInformation("I can log here too!");
});
This worked for me
private static readonly Logger logger = LogManager.GetLogger("Audit")
I found a very easy implementation:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var conn = Configuration.GetValue("conn", Configuration.GetConnectionString("Conn"));
Console.WriteLine($#"System starting at {DateTime.Now}");
Console.WriteLine($#"Database: {conn}");
}
Just using Console.WriteLine worked, even on Docker.
Just use the line below for logging in Startup.cs
Log.Information("App started.");
Azure Functions V2 now supports .net dependency injection
In order to achieve that you need to do the following code:
[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]
namespace MyNamespace
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddHttpClient();
builder.Services.AddSingleton((s) => {
return new CosmosClient(Environment.GetEnvironmentVariable("COSMOSDB_CONNECTIONSTRING"));
});
builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
}
}
}
I want to change the default container from .net to "Lamar" DI framework.
On their documentation they have an example for a WebHost:
var builder = new WebHostBuilder();
builder
// Replaces the built in DI container
// with Lamar
.UseLamar()
// Normal ASP.Net Core bootstrapping
.UseUrls("http://localhost:5002")
.UseKestrel()
.UseStartup<Startup>();
builder.Start();
But I'm not able to change IFunctionsHostBuilder to use "UseLamar()" extension. Since this extends IWebHostBuilder.
The only ways I was able to intercept the initialization of azure functions was Either with FunctionsStartup that configures IFunctionsHostBuilder or IWebJobsStartup that configures IWebJobsBuilder, but I don't find extensions for those kinds of builds on Lamar.
I've tried to check the existing extension to create a similar code but is not working because probably I need to create more stuff:
[assembly: FunctionsStartup(typeof(FunctionAppPrototype.Startup))]
namespace FunctionAppPrototype
{
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
var container = new Container(x =>
{
x.AddTransient<IMyService, MyService>();
});
builder.Services.AddSingleton<IServiceProviderFactory<IServiceCollection>, LamarServiceProviderFactory>();
builder.Services.AddSingleton<IServiceProviderFactory<ServiceRegistry>, LamarServiceProviderFactory>();
}
}
}
After some research, I was able to find a solution using Autofac. I was not able to do it with Lamar it had no extension either for IFunctionsHostBuilder or IWebJobsBuilder.
Source Code: Binding extensions for dependency injection in Azure Function v2
Nuget: Willezone.Azure.WebJobs.Extensions.DependencyInjection
First, you need to intercept the startup of the function app by doing the following code:
[assembly: WebJobsStartup(typeof(AutoFacFunctionAppPrototype.WebJobsStartup))]
namespace AutoFacFunctionAppPrototype
{
public class WebJobsStartup : IWebJobsStartup
{
public void Configure(IWebJobsBuilder builder) =>
builder.AddDependencyInjection<AutoFacServiceProviderBuilder>();
}
}
Then create the container and register the dependencies:
namespace AutoFacFunctionAppPrototype.Builders
{
public class AutoFacServiceProviderBuilder : IServiceProviderBuilder
{
private readonly IConfiguration configuration;
public AutoFacServiceProviderBuilder(IConfiguration configuration)
=> this.configuration = configuration;
public IServiceProvider Build()
{
var services = new ServiceCollection();
services.AddTransient<ITransientService, TransientService>();
services.AddScoped<IScopedService, ScopedService>();
var builder = new ContainerBuilder();
builder.RegisterType<SingletonService>().As<ISingletonService>().SingleInstance();
builder.Populate(services); // Populate is needed to have support for scopes.
return new AutofacServiceProvider(builder.Build());
}
}
}
Then you can use them on the function using the attribute [Inject]:
namespace AutoFacFunctionAppPrototype.Functions
{
public static class CounterFunction
{
[FunctionName("Counter")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
[Inject]ITransientService transientService,
[Inject]IScopedService scopedService,
[Inject]ISingletonService singletonService,
ILogger logger)
{
logger.LogInformation("C# HTTP trigger function processed a request.");
string result = String.Join(Environment.NewLine, new[] {
$"Transient: {transientService.GetCounter()}",
$"Scoped: {scopedService.GetCounter()}",
$"Singleton: {singletonService.GetCounter()}",
});
return new OkObjectResult(result);
}
}
}
Using this approach I was only able to inject parameters, was not able to do constructor or property injection, even though I was a non-static class.
Note: If in the future Autofac supports extension for IFunctionsHostBuilder, probably would be better to use that approach instead of IWebJobsStartup.
I've created a new way to use Autofac DI in a Azure Functions v3 project, without using static functions or inject attributes. The disposal of services are called using the appropriate scope.
GitHub: https://github.com/junalmeida/autofac-azurefunctions
NuGet: https://www.nuget.org/packages/Autofac.Extensions.DependencyInjection.AzureFunctions
Feel free to contribute with me!
Example
public class Function1
{
private readonly IService1 _service1;
public Function1(IService1 service1)
{
_service1 = service1;
}
[FunctionName(nameof(Function1))]
public async Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")]string myQueueItem, ILogger log)
{
...
}
Good afternoon,
I recently started experimenting with Service Fabric and .NET Core.
I created a Stateless Web API and performed some DI using:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
var connString = Configuration.GetConnectionString("DefaultConnection");
services.AddScoped<FaxLogic>();
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connString));
}
With the above I can use constructor inject on my FaxLogic class as well as my DbContext class (through the FaxLogic):
private readonly FaxLogic _faxLogic;
public FaxController(
FaxLogic faxLogic)
{
_faxLogic = faxLogic;
}
private readonly ApplicationContext _context;
public FaxLogic(ApplicationContext context)
{
_context = context;
}
I then created a non-Web API stateless service. I want to be able to access my FaxLogic and DbContext like in my WebAPI, but within the RunAsync method of the stateless service:
protected override async Task RunAsync(CancellationToken cancellationToken)
{
// TODO: Replace the following sample code with your own logic
// or remove this RunAsync override if it's not needed in your service.
while (true)
{
cancellationToken.ThrowIfCancellationRequested();
ServiceEventSource.Current.ServiceMessage(this.Context, "Hello!");
// do db stuff here!
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
}
}
I am wondering how I'd do it. I tried playing with the CreateServiceInstanceListeners() method and the Program.cs file where ServiceRuntime is is used to register but I can't seem to figure it out! Any help would be appreciated.
The solution has been already answered here: Set up Dependency Injection on Service Fabric using default ASP.NET Core DI container
In summary, you have to register the dependencies before you create a new instance of your stateless service and then create a factory method to resolve the dependencies:
i.e:
public static class Program
{
public static void Main(string[] args)
{
var provider = new ServiceCollection()
.AddLogging()
.AddSingleton<IFooService, FooService>()
.AddSingleton<IMonitor, MyMonitor>()
.BuildServiceProvider();
ServiceRuntime.RegisterServiceAsync("MyServiceType",
context => new MyService(context, provider.GetService<IMonitor>());
}).GetAwaiter().GetResult();
See the linked answer for more details.
TaeSeo,
I think what you are looking for is implemented in the project I am working on - CoherentSolutions.Extensions.Hosting.ServiceFabric.
In the terms of CoherentSolutions.Extensions.Hosting.ServiceFabric what you are looking for would look like:
private static void Main(string[] args)
{
new HostBuilder()
.DefineStatelessService(
serviceBuilder => {
serviceBuilder
.UseServiceType("ServiceName")
.DefineDelegate(
delegateBuilder => {
delegateBuilder.ConfigureDependencies(
dependencies => {
dependencies.AddScoped<FaxLogic>();
});
delegateBuilder.UseDelegate(
async (StatelessServiceContext context, FaxLogic faxLogic) => {
while (true) {
cancellationToken.ThrowIfCancellationRequested();
ServiceEventSource.Current.ServiceMessage(context, "Hello!");
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
});
})
})
.Build()
.Run();
}
If you have more questions feel free to ask or check out the project wiki
Hope it helps.
In order to debug a .NET Core app which is failing on startup, I would like to write logs from within the startup.cs file. I have logging setup within the file that can be used in the rest of the app outside the startup.cs file, but not sure how to write logs from within the startup.cs file itself.
.Net Core 3.1
Unfortunately, for ASP.NET Core 3.0, the situation is again a bit different. The default templates use the HostBuilder (instead of the WebHostBuilder) which sets up a new generic host that can host several different applications, not limited to web applications. Part of this new host is also the removal of the second dependency injection container that previously existed for the web host. This ultimately means that you won’t be able to inject any dependencies apart from the IConfiguration into the Startup class. So you won’t be able to log during the ConfigureServices method. You can, however, inject the logger into the Configure method and log there:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
{
logger.LogInformation("Configure called");
// …
}
If you absolutely need to log within ConfigureServices, then you can continue to use the WebHostBuilder which will create the legacy WebHost that can inject the logger into the Startup class. Note that it’s likely that the web host will be removed at some point in the future. So you should try to find a solution that works for you without having to log within ConfigureServices.
.NET Core 2.x
This has changed significantly with the release of ASP.NET Core 2.0. In ASP.NET Core 2.x, logging is created at the host builder. This means that logging is available through DI by default and can be injected into the Startup class:
public class Startup
{
private readonly ILogger<Startup> _logger;
public IConfiguration Configuration { get; }
public Startup(ILogger<Startup> logger, IConfiguration configuration)
{
_logger = logger;
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
_logger.LogInformation("ConfigureServices called");
// …
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
_logger.LogInformation("Configure called");
// …
}
}
Option 1: Directly use log (e.g. Serilog) in startup-
public class Startup
{
public Startup(IHostingEnvironment env)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.RollingFile(Path.Combine(env.ContentRootPath, "Serilog-{Date}.txt"))
.CreateLogger();
Log.Information("Inside Startup ctor");
....
}
public void ConfigureServices(IServiceCollection services)
{
Log.Information("ConfigureServices");
....
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
Log.Information("Configure");
....
}
Output:
To setup Serilog in asp.net-core application, check out the Serilog.AspNetCore package on GitHub.
Option2: Configure logging in program.cs like this-
var host = new WebHostBuilder()
.UseKestrel()
.ConfigureServices(s => {
s.AddSingleton<IFormatter, LowercaseFormatter>();
})
.ConfigureLogging(f => f.AddConsole(LogLevel.Debug))
.UseStartup<Startup>()
.Build();
host.Run();
User loggerFactory in startup like this-
public class Startup
{
ILogger _logger;
IFormatter _formatter;
public Startup(ILoggerFactory loggerFactory, IFormatter formatter)
{
_logger = loggerFactory.CreateLogger<Startup>();
_formatter = formatter;
}
public void ConfigureServices(IServiceCollection services)
{
_logger.LogDebug($"Total Services Initially: {services.Count}");
// register services
//services.AddSingleton<IFoo, Foo>();
}
public void Configure(IApplicationBuilder app, IFormatter formatter)
{
// note: can request IFormatter here as well as via constructor
_logger.LogDebug("Configure() started...");
app.Run(async (context) => await context.Response.WriteAsync(_formatter.Format("Hi!")));
_logger.LogDebug("Configure() complete.");
}
}
Complete details available on this link
In .NET Core 3.1, you can create a logger directly using LogFactory.
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
});
ILogger logger = loggerFactory.CreateLogger<Startup>();
logger.LogInformation("Example log message");
The official solution is currently to setup a local LoggerFactory like this:
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Information);
builder.AddConsole();
builder.AddEventSourceLogger();
});
var logger = loggerFactory.CreateLogger("Startup");
logger.LogInformation("Hello World");
See also: https://github.com/dotnet/aspnetcore/issues/9337#issuecomment-539859667
For .Net 6
var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build();
var logger = ((IApplicationBuilder)app).ApplicationServices.GetService<ILogger<Program>>();
logger.LogInformation("Some logs");
Or even more easy way:
var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build();
ILogger logger = app.Logger;
Using Rolf's answer, I put this in my Startup constructor:
private readonly ILogger _logger;
public Startup(IConfiguration configuration)
{
Configuration = configuration;
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.SetMinimumLevel(LogLevel.Information);
builder.AddConsole();
builder.AddEventSourceLogger();
});
_logger = loggerFactory.CreateLogger<Startup>();
}
public void ConfigureServices(IServiceCollection services)
{
_logger.LogInformation("ConfigureServices...");
// ...and so on...
}
For .NET Core 3.0 the official docs has this to say: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0#create-logs-in-startup
Writing logs before completion of the DI container setup in the Startup.ConfigureServices method is not supported:
Logger injection into the Startup constructor is not supported.
Logger injection into the Startup.ConfigureServices method signature is not supported
But as they say in the docs you can configure a service that depends on ILogger, so if you wrote a class StartupLogger:
public class StartupLogger
{
private readonly ILogger _logger;
public StartupLogger(ILogger<StartupLogger> logger)
{
_logger = logger;
}
public void Log(string message)
{
_logger.LogInformation(message);
}
}
Then in Startup.ConfigureServices add the service, then you need to build the service provider to get access to the DI container:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(provider =>
{
var service = provider.GetRequiredService<ILogger<StartupLogger>>();
return new StartupLogger(service);
});
var logger = services.BuildServiceProvider().GetRequiredService<StartupLogger>();
logger.Log("Startup.ConfigureServices called");
}
Edit: this produces a compiler warning, for the sake of debugging your StartUp class this should be OK but not for production:
Startup.cs(39, 32): [ASP0000] Calling 'BuildServiceProvider' from application code results in an additional copy of singleton services being created. Consider alternatives such as dependency injecting services as parameters to 'Configure'.
None of the existing answers worked for me. I'm using NLog, and even building a new ServiceCollection, calling .CreateBuilder() on any service collection, creating a logging service ... none of that would write to a log file during ConfigureServices.
The problem is that logging isn't really a thing until after the ServiceCollection is built, and it's not built during ConfigureServices.
Basically, I just want (need) to log what's going on during startup in a configuration extension method, because the only tier I'm having a problem on is PROD, where I can't attach a debugger.
The solution that worked for me was using the old .NET Framework NLog method:
private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();
Added that right to the extension method class, and I was able to write to a log ("the" log) during ConfigureServices and after.
I have no idea if this is a good idea to actually release into production code (I don't know if the .NET controlled ILogger and this NLog.ILogger will conflict at any point), but I only needed it to see what was going on.
I use a solution avoiding 3rd party loggers implementing a "logger buffer" with ILogger interface.
public class LoggerBuffered : ILogger
{
class Entry
{
public LogLevel _logLevel;
public EventId _eventId;
public string _message;
}
LogLevel _minLogLevel;
List<Entry> _buffer;
public LoggerBuffered(LogLevel minLogLevel)
{
_minLogLevel = minLogLevel;
_buffer = new List<Entry>();
}
public IDisposable BeginScope<TState>(TState state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return logLevel >= _minLogLevel;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (IsEnabled(logLevel)) {
var str = formatter(state, exception);
_buffer.Add(new Entry { _logLevel = logLevel, _eventId = eventId, _message = str });
}
}
public void CopyToLogger (ILogger logger)
{
foreach (var entry in _buffer)
{
logger.Log(entry._logLevel, entry._eventId, entry._message);
}
_buffer.Clear();
}
}
Usage in startup.cs is easy, of course you get log output after call of Configure. But better than nothing. :
public class Startup
{
ILogger _logger;
public Startup(IConfiguration configuration, IWebHostEnvironment env)
{
_logger = new LoggerBuffered(LogLevel.Debug);
_logger.LogInformation($"Create Startup {env.ApplicationName} - {env.EnvironmentName}");
}
public void ConfigureServices(IServiceCollection services)
{
_logger.LogInformation("ConfigureServices");
services.AddControllersWithViews();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
(_logger as LoggerBuffered).CopyToLogger(logger);
_logger = logger; // Replace buffered by "real" logger
_logger.LogInformation("Configure");
if (env.IsDevelopment())
Main code:
public class Program
{
public static void Main(string[] args)
{
BuildWebHost(args).Run();
}
public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.Build();
}
CreateDefaultBuilder sets up a default console logger.
... configures the ILoggerFactory to log to the console and debug output
Startup code:
using Microsoft.Extensions.Logging;
...
public class Startup
{
private readonly ILogger _logger;
public Startup(IConfiguration configuration, ILoggerFactory logFactory)
{
_logger = logFactory.CreateLogger<Startup>();
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
_logger.LogInformation("hello stackoverflow");
}
I couldn't get the injection of an ILogger to work, but perhaps that's because it's not a controller. More info welcome!
Refs:
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1&tabs=aspnetcore2x
Are you making decisions about which services you are using at runtime that you wish to log? Or are you making decisions about how those services are configured, which you wish to log?
In other words;
public void ConfigureServices(IServiceCollection services){
// Do you really want to log something here?
services.AddRazorPages(options => {
// Or would you be satisfied by logging something here?
});
}
If it is only the latter, you can move the implementation of these lambda functions into an IConfigureOptions<T> service, allowing you to inject other services. Continuing the above example, you could create the following;
public class ConfigureRazorPagesOptions : IConfigureOptions<RazorPagesOptions>
{
private readonly ILogger<ConfigureRazorPagesOptions> logger;
public ConfigureRazorPagesOptions(ILogger<ConfigureRazorPagesOptions> logger)
{
this.logger = logger;
}
public void Configure(RazorPagesOptions options)
{
logger.LogInformation("Now I can log here!");
}
}
public void ConfigureServices(IServiceCollection services){
services.AddRazorPages();
services.AddSingleton<IConfigureOptions<RazorPagesOptions>, ConfigureRazorPagesOptions>();
}
If your .ConfigureServices method is getting too complicated, you might want to create such services. However, that's a lot of boilerplate to add for each options type. There is also an equivalent shorthand, to inject other services into a configuration lamda;
services.AddOptions<RazorPagesOptions>()
.Configure<ILogger<RazorPagesOptions>>((options, logger) => {
logger.LogInformation("I can log here too!");
});
This worked for me
private static readonly Logger logger = LogManager.GetLogger("Audit")
I found a very easy implementation:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
var conn = Configuration.GetValue("conn", Configuration.GetConnectionString("Conn"));
Console.WriteLine($#"System starting at {DateTime.Now}");
Console.WriteLine($#"Database: {conn}");
}
Just using Console.WriteLine worked, even on Docker.
Just use the line below for logging in Startup.cs
Log.Information("App started.");