markmcdowell / NLog.Targets.ElasticSearch

NLog target for Elasticsearch
MIT License
176 stars 89 forks source link

ConfigSetting Layout Renderer in Various Environment #103

Closed maximdx closed 5 years ago

maximdx commented 5 years ago

Hi,

It looks like the NLog.Targets.ElasticSearch can only load config from appsetting.json as config setting layout renderer. I hope I am wrong, please correct me if it is possible to load other appsetting files with environment (ASPNETCORE_ENVIRONMENT). I have tried to put something like the following json in the appsetting.Development.json:

{
  "ConnectionStrings": {
    "ElasticUrl": "http://localhost:31114"
  }
}

and running the project under the Development environment but unfortunately it is not respected, instead the value in appsetting.json is still used.

The project I am running is a .net core 2.2 web api and I am following the instruction here for the config setting layout renderer: https://github.com/NLog/NLog/wiki/ConfigSetting-Layout-Renderer

Since it will automatically register hosting environment configuration with ConfigSettingLayoutRenderer, so I skip to do it manually. The .net core 2.2 framework handles the multiple environment appsetting files in behind, so I am not set it up explicitly.

Here is my code snippet to start the project (Program.cs):

        public static void Main(string[] args)
        {
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile("secret/appsettings.secret.json", optional: true, reloadOnChange: true)
                .Build();
            CreateWebHostBuilder(args, configuration).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args, IConfiguration config) =>
            WebHost.CreateDefaultBuilder(args)
                .UseConfiguration(config)
                .ConfigureLogging((hostContext, configLogging) =>
                {
                    configLogging.ClearProviders();
                    configLogging.SetMinimumLevel(LogLevel.Trace);
                })
                .UseNLog()
                .UseStartup<Startup>();

I am not sure if I miss something to make this happen? Any help is appreciated.

snakefoot commented 5 years ago

Is this correct:

  1. You load the appsettings.json manually in Main() (Contains default setting value)
  2. You call CreateDefaultBuilder that loads all appsettings files also environment specific one (Contains wanted setting value).
  3. You call UseConfiguration() that overwrites all appsettings with the one loaded in Main() (Wanted setting is now overwritten by default setting value)

You are now surprised that your environment-specific setting is lost after having called UseConfiguration() ?

maximdx commented 5 years ago

I have to admit that the first example is not appropriate. I have tried adding the environment config files manually, but it makes no difference:

        public static void Main(string[] args)
        {
            var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
                .AddJsonFile("secret/appsettings.secret.json", optional: true, reloadOnChange: true)
                .Build();
            CreateWebHostBuilder(args, configuration).Build().Run();
        } 

        public static IWebHostBuilder CreateWebHostBuilder(string[] args, IConfiguration config) =>
            WebHost.CreateDefaultBuilder(args)
                .UseConfiguration(config)
                .ConfigureLogging((hostContext, configLogging) =>
                {
                    configLogging.ClearProviders();
                    configLogging.SetMinimumLevel(LogLevel.Trace);
                })
                .UseNLog()
                .UseStartup<Startup>();

Meanwhile, I have tried the default settings as well:

        public static void Main(string[] args)
        {
            CreateWebHostBuilder(args).Build().Run();
        }

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .ConfigureLogging((hostContext, configLogging) =>
                {
                    configLogging.ClearProviders();
                    configLogging.SetMinimumLevel(LogLevel.Trace);
                })
                .UseNLog()
                .UseStartup<Startup>();

However, the elastic url goes to the default value for both above.

snakefoot commented 5 years ago

Have very little experience with ASP.NetCore and how to override settings using appsettings.json.

What value does lookupElasticUrl have if you do this:

var webHostBuilder = CreateWebHostBuilder(args);
var lookupElasticUrl = webHostBuilder.GetSetting("ConnectionStrings:ElasticUrl");
NLog.LogManager.GetLogger("Hello").Error("ElasticUrl={0}", lookupElasticUrl);
NLog.LogManager.GetLogger("Hello").Error("CurrentDir={0}", Directory.GetCurrentDirectory());
webHostBuilder.Build().Run();

Maybe the environment-specific appsettings.json is not being deployed correctly? Or maybe it cannot be found in the path returned by Directory.GetCurrentDirectory().

maximdx commented 5 years ago

The lookupElasticUrl did retrieve the wanted url in the environment appsettings.json (e.g. appsettings.Development.json). The CurrentDir points to the root directory of the web api folder. It looks like configsetting layout render is working as I think, but it doesn't send the logs to elasticsearch in the end. Having enabled the internal log of NLog, it complains about issue connecting the target elasticsearch host (Exception: System.Net.Http.HttpRequestException: No connection could be made because the target machine actively refused it). Probably it still didn't get the wanted elastic url.

My project folders are organized as following:

|
|----web api project
|               |----appsettings.{environment}.json
|               |----Controllers
|               |             |_____ ...
|               |____other files in the web api project
|
|____log project
                |____NLog.config
snakefoot commented 5 years ago

It sounds like the NLog.config is being loaded before having fully initialized the ConfigSetting.

Curious what part of your initialization logic causes NLog to load the NLog.config, before ConfigSetting is available.

Sounds similar to this issue: https://github.com/NLog/NLog.Extensions.Logging/issues/265

snakefoot commented 5 years ago

What happens if you perform a manual reload of NLog.config after initialization of WebHostBuilder?

See also: https://github.com/NLog/NLog/wiki/Reinitialize-NLog-configuration

maximdx commented 5 years ago

Hi @snakefoot

Thanks for pointing this out.

The NLog.config did get loaded before initializing the ConfigSetting. My initialization logic is nearly intact compare with the new web api project expect I have replaced the log provider plus I have an additional log project with a legacy singleton logger class. The singleton class is obsoleted so that no one is using it any more. It's been a bit wired to see this behavior.

I have read the both post you shared with me. Tried to load NLog config file as the way it did in #265, and that works for me. Actually I am happy with this solution because I can prepare different NLog config file for the according environment, and it is a way more clear than putting everything into the appsettings config files for me.

Now I am happy I can achieve that environment setting with this:

            var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                .AddJsonFile($"appsettings.{environment}.json", optional: true, reloadOnChange: true)
                .AddJsonFile("secret/appsettings.secret.json", optional: true, reloadOnChange: true)
                .Build();
            ConfigSettingLayoutRenderer.DefaultConfiguration = configuration;
            NLogBuilder.ConfigureNLog($"NLog.{environment}.config").GetCurrentClassLogger();
            CreateWebHostBuilder(args, configuration).Build().Run();
snakefoot commented 4 years ago

NLog.Web.AspNetCore ver 4.9.3 has been released, and now you can do this:

var logger = NLog.LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();

That replaces the old style:

var logger = NLog.Web.NLogBuilder.ConfigureNLog("nlog.config").GetCurrentClassLogger();