Wie schreibe ich Protokolle aus Startup.cs

106

Um eine .net-Kern-App zu debuggen, die beim Start fehlschlägt, möchte ich Protokolle aus der Datei startup.cs schreiben. Ich habe ein Protokollierungssetup in der Datei, das im Rest der App außerhalb der Datei startup.cs verwendet werden kann, bin mir jedoch nicht sicher, wie Protokolle aus der Datei startup.cs selbst geschrieben werden sollen.

Mark Redman
quelle

Antworten:

171

.Net Core 3.1

Leider ist die Situation für ASP.NET Core 3.0 wieder etwas anders. Die Standardvorlagen verwenden den HostBuilder(anstelle des WebHostBuilder), der einen neuen generischen Host einrichtet, der mehrere verschiedene Anwendungen hosten kann, nicht nur Webanwendungen. Teil dieses neuen Hosts ist auch das Entfernen des zweiten Abhängigkeitsinjektionscontainers, der zuvor für den Webhost vorhanden war. Dies bedeutet letztendlich, dass Sie keine Abhängigkeiten außer der IConfigurationin die StartupKlasse einfügen können . Sie können sich also während der ConfigureServicesMethode nicht anmelden . Sie können den Logger jedoch in die ConfigureMethode einfügen und dort protokollieren:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILogger<Startup> logger)
{
    logger.LogInformation("Configure called");

    // …
}

Wenn Sie unbedingt brauchen innerhalb anmelden ConfigureServices, dann können Sie auch weiterhin die verwenden , WebHostBuilderdie das Vermächtnis wird erstellen , WebHostdie den Logger in die injizieren StartupKlasse. Beachten Sie, dass der Webhost wahrscheinlich zu einem späteren Zeitpunkt entfernt wird. Sie sollten also versuchen, eine Lösung zu finden, die für Sie funktioniert, ohne sich anmelden zu müssen ConfigureServices.


.NET Core 2.x.

Dies hat sich mit der Veröffentlichung von ASP.NET Core 2.0 erheblich geändert. In ASP.NET Core 2.x wird die Protokollierung beim Host Builder erstellt. Dies bedeutet, dass die Protokollierung standardmäßig über DI verfügbar ist und in die StartupKlasse eingefügt werden kann :

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

        // …
    }
}
Sack
quelle
4
DANKE. Es ist erstaunlich, wie viel Zeit Sie verbrennen können, um Antworten auf einfache Fragen zu finden. @poke Vielen Dank (noch einmal), dass Sie mich über meine Optionen informiert haben. Woher haben Sie diese Informationen? Ich habe bestätigt, dass ich Dinge in Configure protokollieren kann, was einem Stich (Wortspiel beabsichtigt) mit einem scharfen Stock vorzuziehen ist, aber vielleicht nicht so großartig, wie es bei ConfigureServices möglich ist. In meinem Fall möchte ich protokollieren, ob ich env-Einstellungen habe oder nicht, vielleicht sogar im Protokoll veröffentlichen. Kein Würfel? Seufz ... nicht sicher, warum das so schwer sein sollte. Aber zumindest weiß ich dank dieses Beitrags, was ich kann und was nicht.
Wellspring
2
@Wellspring In 3.0 ist es „schwer“, da ConfigureServicesder Logger zum Zeitpunkt der Ausführung noch nicht vorhanden ist. Sie können sich zu diesem Zeitpunkt also nicht einfach anmelden, da noch kein Protokollierer vorhanden ist. Auf der positiven Seite gibt Ihnen dies immer noch die Möglichkeit, den Logger innerhalb des zu konfigurieren, ConfigureServicesda es sich um denselben DI-Container handelt (was eigentlich eine gute Sache ist). - Wenn Sie unbedingt Daten protokollieren müssen, können Sie die Informationen beispielsweise separat sammeln (z. B. in einer Liste) und diese dann abmelden, sobald der Protokollierer verfügbar ist.
Poke
Verstanden, @poke - aber wenn Sie versuchen, etwas zu debuggen, das beim Start passiert (z. B. Umgebungsvariablen, die auf mysteriöse Weise nicht richtig eingehen ConfiguringServices?), Wäre es sicher schön, protokollieren zu können, was los ist! Übrigens habe ich das im Wesentlichen getan - Dinge nachträglich im ConfigureCode protokolliert .
Wellspring
@ Wellspring Ja, ich verstehe den Anwendungsfall vollkommen. Ich habe mich auch für eine Möglichkeit ausgesprochen, Dinge während des Startvorgangs zu protokollieren, aber leider gibt es keine wirklich gute Möglichkeit, dies zu tun, wenn Sie den Logger über DI konfigurieren möchten, aber auch mehrere DI-Container vermeiden möchten.
stupsen
1
@Aage Dies hat jedoch mehrere Nachteile: Sie müssen Ihre vollständige Protokollierungskonfiguration wiederholen, die Protokollierungskonfiguration spiegelt auch nicht Ihre Anwendungskonfiguration wider (z. B. in Appsettings usw. konfigurierte Protokollebenen), und Sie richten im Allgemeinen eine zweite Protokollierungsinfrastruktur ein. Ich würde Ihnen dennoch empfehlen, nach einer Lösung zu suchen, wie Sie die Protokollierung während des DI-Setups insgesamt vermeiden können.
stecken
36

