diff --git a/.github/workflows/pipelines.yml b/.github/workflows/pipelines.yml index 608db0c..725fda9 100644 --- a/.github/workflows/pipelines.yml +++ b/.github/workflows/pipelines.yml @@ -18,6 +18,9 @@ on: - Debug - Release +permissions: + contents: read + jobs: build: name: 🛠️ Build @@ -130,6 +133,8 @@ jobs: name: call-codeql needs: [build,test] uses: codebeltnet/jobs-codeql/.github/workflows/default.yml@v1 + permissions: + security-events: write deploy: if: github.event_name != 'pull_request' @@ -137,6 +142,9 @@ jobs: runs-on: ubuntu-24.04 needs: [build, pack, test, sonarcloud, codecov, codeql] environment: Production + permissions: + contents: write + packages: write steps: - uses: codebeltnet/nuget-push@v1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index a71d378..813554e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder. -## [4.0.0] - TBD +## [4.0.0] - 2025-04-12 This major release revisits and refines some of the earlier design decisions to offer a more consistent and flexible API. It also brings forward improvements to reliability and maintainability. diff --git a/Directory.Build.props b/Directory.Build.props index 6e78383..1ffd242 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -32,6 +32,9 @@ true true $(MSBuildThisFileDirectory)bootstrapper.snk + true + latest + Recommended diff --git a/Directory.Packages.props b/Directory.Packages.props index 3839aae..c916437 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ - + diff --git a/app/Codebelt.Bootstrapper.WebApp.App/Properties/launchSettings.json b/app/Codebelt.Bootstrapper.WebApp.App/Properties/launchSettings.json index 7d5d862..f8cb865 100644 --- a/app/Codebelt.Bootstrapper.WebApp.App/Properties/launchSettings.json +++ b/app/Codebelt.Bootstrapper.WebApp.App/Properties/launchSettings.json @@ -1,14 +1,14 @@ { "profiles": { "Codebelt.Bootstrapper.WebApp.App": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "LocalDevelopment", - "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" - } + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "applicationUrl": "https://localhost:7173;http://localhost:5249", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "LocalDevelopment", + "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" + } } } } diff --git a/app/Codebelt.Bootstrapper.WebMvc.App/Properties/launchSettings.json b/app/Codebelt.Bootstrapper.WebMvc.App/Properties/launchSettings.json index 9f66391..fa94b70 100644 --- a/app/Codebelt.Bootstrapper.WebMvc.App/Properties/launchSettings.json +++ b/app/Codebelt.Bootstrapper.WebMvc.App/Properties/launchSettings.json @@ -1,14 +1,14 @@ { "profiles": { "Codebelt.Bootstrapper.WebMvc.App": { - "commandName": "Project", - "dotnetRunMessages": "true", - "launchBrowser": true, - "applicationUrl": "https://localhost:5001;http://localhost:5000", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "LocalDevelopment", - "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" - } + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "applicationUrl": "https://localhost:7173;http://localhost:5249", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "LocalDevelopment", + "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" + } } } } diff --git a/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs b/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs index 02f3396..896e5a4 100644 --- a/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs +++ b/src/Codebelt.Bootstrapper.Console/ConsoleHostedService.cs @@ -1,9 +1,10 @@ -using System; -using System.Threading; -using System.Threading.Tasks; +using Cuemon; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; +using System; +using System.Threading; +using System.Threading.Tasks; namespace Codebelt.Bootstrapper.Console { @@ -55,13 +56,13 @@ public Task StartAsync(CancellationToken cancellationToken) { try { - _logger.LogInformation("RunAsync started."); + Decorator.EncloseToExpose(_logger, false).RunAsyncStarted(); await startup.RunAsync(_provider, cancellationToken).ConfigureAwait(false); _ranToCompletion = true; } catch (Exception e) { - _logger.LogCritical(e, "Fatal error occurred while activating {TypeFullName}.", typeof(TStartup).FullName); + Decorator.EncloseToExpose(_logger, false).FatalErrorActivating(typeof(TStartup).FullName, e); } }, cancellationToken); @@ -70,7 +71,7 @@ public Task StartAsync(CancellationToken cancellationToken) } else { - _logger.LogWarning("Unable to activate an instance of {TypeFullName}.", typeof(TStartup).FullName); + Decorator.EncloseToExpose(_logger, false).UnableToActivateInstance(typeof(TStartup).FullName); } return Task.CompletedTask; @@ -91,11 +92,11 @@ public Task StopAsync(CancellationToken cancellationToken) { if (!_ranToCompletion) { - _logger?.LogInformation("RunAsync ended prematurely."); + Decorator.EncloseToExpose(_logger, false)?.RunAsyncPrematureEnd(); } else { - _logger?.LogInformation("RunAsync completed successfully."); + Decorator.EncloseToExpose(_logger, false)?.RunAsyncCompleted(); } return Task.CompletedTask; diff --git a/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs b/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs index 67e68c9..0596bb0 100644 --- a/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs +++ b/src/Codebelt.Bootstrapper.Console/MinimalConsoleHostedService.cs @@ -4,6 +4,7 @@ using System; using System.Threading; using System.Threading.Tasks; +using Cuemon; namespace Codebelt.Bootstrapper.Console { @@ -57,18 +58,18 @@ public Task StartAsync(CancellationToken cancellationToken) { if (program != null) { - _logger.LogInformation("RunAsync started."); + Decorator.EncloseToExpose(_logger, false).RunAsyncStarted(); await program.RunAsync(_provider, cancellationToken).ConfigureAwait(false); _ranToCompletion = true; } else { - _logger.LogWarning("Unable to activate an instance of {TypeFullName}.", programType.FullName); + Decorator.EncloseToExpose(_logger, false).UnableToActivateInstance(programType.FullName); } } catch (Exception e) { - _logger.LogCritical(e, "Fatal error occurred while activating {TypeFullName}.", programType.FullName); + Decorator.EncloseToExpose(_logger, false).FatalErrorActivating(programType.FullName, e); } }, cancellationToken); @@ -93,11 +94,11 @@ public Task StopAsync(CancellationToken cancellationToken) { if (!_ranToCompletion) { - _logger?.LogInformation("RunAsync ended prematurely."); + Decorator.EncloseToExpose(_logger, false)?.RunAsyncPrematureEnd(); } else { - _logger?.LogInformation("RunAsync completed successfully."); + Decorator.EncloseToExpose(_logger, false)?.RunAsyncCompleted(); } return Task.CompletedTask; diff --git a/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs b/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs index 24a5160..31be448 100644 --- a/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs +++ b/src/Codebelt.Bootstrapper/BootstrapperLifetime.cs @@ -87,7 +87,7 @@ private void OnApplicationStopping() } /// - /// Called when this object is being disposed by either or having disposing set to true and is false. + /// Called when this object is being disposed by either or having disposing set to true and is false. /// protected override void OnDisposeManagedResources() { diff --git a/src/Codebelt.Bootstrapper/BootstrapperLogMessages.cs b/src/Codebelt.Bootstrapper/BootstrapperLogMessages.cs new file mode 100644 index 0000000..92eb249 --- /dev/null +++ b/src/Codebelt.Bootstrapper/BootstrapperLogMessages.cs @@ -0,0 +1,73 @@ +using System; +using Cuemon; +using Microsoft.Extensions.Logging; + +namespace Codebelt.Bootstrapper +{ + /// + /// Provides centralized logging messages for the Bootstrapper SDK using the LoggerMessage pattern. + /// This API supports the product infrastructure and is not intended to be used directly from your code. + /// + /// https://learn.microsoft.com/en-us/dotnet/core/extensions/high-performance-logging + public static class BootstrapperLogMessages + { + // Information messages + private static readonly Action RunAsyncStartedDefinition = LoggerMessage.Define(LogLevel.Information, new EventId(1000, nameof(RunAsyncStarted)), "RunAsync started."); + private static readonly Action RunAsyncPrematureEndDefinition = LoggerMessage.Define(LogLevel.Information, new EventId(1001, nameof(RunAsyncPrematureEnd)), "RunAsync ended prematurely."); + private static readonly Action RunAsyncCompletedDefinition = LoggerMessage.Define(LogLevel.Information, new EventId(1002, nameof(RunAsyncCompleted)), "RunAsync completed successfully."); + + // Warning messages + private static readonly Action UnableToActivateInstanceDefinition = LoggerMessage.Define(LogLevel.Warning, new EventId(2000, nameof(UnableToActivateInstance)), "Unable to activate an instance of {TypeFullName}."); + + // Critical messages + private static readonly Action FatalErrorActivatingDefinition = LoggerMessage.Define(LogLevel.Critical, new EventId(3000, nameof(FatalErrorActivating)), "Fatal error occurred while activating {TypeFullName}."); + + /// + /// Logs that RunAsync has started. + /// + /// The logger instance. + public static void RunAsyncStarted(this IDecorator logger) + { + RunAsyncStartedDefinition(logger.Inner, null); + } + + /// + /// Logs that RunAsync ended prematurely. + /// + /// The logger instance. + public static void RunAsyncPrematureEnd(this IDecorator logger) + { + RunAsyncPrematureEndDefinition(logger.Inner, null); + } + + /// + /// Logs that RunAsync completed successfully. + /// + /// The logger instance. + public static void RunAsyncCompleted(this IDecorator logger) + { + RunAsyncCompletedDefinition(logger.Inner, null); + } + + /// + /// Logs an inability to activate the specified type. + /// + /// The logger instance. + /// The full name of the type that couldn't be activated. + public static void UnableToActivateInstance(this IDecorator logger, string typeFullName) + { + UnableToActivateInstanceDefinition(logger.Inner, typeFullName, null); + } + + /// + /// Logs a fatal error that occurred while activating a type. + /// + /// The logger instance. + /// The full name of the type being activated. + /// The exception that occurred. + public static void FatalErrorActivating(this IDecorator logger, string typeFullName, Exception exception) + { + FatalErrorActivatingDefinition(logger.Inner, typeFullName, exception); + } + } +}