How do I write logs from within Startup.cs?
Asked Answered
T

15

228

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.

Trunnion answered 22/12, 2016 at 16:27 Comment(0)
C
307

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

        // …
    }
}
Corfam answered 19/9, 2017 at 10:25 Comment(13)
THANK YOU. It is amazing how much time you can burn looking for answers to simple questions. @Corfam 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.Feder
@Feder 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.Corfam
Within .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-csSterilize
@Sterilize 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.Corfam
@Corfam 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.Sterilize
.NET 5: "pass additional parameters to Startup that are initialized along with the host" builder.UseStartup(context => new Startup(logger)); - learn.microsoft.com/en-us/aspnet/core/release-notes/…Brittni
IMHO Use the options pattern to shift as much config as possible into IConfigureOptions<T> services. Then you can inject loggers into those services, leaving your ConfigureServices method as just a list of .Add... calls.Fillagree
@Brittni 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.Corfam
Need a .NET 6 answer that handles getting the logger before the app is built and not using the Startup flow.Aryanize
@Aryanize 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.Corfam
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?Illfounded
@Corfam 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)Flump
this solution works nicely for .net 8 also :)Sym
L
51

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

Longeron answered 22/12, 2016 at 17:12 Comment(0)
S
27

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");
Savagism answered 16/6, 2020 at 8:55 Comment(1)
For log4net it boils down to 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
D
23

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

Detail answered 28/4, 2020 at 19:31 Comment(0)
P
17

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;
Philomel answered 17/5, 2022 at 13:33 Comment(1)
How to get the logger before the app is built?Aryanize
U
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...
}
Ufa answered 17/3, 2021 at 9:15 Comment(2)
King. This is what worked for me.Wenoa
also include builder.AddDebug() if you want it to show up in VS debug output.Epifaniaepifano
O
7

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.

Ostracoderm answered 24/4, 2020 at 14:37 Comment(0)
C
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'.

Catoptrics answered 11/11, 2019 at 13:17 Comment(1)
Why is StartupLogger needed? Why can't you simply get ILogger<Startup> when you build service provider? :)Shin
D
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())
Device answered 24/3, 2020 at 15:16 Comment(0)
S
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:

Suetonius answered 8/5, 2018 at 15:52 Comment(2)
for some reason the same constructor injection works for me just fine with the netcoreapp3.1. I even inject directly ILogger<Startup> instead of the factoryWeariless
You also can save private readonly ILoggerFactory _loggerFactory; and create logger when required var defaultLogger = _loggerFactory.CreateLogger<ItineraryChangePublisher>();Uncritical
F
2

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!");
    });
Fillagree answered 2/12, 2021 at 1:7 Comment(0)
I
0

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");
        });
Incomprehensive answered 11/12, 2023 at 18:53 Comment(0)
I
-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.

Importune answered 1/12, 2021 at 23:4 Comment(2)
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.Pufahl
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.Connor
S
-3

This worked for me

private static readonly Logger logger = LogManager.GetLogger("Audit")
Superfamily answered 31/12, 2020 at 18:11 Comment(3)
That LogManager doesn't exist in dotnet.core, I guess it comes from an external library (NLog, log4net,Serilog,...)?Schoolbook
@xisket Yes its from NLog, NLog.LogManager.GetLogger("Audit")Superfamily
Can you edit your answer so that we do not need to read the comments to understand it?Jaan
S
-7

Just use the line below for logging in Startup.cs

Log.Information("App started.");
Sporozoite answered 11/6, 2020 at 10:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.