From eea417ff44d079e307fa563890f93f54e682b3f2 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Thu, 1 Jul 2021 07:37:03 -0400 Subject: [PATCH] added logging for startup issues --- Oqtane.Client/UI/SiteRouter.razor | 10 +-- .../Controllers/InstallationController.cs | 12 ++-- Oqtane.Server/Controllers/ModuleController.cs | 16 +++++ .../Controllers/ModuleDefinitionController.cs | 37 +++++++--- .../OqtaneServiceCollectionExtensions.cs | 3 +- .../Infrastructure/DatabaseManager.cs | 10 +-- Oqtane.Server/Infrastructure/LogManager.cs | 19 +++-- .../Infrastructure/Logging/FileLogger.cs | 72 +++++++++++++++++++ .../Logging/FileLoggerProvider.cs | 30 ++++++++ Oqtane.Server/Infrastructure/TenantManager.cs | 2 +- .../Infrastructure/UpgradeManager.cs | 13 ++++ Oqtane.Server/Modules/MigratableModuleBase.cs | 23 ++---- Oqtane.Server/Program.cs | 13 ++-- .../Repository/ModuleDefinitionRepository.cs | 2 +- Oqtane.Server/Repository/ModuleRepository.cs | 3 +- Oqtane.Shared/Shared/Utilities.cs | 5 ++ 16 files changed, 203 insertions(+), 67 deletions(-) create mode 100644 Oqtane.Server/Infrastructure/Logging/FileLogger.cs create mode 100644 Oqtane.Server/Infrastructure/Logging/FileLoggerProvider.cs diff --git a/Oqtane.Client/UI/SiteRouter.razor b/Oqtane.Client/UI/SiteRouter.razor index 629e6078..3714eeb9 100644 --- a/Oqtane.Client/UI/SiteRouter.razor +++ b/Oqtane.Client/UI/SiteRouter.razor @@ -53,15 +53,7 @@ { if (PageState == null) { - // misconfigured api calls should not be processed through the router - if (!_absoluteUri.Contains("~/api/")) - { - await Refresh(); - } - else - { - System.Diagnostics.Debug.WriteLine(GetType().FullName + ": Error: API call to " + _absoluteUri + " is not mapped to a Controller"); - } + await Refresh(); } } diff --git a/Oqtane.Server/Controllers/InstallationController.cs b/Oqtane.Server/Controllers/InstallationController.cs index 8ffd8aa4..ccf181b8 100644 --- a/Oqtane.Server/Controllers/InstallationController.cs +++ b/Oqtane.Server/Controllers/InstallationController.cs @@ -14,10 +14,10 @@ using Microsoft.Extensions.Caching.Memory; using System.Net; using Oqtane.Repository; using Microsoft.AspNetCore.Http; -using System.Diagnostics; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; namespace Oqtane.Controllers { @@ -31,8 +31,9 @@ namespace Oqtane.Controllers private readonly IMemoryCache _cache; private readonly IHttpContextAccessor _accessor; private readonly IAliasRepository _aliases; + private readonly ILogger _filelogger; - public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases) + public InstallationController(IConfigManager configManager, IInstallationManager installationManager, IDatabaseManager databaseManager, ILocalizationManager localizationManager, IMemoryCache cache, IHttpContextAccessor accessor, IAliasRepository aliases, ILogger filelogger) { _configManager = configManager; _installationManager = installationManager; @@ -41,6 +42,7 @@ namespace Oqtane.Controllers _cache = cache; _accessor = accessor; _aliases = aliases; + _filelogger = filelogger; } // POST api/ @@ -138,7 +140,7 @@ namespace Oqtane.Controllers } else { - Debug.WriteLine($"Oqtane Error: The Satellite Assembly Folder For {culture} Does Not Exist"); + _filelogger.LogError(Utilities.LogMessage(this, $"The Satellite Assembly Folder For {culture} Does Not Exist")); } } @@ -156,7 +158,7 @@ namespace Oqtane.Controllers } else { - Debug.WriteLine($"Oqtane Error: Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist"); + _filelogger.LogError(Utilities.LogMessage(this, $"Module {instance.ModuleDefinition.ModuleDefinitionName} Dependency {name}.dll Does Not Exist")); } } } @@ -171,7 +173,7 @@ namespace Oqtane.Controllers } else { - Debug.WriteLine($"Oqtane Error: Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist"); + _filelogger.LogError(Utilities.LogMessage(this, $"Theme {instance.Theme.ThemeName} Dependency {name}.dll Does Not Exist")); } } } diff --git a/Oqtane.Server/Controllers/ModuleController.cs b/Oqtane.Server/Controllers/ModuleController.cs index 94c58b40..4de5bb0a 100644 --- a/Oqtane.Server/Controllers/ModuleController.cs +++ b/Oqtane.Server/Controllers/ModuleController.cs @@ -194,6 +194,14 @@ namespace Oqtane.Controllers if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit)) { content = _modules.ExportModule(moduleid); + if (!string.IsNullOrEmpty(content)) + { + _logger.Log(LogLevel.Information, this, LogFunction.Read, "Module Content Exported {ModuleId}", moduleid); + } + else + { + _logger.Log(LogLevel.Warning, this, LogFunction.Read, "No Module Content Exported {ModuleId}", moduleid); + } } else { @@ -213,6 +221,14 @@ namespace Oqtane.Controllers if (ModelState.IsValid && module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit)) { success = _modules.ImportModule(moduleid, content); + if (success) + { + _logger.Log(LogLevel.Information, this, LogFunction.Update, "Module Content Imported {ModuleId}", moduleid); + } + else + { + _logger.Log(LogLevel.Warning, this, LogFunction.Update, "Module Content Import Failed {ModuleId}", moduleid); + } } else { diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index e5cf3af3..b3fb0299 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -58,6 +58,7 @@ namespace Oqtane.Controllers { if (_userPermissions.IsAuthorized(User, PermissionNames.Utilize, moduledefinition.Permissions)) { + if (string.IsNullOrEmpty(moduledefinition.Version)) moduledefinition.Version = new Version(1, 0, 0).ToString(); moduledefinitions.Add(moduledefinition); } } @@ -81,6 +82,7 @@ namespace Oqtane.Controllers ModuleDefinition moduledefinition = _moduleDefinitions.GetModuleDefinition(id, SiteId); if (_userPermissions.IsAuthorized(User, PermissionNames.Utilize, moduledefinition.Permissions)) { + if (string.IsNullOrEmpty(moduledefinition.Version)) moduledefinition.Version = new Version(1, 0, 0).ToString(); return moduledefinition; } else @@ -164,25 +166,38 @@ namespace Oqtane.Controllers if (!string.IsNullOrEmpty(moduledefinition.ServerManagerType)) { Type moduletype = Type.GetType(moduledefinition.ServerManagerType); - foreach (Tenant tenant in _tenants.GetTenants()) + if (moduletype != null) { - try + var alias = _tenantManager.GetAlias(); // save current + string result = string.Empty; + foreach (Tenant tenant in _tenants.GetTenants()) { - if (moduletype.GetInterface("IInstallable") != null) + try { - _tenantManager.SetTenant(tenant.TenantId); - var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); - ((IInstallable)moduleobject).Uninstall(tenant); + if (moduletype.GetInterface("IInstallable") != null) + { + _tenantManager.SetTenant(tenant.TenantId); + var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype); + ((IInstallable)moduleobject).Uninstall(tenant); + } + else + { + _sql.ExecuteScript(tenant, moduletype.Assembly, Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + ".Uninstall.sql"); + } } - else + catch (Exception ex) { - _sql.ExecuteScript(tenant, moduletype.Assembly, Utilities.GetTypeName(moduledefinition.ModuleDefinitionName) + ".Uninstall.sql"); + result = "For " + tenant.Name + " " + ex.Message; } - _logger.Log(LogLevel.Information, this, LogFunction.Delete, "{ModuleDefinitionName} Uninstalled For Tenant {Tenant}", moduledefinition.ModuleDefinitionName, tenant.Name); } - catch (Exception ex) + _tenantManager.SetAlias(alias); // restore current + if (string.IsNullOrEmpty(result)) { - _logger.Log(LogLevel.Error, this, LogFunction.Delete, "Error Uninstalling {ModuleDefinitionName} For Tenant {Tenant} {Error}", moduledefinition.ModuleDefinitionName, tenant.Name, ex.Message); + _logger.Log(LogLevel.Information, this, LogFunction.Delete, "{ModuleDefinitionName} Uninstalled For All Tenants", moduledefinition.ModuleDefinitionName); + } + else + { + _logger.Log(LogLevel.Error, this, LogFunction.Delete, "Error Uninstalling {ModuleDefinitionName} {Error}", moduledefinition.ModuleDefinitionName, result); } } } diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 77270f90..2b7310fe 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using Oqtane.Infrastructure; using Oqtane.Modules; @@ -62,7 +63,7 @@ namespace Microsoft.Extensions.DependencyInjection services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - + services.AddSingleton(); return services; } diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 44fdbcb1..333579c5 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -17,7 +17,7 @@ using Oqtane.Repository; using Oqtane.Shared; using Oqtane.Enums; using Newtonsoft.Json; -using System.Diagnostics; +using Microsoft.Extensions.Logging; // ReSharper disable MemberCanBePrivate.Global // ReSharper disable ConvertToUsingDeclaration @@ -33,14 +33,16 @@ namespace Oqtane.Infrastructure private readonly IWebHostEnvironment _environment; private readonly IMemoryCache _cache; private readonly IConfigManager _configManager; + private readonly ILogger _filelogger; - public DatabaseManager(IConfigurationRoot config, IServiceScopeFactory serviceScopeFactory, IWebHostEnvironment environment, IMemoryCache cache, IConfigManager configManager) + public DatabaseManager(IConfigurationRoot config, IServiceScopeFactory serviceScopeFactory, IWebHostEnvironment environment, IMemoryCache cache, IConfigManager configManager, ILogger filelogger) { _config = config; _serviceScopeFactory = serviceScopeFactory; _environment = environment; _cache = cache; _configManager = configManager; + _filelogger = filelogger; } public Installation IsInstalled() @@ -137,8 +139,8 @@ namespace Oqtane.Infrastructure { if (!string.IsNullOrEmpty(installation.Message)) { - Debug.WriteLine($"Oqtane Error: {installation.Message}"); // problem with prior installation + _filelogger.LogError(Utilities.LogMessage(this, installation.Message)); install.ConnectionString = ""; } } @@ -641,7 +643,7 @@ namespace Oqtane.Infrastructure tenant.Version = Constants.Version; tenants.UpdateTenant(tenant); - if (site != null) log.Log(site.SiteId, LogLevel.Trace, this, LogFunction.Create, "Site Created {Site}", site); + if (site != null) log.Log(site.SiteId, Shared.LogLevel.Information, this, LogFunction.Create, "Site Created {Site}", site); } } } diff --git a/Oqtane.Server/Infrastructure/LogManager.cs b/Oqtane.Server/Infrastructure/LogManager.cs index 7ee8cf4a..afbb1de8 100644 --- a/Oqtane.Server/Infrastructure/LogManager.cs +++ b/Oqtane.Server/Infrastructure/LogManager.cs @@ -46,18 +46,14 @@ namespace Oqtane.Infrastructure public void Log(int siteId, LogLevel level, object @class, LogFunction function, Exception exception, string message, params object[] args) { Log log = new Log(); - if (siteId == -1) + + log.SiteId = siteId; + if (log.SiteId == -1 && _alias != null) { - log.SiteId = null; - if (_alias != null) - { - log.SiteId = _alias.SiteId; - } - } - else - { - log.SiteId = siteId; + log.SiteId = _alias.SiteId; } + if (log.SiteId == -1) return; // logs must be site specific + log.PageId = null; log.ModuleId = null; log.UserId = null; @@ -125,9 +121,10 @@ namespace Oqtane.Infrastructure { _logs.AddLog(log); } - catch + catch (Exception ex) { // an error occurred writing to the database + var x = ex.Message; } } } diff --git a/Oqtane.Server/Infrastructure/Logging/FileLogger.cs b/Oqtane.Server/Infrastructure/Logging/FileLogger.cs new file mode 100644 index 00000000..58bc3c0f --- /dev/null +++ b/Oqtane.Server/Infrastructure/Logging/FileLogger.cs @@ -0,0 +1,72 @@ +using System; +using System.IO; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; + +namespace Oqtane.Infrastructure +{ + public class FileLogger : ILogger + { + protected readonly FileLoggerProvider _FileLoggerProvider; + private readonly IWebHostEnvironment _environment; + private readonly IConfigManager _configManager; + + public FileLogger(FileLoggerProvider FileLoggerProvider, IWebHostEnvironment environment,IConfigManager configManager) + { + _FileLoggerProvider = FileLoggerProvider; + _environment = environment; + _configManager = configManager; + } + + public IDisposable BeginScope(TState state) + { + return null; + } + + public bool IsEnabled(LogLevel logLevel) + { + return logLevel != LogLevel.None; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + if (_configManager.GetSetting("Logging:FileLogger:LogLevel:Default", "") == "") + { + _configManager.AddOrUpdateSetting("Logging:FileLogger:LogLevel:Default", "Error", true); + if (logLevel < LogLevel.Error) + { + return; + } + } + + if (!IsEnabled(logLevel)) + { + return; + } + + string folder = Path.Combine(_environment.ContentRootPath, "Content", "Log"); + + // ensure directory exists + if (!Directory.Exists(folder)) + { + Directory.CreateDirectory(folder); + } + + var filepath = Path.Combine(folder, "error.log"); + + var logentry = string.Format("{0} [{1}] {2} {3}", "[" + DateTimeOffset.UtcNow.ToString("yyyy-MM-dd HH:mm:ss+00:00") + "]", logLevel.ToString(), formatter(state, exception), exception != null ? exception.StackTrace : ""); + + try + { + using (var streamWriter = new StreamWriter(filepath, true)) + { + streamWriter.WriteLine(logentry); + } + } + catch + { + // error occurred + } + } + } +} diff --git a/Oqtane.Server/Infrastructure/Logging/FileLoggerProvider.cs b/Oqtane.Server/Infrastructure/Logging/FileLoggerProvider.cs new file mode 100644 index 00000000..11c4ff48 --- /dev/null +++ b/Oqtane.Server/Infrastructure/Logging/FileLoggerProvider.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Logging; + +namespace Oqtane.Infrastructure +{ + /// + /// FileLogger should only be used in scenarios where a database is not available or tenant/site cannot be determined ( ie. during startup ) + /// + [ProviderAlias("FileLogger")] + public class FileLoggerProvider : ILoggerProvider + { + private readonly IWebHostEnvironment _environment; + private readonly IConfigManager _configManager; + + public FileLoggerProvider(IWebHostEnvironment environment, IConfigManager configManager) + { + _environment = environment; + _configManager = configManager; + } + + public ILogger CreateLogger(string categoryName) + { + return new FileLogger(this, _environment, _configManager); + } + + public void Dispose() + { + } + } +} diff --git a/Oqtane.Server/Infrastructure/TenantManager.cs b/Oqtane.Server/Infrastructure/TenantManager.cs index 7f0470f8..72798454 100644 --- a/Oqtane.Server/Infrastructure/TenantManager.cs +++ b/Oqtane.Server/Infrastructure/TenantManager.cs @@ -81,7 +81,7 @@ namespace Oqtane.Infrastructure public void SetTenant(int tenantId) { // background processes can set the alias using the SiteState service - _siteState.Alias = new Alias { TenantId = tenantId }; + _siteState.Alias = new Alias { TenantId = tenantId, AliasId = -1, SiteId = -1 }; } } } diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs index 8ca6533c..51da8971 100644 --- a/Oqtane.Server/Infrastructure/UpgradeManager.cs +++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs @@ -46,6 +46,9 @@ namespace Oqtane.Infrastructure case "2.1.0": Upgrade_2_1_0(tenant, scope); break; + case "2.2.0": + Upgrade_2_2_0(tenant, scope); + break; } } } @@ -150,5 +153,15 @@ namespace Oqtane.Infrastructure } } + private void Upgrade_2_2_0(Tenant tenant, IServiceScope scope) + { + if (tenant.Name == TenantNames.Master) + { + if (_configManager.GetSetting("Logging:LogLevel:Default", "") == "") + { + _configManager.AddOrUpdateSetting("Logging:LogLevel:Default", "Information", true); + } + } + } } } diff --git a/Oqtane.Server/Modules/MigratableModuleBase.cs b/Oqtane.Server/Modules/MigratableModuleBase.cs index 8d682e26..324d4dba 100644 --- a/Oqtane.Server/Modules/MigratableModuleBase.cs +++ b/Oqtane.Server/Modules/MigratableModuleBase.cs @@ -1,5 +1,3 @@ -using System; -using System.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Oqtane.Enums; @@ -16,27 +14,18 @@ namespace Oqtane.Modules using (dbContext) { - try + var migrator = dbContext.GetService(); + if (migrationType == MigrationType.Down) { - var migrator = dbContext.GetService(); - if (migrationType == MigrationType.Down) - { - migrator.Migrate(Migration.InitialDatabase); - } - else - { - migrator.Migrate(); - } + migrator.Migrate(Migration.InitialDatabase); } - catch (Exception ex) + else { - Debug.WriteLine($"Oqtane Error: Error Executing Migration - {ex}"); - result = false; + migrator.Migrate(); } - } - return result; + return result; } } } diff --git a/Oqtane.Server/Program.cs b/Oqtane.Server/Program.cs index e7dd23fc..14a64abc 100644 --- a/Oqtane.Server/Program.cs +++ b/Oqtane.Server/Program.cs @@ -5,6 +5,8 @@ using Microsoft.AspNetCore; using Microsoft.Extensions.DependencyInjection; using Oqtane.Infrastructure; using System.Diagnostics; +using Microsoft.Extensions.Logging; +using Oqtane.Shared; namespace Oqtane.Server { @@ -13,13 +15,14 @@ namespace Oqtane.Server public static void Main(string[] args) { var host = BuildWebHost(args); - using (var serviceScope = host.Services.GetRequiredService().CreateScope()) + var databaseManager = host.Services.GetService(); + var install = databaseManager.Install(); + if (!string.IsNullOrEmpty(install.Message)) { - var databaseManager = serviceScope.ServiceProvider.GetService(); - var install = databaseManager.Install(); - if (!string.IsNullOrEmpty(install.Message)) + var filelogger = host.Services.GetRequiredService>(); + if (filelogger != null) { - Debug.WriteLine($"Oqtane Error: {install.Message}"); + filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}"); } } host.Run(); diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 5ec1fbb4..62566b19 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -159,7 +159,7 @@ namespace Oqtane.Repository moduledefinition.Name = (!string.IsNullOrEmpty(moduledef.Name)) ? moduledef.Name : moduledefinition.Name; moduledefinition.Description = (!string.IsNullOrEmpty(moduledef.Description)) ? moduledef.Description : moduledefinition.Description; moduledefinition.Categories = (!string.IsNullOrEmpty(moduledef.Categories)) ? moduledef.Categories : moduledefinition.Categories; - moduledefinition.Version = (!string.IsNullOrEmpty(moduledef.Version)) ? moduledef.Version : moduledefinition.Version; + moduledefinition.Version = moduledef.Version; // remove module definition from list as it is already synced moduledefs.Remove(moduledef); } diff --git a/Oqtane.Server/Repository/ModuleRepository.cs b/Oqtane.Server/Repository/ModuleRepository.cs index 90710bd8..922dfb5d 100644 --- a/Oqtane.Server/Repository/ModuleRepository.cs +++ b/Oqtane.Server/Repository/ModuleRepository.cs @@ -159,10 +159,9 @@ namespace Oqtane.Repository } } } - catch (Exception ex) + catch { // error occurred during import - string error = ex.Message; } return success; diff --git a/Oqtane.Shared/Shared/Utilities.cs b/Oqtane.Shared/Shared/Utilities.cs index 23397f89..946b3dc1 100644 --- a/Oqtane.Shared/Shared/Utilities.cs +++ b/Oqtane.Shared/Shared/Utilities.cs @@ -412,5 +412,10 @@ namespace Oqtane.Shared return dictionary; } + + public static string LogMessage(object @class, string message) + { + return $"[{@class.GetType()}] {message}"; + } } }