Skip to content

Commit 125cfb9

Browse files
authored
update local operation of lambda span based on span attribute (#1106)
*Issue #, if available:* *Description of changes:* Currently the local operation of lambda span is hardcoded to function_name//FunctionHandler. In some use cases, some server is running inside lambda function and we should allow the setting of the local operation dynamically at run time. For example, users can use span api to set the attribute `aws.lambda.local.operation.override` and then this value can be used for the local operation. We have an Amazon-internal framework instrumentation that depends on this. By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 685e454 commit 125cfb9

File tree

3 files changed

+84
-1
lines changed

3 files changed

+84
-1
lines changed

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsAttributeKeys.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,19 @@ private AwsAttributeKeys() {}
2929
static final AttributeKey<String> AWS_LOCAL_OPERATION =
3030
AttributeKey.stringKey("aws.local.operation");
3131

32+
/*
33+
* By default the local operation of a Lambda span is hard-coded to "<FunctionName>/FunctionHandler".
34+
* To dynamically override this at runtime—such as when running a custom server inside your Lambda—
35+
* you can set the span attribute "aws.lambda.local.operation.override" before ending the span. For example:
36+
*
37+
* // Obtain the current Span and override its operation name
38+
* Span.current().setAttribute(
39+
* "aws.lambda.local.operation.override",
40+
* "MyService/handleRequest");
41+
*/
42+
static final AttributeKey<String> AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE =
43+
AttributeKey.stringKey("aws.lambda.local.operation.override");
44+
3245
static final AttributeKey<String> AWS_REMOTE_SERVICE =
3346
AttributeKey.stringKey("aws.remote.service");
3447

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import static io.opentelemetry.semconv.SemanticAttributes.URL_PATH;
2828
import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.AWS_LAMBDA_FUNCTION_NAME_CONFIG;
2929
import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.isLambdaEnvironment;
30+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE;
3031
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
3132

3233
import com.fasterxml.jackson.core.type.TypeReference;
@@ -91,7 +92,23 @@ static List<String> getDialectKeywords() {
9192
*/
9293
static String getIngressOperation(SpanData span) {
9394
if (isLambdaEnvironment()) {
94-
return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG) + "/FunctionHandler";
95+
/*
96+
* By default the local operation of a Lambda span is hard-coded to "<FunctionName>/FunctionHandler".
97+
* To dynamically override this at runtime—such as when running a custom server inside your Lambda—
98+
* you can set the span attribute "aws.lambda.local.operation.override" before ending the span. For example:
99+
*
100+
* // Obtain the current Span and override its operation name
101+
* Span.current().setAttribute(
102+
* "aws.lambda.local.operation.override",
103+
* "MyServiceOperation");
104+
*
105+
* The code below will detect that override and use it instead of the default.
106+
*/
107+
String operationOverride = span.getAttributes().get(AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE);
108+
if (operationOverride != null) {
109+
return operationOverride;
110+
}
111+
return getFunctionNameFromEnv() + "/FunctionHandler";
95112
}
96113
String operation = span.getName();
97114
if (shouldUseInternalOperation(span)) {
@@ -102,6 +119,11 @@ static String getIngressOperation(SpanData span) {
102119
return operation;
103120
}
104121

122+
// define a function so that we can mock it in unit test
123+
static String getFunctionNameFromEnv() {
124+
return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG);
125+
}
126+
105127
static String getEgressOperation(SpanData span) {
106128
if (shouldUseInternalOperation(span)) {
107129
return INTERNAL_OPERATION;

awsagentprovider/src/test/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtilTest.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@
1919
import static io.opentelemetry.semconv.SemanticAttributes.MessagingOperationValues.PROCESS;
2020
import static io.opentelemetry.semconv.SemanticAttributes.MessagingOperationValues.RECEIVE;
2121
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.mockito.Answers.CALLS_REAL_METHODS;
2223
import static org.mockito.Mockito.mock;
24+
import static org.mockito.Mockito.mockStatic;
2325
import static org.mockito.Mockito.when;
26+
import static org.mockito.Mockito.withSettings;
27+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE;
2428
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
2529
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.MAX_KEYWORD_LENGTH;
2630
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.getDialectKeywords;
@@ -33,6 +37,7 @@
3337
import java.util.List;
3438
import org.junit.jupiter.api.BeforeEach;
3539
import org.junit.jupiter.api.Test;
40+
import org.mockito.MockedStatic;
3641

3742
public class AwsSpanProcessingUtilTest {
3843
private static final String DEFAULT_PATH_VALUE = "/";
@@ -120,6 +125,49 @@ public void testGetIngressOperationInvalidNameAndValidTargetAndMethod() {
120125
assertThat(actualOperation).isEqualTo(validMethod + " " + validTarget);
121126
}
122127

128+
@Test
129+
public void testGetIngressOperationLambdaOverride() {
130+
try (MockedStatic<AwsApplicationSignalsCustomizerProvider> providerStatic =
131+
mockStatic(
132+
AwsApplicationSignalsCustomizerProvider.class,
133+
withSettings().defaultAnswer(CALLS_REAL_METHODS))) {
134+
// Force Lambda environment branch
135+
providerStatic
136+
.when(AwsApplicationSignalsCustomizerProvider::isLambdaEnvironment)
137+
.thenReturn(true);
138+
// Simulate an override attribute on the span
139+
when(attributesMock.get(AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE)).thenReturn("MyOverrideOp");
140+
141+
String actualOperation = AwsSpanProcessingUtil.getIngressOperation(spanDataMock);
142+
assertThat(actualOperation).isEqualTo("MyOverrideOp");
143+
}
144+
}
145+
146+
@Test
147+
public void testGetIngressOperationLambdaDefault() throws Exception {
148+
try (
149+
// Mock the AWS environment check
150+
MockedStatic<AwsApplicationSignalsCustomizerProvider> providerStatic =
151+
mockStatic(
152+
AwsApplicationSignalsCustomizerProvider.class,
153+
withSettings().defaultAnswer(CALLS_REAL_METHODS));
154+
// Mock only getFunctionNameFromEnv, leave all other util logic untouched
155+
MockedStatic<AwsSpanProcessingUtil> utilStatic =
156+
mockStatic(
157+
AwsSpanProcessingUtil.class, withSettings().defaultAnswer(CALLS_REAL_METHODS))) {
158+
// force lambda branch and no override attribute
159+
providerStatic
160+
.when(AwsApplicationSignalsCustomizerProvider::isLambdaEnvironment)
161+
.thenReturn(true);
162+
when(attributesMock.get(AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE)).thenReturn(null);
163+
// Provide a deterministic function name
164+
utilStatic.when(AwsSpanProcessingUtil::getFunctionNameFromEnv).thenReturn("MockFunction");
165+
166+
String actual = AwsSpanProcessingUtil.getIngressOperation(spanDataMock);
167+
assertThat(actual).isEqualTo("MockFunction/FunctionHandler");
168+
}
169+
}
170+
123171
@Test
124172
public void testGetEgressOperationUseInternalOperation() {
125173
String invalidName = null;

0 commit comments

Comments
 (0)