Como escrevo logs de Startup.cs

109

Para depurar um aplicativo principal .net que está falhando na inicialização, gostaria de escrever logs de dentro do arquivo startup.cs. Tenho uma configuração de registro dentro do arquivo que pode ser usada no restante do aplicativo fora do arquivo startup.cs, mas não tenho certeza de como escrever registros de dentro do próprio arquivo startup.cs.

Mark Redman
fonte

Respostas:

175

.Net Core 3.1

Infelizmente, para ASP.NET Core 3.0, a situação é um pouco diferente. Os modelos padrão usam o HostBuilder(em vez do WebHostBuilder), que configura um novo host genérico que pode hospedar vários aplicativos diferentes, não se limitando a aplicativos da web. Parte desse novo host também é a remoção do segundo contêiner de injeção de dependência que existia anteriormente para o host da web. Em última análise, isso significa que você não poderá injetar quaisquer dependências além do IConfigurationna Startupclasse. Portanto, você não poderá registrar durante o ConfigureServicesmétodo. Você pode, no entanto, injetar o logger no Configuremétodo e registrar lá:

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

    // …
}

Se você realmente precisa fazer login ConfigureServices, pode continuar a usar o WebHostBuilderque criará o legado WebHostque pode injetar o logger na Startupclasse. Observe que é provável que o host da web seja removido em algum momento no futuro. Portanto, você deve tentar encontrar uma solução que funcione para você, sem ter que fazer login ConfigureServices.


.NET Core 2.x

Isso mudou significativamente com o lançamento do ASP.NET Core 2.0. No ASP.NET Core 2.x, o log é criado no construtor de host. Isso significa que o registro está disponível por meio de DI por padrão e pode ser injetado na 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");

        // …
    }
}
cutucar
fonte
4
OBRIGADO. É incrível quanto tempo você pode gastar procurando respostas para perguntas simples. @poke, obrigado (novamente) por me informar quais são minhas opções. Onde você conseguiu essa informação? Eu confirmei que posso registrar coisas no Configure, o que é preferível a uma cutucada (trocadilho intencional) no olho com uma vara afiada, mas talvez não tão incrível quanto conseguir durante os ConfigureServices. No meu caso, gostaria de registrar se tenho ou não configurações de env, talvez até mesmo postá-las no registro. Sem dados? Sigh ... não tenho certeza por que isso deve ser tão difícil. Mas pelo menos, graças a este post, sei o que posso e o que não posso fazer.
Wellspring
2
@Wellspring No 3.0, é “difícil” porque no momento em que ConfigureServiceso logger é executado, o logger ainda não existe. Portanto, você não poderá registrar nesse ponto simplesmente porque ainda não há um registrador. No lado positivo, isso ainda dá a você a capacidade de configurar o logger dentro do, ConfigureServicesjá que é o mesmo contêiner de DI (o que é realmente uma coisa boa). - Se você absolutamente precisa registrar coisas, pode, por exemplo, coletar as informações separadamente (por exemplo, em uma lista) e, em seguida, desconectar assim que o registrador estiver disponível.
cutucar
No .NET 3.1momento, você PODE registrar-se no ConfigureServicesmétodo sem recorrer ao WebHostBuilder. Use a resposta abaixo: stackoverflow.com/a/61488490/2877982
Aage
1
@Aage Isso terá várias desvantagens: você terá que repetir sua configuração de registro completa, a configuração de registro também não refletirá a configuração do seu aplicativo (por exemplo, níveis de registro configurados em appsettings etc.) e você geralmente está configurando uma segunda infraestrutura de registro. Ainda assim, sugiro que você procure uma solução para evitar o registro durante a configuração do DI lá.
cutucar
@poke Eu não tinha pensado nisso. Na verdade, eu só quero a construção de um controlador explícito conectado ao meu Startup.cs(então recebo erros do compilador ao esquecer uma dependência) em vez de apenas registrar dependências personalizadas. Portanto, preciso resolver esses loggers. Mas isso pode ser um pouco hackeado, sim.
Aage
37

Opção 1: usar diretamente o log (por exemplo, Serilog) na inicialização-

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

Resultado:

