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

fix: Update Profiler to check whether Azure function mode support is enabled #2822

Merged
merged 9 commits into from
Oct 10, 2024
2 changes: 1 addition & 1 deletion src/Agent/NewRelic/Home/Home.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</Target>

<ItemGroup>
<PackageReference Include="NewRelic.Agent.Internal.Profiler" Version="10.29.0.66"/>
<PackageReference Include="NewRelic.Agent.Internal.Profiler" Version="10.31.0.15"/>
</ItemGroup>

</Project>
29 changes: 22 additions & 7 deletions src/Agent/NewRelic/Profiler/Configuration/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -605,13 +605,17 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
return false;
}

if (IsAzureFunction()) {
auto retVal = ShouldInstrumentAzureFunction(processPath, appPoolId, commandLine);
if (retVal == 0) {
return false;
}
if (retVal == 1) {
return true;
if (IsAzureFunction())
{
if (IsAzureFunctionModeEnabled()) // if not explicitly enabled, fall back to "legacy" behavior
{
auto retVal = ShouldInstrumentAzureFunction(processPath, appPoolId, commandLine);
if (retVal == 0) {
return false;
}
if (retVal == 1) {
return true;
}
}
}

Expand All @@ -622,6 +626,17 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
return true;
}

bool IsAzureFunctionModeEnabled() const
{
auto azureFunctionModeEnabled = _systemCalls->TryGetEnvironmentVariable(_X("NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"));

if (azureFunctionModeEnabled == nullptr || azureFunctionModeEnabled->length() == 0) {
return false;
}

return Strings::AreEqualCaseInsensitive(*azureFunctionModeEnabled, _X("true")) || Strings::AreEqualCaseInsensitive(*azureFunctionModeEnabled, _X("1"));
}

bool IsAzureFunction() const
{
// Azure Functions sets the FUNCTIONS_WORKER_RUNTIME environment variable to "dotnet-isolated" when running in the .NET worker.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,47 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsFalse(configuration.ShouldInstrument(L"foo.exe", L"", L"", L"", false));
}

// tests to verify that "legacy" behavior (before azure function support) is retained.
// If NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED environment variable is not set or is set to false,
// we should behave as if no azure function support has been added.
TEST_METHOD(azure_function_should_behave_as_legacy_if_azure_function_mode_not_specified)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
<configuration>\
<log level=\"deBug\"/>\
</configuration>\
");

auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet-isolated";

Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);

Assert::IsTrue(configuration.ShouldInstrument(L"functionsnethost.exe", L"", L"", L"blah blah blah FooBarBaz blah blah blah", true));
}

// tests to verify that "legacy" behavior (before azure function support) is retained.
// If NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED environment variable is not set or is set to false,
// we should behave as if no azure function support has been added.
TEST_METHOD(azure_function_should_behave_as_legacy_if_azure_function_mode_disabled)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
<configuration>\
<log level=\"deBug\"/>\
</configuration>\
");

auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet-isolated";
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"0";

Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);

Assert::IsTrue(configuration.ShouldInstrument(L"functionsnethost.exe", L"", L"", L"blah blah blah FooBarBaz blah blah blah", true));
}

TEST_METHOD(should_not_instrument_azure_function_app_pool_id_in_commandline)
{
std::wstring configurationXml(L"\
Expand All @@ -84,6 +125,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te

auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet-isolated";
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"true";

Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);

Expand All @@ -101,6 +143,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te

auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet-isolated";
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"true";

Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);

Expand All @@ -118,6 +161,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te

auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet-isolated";
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"true";

Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);

Expand All @@ -135,6 +179,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te

auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet-isolated";
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"true";

Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,26 @@ protected AzureFunctionHttpTriggerTestsBase(TFixture fixture, ITestOutputHelper
_fixture.AddActions(
setupConfiguration: () =>
{
new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath)
var configModifier = new NewRelicConfigModifier(fixture.DestinationNewRelicConfigFilePath);
configModifier
.ForceTransactionTraces()
.ConfigureFasterTransactionTracesHarvestCycle(20)
.ConfigureFasterMetricsHarvestCycle(15)
.ConfigureFasterSpanEventsHarvestCycle(15)
.SetLogLevel("finest");

// This is a bit of a kludge. When azure function instrumentation is disabled,
// the agent instruments *two* processes: the azure function host (func.exe) and the actual function app.
// Both processes use the same config files, so explicitly setting the log file name forces both
// processes to log to the same file, which makes it easier to verify that the
// actual function app is not being instrumented when the Invoke() method gets hit.
//
// Ideally, we'd prefer to look for the specific log file for the azure function app, but that's less trivial
// and not worth the effort for this one test.
if (!_fixture.AzureFunctionModeEnabled)
{
configModifier.SetLogFileName("azure_function_instrumentation_disabled.log");
}
},
exerciseApplication: () =>
{
Expand Down Expand Up @@ -138,8 +152,8 @@ public void Test_SimpleInvocationMode()

if (!_fixture.AzureFunctionModeEnabled) // look for a specific log line that indicates azure function mode is disabled
{
var disabledLogLine = _fixture.AgentLog.TryGetLogLine(AgentLogBase.AzureFunctionModeDisabledLogLineRegex);
Assert.NotNull(disabledLogLine);
var disabledLogLines = _fixture.AgentLog.TryGetLogLines(AgentLogBase.AzureFunctionModeDisabledLogLineRegex);
Assert.Single(disabledLogLines);
}
}

Expand Down
Loading