Skip to content

Commit 3f06bf6

Browse files
authored
feat: Add support for AWS Lambda APIGatewayHttpApiV2ProxyRequest (#2472)
1 parent 402ba16 commit 3f06bf6

File tree

11 files changed

+829
-23
lines changed

11 files changed

+829
-23
lines changed

src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Lambda/AwsLambdaEventType.cs

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ public enum AwsLambdaEventType
77
{
88
Unknown,
99
APIGatewayProxyRequest,
10+
APIGatewayHttpApiV2ProxyRequest,
1011
ApplicationLoadBalancerRequest,
1112
CloudWatchScheduledEvent,
1213
KinesisStreamingEvent,

src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Lambda/AwsLambdaEventTypeExtensions.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public static AwsLambdaEventType ToEventType(this string typeFullName)
1212
return typeFullName switch
1313
{
1414
"Amazon.Lambda.APIGatewayEvents.APIGatewayProxyRequest" => AwsLambdaEventType.APIGatewayProxyRequest,
15+
"Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest" => AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest,
1516
"Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest" => AwsLambdaEventType.ApplicationLoadBalancerRequest,
1617
"Amazon.Lambda.CloudWatchEvents.ScheduledEvents.ScheduledEvent" => AwsLambdaEventType.CloudWatchScheduledEvent,
1718
"Amazon.Lambda.KinesisEvents.KinesisEvent" => AwsLambdaEventType.KinesisStreamingEvent,
@@ -32,6 +33,7 @@ public static string ToEventTypeString(this AwsLambdaEventType eventType)
3233
return eventType switch
3334
{
3435
AwsLambdaEventType.APIGatewayProxyRequest => "apiGateway",
36+
AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest => "apiGateway",
3537
AwsLambdaEventType.ApplicationLoadBalancerRequest => "alb",
3638
AwsLambdaEventType.CloudWatchScheduledEvent => "cloudWatch_scheduled",
3739
AwsLambdaEventType.KinesisStreamingEvent => "kinesis",
@@ -48,6 +50,6 @@ public static string ToEventTypeString(this AwsLambdaEventType eventType)
4850

4951
public static bool IsWebEvent(this AwsLambdaEventType eventType)
5052
{
51-
return eventType == AwsLambdaEventType.APIGatewayProxyRequest || eventType == AwsLambdaEventType.ApplicationLoadBalancerRequest;
53+
return eventType == AwsLambdaEventType.APIGatewayProxyRequest || eventType == AwsLambdaEventType.ApplicationLoadBalancerRequest || eventType == AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest;
5254
}
5355
}

src/Agent/NewRelic/Agent/Extensions/NewRelic.Agent.Extensions/Lambda/LambdaEventHelpers.cs

+44-15
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public static void AddEventTypeAttributes(IAgent agent, ITransaction transaction
2323
{
2424
case AwsLambdaEventType.APIGatewayProxyRequest:
2525
dynamic apiReqEvent = inputObject; // Amazon.Lambda.APIGatewayEvents.APIGatewayProxyRequest
26-
SetWebRequestProperties(agent, transaction, apiReqEvent);
26+
SetWebRequestProperties(agent, transaction, apiReqEvent, eventType);
2727

2828
if (apiReqEvent.RequestContext != null)
2929
{
@@ -34,14 +34,29 @@ public static void AddEventTypeAttributes(IAgent agent, ITransaction transaction
3434
transaction.AddEventSourceAttribute("resourceId", (string)requestContext.ResourceId);
3535
transaction.AddEventSourceAttribute("resourcePath", (string)requestContext.ResourcePath);
3636
transaction.AddEventSourceAttribute("stage", (string)requestContext.Stage);
37+
}
38+
break;
39+
40+
case AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest:
41+
dynamic apiReqv2Event = inputObject; // Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest
42+
SetWebRequestProperties(agent, transaction, apiReqv2Event, eventType);
3743

44+
if (apiReqv2Event.RequestContext != null)
45+
{
46+
dynamic requestContext = apiReqv2Event.RequestContext;
47+
// arn is not available
48+
transaction.AddEventSourceAttribute("accountId", (string)requestContext.AccountId);
49+
transaction.AddEventSourceAttribute("apiId", (string)requestContext.ApiId);
50+
// resourceId is not available for v2
51+
// resourcePath is not available for v2
52+
transaction.AddEventSourceAttribute("stage", (string)requestContext.Stage);
3853
}
3954
break;
4055

4156
case AwsLambdaEventType.ApplicationLoadBalancerRequest:
4257
dynamic albReqEvent = inputObject; //Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest
4358

44-
SetWebRequestProperties(agent, transaction, albReqEvent);
59+
SetWebRequestProperties(agent, transaction, albReqEvent, eventType);
4560

4661
transaction.AddEventSourceAttribute("arn", (string)albReqEvent.RequestContext.Elb.TargetGroupArn);
4762
break;
@@ -231,26 +246,30 @@ private static void TryParseSNSDistributedTraceHeaders(dynamic snsEvent, ITransa
231246
transaction.AcceptDistributedTraceHeaders(snsHeaders, GetHeaderValue, TransportType.Other);
232247
}
233248

234-
private static void SetWebRequestProperties(IAgent agent, ITransaction transaction, dynamic webReqEvent)
249+
private static void SetWebRequestProperties(IAgent agent, ITransaction transaction, dynamic webReqEvent, AwsLambdaEventType eventType)
235250
{
236251
//HTTP headers
237252
IDictionary<string, string> headers = webReqEvent.Headers;
238253
Func<IDictionary<string, string>, string, string> headersGetter = (h, k) => h[k];
239254

240-
IDictionary<string, IList<string>> multiValueHeaders = webReqEvent.MultiValueHeaders;
241-
Func<IDictionary<string, IList<string>>, string, string> multiValueHeadersGetter = (h, k) => string.Join(",", h[k]);
242-
243-
if (multiValueHeaders != null)
255+
if (eventType != AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest) // v2 doesn't have MultiValueHeaders
244256
{
245-
transaction.SetRequestHeaders(multiValueHeaders, agent.Configuration.AllowAllRequestHeaders ? multiValueHeaders.Keys : Statics.DefaultCaptureHeaders, multiValueHeadersGetter);
257+
IDictionary<string, IList<string>> multiValueHeaders = webReqEvent.MultiValueHeaders;
258+
Func<IDictionary<string, IList<string>>, string, string> multiValueHeadersGetter = (h, k) => string.Join(",", h[k]);
246259

247-
// DT transport comes from the X-Forwarded-Proto header, if present
248-
var forwardedProto = GetMultiHeaderValue(multiValueHeaders, xForwardedProtoHeader).FirstOrDefault();
249-
var dtTransport = GetDistributedTransportType(forwardedProto);
260+
if (multiValueHeaders != null)
261+
{
262+
transaction.SetRequestHeaders(multiValueHeaders, agent.Configuration.AllowAllRequestHeaders ? multiValueHeaders.Keys : Statics.DefaultCaptureHeaders, multiValueHeadersGetter);
263+
264+
// DT transport comes from the X-Forwarded-Proto header, if present
265+
var forwardedProto = GetMultiHeaderValue(multiValueHeaders, xForwardedProtoHeader).FirstOrDefault();
266+
var dtTransport = GetDistributedTransportType(forwardedProto);
250267

251-
transaction.AcceptDistributedTraceHeaders(multiValueHeaders, GetMultiHeaderValue, dtTransport);
268+
transaction.AcceptDistributedTraceHeaders(multiValueHeaders, GetMultiHeaderValue, dtTransport);
269+
}
252270
}
253-
else if (headers != null)
271+
272+
if (headers != null)
254273
{
255274
transaction.SetRequestHeaders(headers, agent.Configuration.AllowAllRequestHeaders ? webReqEvent.Headers?.Keys : Statics.DefaultCaptureHeaders, headersGetter);
256275

@@ -261,8 +280,18 @@ private static void SetWebRequestProperties(IAgent agent, ITransaction transacti
261280
transaction.AcceptDistributedTraceHeaders(headers, GetHeaderValue, dtTransport);
262281
}
263282

264-
transaction.SetRequestMethod(webReqEvent.HttpMethod);
265-
transaction.SetUri(webReqEvent.Path);
283+
if (eventType == AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest) // v2 buries method and path
284+
{
285+
var reqContext = webReqEvent.RequestContext;
286+
transaction.SetRequestMethod(reqContext.Http.Method);
287+
transaction.SetUri(reqContext.Http.Path);
288+
}
289+
else
290+
{
291+
transaction.SetRequestMethod(webReqEvent.HttpMethod);
292+
transaction.SetUri(webReqEvent.Path);
293+
}
294+
266295
transaction.SetRequestParameters(webReqEvent.QueryStringParameters);
267296
}
268297

src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AwsLambda/HandlerMethodWrapper.cs

+6-2
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ public object GetInputObject(InstrumentedMethodCall instrumentedMethodCall)
110110
}
111111
return null;
112112
}
113+
114+
public bool IsWebRequest => EventType is AwsLambdaEventType.APIGatewayProxyRequest or AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest or AwsLambdaEventType.ApplicationLoadBalancerRequest;
113115
}
114116

115117
private List<string> _webResponseHeaders = ["content-type", "content-length"];
@@ -249,7 +251,7 @@ void InvokeTryProcessResponse(Task responseTask)
249251
}
250252

251253
// capture response data for specific request / response types
252-
if (_functionDetails.EventType is AwsLambdaEventType.APIGatewayProxyRequest or AwsLambdaEventType.ApplicationLoadBalancerRequest)
254+
if (_functionDetails.IsWebRequest)
253255
{
254256
var responseGetter = _getRequestResponseFromGeneric ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<object>(responseTask.GetType(), "Result");
255257
var response = responseGetter(responseTask);
@@ -268,7 +270,7 @@ void InvokeTryProcessResponse(Task responseTask)
268270
return Delegates.GetDelegateFor<object>(
269271
onSuccess: response =>
270272
{
271-
if (_functionDetails.EventType is AwsLambdaEventType.APIGatewayProxyRequest or AwsLambdaEventType.ApplicationLoadBalancerRequest)
273+
if (_functionDetails.IsWebRequest)
272274
CaptureResponseData(transaction, response, agent);
273275

274276
segment.End();
@@ -290,6 +292,8 @@ private void CaptureResponseData(ITransaction transaction, object response, IAge
290292
// check response type based on request type to be sure it has the properties we're looking for
291293
var responseType = response.GetType().FullName;
292294
if ((_functionDetails.EventType == AwsLambdaEventType.APIGatewayProxyRequest && responseType != "Amazon.Lambda.APIGatewayEvents.APIGatewayProxyResponse")
295+
||
296+
(_functionDetails.EventType == AwsLambdaEventType.APIGatewayHttpApiV2ProxyRequest && responseType != "Amazon.Lambda.APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse")
293297
||
294298
(_functionDetails.EventType == AwsLambdaEventType.ApplicationLoadBalancerRequest && responseType != "Amazon.Lambda.ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse"))
295299
{

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

+22-3
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ private static void Main(string[] args)
8585
return HandlerWrapper.GetHandlerWrapper<APIGatewayProxyRequest, Stream>(ApiGatewayProxyRequestHandlerReturnsStream, serializer);
8686
case nameof (ApiGatewayProxyRequestHandlerReturnsStreamAsync):
8787
return HandlerWrapper.GetHandlerWrapper<APIGatewayProxyRequest, Stream>(ApiGatewayProxyRequestHandlerReturnsStreamAsync, serializer);
88+
case nameof(ApiGatewayHttpApiV2ProxyRequestHandler):
89+
return HandlerWrapper.GetHandlerWrapper<APIGatewayHttpApiV2ProxyRequest, APIGatewayHttpApiV2ProxyResponse>(ApiGatewayHttpApiV2ProxyRequestHandler, serializer);
90+
case nameof(ApiGatewayHttpApiV2ProxyRequestHandlerAsync):
91+
return HandlerWrapper.GetHandlerWrapper<APIGatewayHttpApiV2ProxyRequest, APIGatewayHttpApiV2ProxyResponse>(ApiGatewayHttpApiV2ProxyRequestHandlerAsync, serializer);
8892
case nameof(ApplicationLoadBalancerRequestHandler):
8993
return HandlerWrapper.GetHandlerWrapper<ApplicationLoadBalancerRequest, ApplicationLoadBalancerResponse>(ApplicationLoadBalancerRequestHandler, serializer);
9094
case nameof(ApplicationLoadBalancerRequestHandlerAsync):
@@ -263,6 +267,14 @@ public static APIGatewayProxyResponse ApiGatewayProxyRequestHandler(APIGatewayPr
263267
return new APIGatewayProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
264268
}
265269

270+
public static async Task<APIGatewayProxyResponse> ApiGatewayProxyRequestHandlerAsync(APIGatewayProxyRequest apiGatewayProxyRequest, ILambdaContext __)
271+
{
272+
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayProxyRequestHandlerAsync));
273+
await Task.Delay(100);
274+
275+
return new APIGatewayProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
276+
}
277+
266278
public static Stream ApiGatewayProxyRequestHandlerReturnsStream(APIGatewayProxyRequest apiGatewayProxyRequest, ILambdaContext __)
267279
{
268280
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayProxyRequestHandlerReturnsStream));
@@ -290,12 +302,19 @@ public static async Task<Stream> ApiGatewayProxyRequestHandlerReturnsStreamAsync
290302
return stream;
291303
}
292304

293-
public static async Task<APIGatewayProxyResponse> ApiGatewayProxyRequestHandlerAsync(APIGatewayProxyRequest apiGatewayProxyRequest, ILambdaContext __)
305+
public static APIGatewayHttpApiV2ProxyResponse ApiGatewayHttpApiV2ProxyRequestHandler(APIGatewayHttpApiV2ProxyRequest apiGatewayProxyRequest, ILambdaContext __)
294306
{
295-
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayProxyRequestHandlerAsync));
307+
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayHttpApiV2ProxyRequestHandler));
308+
309+
return new APIGatewayHttpApiV2ProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
310+
}
311+
312+
public static async Task<APIGatewayHttpApiV2ProxyResponse> ApiGatewayHttpApiV2ProxyRequestHandlerAsync(APIGatewayHttpApiV2ProxyRequest apiGatewayProxyRequest, ILambdaContext __)
313+
{
314+
Console.WriteLine("Executing lambda {0}", nameof(ApiGatewayHttpApiV2ProxyRequestHandlerAsync));
296315
await Task.Delay(100);
297316

298-
return new APIGatewayProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
317+
return new APIGatewayHttpApiV2ProxyResponse() { Body = apiGatewayProxyRequest.Body, StatusCode = 200, Headers = new Dictionary<string, string> { { "Content-Type", "application/json" }, { "Content-Length", "12345" } } };
299318
}
300319

301320
public static ApplicationLoadBalancerResponse ApplicationLoadBalancerRequestHandler(ApplicationLoadBalancerRequest applicationLoadBalancerRequest, ILambdaContext __)

0 commit comments

Comments
 (0)