Skip to content

Commit defae7c

Browse files
committed
Configurable SdkTracerProvider #2
1 parent b2c7745 commit defae7c

13 files changed

Lines changed: 425 additions & 15 deletions

RELEASE-NOTES.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## 4.0-M4
22

33
* #1 OpenTelemetry bootstrap
4-
* #3 Injectable OTel Resource with Bootique application name #3
4+
* #2 Configurable SdkTracerProvider
5+
* #3 Injectable OTel Resource with Bootique application name
56

bootique-opentelemetry/pom.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
<groupId>io.opentelemetry</groupId>
4545
<artifactId>opentelemetry-sdk</artifactId>
4646
</dependency>
47+
<dependency>
48+
<groupId>io.opentelemetry</groupId>
49+
<artifactId>opentelemetry-exporter-logging</artifactId>
50+
</dependency>
4751
<dependency>
4852
<groupId>org.slf4j</groupId>
4953
<artifactId>jul-to-slf4j</artifactId>
@@ -55,11 +59,6 @@
5559
<artifactId>bootique-junit5</artifactId>
5660
<scope>test</scope>
5761
</dependency>
58-
<dependency>
59-
<groupId>io.opentelemetry</groupId>
60-
<artifactId>opentelemetry-exporter-logging</artifactId>
61-
<scope>test</scope>
62-
</dependency>
6362
<dependency>
6463
<groupId>org.slf4j</groupId>
6564
<artifactId>slf4j-simple</artifactId>

bootique-opentelemetry/src/main/java/io/bootique/otel/OpenTelemetryFactory.java

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,47 @@
1919
package io.bootique.otel;
2020

2121
import io.bootique.annotation.BQConfig;
22+
import io.bootique.annotation.BQConfigProperty;
23+
import io.bootique.otel.trace.SdkTracerProviderFactory;
24+
import io.bootique.shutdown.ShutdownManager;
2225
import io.opentelemetry.api.OpenTelemetry;
2326
import io.opentelemetry.sdk.OpenTelemetrySdk;
27+
import io.opentelemetry.sdk.OpenTelemetrySdkBuilder;
28+
import io.opentelemetry.sdk.resources.Resource;
29+
import jakarta.inject.Inject;
2430

2531
/**
2632
* @since 4.0
2733
*/
2834
@BQConfig
2935
public class OpenTelemetryFactory {
36+
37+
private final Resource resource;
38+
private final ShutdownManager shutdownManager;
39+
40+
private SdkTracerProviderFactory traceProvider;
41+
42+
@Inject
43+
public OpenTelemetryFactory(Resource resource, ShutdownManager shutdownManager) {
44+
this.resource = resource;
45+
this.shutdownManager = shutdownManager;
46+
}
47+
48+
@BQConfigProperty
49+
public OpenTelemetryFactory setTraceProvider(SdkTracerProviderFactory traceProvider) {
50+
this.traceProvider = traceProvider;
51+
return this;
52+
}
3053

3154
public OpenTelemetry create() {
32-
return OpenTelemetrySdk.builder().build();
55+
OpenTelemetrySdkBuilder builder = OpenTelemetrySdk.builder();
56+
builder.setTracerProvider(traceProviderOrDefault().create());
57+
return builder.build();
58+
}
59+
60+
private SdkTracerProviderFactory traceProviderOrDefault() {
61+
return traceProvider != null
62+
? traceProvider
63+
: new SdkTracerProviderFactory(resource, shutdownManager);
3364
}
3465
}

bootique-opentelemetry/src/main/java/io/bootique/otel/OpenTelemetryModule.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020

