Skip to content

Commit 5b077f3

Browse files
authored
feat: Add support for .NET Framework Azure Function Apps (#2992)
1 parent aa06d9d commit 5b077f3

File tree

9 files changed

+95
-26
lines changed

9 files changed

+95
-26
lines changed

src/Agent/NewRelic/Home/Home.csproj

+3-3
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
</PropertyGroup>
99

1010
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
11-
<Exec Condition="'$(NR_DEV_BUILD_HOME)' != 'false'" Command="del /q &quot;$(TargetDir)*.*&quot;" />
12-
<Exec Condition="'$(NR_DEV_BUILD_HOME)' != 'false'" WorkingDirectory="$(SolutionDir)Build" Command="powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File .\build_home.ps1 -Configuration $(ConfigurationName)" />
11+
<Exec Condition="'$(NR_DEV_BUILD_HOME)' != 'false'" Command="del /q &quot;$(TargetDir)*.*&quot;"/>
12+
<Exec Condition="'$(NR_DEV_BUILD_HOME)' != 'false'" WorkingDirectory="$(SolutionDir)Build" Command="powershell.exe -ExecutionPolicy Bypass -NoProfile -NonInteractive -File .\build_home.ps1 -Configuration $(ConfigurationName)"/>
1313
</Target>
1414

1515
<ItemGroup>
16-
<PackageReference Include="NewRelic.Agent.Internal.Profiler" Version="10.34.0.14"/>
16+
<PackageReference Include="NewRelic.Agent.Internal.Profiler" Version="10.36.0.5"/>
1717
</ItemGroup>
1818

1919
</Project>

src/Agent/NewRelic/Profiler/Configuration/Configuration.h

+28-14
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,20 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
183183

184184
bool ShouldInstrument(xstring_t const& processPath, xstring_t const& parentProcessPath, xstring_t const& appPoolId, xstring_t const& commandLine, bool isCoreClr)
185185
{
186+
if (IsAzureFunction()) // valid for both .NET Framework and .NET Core
187+
{
188+
if (IsAzureFunctionModeEnabled()) // if not explicitly enabled, fall back to "legacy" behavior
189+
{
190+
auto retVal = ShouldInstrumentAzureFunction(processPath, appPoolId, commandLine);
191+
if (retVal == 0) {
192+
return false;
193+
}
194+
if (retVal == 1) {
195+
return true;
196+
}
197+
}
198+
}
199+
186200
if (isCoreClr)
187201
{
188202
return ShouldInstrumentNetCore(processPath, parentProcessPath, appPoolId, commandLine);
@@ -610,20 +624,6 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
610624
return false;
611625
}
612626

613-
if (IsAzureFunction())
614-
{
615-
if (IsAzureFunctionModeEnabled()) // if not explicitly enabled, fall back to "legacy" behavior
616-
{
617-
auto retVal = ShouldInstrumentAzureFunction(processPath, appPoolId, commandLine);
618-
if (retVal == 0) {
619-
return false;
620-
}
621-
if (retVal == 1) {
622-
return true;
623-
}
624-
}
625-
}
626-
627627
if (IsW3wpProcess(processPath, parentProcessPath)) {
628628
return ShouldInstrumentApplicationPool(appPoolId);
629629
}
@@ -680,6 +680,20 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
680680
return 0;
681681
}
682682

683+
// look for --functions-worker-id (or --worker-id, to instrument during local testing) in the command line and instrument if found
684+
bool isFunctionsWorkerId = NewRelic::Profiler::Strings::ContainsCaseInsensitive(commandLine, _X("--functions-worker-id"));
685+
if (isFunctionsWorkerId)
686+
{
687+
LogInfo(L"Command line contains --functions-worker-id. This process will be instrumented.");
688+
return 1;
689+
}
690+
bool isLocalFunctionsWorkerId = NewRelic::Profiler::Strings::ContainsCaseInsensitive(commandLine, _X("--worker-id"));
691+
if (isLocalFunctionsWorkerId)
692+
{
693+
LogInfo(L"Command line contains --worker-id. This process will be instrumented.");
694+
return 1;
695+
}
696+
683697
LogInfo("Couldn't determine whether this Azure Function process should be instrumented based on commandLine. Falling back to checking application pool");
684698
return -1; // indeterminate
685699
}

src/Agent/NewRelic/Profiler/ConfigurationTest/ConfigurationTest.cpp

+38-1
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,42 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
186186
Assert::IsTrue(configuration.ShouldInstrument(L"functionsnethost.exe", L"", L"", L"blah blah blah FooBarBaz blah blah blah", true));
187187
}
188188