serilog

Para configurar o Serilog no aplicativo asp.net-core, verifique o pacote Serilog.AspNetCore no GitHub .


Opção 2: configurar o login em program.cs como este-

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

host.Run();

LoggerFactory do usuário na inicialização assim-

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

Detalhes completos disponíveis neste link

Sanket
fonte
4

Eu uso uma solução para evitar que loggers de terceiros implementem um "logger buffer" com 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();
    }
}

O uso no startup.cs é fácil, é claro que você obtém a saída do log após chamar Configure. Mas melhor do que nada. :

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
fonte
4

Para .NET Core 3.0, a documentação oficial diz o seguinte: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0#create-logs-in-startup

A gravação de logs antes da conclusão da configuração do contêiner DI no Startup.ConfigureServicesmétodo não é compatível:

  • A injeção de logger no Startupconstrutor não é suportada.
  • A injeção de logger na Startup.ConfigureServicesassinatura do método não é suportada

Mas, como dizem nos documentos, você pode configurar um serviço que depende do ILogger, então se você escreveu uma classe StartupLogger:

public class StartupLogger
{
    private readonly ILogger _logger;

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

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

Em seguida, em Startup.ConfigureServices adicione o serviço, então você precisa construir o provedor de serviços para obter acesso ao contêiner 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");
}

Editar: isso produz um aviso do compilador, para depurar sua classe StartUp, isso deve estar OK, mas não para produção:

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

De acordo com o .net core 3.1 , você pode criar um logger diretamente usando LogFactory.

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

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

A solução oficial atualmente é configurar uma LoggerFactory local como esta:

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

Veja também: https://github.com/dotnet/aspnetcore/issues/9337#issuecomment-539859667

Rolf Kristensen
fonte
1

Código 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 configura um registrador de console padrão.

... configura o ILoggerFactory para registrar no console e depurar a saída

Código de inicialização:

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

Não consegui fazer a injeção de um ILogger funcionar, mas talvez seja porque não é um controlador. Mais informações bem-vindos!

Refs:

Tim Abell
fonte
0

Nenhuma das respostas acima funcionou para mim. Estou usando o NLog e até mesmo construindo um novo ServiceCollection, chamando .CreateBuilder () em qualquer coleção de serviço, criando um serviço de registro ... nada disso gravaria em um arquivo de registro durante ConfigureServices.

O problema é que o log não é realmente uma coisa até depois que o ServiceCollection é construído, e não é construído durante ConfigureServices.

Basicamente, eu só quero (preciso) registrar o que está acontecendo durante a inicialização em um método de extensão de configuração, porque a única camada na qual estou tendo problemas é o PROD, onde não consigo anexar um depurador.

A solução que funcionou para mim foi usar o antigo método .NET Framework NLog: private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); adicionado esse direito à classe do método de extensão e consegui gravar em um log ("o" log) durante ConfigureServices e depois.

Não tenho ideia se essa é uma boa ideia realmente lançar no código de produção (não sei se o ILogger controlado por .NET e este NLog.ILogger entrarão em conflito em algum ponto), mas eu só precisava disso para ver o que estava acontecendo em.

emery.noel
fonte
-1

Consegui fazer isso criando estaticamente um logger com Nlog no arquivo e, em seguida, usá-lo nos métodos de inicialização.

private readonly NLog.Logger _logger = new NLog.LogFactory().GetCurrentClassLogger();
Mark Redman
fonte
1
Enquanto isso funciona, eu recomendo usar as interfaces padrão que vêm com o ASP.NET Core e injeção de dependência (DI) - tanto interna quanto de terceiros - para registro. Ao usar interfaces, você pode a) substituir o registro fornecido se necessário eb) eles são mais fáceis de simular quando você testa classes (por exemplo, controladores) onde você injeta ILogger <TController> como um serviço.
Manfred
Acabei de ver isso pela primeira vez depois de postar minha própria resposta exatamente a mesma coisa. @Manfred simplesmente não há outra maneira que funcione. Acho que outros além dos System.IO.File.Write()métodos.
emery.noel
-2

Basta usar a linha abaixo para fazer login no Startup.cs

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