Files
raven/server/AyaNova/Program.cs
2020-07-29 23:13:20 +00:00

259 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NLog.Web;
using NLog.Targets;
using NLog.Config;
using AyaNova.Util;
//using StackExchange.Profiling;
namespace AyaNova
{
public class Program
{
public static void Main(string[] args)
{
//Boot lock for generator
ServerGlobalOpsSettingsCache.BOOTING = true;
//Get config
var config = new ConfigurationBuilder().AddEnvironmentVariables().AddCommandLine(args).Build();
ServerBootConfig.SetConfiguration(config);
#region Initialize Logging
//NOTE: there is a logging issue that breaks all this with .net 3.1 hostbuilder vs webhostbuilder but webhostbuilder will be deprecated so we need to work around it
//the discussion about that is here: https://github.com/aspnet/AspNetCore/issues/9337
//I think I worked around it
//NLOG OFFICIAL GUIDELINES FOR .net core 3.x https://github.com/NLog/NLog/wiki/Getting-started-with-ASP.NET-Core-3
//default log level
NLog.LogLevel NLogLevel = NLog.LogLevel.Info;
bool FilterOutMicrosoftLogItems = 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;
break;
case "trace":
NLogLevel = NLog.LogLevel.Trace;
FilterOutMicrosoftLogItems = false;//Only at TRACE level do we want to see all the Microsoft logging stuff
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.ArchiveNumbering = ArchiveNumberingMode.Date;
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 httpclient logs at INFO level
//2020-01-28 11:19:12.8767|INFO|System.Net.Http.HttpClient.Default.LogicalHandler|Start processing HTTP request GET https://rockfish.ayanova.com/rvf/a6d18a8a-5613-4979-99da-80d07641a2fe
var logRuleFilterOutHttpClient = new LoggingRule("System.Net.Http.HttpClient.Default.*", NLog.LogLevel.Trace, NLog.LogLevel.Info, nullTarget);
logRuleFilterOutHttpClient.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;
var logRuleFilterOutMicrosoftEfCoreCommandExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.Database.Command", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget);
logRuleFilterOutMicrosoftEfCoreCommandExceptions.Final = true;
var logRuleFilterOutMicrosoftEfCoreDbUpdateExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.DbUpdateException", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget);
logRuleFilterOutMicrosoftEfCoreDbUpdateExceptions.Final = true;
//this rule is only intended to filter out this incorrect exception:
//2019-01-16 16:13:03.4808|WARN|Microsoft.EntityFrameworkCore.Query|Query: '(from Widget <generated>_4 in DbSet<Widget> select [<generated>_4]).Skip(__p_1).Take(__p_2)' uses a row limiting operation (Skip/Take) without OrderBy which may lead to unpredictable results.
var logRuleFilterOutMicrosoftEfCoreQueryExceptions = new LoggingRule("Microsoft.EntityFrameworkCore.Query", NLog.LogLevel.Trace, NLog.LogLevel.Error, nullTarget);
logRuleFilterOutMicrosoftEfCoreQueryExceptions.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 (FilterOutMicrosoftLogItems)
{
//filter OUT microsoft stuff
logConfig.LoggingRules.Add(logRuleFilterOutMicrosoft);
logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreConcurrencyExceptions);
logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreCommandExceptions);
logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreDbUpdateExceptions);
logConfig.LoggingRules.Add(logRuleFilterOutMicrosoftEfCoreQueryExceptions);
//also httpclient stuff
logConfig.LoggingRules.Add(logRuleFilterOutHttpClient);
}
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).GetLogger("BOOT");
//This is the first log entry
logger.Info($"AYANOVA SERVER {AyaNovaVersion.VersionString} BOOTING");
//log configuration
try
{
var AyaNovaConfig = config.AsEnumerable().Where(z => z.Key.StartsWith("AYANOVA") && z.Key != "AYANOVA_JWT_SECRET" && z.Key != "AYANOVA_SET_SUPERUSER_PW").Select(z => z.Key + "=" + z.Value).ToList();
var DiagConfig = string.Join(",", AyaNovaConfig);
DiagConfig = DbUtil.PasswordRedactedConnectionString(DiagConfig);
logger.Info($"Config {DiagConfig}");
}
catch (Exception ex)
{
logger.Error(ex, "Error fetching configuration");
}
logger.Debug($"Full configuration is {config.GetDebugView()}");
if (ServerBootConfig.AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG)
logger.Warn("AYANOVA_LOG_ENABLE_LOGGER_DIAGNOSTIC_LOG is enabled, this will cause the server to run very slowly");
//Log environmental settings
logger.Info("OS - {0}", Environment.OSVersion.ToString());
logger.Debug("Machine - {0}", Environment.MachineName);
logger.Debug("User - {0}", Environment.UserName);
logger.Debug(".Net Version - {0}", Environment.Version.ToString());
logger.Debug("CPU count - {0}", Environment.ProcessorCount);
#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("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();
BuildHost(args, logger).Build().Run();
}
catch (Exception e)
{
logger.Fatal(e, "E1090 - AyaNova server can't start due to unexpected exception during initialization");
throw;
}
finally
{
// Ensure to flush and stop internal timers/threads before application-exit (Avoid segmentation fault on Linux)
logger.Info("AyaNova server shutting down");
NLog.LogManager.Shutdown();
}
}
public static IHostBuilder BuildHost(string[] args, NLog.Logger logger)
{
logger.Debug("Building host");
var configuration = new ConfigurationBuilder().AddCommandLine(args).Build();
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.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)
.UseStartup<Startup>()
.ConfigureLogging((context, logging) =>
{
// clear all previously registered providers
//https://stackoverflow.com/a/46336988/8939
logging.ClearProviders();
})
.UseNLog(); // NLog: setup NLog for Dependency injection
});
}
}//eoc
}//eons