¿Cómo escribo registros desde Startup.cs?

119

Para depurar una aplicación .NET Core que falla al iniciarse, me gustaría escribir registros desde el archivo startup.cs. Tengo una configuración de registro dentro del archivo que se puede usar en el resto de la aplicación fuera del archivo startup.cs, pero no estoy seguro de cómo escribir registros desde el archivo startup.cs.

Mark Redman
fuente

Respuestas:

187

.Net Core 3.1

Desafortunadamente, para ASP.NET Core 3.0, la situación es nuevamente un poco diferente. Las plantillas predeterminadas usan HostBuilder(en lugar de WebHostBuilder), que configura un nuevo host genérico que puede albergar varias aplicaciones diferentes, sin limitarse a aplicaciones web. Parte de este nuevo host es también la eliminación del segundo contenedor de inyección de dependencia que existía anteriormente para el host web. En última instancia, esto significa que no podrá inyectar ninguna dependencia aparte de IConfigurationen la Startupclase. Por lo tanto, no podrá iniciar sesión durante el ConfigureServicesmétodo. Sin embargo, puede inyectar el registrador en el Configuremétodo e iniciar sesión allí:

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

    // …
}

Si es absolutamente necesario iniciar sesión ConfigureServices, puede continuar usando el WebHostBuilderque creará el legado WebHostque puede inyectar el registrador en la Startupclase. Tenga en cuenta que es probable que el servidor web se elimine en algún momento en el futuro. Por lo tanto, debe intentar encontrar una solución que funcione para usted sin tener que iniciar sesión ConfigureServices.


.NET Core 2.x

Esto ha cambiado significativamente con el lanzamiento de ASP.NET Core 2.0. En ASP.NET Core 2.x, el registro se crea en el generador de host. Esto significa que el registro está disponible a través de DI de forma predeterminada y se puede inyectar en la Startupclase:

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

        // …
    }
}
dar un toque
fuente
4
GRACIAS. Es asombroso cuánto tiempo puede gastar buscando respuestas a preguntas simples. @poke gracias (de nuevo) por informarme cuáles son mis opciones. ¿De dónde sacaste esta información? He confirmado que puedo registrar cosas en Configure, que es preferible a un golpe (juego de palabras) en el ojo con un palo afilado, pero tal vez no sea tan impresionante como poder durante ConfigureServices. En mi caso, me gustaría registrar si obtuve o no configuraciones de env, tal vez incluso publicarlas en el registro. ¿No dados? Suspiro ... no estoy seguro de por qué esto debería ser tan difícil. Pero al menos, gracias a esta publicación, sé lo que puedo y no puedo hacer.
Wellspring
3
@Wellspring En 3.0, es "difícil" porque cuando se ConfigureServicesejecuta, el registrador todavía no existe. Por lo tanto, no podrá iniciar sesión en ese momento simplemente porque aún no hay un registrador. En el lado positivo, eso todavía le da la capacidad de configurar el registrador dentro del ConfigureServicesya que es el mismo contenedor DI (lo cual es realmente algo bueno). - Si es absolutamente necesario registrar cosas, por ejemplo, puede recopilar la información por separado (por ejemplo, en una lista) y luego cerrar la sesión tan pronto como el registrador esté disponible.
empuje el
Dentro de .NET 3.1usted actualmente PUEDE iniciar sesión dentro del ConfigureServicesmétodo sin recurrir al WebHostBuilder. Utilice la respuesta a continuación: stackoverflow.com/a/61488490/2877982
Edad
2
@Aage Sin embargo, esto tendrá varias desventajas: tendrá que repetir su configuración de registro completa, la configuración de registro tampoco reflejará la configuración de su aplicación (por ejemplo, los niveles de registro configurados en la configuración de la aplicación, etc.), y generalmente está configurando una segunda infraestructura de registro. Aún así, le sugiero que busque una solución sobre cómo evitar el registro durante la configuración de DI por completo allí.
empuje el
@poke No había pensado en esto. Realmente solo quiero una construcción de controlador explícita conectada en mi Startup.cs(por lo que obtengo errores de compilador cuando olvido una dependencia) en lugar de solo registrar dependencias personalizadas. Por lo tanto, necesito resolver estos registradores. Pero esto podría ser un poco complicado, sí.
Edad
37

Opción 1: utilizar directamente el registro (por ejemplo, Serilog) en el inicio

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

Salida:

serilog

Para configurar Serilog en la aplicación asp.net-core, consulte el paquete Serilog.AspNetCore en GitHub .


Opción 2: configurar el inicio de sesión en program.cs de esta manera-

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

host.Run();

User loggerFactory en inicio como este-

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

Detalles completos disponibles en este enlace

Sanket
fuente
7

En .NET Core 3.1 , puede crear un registrador directamente usando LogFactory.

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

ILogger logger = loggerFactory.CreateLogger<Startup>();
logger.LogInformation("Example log message");
Liang
fuente
Para log4net se reduce a ILogger logger = LoggerFactory.Create(builder => builder.AddLog4Net()).CreateLogger<Startup>();. Sin embargo, si solo registra excepciones, no revelará mucho más de lo que ya se muestra en el EventLog de Windows (cuando se usa IIS).
Louis Somers
6

Para .NET Core 3.0, los documentos oficiales tienen esto que decir: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0#create-logs-in-startup

Startup.ConfigureServicesNo se admite la escritura de registros antes de completar la configuración del contenedor DI en el método:

  • StartupNo se admite la inyección de registrador en el constructor.
  • Startup.ConfigureServicesNo se admite la inyección del registrador en la firma del método

Pero como dicen en los documentos, puede configurar un servicio que depende de ILogger, por lo que si escribió una clase StartupLogger:

public class StartupLogger
{
    private readonly ILogger _logger;

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

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

Luego, en Startup.ConfigureServices agregue el servicio, luego necesita construir el proveedor de servicios para obtener acceso al contenedor 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: esto produce una advertencia del compilador, en aras de depurar su clase de inicio, esto debería estar bien, pero no para producción:

Startup.cs (39, 32): [ASP0000] Al llamar a 'BuildServiceProvider' desde el código de la aplicación, se crea una copia adicional de los servicios singleton. Considere alternativas como servicios de inyección de dependencia como parámetros para 'Configurar'.

Dylan Munyard
fuente
5

La solución oficial actualmente es configurar una 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");

Véase también: https://github.com/dotnet/aspnetcore/issues/9337#issuecomment-539859667

Rolf Kristensen
fuente
4

Utilizo una solución que evita que los registradores de terceros implementen un "búfer de registrador" con la interfaz 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();
    }
}

El uso en startup.cs es fácil, por supuesto, obtiene una salida de registro después de llamar a Configure. Pero mejor 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
fuente
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 un registrador de consola predeterminado.

... configura ILoggerFactory para iniciar sesión en la consola y depurar la salida

Código de inicio:

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

No pude hacer funcionar la inyección de un ILogger, pero tal vez sea porque no es un controlador. ¡Más información bienvenido!

Refs:

Tim Abell
fuente
0

Ninguna de las respuestas anteriores funcionó para mí. Estoy usando NLog, e incluso construyendo un nuevo ServiceCollection, llamando a .CreateBuilder () en cualquier colección de servicios, creando un servicio de registro ... nada de eso escribiría en un archivo de registro durante ConfigureServices.

El problema es que el registro no existe realmente hasta después de que se construye ServiceCollection, y no se construye durante ConfigureServices.

Básicamente, solo quiero (necesito) registrar lo que está sucediendo durante el inicio en un método de extensión de configuración, porque el único nivel en el que tengo un problema es PROD, donde no puedo adjuntar un depurador.

La solución que funcionó para mí fue usar el antiguo método .NET Framework NLog: agregué private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); ese derecho a la clase de método de extensión y pude escribir en un registro ("el" registro) durante ConfigureServices y después.

No tengo idea si esta es una buena idea para publicarla en el código de producción (no sé si el ILogger controlado por .NET y este NLog.ILogger entrarán en conflicto en algún momento), pero solo lo necesitaba para ver qué estaba pasando. en.

emery.noel
fuente
-1

Logré hacer esto creando estáticamente un registrador con Nlog en el archivo, luego lo uso dentro de los métodos de inicio.

private readonly NLog.Logger _logger = new NLog.LogFactory().GetCurrentClassLogger();
Mark Redman
fuente
1
Si bien esto funciona, recomendaría usar las interfaces estándar que vienen con ASP.NET Core y la inyección de dependencia (DI), ya sea integradas o de terceros, para el registro. Mediante el uso de interfaces, puede a) reemplazar el registro proporcionado si es necesario yb) son más fáciles de simular cuando prueba clases (por ejemplo, controladores) donde inyecta ILogger <TController> como un servicio.
Manfred
Acabo de ver esto por primera vez después de publicar mi propia respuesta de exactamente lo mismo. @Manfred simplemente no hay otra forma que funcione. Supongo que aparte de los System.IO.File.Write()métodos.
emery.noel
-2

Simplemente use la línea a continuación para iniciar sesión en Startup.cs

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