From b3a568f8cc86c3b4c70691a7de0836c07d131eac Mon Sep 17 00:00:00 2001
From: Marty Tippin <120425148+tippmar-nr@users.noreply.github.com>
Date: Wed, 18 Sep 2024 16:30:41 -0500
Subject: [PATCH] Final cleanup for http trigger param parsing. Updated
integration tests to exercise more scenarios.
---
.../Core/Transactions/NoOpTransaction.cs | 1 +
.../Agent/Core/Transactions/Transaction.cs | 2 +
.../Api/ITransaction.cs | 2 +
.../FunctionsHttpProxyingMiddlewareWrapper.cs | 30 ++--
.../Wrapper/AzureFunction/Instrumentation.xml | 3 +-
.../InvokeFunctionAsyncWrapper.cs | 67 +++++----
.../AzureFunctionApplication.csproj | 4 +-
...pTriggerFunctionUsingAspNetCorePipeline.cs | 4 +-
...ttpTriggerFunctionUsingSimpleInvocation.cs | 7 +
.../AzureFunctionApplication/Program.cs | 6 +
.../AzureFunctionHttpTriggerTests.cs | 132 +++++++++++++++---
...ureFunctionInstrumentationDisabledTests.cs | 2 +-
.../IntegrationTests/IntegrationTests.csproj | 1 +
13 files changed, 195 insertions(+), 66 deletions(-)
diff --git a/src/Agent/NewRelic/Agent/Core/Transactions/NoOpTransaction.cs b/src/Agent/NewRelic/Agent/Core/Transactions/NoOpTransaction.cs
index 20330bc315..404cbac7f4 100644
--- a/src/Agent/NewRelic/Agent/Core/Transactions/NoOpTransaction.cs
+++ b/src/Agent/NewRelic/Agent/Core/Transactions/NoOpTransaction.cs
@@ -23,6 +23,7 @@ public class NoOpTransaction : ITransaction, ITransactionExperimental
public bool IsValid => false;
public bool IsFinished => false;
public ISegment CurrentSegment => Segment.NoOpSegment;
+ public bool HasHttpResponseStatusCode => false;
public DateTime StartTime => DateTime.UtcNow;
diff --git a/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs b/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs
index be528fbb02..9cbe293901 100644
--- a/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs
+++ b/src/Agent/NewRelic/Agent/Core/Transactions/Transaction.cs
@@ -72,6 +72,8 @@ public ISegment CurrentSegment
}
}
+ public bool HasHttpResponseStatusCode => TransactionMetadata.HttpResponseStatusCode.HasValue;
+
public ITracingState TracingState { get; private set; }
public string TraceId
diff --git a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs
index fa51268c47..ea0f22e041 100644
--- a/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs
+++ b/src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Api/ITransaction.cs
@@ -33,6 +33,8 @@ public interface ITransaction
///
ISegment CurrentSegment { get; }
+ bool HasHttpResponseStatusCode { get; }
+
///
/// End this transaction.
///
diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/FunctionsHttpProxyingMiddlewareWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/FunctionsHttpProxyingMiddlewareWrapper.cs
index c6e44e7dc2..e26016a5c2 100644
--- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/FunctionsHttpProxyingMiddlewareWrapper.cs
+++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/FunctionsHttpProxyingMiddlewareWrapper.cs
@@ -32,18 +32,24 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins
agent.CurrentTransaction.SetRequestMethod(httpContext.Request.Method);
agent.CurrentTransaction.SetUri(httpContext.Request.Path);
break;
- // not needed at present for getting status code, but keep in case we need to get more from httpContext - also update instrumentation.xml
- //case "TryHandleHttpResult":
- // object result = instrumentedMethodCall.MethodCall.MethodArguments[0];
- // httpContext = (HttpContext)instrumentedMethodCall.MethodCall.MethodArguments[2];
- // bool isInvocationResult = (bool)instrumentedMethodCall.MethodCall.MethodArguments[3];
-
- // agent.CurrentTransaction.SetHttpResponseStatusCode(httpContext.Response.StatusCode);
- // break;
- //case "TryHandleOutputBindingsHttpResult":
- // httpContext = (HttpContext)instrumentedMethodCall.MethodCall.MethodArguments[1];
- // agent.CurrentTransaction.SetHttpResponseStatusCode(httpContext.Response.StatusCode);
- // break;
+ case "TryHandleHttpResult":
+ if (!agent.CurrentTransaction.HasHttpResponseStatusCode) // these handlers seem to get called more than once; only set the status code one time
+ {
+ object result = instrumentedMethodCall.MethodCall.MethodArguments[0];
+
+ httpContext = (HttpContext)instrumentedMethodCall.MethodCall.MethodArguments[2];
+ bool isInvocationResult = (bool)instrumentedMethodCall.MethodCall.MethodArguments[3];
+
+ agent.CurrentTransaction.SetHttpResponseStatusCode(httpContext.Response.StatusCode);
+ }
+ break;
+ case "TryHandleOutputBindingsHttpResult":
+ if (!agent.CurrentTransaction.HasHttpResponseStatusCode) // these handlers seem to get called more than once; only set the status code one time
+ {
+ httpContext = (HttpContext)instrumentedMethodCall.MethodCall.MethodArguments[1];
+ agent.CurrentTransaction.SetHttpResponseStatusCode(httpContext.Response.StatusCode);
+ }
+ break;
}
}
diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/Instrumentation.xml b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/Instrumentation.xml
index 81560a5090..821b5cc8d2 100644
--- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/Instrumentation.xml
+++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/Instrumentation.xml
@@ -24,14 +24,13 @@ SPDX-License-Identifier: Apache-2.0
-
+
diff --git a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/InvokeFunctionAsyncWrapper.cs b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/InvokeFunctionAsyncWrapper.cs
index b56232ca27..3ba11d76cb 100644
--- a/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/InvokeFunctionAsyncWrapper.cs
+++ b/src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AzureFunction/InvokeFunctionAsyncWrapper.cs
@@ -16,6 +16,7 @@ namespace NewRelic.Providers.Wrapper.AzureFunction
{
public class InvokeFunctionAsyncWrapper : IWrapper
{
+ private static MethodInfo _getInvocationResultMethod;
private static bool _loggedDisabledMessage;
private const string WrapperName = "AzureFunctionInvokeAsyncWrapper";
@@ -105,33 +106,30 @@ void InvokeFunctionAsyncResponse(Task responseTask)
return;
}
- if (functionDetails.IsWebTrigger)
+ // only pull response status code here if it's a web trigger and the Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore assembly is not loaded.
+ if (functionDetails.IsWebTrigger && functionDetails.HasAspNetCoreExtensionReference != null && !functionDetails.HasAspNetCoreExtensionReference.Value)
{
- // GetInvocationResult is a static extension method
- // there are multiple GetInvocationResult methods in this type; we want the one without any generic parameters
- Type type = functionContext.GetType().Assembly.GetType("Microsoft.Azure.Functions.Worker.FunctionContextBindingFeatureExtensions");
- var getInvocationResultMethod = type.GetMethods().Single(m => m.Name == "GetInvocationResult" && !m.ContainsGenericParameters);
+ if (_getInvocationResultMethod == null)
+ {
+ // GetInvocationResult is a static extension method
+ // there are multiple GetInvocationResult methods in this type; we want the one without any generic parameters
+ Type type = functionContext.GetType().Assembly.GetType("Microsoft.Azure.Functions.Worker.FunctionContextBindingFeatureExtensions");
+ _getInvocationResultMethod = type.GetMethods().Single(m => m.Name == "GetInvocationResult" && !m.ContainsGenericParameters);
+ }
- dynamic invocationResult = getInvocationResultMethod.Invoke(null, new[] { functionContext });
+ dynamic invocationResult = _getInvocationResultMethod.Invoke(null, new[] { functionContext });
var result = invocationResult?.Value;
- // the result always seems to be of this type regardless of whether the app
- // uses the Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore package or not
- var resultTypeName = result?.GetType().Name;
- if (resultTypeName == "GrpcHttpResponseData")
- {
- transaction.SetHttpResponseStatusCode((int)result.StatusCode);
- }
- else
+ if (result != null && result.StatusCode != null)
{
- agent.Logger.Debug($"Unexpected Azure Function invocationResult.Value type '{resultTypeName ?? "(null)"}' - unable to set http response status code.");
+ var statusCode = result.StatusCode;
+ transaction.SetHttpResponseStatusCode((int)statusCode);
}
- }
-
+ }
}
catch (Exception ex)
{
- agent.Logger.Error(ex, "Error processing Azure Function response.");
+ agent.Logger.Warn(ex, "Error processing Azure Function response.");
throw;
}
finally
@@ -223,7 +221,7 @@ public FunctionDetails(dynamic functionContext, IAgent agent)
if (IsWebTrigger)
{
- ParseRequestParameters(agent, functionContext);
+ ParseHttpTriggerParameters(agent, functionContext);
}
}
catch (Exception ex)
@@ -233,17 +231,18 @@ public FunctionDetails(dynamic functionContext, IAgent agent)
}
}
- private void ParseRequestParameters(IAgent agent, dynamic functionContext)
+ private void ParseHttpTriggerParameters(IAgent agent, dynamic functionContext)
{
if (!_hasAspNetCoreExtensionsReference.HasValue)
{
// see if the Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore assembly is in the list of loaded assemblies
var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
var assembly = loadedAssemblies.FirstOrDefault(a => a.GetName().Name == "Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore");
+
_hasAspNetCoreExtensionsReference = assembly != null;
if (_hasAspNetCoreExtensionsReference.Value)
- agent.Logger.Debug("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore assembly is loaded; not parsing request parameters in InvokeFunctionAsyncWrapper.");
+ agent.Logger.Debug("Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore assembly is loaded; InvokeFunctionAsyncWrapper will defer HttpTrigger parameter parsing to FunctionsHttpProxyingMiddlewareWrapper.");
}
// don't parse request parameters here if the Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore assembly is loaded.
@@ -285,17 +284,23 @@ private void ParseRequestParameters(IAgent agent, dynamic functionContext)
if (_genericFunctionInputBindingFeatureGetter != null)
{
// Get the input binding feature and bind the input from the function context
- var inputBindingFeature = _genericFunctionInputBindingFeatureGetter.Invoke(features, new object[] { });
- dynamic valueTask = _bindFunctionInputAsync.Invoke(inputBindingFeature, new object[] { functionContext });
- valueTask.AsTask().Wait();
- var inputArguments = valueTask.Result.Values;
- var reqData = inputArguments[0];
+ var inputBindingFeature = _genericFunctionInputBindingFeatureGetter.Invoke(features, []);
+ dynamic valueTask = _bindFunctionInputAsync.Invoke(inputBindingFeature, [functionContext]);
+
+ valueTask.AsTask().Wait(); // BindFunctionInputAsync returns a ValueTask, so we need to convert it to a Task to wait on it
- if (reqData != null && reqData.GetType().Name == "GrpcHttpRequestData" && !string.IsNullOrEmpty(reqData.Method))
+ object[] inputArguments = valueTask.Result.Values;
+
+ if (inputArguments is { Length: > 0 })
{
- RequestMethod = reqData.Method;
- Uri uri = reqData.Url;
- RequestPath = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
+ var reqData = (dynamic)inputArguments[0];
+
+ if (reqData != null && reqData.GetType().Name == "GrpcHttpRequestData" && !string.IsNullOrEmpty(reqData.Method))
+ {
+ RequestMethod = reqData.Method;
+ Uri uri = reqData.Url;
+ RequestPath = $"/{uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)}"; // has to start with a slash
+ }
}
}
}
@@ -312,6 +317,8 @@ public bool IsValid()
public bool IsWebTrigger => Trigger == "http";
public string RequestMethod { get; private set; }
public string RequestPath { get; private set; }
+
+ public bool? HasAspNetCoreExtensionReference => _hasAspNetCoreExtensionsReference;
}
}
diff --git a/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/AzureFunctionApplication.csproj b/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/AzureFunctionApplication.csproj
index d958307a4a..01a31a08ca 100644
--- a/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/AzureFunctionApplication.csproj
+++ b/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/AzureFunctionApplication.csproj
@@ -1,4 +1,4 @@
-
+
net6.0;net8.0
v4
@@ -26,7 +26,7 @@
-
+
diff --git a/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/HttpTriggerFunctionUsingAspNetCorePipeline.cs b/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/HttpTriggerFunctionUsingAspNetCorePipeline.cs
index 94513157af..81fb495bdf 100644
--- a/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/HttpTriggerFunctionUsingAspNetCorePipeline.cs
+++ b/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/HttpTriggerFunctionUsingAspNetCorePipeline.cs
@@ -1,6 +1,7 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
@@ -19,7 +20,7 @@ public HttpTriggerFunctionUsingAspNetCorePipeline(ILogger Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
+ public async Task Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] [FromQuery, Required] string someParam)
{
_logger.LogInformation("HttpTriggerFunctionUsingAspNetCorePipeline processed a request.");
@@ -29,7 +30,6 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Function, "
_firstTime = false;
}
-
return new OkObjectResult("Welcome to Azure Functions!");
}
}
diff --git a/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/HttpTriggerFunctionUsingSimpleInvocation.cs b/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/HttpTriggerFunctionUsingSimpleInvocation.cs
index 655eb94026..108ce91c41 100644
--- a/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/HttpTriggerFunctionUsingSimpleInvocation.cs
+++ b/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/HttpTriggerFunctionUsingSimpleInvocation.cs
@@ -13,6 +13,7 @@ namespace AzureFunctionApplication
///
public class HttpTriggerFunctionUsingSimpleInvocation
{
+ private static bool _firstTime = true;
private readonly ILogger _logger;
public HttpTriggerFunctionUsingSimpleInvocation(ILogger logger)
@@ -25,6 +26,12 @@ public async Task Run([HttpTrigger(AuthorizationLevel.Function
{
_logger.LogInformation("HttpTriggerFunctionUsingSimpleInvocation processed a request.");
+ if (_firstTime)
+ {
+ await Task.Delay(250); // to ensure that the first invocation gets sampled
+ _firstTime = false;
+ }
+
var response = reqData.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
diff --git a/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/Program.cs b/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/Program.cs
index be35451855..5f8c3c6f37 100644
--- a/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/Program.cs
+++ b/tests/Agent/IntegrationTests/Applications/AzureFunctionApplication/Program.cs
@@ -20,7 +20,13 @@ private static async Task Main(string[] args)
var host = new HostBuilder()
+// the net6 target uses the "basic" azure function configuration
+// the net8 target uses the aspnetcore azure function configuration
+#if NET6_0
+ .ConfigureFunctionsWorkerDefaults()
+#else
.ConfigureFunctionsWebApplication()
+#endif
.Build();
var task = host.RunAsync(cts.Token);
diff --git a/tests/Agent/IntegrationTests/IntegrationTests/AzureFunction/AzureFunctionHttpTriggerTests.cs b/tests/Agent/IntegrationTests/IntegrationTests/AzureFunction/AzureFunctionHttpTriggerTests.cs
index 2fa2c392f4..ce6ae40981 100644
--- a/tests/Agent/IntegrationTests/IntegrationTests/AzureFunction/AzureFunctionHttpTriggerTests.cs
+++ b/tests/Agent/IntegrationTests/IntegrationTests/AzureFunction/AzureFunctionHttpTriggerTests.cs
@@ -11,14 +11,21 @@
namespace NewRelic.Agent.IntegrationTests.AzureFunction
{
+ public enum AzureFunctionHttpTriggerTestMode
+ {
+ AspNetCorePipeline,
+ SimpleInvocation
+ }
public abstract class AzureFunctionHttpTriggerTestsBase : NewRelicIntegrationTest
where TFixture : AzureFunctionApplicationFixture
{
private readonly TFixture _fixture;
+ private readonly AzureFunctionHttpTriggerTestMode _testMode;
- protected AzureFunctionHttpTriggerTestsBase(TFixture fixture, ITestOutputHelper output) : base(fixture)
+ protected AzureFunctionHttpTriggerTestsBase(TFixture fixture, ITestOutputHelper output, AzureFunctionHttpTriggerTestMode testMode) : base(fixture)
{
_fixture = fixture;
+ _testMode = testMode;
_fixture.TestLogger = output;
_fixture.AddActions(
@@ -33,9 +40,17 @@ protected AzureFunctionHttpTriggerTestsBase(TFixture fixture, ITestOutputHelper
},
exerciseApplication: () =>
{
- _fixture.Get("api/httpTriggerFunctionUsingAspNetCorePipeline");
- _fixture.Get("api/httpTriggerFunctionUsingAspNetCorePipeline"); // make a second call to verify coldStart is not sent
- _fixture.Get("api/httpTriggerFunctionUsingSimpleInvocation"); // invoke an http trigger function that does not use the aspnet core pipeline
+ if (_testMode == AzureFunctionHttpTriggerTestMode.AspNetCorePipeline)
+ {
+ _fixture.Get("api/httpTriggerFunctionUsingAspNetCorePipeline?someParameter=foo");
+ _fixture.Get("api/httpTriggerFunctionUsingAspNetCorePipeline?someParameter=bar"); // make a second call to verify coldStart is not sent
+ _fixture.Get("api/httpTriggerFunctionUsingSimpleInvocation"); // invoke an http trigger function that does not use the aspnet core pipeline, even in pipeline test mode
+ }
+ else
+ {
+ _fixture.Get("api/httpTriggerFunctionUsingSimpleInvocation");
+ _fixture.Get("api/httpTriggerFunctionUsingSimpleInvocation"); // make a second call to verify coldStart is not sent
+ }
_fixture.AgentLog.WaitForLogLines(AgentLogBase.TransactionSampleLogLineRegex, TimeSpan.FromMinutes(2));
}
);
@@ -43,9 +58,11 @@ protected AzureFunctionHttpTriggerTestsBase(TFixture fixture, ITestOutputHelper
_fixture.Initialize();
}
- [Fact]
- public void Test()
+ [SkippableFact]
+ public void Test_SimpleInvocationMode()
{
+ Skip.IfNot(_testMode == AzureFunctionHttpTriggerTestMode.SimpleInvocation);
+
var firstTransactionExpectedTransactionEventIntrinsicAttributes = new List
{
"faas.coldStart",
@@ -60,26 +77,105 @@ public void Test()
"faas.coldStart"
};
- var simpleTransactionExpectedTransactionEventIntrinsicAttributes = new List
+ var expectedAgentAttributes = new Dictionary
+ {
+ { "request.uri", "/api/httpTriggerFunctionUsingSimpleInvocation"},
+ { "request.method", "GET" },
+ { "http.statusCode", 200 }
+ };
+
+ var simpleTransactionName = "WebTransaction/AzureFunction/HttpTriggerFunctionUsingSimpleInvocation";
+ var simpleExpectedMetrics = new List()
+ {
+ new() {metricName = "DotNet/HttpTriggerFunctionUsingSimpleInvocation", callCount = 2},
+ new() {metricName = "DotNet/HttpTriggerFunctionUsingSimpleInvocation", metricScope = simpleTransactionName, callCount = 2},
+ new() {metricName = simpleTransactionName, callCount = 2},
+ };
+
+ var transactionSample = _fixture.AgentLog.TryGetTransactionSample(simpleTransactionName);
+
+ var metrics = _fixture.AgentLog.GetMetrics().ToList();
+
+ var simpleTransactionEvents = _fixture.AgentLog.GetTransactionEvents()
+ .Where(@event => @event?.IntrinsicAttributes?["name"]?.ToString() == simpleTransactionName)
+ .OrderBy(x => x.IntrinsicAttributes?["timestamp"])
+ .ToList();
+
+ var firstTransaction = simpleTransactionEvents.FirstOrDefault();
+ var secondTransaction = simpleTransactionEvents.Skip(1).FirstOrDefault();
+
+ if (_fixture.AzureFunctionModeEnabled)
+ {
+ Assertions.MetricsExist(simpleExpectedMetrics, metrics);
+
+ Assert.NotNull(transactionSample);
+ Assert.NotNull(firstTransaction);
+ Assert.NotNull(secondTransaction);
+
+ Assertions.TransactionTraceHasAttributes(firstTransactionExpectedTransactionEventIntrinsicAttributes, Tests.TestSerializationHelpers.Models.TransactionTraceAttributeType.Intrinsic, transactionSample);
+ Assertions.TransactionTraceHasAttributes(expectedAgentAttributes, Tests.TestSerializationHelpers.Models.TransactionTraceAttributeType.Agent, transactionSample);
+
+ Assertions.TransactionEventHasAttributes(firstTransactionExpectedTransactionEventIntrinsicAttributes, Tests.TestSerializationHelpers.Models.TransactionEventAttributeType.Intrinsic, firstTransaction);
+ Assertions.TransactionEventHasAttributes(expectedAgentAttributes, Tests.TestSerializationHelpers.Models.TransactionEventAttributeType.Agent, firstTransaction);
+
+ Assertions.TransactionEventDoesNotHaveAttributes(secondTransactionUnexpectedTransactionEventIntrinsicAttributes, Tests.TestSerializationHelpers.Models.TransactionEventAttributeType.Intrinsic, secondTransaction);
+
+
+ Assert.True(firstTransaction.IntrinsicAttributes.TryGetValue("cloud.resource_id", out var cloudResourceIdValue));
+ Assert.Equal("/subscriptions/subscription_id/resourceGroups/my_resource_group/providers/Microsoft.Web/sites/IntegrationTestAppName/functions/HttpTriggerFunctionUsingSimpleInvocation", cloudResourceIdValue);
+ Assert.True(firstTransaction.IntrinsicAttributes.TryGetValue("faas.name", out var faasNameValue));
+ Assert.Equal("HttpTriggerFunctionUsingSimpleInvocation", faasNameValue);
+ Assert.True(firstTransaction.IntrinsicAttributes.TryGetValue("faas.trigger", out var faasTriggerValue));
+ Assert.Equal("http", faasTriggerValue);
+ }
+ else
+ {
+ Assertions.MetricsDoNotExist(simpleExpectedMetrics, metrics);
+ Assert.Null(transactionSample);
+
+ Assert.Empty(simpleTransactionEvents); // there should be no transactions when azure function mode is disabled
+ }
+
+ 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);
+ }
+ }
+
+
+ [SkippableFact]
+ public void Test_PipelineMode()
+ {
+ Skip.IfNot(_testMode == AzureFunctionHttpTriggerTestMode.AspNetCorePipeline);
+
+ var firstTransactionExpectedTransactionEventIntrinsicAttributes = new List
+ {
+ "faas.coldStart",
"faas.invocation_id",
"faas.name",
"faas.trigger",
"cloud.resource_id"
};
- var expectedAgentAttributes = new Dictionary
+ var secondTransactionUnexpectedTransactionEventIntrinsicAttributes = new List
{
- { "request.uri", "/api/httpTriggerFunctionUsingAspNetCorePipeline"}
+ "faas.coldStart"
+ };
+
+ var simpleTransactionExpectedTransactionEventIntrinsicAttributes = new List
+ {
+ "faas.invocation_id",
+ "faas.name",
+ "faas.trigger",
+ "cloud.resource_id"
};
- var transactionTraceExpectedAttributes = new Dictionary()
+ var expectedAgentAttributes = new Dictionary
{
- { "faas.coldStart", true},
- //new("faas.invocation_id", "test_invocation_id"), This one is a random guid, not something we can specifically look for
- { "faas.name", "HttpTriggerFunctionUsingAspNetCorePipeline" },
- { "faas.trigger", "http" },
- { "cloud.resource_id", "/subscriptions/subscription_id/resourceGroups/my_resource_group/providers/Microsoft.Web/sites/IntegrationTestAppName/functions/HttpTriggerFunctionUsingAspNetCorePipeline" }
+ { "request.uri", "/api/httpTriggerFunctionUsingAspNetCorePipeline"},
+ { "request.method", "GET" },
+ { "http.statusCode", 200 }
};
var pipelineTransactionName = "WebTransaction/AzureFunction/HttpTriggerFunctionUsingAspNetCorePipeline";
@@ -157,20 +253,22 @@ public void Test()
}
}
+ // The net6 target builds the function app without the aspnetcore pipeline package included
[NetCoreTest]
public class AzureFunctionHttpTriggerTestsCoreOldest : AzureFunctionHttpTriggerTestsBase
{
public AzureFunctionHttpTriggerTestsCoreOldest(AzureFunctionApplicationFixtureHttpTriggerCoreOldest fixture, ITestOutputHelper output)
- : base(fixture, output)
+ : base(fixture, output, AzureFunctionHttpTriggerTestMode.SimpleInvocation)
{
}
}
+ // the net8 target builds the function app with the aspnetcore pipeline package
[NetCoreTest]
public class AzureFunctionHttpTriggerTestsCoreLatest : AzureFunctionHttpTriggerTestsBase
{
public AzureFunctionHttpTriggerTestsCoreLatest(AzureFunctionApplicationFixtureHttpTriggerCoreLatest fixture, ITestOutputHelper output)
- : base(fixture, output)
+ : base(fixture, output, AzureFunctionHttpTriggerTestMode.AspNetCorePipeline)
{
}
}
diff --git a/tests/Agent/IntegrationTests/IntegrationTests/AzureFunction/AzureFunctionInstrumentationDisabledTests.cs b/tests/Agent/IntegrationTests/IntegrationTests/AzureFunction/AzureFunctionInstrumentationDisabledTests.cs
index de915c0584..81925af22b 100644
--- a/tests/Agent/IntegrationTests/IntegrationTests/AzureFunction/AzureFunctionInstrumentationDisabledTests.cs
+++ b/tests/Agent/IntegrationTests/IntegrationTests/AzureFunction/AzureFunctionInstrumentationDisabledTests.cs
@@ -11,7 +11,7 @@ namespace NewRelic.Agent.IntegrationTests.AzureFunction
public class AzureFunctionInstrumentationDisabledTestsCoreLatest : AzureFunctionHttpTriggerTestsBase
{
public AzureFunctionInstrumentationDisabledTestsCoreLatest(AzureFunctionApplicationFixtureInstrumentationDisabledCoreLatest fixture, ITestOutputHelper output)
- : base(fixture, output)
+ : base(fixture, output, AzureFunctionHttpTriggerTestMode.AspNetCorePipeline) // test mode doesn't really matter here
{
}
}
diff --git a/tests/Agent/IntegrationTests/IntegrationTests/IntegrationTests.csproj b/tests/Agent/IntegrationTests/IntegrationTests/IntegrationTests.csproj
index 00850a046d..21a6d82f89 100644
--- a/tests/Agent/IntegrationTests/IntegrationTests/IntegrationTests.csproj
+++ b/tests/Agent/IntegrationTests/IntegrationTests/IntegrationTests.csproj
@@ -54,6 +54,7 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+