Comment écrire des journaux à partir de Startup.cs


109

Afin de déboguer une application principale .net qui échoue au démarrage, j'aimerais écrire des journaux à partir du fichier startup.cs. J'ai une configuration de journalisation dans le fichier qui peut être utilisée dans le reste de l'application en dehors du fichier startup.cs, mais je ne sais pas comment écrire des journaux à partir du fichier startup.cs lui-même.

Réponses:


175

.Net Core 3.1

Malheureusement, pour ASP.NET Core 3.0, la situation est encore un peu différente. Les modèles par défaut utilisent le HostBuilder(au lieu du WebHostBuilder) qui configure un nouvel hôte générique pouvant héberger plusieurs applications différentes, sans se limiter aux applications Web. Une partie de ce nouvel hôte est également la suppression du deuxième conteneur d'injection de dépendances qui existait auparavant pour l'hôte Web. Cela signifie finalement que vous ne pourrez pas injecter de dépendances en dehors de IConfigurationdans la Startupclasse. Vous ne pourrez donc pas vous connecter pendant la ConfigureServicesméthode. Vous pouvez cependant injecter le logger dans la Configureméthode et vous y connecter:

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

    // …
}

Si vous devez absolument vous connecter ConfigureServices, vous pouvez continuer à utiliser le WebHostBuilderqui créera l'héritage WebHostqui peut injecter le logger dans la Startupclasse. Notez qu'il est probable que l'hôte Web sera supprimé à un moment donné dans le futur. Vous devriez donc essayer de trouver une solution qui fonctionne pour vous sans avoir à vous connecter ConfigureServices.


.NET Core 2.x

Cela a considérablement changé avec la publication d'ASP.NET Core 2.0. Dans ASP.NET Core 2.x, la journalisation est créée au niveau du générateur d'hôte. Cela signifie que la journalisation est disponible via DI par défaut et peut être injectée dans la Startupclasse:

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

        // …
    }
}

4
MERCI. Le temps que vous pouvez consacrer à la recherche de réponses à des questions simples est incroyable. @poke merci (encore) de m'avoir informé de mes options. Où avez-vous obtenu cette information? J'ai confirmé que je pouvais enregistrer des choses dans Configure, ce qui est préférable à un coup de poing (jeu de mots) dans l'œil avec un bâton pointu, mais peut-être pas aussi génial que de pouvoir le faire pendant ConfigureServices. Dans mon cas, j'aimerais enregistrer si j'ai ou non les paramètres d'environnement, peut-être même les publier dans le journal. Pas de dé? Soupir ... je ne sais pas pourquoi cela devrait être si difficile. Mais au moins, grâce à ce post, je sais ce que je peux et ne peux pas faire.
Wellspring

2
@Wellspring Dans la version 3.0, c'est «difficile» car au moment où il ConfigureServicess'exécute, l'enregistreur n'existe pas encore. Vous ne pourrez donc pas vous connecter à ce stade simplement parce qu'il n'y a pas encore d'enregistreur. Du côté positif, cela vous donne toujours la possibilité de configurer l'enregistreur dans le ConfigureServicescar il s'agit du même conteneur DI (ce qui est en fait une bonne chose). - Si vous avez absolument besoin de consigner des données, vous pouvez par exemple collecter les informations séparément (par exemple dans une liste), puis vous déconnecter dès que l'enregistreur est disponible.
poke le

Au sein .NET 3.1vous pouvez actuellement vous connecter au sein de la ConfigureServicesméthode sans retomber sur le WebHostBuilder. Utilisez la réponse ci-dessous: stackoverflow.com/a/61488490/2877982
Aage

1
@Aage Cela aura cependant plusieurs inconvénients: Vous devrez répéter votre configuration de journalisation complète, la configuration de journalisation ne reflétera pas non plus la configuration de votre application (par exemple, les niveaux de journalisation configurés dans les paramètres d'applications, etc.), et vous configurez généralement une deuxième infrastructure de journalisation. Je vous suggérerais toujours de rechercher une solution pour éviter de vous connecter pendant la configuration DI.
piquer le

@poke je n'avais pas pensé à ça. Vraiment, je veux juste une construction de contrôleur explicite câblée dans mon Startup.cs(donc j'obtiens des erreurs de compilateur en oubliant une dépendance) au lieu d' enregistrer uniquement des dépendances personnalisées. Par conséquent, je dois résoudre ces enregistreurs. Mais cela pourrait être un peu piraté, oui.
Aage