Option 1: Verwenden Sie das Protokoll (z. B. Serilog) direkt beim Start.

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

Ausgabe:

Serilog

Informationen zum Einrichten von Serilog in der asp.net-core-Anwendung finden Sie im Serilog.AspNetCore-Paket auf GitHub .


Option 2: Konfigurieren Sie die Anmeldung in program.cs wie folgt:

var host = new WebHostBuilder()
            .UseKestrel()
            .ConfigureServices(s => {
                s.AddSingleton<IFormatter, LowercaseFormatter>();
            })
            .ConfigureLogging(f => f.AddConsole(LogLevel.Debug))
            .UseStartup<Startup>()
            .Build();

host.Run();

Benutzer loggerFactory beim Start wie folgt:

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

Vollständige Details finden Sie unter diesem Link

Sanket
quelle
5

Für .NET Core 3.0 haben die offiziellen Dokumente Folgendes zu sagen: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0#create-logs-in-the- Startklasse

Das Schreiben von Protokollen vor Abschluss des DI-Container-Setups in der Startup.ConfigureServices-Methode wird nicht unterstützt:

  • Die Logger-Injection in den Startup-Konstruktor wird nicht unterstützt.
  • Die Logger-Injektion in die Signatur der Startup.ConfigureServices-Methode wird nicht unterstützt

Aber wie in den Dokumenten angegeben, können Sie einen Dienst konfigurieren, der von ILogger abhängt. Wenn Sie also eine Klasse StartupLogger geschrieben haben:

public class StartupLogger
{
    private readonly ILogger _logger;

    public StartupLogger(ILogger<StartupLogger> logger)
    {
        _logger = logger;
    }

    public void Log(string message)
    {
        _logger.LogInformation(message);
    }
}

Fügen Sie dann in Startup.ConfigureServices den Dienst hinzu. Anschließend müssen Sie den Dienstanbieter erstellen, um Zugriff auf den DI-Container zu erhalten:

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

Bearbeiten: Dies erzeugt eine Compiler-Warnung. Zum Debuggen Ihrer StartUp-Klasse sollte dies in Ordnung sein, jedoch nicht für die Produktion:

  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'.
Dylan Munyard
quelle
4

Ich verwende eine Lösung, die verhindert, dass Logger von Drittanbietern einen "Logger-Puffer" mit ILogger- Schnittstelle implementieren.

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

Die Verwendung in startup.cs ist einfach. Natürlich erhalten Sie nach dem Aufruf von Configure eine Protokollausgabe. Aber besser als nichts. ::

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
quelle
3

Gemäß .net Core 3.1 können Sie einen Logger direkt mit LogFactory erstellen.

