Skip to content

Commit 3b22aad

Browse files
videnkzSylvainJuge
andauthored
added support to Elastic Load Balancer triggers (#3411)
--------- Co-authored-by: Sylvain Juge <[email protected]>
1 parent 3c4a753 commit 3b22aad

13 files changed

+617
-132
lines changed

CHANGELOG.asciidoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Use subheadings with the "=====" level for adding notes for unreleased changes:
3838
[float]
3939
===== Features
4040
* Differentiate Lambda URLs from API Gateway in AWS Lambda integration - {pull}3417[#3417]
41+
* Added lambda support for ELB triggers {pull}#3411[#3411]
4142
4243
[[release-notes-1.x]]
4344
=== Java Agent version 1.x
@@ -127,7 +128,7 @@ affect you, if you are using the OpenTelemetry API only and not the SDK. - {pull
127128
===== Features
128129
* Added protection against invalid timestamps provided by manual instrumentation - {pull}3363[#3363]
129130
* Added support for AWS SDK 2.21 - {pull}3373[#3373]
130-
* Capture bucket and object key to Lambda transaction as OTel attributes - `aws.s3.bueckt`, `aws.s3.key` - {pull}3364[#3364]
131+
* Capture bucket and object key to Lambda transaction as OTel attributes - `aws.s3.bucket`, `aws.s3.key` - {pull}3364[#3364]
131132
* Added `context_propagation_only` configuration option - {pull}3358[#3358]
132133
* Added attribute[*] for JMX pattern metrics (all metrics can now be generated with `object_name[*:type=*,name=*] attribute[*]`) - {pull}3376[#3376]
133134

apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV1TransactionHelper.java

Lines changed: 6 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,15 @@
1919
package co.elastic.apm.agent.awslambda.helper;
2020

2121
import co.elastic.apm.agent.awslambda.MapTextHeaderGetter;
22-
import co.elastic.apm.agent.tracer.GlobalTracer;
2322
import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils;
23+
import co.elastic.apm.agent.tracer.GlobalTracer;
2424
import co.elastic.apm.agent.tracer.Tracer;
2525
import co.elastic.apm.agent.tracer.Transaction;
2626
import com.amazonaws.services.lambda.runtime.Context;
2727
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
2828
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
2929

3030
import javax.annotation.Nullable;
31-
import java.util.Map;
3231

3332
public class APIGatewayProxyV1TransactionHelper extends AbstractAPIGatewayTransactionHelper<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {
3433

@@ -49,48 +48,17 @@ public static APIGatewayProxyV1TransactionHelper getInstance() {
4948
@Override
5049
protected Transaction<?> doStartTransaction(APIGatewayProxyRequestEvent apiGatewayEvent, Context lambdaContext) {
5150
Transaction<?> transaction = tracer.startChildTransaction(apiGatewayEvent.getHeaders(), MapTextHeaderGetter.INSTANCE, PrivilegedActionUtils.getClassLoader(apiGatewayEvent.getClass()));
52-
String host = getHost(apiGatewayEvent);
5351

5452
if (null != transaction) {
53+
String host = getHost(apiGatewayEvent.getHeaders());
54+
5555
fillHttpRequestData(transaction, getHttpMethod(apiGatewayEvent), apiGatewayEvent.getHeaders(), host,
56-
apiGatewayEvent.getRequestContext().getPath(), getQueryString(apiGatewayEvent), apiGatewayEvent.getBody());
56+
apiGatewayEvent.getRequestContext().getPath(), getQueryString(apiGatewayEvent.getQueryStringParameters()), apiGatewayEvent.getBody());
5757
}
5858

5959
return transaction;
6060
}
6161

62-
@Nullable
63-
private String getHost(APIGatewayProxyRequestEvent apiGatewayEvent) {
64-
String host = null;
65-
if (null != apiGatewayEvent.getHeaders()) {
66-
host = apiGatewayEvent.getHeaders().get("host");
67-
if (null == host) {
68-
host = apiGatewayEvent.getHeaders().get("Host");
69-
}
70-
}
71-
return host;
72-
}
73-
74-
@Nullable
75-
private String getQueryString(APIGatewayProxyRequestEvent apiGatewayEvent) {
76-
Map<String, String> queryParameters = apiGatewayEvent.getQueryStringParameters();
77-
if (null != queryParameters && !queryParameters.isEmpty()) {
78-
StringBuilder queryString = new StringBuilder();
79-
int i = 0;
80-
for (Map.Entry<String, String> entry : apiGatewayEvent.getQueryStringParameters().entrySet()) {
81-
if (i > 0) {
82-
queryString.append('&');
83-
}
84-
queryString.append(entry.getKey());
85-
queryString.append('=');
86-
queryString.append(entry.getValue());
87-
i++;
88-
}
89-
return queryString.toString();
90-
}
91-
return null;
92-
}
93-
9462
@Override
9563
public void captureOutputForTransaction(Transaction<?> transaction, APIGatewayProxyResponseEvent responseEvent) {
9664
Integer statusCode = responseEvent.getStatusCode();
@@ -107,7 +75,7 @@ protected void setTransactionTriggerData(Transaction<?> transaction, APIGatewayP
10775

10876
if (null != rContext) {
10977
setApiGatewayContextData(transaction, rContext.getRequestId(), rContext.getApiId(),
110-
getHost(apiGatewayRequest), rContext.getAccountId());
78+
getHost(apiGatewayRequest.getHeaders()), rContext.getAccountId());
11179
}
11280
}
11381

@@ -149,4 +117,5 @@ protected String getStage(APIGatewayProxyRequestEvent event) {
149117
protected String getResourcePath(APIGatewayProxyRequestEvent event) {
150118
return event.getRequestContext().getResourcePath();
151119
}
120+
152121
}

apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/APIGatewayProxyV2TransactionHelper.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ public static APIGatewayProxyV2TransactionHelper getInstance() {
4545
}
4646

4747
@Override
48-
protected Transaction doStartTransaction(APIGatewayV2HTTPEvent apiGatewayEvent, Context lambdaContext) {
49-
Transaction transaction = tracer.startChildTransaction(apiGatewayEvent.getHeaders(), MapTextHeaderGetter.INSTANCE, PrivilegedActionUtils.getClassLoader(apiGatewayEvent.getClass()));
48+
protected Transaction<?> doStartTransaction(APIGatewayV2HTTPEvent apiGatewayEvent, Context lambdaContext) {
49+
Transaction<?> transaction = tracer.startChildTransaction(apiGatewayEvent.getHeaders(), MapTextHeaderGetter.INSTANCE, PrivilegedActionUtils.getClassLoader(apiGatewayEvent.getClass()));
5050

5151
APIGatewayV2HTTPEvent.RequestContext requestContext = apiGatewayEvent.getRequestContext();
5252
if (transaction != null) {
@@ -60,12 +60,12 @@ protected Transaction doStartTransaction(APIGatewayV2HTTPEvent apiGatewayEvent,
6060
}
6161

6262
@Override
63-
public void captureOutputForTransaction(Transaction transaction, APIGatewayV2HTTPResponse responseEvent) {
63+
public void captureOutputForTransaction(Transaction<?> transaction, APIGatewayV2HTTPResponse responseEvent) {
6464
fillHttpResponseData(transaction, responseEvent.getHeaders(), responseEvent.getStatusCode());
6565
}
6666

6767
@Override
68-
protected void setTransactionTriggerData(Transaction transaction, APIGatewayV2HTTPEvent apiGatewayRequest) {
68+
protected void setTransactionTriggerData(Transaction<?> transaction, APIGatewayV2HTTPEvent apiGatewayRequest) {
6969
super.setTransactionTriggerData(transaction, apiGatewayRequest);
7070
APIGatewayV2HTTPEvent.RequestContext rContext = apiGatewayRequest.getRequestContext();
7171
setApiGatewayContextData(transaction, rContext.getRequestId(), rContext.getApiId(),

apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AWSEventsHelper.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;
2525
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
2626
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPResponse;
27+
import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent;
28+
import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent;
2729
import com.amazonaws.services.lambda.runtime.events.S3Event;
2830
import com.amazonaws.services.lambda.runtime.events.SNSEvent;
2931
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
@@ -50,6 +52,9 @@ public static Transaction<?> startTransaction(Object input, Context lambdaContex
5052
} else if (input instanceof S3Event) {
5153
// S3 event trigger
5254
return S3TransactionHelper.getInstance().startTransaction((S3Event) input, lambdaContext);
55+
} else if (input instanceof ApplicationLoadBalancerRequestEvent) {
56+
// Load Balancer Request event trigger
57+
return ApplicationLoadBalancerRequestTransactionHelper.getInstance().startTransaction((ApplicationLoadBalancerRequestEvent) input, lambdaContext);
5358
}
5459
return PlainTransactionHelper.getInstance().startTransaction(input, lambdaContext);
5560
}
@@ -59,6 +64,8 @@ public static void finalizeTransaction(Transaction<?> transaction, Object output
5964
APIGatewayProxyV2TransactionHelper.getInstance().finalizeTransaction(transaction, (APIGatewayV2HTTPResponse) output, thrown);
6065
} else if (output instanceof APIGatewayProxyResponseEvent) {
6166
APIGatewayProxyV1TransactionHelper.getInstance().finalizeTransaction(transaction, (APIGatewayProxyResponseEvent) output, thrown);
67+
} else if (output instanceof ApplicationLoadBalancerResponseEvent) {
68+
ApplicationLoadBalancerRequestTransactionHelper.getInstance().finalizeTransaction(transaction, (ApplicationLoadBalancerResponseEvent) output, thrown);
6269
} else {
6370
// use PlainTransactionHelper for all triggers that do not expect an output
6471
PlainTransactionHelper.getInstance().finalizeTransaction(transaction, output, thrown);

apm-agent-plugins/apm-awslambda-plugin/src/main/java/co/elastic/apm/agent/awslambda/helper/AbstractAPIGatewayTransactionHelper.java

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,18 @@
1818
*/
1919
package co.elastic.apm.agent.awslambda.helper;
2020

21+
import co.elastic.apm.agent.common.util.WildcardMatcher;
22+
import co.elastic.apm.agent.sdk.logging.Logger;
23+
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
24+
import co.elastic.apm.agent.tracer.AbstractSpan;
2125
import co.elastic.apm.agent.tracer.ServiceOrigin;
2226
import co.elastic.apm.agent.tracer.Tracer;
2327
import co.elastic.apm.agent.tracer.Transaction;
2428
import co.elastic.apm.agent.tracer.metadata.CloudOrigin;
2529
import co.elastic.apm.agent.tracer.metadata.Request;
2630
import co.elastic.apm.agent.tracer.metadata.Response;
2731
import co.elastic.apm.agent.tracer.util.ResultUtil;
28-
import co.elastic.apm.agent.common.util.WildcardMatcher;
29-
import co.elastic.apm.agent.sdk.logging.Logger;
30-
import co.elastic.apm.agent.sdk.logging.LoggerFactory;
31-
import co.elastic.apm.agent.tracer.AbstractSpan;
3232
import com.amazonaws.services.lambda.runtime.Context;
33-
import com.amazonaws.services.lambda.runtime.events.APIGatewayV2HTTPEvent;
3433

3534
import javax.annotation.Nullable;
3635
import java.nio.CharBuffer;
@@ -68,6 +67,38 @@ protected void fillHttpRequestData(Transaction<?> transaction, @Nullable String
6867
}
6968
}
7069

70+
@Nullable
71+
protected String getHost(@Nullable Map<String, String> headers) {
72+
if (null == headers) {
73+
return null;
74+
}
75+
String host = headers.get("host");
76+
if (null == host) {
77+
host = headers.get("Host");
78+
}
79+
return host;
80+
}
81+
82+
@Nullable
83+
protected String getQueryString(@Nullable Map<String, String> queryParameters) {
84+
if (null == queryParameters || queryParameters.isEmpty()) {
85+
return null;
86+
}
87+
StringBuilder queryString = new StringBuilder();
88+
int i = 0;
89+
for (Map.Entry<String, String> entry : queryParameters.entrySet()) {
90+
if (i > 0) {
91+
queryString.append('&');
92+
}
93+
queryString.append(entry.getKey());
94+
queryString.append('=');
95+
queryString.append(entry.getValue());
96+
i++;
97+
}
98+
return queryString.toString();
99+
}
100+
101+
71102
protected void fillHttpResponseData(Transaction<?> transaction, @Nullable Map<String, String> headers, int statusCode) {
72103
Response response = transaction.getContext().getResponse();
73104
response.withFinished(true);
@@ -82,7 +113,7 @@ protected void fillHttpResponseData(Transaction<?> transaction, @Nullable Map<St
82113
}
83114

84115
private void fillUrlRelatedFields(Request request, @Nullable String serverName, @Nullable String path, @Nullable String queryString) {
85-
String qString = queryString == null || queryString.trim().isEmpty() ? null: queryString;
116+
String qString = queryString == null || queryString.trim().isEmpty() ? null : queryString;
86117
request.getUrl().fillFrom("https", serverName, 443, path, qString);
87118
}
88119

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package co.elastic.apm.agent.awslambda.helper;
20+
21+
import co.elastic.apm.agent.awslambda.MapTextHeaderGetter;
22+
import co.elastic.apm.agent.sdk.internal.util.PrivilegedActionUtils;
23+
import co.elastic.apm.agent.tracer.*;
24+
import co.elastic.apm.agent.tracer.metadata.CloudOrigin;
25+
import com.amazonaws.services.lambda.runtime.Context;
26+
import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerRequestEvent;
27+
import com.amazonaws.services.lambda.runtime.events.ApplicationLoadBalancerResponseEvent;
28+
29+
import javax.annotation.Nonnull;
30+
import javax.annotation.Nullable;
31+
import java.util.Map;
32+
33+
public class ApplicationLoadBalancerRequestTransactionHelper extends AbstractAPIGatewayTransactionHelper<ApplicationLoadBalancerRequestEvent, ApplicationLoadBalancerResponseEvent> {
34+
@Nullable
35+
private static ApplicationLoadBalancerRequestTransactionHelper INSTANCE;
36+
37+
private ApplicationLoadBalancerRequestTransactionHelper(Tracer tracer) {
38+
super(tracer);
39+
}
40+
41+
public static ApplicationLoadBalancerRequestTransactionHelper getInstance() {
42+
if (INSTANCE == null) {
43+
INSTANCE = new ApplicationLoadBalancerRequestTransactionHelper(GlobalTracer.get());
44+
}
45+
return INSTANCE;
46+
}
47+
48+
@Override
49+
protected Transaction doStartTransaction(ApplicationLoadBalancerRequestEvent loadBalancerRequestEvent, Context lambdaContext) {
50+
Transaction transaction = tracer.startChildTransaction(loadBalancerRequestEvent.getHeaders(), MapTextHeaderGetter.INSTANCE, PrivilegedActionUtils.getClassLoader(loadBalancerRequestEvent.getClass()));
51+
52+
if (transaction != null) {
53+
String host = getHost(loadBalancerRequestEvent.getHeaders());
54+
super.fillHttpRequestData(transaction, loadBalancerRequestEvent.getHttpMethod(), loadBalancerRequestEvent.getHeaders(), host,
55+
loadBalancerRequestEvent.getPath(), getQueryString(loadBalancerRequestEvent.getQueryStringParameters()), loadBalancerRequestEvent.getBody());
56+
}
57+
58+
return transaction;
59+
}
60+
61+
@Override
62+
public void captureOutputForTransaction(Transaction transaction, ApplicationLoadBalancerResponseEvent responseEvent) {
63+
fillHttpResponseData(transaction, responseEvent.getHeaders(), responseEvent.getStatusCode());
64+
}
65+
66+
@Override
67+
protected void setTransactionTriggerData(Transaction transaction, ApplicationLoadBalancerRequestEvent loadBalancerRequestEvent) {
68+
transaction.withType(TRANSACTION_TYPE);
69+
CloudOrigin cloudOrigin = transaction.getContext().getCloudOrigin();
70+
cloudOrigin.withServiceName("elb");
71+
cloudOrigin.withProvider("aws");
72+
FaasTrigger faasTrigger = transaction.getFaas().getTrigger();
73+
faasTrigger.withType("http");
74+
faasTrigger.withRequestId(getHeader(loadBalancerRequestEvent, "x-amzn-trace-id"));
75+
LoadBalancerElbTargetGroupArnMetadata metadata = parseMetadata(loadBalancerRequestEvent);
76+
if (null != metadata) {
77+
ServiceOrigin serviceOrigin = transaction.getContext().getServiceOrigin();
78+
serviceOrigin.withName(metadata.getTargetGroupName());
79+
serviceOrigin.withId(metadata.getTargetGroupArn());
80+
cloudOrigin.withAccountId(metadata.getAccountId());
81+
cloudOrigin.withRegion(metadata.getCloudRegion());
82+
}
83+
}
84+
85+
@Nullable
86+
private String getHeader(@Nonnull ApplicationLoadBalancerRequestEvent loadBalancerRequestEvent,
87+
@Nonnull String headerName) {
88+
Map<String, String> headers = loadBalancerRequestEvent.getHeaders();
89+
if (null == headers) {
90+
return null;
91+
}
92+
return headers.get(headerName);
93+
}
94+
95+
@Nullable
96+
private LoadBalancerElbTargetGroupArnMetadata parseMetadata(ApplicationLoadBalancerRequestEvent event) {
97+
if (null == event.getRequestContext()) {
98+
return null;
99+
}
100+
ApplicationLoadBalancerRequestEvent.Elb elb = event.getRequestContext().getElb();
101+
if (null == elb) {
102+
return null;
103+
}
104+
String targetGroupArn = elb.getTargetGroupArn();
105+
if (null == targetGroupArn) {
106+
return null;
107+
}
108+
LoadBalancerElbTargetGroupArnMetadata metadata = new LoadBalancerElbTargetGroupArnMetadata(targetGroupArn);
109+
String[] arnParts = targetGroupArn.split(":");
110+
int arnPartsLength = arnParts.length;
111+
if (arnPartsLength < 4) {
112+
return metadata;
113+
}
114+
metadata.withCloudRegion(arnParts[3]);
115+
if (arnPartsLength < 5) {
116+
return metadata;
117+
}
118+
metadata.withAccountId(arnParts[4]);
119+
if (arnPartsLength < 6) {
120+
return metadata;
121+
}
122+
String targetGroup = arnParts[5];
123+
String[] targetGroupParts = targetGroup.split("/");
124+
if (targetGroupParts.length < 2) {
125+
return metadata;
126+
}
127+
return metadata.withTargetGroupName(targetGroupParts[2]);
128+
}
129+
130+
@Override
131+
protected String getApiGatewayVersion() {
132+
throw new UnsupportedOperationException("Not supported by ELB");
133+
}
134+
135+
@Nullable
136+
@Override
137+
protected String getHttpMethod(ApplicationLoadBalancerRequestEvent event) {
138+
return event.getHttpMethod();
139+
}
140+
141+
@Nullable
142+
@Override
143+
protected String getRequestContextPath(ApplicationLoadBalancerRequestEvent event) {
144+
return event.getPath();
145+
}
146+
147+
@Nullable
148+
@Override
149+
protected String getStage(ApplicationLoadBalancerRequestEvent event) {
150+
throw new UnsupportedOperationException("Not supported by ELB");
151+
}
152+
153+
@Nullable
154+
@Override
155+
protected String getResourcePath(ApplicationLoadBalancerRequestEvent event) {
156+
return null;
157+
}
158+
159+
@Nullable
160+
@Override
161+
String getDomainName(ApplicationLoadBalancerRequestEvent apiGatewayRequest) {
162+
return null;
163+
}
164+
}

0 commit comments

Comments
 (0)