記一個ASP.Net Core配置檔案問題
最近排查一個ASP.Net Core專案的Bug,用LogInformation()記錄一些執行日誌,本地測試日誌記錄正常,然後發到RC環境測試,結果發現死活沒有日誌資訊。
首先想到就是LogLevel設定有問題。檢查了基礎的配置檔案(appsettings.json)沒有問題,而RC環境的配置檔案(appsettings.RC.json)未配置Logging節點,也就不會覆蓋。
1 "Logging": { 2 "IncludeScopes": false, 3 "LogLevel": { 4 "Default": "Informationappsettings.json", 5 "System": "Warning", 6 "Microsoft": "Warning" 7 }, 8 "Console": { 9 "LogLevel": { 10 "Default": "Warning" 11 } 12 } 13 }
然後檢查了涉及appsettings.json的程式碼。在Startup.cs中,會根據“environment.json”檔案中配置的“EnvironmentName”值,來載入不同配置檔案。
1 privateStartupreadonly ILoggerFactory m_LoggerFactory; 2 private readonly IConfiguration m_Configuration; 3 4 public Startup(IHostingEnvironment env, ILoggerFactory loggerFactory) 5 { 6 m_LoggerFactory = loggerFactory; 7 8 var environmentName = GetEnvironmentName(); 9 var builder = new ConfigurationBuilder()10 .SetBasePath(env.ContentRootPath) 11 .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 12 .AddJsonFile($"appsettings.{environmentName}.json", optional: true, reloadOnChange: true); 13 14 m_Configuration = builder.Build(); 15 } 16 17 private static string GetEnvironmentName() 18 { 19 var configuration = new ConfigurationBuilder() 20 .AddJsonFile("environment.json", optional: true) 21 .Build(); 22 return configuration["EnvironmentName"] ?? string.Empty; 23 }
可是environment.json配置也沒問題,而其中的m_Configuration變數只是會注入IoC中供業務程式碼讀取配置使用,並沒有對Logging配置做任何修改。
之後又想到,所有環境的配置檔案都是放在同一個目錄下,是否是串檔案了呢?而其中appsettings.Production.json中確實有配置Logging.LogLevel為“Warning”,如果載入了這個配置,那LogInformation()就不會輸出日誌了。因而調整了下appsettings.Production.json中的Loggind.LogLevel為“Information”,然後再測試,嘿,有日記資訊了!也就是說,Production的配置檔案確實被載入了。
再次檢查程式碼發現並未有明確載入Production檔案,那該不會是某個系統方法通過環境變數ASPNETCORE_ENVIRONMENT載入的吧?因為未設定該值(確實沒設定)預設就會是Production[1]。
於是立馬修改ASPNETCORE_ENVIRONMENT=RC(注意是1個“_”),還原appsettings.Production.json,然後重啟服務測試,不出所料,日誌正常記錄。那麼,接下來就是找到那個“系統方法”了。
仔細翻了翻官方文件,找到了以下內容[2]:
CreateDefaultBuilder方法(2.0新增[4])會呼叫2次AddJsonFile(),第1次載入appsettings.json,第2次載入appsettings.{Environment}.json,而Environment取至IHostingEnvironment.EnvironmentName,即環境值。對應原始碼[5]:
1 builder.UseKestrel((builderContext, options) => 2 { 3 options.Configure(builderContext.Configuration.GetSection("Kestrel")); 4 }) 5 .ConfigureAppConfiguration((hostingContext, config) => 6 { 7 var env = hostingContext.HostingEnvironment; 8 9 config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 10 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); 11 12 if (env.IsDevelopment()) 13 { 14 var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); 15 if (appAssembly != null) 16 { 17 config.AddUserSecrets(appAssembly, optional: true); 18 } 19 } 20 21 config.AddEnvironmentVariables(); 22 23 if (args != null) 24 { 25 config.AddCommandLine(args); 26 } 27 })CreateDefaultBuilder
好嘛,真相大白:
- 未設定環境變數ASPNETCORE_ENVIRONMENT,則預設為Production
- 呼叫CreateDefaultBuilder方法構建WebHost,自動載入appsettings.Production.json
- 最終,Logging.LogLevel被設定為了Production配置的值“Warning”,因而LogInformation()失效
那麼,只要正確設定ASPNETCORE_ENVIRONMENT值即可解決問題咯。
但是,還記得上面提到的“environment.json”檔案嗎?這個檔案目的本就是為了方便切換不同環境的配置檔案而建立的,Logging的配置理應由它來決定,如果通過ASPNETCORE_ENVIRONMENT來設定,就多此一舉了。那怎麼才能讓在Startup構造方法中構建的m_Configuration物件對Logging生效呢?官方也給出了方案:ConfigureAppConfiguration方法[2]!是不是覺得眼熟?在上面的CreateDefaultBuilder方法中正是通過ConfigureAppConfiguration()來載入預設配置的。
最終,程式碼修正如下:
1 private static IWebHost BuildWebHost(string[] args) 2 { 3 return WebHost.CreateDefaultBuilder(args) 4 .CaptureStartupErrors(true) 5 .UseSetting(WebHostDefaults.DetailedErrorsKey, "true") 6 .ConfigureAppConfiguration((hostingContext, config) => 7 { 8 config.Sources.Clear(); 9 config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 10 .AddJsonFile($"appsettings.{GetEnvironmentName()}.json", optional: true, reloadOnChange: true); 11 }) 12 .UseStartup<Startup>() 13 .UseNLog() 14 .Build(); 15 } 16 17 private static string GetEnvironmentName() 18 { 19 var configuration = new ConfigurationBuilder() 20 .AddJsonFile("environment.json", optional: true) 21 .Build(); 22 return configuration["EnvironmentName"] ?? string.Empty; 23 }BuildWebHost
構建m_Configuration的程式碼,由Startup.cs轉移到了Program.cs,而在Startup.cs中,Configuration物件可直接注入:
1 private readonly ILoggerFactory m_LoggerFactory; 2 private readonly IConfiguration m_Configuration; 3 4 public Startup(IConfiguration configuration, ILoggerFactory loggerFactory) 5 { 6 m_LoggerFactory = loggerFactory; 7 m_Configuration = configuration; 8 }Startup
至此,告一段落!