37

Option 1: utiliser directement le journal (par exemple Serilog) au démarrage-

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

Production:

serilog

Pour configurer Serilog dans l'application asp.net-core, consultez le package Serilog.AspNetCore sur GitHub .


Option2: Configurez la journalisation dans program.cs comme ceci-

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

host.Run();

User loggerFactory au démarrage comme ceci-

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

Détails complets disponibles sur ce lien


4

J'utilise une solution évitant les enregistreurs tiers implémentant un «tampon enregistreur» avec l' interface ILogger .

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

L'utilisation dans startup.cs est facile, bien sûr, vous obtenez une sortie de journal après l'appel de Configure. Mais mieux que rien. :

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

4

Pour .NET Core 3.0, la documentation officielle indique ceci: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0#create-logs-in-startup

L'écriture de journaux avant la fin de la configuration du conteneur DI dans la Startup.ConfigureServicesméthode n'est pas prise en charge:

  • L'injection de l'enregistreur dans le Startupconstructeur n'est pas prise en charge.
  • L'injection d'enregistreur dans la Startup.ConfigureServicessignature de méthode n'est pas prise en charge

Mais comme on dit dans la documentation, vous pouvez configurer un service qui dépend d'ILogger, donc si vous avez écrit une classe StartupLogger:

public class StartupLogger
{
    private readonly ILogger _logger;

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

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

Ensuite, dans Startup.ConfigureServices, ajoutez le service, puis vous devez créer le fournisseur de services pour accéder au conteneur DI:

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: cela produit un avertissement du compilateur, dans le but de déboguer votre classe StartUp cela devrait être OK mais pas pour la 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'.

3

Selon .net core 3.1 , vous pouvez créer un enregistreur directement à l'aide de LogFactory.

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

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


1

Code principal:

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 configure un enregistreur de console par défaut.

... configure ILoggerFactory pour se connecter à la console et déboguer la sortie

Code de démarrage:

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

Je n'ai pas pu faire fonctionner l'injection d'un ILogger, mais c'est peut-être parce que ce n'est pas un contrôleur. Plus d'informations bienvenue!

Réf:


0

Aucune des réponses ci-dessus n'a fonctionné pour moi. J'utilise NLog et je construis même un nouveau ServiceCollection, en appelant .CreateBuilder () sur n'importe quelle collection de services, en créant un service de journalisation ... rien de tout cela n'écrirait dans un fichier journal pendant ConfigureServices.

Le problème est que la journalisation n'est vraiment une chose qu'après la génération de ServiceCollection, et elle n'est pas générée pendant ConfigureServices.

Fondamentalement, je veux juste (besoin) d'enregistrer ce qui se passe au démarrage dans une méthode d'extension de configuration, car le seul niveau sur lequel j'ai un problème est PROD, où je ne peux pas attacher un débogueur.

La solution qui a fonctionné pour moi utilisait l'ancienne méthode NLog .NET Framework: private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); ajouté ce droit à la classe de méthode d'extension, et j'ai pu écrire dans un journal ("le" journal) pendant ConfigureServices et après.

Je n'ai aucune idée si c'est une bonne idée de publier dans le code de production (je ne sais pas si l'ILogger contrôlé par .NET et ce NLog.ILogger seront en conflit à tout moment), mais j'en avais besoin seulement pour voir ce qui se passait sur.


-1

J'ai réussi à le faire en créant statiquement un enregistreur avec Nlog dans le fichier, puis en l'utilisant dans les méthodes de démarrage.

private readonly NLog.Logger _logger = new NLog.LogFactory().GetCurrentClassLogger();

1
Bien que cela fonctionne, je vous recommande d'utiliser les interfaces standard fournies avec ASP.NET Core et l'injection de dépendances (DI) - intégrées ou tierces - pour la journalisation. En utilisant des interfaces, vous pouvez a) remplacer la journalisation fournie si nécessaire et b) elles sont plus faciles à simuler lorsque vous testez des classes (par exemple des contrôleurs) où vous injectez ILogger <TController> en tant que service.
Manfred

Je viens de voir cela pour la première fois après avoir publié ma propre réponse sur exactement la même chose. @Manfred il n'y a tout simplement pas d'autre moyen qui fonctionne. Je suppose que d'autres System.IO.File.Write()méthodes.
emery.noel le

-2

Utilisez simplement la ligne ci-dessous pour vous connecter à Startup.cs

Log.Information("App started.");
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.