ChatGPT解决这个技术问题 Extra ChatGPT

如何从 Startup.cs 中写入日志?

为了调试在启动时失败的 .NET Core 应用程序,我想从 startup.cs 文件中写入日志。我在文件中有日志记录设置,可以在 startup.cs 文件之外的应用程序的其余部分中使用,但不确定如何从 startup.cs 文件本身写入日志。


K
Kishan Vaishnav

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

        // …
    }
}

谢谢你。令人惊讶的是,您可以花费多少时间来寻找简单问题的答案。 @poke(再次)感谢您告诉我我的选择是什么。你从哪里得到这些信息的?我已经确认我可以在 Configure 中记录东西,这比用锋利的棍子戳(双关语)眼睛更可取,但可能不如在 ConfigureServices 期间能够做到的那么棒。就我而言,我想记录我是否有环境设置,甚至可能将它们发布到日志中。没有骰子?叹息......不知道为什么这应该这么难。但至少,多亏了这篇文章,我知道我能做什么,不能做什么。
@Wellspring 在 3.0 中,这很“困难”,因为当 ConfigureServices 运行时,记录器实际上还不存在。因此,您将无法仅仅因为还没有记录器而在此时登录。从好的方面来说,这仍然使您能够在 ConfigureServices 中配置记录器,因为它都是相同的 DI 容器(这实际上是一件好事)。 – 如果您绝对需要记录内容,例如,您可以单独收集信息(例如在列表中),然后在记录器可用时立即将其记录下来。
.NET 3.1 中,您目前可以在 ConfigureServices 方法中 登录,而不会退回到 WebHostBuilder。使用下面的答案:stackoverflow.com/a/61488490/2877982
@Aage 这将有几个缺点:您将不得不重复完整的日志记录配置,日志记录配置也不会反映您的应用程序配置(例如,在 appsettings 中配置的日志级别等),并且您通常会设置第二个日志记录基础架构。我仍然建议您寻找一个解决方案,如何在 DI 设置期间完全避免日志记录。
.NET 5:“将附加参数传递给与主机一起初始化的 Startupbuilder.UseStartup(context => new Startup(logger)); - docs.microsoft.com/en-us/aspnet/core/release-notes/…
p
poke

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

输出:

https://i.stack.imgur.com/96E8P.png

要在 asp.net-core 应用程序中设置 Serilog,请查看 Serilog.AspNetCore package on GitHub

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

link上提供的完整详细信息


P
Pang

.NET Core 3.1 中,您可以直接使用 LogFactory 创建记录器。

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

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

对于 log4net,它归结为 ILogger logger = LoggerFactory.Create(builder => builder.AddLog4Net()).CreateLogger<Startup>();。但是,如果您只记录异常,则它不会显示比 Windows 事件日志中已经显示的更多信息(使用 IIS 时)。
R
Rolf Kristensen

目前官方的解决方案是像这样设置一个本地 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


C
Christian Riedl

我使用了一种解决方案,避免使用 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())

U
Uwe Keim

对于 .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 容器设置之前写入日志:不支持将 Logger 注入 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”会导致创建一个额外的单例服务副本。考虑替代方案,例如依赖注入服务作为“配置”的参数。


为什么需要 StartupLogger?为什么在构建服务提供者时不能简单地获得ILogger<Startup>? :)
P
Pang

现有的答案都不适合我。我正在使用 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 是否会在任何时候发生冲突),但我只需要它来看看发生了什么上。


P
Pang

使用 Rolf's answer,我把它放在我的 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...
}

T
Tim Abell

主要代码:

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 注入工作,但也许那是因为它不是控制器。欢迎更多信息!

参考:

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-2.1&tabs=aspnetcore2x


出于某种原因,相同的构造函数注入对我来说对 netcoreapp3.1 来说效果很好。我什至直接注入 ILogger<Startup> 而不是工厂
您还可以保存私有的只读 ILoggerFactory _loggerFactory;并在需要时创建记录器 var defaultLogger = _loggerFactory.CreateLogger();
R
R.Titov

对于.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;

J
Jeremy Lakeman

您是否正在决定在运行时使用哪些服务并希望记录?或者您是否正在决定如何配置这些服务,您希望记录哪些?

换句话说;

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

K
Karthik

这对我有用

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

dotnet.core 中不存在 LogManager,我猜它来自外部库(NLog、log4net、Serilog、...)?
@xisket 是的,它来自 NLog,NLog.LogManager.GetLogger("Audit")
D
Dharman

我发现了一个非常简单的实现:

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 上也是如此。


这个问题有 12 个现有答案,包括一个接受的答案,有 237 个(!!!)赞成票。您确定尚未提供您的答案吗?如果不是,为什么有人会更喜欢您的方法而不是提议的现有方法?您是否正在利用新功能?是否存在更适合您的方法的场景?解释总是有用的,但在这里尤其重要。
D
David Buck

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

Log.Information("App started.");