From 64e776abd7a382b7fd1f9b123b6b923ee903f7b5 Mon Sep 17 00:00:00 2001 From: Aleksander Heintz Date: Wed, 8 Jan 2025 15:22:57 +0100 Subject: [PATCH] feat: enable disabling pre-startup logging (#181) --- .../ServiceDefaults/AltinnPreStartLogger.cs | 150 ++++++++++++++++++ .../AltinnServiceDefaultsExtensions.cs | 55 +++---- .../DatabaseTestsBase.AppContext.Builder.cs | 8 + 3 files changed, 184 insertions(+), 29 deletions(-) create mode 100644 src/Altinn.Authorization.ServiceDefaults/src/ServiceDefaults/AltinnPreStartLogger.cs diff --git a/src/Altinn.Authorization.ServiceDefaults/src/ServiceDefaults/AltinnPreStartLogger.cs b/src/Altinn.Authorization.ServiceDefaults/src/ServiceDefaults/AltinnPreStartLogger.cs new file mode 100644 index 0000000..c8af536 --- /dev/null +++ b/src/Altinn.Authorization.ServiceDefaults/src/ServiceDefaults/AltinnPreStartLogger.cs @@ -0,0 +1,150 @@ +using Microsoft.Extensions.Configuration; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Text; + +namespace Altinn.Authorization.ServiceDefaults; + +/// +/// A pre-start logger for Altinn services. +/// +public sealed class AltinnPreStartLogger +{ + private static readonly Action _writeToStdout = Console.Out.WriteLine; + + /// + /// Gets the configuration key for disabling the pre-start logger. + /// + public static string DisableConfigKey { get; } = "Altinn:Logging:PreStart:Disable"; + + /// + /// Creates a new instance of the class. + /// + /// The name of the logger. + /// The to use for the logger. + /// A new . + public static AltinnPreStartLogger Create(IConfiguration config) + => Create(config, typeof(T).Name); + + /// + /// Creates a new instance of the class. + /// + /// The to use for the logger. + /// The name of the logger. + /// A new . + public static AltinnPreStartLogger Create(IConfiguration config, string name) + { + var disabled = config.GetValue(DisableConfigKey, defaultValue: false); + + return new(name, !disabled); + } + + private readonly string _name; + private readonly bool _enabled; + + private AltinnPreStartLogger(string name, bool enabled) + { + _name = name; + _enabled = enabled; + } + + /// + /// Logs the specified message. + /// + /// The message. + /// See . + public void Log(string message, [CallerMemberName] string callerMemberName = "") + { + var handler = new LogHandler(message.Length, 0, this, callerMemberName); + handler.AppendLiteral(message); + handler.LogTo(_writeToStdout); + } + + /// + /// Logs the specified message. + /// + /// The message. + public void Log([InterpolatedStringHandlerArgument("")] ref LogHandler handler) + { + handler.LogTo(_writeToStdout); + } + + /// + /// Provides a handler used by the language compiler to log efficiently. + /// + [InterpolatedStringHandler] + [EditorBrowsable(EditorBrowsableState.Never)] + public struct LogHandler + { + StringBuilder? _builder; + StringBuilder.AppendInterpolatedStringHandler? _formatter; + + /// + /// Initializes a new instance of the struct. + /// + /// The number of constant characters outside of interpolation expressions in the interpolated string. + /// The number of interpolation expressions in the interpolated string. + /// The logger to log to. + /// The caller member name. + public LogHandler(int literalLength, int formattedCount, AltinnPreStartLogger logger, [CallerMemberName] string callerMemberName = "") + { + if (logger._enabled) + { + _builder = new StringBuilder(literalLength * 10); + _builder.Append("// ").Append(logger._name).Append('.').Append(callerMemberName).Append(": "); + _formatter = new(literalLength, formattedCount, _builder); + } + } + + /// + /// Logs the message to the logger. + /// + /// The log write method. + internal void LogTo(Action log) + { + if (_builder is { } builder) + { + log(builder.ToString()); + } + } + + /// + /// Writes the specified string to the handler. + /// + /// The string to write. + public void AppendLiteral(string literal) + { + _formatter?.AppendLiteral(literal); + } + + /// + /// Writes the specified value to the handler. + /// + /// The type of the value to write. + /// The value to write. + public void AppendFormatted(T value) + { + _formatter?.AppendFormatted(value); + } + + /// + /// Writes the specified value to the handler. + /// + /// The type of the value to write. + /// The value to write. + /// The format string. + public void AppendFormatted(T value, string? format) + { + _formatter?.AppendFormatted(value, format); + } + + /// + /// Writes the specified character span to the handler. + /// + /// The span to write. + public void AppendFormatted(ReadOnlySpan value) + { + _formatter?.AppendFormatted(value); + } + } +} diff --git a/src/Altinn.Authorization.ServiceDefaults/src/ServiceDefaults/Microsoft.Extensions.Hosting/AltinnServiceDefaultsExtensions.cs b/src/Altinn.Authorization.ServiceDefaults/src/ServiceDefaults/Microsoft.Extensions.Hosting/AltinnServiceDefaultsExtensions.cs index 20e8e00..1b59dc7 100644 --- a/src/Altinn.Authorization.ServiceDefaults/src/ServiceDefaults/Microsoft.Extensions.Hosting/AltinnServiceDefaultsExtensions.cs +++ b/src/Altinn.Authorization.ServiceDefaults/src/ServiceDefaults/Microsoft.Extensions.Hosting/AltinnServiceDefaultsExtensions.cs @@ -22,8 +22,10 @@ using OpenTelemetry.Logs; using OpenTelemetry.Metrics; using OpenTelemetry.Trace; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using System.Text; namespace Microsoft.Extensions.Hosting; @@ -56,11 +58,12 @@ public static IHostApplicationBuilder AddAltinnServiceDefaults(this IHostApplica return builder; } - builder.AddAltinnConfiguration(); + var logger = AltinnPreStartLogger.Create(builder.Configuration, nameof(AltinnServiceDefaultsExtensions)); + builder.AddAltinnConfiguration(logger); // Note - this has to happen early due to a bug in Application Insights // See: https://github.com/microsoft/ApplicationInsights-dotnet/issues/2879 - builder.AddApplicationInsights(); + builder.AddApplicationInsights(logger); var isLocalDevelopment = builder.Environment.IsDevelopment() && builder.Configuration.GetValue("Altinn:LocalDev"); @@ -108,17 +111,18 @@ public static IHostApplicationBuilder AddAltinnServiceDefaults(this IHostApplica /// . public static WebApplication AddDefaultAltinnMiddleware(this WebApplication app, string errorHandlingPath) { - Log("Startup // Configure"); + var logger = app.Services.GetRequiredService().CreateLogger(nameof(AltinnServiceDefaultsExtensions)); + logger.LogInformation("Startup // Configure"); if (app.Environment.IsDevelopment() || app.Environment.IsStaging()) { - Log("IsDevelopment || IsStaging => Using developer exception page"); + logger.LogInformation("IsDevelopment || IsStaging => Using developer exception page"); app.UseDeveloperExceptionPage(); } else { - Log("Production => Using exception handler"); + logger.LogInformation("Production => Using exception handler"); app.UseExceptionHandler(errorHandlingPath); } @@ -334,7 +338,7 @@ private static IHostApplicationBuilder AddDefaultHealthChecks(this IHostApplicat return builder; } - private static IHostApplicationBuilder AddApplicationInsights(this IHostApplicationBuilder builder) + private static IHostApplicationBuilder AddApplicationInsights(this IHostApplicationBuilder builder, AltinnPreStartLogger logger) { var applicationInsightsInstrumentationKey = builder.Configuration.GetValue("ApplicationInsights:InstrumentationKey"); @@ -358,30 +362,30 @@ private static IHostApplicationBuilder AddApplicationInsights(this IHostApplicat builder.Services.AddApplicationInsightsTelemetryProcessor(); builder.Services.AddSingleton(); - Log($"ApplicationInsightsConnectionString = {applicationInsightsConnectionString}"); + logger.Log($"ApplicationInsightsConnectionString = {applicationInsightsConnectionString}"); } else { - Log("No ApplicationInsights:InstrumentationKey found - skipping Application Insights"); + logger.Log("No ApplicationInsights:InstrumentationKey found - skipping Application Insights"); } return builder; } - private static IHostApplicationBuilder AddAltinnConfiguration(this IHostApplicationBuilder builder) + private static IHostApplicationBuilder AddAltinnConfiguration(this IHostApplicationBuilder builder, AltinnPreStartLogger logger) { - builder.Configuration.AddAltinnDbSecretsJson(); - builder.Configuration.AddAltinnKeyVault(); + builder.Configuration.AddAltinnDbSecretsJson(logger); + builder.Configuration.AddAltinnKeyVault(logger); return builder; } - private static IConfigurationBuilder AddAltinnDbSecretsJson(this IConfigurationBuilder builder) + private static IConfigurationBuilder AddAltinnDbSecretsJson(this IConfigurationBuilder builder, AltinnPreStartLogger logger) { var parentDir = Path.GetDirectoryName(Environment.CurrentDirectory); if (parentDir is null) { - Log("No parent directory found - skipping altinn-dbsettings-secret.json"); + logger.Log("No parent directory found - skipping altinn-dbsettings-secret.json"); return builder; } @@ -392,16 +396,16 @@ private static IConfigurationBuilder AddAltinnDbSecretsJson(this IConfigurationB if (!File.Exists(altinnDbSecretsConfigFile)) { - Log($"No altinn-dbsettings-secret.json found at \"{altinnDbSecretsConfigFile}\" - skipping altinn-dbsettings-secret.json"); + logger.Log($"No altinn-dbsettings-secret.json found at \"{altinnDbSecretsConfigFile}\" - skipping altinn-dbsettings-secret.json"); return builder; } - Log($"Loading configuration from \"{altinnDbSecretsConfigFile}\""); + logger.Log($"Loading configuration from \"{altinnDbSecretsConfigFile}\""); builder.AddJsonFile(altinnDbSecretsConfigFile, optional: false, reloadOnChange: true); return builder; } - private static IConfigurationBuilder AddAltinnKeyVault(this IConfigurationManager manager) + private static IConfigurationBuilder AddAltinnKeyVault(this IConfigurationManager manager, AltinnPreStartLogger logger) { var clientId = manager.GetValue("kvSetting:ClientId"); var tenantId = manager.GetValue("kvSetting:TenantId"); @@ -419,7 +423,7 @@ private static IConfigurationBuilder AddAltinnKeyVault(this IConfigurationManage && !string.IsNullOrEmpty(tenantId) && !string.IsNullOrEmpty(clientSecret)) { - Log($"adding config from keyvault using client-secret credentials"); + logger.Log($"adding config from keyvault using client-secret credentials"); credentialList.Add(new ClientSecretCredential( tenantId: tenantId, clientId: clientId, @@ -428,25 +432,25 @@ private static IConfigurationBuilder AddAltinnKeyVault(this IConfigurationManage if (enableEnvironmentCredential) { - Log("adding config from keyvault using environment credentials"); + logger.Log("adding config from keyvault using environment credentials"); credentialList.Add(new EnvironmentCredential()); } if (enableWorkloadIdentityCredential) { - Log("adding config from keyvault using workload identity credentials"); + logger.Log("adding config from keyvault using workload identity credentials"); credentialList.Add(new WorkloadIdentityCredential()); } if (enableManagedIdentityCredential) { - Log("adding config from keyvault using managed identity credentials"); + logger.Log("adding config from keyvault using managed identity credentials"); credentialList.Add(new ManagedIdentityCredential()); } if (credentialList.Count == 0) { - Log("No credentials found for keyvault - skipping adding keyvault to configuration"); + logger.Log("No credentials found for keyvault - skipping adding keyvault to configuration"); return manager; } @@ -456,16 +460,9 @@ private static IConfigurationBuilder AddAltinnKeyVault(this IConfigurationManage } else { - Log($"Missing keyvault settings - skipping adding keyvault to configuration"); + logger.Log($"Missing keyvault settings - skipping adding keyvault to configuration"); } return manager; } - - private static void Log( - string message, - [CallerMemberName] string callerMemberName = "") - { - Console.WriteLine($"// {nameof(AltinnServiceDefaultsExtensions)}.{callerMemberName}: {message}"); - } } diff --git a/src/Altinn.Authorization.ServiceDefaults/test/ServiceDefaults.Npgsql.Tests/DatabaseTestsBase.AppContext.Builder.cs b/src/Altinn.Authorization.ServiceDefaults/test/ServiceDefaults.Npgsql.Tests/DatabaseTestsBase.AppContext.Builder.cs index 3907857..f3312f7 100644 --- a/src/Altinn.Authorization.ServiceDefaults/test/ServiceDefaults.Npgsql.Tests/DatabaseTestsBase.AppContext.Builder.cs +++ b/src/Altinn.Authorization.ServiceDefaults/test/ServiceDefaults.Npgsql.Tests/DatabaseTestsBase.AppContext.Builder.cs @@ -45,6 +45,14 @@ protected AppContext.Builder CreateBuilder() var appConnectionString = builder.ConnectionString; var configuration = new ConfigurationManager(); + + if (!Debugger.IsAttached) + { + configuration.AddInMemoryCollection([ + new(AltinnPreStartLogger.DisableConfigKey, "true"), + ]); + } + configuration.AddJsonConfiguration( $$""" {