簡體   English   中英

如何從 Startup.cs 中寫入日志?

[英]How do I write logs from within Startup.cs?

為了調試在啟動時失敗的 .NET Core 應用程序,我想從 startup.cs 文件中寫入日志。 我在文件中有日志記錄設置,可以在 startup.cs 文件之外的應用程序的其余部分中使用,但不確定如何從 startup.cs 文件本身寫入日志。

.Net 核心 3.1

不幸的是,對於 ASP.NET Core 3.0,情況又有點不同。 默認模板使用HostBuilder (而不是WebHostBuilder ),它設置了一個新的通用主機,可以托管多個不同的應用程序,不限於 Web 應用程序。 這個新主機的一部分還刪除了以前為 Web 主機存在的第二個依賴注入容器。 這最終意味着您將無法將除IConfiguration之外的任何依賴項注入Startup類。 因此,您將無法在ConfigureServices方法期間進行記錄。 但是,您可以將記錄器注入到Configure方法中並在那里記錄:

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

    // …
}

如果您絕對需要ConfigureServices中登錄,那么您可以繼續使用WebHostBuilder ,它將創建可以將記錄器注入Startup類的舊版WebHost 請注意,Web 主機可能會在將來的某個時候被刪除。 因此,您應該嘗試找到適合您的解決方案,而無需登錄ConfigureServices


.NET 核心 2.x

隨着 ASP.NET Core 2.0 的發布,這種情況發生了顯着變化。 在 ASP.NET Core 2.x 中,日志記錄是在主機生成器中創建的。 這意味着默認情況下可以通過 DI 使用日志記錄,並且可以將其注入到Startup類中:

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

        // …
    }
}

選項1:在啟動時直接使用日志(例如Serilog)-

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

輸出:

序列日志

要在 asp.net-core 應用程序中設置 Serilog,請查看GitHub 上的 Serilog.AspNetCore 包


選項 2:像這樣在 program.cs 中配置日志記錄-

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

host.Run();

像這樣啟動的用戶 loggerFactory -

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

鏈接上提供了完整的詳細信息

.NET Core 3.1中,您可以直接使用 LogFactory 創建記錄器。

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

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

目前官方的解決方案是像這樣設置一個本地 LoggerFactory:

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

另請參閱: https ://github.com/dotnet/aspnetcore/issues/9337#issuecomment-539859667

對於 .NET Core 3.0,官方文檔有這樣的說法: https ://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.0#create-logs-in-startup

不支持在Startup.ConfigureServices方法中完成 DI 容器設置之前寫入日志:

  • 不支持將記錄器注入Startup構造函數。
  • 不支持將記錄器注入Startup.ConfigureServices方法簽名

但正如他們在文檔中所說,您可以配置依賴於 ILogger 的服務,因此如果您編寫了一個 StartupLogger 類:

public class StartupLogger
{
    private readonly ILogger _logger;

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

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

然后在 Startup.ConfigureServices 添加服務,然后您需要構建服務提供者以訪問 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");
}

編輯:這會產生編譯器警告,為了調試您的 StartUp 類,這應該沒問題,但不適用於生產:

Startup.cs(39, 32): [ASP0000] 從應用程序代碼中調用“BuildServiceProvider”會導致創建一個額外的單例服務副本。 考慮替代方案,例如依賴注入服務作為“配置”的參數。

我使用了一種解決方案,避免使用ILogger接口實現“記錄器緩沖區”的第 3 方記錄器。

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

在 startup.cs 中使用很簡單,當然你在調用 Configure 后會得到日志輸出。 但總比沒有好。

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

現有的答案都不適合我。 我正在使用 NLog,甚至構建一個新的 ServiceCollection,在任何服務集合上調用 .CreateBuilder(),創建一個日志服務......在 ConfigureServices 期間,這些都不會寫入日志文件。

問題是,在構建 ServiceCollection 之前,日志記錄並不是真正的事情,而且它不是在 ConfigureServices 期間構建的。

基本上,我只想(需要)以配置擴展方法記錄啟動期間發生的事情,因為我遇到的唯一問題是 PROD,我無法附加調試器。

