Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Diagnostic Logging for Runtime Store Assembly Version Validation #3187

Merged
merged 14 commits into from
Jan 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ private static List<Rule> CreateDefaultOptionalRules()
{
return new()
{
new RuntimeStoreDiagnosticRule(),
new OpenTelemetrySdkMinimumVersionRule(),
new AssemblyFileVersionRule(),
new NativeProfilerDiagnosticsRule()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

using System.Diagnostics;
using System.Reflection;
using OpenTelemetry.AutoInstrumentation.Logging;

namespace OpenTelemetry.AutoInstrumentation.RulesEngine;

internal class RuntimeStoreDiagnosticRule : Rule
{
private const string RuntimeStoreEnvironmentVariable = "DOTNET_SHARED_STORE";
private static readonly IOtelLogger Logger = OtelLogging.GetLogger("StartupHook");

public RuntimeStoreDiagnosticRule()
{
Name = "Runtime Store Diagnostic Rule";
Description = "Logs detail that assembly versions in the runtime store are not lower than the version the Application uses.";
}

internal override bool Evaluate()
{
try
{
// Skip rule evaluation if the application is running in self-contained mode.
if (IsSelfContained())
{
Logger.Debug("Rule Engine: Skipping rule evaluation for self-contained application.");
return true;
}

var configuredStoreDirectory = GetConfiguredStoreDirectory();
if (configuredStoreDirectory == null)
{
// Store location not found, skip rule evaluation
Logger.Debug("Rule Engine: Skipping rule evaluation as runtime store location is not found.");
return true;
}

var storeFiles = Directory.GetFiles(configuredStoreDirectory, "Microsoft.Extensions*.dll", SearchOption.AllDirectories);

foreach (var file in storeFiles)
{
var assemblyName = Path.GetFileNameWithoutExtension(file);
Assembly appInstrumentationAssembly;

try
{
appInstrumentationAssembly = Assembly.Load(assemblyName);
}
catch (Exception ex)
{
Logger.Warning(ex, $"Rule Engine: Assembly load failed. Skipping rule evaluation for assembly - {assemblyName}");
continue;
}

var appInstrumentationFileVersionInfo = FileVersionInfo.GetVersionInfo(appInstrumentationAssembly.Location);
var appInstrumentationFileVersion = new Version(appInstrumentationFileVersionInfo.FileVersion);

if (appInstrumentationFileVersion.Major < 5)
{
// Special case to handle runtime store version 3.1.x.x package references in app.
// Skip rule evaluation for assemblies with version 3.1.x.x.
Logger.Debug($"Rule Engine: Skipping rule evaluation for runtime store assembly {appInstrumentationFileVersionInfo.FileName} with version {appInstrumentationFileVersion}.");
continue;
}

var runTimeStoreFileVersionInfo = FileVersionInfo.GetVersionInfo(file);
var runTimeStoreFileVersion = new Version(runTimeStoreFileVersionInfo.FileVersion);

if (appInstrumentationFileVersion < runTimeStoreFileVersion)
{
Logger.Warning($"Rule Engine: Application has direct or indirect reference to lower version of runtime store assembly {runTimeStoreFileVersionInfo.FileName} - {appInstrumentationFileVersion}. ");
}
else
{
Logger.Debug($"Rule Engine: Runtime store assembly {runTimeStoreFileVersionInfo.FileName} is validated successfully.");
}
}
}
catch (Exception ex)
{
// Exception in rule evaluation should not impact the result of the rule.
Logger.Warning(ex, "Rule Engine: Couldn't evaluate reference to runtime store assemblies in an app.");
}

// This a diagnostic rule, so we always return true.
return true;
}

private static string? GetConfiguredStoreDirectory()
{
try
{
var storeDirectory = Environment.GetEnvironmentVariable(RuntimeStoreEnvironmentVariable);
// Skip rule evaluation if the store directory is not configured.
if (storeDirectory == null)
{
Logger.Debug($"Rule Engine: {RuntimeStoreEnvironmentVariable} environment variable not found. Skipping rule evaluation.");
return null;
}

// Check if the store directory exists
if (!Directory.Exists(storeDirectory))
{
Logger.Debug($"Rule Engine: Runtime store directory not found at {storeDirectory}. Skipping rule evaluation.");
return null;
}

var architecture = Environment.Is64BitProcess ? "x64" : "x86";
var targetFramework = $"net{Environment.Version.Major}.{Environment.Version.Minor}";
var finalPath = Path.Combine(storeDirectory, architecture, targetFramework);

return finalPath;
}
catch (Exception ex)
{
Logger.Warning(ex, "Error getting store directory location");
throw;
}
}

private static bool IsSelfContained()
{
var assemblyPath = Assembly.GetExecutingAssembly().Location;
var directory = Path.GetDirectoryName(assemblyPath);

// Check for the presence of a known .NET runtime file
if (directory != null &&
rajkumar-rangaraj marked this conversation as resolved.
Show resolved Hide resolved
(File.Exists(Path.Combine(directory, "coreclr.dll")) ||
File.Exists(Path.Combine(directory, "libcoreclr.so")) ||
File.Exists(Path.Combine(directory, "libcoreclr.dylib"))))
{
return true;
}

return false;
}
}
Loading