【问题标题】:How do I write logs from within Startup.cs?如何从 Startup.cs 中写入日志?
【发布时间】:2016-12-22 16:27:22
【问题描述】:

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

【问题讨论】:

    标签: c# asp.net-core


    【解决方案1】:

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

    【讨论】:

    • 谢谢。令人惊讶的是,您可以花费多少时间来寻找简单问题的答案。 @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 =&gt; new Startup(logger)); - docs.microsoft.com/en-us/aspnet/core/release-notes/…
    【解决方案2】:

    方案一:在启动时直接使用日志(如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的完整详细信息

    【讨论】:

      【解决方案3】:

      .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 =&gt; builder.AddLog4Net()).CreateLogger&lt;Startup&gt;();。但是,如果您只记录异常,它不会显示比 Windows 事件日志中已经显示的更多信息(使用 IIS 时)。
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          对于 .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&lt;Startup&gt;? :)
          【解决方案7】:

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

          【讨论】:

            【解决方案8】:

            主要代码:

            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&lt;Startup&gt;而不是工厂
            • 你也可以保存私有的只读 ILoggerFactory _loggerFactory;并在需要时创建记录器 var defaultLogger = _loggerFactory.CreateLogger();
            【解决方案9】:

            使用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...
            }
            

            【讨论】:

              【解决方案10】:

              我已经设法通过在文件中静态创建一个带有 NLog 的记录器来做到这一点,然后在启动方法中使用它。

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

              【讨论】:

              • 虽然这可行,但我建议使用 ASP.NET Core 和依赖注入 (DI) 附带的标准接口 - 内置或第 3 方 - 进行日志记录。通过使用接口,您可以 a) 根据需要替换提供的日志记录;b) 当您测试将 ILogger 作为服务注入的类(例如控制器)时,它们更容易模拟。
              • 在发布我自己对完全相同的事情的答案后,我才第一次看到这个。 @Manfred 没有另一种可行的方法。我猜除了System.IO.File.Write() 方法。
              【解决方案11】:

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

              换句话说;

              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&lt;T&gt; 服务中,允许你注入其他服务。继续上面的示例,您可以创建以下内容;

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

              【讨论】:

                【解决方案12】:

                这对我有用

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

                【讨论】:

                • dotnet.core 中不存在 LogManager,我猜它来自外部库(NLog、log4net、Serilog、...)?
                • @xisket 是的,它来自 NLog,NLog.LogManager.GetLogger("Audit")
                【解决方案13】:

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

                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 (!!!) upvotes。您确定尚未提供您的答案吗?如果不是,为什么有人会更喜欢您的方法而不是提议的现有方法?您是否正在利用新功能?是否存在更适合您的方法的场景?解释总是有用,但在这里特别很重要。
                【解决方案14】:

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

                Log.Information("App started.");
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2021-01-18
                  • 2016-03-09
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-09-15
                  • 1970-01-01
                  • 2015-08-27
                  相关资源
                  最近更新 更多