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.
ASP.NET Core 6+ (without Startup)
With the new approach with ASP.NET Core, not to use an explicit Startup
class, the explanations below with ConfigureServices
and Configure
no longer apply. Instead, everything is configured directly on the web application builder or the built app instead.
To access the logger on the application, you can just retrieve it from the service provider:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// … adding services to the container
// build the application
var app = builder.Build();
// retrieve the logger
var logger = app.Services.GetService<ILogger<Program>>();
// configure request pipeline
if (!app.Environment.IsDevelopment())
{
logger.LogInformation("Using production pipeline");
app.UseExceptionHandler("/Error");
}
// …
app.MapDefaultControllerRoute();
app.Run();
As before, you cannot access the logger before building the service container (builder.Build()
) though, for the same reasons explained below.
ASP.NET Core 3.1+ (with Startup)
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
.
ASP.NET Core 2
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");
// …
}
}
ConfigureServices
runs, the logger does not actually exist yet. So you won’t be able to log at that point simply because there is no logger yet. On the plus side, that still gives you the ability to configure the logger within the ConfigureServices
since it’s all the same DI container (which is actually a good thing). – If you absolutely need to log stuff, you could for example collect the information separately (e.g. in a list) and then log that out as soon as the logger is available. –
Corfam .NET 3.1
you currently CAN log within the ConfigureServices
method without falling back on the WebHostBuilder
. Use answer below: https://mcmap.net/q/117710/-how-do-i-write-logs-from-within-startup-cs –
Sterilize Startup.cs
(so I get compiler errors when forgetting a dependency) instead of only registering custom dependencies. Therefore I need to resolve these loggers. But this might be a bit hacky, yes. –
Sterilize Startup
that are initialized along with the host" builder.UseStartup(context => new Startup(logger));
- learn.microsoft.com/en-us/aspnet/core/release-notes/… –
Brittni IConfigureOptions<T>
services. Then you can inject loggers into those services, leaving your ConfigureServices
method as just a list of .Add...
calls. –
Fillagree Startup
flow. –
Aryanize AddMyProjectCore(IConfiguration config, ILogger logger)
and can be used in the following manner: builder.Services.AddMyProject(config, logger)
. What would be the right approach to get the config
and logger
objects? Or do I need to refactor these methods? –
Illfounded ILogger
into the constructor of the Startup
and then use it ConfigureServices
method (Note: I am using .NET 6 and Serilog, Serilog is configured in the Program.cs
) –
Flump 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");
ILogger logger = LoggerFactory.Create(builder => builder.AddLog4Net()).CreateLogger<Startup>();
. However, if you only log exceptions it will not reveal much more than what already shows up in the Windows EventLog (when using IIS). –
Ljubljana 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...
}
builder.AddDebug()
if you want it to show up in VS debug output. –
Epifaniaepifano 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.
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'.
StartupLogger
needed? Why can't you simply get ILogger<Startup>
when you build service provider? :) –
Shin 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:
netcoreapp3.1
. I even inject directly ILogger<Startup>
instead of the factory –
Weariless 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!");
});
The existing and accepted answers still reflect an accurate state of affairs as of Dec '23 regarding the availability of the ILogger
abstraction during services configuration and adequate workarounds, especially if you're trying to capture information about the host builder itself prior to run.
I've been using a lot of approaches similar to what @jeremy-lakeman outlines in his answer with an IConfigureOptions
mechanism to handle logging related to the services registration flow itself during startup. While these logs are effectively being deferred until after the host starts and may not be suitable for all cases, I'd repeated the pattern enough times that it warranted creating a library for it to add some syntactic sugar such that I could invoke ILogger
behavior against the IServiceCollection
itself.
https://github.com/agertenbach/LogDeferred
https://www.nuget.org/packages/LogDeferred/
The library maps the ILogger
and several of the IOptions
methods such that you can use code like the following:
// ILogger interface support implemented against IServiceCollection
builder.Services.LogDebug("Logs against a default Microsoft.Hosting.Startup logger");
builder.Services.LogInformation<MyTypeHere>("Logs deferred against the {type} logger", typeof(MyTypeHere).FullName);
// Multiple delegated actions against an ILogger
builder.Services.Log(logger =>
{
logger.LogInformation("Information log");
logger.LogDebug("Debug logger");
});
builder.Services.Log<MyTypeHere>(logger =>
{
logger.LogInformation("Types are supported here too");
});
// Delegated log actions with optional or required dependencies injected
builder.Services.Log<MyFeatures>()
.For<FeatureOne>((logger, feature) =>
{
logger.LogInformation("Feature one is null? {isNull}", feature is null);
})
.ForRequired<FeatureTwo, IOptions<MyFeatureConfig>>((logger, f2, config) =>
{
logger.LogInformation("If either of these were null, you'd get an InvalidOperationException");
});
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.
This worked for me
private static readonly Logger logger = LogManager.GetLogger("Audit")
Just use the line below for logging in Startup.cs
Log.Information("App started.");
© 2022 - 2024 — McMap. All rights reserved.