Skip to content

Commit

Permalink
feat: enable disabling pre-startup logging (#181)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alxandr authored Jan 8, 2025
1 parent ec4c1fe commit 64e776a
Show file tree
Hide file tree
Showing 3 changed files with 184 additions and 29 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
using Microsoft.Extensions.Configuration;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;

namespace Altinn.Authorization.ServiceDefaults;

/// <summary>
/// A pre-start logger for Altinn services.
/// </summary>
public sealed class AltinnPreStartLogger
{
private static readonly Action<string> _writeToStdout = Console.Out.WriteLine;

/// <summary>
/// Gets the configuration key for disabling the pre-start logger.
/// </summary>
public static string DisableConfigKey { get; } = "Altinn:Logging:PreStart:Disable";

/// <summary>
/// Creates a new instance of the <see cref="AltinnPreStartLogger"/> class.
/// </summary>
/// <typeparam name="T">The name of the logger.</typeparam>
/// <param name="config">The <see cref="IConfiguration"/> to use for the logger.</param>
/// <returns>A new <see cref="AltinnPreStartLogger"/>.</returns>
public static AltinnPreStartLogger Create<T>(IConfiguration config)
=> Create(config, typeof(T).Name);

/// <summary>
/// Creates a new instance of the <see cref="AltinnPreStartLogger"/> class.
/// </summary>
/// <param name="config">The <see cref="IConfiguration"/> to use for the logger.</param>
/// <param name="name">The name of the logger.</param>
/// <returns>A new <see cref="AltinnPreStartLogger"/>.</returns>
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;
}

/// <summary>
/// Logs the specified message.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="callerMemberName">See <see cref="CallerMemberNameAttribute"/>.</param>
public void Log(string message, [CallerMemberName] string callerMemberName = "")
{
var handler = new LogHandler(message.Length, 0, this, callerMemberName);
handler.AppendLiteral(message);
handler.LogTo(_writeToStdout);
}

/// <summary>
/// Logs the specified message.
/// </summary>
/// <param name="handler">The message.</param>
public void Log([InterpolatedStringHandlerArgument("")] ref LogHandler handler)
{
handler.LogTo(_writeToStdout);
}

/// <summary>
/// Provides a handler used by the language compiler to log efficiently.
/// </summary>
[InterpolatedStringHandler]
[EditorBrowsable(EditorBrowsableState.Never)]
public struct LogHandler
{
StringBuilder? _builder;
StringBuilder.AppendInterpolatedStringHandler? _formatter;

/// <summary>
/// Initializes a new instance of the <see cref="LogHandler"/> struct.
/// </summary>
/// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
/// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
/// <param name="logger">The logger to log to.</param>
/// <param name="callerMemberName">The caller member name.</param>
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);
}
}

/// <summary>
/// Logs the message to the logger.
/// </summary>
/// <param name="log">The log write method.</param>
internal void LogTo(Action<string> log)
{
if (_builder is { } builder)
{
log(builder.ToString());
}
}

/// <summary>
/// Writes the specified string to the handler.
/// </summary>
/// <param name="literal">The string to write.</param>
public void AppendLiteral(string literal)
{
_formatter?.AppendLiteral(literal);
}

/// <summary>
/// Writes the specified value to the handler.
/// </summary>
/// <typeparam name="T">The type of the value to write.</typeparam>
/// <param name="value">The value to write.</param>
public void AppendFormatted<T>(T value)
{
_formatter?.AppendFormatted(value);
}

/// <summary>
/// Writes the specified value to the handler.
/// </summary>
/// <typeparam name="T">The type of the value to write.</typeparam>
/// <param name="value">The value to write.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted<T>(T value, string? format)
{
_formatter?.AppendFormatted(value, format);
}

/// <summary>
/// Writes the specified character span to the handler.
/// </summary>
/// <param name="value">The span to write.</param>
public void AppendFormatted(ReadOnlySpan<char> value)
{
_formatter?.AppendFormatted(value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<bool>("Altinn:LocalDev");

Expand Down Expand Up @@ -108,17 +111,18 @@ public static IHostApplicationBuilder AddAltinnServiceDefaults(this IHostApplica
/// <returns><paramref name="app"/>.</returns>
public static WebApplication AddDefaultAltinnMiddleware(this WebApplication app, string errorHandlingPath)
{
Log("Startup // Configure");
var logger = app.Services.GetRequiredService<ILoggerFactory>().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);
}
Expand Down Expand Up @@ -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<string>("ApplicationInsights:InstrumentationKey");

Expand All @@ -358,30 +362,30 @@ private static IHostApplicationBuilder AddApplicationInsights(this IHostApplicat
builder.Services.AddApplicationInsightsTelemetryProcessor<ApplicationInsightsRequestTelemetryEnricherProcessor>();
builder.Services.AddSingleton<ITelemetryInitializer, AltinnServiceTelemetryInitializer>();

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;
}

Expand All @@ -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<string>("kvSetting:ClientId");
var tenantId = manager.GetValue<string>("kvSetting:TenantId");
Expand All @@ -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,
Expand All @@ -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;
}

Expand All @@ -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}");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
$$"""
{
Expand Down

0 comments on commit 64e776a

Please sign in to comment.