From ce25967633a8beb7259127af25089ef3e86deb45 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Sat, 19 Oct 2019 11:09:10 -0400 Subject: [PATCH] renamed control to action to reflect its purpose and be more consistent with asp.net conventions --- Oqtane.Client/Shared/ContainerBuilder.razor | 4 +- Oqtane.Client/Shared/ModuleInstance.razor | 4 +- Oqtane.Client/Shared/PageState.cs | 2 +- Oqtane.Client/Shared/Pane.razor | 10 +- Oqtane.Client/Shared/SiteRouter.razor | 18 ++-- Oqtane.Client/Shared/ThemeBuilder.razor | 2 +- .../Themes/Controls/ModuleTitle.razor | 4 +- Oqtane.Server/Logging/DBLoggerExtensions.cs | 36 +++++++ Oqtane.Server/Logging/DBLoggerOptions.cs | 13 +++ Oqtane.Server/Logging/DBLoggerOptionsSetup.cs | 11 +++ Oqtane.Server/Logging/DBLoggerProvider.cs | 94 +++++++++++++++++++ Oqtane.Server/Logging/LogEntry.cs | 32 +++++++ Oqtane.Server/Logging/LogScope.cs | 10 ++ Oqtane.Server/Logging/Logger.cs | 91 ++++++++++++++++++ Oqtane.Server/Logging/LoggerProvider.cs | 80 ++++++++++++++++ Oqtane.Server/Oqtane.Server.csproj | 1 + .../Repository/ModuleDefinitionRepository.cs | 5 +- Oqtane.Shared/Shared/Constants.cs | 14 +-- 18 files changed, 401 insertions(+), 30 deletions(-) create mode 100644 Oqtane.Server/Logging/DBLoggerExtensions.cs create mode 100644 Oqtane.Server/Logging/DBLoggerOptions.cs create mode 100644 Oqtane.Server/Logging/DBLoggerOptionsSetup.cs create mode 100644 Oqtane.Server/Logging/DBLoggerProvider.cs create mode 100644 Oqtane.Server/Logging/LogEntry.cs create mode 100644 Oqtane.Server/Logging/LogScope.cs create mode 100644 Oqtane.Server/Logging/Logger.cs create mode 100644 Oqtane.Server/Logging/LoggerProvider.cs diff --git a/Oqtane.Client/Shared/ContainerBuilder.razor b/Oqtane.Client/Shared/ContainerBuilder.razor index 09106a68..3e2e19ed 100644 --- a/Oqtane.Client/Shared/ContainerBuilder.razor +++ b/Oqtane.Client/Shared/ContainerBuilder.razor @@ -19,7 +19,7 @@ { ModuleState = Module; // passed in from Pane component string container = ModuleState.ContainerType; - if (PageState.ModuleId != -1 && PageState.Control != "" && ModuleState.UseAdminContainer) + if (PageState.ModuleId != -1 && PageState.Action != "" && ModuleState.UseAdminContainer) { container = Constants.DefaultAdminContainer; } @@ -35,7 +35,7 @@ else { // container does not exist with type specified - builder.OpenComponent(0, Type.GetType(Constants.ModuleMessageControl)); + builder.OpenComponent(0, Type.GetType(Constants.ModuleMessageComponent)); builder.AddAttribute(1, "Message", "Error Loading Module Container " + container); builder.CloseComponent(); } diff --git a/Oqtane.Client/Shared/ModuleInstance.razor b/Oqtane.Client/Shared/ModuleInstance.razor index 69d40111..250f6203 100644 --- a/Oqtane.Client/Shared/ModuleInstance.razor +++ b/Oqtane.Client/Shared/ModuleInstance.razor @@ -30,9 +30,9 @@ { string typename = ModuleState.ModuleType; // check for core module actions component - if (Constants.DefaultModuleActions.Contains(PageState.Control)) + if (Constants.DefaultModuleActions.Contains(PageState.Action)) { - typename = Constants.DefaultModuleActionsTemplate.Replace("{Control}", PageState.Control); + typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, PageState.Action); } Type moduleType = null; diff --git a/Oqtane.Client/Shared/PageState.cs b/Oqtane.Client/Shared/PageState.cs index f8dfe93e..f0378809 100644 --- a/Oqtane.Client/Shared/PageState.cs +++ b/Oqtane.Client/Shared/PageState.cs @@ -18,7 +18,7 @@ namespace Oqtane.Shared public Uri Uri { get; set; } public Dictionary QueryString { get; set; } public int ModuleId { get; set; } - public string Control { get; set; } + public string Action { get; set; } public bool EditMode { get; set; } public bool DesignMode { get; set; } } diff --git a/Oqtane.Client/Shared/Pane.razor b/Oqtane.Client/Shared/Pane.razor index e45f3efd..2f83ea33 100644 --- a/Oqtane.Client/Shared/Pane.razor +++ b/Oqtane.Client/Shared/Pane.razor @@ -38,7 +38,7 @@ DynamicComponent = builder => { - if (PageState.ModuleId != -1 && PageState.Control != "") + if (PageState.ModuleId != -1 && PageState.Action != "") { if (Name.ToLower() == Constants.AdminPane.ToLower()) { @@ -47,15 +47,15 @@ { string typename = module.ModuleType; // check for core module actions component - if (Constants.DefaultModuleActions.Contains(PageState.Control)) + if (Constants.DefaultModuleActions.Contains(PageState.Action)) { - typename = Constants.DefaultModuleActionsTemplate.Replace("{Control}", PageState.Control); + typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, PageState.Action); } Type moduleType = Type.GetType(typename); if (moduleType != null) { bool authorized = false; - if (Constants.DefaultModuleActions.Contains(PageState.Control)) + if (Constants.DefaultModuleActions.Contains(PageState.Action)) { authorized = UserSecurity.IsAuthorized(PageState.User, "Edit", PageState.Page.Permissions); } @@ -83,7 +83,7 @@ } if (authorized) { - if (!Constants.DefaultModuleActions.Contains(PageState.Control) && module.ControlTitle != "") + if (!Constants.DefaultModuleActions.Contains(PageState.Action) && module.ControlTitle != "") { module.Title = module.ControlTitle; } diff --git a/Oqtane.Client/Shared/SiteRouter.razor b/Oqtane.Client/Shared/SiteRouter.razor index 91ae2d42..481d01d6 100644 --- a/Oqtane.Client/Shared/SiteRouter.razor +++ b/Oqtane.Client/Shared/SiteRouter.razor @@ -77,7 +77,7 @@ User user; List modules; int moduleid = -1; - string control = ""; + string action = ""; bool editmode = false; bool designmode = false; Reload reload = Reload.None; @@ -162,9 +162,9 @@ // check if path has moduleid and control specification ie. page/moduleid/control/ if (segments.Length >= 2 && int.TryParse(segments[segments.Length - 2], out result)) { - control = segments[segments.Length - 1]; + action = segments[segments.Length - 1]; moduleid = result; - path = path.Replace(moduleid.ToString() + "/" + control + "/", ""); + path = path.Replace(moduleid.ToString() + "/" + action + "/", ""); } else { @@ -235,9 +235,9 @@ pagestate.Uri = new Uri(_absoluteUri, UriKind.Absolute); pagestate.QueryString = querystring; pagestate.ModuleId = moduleid; - pagestate.Control = control; + pagestate.Action = action; - if (PageState != null && (PageState.ModuleId != pagestate.ModuleId || PageState.Control != pagestate.Control)) + if (PageState != null && (PageState.ModuleId != pagestate.ModuleId || PageState.Action != pagestate.Action)) { reload = Reload.Page; } @@ -245,7 +245,7 @@ if (PageState == null || reload >= Reload.Page) { modules = await ModuleService.GetModulesAsync(site.SiteId); - modules = ProcessModules(modules, moduledefinitions, page.PageId, pagestate.ModuleId, pagestate.Control, page.Panes, site.DefaultContainerType); + modules = ProcessModules(modules, moduledefinitions, page.PageId, pagestate.ModuleId, pagestate.Action, page.Panes, site.DefaultContainerType); } else { @@ -365,7 +365,7 @@ } } } - module.ModuleType = typename.Replace("{Control}", control); + module.ModuleType = typename.Replace(Constants.ActionToken, control); // admin controls need to load additional metadata from the IModuleControl interface if (moduleid == module.ModuleId) @@ -374,7 +374,7 @@ // check for core module actions component if (Constants.DefaultModuleActions.Contains(control)) { - typename = Constants.DefaultModuleActionsTemplate.Replace("{Control}", control); + typename = Constants.DefaultModuleActionsTemplate.Replace(Constants.ActionToken, control); } Type moduletype = Type.GetType(typename); if (moduletype != null) @@ -389,7 +389,7 @@ } else { - module.ModuleType = typename.Replace("{Control}", Constants.DefaultControl); + module.ModuleType = typename.Replace(Constants.ActionToken, Constants.DefaultAction); } } diff --git a/Oqtane.Client/Shared/ThemeBuilder.razor b/Oqtane.Client/Shared/ThemeBuilder.razor index 4aa92261..145e7449 100644 --- a/Oqtane.Client/Shared/ThemeBuilder.razor +++ b/Oqtane.Client/Shared/ThemeBuilder.razor @@ -21,7 +21,7 @@ else { // theme does not exist with type specified - builder.OpenComponent(0, Type.GetType(Constants.ModuleMessageControl)); + builder.OpenComponent(0, Type.GetType(Constants.ModuleMessageComponent)); builder.AddAttribute(1, "Message", "Error Loading Page Theme " + PageState.Page.ThemeType); builder.CloseComponent(); } diff --git a/Oqtane.Client/Themes/Controls/ModuleTitle.razor b/Oqtane.Client/Themes/Controls/ModuleTitle.razor index 52184586..eec5bc28 100644 --- a/Oqtane.Client/Themes/Controls/ModuleTitle.razor +++ b/Oqtane.Client/Themes/Controls/ModuleTitle.razor @@ -10,9 +10,9 @@ { title = ModuleState.Title; // check for core module actions component - if (Constants.DefaultModuleActions.Contains(PageState.Control)) + if (Constants.DefaultModuleActions.Contains(PageState.Action)) { - title = PageState.Control; + title = PageState.Action; } return Task.CompletedTask; } diff --git a/Oqtane.Server/Logging/DBLoggerExtensions.cs b/Oqtane.Server/Logging/DBLoggerExtensions.cs new file mode 100644 index 00000000..16623d85 --- /dev/null +++ b/Oqtane.Server/Logging/DBLoggerExtensions.cs @@ -0,0 +1,36 @@ +using System; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Oqtane.Logging +{ + static public class DBLoggerExtensions + { + static public ILoggingBuilder AddDBLogger(this ILoggingBuilder builder) + { + builder.AddConfiguration(); + + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton()); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, DBLoggerOptionsSetup>()); + builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton, LoggerProviderOptionsChangeTokenSource>()); + return builder; + } + + static public ILoggingBuilder AddDBLogger(this ILoggingBuilder builder, Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + builder.AddDBLogger(); + builder.Services.Configure(configure); + + return builder; + } + } +} diff --git a/Oqtane.Server/Logging/DBLoggerOptions.cs b/Oqtane.Server/Logging/DBLoggerOptions.cs new file mode 100644 index 00000000..9c9edba0 --- /dev/null +++ b/Oqtane.Server/Logging/DBLoggerOptions.cs @@ -0,0 +1,13 @@ +using Microsoft.Extensions.Logging; + +namespace Oqtane.Logging +{ + public class DBLoggerOptions + { + public DBLoggerOptions() + { + } + + public LogLevel LogLevel { get; set; } = LogLevel.Information; + } +} diff --git a/Oqtane.Server/Logging/DBLoggerOptionsSetup.cs b/Oqtane.Server/Logging/DBLoggerOptionsSetup.cs new file mode 100644 index 00000000..fece7ba3 --- /dev/null +++ b/Oqtane.Server/Logging/DBLoggerOptionsSetup.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging.Configuration; + +namespace Oqtane.Logging +{ + internal class DBLoggerOptionsSetup : ConfigureFromConfigurationOptions + { + public DBLoggerOptionsSetup(ILoggerProviderConfiguration providerConfiguration) + : base(providerConfiguration.Configuration) {} + } +} diff --git a/Oqtane.Server/Logging/DBLoggerProvider.cs b/Oqtane.Server/Logging/DBLoggerProvider.cs new file mode 100644 index 00000000..b9feefc7 --- /dev/null +++ b/Oqtane.Server/Logging/DBLoggerProvider.cs @@ -0,0 +1,94 @@ +using System; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; +using Oqtane.Models; +using Oqtane.Repository; + +namespace Oqtane.Logging +{ + [Microsoft.Extensions.Logging.ProviderAlias("DB")] + public class DBLoggerProvider : LoggerProvider + { + bool Terminated; + ConcurrentQueue LogQueue = new ConcurrentQueue(); + private Tenant tenant; + internal DBLoggerOptions Settings { get; private set; } + + public DBLoggerProvider(ITenantResolver TenantResolver) + { + tenant = TenantResolver.GetTenant(); + } + + public override void WriteLog(LogEntry LogEntry) + { + // enrich with tenant information + LogQueue.Enqueue(LogEntry); + } + + void ThreadProc() + { + Task.Run(() => { + + while (!Terminated) + { + try + { + AddLogEntry(); + System.Threading.Thread.Sleep(100); + } + catch + { + } + } + + }); + } + + void AddLogEntry() + { + // check the LogQueue.Count to determine if a threshold has been reached + // then dequeue the items into temp storage based on TenantId and bulk insert them into the database + LogEntry logentry = null; + if (LogQueue.TryDequeue(out logentry)) + { + // convert logentry object to object which can be stored in database + } + + } + + protected override void Dispose(bool Disposing) + { + Terminated = true; + base.Dispose(Disposing); + } + + public DBLoggerProvider(IOptionsMonitor Settings) + : this(Settings.CurrentValue) + { + SettingsChangeToken = Settings.OnChange(settings => { + this.Settings = settings; + }); + } + + public DBLoggerProvider(DBLoggerOptions Settings) + { + this.Settings = Settings; + ThreadProc(); + } + + public override bool IsEnabled(LogLevel LogLevel) + { + bool Result = LogLevel != LogLevel.None + && this.Settings.LogLevel != LogLevel.None + && Convert.ToInt32(LogLevel) >= Convert.ToInt32(this.Settings.LogLevel); + + return Result; + } + + + + + } +} diff --git a/Oqtane.Server/Logging/LogEntry.cs b/Oqtane.Server/Logging/LogEntry.cs new file mode 100644 index 00000000..2e9ae122 --- /dev/null +++ b/Oqtane.Server/Logging/LogEntry.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Logging; +using Oqtane.Models; +using Oqtane.Repository; +using System; +using System.Collections.Generic; + +namespace Oqtane.Logging +{ + public class LogEntry + { + public LogEntry() + { + TimeStampUtc = DateTime.UtcNow; + UserName = Environment.UserName; + } + + static public readonly string StaticHostName = System.Net.Dns.GetHostName(); + + public string UserName { get; private set; } + public string HostName { get { return StaticHostName; } } + public DateTime TimeStampUtc { get; private set; } + public string Category { get; set; } + public LogLevel Level { get; set; } + public string Text { get; set; } + public Exception Exception { get; set; } + public EventId EventId { get; set; } + public object State { get; set; } + public string StateText { get; set; } + public Dictionary StateProperties { get; set; } + public List Scopes { get; set; } + } +} diff --git a/Oqtane.Server/Logging/LogScope.cs b/Oqtane.Server/Logging/LogScope.cs new file mode 100644 index 00000000..8c3de6ce --- /dev/null +++ b/Oqtane.Server/Logging/LogScope.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; + +namespace Oqtane.Logging +{ + public class LogScope + { + public string Text { get; set; } + public Dictionary Properties { get; set; } + } +} diff --git a/Oqtane.Server/Logging/Logger.cs b/Oqtane.Server/Logging/Logger.cs new file mode 100644 index 00000000..648cb885 --- /dev/null +++ b/Oqtane.Server/Logging/Logger.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using Microsoft.Extensions.Logging; + +namespace Oqtane.Logging +{ + internal class Logger : ILogger + { + public LoggerProvider Provider { get; private set; } + public string Category { get; private set; } + + public Logger(LoggerProvider Provider, string Category) + { + this.Provider = Provider; + this.Category = Category; + } + + IDisposable ILogger.BeginScope(TState State) + { + return Provider.ScopeProvider.Push(State); + } + + bool ILogger.IsEnabled(LogLevel LogLevel) + { + return Provider.IsEnabled(LogLevel); + } + + void ILogger.Log(LogLevel LogLevel, EventId EventId, TState State, Exception Exception, Func Formatter) + { + if ((this as ILogger).IsEnabled(LogLevel)) + { + + LogEntry logentry = new LogEntry(); + // we need the TenantId and SiteId + logentry.Category = this.Category; + logentry.Level = LogLevel; + logentry.Text = Exception?.Message ?? State.ToString(); + logentry.Exception = Exception; + logentry.EventId = EventId; + logentry.State = State; + + if (State is string) + { + logentry.StateText = State.ToString(); + } + else if (State is IEnumerable> Properties) + { + logentry.StateProperties = new Dictionary(); + + foreach (KeyValuePair item in Properties) + { + logentry.StateProperties[item.Key] = item.Value; + } + } + + if (Provider.ScopeProvider != null) + { + Provider.ScopeProvider.ForEachScope((value, loggingProps) => + { + if (logentry.Scopes == null) + { + logentry.Scopes = new List(); + } + + LogScope Scope = new LogScope(); + logentry.Scopes.Add(Scope); + + if (value is string) + { + Scope.Text = value.ToString(); + } + else if (value is IEnumerable> props) + { + if (Scope.Properties == null) + Scope.Properties = new Dictionary(); + + foreach (var pair in props) + { + Scope.Properties[pair.Key] = pair.Value; + } + } + }, State); + + } + + Provider.WriteLog(logentry); + } + } + + } +} \ No newline at end of file diff --git a/Oqtane.Server/Logging/LoggerProvider.cs b/Oqtane.Server/Logging/LoggerProvider.cs new file mode 100644 index 00000000..10385aac --- /dev/null +++ b/Oqtane.Server/Logging/LoggerProvider.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; + +namespace Oqtane.Logging +{ + public abstract class LoggerProvider : IDisposable, ILoggerProvider, ISupportExternalScope + { + ConcurrentDictionary loggers = new ConcurrentDictionary(); + IExternalScopeProvider fScopeProvider; + protected IDisposable SettingsChangeToken; + + void ISupportExternalScope.SetScopeProvider(IExternalScopeProvider ScopeProvider) + { + fScopeProvider = ScopeProvider; + } + + ILogger ILoggerProvider.CreateLogger(string Category) + { + return loggers.GetOrAdd(Category, + (category) => { + return new Logger(this, category); + }); + } + + void IDisposable.Dispose() + { + if (!this.IsDisposed) + { + try + { + Dispose(true); + } + catch + { + } + + this.IsDisposed = true; + GC.SuppressFinalize(this); + } + } + + protected virtual void Dispose(bool Disposing) + { + if (SettingsChangeToken != null) + { + SettingsChangeToken.Dispose(); + SettingsChangeToken = null; + } + } + + public LoggerProvider() + { + } + + ~LoggerProvider() + { + if (!this.IsDisposed) + { + Dispose(false); + } + } + + public abstract bool IsEnabled(LogLevel LogLevel); + + public abstract void WriteLog(LogEntry LogEntry); + + internal IExternalScopeProvider ScopeProvider + { + get + { + if (fScopeProvider == null) + fScopeProvider = new LoggerExternalScopeProvider(); + return fScopeProvider; + } + } + + public bool IsDisposed { get; protected set; } + } +} \ No newline at end of file diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj index fca66b6f..400acb30 100644 --- a/Oqtane.Server/Oqtane.Server.csproj +++ b/Oqtane.Server/Oqtane.Server.csproj @@ -43,6 +43,7 @@ + diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index ab8c1d9d..e86c6213 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -6,6 +6,7 @@ using System.Reflection; using System; using Oqtane.Modules; using Microsoft.Extensions.Caching.Memory; +using Oqtane.Shared; namespace Oqtane.Repository { @@ -120,7 +121,7 @@ namespace Oqtane.Repository Dependencies = GetProperty(properties, "Dependencies"), PermissionNames = GetProperty(properties, "PermissionNames"), ServerAssemblyName = GetProperty(properties, "ServerAssemblyName"), - ControlTypeTemplate = ModuleType + ".{Control}" + ", " + typename[1], + ControlTypeTemplate = ModuleType + "." + Constants.ActionToken + ", " + typename[1], ControlTypeRoutes = "", AssemblyName = assembly.FullName.Split(",")[0] }; @@ -141,7 +142,7 @@ namespace Oqtane.Repository Dependencies = "", PermissionNames = "", ServerAssemblyName = "", - ControlTypeTemplate = ModuleType + ".{Control}" + ", " + typename[1], + ControlTypeTemplate = ModuleType + "." + Constants.ActionToken + ", " + typename[1], ControlTypeRoutes = "", AssemblyName = assembly.FullName.Split(",")[0] }; diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index d168b592..faf458f0 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -13,15 +13,17 @@ public const string DefaultContainer = "Oqtane.Themes.BlazorTheme.Container, Oqtane.Client"; public const string DefaultAdminContainer = "Oqtane.Themes.AdminContainer, Oqtane.Client"; + public const string ActionToken = "{Action}"; + public const string DefaultAction = "Index"; + public const string AdminPane = "Admin"; + // Default Module Actions are reserved and should not be used by modules - public static readonly string[] DefaultModuleActions = new[] { "Settings", "Import", "Export" }; - public const string DefaultModuleActionsTemplate = "Oqtane.Modules.Admin.Modules.{Control}, Oqtane.Client"; + public static readonly string[] DefaultModuleActions = new[] { "Settings", "Import", "Export" }; + public static readonly string DefaultModuleActionsTemplate = "Oqtane.Modules.Admin.Modules." + ActionToken + ", Oqtane.Client"; + public const string AdminDashboardModule = "Oqtane.Modules.Admin.Dashboard, Oqtane.Client"; public const string PageManagementModule = "Oqtane.Modules.Admin.Pages, Oqtane.Client"; - public const string ModuleMessageControl = "Oqtane.Modules.Controls.ModuleMessage, Oqtane.Client"; - - public const string DefaultControl = "Index"; - public const string AdminPane = "Admin"; + public const string ModuleMessageComponent = "Oqtane.Modules.Controls.ModuleMessage, Oqtane.Client"; public const string AllUsersRole = "All Users"; public const string HostRole = "Host Users";