var loggerFactory = LoggerFactory.Create(builder =>
{
     builder.AddConsole();                
});

ILogger logger = loggerFactory.CreateLogger<Startup>();
logger.LogInformation("Example log message");
Liang
quelle
3

Die offizielle Lösung besteht derzeit darin, eine lokale LoggerFactory wie folgt einzurichten:

    using var loggerFactory = LoggerFactory.Create(builder =>
    {
        builder.SetMinimumLevel(LogLevel.Information);
        builder.AddConsole();
        builder.AddEventSourceLogger();
    });
    var logger = loggerFactory.CreateLogger("Startup");
    logger.LogInformation("Hello World");

Siehe auch: https://github.com/dotnet/aspnetcore/issues/9337#issuecomment-539859667

Rolf Kristensen
quelle
1

Haupt 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 richtet einen Standard-Konsolenlogger ein.

... konfiguriert die ILoggerFactory so, dass sie sich an der Konsole anmeldet und die Ausgabe debuggt

Startcode:

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

Ich konnte die Injektion eines ILoggers nicht zum Laufen bringen, aber vielleicht liegt das daran, dass es kein Controller ist. Weitere Infos willkommen!

Refs:

Tim Abell
quelle
0

Keine der oben genannten Antworten hat bei mir funktioniert. Ich verwende NLog und erstelle sogar eine neue ServiceCollection, rufe .CreateBuilder () für eine beliebige Service-Sammlung auf, erstelle einen Protokollierungsdienst ... nichts davon würde während ConfigureServices in eine Protokolldatei schreiben.

Das Problem ist, dass die Protokollierung erst nach dem Erstellen der SericeCollection erfolgt und erst während ConfigureServices erstellt wird.

Grundsätzlich möchte (muss) ich nur protokollieren, was während des Starts in einer Konfigurationserweiterungsmethode vor sich geht, da die einzige Ebene, auf der ich ein Problem habe, PROD ist, bei der ich keinen Debugger anhängen kann.

Die Lösung, die für mich funktioniert hat, war die Verwendung der alten .NET Framework NLog-Methode: Dieses private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); Recht wurde der Erweiterungsmethodenklasse hinzugefügt, und ich konnte während ConfigureServices und danach in ein Protokoll ("das" Protokoll) schreiben.

Ich habe keine Ahnung, ob dies eine gute Idee ist, um tatsächlich in Produktionscode zu veröffentlichen (ich weiß nicht, ob der .NET-gesteuerte ILogger und dieser NLog.ILogger zu irgendeinem Zeitpunkt Konflikte verursachen werden), aber ich brauchte ihn nur, um zu sehen, was los war auf.

emery.noel
quelle
-1

Ich habe es geschafft, indem ich statisch einen Logger mit Nlog in der Datei erstellt und diesen dann innerhalb der Startmethoden verwendet habe.

private readonly NLog.Logger _logger = new NLog.LogFactory().GetCurrentClassLogger();
Mark Redman
quelle
1
Während dies funktioniert, würde ich empfehlen, die Standardschnittstellen, die mit ASP.NET Core und Dependency Injection (DI) geliefert werden - entweder integriert oder von Drittanbietern - für die Protokollierung zu verwenden. Durch die Verwendung von Schnittstellen können Sie a) die bereitgestellte Protokollierung bei Bedarf ersetzen und b) sie leichter verspotten, wenn Sie Klassen (z. B. Controller) testen, in die Sie ILogger <TController> als Dienst einfügen.
Manfred
Ich habe das gerade zum ersten Mal gesehen, nachdem ich meine eigene Antwort auf genau das Gleiche veröffentlicht hatte. @Manfred gibt es einfach keinen anderen Weg, der funktioniert. Ich denke anders als System.IO.File.Write()Methoden.
emery.noel
-2

Verwenden Sie einfach die folgende Zeile, um sich bei Startup.cs anzumelden

Log.Information("App started.");
Imran Shabbir
quelle