189+
TEST_METHOD(should_instrument_azure_function_functions_worker_id_in_command_line)
190+
{
191+
std::wstring configurationXml(L"\
192+
<?xml version=\"1.0\"?>\
193+
<configuration>\
194+
<log level=\"deBug\"/>\
195+
</configuration>\
196+
");
197+
198+
auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
199+
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet-isolated";
200+
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"true";
201+
202+
Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);
203+
204+
Assert::IsTrue(configuration.ShouldInstrument(L"SomeFW481FunctionApp.exe", L"", L"", L"blah blah blah --functions-worker-id FooBarBaz blah blah blah", false));
205+
}
206+
207+
TEST_METHOD(should_instrument_azure_function_worker_id_in_command_line)
208+
{
209+
std::wstring configurationXml(L"\
210+
<?xml version=\"1.0\"?>\
211+
<configuration>\
212+
<log level=\"deBug\"/>\
213+
</configuration>\
214+
");
215+
216+
auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
217+
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet-isolated";
218+
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"true";
219+
220+
Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);
221+
222+
Assert::IsTrue(configuration.ShouldInstrument(L"SomeFW481FunctionApp.exe", L"", L"", L"blah blah blah --worker-id FooBarBaz blah blah blah", false));
223+
}
224+
189225
TEST_METHOD(instrument_process)
190226
{
191227
ProcessesPtr processes(new Processes());
@@ -373,7 +409,8 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
373409
TEST_METHOD(Azure_WebSites_background_application_pool_ignored)
374410
{
375411
std::wstring configurationXml(L"<?xml version=\"1.0\"?><configuration/>");
376-
Configuration configuration(configurationXml, _missingAgentEnabledConfigPair);
412+
auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
413+
Configuration configuration(configurationXml, _missingAgentEnabledConfigPair, L"", systemCalls);
377414
Assert::IsFalse(configuration.ShouldInstrument(L"w3wp.exe", L"", L"~Foo", L"", false));
378415
}
379416

tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/AzureFunctionApplication.csproj

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
3+
<TargetFrameworks>net481;net8.0;net9.0</TargetFrameworks>
44
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
55
<OutputType>Exe</OutputType>
66
<ImplicitUsings>enable</ImplicitUsings>
@@ -22,7 +22,7 @@
2222
</Content>
2323
</ItemGroup>
2424
<ItemGroup>
25-
<FrameworkReference Include="Microsoft.AspNetCore.App" />
25+
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' == 'net9.0'"/>
2626
<None Include="local.settings.json" />
2727
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.0.0" />
2828
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />

tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/HttpTriggerFunctionUsingAspNetCorePipeline.cs

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright 2020 New Relic, Inc. All rights reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
#if NET9_0
45
using System.ComponentModel.DataAnnotations;
56
using Microsoft.AspNetCore.Http;
67
using Microsoft.AspNetCore.Mvc;
@@ -34,3 +35,4 @@ public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "
3435
}
3536
}
3637
}
38+
#endif

tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/Program.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ private static async Task Main(string[] args)
2020

2121

2222
var host = new HostBuilder()
23-
// the net8 target uses the "basic" azure function configuration
23+
// the net481 and net8 target uses the "basic" azure function configuration
2424
// the net9 target uses the aspnetcore azure function configuration
25-
#if NET8_0
25+
#if NETFRAMEWORK || NET8_0
2626
.ConfigureFunctionsWorkerDefaults()
2727
#elif NET9_0
2828
.ConfigureFunctionsWebApplication()

tests/Agent/IntegrationTests/IntegrationTests/AzureFunction/AzureFunctionHttpTriggerTests.cs

+9
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,12 @@ public AzureFunctionHttpTriggerTestsCoreLatest(AzureFunctionApplicationFixtureHt
357357
{
358358
}
359359
}
360+
361+
[NetFrameworkTest]
362+
public class AzureFunctionHttpTriggerTestsFWLatest : AzureFunctionHttpTriggerTestsBase<AzureFunctionApplicationFixtureHttpTriggerFWLatest>
363+
{
364+
public AzureFunctionHttpTriggerTestsFWLatest(AzureFunctionApplicationFixtureHttpTriggerFWLatest fixture, ITestOutputHelper output)
365+
: base(fixture, output, AzureFunctionHttpTriggerTestMode.SimpleInvocation)
366+
{
367+
}
368+
}

tests/Agent/IntegrationTests/IntegrationTests/RemoteServiceFixtures/AzureFunctionApplicationFixture.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ public abstract class AzureFunctionApplicationFixture : RemoteApplicationFixture
2323
private const string Priority = "1.23456";
2424
private const string Timestamp = "1518469636025";
2525

26-
protected AzureFunctionApplicationFixture(string functionNames, string targetFramework, bool enableAzureFunctionMode)
27-
: base(new AzureFuncTool(ApplicationDirectoryName, targetFramework, ApplicationType.Bounded, true, true, true, enableAzureFunctionMode))
26+
protected AzureFunctionApplicationFixture(string functionNames, string targetFramework, bool enableAzureFunctionMode, bool isCoreApp = true)
27+
: base(new AzureFuncTool(ApplicationDirectoryName, targetFramework, ApplicationType.Bounded, true, isCoreApp, true, enableAzureFunctionMode))
2828
{
2929
CommandLineArguments = $"start --no-build --language-worker dotnet-isolated --dotnet-isolated --functions {functionNames} ";
3030

@@ -73,6 +73,13 @@ public AzureFunctionApplicationFixtureHttpTriggerCoreLatest() : base("httpTrigge
7373
}
7474
}
7575

76+
public class AzureFunctionApplicationFixtureHttpTriggerFWLatest : AzureFunctionApplicationFixture
77+
{
78+
public AzureFunctionApplicationFixtureHttpTriggerFWLatest() : base("httpTriggerFunctionUsingSimpleInvocation", "net481", true, false)
79+
{
80+
}
81+
}
82+
7683
public class AzureFunctionApplicationFixtureInstrumentationDisabledCoreLatest : AzureFunctionApplicationFixture
7784
{
7885
public AzureFunctionApplicationFixtureInstrumentationDisabledCoreLatest() : base("httpTriggerFunctionUsingAspNetCorePipeline httpTriggerFunctionUsingSimpleInvocation", "net9.0", false)

tests/Agent/UnitTests/Core.UnitTest/CustomEvents/CustomEventWireModelTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ public void CustomEvents_MultipleEvents_Serialization()
9292
[Test]
9393
public void CustomEvents_UserAttributes_AllAttributeTypesSerializeCorrectly()
9494
{
95-
var dateTime = DateTime.UtcNow;
95+
var dateTime = DateTime.Parse("2025-02-13T20:15:14.4214979Z");
9696
var timestampVal = dateTime;
9797
var typeVal = $"CustomEvent";
9898

0 commit comments

Comments
 (0)