Jak pisać dzienniki z poziomu Startup.cs

119

W celu debugowania podstawowej aplikacji .net, która nie działa podczas uruchamiania, chciałbym zapisywać dzienniki z pliku startup.cs. Mam konfigurację rejestrowania w pliku, który może być używany w pozostałej części aplikacji poza plikiem startup.cs, ale nie wiem, jak zapisywać dzienniki z samego pliku startup.cs.

Mark Redman
źródło

Odpowiedzi:

186

.Net Core 3.1

Niestety w przypadku ASP.NET Core 3.0 sytuacja jest nieco inna. Domyślne szablony używają HostBuilder(zamiast WebHostBuilder), które konfiguruje nowy ogólny host, który może obsługiwać kilka różnych aplikacji, nie ograniczając się do aplikacji internetowych. Częścią tego nowego hosta jest również usunięcie drugiego kontenera iniekcji zależności, który wcześniej istniał dla hosta internetowego. Ostatecznie oznacza to, że nie będziesz w stanie wstrzyknąć IConfigurationdo Startupklasy żadnych zależności poza . Więc nie będziesz mógł zalogować się podczas ConfigureServicesmetody. Możesz jednak wstrzyknąć rejestrator do Configuremetody i zalogować się tam:

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

    // …
}

Jeśli absolutnie musisz się zalogować ConfigureServices, możesz nadal używać programu, WebHostBuilderktóry utworzy starszą wersję, WebHostktóra może wstrzyknąć rejestrator do Startupklasy. Pamiętaj, że jest prawdopodobne, że usługodawca hostingowy zostanie usunięty w pewnym momencie w przyszłości. Dlatego powinieneś spróbować znaleźć rozwiązanie, które będzie dla Ciebie odpowiednie, bez konieczności logowania się ConfigureServices.


.NET Core 2.x

Zmieniło się to znacznie wraz z wydaniem ASP.NET Core 2,0. W ASP.NET Core 2.x rejestrowanie jest tworzone w konstruktorze hosta. Oznacza to, że rejestrowanie jest domyślnie dostępne przez DI i można je wstrzyknąć do Startupklasy:

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

        // …
    }
}
szturchać
źródło
4
DZIĘKUJĘ CI. To niesamowite, ile czasu można poświęcić na szukanie odpowiedzi na proste pytania. @poke dziękuję (jeszcze raz) za poinformowanie mnie o moich opcjach. Skąd masz te informacje? Potwierdziłem, że mogę rejestrować rzeczy w Configure, co jest lepsze niż szturchanie (gra słów zamierzona) w oko ostrym kijem, ale być może nie tak niesamowite, jak w przypadku ConfigureServices. W moim przypadku chciałbym zarejestrować, czy mam ustawienia środowiska, może nawet opublikować je w dzienniku. Nie ma kości? Ech ... nie wiem, dlaczego to powinno być takie trudne. Ale przynajmniej dzięki temu wpisowi wiem, co mogę, a czego nie.
Wellspring
3
@Wellspring W wersji 3.0 jest to „trudne”, ponieważ do czasu ConfigureServicesuruchomienia rejestrator jeszcze nie istnieje. Więc nie będziesz mógł zalogować się w tym momencie tylko dlatego, że nie ma jeszcze rejestratora. Plusem jest to, że nadal daje to możliwość skonfigurowania rejestratora w ramach, ConfigureServicesponieważ jest to ten sam kontener DI (co jest w rzeczywistości dobrą rzeczą). - Jeśli absolutnie potrzebujesz rejestrować dane, możesz na przykład zebrać informacje oddzielnie (np. W postaci listy), a następnie wylogować się, gdy tylko rejestrator będzie dostępny.
poke
W ciągu .NET 3.1obecnie mogą logować się w ConfigureServicesmetodzie bez upadku na plecy WebHostBuilder. Użyj odpowiedzi poniżej: stackoverflow.com/a/61488490/2877982
Aage,
2
@Aage Będzie to miało jednak kilka wad: będziesz musiał powtórzyć pełną konfigurację rejestrowania, konfiguracja rejestrowania również nie będzie odzwierciedlać konfiguracji aplikacji (np. Poziomy dzienników skonfigurowane w appsettings itp.), A zazwyczaj konfigurujesz drugą infrastrukturę rejestrowania. Nadal sugerowałbym, abyś poszukał rozwiązania, w jaki sposób możesz całkowicie uniknąć logowania podczas konfiguracji DI.
szturchnij
@poke Nie myślałem o tym. Naprawdę chcę tylko jawnej konstrukcji kontrolera podłączonej do mojego Startup.cs(więc otrzymuję błędy kompilatora, gdy zapominam o zależności), zamiast rejestrować tylko niestandardowe zależności. Dlatego muszę rozwiązać te rejestratory. Ale to może być trochę hakerskie, tak.
Aage
37

Opcja 1: Bezpośrednio użyj dziennika (np. Serilog) podczas uruchamiania

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

Wynik:

serilog

Aby skonfigurować Serilog w aplikacji asp.net-core, sprawdź pakiet Serilog.AspNetCore w serwisie GitHub .


Opcja 2: Skonfiguruj logowanie w program.cs w ten sposób-

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

host.Run();

Rejestrator użytkownika Fabryka w starcie w ten sposób-

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

Pełne informacje dostępne pod tym linkiem

