Skip to content

Instantly share code, notes, and snippets.

@meixger
Created May 2, 2022 09:59
Show Gist options
  • Save meixger/c3f0bb3e4b64a9915ed16692743c5962 to your computer and use it in GitHub Desktop.
Save meixger/c3f0bb3e4b64a9915ed16692743c5962 to your computer and use it in GitHub Desktop.
Azure App Service RoleName for Serilog
/// <summary>
/// We need to check the App Service Kudo header WAS-DEFAULT-HOSTNAME.
/// The environment variable "WEBSITE_HOSTNAME" will not be updated to production after swapping App Service slots.
/// </summary>
public class RoleNameContainer
{
// See internal class https://github.com/microsoft/ApplicationInsights-dotnet/blob/2.19.0/NETCORE/src/Microsoft.ApplicationInsights.AspNetCore/Implementation/RoleNameContainer.cs
// https://github.com/microsoft/ApplicationInsights-dotnet/blob/51dc14afaf7de2b6902a10d6518d2d400cb61f67/NETCORE/src/Microsoft.ApplicationInsights.AspNetCore/DiagnosticListeners/Implementation/HostingDiagnosticListener.cs#L192
private const string WebAppHostNameHeaderName = "WAS-DEFAULT-HOSTNAME";
private const string WebAppHostNameEnvironmentVariable = "WEBSITE_HOSTNAME";
private string roleName = string.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="Microsoft.ApplicationInsights.AspNetCore.Implementation.RoleNameContainer"/> class.
/// Will set the RoleName based on an environment variable.
/// </summary>
/// <param name="hostNameSuffix">Host name suffix will be used to parse the prefix from the host name. The value of the prefix is the RoleName.</param>
internal RoleNameContainer(string hostNameSuffix = ".azurewebsites.net")
{
this.HostNameSuffix = hostNameSuffix;
var enVarValue = Environment.GetEnvironmentVariable(WebAppHostNameEnvironmentVariable);
this.ParseAndSetRoleName(enVarValue);
this.IsAzureWebApp = !string.IsNullOrEmpty(enVarValue);
}
/// <summary>
/// Gets or sets static instance for Initializer and DiagnosticListener to share access to RoleName variable.
/// </summary>
public static RoleNameContainer Instance { get; set; }
/// <summary>
/// Gets or sets role name of the current application.
/// </summary>
public string RoleName
{
get => this.roleName;
set
{
if (value != this.roleName)
{
Interlocked.Exchange(ref this.roleName, value);
}
}
}
/// <summary>
/// Gets a value indicating whether indicates if the current app is an Azure Web App based on the presence of a specific environment variable. Set in constructor.
/// </summary>
public bool IsAzureWebApp { get; private set; }
/// <summary>
/// Gets suffix of website name. This must be changed when running in non public Azure region.
/// Default value (Public Cloud): ".azurewebsites.net"
/// For US Gov Cloud: ".azurewebsites.us"
/// For Azure Germany: ".azurewebsites.de".
/// </summary>
public string HostNameSuffix { get; private set; }
/// <summary>
/// Attempt to set the role name from a given collection of request headers.
/// </summary>
/// <param name="requestHeaders">Request headers to check for role name.</param>
public void Set(IHeaderDictionary requestHeaders)
{
string headerValue = requestHeaders[WebAppHostNameHeaderName];
this.ParseAndSetRoleName(headerValue);
}
private void ParseAndSetRoleName(string input)
{
if (string.IsNullOrEmpty(input))
{
// do nothing
}
else if (input.EndsWith(this.HostNameSuffix, StringComparison.OrdinalIgnoreCase))
{
this.RoleName = input.Substring(0, input.Length - this.HostNameSuffix.Length);
}
else
{
this.RoleName = input;
}
}
}
public static class RoleNameContainerExtensions
{
/// <summary>
/// Gets the role name from App Service Kudo header
/// </summary>
public static IApplicationBuilder UseRoleNameContainer(this IApplicationBuilder app)
{
app.Use(next => async context =>
{
RoleNameContainer.Instance ??= new RoleNameContainer();
RoleNameContainer.Instance.Set(context.Request.Headers);
await next(context);
});
return app;
}
}
public class SerilogAppInsightsTraceTelemetryConverter : TraceTelemetryConverter
{
// SeriLog does not populate the cloud_RoleName property:
// https://github.com/serilog/serilog-sinks-applicationinsights/issues/152
public override IEnumerable<ITelemetry> Convert(LogEvent logEvent, IFormatProvider formatProvider)
{
foreach (var telemetry in base.Convert(logEvent, formatProvider))
{
if (RoleNameContainer.Instance?.IsAzureWebApp ?? false)
if (string.IsNullOrEmpty(telemetry.Context.Cloud.RoleName))
telemetry.Context.Cloud.RoleName = RoleNameContainer.Instance.RoleName;
yield return telemetry;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment