StartupHook: Move all checks to mandatory rules (#2432)

* InstrumentationAssemblyRule

* Test changes.

* Update src/OpenTelemetry.AutoInstrumentation.StartupHook/RulesEngine/InstrumentationAssemblyRule.cs

Co-authored-by: Paulo Janotti <pjanotti@splunk.com>

* Move ApplicationInExcludeList to Rule

* New changes

* PR feedback.

---------

Co-authored-by: Paulo Janotti <pjanotti@splunk.com>
This commit is contained in:
Rajkumar Rangaraj 2023-04-12 22:55:41 -07:00 committed by GitHub
parent 0f15c72088
commit 3e5ce4ab11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 200 additions and 104 deletions

View File

@ -0,0 +1,97 @@
// <copyright file="ApplicationInExcludeListRule.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using OpenTelemetry.AutoInstrumentation.Logging;
namespace OpenTelemetry.AutoInstrumentation.RulesEngine;
internal class ApplicationInExcludeListRule : Rule
{
private static readonly IOtelLogger Logger = OtelLogging.GetLogger("StartupHook");
public ApplicationInExcludeListRule()
{
Name = "Application is in exclude list validator";
Description = "This rule checks if the application is included in the exclusion list specified by the OTEL_DOTNET_AUTO_EXCLUDE_PROCESSES environment variable. If the application is in the exclusion list, the rule skips initialization.";
}
internal override bool Evaluate()
{
var applicationName = GetApplicationName();
if (IsApplicationInExcludeList(applicationName))
{
Logger.Information($"Rule Engine: {applicationName} is in the exclusion list. Skipping initialization.");
return false;
}
Logger.Debug($"Rule Engine: {applicationName} is not in the exclusion list. ApplicationInExcludeListRule evaluation success.");
return true;
}
private static string GetApplicationName()
{
try
{
return AppDomain.CurrentDomain.FriendlyName;
}
catch (Exception ex)
{
Logger.Error($"Error getting AppDomain.CurrentDomain.FriendlyName: {ex}");
return string.Empty;
}
}
private static bool IsApplicationInExcludeList(string applicationName)
{
return GetExcludedApplicationNames().Contains(applicationName);
}
private static List<string> GetExcludedApplicationNames()
{
var excludedProcesses = new List<string>();
var environmentValue = GetEnvironmentVariable("OTEL_DOTNET_AUTO_EXCLUDE_PROCESSES");
if (environmentValue == null)
{
return excludedProcesses;
}
foreach (var processName in environmentValue.Split(Constants.ConfigurationValues.Separator))
{
if (!string.IsNullOrWhiteSpace(processName))
{
excludedProcesses.Add(processName.Trim());
}
}
return excludedProcesses;
}
private static string? GetEnvironmentVariable(string variableName)
{
try
{
return Environment.GetEnvironmentVariable(variableName);
}
catch (Exception ex)
{
Logger.Error($"Error getting environment variable {variableName}: {ex}");
return null;
}
}
}

View File

@ -0,0 +1,54 @@
// <copyright file="MinSupportedFrameworkRule.cs" company="OpenTelemetry Authors">
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// </copyright>
using System.Reflection;
using System.Runtime.Versioning;
using OpenTelemetry.AutoInstrumentation.Logging;
namespace OpenTelemetry.AutoInstrumentation.RulesEngine;
internal class MinSupportedFrameworkRule : Rule
{
private static readonly IOtelLogger Logger = OtelLogging.GetLogger("StartupHook");
public MinSupportedFrameworkRule()
{
Name = "Minimum Supported Framework Version Validator";
Description = "Verifies that the application is running on a supported version of the .NET runtime.";
}
internal override bool Evaluate()
{
var minSupportedFramework = new FrameworkName(".NETCoreApp,Version=v6.0");
var appTargetFramework = Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
// This is the best way to identify application's target framework.
// If entry assembly framework is null, StartupHook should continue its execution.
if (appTargetFramework != null)
{
var appTargetFrameworkName = new FrameworkName(appTargetFramework);
var appTargetFrameworkVersion = appTargetFrameworkName.Version;
if (appTargetFrameworkVersion < minSupportedFramework.Version)
{
Logger.Information($"Rule Engine: Error in StartupHook initialization: {appTargetFramework} is not supported");
return false;
}
}
Logger.Information("Rule Engine: MinSupportedFrameworkRule evaluation success.");
return true;
}
}

View File

@ -22,7 +22,13 @@ internal class RuleEngine
{
private static readonly IOtelLogger Logger = OtelLogging.GetLogger("StartupHook");
private readonly List<Rule> _rules = new()
private readonly List<Rule> _mandatoryRules = new()
{
new ApplicationInExcludeListRule(),
new MinSupportedFrameworkRule()
};
private readonly List<Rule> _otherRules = new()
{
new OpenTelemetrySdkMinimumVersionRule(),
new DiagnosticSourceRule(),
@ -36,36 +42,55 @@ internal class RuleEngine
// This constructor is used for test purpose.
internal RuleEngine(List<Rule> rules)
{
_rules = rules;
_otherRules = rules;
}
internal bool Validate()
internal bool ValidateRules()
{
var result = true;
// Single rule failure will stop the execution.
foreach (var rule in _mandatoryRules)
{
if (!EvaluateRule(rule))
{
return false;
}
}
if (bool.TryParse(Environment.GetEnvironmentVariable("OTEL_DOTNET_AUTO_RULE_ENGINE_ENABLED"), out var shouldTrack) && !shouldTrack)
{
Logger.Information($"OTEL_DOTNET_AUTO_RULE_ENGINE_ENABLED is set to false, skipping rule engine validation.");
return result;
}
foreach (var rule in _rules)
// All the rules are validated here.
foreach (var rule in _otherRules)
{
try
if (!EvaluateRule(rule))
{
if (!rule.Evaluate())
{
Logger.Error($"Rule '{rule.Name}' failed: {rule.Description}");
result = false;
}
}
catch (Exception ex)
{
Logger.Error($"Error evaluating rule '{rule.Name}': {ex.Message}");
result = false;
}
}
return result;
}
private static bool EvaluateRule(Rule rule)
{
try
{
if (!rule.Evaluate())
{
Logger.Error($"Rule '{rule.Name}' failed: {rule.Description}");
return false;
}
}
catch (Exception ex)
{
Logger.Warning($"Error evaluating rule '{rule.Name}': {ex.Message}");
}
return true;
}
}

View File

@ -14,10 +14,8 @@
// limitations under the License.
// </copyright>
using System.Diagnostics;
using System.Reflection;
using System.Runtime.Versioning;
using OpenTelemetry.AutoInstrumentation;
using OpenTelemetry.AutoInstrumentation.Logging;
using OpenTelemetry.AutoInstrumentation.RulesEngine;
@ -37,44 +35,19 @@ internal class StartupHook
/// </summary>
public static void Initialize()
{
var minSupportedFramework = new FrameworkName(".NETCoreApp,Version=v6.0");
var appTargetFramework = Assembly.GetEntryAssembly()?.GetCustomAttribute<TargetFrameworkAttribute>()?.FrameworkName;
// This is the best way to identify application's target framework.
// If entry assembly framework is null, StartupHook should continue its execution.
if (appTargetFramework != null)
{
var appTargetFrameworkName = new FrameworkName(appTargetFramework);
var appTargetFrameworkVersion = appTargetFrameworkName.Version;
if (appTargetFrameworkVersion < minSupportedFramework.Version)
{
Logger.Information($"Error in StartupHook initialization: {appTargetFramework} is not supported");
return;
}
}
var applicationName = GetApplicationName();
Logger.Information($"StartupHook loaded for application with name {applicationName}.");
if (IsApplicationInExcludeList(applicationName))
{
Logger.Information("Application is in the exclusion list. Skipping initialization.");
return;
}
Logger.Information("Attempting initialization.");
LoaderAssemblyLocation = GetLoaderAssemblyLocation();
try
{
LoaderAssemblyLocation = GetLoaderAssemblyLocation();
var ruleEngine = new RuleEngine();
if (!ruleEngine.Validate())
if (!ruleEngine.ValidateRules())
{
Logger.Error("Rule Engine Failure: One or more rules failed validation. Auto-Instrumentation won't be loaded.");
return;
}
Logger.Information("Initialization.");
// Creating an instance of OpenTelemetry.AutoInstrumentation.Loader.Startup
// will initialize Instrumentation through its static constructor.
string loaderFilePath = Path.Combine(LoaderAssemblyLocation, "OpenTelemetry.AutoInstrumentation.Loader.dll");
@ -118,57 +91,4 @@ internal class StartupHook
throw;
}
}
private static string GetApplicationName()
{
try
{
return AppDomain.CurrentDomain.FriendlyName;
}
catch (Exception ex)
{
Logger.Error($"Error getting AppDomain.CurrentDomain.FriendlyName: {ex}");
return string.Empty;
}
}
private static bool IsApplicationInExcludeList(string applicationName)
{
return GetExcludedApplicationNames().Contains(applicationName);
}
private static List<string> GetExcludedApplicationNames()
{
var excludedProcesses = new List<string>();
var environmentValue = GetEnvironmentVariable("OTEL_DOTNET_AUTO_EXCLUDE_PROCESSES");
if (environmentValue == null)
{
return excludedProcesses;
}
foreach (var processName in environmentValue.Split(Constants.ConfigurationValues.Separator))
{
if (!string.IsNullOrWhiteSpace(processName))
{
excludedProcesses.Add(processName.Trim());
}
}
return excludedProcesses;
}
private static string? GetEnvironmentVariable(string variableName)
{
try
{
return Environment.GetEnvironmentVariable(variableName);
}
catch (Exception ex)
{
Logger.Error($"Error getting environment variable {variableName}: {ex}");
return null;
}
}
}

View File

@ -35,7 +35,7 @@ public class RuleEngineTests : IDisposable
var ruleEngine = new RuleEngine(new List<Rule> { testRule });
// Act
var result = ruleEngine.Validate();
var result = ruleEngine.ValidateRules();
// Assert
Assert.True(result);
@ -51,7 +51,7 @@ public class RuleEngineTests : IDisposable
var ruleEngine = new RuleEngine(new List<Rule> { testRule });
// Act
var result = ruleEngine.Validate();
var result = ruleEngine.ValidateRules();
// Assert
Assert.True(result);
@ -67,7 +67,7 @@ public class RuleEngineTests : IDisposable
var ruleEngine = new RuleEngine(new List<Rule> { testRule });
// Act
var result = ruleEngine.Validate();
var result = ruleEngine.ValidateRules();
// Assert
Assert.True(result);
@ -82,7 +82,7 @@ public class RuleEngineTests : IDisposable
var ruleEngine = new RuleEngine(new List<Rule> { testRule });
// Act
var result = ruleEngine.Validate();
var result = ruleEngine.ValidateRules();
// Assert
Assert.True(result);
@ -98,7 +98,7 @@ public class RuleEngineTests : IDisposable
var ruleEngine = new RuleEngine(new List<Rule> { testRule });
// Act
var result = ruleEngine.Validate();
var result = ruleEngine.ValidateRules();
// Assert
Assert.True(result);