diff --git a/defaults.env b/defaults.env index 00e7695cb..204ac1fce 100644 --- a/defaults.env +++ b/defaults.env @@ -1,5 +1,6 @@ export CORECLR_ENABLE_PROFILING=1 export CORECLR_PROFILER='{918728DD-259F-4A6A-AC2B-B85E1B658318}' export CORECLR_PROFILER_PATH=/opt/opentelemetry-dotnet-autoinstrumentation/OpenTelemetry.ClrProfiler.Native.so +export DOTNET_STARTUP_HOOKS=/opt/opentelemetry-dotnet-autoinstrumentation/OpenTelemetry.Instrumentation.StartupHook.dll export OTEL_INTEGRATIONS=/opt/opentelemetry-dotnet-autoinstrumentation/integrations.json export OTEL_DOTNET_TRACER_HOME=/opt/opentelemetry-dotnet-autoinstrumentation \ No newline at end of file diff --git a/dev/envvars.sh b/dev/envvars.sh index 31b8b5313..bef71ea56 100644 --- a/dev/envvars.sh +++ b/dev/envvars.sh @@ -53,6 +53,7 @@ then export CORECLR_PROFILER_PATH_64="${CURDIR}/bin/tracer-home/win-x64/OpenTelemetry.ClrProfiler.Native.${SUFIX}" export CORECLR_PROFILER_PATH_32="${CURDIR}/bin/tracer-home/win-x86/OpenTelemetry.ClrProfiler.Native.${SUFIX}" fi +export DOTNET_STARTUP_HOOKS="${CURDIR}/bin/tracer-home/netcoreapp3.1/OpenTelemetry.Instrumentation.StartupHook.dll" # Configure OpenTelemetry Tracer export OTEL_DOTNET_TRACER_HOME="${CURDIR}/bin/tracer-home" diff --git a/samples/Samples.AspNetCoreMvc31/Controllers/HomeController.cs b/samples/Samples.AspNetCoreMvc31/Controllers/HomeController.cs index 74b2aba6b..110cfa59a 100644 --- a/samples/Samples.AspNetCoreMvc31/Controllers/HomeController.cs +++ b/samples/Samples.AspNetCoreMvc31/Controllers/HomeController.cs @@ -22,7 +22,7 @@ namespace Samples.AspNetCoreMvc.Controllers ViewBag.ClrProfilerAssemblyLocation = instrumentationType?.Assembly.Location; ViewBag.StackTrace = StackTraceHelper.GetUsefulStack(); - var prefixes = new[] { "COR_", "CORECLR_", "OTEL_" }; + var prefixes = new[] { "COR_", "CORECLR_", "DOTNET_", "OTEL_" }; var envVars = from envVar in Environment.GetEnvironmentVariables().Cast() from prefix in prefixes diff --git a/samples/Samples.AspNetCoreMvc31/Properties/launchSettings.json b/samples/Samples.AspNetCoreMvc31/Properties/launchSettings.json index 5fe67b1d0..9288ba263 100644 --- a/samples/Samples.AspNetCoreMvc31/Properties/launchSettings.json +++ b/samples/Samples.AspNetCoreMvc31/Properties/launchSettings.json @@ -16,10 +16,11 @@ "CORECLR_ENABLE_PROFILING": "1", "CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}", - "CORECLR_PROFILER_PATH": "$(ProjectDir)$(OutputPath)profiler-lib\\OpenTelemetry.ClrProfiler.Native.dll", + "CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home\\win-x64\\OpenTelemetry.ClrProfiler.Native.dll", + "DOTNET_STARTUP_HOOKS": "$(SolutionDir)bin\\tracer-home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll", - "OTEL_DOTNET_TRACER_HOME": "$(ProjectDir)$(OutputPath)profiler-lib", - "OTEL_INTEGRATIONS": "$(ProjectDir)$(OutputPath)profiler-lib\\integrations.json", + "OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home", + "OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json", "OTEL_SERVICE_NAME": "Samples.AspNetCoreMvc31", "OTEL_EXPORTER": "jaeger", "OTEL_EXPORTER_JAEGER_AGENT_HOST": "localhost", @@ -36,10 +37,11 @@ "CORECLR_ENABLE_PROFILING": "1", "CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}", - "CORECLR_PROFILER_PATH": "$(ProjectDir)$(OutputPath)profiler-lib\\OpenTelemetry.ClrProfiler.Native.dll", + "CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home$(OutputPath)\\OpenTelemetry.ClrProfiler.Native.dll", + "DOTNET_STARTUP_HOOKS": "$(SolutionDir)bin\\tracer-home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll", - "OTEL_DOTNET_TRACER_HOME": "$(ProjectDir)$(OutputPath)profiler-lib", - "OTEL_INTEGRATIONS": "$(ProjectDir)$(OutputPath)profiler-lib\\integrations.json", + "OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home", + "OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json", "OTEL_SERVICE_NAME": "Samples.AspNetCoreMvc31", "OTEL_EXPORTER": "jaeger", diff --git a/src/OpenTelemetry.ClrProfiler.Native/OpenTelemetry.ClrProfiler.Native.vcxproj b/src/OpenTelemetry.ClrProfiler.Native/OpenTelemetry.ClrProfiler.Native.vcxproj index fadb1cc1e..7bdce0a86 100644 --- a/src/OpenTelemetry.ClrProfiler.Native/OpenTelemetry.ClrProfiler.Native.vcxproj +++ b/src/OpenTelemetry.ClrProfiler.Native/OpenTelemetry.ClrProfiler.Native.vcxproj @@ -202,6 +202,7 @@ + diff --git a/src/OpenTelemetry.ClrProfiler.Native/cor_profiler.cpp b/src/OpenTelemetry.ClrProfiler.Native/cor_profiler.cpp index ef1c84883..ef958ad19 100644 --- a/src/OpenTelemetry.ClrProfiler.Native/cor_profiler.cpp +++ b/src/OpenTelemetry.ClrProfiler.Native/cor_profiler.cpp @@ -17,6 +17,7 @@ #include "module_metadata.h" #include "pal.h" #include "resource.h" +#include "startup_hook.h" #include "stats.h" #include "util.h" #include "version.h" @@ -119,6 +120,10 @@ HRESULT STDMETHODCALLTYPE CorProfiler::Initialize(IUnknown* cor_profiler_info_un if (SUCCEEDED(hr)) { Logger::Debug("Interface ICorProfilerInfo10 found."); + // .NET Core applications should use the dotnet startup hook to bootstrap OpenTelemetry so that the + // necessary dependencies will be available. Bootstrapping with the profiling APIs occurs too early + // and the necessary dependencies are not available yet. + use_dotnet_startuphook_bootstrapper = true; } else { @@ -135,6 +140,17 @@ HRESULT STDMETHODCALLTYPE CorProfiler::Initialize(IUnknown* cor_profiler_info_un } } + if (use_dotnet_startuphook_bootstrapper) + { + const auto home_path = GetEnvironmentValue(environment::profiler_home_path); + const auto startup_hooks = GetEnvironmentValue(environment::dotnet_startup_hooks); + if (!IsStartupHookEnabled(startup_hooks, home_path)) + { + Logger::Error("The required startup hook was not configured correctly. No telemetry will be captured."); + return E_FAIL; + } + } + if (IsAzureAppServices()) { Logger::Info("Profiler is operating within Azure App Services context."); @@ -742,7 +758,7 @@ HRESULT STDMETHODCALLTYPE CorProfiler::JITCompilationStarted(FunctionID function { auto _ = trace::Stats::Instance()->JITCompilationStartedMeasure(); - if (!is_attached_ || !is_safe_to_block) + if (!is_attached_ || !is_safe_to_block || use_dotnet_startuphook_bootstrapper) { return S_OK; } diff --git a/src/OpenTelemetry.ClrProfiler.Native/cor_profiler.h b/src/OpenTelemetry.ClrProfiler.Native/cor_profiler.h index 4764558fd..b68dafff9 100644 --- a/src/OpenTelemetry.ClrProfiler.Native/cor_profiler.h +++ b/src/OpenTelemetry.ClrProfiler.Native/cor_profiler.h @@ -29,6 +29,7 @@ private: // Startup helper variables bool first_jit_compilation_completed = false; + bool use_dotnet_startuphook_bootstrapper = false; bool instrument_domain_neutral_assemblies = false; bool corlib_module_loaded = false; diff --git a/src/OpenTelemetry.ClrProfiler.Native/dd_profiler_constants.h b/src/OpenTelemetry.ClrProfiler.Native/dd_profiler_constants.h index a5f96af92..3a921c2d4 100644 --- a/src/OpenTelemetry.ClrProfiler.Native/dd_profiler_constants.h +++ b/src/OpenTelemetry.ClrProfiler.Native/dd_profiler_constants.h @@ -26,7 +26,8 @@ const WSTRING env_vars_to_display[]{environment::tracing_enabled, environment::azure_app_services, environment::azure_app_services_app_pool_id, environment::azure_app_services_cli_telemetry_profile_value, - environment::internal_trace_profiler_path}; + environment::internal_trace_profiler_path, + environment::dotnet_startup_hooks}; const WSTRING skip_assembly_prefixes[]{ WStr("Microsoft.AI"), diff --git a/src/OpenTelemetry.ClrProfiler.Native/environment_variables.h b/src/OpenTelemetry.ClrProfiler.Native/environment_variables.h index 51558d9cf..2c5073d2e 100644 --- a/src/OpenTelemetry.ClrProfiler.Native/environment_variables.h +++ b/src/OpenTelemetry.ClrProfiler.Native/environment_variables.h @@ -89,6 +89,12 @@ const WSTRING internal_trace_profiler_path = // Sets whether to enable NGEN images. const WSTRING clr_enable_ngen = WStr("OTEL_CLR_ENABLE_NGEN"); +// The list of startup hooks defined for .NET Core 3.1+ applications. +// This is a .NET runtime environment variable. +// See https://github.com/dotnet/runtime/blob/main/docs/design/features/host-startup-hook.md +// for more information about this environment variable. +const WSTRING dotnet_startup_hooks = WStr("DOTNET_STARTUP_HOOKS"); + } // namespace environment } // namespace trace diff --git a/src/OpenTelemetry.ClrProfiler.Native/pal.h b/src/OpenTelemetry.ClrProfiler.Native/pal.h index 27fead785..34d81b973 100644 --- a/src/OpenTelemetry.ClrProfiler.Native/pal.h +++ b/src/OpenTelemetry.ClrProfiler.Native/pal.h @@ -22,6 +22,12 @@ #include "string.h" // NOLINT #include "util.h" +#ifdef _WIN32 +#define DIR_SEPARATOR WStr('\\') +#else +#define DIR_SEPARATOR WStr('/') +#endif + namespace trace { @@ -34,13 +40,7 @@ inline WSTRING GetDatadogLogFilePath(const std::string& file_name_suffix) if (directory.length() > 0) { - return directory + -#ifdef _WIN32 - WStr('\\') + -#else - WStr('/') + -#endif - ToWSTRING(file_name); + return directory + DIR_SEPARATOR + ToWSTRING(file_name); } WSTRING path = GetEnvironmentValue(TLoggerPolicy::logging_environment::log_path); diff --git a/src/OpenTelemetry.ClrProfiler.Native/startup_hook.h b/src/OpenTelemetry.ClrProfiler.Native/startup_hook.h new file mode 100644 index 000000000..c95b51563 --- /dev/null +++ b/src/OpenTelemetry.ClrProfiler.Native/startup_hook.h @@ -0,0 +1,30 @@ +#ifndef OTEL_CLR_PROFILER_STARTUP_HOOK_H_ +#define OTEL_CLR_PROFILER_STARTUP_HOOK_H_ + +#include "string.h" // NOLINT +#include "pal.h" + +namespace trace +{ + +inline WSTRING GetExpectedStartupHookPath(const WSTRING& home_path) { + WSTRING separator = EmptyWStr; + if (home_path.back() != DIR_SEPARATOR) { + separator = DIR_SEPARATOR; + } + + return home_path + separator + WStr("netcoreapp3.1") + DIR_SEPARATOR + + WStr("OpenTelemetry.Instrumentation.StartupHook.dll"); +} + +inline bool IsStartupHookEnabled(const WSTRING& startup_hooks, const WSTRING& home_path) +{ + const WSTRING expected_startup_hook = GetExpectedStartupHookPath(home_path); + auto n = startup_hooks.find(expected_startup_hook); + + return n != WSTRING::npos; +} + +} // namespace trace + +#endif // OTEL_CLR_PROFILER_STARTUP_HOOK_H_ \ No newline at end of file diff --git a/test/OpenTelemetry.ClrProfiler.Native.Tests/OpenTelemetry.ClrProfiler.Native.Tests.vcxproj b/test/OpenTelemetry.ClrProfiler.Native.Tests/OpenTelemetry.ClrProfiler.Native.Tests.vcxproj index b7ccd30b2..cb44766dd 100644 --- a/test/OpenTelemetry.ClrProfiler.Native.Tests/OpenTelemetry.ClrProfiler.Native.Tests.vcxproj +++ b/test/OpenTelemetry.ClrProfiler.Native.Tests/OpenTelemetry.ClrProfiler.Native.Tests.vcxproj @@ -80,6 +80,7 @@ Create Create + diff --git a/test/OpenTelemetry.ClrProfiler.Native.Tests/startup_hook_test.cpp b/test/OpenTelemetry.ClrProfiler.Native.Tests/startup_hook_test.cpp new file mode 100644 index 000000000..774e9678b --- /dev/null +++ b/test/OpenTelemetry.ClrProfiler.Native.Tests/startup_hook_test.cpp @@ -0,0 +1,82 @@ +#include "pch.h" + +#include "../../src/OpenTelemetry.ClrProfiler.Native/startup_hook.h" + +using namespace trace; + +TEST(StartupHookTest, GetExpectedStartupHookPathDoesNotAddSeparator) { + const auto home_path = WStr("C:\\tracer_home\\"); + const auto expected_path = WStr("C:\\tracer_home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll"); + + const auto startup_hook_path = GetExpectedStartupHookPath(home_path); + + ASSERT_EQ(expected_path, startup_hook_path); +} + +TEST(StartupHookTest, GetExpectedStartupHookPathAddsSeparator) { + const auto home_path = WStr("C:\\tracer_home"); + const auto expected_path = WStr("C:\\tracer_home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll"); + + const auto startup_hook_path = GetExpectedStartupHookPath(home_path); + + ASSERT_EQ(expected_path, startup_hook_path); +} + +TEST(StartupHookTest, StartupHookIsEnabled) { + const auto startup_hooks = WStr("C:\\tracer_home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll"); + const auto home_path = WStr("C:\\tracer_home"); + + const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path); + + ASSERT_TRUE(is_enabled); +} + +TEST(StartupHookTest, StartupHookIsNotEnabledWhenStartupHooksIsEmpty) { + const auto startup_hooks = WStr(""); + const auto home_path = WStr("C:\\tracer_home"); + + const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path); + + ASSERT_FALSE(is_enabled); +} + +TEST(StartupHookTest, StartupHookIsNotEnabledWhenNoStartupHooksDefined) { + const WSTRING startup_hooks; + const auto home_path = WStr("C:\\tracer_home"); + + const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path); + + ASSERT_FALSE(is_enabled); +} + +TEST(StartupHookTest, StartupHookIsNotEnabledWhenStartupHookDoesNotContainOpenTelemetryHook) { + const auto startup_hooks = WStr("C:\\other_folder\\StartupHook.dll"); + const auto home_path = WStr("C:\\tracer_home"); + + const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path); + + ASSERT_FALSE(is_enabled); +} + +TEST(StartupHookTest, StartupHookIsNotEnabledWhenNotInTheCorrectLocation) { + const auto startup_hooks = WStr("C:\\other_folder\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll"); + const auto home_path = WStr("C:\\tracer_home"); + + const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path); + + ASSERT_FALSE(is_enabled); +} + +TEST(StartupHookTest, StartupHookIsEnabledWhenMultipleStartupHooksDefined) { + std::stringstream ss; + ss << "C:\\folder1\\StartupHook.dll" << DIR_SEPARATOR + << "C:\\tracer_home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll" + << DIR_SEPARATOR << "C:\\folder2\\StartupHook.dll"; + + const auto startup_hooks = ToWSTRING(ss.str()); + const auto home_path = WStr("C:\\tracer_home"); + + const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path); + + ASSERT_TRUE(is_enabled); +} diff --git a/test/integration-tests/IntegrationTests.Helpers/EnvironmentHelper.cs b/test/integration-tests/IntegrationTests.Helpers/EnvironmentHelper.cs index a67ad9a77..03cf129cc 100644 --- a/test/integration-tests/IntegrationTests.Helpers/EnvironmentHelper.cs +++ b/test/integration-tests/IntegrationTests.Helpers/EnvironmentHelper.cs @@ -142,34 +142,31 @@ namespace IntegrationTests.Helpers int agentPort, int aspNetCorePort, StringDictionary environmentVariables, - string processToProfile = null, - bool isStartupHook = false) + bool enableStartupHook, + string processToProfile = null) { string profilerEnabled = _requiresProfiling ? "1" : "0"; + string profilerPath = GetProfilerPath(); - if (isStartupHook) + if (IsCoreClr()) { - string hookPath = GetStartupHookOutputPath(); + // enableStartupHook should be true by default, and the parameter should only be set + // to false when testing the case that instrumentation should not be available. + if (enableStartupHook) + { + string hookPath = GetStartupHookOutputPath(); + environmentVariables["DOTNET_STARTUP_HOOKS"] = hookPath; + } - environmentVariables["DOTNET_STARTUP_HOOKS"] = hookPath; - environmentVariables["OTEL_DOTNET_TRACER_INSTRUMENTATIONS"] = "HttpClient"; + environmentVariables["CORECLR_ENABLE_PROFILING"] = profilerEnabled; + environmentVariables["CORECLR_PROFILER"] = EnvironmentTools.ProfilerClsId; + environmentVariables["CORECLR_PROFILER_PATH"] = profilerPath; } else { - string profilerPath = GetProfilerPath(); - - if (IsCoreClr()) - { - environmentVariables["CORECLR_ENABLE_PROFILING"] = profilerEnabled; - environmentVariables["CORECLR_PROFILER"] = EnvironmentTools.ProfilerClsId; - environmentVariables["CORECLR_PROFILER_PATH"] = profilerPath; - } - else - { - environmentVariables["COR_ENABLE_PROFILING"] = profilerEnabled; - environmentVariables["COR_PROFILER"] = EnvironmentTools.ProfilerClsId; - environmentVariables["COR_PROFILER_PATH"] = profilerPath; - } + environmentVariables["COR_ENABLE_PROFILING"] = profilerEnabled; + environmentVariables["COR_PROFILER"] = EnvironmentTools.ProfilerClsId; + environmentVariables["COR_PROFILER_PATH"] = profilerPath; } if (DebugModeEnabled) diff --git a/test/integration-tests/IntegrationTests.Helpers/ProfilerHelper.cs b/test/integration-tests/IntegrationTests.Helpers/InstrumentedProcessHelper.cs similarity index 80% rename from test/integration-tests/IntegrationTests.Helpers/ProfilerHelper.cs rename to test/integration-tests/IntegrationTests.Helpers/InstrumentedProcessHelper.cs index 76eb62d49..aabb02dd0 100644 --- a/test/integration-tests/IntegrationTests.Helpers/ProfilerHelper.cs +++ b/test/integration-tests/IntegrationTests.Helpers/InstrumentedProcessHelper.cs @@ -3,16 +3,17 @@ using System.Diagnostics; namespace IntegrationTests.Helpers { - public class ProfilerHelper + public class InstrumentedProcessHelper { - public static Process StartProcessWithProfiler( + public static Process StartInstrumentedProcess( string executable, EnvironmentHelper environmentHelper, string arguments = null, bool redirectStandardInput = false, int traceAgentPort = 9696, int aspNetCorePort = 5000, - string processToProfile = null) + string processToProfile = null, + bool enableStartupHook = true) { if (environmentHelper == null) { @@ -24,7 +25,7 @@ namespace IntegrationTests.Helpers var startInfo = new ProcessStartInfo(executable, $"{arguments ?? string.Empty}"); - environmentHelper.SetEnvironmentVariables(traceAgentPort, aspNetCorePort, startInfo.EnvironmentVariables, processToProfile); + environmentHelper.SetEnvironmentVariables(traceAgentPort, aspNetCorePort, startInfo.EnvironmentVariables, enableStartupHook, processToProfile); startInfo.UseShellExecute = false; startInfo.CreateNoWindow = true; diff --git a/test/integration-tests/IntegrationTests.Helpers/StartupHookHelper.cs b/test/integration-tests/IntegrationTests.Helpers/StartupHookHelper.cs deleted file mode 100644 index 9b63d8540..000000000 --- a/test/integration-tests/IntegrationTests.Helpers/StartupHookHelper.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Diagnostics; - -namespace IntegrationTests.Helpers -{ - public class StartupHookHelper - { - public static Process StartProcessWithStartupHook( - string executable, - EnvironmentHelper environmentHelper, - string arguments = null, - bool redirectStandardInput = false, - int traceAgentPort = 9696, - int aspNetCorePort = 5000, - string processToProfile = null) - { - if (environmentHelper == null) - { - throw new ArgumentNullException(nameof(environmentHelper)); - } - - // clear all relevant environment variables to start with a clean slate - EnvironmentHelper.ClearProfilerEnvironmentVariables(); - - var startInfo = new ProcessStartInfo(executable, $"{arguments ?? string.Empty}"); - - environmentHelper.SetEnvironmentVariables(traceAgentPort, aspNetCorePort, startInfo.EnvironmentVariables, processToProfile, true); - - startInfo.UseShellExecute = false; - startInfo.CreateNoWindow = true; - startInfo.RedirectStandardOutput = true; - startInfo.RedirectStandardError = true; - startInfo.RedirectStandardInput = redirectStandardInput; - - return Process.Start(startInfo); - } - } -} diff --git a/test/integration-tests/IntegrationTests.Helpers/TestHelper.cs b/test/integration-tests/IntegrationTests.Helpers/TestHelper.cs index 2b31a8bb4..c7d9f4920 100644 --- a/test/integration-tests/IntegrationTests.Helpers/TestHelper.cs +++ b/test/integration-tests/IntegrationTests.Helpers/TestHelper.cs @@ -89,7 +89,7 @@ namespace IntegrationTests.Helpers return new Container(container); } - public Process StartSample(int traceAgentPort, string arguments, string packageVersion, int aspNetCorePort, string framework = "", bool startupHook = false) + public Process StartSample(int traceAgentPort, string arguments, string packageVersion, int aspNetCorePort, string framework = "", bool enableStartupHook = true) { // get path to sample app that the profiler will attach to string sampleAppPath = EnvironmentHelper.GetSampleApplicationPath(packageVersion, framework); @@ -102,31 +102,19 @@ namespace IntegrationTests.Helpers var executable = EnvironmentHelper.IsCoreClr() ? EnvironmentHelper.GetSampleExecutionSource() : sampleAppPath; var args = EnvironmentHelper.IsCoreClr() ? $"{sampleAppPath} {arguments ?? string.Empty}" : arguments; - if (startupHook) - { - return StartupHookHelper.StartProcessWithStartupHook( - executable, - EnvironmentHelper, - args, - traceAgentPort: traceAgentPort, - aspNetCorePort: aspNetCorePort, - processToProfile: executable); - } - else - { - return ProfilerHelper.StartProcessWithProfiler( - executable, - EnvironmentHelper, - args, - traceAgentPort: traceAgentPort, - aspNetCorePort: aspNetCorePort, - processToProfile: executable); - } + return InstrumentedProcessHelper.StartInstrumentedProcess( + executable, + EnvironmentHelper, + args, + traceAgentPort: traceAgentPort, + aspNetCorePort: aspNetCorePort, + processToProfile: executable, + enableStartupHook: enableStartupHook); } - public ProcessResult RunSampleAndWaitForExit(int traceAgentPort, string arguments = null, string packageVersion = "", string framework = "", int aspNetCorePort = 5000, bool startupHook = false) + public ProcessResult RunSampleAndWaitForExit(int traceAgentPort, string arguments = null, string packageVersion = "", string framework = "", int aspNetCorePort = 5000, bool enableStartupHook = true) { - var process = StartSample(traceAgentPort, arguments, packageVersion, aspNetCorePort: aspNetCorePort, framework: framework, startupHook: startupHook); + var process = StartSample(traceAgentPort, arguments, packageVersion, aspNetCorePort: aspNetCorePort, framework: framework, enableStartupHook: enableStartupHook); var name = process.ProcessName; using var helper = new ProcessHelper(process); diff --git a/test/integration-tests/IntegrationTests.StartupHook/StartupHookTests.cs b/test/integration-tests/IntegrationTests.StartupHook/StartupHookTests.cs index 7e704270e..858b68c28 100644 --- a/test/integration-tests/IntegrationTests.StartupHook/StartupHookTests.cs +++ b/test/integration-tests/IntegrationTests.StartupHook/StartupHookTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using IntegrationTests.Helpers; using IntegrationTests.Helpers.Mocks; @@ -18,6 +19,8 @@ namespace IntegrationTests.StartupHook public StartupHookTests(ITestOutputHelper output) : base("StartupHook", output) { + SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName); + SetEnvironmentVariable("OTEL_DOTNET_TRACER_INSTRUMENTATIONS", "HttpClient"); _expectations.Add(new WebServerSpanExpectation(ServiceName, null, "SayHello", "SayHello", null)); _expectations.Add(new WebServerSpanExpectation(ServiceName, null, "HTTP GET", "HTTP GET", null, "GET")); } @@ -26,9 +29,22 @@ namespace IntegrationTests.StartupHook [Trait("Category", "EndToEnd")] public void SubmitsTraces() { - SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName); + const int expectedSpanCount = 2; - RunTestAndValidateResults(2); + var spans = RunTestApplication(enableStartupHook: true); + + AssertExpectedSpansReceived(expectedSpanCount, spans); + } + + [Fact] + [Trait("Category", "EndToEnd")] + public void InstrumentationNotAvailableWhenStartupHookIsNotEnabled() + { + const int expectedSpanCount = 0; + + var spans = RunTestApplication(enableStartupHook: false); + + AssertExpectedSpansReceived(expectedSpanCount, spans); } [Theory] @@ -45,30 +61,34 @@ namespace IntegrationTests.StartupHook expectedSpanCount = 0; } - SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName); SetEnvironmentVariable("OTEL_PROFILER_EXCLUDE_PROCESSES", $"dotnet,dotnet.exe,{applicationNameToExclude}"); - RunTestAndValidateResults(expectedSpanCount); + var spans = RunTestApplication(enableStartupHook: true); + + AssertExpectedSpansReceived(expectedSpanCount, spans); } - private void RunTestAndValidateResults(int expectedSpanCount) + private IImmutableList RunTestApplication(bool enableStartupHook) { int agentPort = TcpPortProvider.GetOpenPort(); using (var agent = new MockZipkinCollector(Output, agentPort)) - using (var processResult = RunSampleAndWaitForExit(agent.Port, startupHook: true)) + using (var processResult = RunSampleAndWaitForExit(agent.Port, enableStartupHook: enableStartupHook)) { Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode} and exception: {processResult.StandardError}"); - var span = agent.WaitForSpans(2, 500); + return agent.WaitForSpans(2, 500); + } + } - Assert.True(span.Count() == expectedSpanCount, $"Expecting {expectedSpanCount} spans, received {span.Count()}"); - if (expectedSpanCount > 0) - { - Assert.Single(span.Select(s => s.Service).Distinct()); + private void AssertExpectedSpansReceived(int expectedSpanCount, IImmutableList spans) + { + Assert.True(spans.Count() == expectedSpanCount, $"Expecting {expectedSpanCount} spans, received {spans.Count()}"); + if (expectedSpanCount > 0) + { + Assert.Single(spans.Select(s => s.Service).Distinct()); - var spanList = span.ToList(); - AssertExpectationsMet(_expectations, spanList); - } + var spanList = spans.ToList(); + AssertExpectationsMet(_expectations, spanList); } } diff --git a/test/test-applications/integrations/Samples.GraphQL/Program.cs b/test/test-applications/integrations/Samples.GraphQL/Program.cs index b1635d8af..62b8cef8b 100644 --- a/test/test-applications/integrations/Samples.GraphQL/Program.cs +++ b/test/test-applications/integrations/Samples.GraphQL/Program.cs @@ -28,7 +28,7 @@ namespace Samples.GraphQL var logger = host.Services.GetRequiredService>(); logger.LogInformation($"Instrumentation.ProfilerAttached = {IsProfilerAttached()}"); - var prefixes = new[] { "COR_", "CORECLR_", "OTEL_" }; + var prefixes = new[] { "COR_", "CORECLR_", "DOTNET_", "OTEL_" }; var envVars = from envVar in Environment.GetEnvironmentVariables().Cast() from prefix in prefixes let key = (envVar.Key as string)?.ToUpperInvariant() diff --git a/test/test-applications/integrations/Samples.GraphQL/Properties/launchSettings.json b/test/test-applications/integrations/Samples.GraphQL/Properties/launchSettings.json index 10cdbd050..201860748 100644 --- a/test/test-applications/integrations/Samples.GraphQL/Properties/launchSettings.json +++ b/test/test-applications/integrations/Samples.GraphQL/Properties/launchSettings.json @@ -17,6 +17,7 @@ "CORECLR_ENABLE_PROFILING": "1", "CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}", "CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home\\win-x64\\OpenTelemetry.ClrProfiler.Native.dll", + "DOTNET_STARTUP_HOOKS": "$(SolutionDir)bin\\tracer-home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll", "OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home\\", "OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json", diff --git a/test/test-applications/integrations/Samples.MongoDB/Properties/launchSettings.json b/test/test-applications/integrations/Samples.MongoDB/Properties/launchSettings.json index aaa9f6914..61f8ffc13 100644 --- a/test/test-applications/integrations/Samples.MongoDB/Properties/launchSettings.json +++ b/test/test-applications/integrations/Samples.MongoDB/Properties/launchSettings.json @@ -6,6 +6,7 @@ "CORECLR_ENABLE_PROFILING": "1", "CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}", "CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home\\win-x64\\OpenTelemetry.ClrProfiler.Native.dll", + "DOTNET_STARTUP_HOOKS": "$(SolutionDir)bin\\tracer-home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll", "OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home\\", "OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json", diff --git a/test/test-applications/integrations/aspnet/Samples.AspNet/Controllers/HomeController.cs b/test/test-applications/integrations/aspnet/Samples.AspNet/Controllers/HomeController.cs index d19720c5a..096545ea3 100644 --- a/test/test-applications/integrations/aspnet/Samples.AspNet/Controllers/HomeController.cs +++ b/test/test-applications/integrations/aspnet/Samples.AspNet/Controllers/HomeController.cs @@ -12,7 +12,7 @@ namespace Samples.AspNet.Controllers { public ActionResult Index() { - var prefixes = new[] { "COR_", "CORECLR_", "OTEL_" }; + var prefixes = new[] { "COR_", "CORECLR_", "DOTNET_", "OTEL_" }; var envVars = from envVar in Environment.GetEnvironmentVariables().Cast() from prefix in prefixes