diff --git a/Src/WindowsServer/WindowsServer.Shared.Tests/AppServiceEnvVarMonitorTests.cs b/Src/WindowsServer/WindowsServer.Shared.Tests/AppServiceEnvVarMonitorTests.cs new file mode 100644 index 000000000..f68910e14 --- /dev/null +++ b/Src/WindowsServer/WindowsServer.Shared.Tests/AppServiceEnvVarMonitorTests.cs @@ -0,0 +1,117 @@ +namespace Microsoft.ApplicationInsights.WindowsServer +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Runtime.Serialization.Json; + using System.Text; + using System.Threading.Tasks; + using Microsoft.ApplicationInsights.WindowsServer.Implementation; + using Microsoft.ApplicationInsights.WindowsServer.Implementation.DataContracts; + using Microsoft.ApplicationInsights.WindowsServer.Mock; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using Assert = Xunit.Assert; + + [TestClass] + public class AppServiceEnvVarMonitorTests + { + // used to clean up the environment variables after we've run this test + static private Dictionary environmentInitialState; + + [ClassInitialize] + static public void InitializeTests(TestContext context) + { + environmentInitialState = GetCurrentAppServiceEnvironmentVariableValues(false); + } + + [ClassCleanup] + static public void CleanupTests() + { + foreach (var kvp in environmentInitialState) + { + Environment.SetEnvironmentVariable(kvp.Key, kvp.Value); + } + + environmentInitialState = null; + } + + [TestMethod] + public void ConfirmIntervalCheckEnforced() + { + var envVars = GetCurrentAppServiceEnvironmentVariableValues(); + + foreach (var kvp in envVars) + { + string val = string.Empty; + AppServiceEnvVarMonitor.GetUpdatedEnvironmentVariable(kvp.Key, ref val); + // set the value to something new + Environment.SetEnvironmentVariable(kvp.Key, string.Concat("UPDATED-", val, "-UPDATED")); + } + + // set the next-check time to a value that won't get hit + AppServiceEnvVarMonitor.NextCheckTime = DateTime.MaxValue; + + // ensure the current values are indeed different + var currentEnvVars = GetCurrentAppServiceEnvironmentVariableValues(); + + // ensure the values are cached and aren't getting re-read at this time + foreach (var kvp in envVars) + { + string cachedVal = string.Empty; + AppServiceEnvVarMonitor.GetUpdatedEnvironmentVariable(kvp.Key, ref cachedVal); + Assert.Equal(kvp.Value, cachedVal, StringComparer.Ordinal); + Assert.NotEqual(cachedVal, currentEnvVars[kvp.Key], StringComparer.Ordinal); + } + } + + [TestMethod] + public void ConfirmUpdatedEnvironmentIsCaptured() + { + var envVars = GetCurrentAppServiceEnvironmentVariableValues(); + + foreach (var kvp in envVars) + { + string val = string.Empty; + AppServiceEnvVarMonitor.GetUpdatedEnvironmentVariable(kvp.Key, ref val); + // set the value to something new + Environment.SetEnvironmentVariable(kvp.Key, string.Concat("UPDATED-", val, "-UPDATED")); + } + + // set the next-check time to a value that will re-read the values immediately + AppServiceEnvVarMonitor.NextCheckTime = DateTime.MinValue; + + // ensure the current values are indeed different + var currentEnvVars = GetCurrentAppServiceEnvironmentVariableValues(); + + // ensure the values are re-read + foreach (var kvp in envVars) + { + string cachedVal = string.Empty; + AppServiceEnvVarMonitor.GetUpdatedEnvironmentVariable(kvp.Key, ref cachedVal); + Assert.Equal(currentEnvVars[kvp.Key], cachedVal, StringComparer.Ordinal); + Assert.NotEqual(cachedVal, kvp.Value, StringComparer.Ordinal); + } + } + + static private Dictionary GetCurrentAppServiceEnvironmentVariableValues(bool supplyValue = true) + { + int testValueCount = 0; + Dictionary envVars = new Dictionary(); + + foreach (var kvp in AppServiceEnvVarMonitor.CheckedValues) + { + string envVar = Environment.GetEnvironmentVariable(kvp.Key); + if (supplyValue && string.IsNullOrEmpty(envVar)) + { + envVar = $"{testValueCount}_Stand-inValue_{testValueCount}"; + testValueCount++; + Environment.SetEnvironmentVariable(kvp.Key, envVar); + } + envVars.Add(kvp.Key, envVar); + } + + return envVars; + } + } +} diff --git a/Src/WindowsServer/WindowsServer.Shared.Tests/WindowsServer.Shared.Tests.projitems b/Src/WindowsServer/WindowsServer.Shared.Tests/WindowsServer.Shared.Tests.projitems index a653567ac..c3243d467 100644 --- a/Src/WindowsServer/WindowsServer.Shared.Tests/WindowsServer.Shared.Tests.projitems +++ b/Src/WindowsServer/WindowsServer.Shared.Tests/WindowsServer.Shared.Tests.projitems @@ -9,6 +9,7 @@ Microsoft.ApplicationInsights.WindowsServer + diff --git a/Src/WindowsServer/WindowsServer.Shared/AzureWebAppRoleEnvironmentTelemetryInitializer.cs b/Src/WindowsServer/WindowsServer.Shared/AzureWebAppRoleEnvironmentTelemetryInitializer.cs index c0b0e6324..6268ac8a9 100644 --- a/Src/WindowsServer/WindowsServer.Shared/AzureWebAppRoleEnvironmentTelemetryInitializer.cs +++ b/Src/WindowsServer/WindowsServer.Shared/AzureWebAppRoleEnvironmentTelemetryInitializer.cs @@ -1,8 +1,6 @@ namespace Microsoft.ApplicationInsights.WindowsServer { using System; - using System.Threading; - using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.Extensibility.Implementation; @@ -19,9 +17,6 @@ public class AzureWebAppRoleEnvironmentTelemetryInitializer : ITelemetryInitiali /// Predefined suffix for Azure Web App Hostname. private const string WebAppSuffix = ".azurewebsites.net"; - private string nodeName; - private string roleName; - /// /// Initializes a new instance of the class. /// @@ -38,14 +33,12 @@ public void Initialize(ITelemetry telemetry) { if (string.IsNullOrEmpty(telemetry.Context.Cloud.RoleName)) { - string name = LazyInitializer.EnsureInitialized(ref this.roleName, this.GetRoleName); - telemetry.Context.Cloud.RoleName = name; + telemetry.Context.Cloud.RoleName = this.GetRoleName(); } if (string.IsNullOrEmpty(telemetry.Context.GetInternalContext().NodeName)) { - string name = LazyInitializer.EnsureInitialized(ref this.nodeName, this.GetNodeName); - telemetry.Context.GetInternalContext().NodeName = name; + telemetry.Context.GetInternalContext().NodeName = this.GetNodeName(); } } @@ -62,7 +55,9 @@ private string GetRoleName() private string GetNodeName() { - return Environment.GetEnvironmentVariable(WebAppHostNameEnvironmentVariable) ?? string.Empty; + string nodeName = string.Empty; + AppServiceEnvVarMonitor.GetUpdatedEnvironmentVariable(WebAppHostNameEnvironmentVariable, ref nodeName); + return nodeName; } } } diff --git a/Src/WindowsServer/WindowsServer.Shared/Implementation/AppServiceEnvVarMonitor.cs b/Src/WindowsServer/WindowsServer.Shared/Implementation/AppServiceEnvVarMonitor.cs new file mode 100644 index 000000000..0b509a904 --- /dev/null +++ b/Src/WindowsServer/WindowsServer.Shared/Implementation/AppServiceEnvVarMonitor.cs @@ -0,0 +1,60 @@ +namespace Microsoft.ApplicationInsights.WindowsServer.Implementation +{ + using System; + using System.Collections.Generic; + + /// + /// Utility to monitor the value of environment variables which may change + /// during the run of an application. Checks the environment variables + /// intermittently. + /// + internal static class AppServiceEnvVarMonitor + { + // Environment variables tracked by this monitor. (internal to allow tests to modify them) + internal static readonly Dictionary CheckedValues = new Dictionary() + { + { "WEBSITE_SITE_NAME", string.Empty }, + { "WEBSITE_HOME_STAMPNAME", string.Empty }, + { "WEBSITE_HOSTNAME", string.Empty } + }; + + // When is the next time we will allow a check to occur? (internal to allow tests to modify this to avoid waits) + internal static DateTime NextCheckTime = DateTime.MinValue; + + // how often we allow the code to re-check the environment + private static readonly TimeSpan CheckInterval = TimeSpan.FromSeconds(30); + + /// + /// Get the latest value assigned to an environment variable. + /// + /// Name of the environment variable to acquire. + /// Value of the environment variable (updated within the update interval). + public static void GetUpdatedEnvironmentVariable(string envVarName, ref string value) + { + if (!string.IsNullOrEmpty(envVarName)) + { + CheckVariablesIntermittent(); + CheckedValues.TryGetValue(envVarName, out value); + } + } + + /// + /// Simply update the stored environment variables if the last time we + /// checked from now is greater than the check interval. + /// + private static void CheckVariablesIntermittent() + { + DateTime rightNow = DateTime.UtcNow; + if (rightNow > NextCheckTime) + { + NextCheckTime = rightNow + CheckInterval; + + List keys = new List(CheckedValues.Keys); + foreach (var key in keys) + { + CheckedValues[key] = Environment.GetEnvironmentVariable(key); + } + } + } + } +} diff --git a/Src/WindowsServer/WindowsServer.Shared/WindowsServer.Shared.projitems b/Src/WindowsServer/WindowsServer.Shared/WindowsServer.Shared.projitems index 5d1246adb..13f3348e1 100644 --- a/Src/WindowsServer/WindowsServer.Shared/WindowsServer.Shared.projitems +++ b/Src/WindowsServer/WindowsServer.Shared/WindowsServer.Shared.projitems @@ -15,6 +15,7 @@ +