Sanket
źródło
7

Zgodnie z .net core 3.1 , możesz utworzyć rejestrator bezpośrednio przy użyciu LogFactory.

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

ILogger logger = loggerFactory.CreateLogger<Startup>();
logger.LogInformation("Example log message");
Liang
źródło
W przypadku log4net sprowadza się to do ILogger logger = LoggerFactory.Create(builder => builder.AddLog4Net()).CreateLogger<Startup>();. Jeśli jednak rejestrujesz tylko wyjątki, nie ujawni to nic więcej niż to, co już pojawia się w dzienniku zdarzeń systemu Windows (podczas korzystania z usług IIS).
Louis Somers
6

W przypadku .NET Core 3.0 oficjalna dokumentacja ma to do powiedzenia: https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0#create-logs-in-startup

Zapisywanie dzienników przed zakończeniem konfiguracji kontenera DI w Startup.ConfigureServicesmetodzie nie jest obsługiwane:

  • Wtryskiwanie rejestratora do Startupkonstruktora nie jest obsługiwane.
  • Wstrzykiwanie rejestratora do Startup.ConfigureServicessygnatury metody nie jest obsługiwane

Ale jak mówią w dokumentach, możesz skonfigurować usługę, która zależy od ILogger, więc jeśli napisałeś klasę StartupLogger:

public class StartupLogger
{
    private readonly ILogger _logger;

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

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

Następnie w Startup.ConfigureServices dodaj usługę, a następnie musisz zbudować dostawcę usług, aby uzyskać dostęp do kontenera 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");
}

Edycja: to generuje ostrzeżenie kompilatora, w celu debugowania klasy StartUp powinno to być OK, ale nie do produkcji:

Startup.cs (39, 32): [ASP0000] Wywołanie „BuildServiceProvider” z kodu aplikacji powoduje utworzenie dodatkowej kopii pojedynczych usług. Rozważ alternatywy, takie jak usługi wstrzykiwania zależności, jako parametry opcji „Konfiguruj”.

Dylan Munyard
źródło
5

Oficjalnym rozwiązaniem jest obecnie konfiguracja lokalnej LoggerFactory w następujący sposób:

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

Zobacz też: https://github.com/dotnet/aspnetcore/issues/9337#issuecomment-539859667

Rolf Kristensen
źródło
4

Używam rozwiązania unikającego loggerów firm trzecich implementujących "bufor loggera" z interfejsem 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();
    }
}

Użycie w startup.cs jest łatwe, oczywiście po wywołaniu Configure otrzymujesz dane wyjściowe dziennika. Ale lepsze niż nic. :

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
źródło
1

Kod główny:

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 konfiguruje domyślny rejestrator konsoli.

... konfiguruje ILoggerFactory tak, aby logował się do konsoli i debugował dane wyjściowe

Kod startowy:

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

Nie mogłem uruchomić wtrysku ILoggera, ale może to dlatego, że nie jest to kontroler. Więcej informacji zapraszamy!

Odniesienia:

Tim Abell
źródło
0

Żadna z powyższych odpowiedzi nie zadziałała dla mnie. Używam NLog, a nawet buduję nową usługę ServiceCollection, wywołuję .CreateBuilder () w dowolnej kolekcji usług, tworzę usługę rejestrowania ... nic z tego nie zapisuje w pliku dziennika podczas ConfigureServices.

Problem polega na tym, że rejestrowanie nie jest tak naprawdę rzeczą do czasu skompilowania ServiceCollection i nie jest budowane podczas ConfigureServices.

Zasadniczo chcę (muszę) rejestrować, co się dzieje podczas uruchamiania w metodzie rozszerzenia konfiguracji, ponieważ jedyną warstwą, z którą mam problem, jest PROD, gdzie nie mogę dołączyć debugera.

Rozwiązaniem, które zadziałało, było użycie starej metody .NET Framework NLog: private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger(); dodano to prawo do klasy metody rozszerzającej i mogłem zapisywać do dziennika („dziennika”) podczas ConfigureServices i później.

Nie mam pojęcia, czy to jest dobry pomysł, aby faktycznie udostępnić kod produkcyjny (nie wiem, czy ILogger kontrolowany. na.

emery.noel
źródło
-1

Udało mi się to zrobić, statycznie tworząc rejestrator z Nlog w pliku, a następnie wykorzystując to w ramach metod uruchamiania.

private readonly NLog.Logger _logger = new NLog.LogFactory().GetCurrentClassLogger();
Mark Redman
źródło
1
Chociaż to działa, zalecamy korzystanie ze standardowych interfejsów, które są dostarczane z ASP.NET Core i iniekcją zależności (DI) - wbudowaną lub zewnętrzną - do rejestrowania. Korzystając z interfejsów, możesz a) w razie potrzeby zamienić dostarczone protokoły logowania oraz b) łatwiej je zampować, gdy testujesz klasy (np. Kontrolery), w których wprowadzasz ILogger <TController> jako usługę.
Manfred,
Właśnie zobaczyłem to po raz pierwszy po opublikowaniu własnej odpowiedzi na dokładnie to samo. @Manfred po prostu nie ma innego sposobu, który działa. Chyba inne niż System.IO.File.Write()metody.
emery.noel
-2

Po prostu użyj poniższej linii, aby zalogować się do Startup.cs

Log.Information("App started.");
Imran Shabbir
źródło