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": "Information", 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 private readonly 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 })
好嘛,真相大白:
- 未设置环境变量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 }
构建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 }
至此,告一段落!