Skip to content

Commit e524eda

Browse files
pxawsJeel Mehta
authored andcommitted
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 43198cf commit e524eda

File tree

3 files changed

+83
-4
lines changed

3 files changed

+83
-4
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: 22 additions & 4 deletions
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;
@@ -96,11 +97,23 @@ static List<String> getDialectKeywords() {
9697
*/
9798
static String getIngressOperation(SpanData span) {
9899
if (isLambdaEnvironment()) {
99-
String op = generateIngressOperation(span);
100-
if (!op.equals(UNKNOWN_OPERATION)) {
101-
return op;
100+
/*
101+
* By default the local operation of a Lambda span is hard-coded to "<FunctionName>/FunctionHandler".
102+
* To dynamically override this at runtime—such as when running a custom server inside your Lambda—
103+
* you can set the span attribute "aws.lambda.local.operation.override" before ending the span. For example:
104+
*
105+
* // Obtain the current Span and override its operation name
106+
* Span.current().setAttribute(
107+
* "aws.lambda.local.operation.override",
108+
* "MyServiceOperation");
109+
*
110+
* The code below will detect that override and use it instead of the default.
111+
*/
112+
String operationOverride = span.getAttributes().get(AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE);
113+
if (operationOverride != null) {
114+
return operationOverride;
102115
}
103-
return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG) + "/FunctionHandler";
116+
return getFunctionNameFromEnv() + "/FunctionHandler";
104117
}
105118
String operation = span.getName();
106119
if (shouldUseInternalOperation(span)) {
@@ -111,6 +124,11 @@ static String getIngressOperation(SpanData span) {
111124
return operation;
112125
}
113126

127+
// define a function so that we can mock it in unit test
128+
static String getFunctionNameFromEnv() {
129+
return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG);
130+
}
131+
114132
static String getEgressOperation(SpanData span) {
115133
if (shouldUseInternalOperation(span)) {
116134
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
@@ -21,8 +21,12 @@
2121
import static org.assertj.core.api.Assertions.assertThat;
2222
import static org.junit.jupiter.api.Assertions.assertFalse;
2323
import static org.junit.jupiter.api.Assertions.assertTrue;
24+
import static org.mockito.Answers.CALLS_REAL_METHODS;
2425
import static org.mockito.Mockito.mock;
26+
import static org.mockito.Mockito.mockStatic;
2527
import static org.mockito.Mockito.when;
28+
import static org.mockito.Mockito.withSettings;
29+
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LAMBDA_LOCAL_OPERATION_OVERRIDE;
2630
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
2731
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.MAX_KEYWORD_LENGTH;
2832
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.getDialectKeywords;
@@ -36,6 +40,7 @@
3640
import java.util.List;
3741
import org.junit.jupiter.api.BeforeEach;
3842
import org.junit.jupiter.api.Test;
43+
import org.mockito.MockedStatic;
3944

4045
public class AwsSpanProcessingUtilTest {
4146
private static final String DEFAULT_PATH_VALUE = "/";
@@ -123,6 +128,49 @@ public void testGetIngressOperationInvalidNameAndValidTargetAndMethod() {
123128
assertThat(actualOperation).isEqualTo(validMethod + " " + validTarget);
124129
}
125130

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

0 commit comments

Comments
 (0)