ChatGPT解决这个技术问题 Extra ChatGPT

How do I write logs from within Startup.cs?

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.


K
Kishan Vaishnav

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

        // …
    }
}

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 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.
Within .NET 3.1 you currently CAN log within the ConfigureServices method without falling back on the WebHostBuilder. Use answer below: stackoverflow.com/a/61488490/2877982
@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.
.NET 5: "pass additional parameters to Startup that are initialized along with the host" builder.UseStartup(context => new Startup(logger)); - docs.microsoft.com/en-us/aspnet/core/release-notes/…
p
poke

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:

https://i.stack.imgur.com/96E8P.png

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


P
Pang

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

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).
R
Rolf Kristensen

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


C
Christian Riedl

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())

U
Uwe Keim

For .NET Core 3.0 the official docs has this to say: https://docs.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'.


Why is StartupLogger needed? Why can't you simply get ILogger<Startup> when you build service provider? :)
P
Pang

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.


P
Pang

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

T
Tim Abell

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://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1&tabs=aspnetcore2x


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 factory
You also can save private readonly ILoggerFactory _loggerFactory; and create logger when required var defaultLogger = _loggerFactory.CreateLogger();
R
R.Titov

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;

J
Jeremy Lakeman

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

K
Karthik

This worked for me

private static readonly Logger logger = LogManager.GetLogger("Audit")

That LogManager doesn't exist in dotnet.core, I guess it comes from an external library (NLog, log4net,Serilog,...)?
@xisket Yes its from NLog, NLog.LogManager.GetLogger("Audit")
D
Dharman

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.


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.
D
David Buck

Just use the line below for logging in Startup.cs

Log.Information("App started.");