對我有用的解決方案是使用舊的 .NET Framework NLog 方法:

private static readonly NLog.Logger Logger = NLog.LogManager.GetCurrentClassLogger();

將該權利添加到擴展方法類中,並且我能夠在 ConfigureServices 期間及之后寫入日志(“the”日志)。

我不知道這是否是一個真正發布到生產代碼中的好主意(我不知道.NET 控制的 ILogger 和這個 NLog.ILogger 是否會在任何時候發生沖突),但我只需要它來看看發生了什么上。

使用Rolf 的回答,我把它放在我的 Startup 構造函數中:

private readonly ILogger _logger;

public Startup(IConfiguration configuration)
{
    Configuration = configuration;

    using var loggerFactory = LoggerFactory.Create(builder =>
    {
        builder.SetMinimumLevel(LogLevel.Information);
        builder.AddConsole();
        builder.AddEventSourceLogger();
    });
    _logger = loggerFactory.CreateLogger<Startup>();
}

public void ConfigureServices(IServiceCollection services)
{
    _logger.LogInformation("ConfigureServices...");
    // ...and so on...
}

主要代碼:

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設置一個默認的控制台記錄器。

... 將 ILoggerFactory 配置為登錄到控制台並調試輸出

啟動代碼:

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

我無法讓 ILogger 注入工作,但也許那是因為它不是控制器。 歡迎更多信息!

參考:

對於.Net 6

var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build();
var logger = ((IApplicationBuilder)app).ApplicationServices.GetService<ILogger<Program>>();
logger.LogInformation("Some logs");

或者更簡單的方法:

var builder = WebApplication.CreateBuilder(args);
...
var app = builder.Build();
ILogger logger = app.Logger;

我設法通過在文件中靜態創建一個帶有 NLog 的記錄器來做到這一點,然后在啟動方法中使用它。

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

您是否正在決定在運行時使用哪些服務並希望記錄? 或者您是否正在決定如何配置這些服務,您希望記錄哪些?

換句話說;

public void ConfigureServices(IServiceCollection services){
   // Do you really want to log something here?
   services.AddRazorPages(options => {
       // Or would you be satisfied by logging something here?
   });
}

如果只是后者,您可以將這些 lambda 函數的實現移動到IConfigureOptions<T>服務中,從而允許您注入其他服務。 繼續上面的示例,您可以創建以下內容;

public class ConfigureRazorPagesOptions : IConfigureOptions<RazorPagesOptions>
{
    private readonly ILogger<ConfigureRazorPagesOptions> logger;
    public ConfigureRazorPagesOptions(ILogger<ConfigureRazorPagesOptions> logger)
    {
        this.logger = logger;
    }

    public void Configure(RazorPagesOptions options)
    {
        logger.LogInformation("Now I can log here!");
    }
}

public void ConfigureServices(IServiceCollection services){
   services.AddRazorPages();
   services.AddSingleton<IConfigureOptions<RazorPagesOptions>, ConfigureRazorPagesOptions>();
}

如果您的.ConfigureServices方法變得過於復雜,您可能需要創建此類服務。 但是,要為每種選項類型添加大量樣板。 還有一個等效的簡寫,將其他服務注入到配置 lamda 中;

services.AddOptions<RazorPagesOptions>()
    .Configure<ILogger<RazorPagesOptions>>((options, logger) => { 
        logger.LogInformation("I can log here too!");
    });

這對我有用

private static readonly Logger logger = LogManager.GetLogger("Audit")

我發現了一個非常簡單的實現:

public void ConfigureServices(IServiceCollection services)
{
     services.AddControllersWithViews();

     var conn = Configuration.GetValue("conn", Configuration.GetConnectionString("Conn"));

     Console.WriteLine($@"System starting at {DateTime.Now}");

     Console.WriteLine($@"Database: {conn}");
}

只需使用Console.WriteLine 即可,即使在 Docker 上也是如此。

只需使用下面的行登錄 Startup.cs

Log.Information("App started.");

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM