Skip to content

Commit b58b94f

Browse files
authored
Configure SDK when running in Lambda environment (#950)
1 parent 2f6490b commit b58b94f

File tree

5 files changed

+122
-12
lines changed

5 files changed

+122
-12
lines changed

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

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import io.opentelemetry.contrib.awsxray.AlwaysRecordSampler;
2121
import io.opentelemetry.contrib.awsxray.ResourceHolder;
2222
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
23+
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
2324
import io.opentelemetry.exporter.otlp.internal.OtlpConfigUtil;
2425
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
26+
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
2527
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
2628
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
2729
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
@@ -45,6 +47,7 @@
4547
import java.util.HashSet;
4648
import java.util.List;
4749
import java.util.Map;
50+
import java.util.Optional;
4851
import java.util.Set;
4952
import java.util.logging.Level;
5053
import java.util.logging.Logger;
@@ -66,6 +69,8 @@
6669
*/
6770
public class AwsApplicationSignalsCustomizerProvider
6871
implements AutoConfigurationCustomizerProvider {
72+
static final String AWS_LAMBDA_FUNCTION_NAME_CONFIG = "AWS_LAMBDA_FUNCTION_NAME";
73+
6974
private static final Duration DEFAULT_METRIC_EXPORT_INTERVAL = Duration.ofMinutes(1);
7075
private static final Logger logger =
7176
Logger.getLogger(AwsApplicationSignalsCustomizerProvider.class.getName());
@@ -85,16 +90,34 @@ public class AwsApplicationSignalsCustomizerProvider
8590
"otel.aws.application.signals.exporter.endpoint";
8691

8792
private static final String OTEL_JMX_TARGET_SYSTEM_CONFIG = "otel.jmx.target.system";
93+
private static final String OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_CONFIG =
94+
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT";
95+
private static final String AWS_XRAY_DAEMON_ADDRESS_CONFIG = "AWS_XRAY_DAEMON_ADDRESS";
96+
private static final String DEFAULT_UDP_ENDPOINT = "127.0.0.1:2000";
97+
private static final String OTEL_DISABLED_RESOURCE_PROVIDERS_CONFIG =
98+
"otel.java.disabled.resource.providers";
99+
private static final String OTEL_BSP_MAX_EXPORT_BATCH_SIZE_CONFIG =
100+
"otel.bsp.max.export.batch.size";
101+
102+
// UDP packet can be upto 64KB. To limit the packet size, we limit the exported batch size.
103+
// This is a bit of a magic number, as there is no simple way to tell how many spans can make a
104+
// 64KB batch since spans can vary in size.
105+
private static final int LAMBDA_SPAN_EXPORT_BATCH_SIZE = 10;
88106

89107
public void customize(AutoConfigurationCustomizer autoConfiguration) {
90108
autoConfiguration.addPropertiesCustomizer(this::customizeProperties);
109+
autoConfiguration.addPropertiesCustomizer(this::customizeLambdaEnvProperties);
91110
autoConfiguration.addResourceCustomizer(this::customizeResource);
92111
autoConfiguration.addSamplerCustomizer(this::customizeSampler);
93112
autoConfiguration.addTracerProviderCustomizer(this::customizeTracerProviderBuilder);
94113
autoConfiguration.addMeterProviderCustomizer(this::customizeMeterProvider);
95114
autoConfiguration.addSpanExporterCustomizer(this::customizeSpanExporter);
96115
}
97116

117+
static boolean isLambdaEnvironment() {
118+
return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG) != null;
119+
}
120+
98121
private boolean isApplicationSignalsEnabled(ConfigProperties configProps) {
99122
return configProps.getBoolean(
100123
APPLICATION_SIGNALS_ENABLED_CONFIG,
@@ -126,6 +149,30 @@ private Map<String, String> customizeProperties(ConfigProperties configProps) {
126149
return Collections.emptyMap();
127150
}
128151

152+
private Map<String, String> customizeLambdaEnvProperties(ConfigProperties configProperties) {
153+
if (isLambdaEnvironment()) {
154+
Map<String, String> propsOverride = new HashMap<>(2);
155+
156+
// Disable other AWS Resource Providers
157+
List<String> list = configProperties.getList(OTEL_DISABLED_RESOURCE_PROVIDERS_CONFIG);
158+
List<String> disabledResourceProviders = new ArrayList<>(list);
159+
disabledResourceProviders.add(
160+
"io.opentelemetry.contrib.aws.resource.BeanstalkResourceProvider");
161+
disabledResourceProviders.add("io.opentelemetry.contrib.aws.resource.Ec2ResourceProvider");
162+
disabledResourceProviders.add("io.opentelemetry.contrib.aws.resource.EcsResourceProvider");
163+
disabledResourceProviders.add("io.opentelemetry.contrib.aws.resource.EksResourceProvider");
164+
propsOverride.put(
165+
OTEL_DISABLED_RESOURCE_PROVIDERS_CONFIG, String.join(",", disabledResourceProviders));
166+
167+
// Set the max export batch size for BatchSpanProcessors
168+
propsOverride.put(
169+
OTEL_BSP_MAX_EXPORT_BATCH_SIZE_CONFIG, String.valueOf(LAMBDA_SPAN_EXPORT_BATCH_SIZE));
170+
171+
return propsOverride;
172+
}
173+
return Collections.emptyMap();
174+
}
175+
129176
private Resource customizeResource(Resource resource, ConfigProperties configProps) {
130177
if (isApplicationSignalsEnabled(configProps)) {
131178
AttributesBuilder builder = Attributes.builder();
@@ -156,6 +203,17 @@ private SdkTracerProviderBuilder customizeTracerProviderBuilder(
156203
// Construct and set local and remote attributes span processor
157204
tracerProviderBuilder.addSpanProcessor(
158205
AttributePropagatingSpanProcessorBuilder.create().build());
206+
207+
// If running on Lambda, we just need to export 100% spans and skip generating any Application
208+
// Signals metrics.
209+
if (isLambdaEnvironment()) {
210+
tracerProviderBuilder.addSpanProcessor(
211+
AwsUnsampledOnlySpanProcessorBuilder.create()
212+
.setMaxExportBatchSize(LAMBDA_SPAN_EXPORT_BATCH_SIZE)
213+
.build());
214+
return tracerProviderBuilder;
215+
}
216+
159217
// Construct meterProvider
160218
MetricExporter metricsExporter =
161219
ApplicationSignalsExporterProvider.INSTANCE.createExporter(configProps);
@@ -207,6 +265,21 @@ private SdkMeterProviderBuilder customizeMeterProvider(
207265

208266
private SpanExporter customizeSpanExporter(
209267
SpanExporter spanExporter, ConfigProperties configProps) {
268+
// When running in Lambda, override the default OTLP exporter with UDP exporter
269+
if (isLambdaEnvironment()) {
270+
if (isOtlpSpanExporter(spanExporter)
271+
&& System.getenv(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT_CONFIG) == null) {
272+
String tracesEndpoint =
273+
Optional.ofNullable(System.getenv(AWS_XRAY_DAEMON_ADDRESS_CONFIG))
274+
.orElse(DEFAULT_UDP_ENDPOINT);
275+
spanExporter =
276+
new OtlpUdpSpanExporterBuilder()
277+
.setPayloadSampleDecision(TracePayloadSampleDecision.SAMPLED)
278+
.setEndpoint(tracesEndpoint)
279+
.build();
280+
}
281+
}
282+
210283
if (isApplicationSignalsEnabled(configProps)) {
211284
return AwsMetricAttributesSpanExporterBuilder.create(
212285
spanExporter, ResourceHolder.getResource())
@@ -216,6 +289,11 @@ private SpanExporter customizeSpanExporter(
216289
return spanExporter;
217290
}
218291

292+
private boolean isOtlpSpanExporter(SpanExporter spanExporter) {
293+
return spanExporter instanceof OtlpGrpcSpanExporter
294+
|| spanExporter instanceof OtlpHttpSpanExporter;
295+
}
296+
219297
private enum ApplicationSignalsExporterProvider {
220298
INSTANCE;
221299

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import static io.opentelemetry.semconv.SemanticAttributes.MESSAGING_OPERATION;
2424
import static io.opentelemetry.semconv.SemanticAttributes.MessagingOperationValues.PROCESS;
2525
import static io.opentelemetry.semconv.SemanticAttributes.RPC_SYSTEM;
26+
import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.AWS_LAMBDA_FUNCTION_NAME_CONFIG;
27+
import static software.amazon.opentelemetry.javaagent.providers.AwsApplicationSignalsCustomizerProvider.isLambdaEnvironment;
2628
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
2729

2830
import com.fasterxml.jackson.core.type.TypeReference;
@@ -82,9 +84,13 @@ static List<String> getDialectKeywords() {
8284
/**
8385
* Ingress operation (i.e. operation for Server and Consumer spans) will be generated from
8486
* "http.method + http.target/with the first API path parameter" if the default span name equals
85-
* null, UnknownOperation or http.method value.
87+
* null, UnknownOperation or http.method value. If running in Lambda, the ingress operation will
88+
* be the function name + /FunctionHandler.
8689
*/
8790
static String getIngressOperation(SpanData span) {
91+
if (isLambdaEnvironment()) {
92+
return System.getenv(AWS_LAMBDA_FUNCTION_NAME_CONFIG) + "/FunctionHandler";
93+
}
8894
String operation = span.getName();
8995
if (shouldUseInternalOperation(span)) {
9096
operation = INTERNAL_OPERATION;

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,6 @@ final class AwsUnsampledOnlySpanProcessor implements SpanProcessor {
3434
this.delegate = delegate;
3535
}
3636

37-
public static AwsUnsampledOnlySpanProcessorBuilder builder() {
38-
return new AwsUnsampledOnlySpanProcessorBuilder();
39-
}
40-
4137
@Override
4238
public void onStart(Context parentContext, ReadWriteSpan span) {
4339
if (!span.getSpanContext().isSampled()) {

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,25 +21,40 @@
2121
import io.opentelemetry.sdk.trace.export.SpanExporter;
2222

2323
final class AwsUnsampledOnlySpanProcessorBuilder {
24+
public static AwsUnsampledOnlySpanProcessorBuilder create() {
25+
return new AwsUnsampledOnlySpanProcessorBuilder();
26+
}
2427

2528
// Default exporter is OtlpUdpSpanExporter with unsampled payload prefix
2629
private SpanExporter exporter =
2730
new OtlpUdpSpanExporterBuilder()
2831
.setPayloadSampleDecision(TracePayloadSampleDecision.UNSAMPLED)
2932
.build();
3033

34+
// Default batch size to be same as Otel BSP default
35+
private int maxExportBatchSize = 512;
36+
3137
public AwsUnsampledOnlySpanProcessorBuilder setSpanExporter(SpanExporter exporter) {
3238
requireNonNull(exporter, "exporter cannot be null");
3339
this.exporter = exporter;
3440
return this;
3541
}
3642

43+
public AwsUnsampledOnlySpanProcessorBuilder setMaxExportBatchSize(int maxExportBatchSize) {
44+
this.maxExportBatchSize = maxExportBatchSize;
45+
return this;
46+
}
47+
3748
public AwsUnsampledOnlySpanProcessor build() {
3849
BatchSpanProcessor bsp =
39-
BatchSpanProcessor.builder(exporter).setExportUnsampledSpans(true).build();
50+
BatchSpanProcessor.builder(exporter)
51+
.setExportUnsampledSpans(true)
52+
.setMaxExportBatchSize(maxExportBatchSize)
53+
.build();
4054
return new AwsUnsampledOnlySpanProcessor(bsp);
4155
}
4256

57+
// Visible for testing
4358
SpanExporter getSpanExporter() {
4459
return exporter;
4560
}

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,19 @@ public class AwsUnsampledOnlySpanProcessorTest {
3535

3636
@Test
3737
public void testIsStartRequired() {
38-
SpanProcessor processor = AwsUnsampledOnlySpanProcessor.builder().build();
38+
SpanProcessor processor = AwsUnsampledOnlySpanProcessorBuilder.create().build();
3939
assertThat(processor.isStartRequired()).isTrue();
4040
}
4141

4242
@Test
4343
public void testIsEndRequired() {
44-
SpanProcessor processor = AwsUnsampledOnlySpanProcessor.builder().build();
44+
SpanProcessor processor = AwsUnsampledOnlySpanProcessorBuilder.create().build();
4545
assertThat(processor.isEndRequired()).isTrue();
4646
}
4747

4848
@Test
4949
public void testDefaultSpanProcessor() {
50-
AwsUnsampledOnlySpanProcessorBuilder builder = AwsUnsampledOnlySpanProcessor.builder();
50+
AwsUnsampledOnlySpanProcessorBuilder builder = AwsUnsampledOnlySpanProcessorBuilder.create();
5151
AwsUnsampledOnlySpanProcessor unsampledSP = builder.build();
5252

5353
assertThat(builder.getSpanExporter()).isInstanceOf(OtlpUdpSpanExporter.class);
@@ -59,12 +59,14 @@ public void testDefaultSpanProcessor() {
5959
.contains(
6060
"spanExporter=software.amazon.opentelemetry.javaagent.providers.OtlpUdpSpanExporter");
6161
assertThat(delegateBspString).contains("exportUnsampledSpans=true");
62+
assertThat(delegateBspString).contains("maxExportBatchSize=512");
6263
}
6364

6465
@Test
6566
public void testSpanProcessorWithExporter() {
6667
AwsUnsampledOnlySpanProcessorBuilder builder =
67-
AwsUnsampledOnlySpanProcessor.builder().setSpanExporter(InMemorySpanExporter.create());
68+
AwsUnsampledOnlySpanProcessorBuilder.create()
69+
.setSpanExporter(InMemorySpanExporter.create());
6870
AwsUnsampledOnlySpanProcessor unsampledSP = builder.build();
6971

7072
assertThat(builder.getSpanExporter()).isInstanceOf(InMemorySpanExporter.class);
@@ -77,6 +79,19 @@ public void testSpanProcessorWithExporter() {
7779
assertThat(delegateBspString).contains("exportUnsampledSpans=true");
7880
}
7981

82+
@Test
83+
public void testSpanProcessorWithBatchSize() {
84+
AwsUnsampledOnlySpanProcessorBuilder builder =
85+
AwsUnsampledOnlySpanProcessorBuilder.create().setMaxExportBatchSize(100);
86+
AwsUnsampledOnlySpanProcessor unsampledSP = builder.build();
87+
88+
SpanProcessor delegate = unsampledSP.getDelegate();
89+
assertThat(delegate).isInstanceOf(BatchSpanProcessor.class);
90+
BatchSpanProcessor delegateBsp = (BatchSpanProcessor) delegate;
91+
String delegateBspString = delegateBsp.toString();
92+
assertThat(delegateBspString).contains("maxExportBatchSize=100");
93+
}
94+
8095
@Test
8196
public void testStartAddsAttributeToSampledSpan() {
8297
SpanContext mockSpanContext = mock(SpanContext.class);
@@ -85,7 +100,7 @@ public void testStartAddsAttributeToSampledSpan() {
85100
ReadWriteSpan spanMock = mock(ReadWriteSpan.class);
86101
when(spanMock.getSpanContext()).thenReturn(mockSpanContext);
87102

88-
AwsUnsampledOnlySpanProcessor processor = AwsUnsampledOnlySpanProcessor.builder().build();
103+
AwsUnsampledOnlySpanProcessor processor = AwsUnsampledOnlySpanProcessorBuilder.create().build();
89104
processor.onStart(parentContextMock, spanMock);
90105

91106
// verify setAttribute was never called
@@ -100,7 +115,7 @@ public void testStartAddsAttributeToUnsampledSpan() {
100115
ReadWriteSpan spanMock = mock(ReadWriteSpan.class);
101116
when(spanMock.getSpanContext()).thenReturn(mockSpanContext);
102117

103-
AwsUnsampledOnlySpanProcessor processor = AwsUnsampledOnlySpanProcessor.builder().build();
118+
AwsUnsampledOnlySpanProcessor processor = AwsUnsampledOnlySpanProcessorBuilder.create().build();
104119
processor.onStart(parentContextMock, spanMock);
105120

106121
// verify setAttribute was called with the correct arguments

0 commit comments

Comments
 (0)