Introduce properties for configuring the OpenTelemetry BatchSpanProcessor

This commit adds the following properties to allow customization
of the BatchSpanProcessor:

- management.tracing.opentelemetry.export.include-unsampled
- management.tracing.opentelemetry.export.timeout
- management.tracing.opentelemetry.export.max-batch-size
- management.tracing.opentelemetry.export.max-queue-size
- management.tracing.opentelemetry.export.schedule-delay

Default values were taken from BatchSpanProcessorBuilder.

See gh-44655

Signed-off-by: Dmytro Nosan <dimanosan@gmail.com>
This commit is contained in:
Dmytro Nosan
2025-03-09 13:19:25 +02:00
committed by Moritz Halbritter
parent a53aba6abc
commit ea4822177f
3 changed files with 143 additions and 4 deletions

View File

@@ -119,9 +119,16 @@ public class OpenTelemetryTracingAutoConfiguration {
BatchSpanProcessor otelSpanProcessor(SpanExporters spanExporters,
ObjectProvider<SpanExportingPredicate> spanExportingPredicates, ObjectProvider<SpanReporter> spanReporters,
ObjectProvider<SpanFilter> spanFilters, ObjectProvider<MeterProvider> meterProvider) {
BatchSpanProcessorBuilder builder = BatchSpanProcessor
.builder(new CompositeSpanExporter(spanExporters.list(), spanExportingPredicates.orderedStream().toList(),
spanReporters.orderedStream().toList(), spanFilters.orderedStream().toList()));
TracingProperties.OpenTelemetry.Export properties = this.tracingProperties.getOpentelemetry().getExport();
CompositeSpanExporter spanExporter = new CompositeSpanExporter(spanExporters.list(),
spanExportingPredicates.orderedStream().toList(), spanReporters.orderedStream().toList(),
spanFilters.orderedStream().toList());
BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder(spanExporter)
.setExportUnsampledSpans(properties.isIncludeUnsampled())
.setExporterTimeout(properties.getTimeout())
.setMaxExportBatchSize(properties.getMaxBatchSize())
.setMaxQueueSize(properties.getMaxQueueSize())
.setScheduleDelay(properties.getScheduleDelay());
meterProvider.ifAvailable(builder::setMeterProvider);
return builder.build();
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.tracing;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
@@ -51,6 +52,11 @@ public class TracingProperties {
*/
private final Brave brave = new Brave();
/**
* OpenTelemetry configuration.
*/
private final OpenTelemetry opentelemetry = new OpenTelemetry();
public Sampling getSampling() {
return this.sampling;
}
@@ -67,6 +73,10 @@ public class TracingProperties {
return this.brave;
}
public OpenTelemetry getOpentelemetry() {
return this.opentelemetry;
}
public static class Sampling {
/**
@@ -293,4 +303,87 @@ public class TracingProperties {
}
public static class OpenTelemetry {
/**
* Span export configuration.
*/
private final Export export = new Export();
public Export getExport() {
return this.export;
}
public static class Export {
/**
* Whether unsampled spans should be exported.
*/
private boolean includeUnsampled;
/**
* Maximum time an export will be allowed to run before being cancelled.
*/
private Duration timeout = Duration.ofSeconds(30);
/**
* Maximum batch size for each export. This must be less than or equal to
* 'maxQueueSize'.
*/
private int maxBatchSize = 512;
/**
* Maximum number of Spans that are kept in the queue before start dropping.
*/
private int maxQueueSize = 2048;
/**
* The delay interval between two consecutive exports.
*/
private Duration scheduleDelay = Duration.ofSeconds(5);
public boolean isIncludeUnsampled() {
return this.includeUnsampled;
}
public void setIncludeUnsampled(boolean includeUnsampled) {
this.includeUnsampled = includeUnsampled;
}
public Duration getTimeout() {
return this.timeout;
}
public void setTimeout(Duration timeout) {
this.timeout = timeout;
}
public int getMaxBatchSize() {
return this.maxBatchSize;
}
public void setMaxBatchSize(int maxBatchSize) {
this.maxBatchSize = maxBatchSize;
}
public int getMaxQueueSize() {
return this.maxQueueSize;
}
public void setMaxQueueSize(int maxQueueSize) {
this.maxQueueSize = maxQueueSize;
}
public Duration getScheduleDelay() {
return this.scheduleDelay;
}
public void setScheduleDelay(Duration scheduleDelay) {
this.scheduleDelay = scheduleDelay;
}
}
}
}

View File

@@ -70,6 +70,7 @@ import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.test.util.ReflectionTestUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;
@@ -324,6 +325,44 @@ class OpenTelemetryTracingAutoConfigurationTests {
});
}
@Test
void batchSpanProcessorShouldBeConfiguredWithCustomProperties() {
this.contextRunner
.withPropertyValues("management.tracing.opentelemetry.export.timeout=45s",
"management.tracing.opentelemetry.export.include-unsampled=true",
"management.tracing.opentelemetry.export.max-batch-size=256",
"management.tracing.opentelemetry.export.max-queue-size=4096",
"management.tracing.opentelemetry.export.schedule-delay=15s")
.run((context) -> {
assertThat(context).hasSingleBean(BatchSpanProcessor.class);
BatchSpanProcessor batchSpanProcessor = context.getBean(BatchSpanProcessor.class);
assertThat(batchSpanProcessor).hasFieldOrPropertyWithValue("exportUnsampledSpans", true)
.extracting("worker")
.hasFieldOrPropertyWithValue("exporterTimeoutNanos", Duration.ofSeconds(45).toNanos())
.hasFieldOrPropertyWithValue("maxExportBatchSize", 256)
.hasFieldOrPropertyWithValue("scheduleDelayNanos", Duration.ofSeconds(15).toNanos())
.extracting("queue")
.satisfies((queue) -> assertThat(ReflectionTestUtils.<Integer>invokeMethod(queue, "capacity"))
.isEqualTo(4096));
});
}
@Test
void batchSpanProcessorShouldBeConfiguredWithDefaultProperties() {
this.contextRunner.run((context) -> {
assertThat(context).hasSingleBean(BatchSpanProcessor.class);
BatchSpanProcessor batchSpanProcessor = context.getBean(BatchSpanProcessor.class);
assertThat(batchSpanProcessor).hasFieldOrPropertyWithValue("exportUnsampledSpans", false)
.extracting("worker")
.hasFieldOrPropertyWithValue("exporterTimeoutNanos", Duration.ofSeconds(30).toNanos())
.hasFieldOrPropertyWithValue("maxExportBatchSize", 512)
.hasFieldOrPropertyWithValue("scheduleDelayNanos", Duration.ofSeconds(5).toNanos())
.extracting("queue")
.satisfies((queue) -> assertThat(ReflectionTestUtils.<Integer>invokeMethod(queue, "capacity"))
.isEqualTo(2048));
});
}
@Test // gh-41439
@ForkedClassPath
void shouldPublishEventsWhenContextStorageIsInitializedEarly() {