【发布时间】:2016-12-22 16:27:22
【问题描述】:
为了调试在启动时失败的 .NET Core 应用程序,我想从 startup.cs 文件中写入日志。我在文件中有日志记录设置,可以在 startup.cs 文件之外的应用程序的其余部分中使用,但不确定如何从 startup.cs 文件本身写入日志。
【问题讨论】:
标签: c# asp.net-core
为了调试在启动时失败的 .NET Core 应用程序,我想从 startup.cs 文件中写入日志。我在文件中有日志记录设置,可以在 startup.cs 文件之外的应用程序的其余部分中使用,但不确定如何从 startup.cs 文件本身写入日志。
【问题讨论】:
标签: c# asp.net-core
.Net Core 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 .请注意,网络主机可能会在将来的某个时候被删除。因此,您应该尝试找到适合您的解决方案,而无需登录ConfigureServices。
.NET Core 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");
// …
}
}
【讨论】:
ConfigureServices 运行时,记录器实际上还不存在。因此,您将无法仅仅因为还没有记录器而在那时进行记录。从好的方面来说,这仍然使您能够在ConfigureServices 中配置记录器,因为它都是同一个 DI 容器(这实际上是一件好事)。 – 如果您绝对需要记录内容,例如,您可以单独收集信息(例如在列表中),然后在记录器可用时立即将其注销。
.NET 3.1 内,您目前可以在ConfigureServices 方法内进行登录而不依赖WebHostBuilder。使用下面的答案:stackoverflow.com/a/61488490/2877982
Startup”builder.UseStartup(context => new Startup(logger)); - docs.microsoft.com/en-us/aspnet/core/release-notes/…
方案一:在启动时直接使用日志(如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,请查看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的完整详细信息
【讨论】:
在.NET Core 3.1中,可以直接使用LogFactory创建记录器。
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
});
ILogger logger = loggerFactory.CreateLogger<Startup>();
logger.LogInformation("Example log message");
【讨论】:
ILogger logger = LoggerFactory.Create(builder => builder.AddLog4Net()).CreateLogger<Startup>();。但是,如果您只记录异常,它不会显示比 Windows 事件日志中已经显示的更多信息(使用 IIS 时)。
目前官方的解决方案是这样设置本地 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
【讨论】:
我使用了一种解决方案,避免使用 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())
【讨论】:
对于 .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”会导致创建一个额外的单例服务副本。考虑将依赖注入服务等替代方案作为“配置”的参数。
【讨论】:
StartupLogger?为什么在构建服务提供者时不能简单地获得ILogger<Startup>? :)
现有的答案都不适合我。我正在使用 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 是否会在任何时候发生冲突),但我只需要它来看看发生了什么事。
【讨论】:
主要代码:
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 注入工作,但这可能是因为它不是控制器。欢迎提供更多信息!
参考:
【讨论】:
netcoreapp3.1 来说效果很好。我什至直接注入ILogger<Startup>而不是工厂
使用Rolf's answer,我把它放在我的启动构造函数中:
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...
}
【讨论】:
我已经设法通过在文件中静态创建一个带有 NLog 的记录器来做到这一点,然后在启动方法中使用它。
private readonly NLog.Logger _logger = new NLog.LogFactory().GetCurrentClassLogger();
【讨论】:
System.IO.File.Write() 方法。
您是否正在决定在运行时使用哪些服务并希望记录?或者您正在决定如何配置这些服务,您希望记录哪些内容?
换句话说;
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.");
【讨论】: