217

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
Mark Redman
  • 24,079
  • 20
  • 92
  • 147

14 Answers14

293

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");

        // …
    }
}
Hakan Fıstık
  • 16,800
  • 14
  • 110
  • 131
poke
  • 369,085
  • 72
  • 557
  • 602
  • 10
    THANK YOU. It is amazing how much time you can burn looking for answers to simple questions. @poke thank you (again) for informing me what my options are. Where did you get this information? I've confirmed I can log stuff in Configure, which is preferable to a poke (pun intended) in the eye with a sharp stick but perhaps not as awesome as being to able during ConfigureServices. In my case I'd like to log whether or not I got env settings, perhaps even post them to the log. No dice? Sigh... not sure why this should be so hard. But at least, thanks to this post, I know what I can and cannot do. – Wellspring Jun 29 '19 at 21:46
  • 4
    @Wellspring In 3.0, it is “hard” because by the time the `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. – poke Jun 30 '19 at 17:02
  • 2
    Within `.NET 3.1` you currently CAN log within the `ConfigureServices` method *without* falling back on the `WebHostBuilder`. Use answer below: https://stackoverflow.com/a/61488490/2877982 – Aage Jul 02 '20 at 09:00
  • 2
    @Aage This will have several disadvantages though: You will have to repeat your full logging configuration, the logging configuration will also not reflect your application configuration (e.g. log levels configured in appsettings etc), and you are generally setting up a second logging infrastructure. I would still suggest you to look for a solution how you can avoid logging during the DI setup altogether there. – poke Jul 02 '20 at 13:59
  • @poke I hadn't thought about this. Really I just want *explicit* controller construction wired up in my `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. – Aage Jul 03 '20 at 04:32
  • 9
    **.NET 5**: "pass additional parameters to `Startup` that are initialized along with the host" `builder.UseStartup(context => new Startup(logger));` - https://learn.microsoft.com/en-us/aspnet/core/release-notes/aspnetcore-5.0?view=aspnetcore-5.0#control-startup-class-activation – Pang Nov 12 '20 at 00:45
  • IMHO Use the options pattern to shift as much config as possible into `IConfigureOptions` services. Then you can inject loggers into those services, leaving your `ConfigureServices` method as just a list of `.Add...` calls. – Jeremy Lakeman Nov 12 '20 at 01:02
  • 1
    @Pang That’s an interesting new thing but it still won’t solve the chicken and egg problem there considering that the logging infrastructure is created (and configured) by the host only after the startup gets instantiated. – poke Nov 12 '20 at 07:31
  • Need a .NET 6 answer that handles getting the logger before the app is built and not using the `Startup` flow. – jjxtra Mar 06 '23 at 16:41
  • 1
    @jjxtra I’ve added some details on how to retrieve the logger when using the Startup-less approach. For the same reasons as before, you cannot get a logger before building though. If you want to do this in order to log dynamic service registration decisions, you could log those afterwards. If you need the logger as part of some configured dependency, then you should look into resolving the logger there dynamically. – poke Mar 06 '23 at 16:59
  • Hi @poke, I have a few custom extension methods that add services to the container: `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? – phougatv Jul 13 '23 at 05:56
  • @poke I just wanted to say that I was able to inject `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`) – Hakan Fıstık Jul 25 '23 at 11:27
50

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:

serilog

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

poke
  • 369,085
  • 72
  • 557
  • 602
Sanket
  • 19,295
  • 10
  • 71
  • 82
26

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");
Pang
  • 9,564
  • 146
  • 81
  • 122
Liang
  • 867
  • 11
  • 13
  • For log4net it boils down to `ILogger logger = LoggerFactory.Create(builder => builder.AddLog4Net()).CreateLogger();`. However, if you only log exceptions it will not reveal much more than what already shows up in the Windows EventLog (when using IIS). – Louis Somers Sep 25 '20 at 08:03
22

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

spottedmahn
  • 14,823
  • 13
  • 108
  • 178
Rolf Kristensen
  • 17,785
  • 1
  • 51
  • 70
15

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;
R.Titov
  • 3,115
  • 31
  • 35
9

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...
}
Pang
  • 9,564
  • 146
  • 81
  • 122
Johan Danforth
  • 4,469
  • 6
  • 37
  • 36
6

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'.

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Dylan Munyard
  • 115
  • 1
  • 4
6

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
emery.noel
  • 1,073
  • 1
  • 9
  • 24
5

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())
Christian Riedl
  • 103
  • 3
  • 9
4

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:

Tim Abell
  • 11,186
  • 8
  • 79
  • 110
  • for some reason the same constructor injection works for me just fine with the `netcoreapp3.1`. I even inject directly `ILogger` instead of the factory – dodbrian Aug 04 '21 at 12:02
  • You also can save private readonly ILoggerFactory _loggerFactory; and create logger when required var defaultLogger = _loggerFactory.CreateLogger(); – Michael Freidgeim Nov 04 '21 at 01:31
1

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!");
    });
Jeremy Lakeman
  • 9,515
  • 25
  • 29
-2

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.

Dharman
  • 30,962
  • 25
  • 85
  • 135
  • There are **twelve existing answers** to this question, including an accepted answer with 237 (!!!) upvotes. Are you sure your answer hasn't already been provided? If not, why might someone prefer your approach over the existing approaches proposed? Are you taking advantage of new capabilities? Are there scenarios where your approach is better suited? Explanations are _always_ useful, but are _especially_ important here. – Jeremy Caney Dec 01 '21 at 23:39
  • Console.WriteLine(...) in ConfigureServices(...) isn't writing to anywhere for me; although I set all logging levels to debug. Strange how difficult it is to get something out of the configuring process. – Jens Mander Sep 26 '22 at 00:26
-3

This worked for me

private static readonly Logger logger = LogManager.GetLogger("Audit")
Karthik
  • 65
  • 1
  • 4
-6

Just use the line below for logging in Startup.cs

Log.Information("App started.");
David Buck
  • 3,752
  • 35
  • 31
  • 35