为了调试在启动时失败的 .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");
....
}
输出:
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上提供的完整详细信息
在 .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 容器设置之前写入日志:不支持将 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>
? :)
现有的答案都不适合我。我正在使用 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'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...
}
主要代码:
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>
而不是工厂
对于.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;
您是否正在决定在运行时使用哪些服务并希望记录?或者您是否正在决定如何配置这些服务,您希望记录哪些?
换句话说;
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.");
不定期副业成功案例分享
ConfigureServices
运行时,记录器实际上还不存在。因此,您将无法仅仅因为还没有记录器而在此时登录。从好的方面来说,这仍然使您能够在ConfigureServices
中配置记录器,因为它都是相同的 DI 容器(这实际上是一件好事)。 – 如果您绝对需要记录内容,例如,您可以单独收集信息(例如在列表中),然后在记录器可用时立即将其记录下来。.NET 3.1
中,您目前可以在ConfigureServices
方法中 登录,而不会退回到WebHostBuilder
。使用下面的答案:stackoverflow.com/a/61488490/2877982Startup
”builder.UseStartup(context => new Startup(logger));
- docs.microsoft.com/en-us/aspnet/core/release-notes/…