2121
import io.bootique.BQModule;
2222
import io.bootique.ModuleCrate;
23+
import io.bootique.config.ConfigurationFactory;
2324
import io.bootique.di.Binder;
2425
import io.bootique.di.Provides;
2526
import io.bootique.meta.application.ApplicationMetadata;
2627
import io.opentelemetry.api.GlobalOpenTelemetry;
2728
import io.opentelemetry.api.OpenTelemetry;
2829
import io.opentelemetry.api.common.AttributeKey;
2930
import io.opentelemetry.api.common.Attributes;
30-
import io.opentelemetry.sdk.OpenTelemetrySdk;
3131
import io.opentelemetry.sdk.resources.Resource;
3232
import jakarta.inject.Singleton;
3333
import org.slf4j.bridge.SLF4JBridgeHandler;
@@ -39,10 +39,13 @@
3939
*/
4040
public class OpenTelemetryModule implements BQModule {
4141

42+
private static final String CONFIG_PREFIX = "opentelemetry";
43+
4244
@Override
4345
public ModuleCrate crate() {
4446
return ModuleCrate.of(this)
4547
.description("Integrates OpenTelemetry.")
48+
.config(CONFIG_PREFIX, OpenTelemetryFactory.class)
4649
.build();
4750
}
4851

@@ -59,7 +62,7 @@ Resource provideResource(ApplicationMetadata md) {
5962

6063
@Singleton
6164
@Provides
62-
OpenTelemetry provideOpenTelemetry() {
65+
OpenTelemetry provideOpenTelemetry(ConfigurationFactory configFactory) {
6366

6467
// reconfigure JUL used by LoggingMetricExporter and friends
6568
LogManager.getLogManager().reset();
@@ -68,9 +71,6 @@ OpenTelemetry provideOpenTelemetry() {
6871
// If started via agent, use the global singleton. Otherwise, create a Bootique managed instance
6972
return GlobalOpenTelemetry.isSet()
7073
? GlobalOpenTelemetry.get()
71-
72-
// TODO: OpenTelemetry instance is injectable throughout Bootique. Does it ever make sense to use
73-
// "buildAndRegisterGlobal()" and share it outside?
74-
: OpenTelemetrySdk.builder().build();
74+
: configFactory.config(OpenTelemetryFactory.class, CONFIG_PREFIX).create();
7575
}
7676
}

bootique-opentelemetry/src/main/java/io/bootique/otel/OpenTelemetryVar.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,15 @@
2828
*/
2929
enum OpenTelemetryVar {
3030

31-
OTEL_SERVICE_NAME("service.name");
31+
/* General SDK Configuration */
32+
OTEL_SERVICE_NAME("service.name"),
33+
34+
/* Exporter Selection */
35+
OTEL_TRACES_EXPORTER("traces.exporter"),
36+
37+
/* OTLP Exporter */
38+
OTEL_EXPORTER_OTLP_PROTOCOL("exporter.otlp.protocol");
39+
// TODO: OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, OTEL_EXPORTER_OTLP_LOGS_PROTOCOL
3240

3341
public final String otelProperty;
3442

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Licensed to ObjectStyle LLC under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ObjectStyle LLC licenses
6+
* this file to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. 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 io.bootique.otel.trace;
20+
21+
import com.fasterxml.jackson.annotation.JsonTypeName;
22+
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
23+
24+
@JsonTypeName("console")
25+
public class ConsoleSpanExporterFactory implements SpanExporterFactory {
26+
27+
@Override
28+
public SpanExporterSupplier create() {
29+
return new SpanExporterSupplier(() -> LoggingSpanExporter.create(), false);
30+
}
31+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Licensed to ObjectStyle LLC under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ObjectStyle LLC licenses
6+
* this file to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. 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 io.bootique.otel.trace;
20+
21+
import com.fasterxml.jackson.annotation.JsonTypeName;
22+
23+
@JsonTypeName("none")
24+
public class NoneSpanExporterFactory implements SpanExporterFactory {
25+
26+
@Override
27+
public SpanExporterSupplier create() {
28+
return new SpanExporterSupplier(null, false);
29+
}
30+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* Licensed to ObjectStyle LLC under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ObjectStyle LLC licenses
6+
* this file to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. 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 io.bootique.otel.trace;
20+
21+
import io.bootique.annotation.BQConfig;
22+
import io.bootique.annotation.BQConfigProperty;
23+
import io.bootique.shutdown.ShutdownManager;
24+
import io.opentelemetry.sdk.resources.Resource;
25+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
26+
import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder;
27+
import io.opentelemetry.sdk.trace.SpanProcessor;
28+
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
29+
import io.opentelemetry.sdk.trace.export.BatchSpanProcessorBuilder;
30+
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
31+
import io.opentelemetry.sdk.trace.export.SpanExporter;
32+
import jakarta.inject.Inject;
33+
34+
import java.util.ArrayList;
35+
import java.util.List;
36+
37+
/**
38+
* @since 4.0
39+
*/
40+
@BQConfig
41+
public class SdkTracerProviderFactory {
42+
43+
private final Resource resource;
44+
private final ShutdownManager shutdownManager;
45+
46+
private List<SpanExporterFactory> tracesExporters;
47+
48+
@Inject
49+
public SdkTracerProviderFactory(Resource resource, ShutdownManager shutdownManager) {
50+
this.resource = resource;
51+
this.shutdownManager = shutdownManager;
52+
}
53+
54+
@BQConfigProperty
55+
public SdkTracerProviderFactory setTracesExporters(List<SpanExporterFactory> tracesExporters) {
56+
this.tracesExporters = tracesExporters;
57+
return this;
58+
}
59+
60+
public SdkTracerProvider create() {
61+
62+
SdkTracerProviderBuilder builder = SdkTracerProvider
63+
.builder()
64+
.setResource(resource);
65+
66+
// TODO: span limits
67+
// TODO: meter provider
68+
// TODO: sampler
69+
70+
createSpanProcessors().forEach(builder::addSpanProcessor);
71+
72+
return shutdownManager.onShutdown(builder.build());
73+
}
74+
75+
private List<SpanProcessor> createSpanProcessors() {
76+
77+
List<SpanProcessor> processors = new ArrayList<>(2);
78+
79+
List<SpanExporterSupplier> batchedExporters = exporterSuppliers()
80+
.stream()
81+
82+
// add a simple processor for console exporter, feed the rest into a single batch processor
83+
.peek(e -> {
84+
if (!e.shouldBatch()) {
85+
processors.add(createSimpleSpanProcessor(e));
86+
}
87+
})
88+
.filter(SpanExporterSupplier::shouldBatch)
89+
.toList();
90+
91+
if (!batchedExporters.isEmpty()) {
92+
processors.add(createBatchSpanProcessor(batchedExporters));
93+
}
94+
95+
return processors;
96+
}
97+
98+
private List<SpanExporterSupplier> exporterSuppliers() {
99+
100+
// unlike the agent whose default is "otlp", our default will be "console", so that the app could
101+
// work standalone out of the box. To suppress exporting, an explicit "none" exporter should be set
102+
103+
// A single "none" exporter would suppress the default "console" exporter. Though unlike the agent, having a
104+
// "none" exporter mixed with others doesn't result in an exception. It will just be ignored
105+
106+
List<SpanExporterFactory> tracesExporters = this.tracesExporters == null || this.tracesExporters.isEmpty()
107+
? List.of(new ConsoleSpanExporterFactory())
108+
: this.tracesExporters;
109+
110+
return tracesExporters.stream()
111+
.map(SpanExporterFactory::create)
112+
.filter(s -> !s.isNone())
113+
.toList();
114+
}
115+
116+
private SpanProcessor createSimpleSpanProcessor(SpanExporterSupplier exporterSupplier) {
117+
// TODO: .setMeterProvider(() -> meterProvider)
118+
119+
// presumably we don't need to shut down the exporter, as SpanProcessor would do it for us
120+
SpanProcessor processor = SimpleSpanProcessor.builder(exporterSupplier.spanExporter().get()).build();
121+
return shutdownManager.onShutdown(processor);
122+
}
123+
124+
private BatchSpanProcessor createBatchSpanProcessor(List<SpanExporterSupplier> exporterSuppliers) {
125+
126+
List<SpanExporter> exporters = exporterSuppliers.stream().map(s -> s.spanExporter().get()).toList();
127+
SpanExporter composite = SpanExporter.composite(exporters);
128+
129+
// as of OTel 1.59.0 unlike SimpleSpanProcessor, the batch processor will not shut down the exporters, so we
130+
// need to do it ourselves
131+
BatchSpanProcessorBuilder builder = BatchSpanProcessor.builder(shutdownManager.onShutdown(composite));
132+
133+
// TODO:
134+
// support .setMeterProvider(..)
135+
// support props for the builder:
136+
// otel.bsp.schedule.delay
137+
// otel.bsp.max.queue.size
138+
// otel.bsp.max.export.batch.size
139+
// otel.bsp.export.timeout
140+
141+
return shutdownManager.onShutdown(builder.build());
142+
}
143+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Licensed to ObjectStyle LLC under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ObjectStyle LLC licenses
6+
* this file to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. 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 io.bootique.otel.trace;
20+
21+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
22+
import io.bootique.annotation.BQConfig;
23+
import io.bootique.config.PolymorphicConfiguration;
24+
25+
@BQConfig
26+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = ConsoleSpanExporterFactory.class)
27+
public interface SpanExporterFactory extends PolymorphicConfiguration {
28+
29+
SpanExporterSupplier create();
30+
}

0 commit comments

Comments
 (0)