using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using NLog.Web; using NLog.Targets; using NLog.Config; using App.Metrics; using App.Metrics.AspNetCore; using AyaNova.Util; using AyaNova.Api.ControllerHelpers; namespace AyaNova { public class Program { public static void Main(string[] args) { //Get config var config = new ConfigurationBuilder().AddEnvironmentVariables().AddCommandLine(args).Build(); ServerBootConfig.SetConfiguration(config); #region Initialize Logging //default log level NLog.LogLevel NLogLevel = NLog.LogLevel.Info; bool logLevelIsInfoOrHigher = true; switch (ServerBootConfig.AYANOVA_LOG_LEVEL.ToLowerInvariant()) { case "fatal": NLogLevel = NLog.LogLevel.Fatal; break; case "error": NLogLevel = NLog.LogLevel.Error; break; case "warn": NLogLevel = NLog.LogLevel.Warn; break; case "info": NLogLevel = NLog.LogLevel.Info; break; case "debug": NLogLevel = NLog.LogLevel.Debug; logLevelIsInfoOrHigher = false; break; case "trace": NLogLevel = NLog.LogLevel.Trace; logLevelIsInfoOrHigher = false; break; default: NLogLevel = NLog.LogLevel.Info; break; } // Step 1. Create configuration object var logConfig = new LoggingConfiguration(); // Step 2. Create targets and add them to the configuration var fileTarget = new FileTarget(); logConfig.AddTarget("file", fileTarget); //console target for really serious errors only var consoleTarget = new ConsoleTarget(); logConfig.AddTarget("console", consoleTarget); var nullTarget = new NLog.Targets.NullTarget(); logConfig.AddTarget("blackhole", nullTarget); // Step 3. Set target properties fileTarget.FileName = Path.Combine(ServerBootConfig.AYANOVA_LOG_PATH, "log-ayanova.txt"); fileTarget.Layout = "${longdate}|${uppercase:${level}}|${logger}|${message:exceptionSeparator==>:withException=true}"; fileTarget.ArchiveFileName = Path.Combine(ServerBootConfig.AYANOVA_LOG_PATH, "log-ayanova-{#}.txt"); fileTarget.ArchiveEvery = FileArchivePeriod.Wednesday; fileTarget.MaxArchiveFiles = 3; // Step 4. Define rules //filter out all Microsoft INFO level logs as they are too much var logRuleFilterOutMicrosoft = new LoggingRule("Microsoft.*", NLog.LogLevel.Trace, NLog.LogLevel.Info, nullTarget); logRuleFilterOutMicrosoft.Final = true; //filter out all Microsoft EF CORE concurrency exceptions, it's a nuisance unless debugging or something //This is what I have to filter because it's the top exception: Microsoft.EntityFrameworkCore.Update //But this is what I'm actually trying to filter: Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException //however there doesn't appear to be a way to filter out based on content so... var logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.Update", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget); logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions.Final = true; //Log all other regular items at selected level var logRuleAyaNovaItems = new LoggingRule("*", NLogLevel, fileTarget); //Log error or above to console var logRuleForConsole = new LoggingRule("*", NLog.LogLevel.Error, consoleTarget); //add console serious error only log rule logConfig.LoggingRules.Add(logRuleForConsole); //only log microsoft stuff it log is debug level or lower if (logLevelIsInfoOrHigher) { //filter OUT microsoft stuff logConfig.LoggingRules.Add(logRuleFilterOutMicrosoft); logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions); } logConfig.LoggingRules.Add(logRuleAyaNovaItems); //Turn on internal logging: if (ServerBootConfig.AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG) { NLog.Common.InternalLogger.LogFile = "log-ayanova-logger.txt"; NLog.Common.InternalLogger.LogLevel = NLog.LogLevel.Debug; } // NLog: setup the logger first to catch all errors var logger = NLogBuilder.ConfigureNLog(logConfig).GetCurrentClassLogger(); //This is the first log entry logger.Info("AYANOVA SERVER BOOTING (log level: \"{0}\")", ServerBootConfig.AYANOVA_LOG_LEVEL); logger.Info(AyaNovaVersion.FullNameAndVersion); logger.Debug("BOOT: Log path is \"{0}\" ", ServerBootConfig.AYANOVA_LOG_PATH); if (ServerBootConfig.AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG) { logger.Warn("BOOT: AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG is enabled! Disable as soon as no longer required."); } //Log environmental settings logger.Info("BOOT: OS - {0}", Environment.OSVersion.ToString()); logger.Debug("BOOT: Machine - {0}", Environment.MachineName); logger.Debug("BOOT: User - {0}", Environment.UserName); logger.Debug("BOOT: .Net Version - {0}", Environment.Version.ToString()); logger.Debug("BOOT: CPU count - {0}", Environment.ProcessorCount); logger.Debug("BOOT: Default language - \"{0}\"", ServerBootConfig.AYANOVA_DEFAULT_LANGUAGE); #endregion //Ensure we are in the correct folder string startFolder = Directory.GetCurrentDirectory(); var wwwRootFolder = Path.Combine(startFolder, "wwwroot"); //Test for web root path //If user starts AyaNova from folder that is not the contentRoot then //AyaNova won't be able to serve static files if (!Directory.Exists(wwwRootFolder)) { var err = string.Format("BOOT: E1010 - AyaNova was not started in the correct folder. AyaNova must be started from the folder that contains the \"wwwroot\" folder but was started instead from this folder: \"{0}\" which does not contain the wwwroot folder.", startFolder); logger.Fatal(err); throw new System.ApplicationException(err); } try { BuildWebHost(args, logger).Run(); } catch (Exception e) { logger.Fatal(e, "BOOT: E1090 - AyaNova server can't start due to unexpected exception during initialization"); throw; } } public static IWebHost BuildWebHost(string[] args, NLog.Logger logger) { logger.Debug("BOOT: building web host"); var configuration = new ConfigurationBuilder().AddCommandLine(args).Build(); return WebHost.CreateDefaultBuilder(args) .CaptureStartupErrors(true) .UseSetting("detailedErrors", "true") .UseUrls(ServerBootConfig.AYANOVA_USE_URLS)//default port and urls, set first can be overridden by any later setting here .UseConfiguration(configuration)//support command line override of port (dotnet run urls=http://*:8081) .UseIISIntegration()//support IIS integration just in case, it appears here to override prior settings if necessary (port) .ConfigureMetricsWithDefaults(builder => { if (ServerBootConfig.AYANOVA_METRICS_USE_INFLUXDB) { builder.Report.ToInfluxDb( options => { options.InfluxDb.BaseUri = new Uri(ServerBootConfig.AYANOVA_METRICS_INFLUXDB_BASEURL); options.InfluxDb.Database = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_DBNAME; if (!string.IsNullOrWhiteSpace(ServerBootConfig.AYANOVA_METRICS_INFLUXDB_CONSISTENCY)) { options.InfluxDb.Consistenency = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_CONSISTENCY; } options.InfluxDb.UserName = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_USERNAME; options.InfluxDb.Password = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_PASSWORD; if (!string.IsNullOrWhiteSpace(ServerBootConfig.AYANOVA_METRICS_INFLUXDB_RETENSION_POLICY)) { options.InfluxDb.RetensionPolicy = ServerBootConfig.AYANOVA_METRICS_INFLUXDB_RETENSION_POLICY; } options.InfluxDb.CreateDataBaseIfNotExists = true; options.HttpPolicy.BackoffPeriod = TimeSpan.FromSeconds(30); options.HttpPolicy.FailuresBeforeBackoff = 5; options.HttpPolicy.Timeout = TimeSpan.FromSeconds(10); //options.MetricsOutputFormatter = new App.Metrics.Formatters.Json.MetricsJsonOutputFormatter(); //options.Filter = filter; options.FlushInterval = TimeSpan.FromSeconds(20); }); } }) .UseMetricsEndpoints(opt => { opt.EnvironmentInfoEndpointEnabled = false; opt.MetricsEndpointEnabled = false; opt.MetricsTextEndpointEnabled = false; }) .UseMetrics() .UseStartup() .ConfigureLogging((context, logging) => { // clear all previously registered providers //https://stackoverflow.com/a/46336988/8939 logging.ClearProviders(); }) .UseNLog() // NLog: setup NLog for Dependency injection .Build(); } }//eoc }//eons