Skip to content

Commit 8f3d78c

Browse files
committed
"otlp" protocol exporters #6
tests
1 parent b0db0ae commit 8f3d78c

4 files changed

Lines changed: 245 additions & 22 deletions

File tree

bootique-opentelemetry/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,16 @@
6868
<artifactId>slf4j-simple</artifactId>
6969
<scope>test</scope>
7070
</dependency>
71+
<dependency>
72+
<groupId>org.testcontainers</groupId>
73+
<artifactId>testcontainers</artifactId>
74+
<scope>test</scope>
75+
</dependency>
76+
<dependency>
77+
<groupId>org.testcontainers</groupId>
78+
<artifactId>testcontainers-junit-jupiter</artifactId>
79+
<scope>test</scope>
80+
</dependency>
7181
</dependencies>
7282

7383
<build>
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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.BQCoreModule;
22+
import io.bootique.BQRuntime;
23+
import io.bootique.junit5.BQTest;
24+
import io.bootique.junit5.BQTestFactory;
25+
import io.bootique.junit5.BQTestTool;
26+
import io.opentelemetry.api.OpenTelemetry;
27+
import io.opentelemetry.api.trace.Span;
28+
import org.junit.jupiter.api.AfterAll;
29+
import org.junit.jupiter.api.BeforeAll;
30+
import org.junit.jupiter.api.Test;
31+
import org.slf4j.Logger;
32+
import org.slf4j.LoggerFactory;
33+
import org.testcontainers.containers.GenericContainer;
34+
import org.testcontainers.containers.wait.strategy.Wait;
35+
import org.testcontainers.utility.MountableFile;
36+
37+
import java.time.Duration;
38+
import java.util.ArrayList;
39+
import java.util.HashMap;
40+
import java.util.List;
41+
import java.util.Map;
42+
43+
import static org.junit.jupiter.api.Assertions.assertFalse;
44+
import static org.junit.jupiter.api.Assertions.assertTrue;
45+
46+
@BQTest
47+
public class OtlpTracesExporterIT {
48+
49+
private static final Logger LOGGER = LoggerFactory.getLogger(OtlpTracesExporterIT.class);
50+
51+
@BQTestTool
52+
private static final BQTestFactory testFactory = new BQTestFactory().autoLoadModules();
53+
private static GenericContainer<?> otelCollector;
54+
private static int lastReadLineCount = 0;
55+
56+
@BeforeAll
57+
static void setupCollector() {
58+
otelCollector = new GenericContainer<>("otel/opentelemetry-collector-contrib:0.95.0")
59+
.withCopyFileToContainer(
60+
MountableFile.forClasspathResource("otel-collector-config.yaml"),
61+
"/etc/otel-collector-config.yaml")
62+
.withCommand("--config=/etc/otel-collector-config.yaml")
63+
.withExposedPorts(4317, 4318)
64+
.waitingFor(Wait.forLogMessage(".*Everything is ready.*", 1)
65+
.withStartupTimeout(Duration.ofSeconds(30)));
66+
67+
otelCollector.start();
68+
}
69+
70+
@AfterAll
71+
static void teardownCollector() {
72+
if (otelCollector != null) {
73+
otelCollector.stop();
74+
}
75+
}
76+
77+
@Test
78+
public void testGrpcExport() throws InterruptedException {
79+
80+
BQRuntime runtime = testFactory.app()
81+
.module(b -> BQCoreModule.extend(b)
82+
.setProperty("bq.opentelemetry.tracerProvider.traceExporters[0].type", "otlp")
83+
.setProperty("bq.opentelemetry.otlp.protocol", "grpc")
84+
.setProperty("bq.opentelemetry.otlp.url",
85+
"http://localhost:" + otelCollector.getMappedPort(4317)))
86+
.createRuntime();
87+
88+
89+
OpenTelemetry otel = runtime.getInstance(OpenTelemetry.class);
90+
Span testSpan = otel.getTracer("test-tracer")
91+
.spanBuilder("test-span-grpc")
92+
.setAttribute("test.key", "test-value")
93+
.startSpan();
94+
testSpan.end();
95+
96+
List<SpanInfo> spans = readExportedSpans(8_000L);
97+
98+
assertFalse(spans.isEmpty(), "Expected at least one span to be exported");
99+
100+
SpanInfo span = spans.stream()
101+
.filter(s -> "test-span-grpc".equals(s.name))
102+
.findFirst()
103+
.orElseThrow(() -> new AssertionError("test-span-grpc not found"));
104+
105+
assertTrue(span.attributes.containsKey("test.key"), "test.key attribute should be present");
106+
assertTrue(span.attributes.get("test.key").contains("test-value"), "test.key should have value 'test-value'");
107+
}
108+
109+
@Test
110+
public void testHttpProtobufExport() throws InterruptedException {
111+
// no need for BQTestFactory, we'll be doing manual shutdown,
112+
BQRuntime runtime = testFactory.app()
113+
.module(b -> BQCoreModule.extend(b)
114+
.setProperty("bq.opentelemetry.tracerProvider.traceExporters[0].type", "otlp")
115+
.setProperty("bq.opentelemetry.otlp.protocol", "http/protobuf")
116+
.setProperty("bq.opentelemetry.otlp.url",
117+
"http://localhost:" + otelCollector.getMappedPort(4318)))
118+
.createRuntime();
119+
120+
121+
OpenTelemetry otel = runtime.getInstance(OpenTelemetry.class);
122+
Span testSpan = otel.getTracer("test-tracer")
123+
.spanBuilder("test-span-http")
124+
.setAttribute("http.test", "http-value")
125+
.startSpan();
126+
testSpan.end();
127+
128+
List<SpanInfo> spans = readExportedSpans(8_000L);
129+
130+
assertFalse(spans.isEmpty(), "Expected at least one span to be exported");
131+
132+
SpanInfo span = spans.stream()
133+
.filter(s -> "test-span-http".equals(s.name))
134+
.findFirst()
135+
.orElseThrow(() -> new AssertionError("test-span-http not found"));
136+
137+
assertTrue(span.attributes.containsKey("http.test"), "http.test attribute should be present");
138+
assertTrue(span.attributes.get("http.test").contains("http-value"), "http.test should have value 'http-value'");
139+
}
140+
141+
private List<SpanInfo> readExportedSpans(long timeoutMs) throws InterruptedException {
142+
143+
long sleep = timeoutMs < 500 ? timeoutMs : 500;
144+
long tries = timeoutMs / sleep + (timeoutMs % sleep > 0 ? 1 : 0);
145+
146+
for (int i = 0; i < tries; i++) {
147+
Thread.sleep(sleep);
148+
149+
if (i > 0) {
150+
LOGGER.info("reading container spans, attempt {}", i + 1);
151+
}
152+
153+
List<SpanInfo> spans = readExportedSpans();
154+
if (!spans.isEmpty()) {
155+
return spans;
156+
}
157+
}
158+
159+
return List.of();
160+
}
161+
162+
private List<SpanInfo> readExportedSpans() {
163+
String logs = otelCollector.getLogs();
164+
List<SpanInfo> spans = new ArrayList<>();
165+
166+
String[] lines = logs.split("\n");
167+
SpanInfo currentSpan = null;
168+
169+
// Skip lines we've already seen in previous test runs
170+
for (int i = lastReadLineCount; i < lines.length; i++) {
171+
String line = lines[i];
172+
173+
if (line.contains("Span #")) {
174+
if (currentSpan != null) {
175+
spans.add(currentSpan);
176+
}
177+
currentSpan = new SpanInfo();
178+
} else if (currentSpan != null) {
179+
if (line.contains("Name :")) {
180+
currentSpan.name = line.split(":", 2)[1].trim();
181+
} else if (line.contains("->") && line.contains(":")) {
182+
// Parse attributes like " -> test.key: Str(test-value)"
183+
String attrLine = line.substring(line.indexOf("->") + 2).trim();
184+
int colonIdx = attrLine.indexOf(":");
185+
if (colonIdx > 0) {
186+
String key = attrLine.substring(0, colonIdx).trim();
187+
String value = attrLine.substring(colonIdx + 1).trim();
188+
currentSpan.attributes.put(key, value);
189+
}
190+
}
191+
}
192+
}
193+
194+
if (currentSpan != null) {
195+
spans.add(currentSpan);
196+
}
197+
198+
lastReadLineCount = lines.length;
199+
200+
return spans;
201+
}
202+
203+
private static class SpanInfo {
204+
String name;
205+
final Map<String, String> attributes = new HashMap<>();
206+
}
207+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
receivers:
2+
otlp:
3+
protocols:
4+
grpc:
5+
endpoint: 0.0.0.0:4317
6+
http:
7+
endpoint: 0.0.0.0:4318
8+
9+
exporters:
10+
logging:
11+
verbosity: detailed
12+
13+
service:
14+
pipelines:
15+
traces:
16+
receivers: [otlp]
17+
processors: []
18+
exporters: [logging]

pom.xml

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -68,39 +68,27 @@
6868
<groupId>io.opentelemetry</groupId>
6969
<artifactId>opentelemetry-exporter-logging</artifactId>
7070
<version>${opentelemetry.version}</version>
71-
<exclusions>
72-
<!--
73-
We configure everything through Bootique, so autoconfigure SPI is currently not supported by design.
74-
Though eventually we may revert this policy and allow extensions declared in "META-INF/services",
75-
and then we'll need this jar
76-
-->
77-
<exclusion>
78-
<groupId>io.opentelemetry</groupId>
79-
<artifactId>opentelemetry-sdk-extension-autoconfigure-spi</artifactId>
80-
</exclusion>
81-
</exclusions>
8271
</dependency>
8372
<dependency>
8473
<groupId>io.opentelemetry</groupId>
8574
<artifactId>opentelemetry-exporter-otlp</artifactId>
8675
<version>${opentelemetry.version}</version>
87-
<exclusions>
88-
<!--
89-
We configure everything through Bootique, so autoconfigure SPI is currently not supported by design.
90-
Though eventually we may revert this policy and allow extensions declared in "META-INF/services",
91-
and then we'll need this jar
92-
-->
93-
<exclusion>
94-
<groupId>io.opentelemetry</groupId>
95-
<artifactId>opentelemetry-sdk-extension-autoconfigure-spi</artifactId>
96-
</exclusion>
97-
</exclusions>
9876
</dependency>
9977
<dependency>
10078
<groupId>io.bootique</groupId>
10179
<artifactId>bootique-junit5</artifactId>
10280
<version>${project.version}</version>
10381
</dependency>
82+
<dependency>
83+
<groupId>org.testcontainers</groupId>
84+
<artifactId>testcontainers</artifactId>
85+
<version>${testcontainers.version}</version>
86+
</dependency>
87+
<dependency>
88+
<groupId>org.testcontainers</groupId>
89+
<artifactId>testcontainers-junit-jupiter</artifactId>
90+
<version>${testcontainers.version}</version>
91+
</dependency>
10492
<dependency>
10593
<groupId>org.slf4j</groupId>
10694
<artifactId>slf4j-api</artifactId>

0 commit comments

Comments
 (0)