Skip to content

Commit 3f6d990

Browse files
committed
Add Zipkin sender based on RestTemplate
See spring-projectsgh-30156
1 parent 586e3c8 commit 3f6d990

File tree

9 files changed

+333
-12
lines changed

9 files changed

+333
-12
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/BraveAutoConfiguration.java

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import io.micrometer.tracing.brave.bridge.BraveCurrentTraceContext;
3434
import io.micrometer.tracing.brave.bridge.BraveTracer;
3535

36+
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration;
3637
import org.springframework.boot.autoconfigure.AutoConfiguration;
3738
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3839
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
2121

2222
import org.springframework.boot.actuate.autoconfigure.observation.ObservationAutoConfiguration;
23+
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration;
2324
import org.springframework.boot.autoconfigure.AutoConfiguration;
2425
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2526
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/OpenTelemetryAutoConfiguration.java

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import io.opentelemetry.sdk.trace.samplers.Sampler;
3737

3838
import org.springframework.boot.SpringBootVersion;
39+
import org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration;
3940
import org.springframework.boot.autoconfigure.AutoConfiguration;
4041
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
4142
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/TracingProperties.java

+7-7
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import org.springframework.boot.context.properties.ConfigurationProperties;
2020

2121
/**
22-
* Configuration properties for {@link ZipkinAutoConfiguration}.
22+
* Configuration properties for tracing.
2323
*
2424
* @author Moritz Halbritter
2525
* @since 3.0.0
@@ -37,11 +37,11 @@ public class TracingProperties {
3737
*/
3838
private final Sampling sampling = new Sampling();
3939

40-
Zipkin getZipkin() {
40+
public Zipkin getZipkin() {
4141
return this.zipkin;
4242
}
4343

44-
Sampling getSampling() {
44+
public Sampling getSampling() {
4545
return this.sampling;
4646
}
4747

@@ -52,11 +52,11 @@ public static class Zipkin {
5252
*/
5353
private String endpoint = "http://localhost:9411/api/v2/spans";
5454

55-
String getEndpoint() {
55+
public String getEndpoint() {
5656
return this.endpoint;
5757
}
5858

59-
void setEndpoint(String endpoint) {
59+
public void setEndpoint(String endpoint) {
6060
this.endpoint = endpoint;
6161
}
6262

@@ -69,11 +69,11 @@ public static class Sampling {
6969
*/
7070
private float probability = 0.10f;
7171

72-
float getProbability() {
72+
public float getProbability() {
7373
return this.probability;
7474
}
7575

76-
void setProbability(float probability) {
76+
public void setProbability(float probability) {
7777
this.probability = probability;
7878
}
7979

Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.actuate.autoconfigure.tracing;
17+
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
1818

1919
import brave.handler.SpanHandler;
2020
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
@@ -27,6 +27,7 @@
2727
import zipkin2.reporter.brave.ZipkinSpanHandler;
2828
import zipkin2.reporter.urlconnection.URLConnectionSender;
2929

30+
import org.springframework.boot.actuate.autoconfigure.tracing.TracingProperties;
3031
import org.springframework.boot.autoconfigure.AutoConfiguration;
3132
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3233
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@@ -36,6 +37,7 @@
3637
import org.springframework.context.annotation.Bean;
3738
import org.springframework.context.annotation.Configuration;
3839
import org.springframework.context.annotation.Import;
40+
import org.springframework.web.client.RestTemplate;
3941

4042
/**
4143
* {@link EnableAutoConfiguration Auto-configuration} for Zipkin.
@@ -54,9 +56,16 @@ static class SenderConfiguration {
5456
@Bean
5557
@ConditionalOnMissingBean
5658
@ConditionalOnClass(URLConnectionSender.class)
57-
public Sender sender(TracingProperties properties) {
59+
public Sender urlConnectionSender(TracingProperties properties) {
5860
return URLConnectionSender.newBuilder().endpoint(properties.getZipkin().getEndpoint()).build();
5961
}
62+
63+
@Bean
64+
@ConditionalOnMissingBean
65+
@ConditionalOnClass(RestTemplate.class)
66+
public Sender restTemplateSender(TracingProperties properties) {
67+
return new ZipkinRestTemplateSender(properties.getZipkin().getEndpoint(), new RestTemplate());
68+
}
6069
}
6170

6271
@Configuration(proxyBeanMethods = false)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
/*
2+
* Copyright 2012-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
18+
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.IOException;
21+
import java.util.List;
22+
import java.util.zip.GZIPOutputStream;
23+
24+
import zipkin2.Call;
25+
import zipkin2.Callback;
26+
import zipkin2.CheckResult;
27+
import zipkin2.codec.Encoding;
28+
import zipkin2.reporter.BytesMessageEncoder;
29+
import zipkin2.reporter.ClosedSenderException;
30+
import zipkin2.reporter.Sender;
31+
32+
import org.springframework.http.HttpEntity;
33+
import org.springframework.http.HttpHeaders;
34+
import org.springframework.http.HttpMethod;
35+
import org.springframework.web.client.RestTemplate;
36+
37+
/**
38+
* A Zipkin {@link Sender} which uses {@link RestTemplate} for HTTP communication.
39+
* Supports automatic compression with gzip.
40+
*
41+
* @author Moritz Halbritter
42+
*/
43+
class ZipkinRestTemplateSender extends Sender {
44+
45+
private static final int MESSAGE_MAX_BYTES = 500_000;
46+
47+
private final String endpoint;
48+
49+
private final RestTemplate restTemplate;
50+
51+
private volatile boolean closed;
52+
53+
ZipkinRestTemplateSender(String endpoint, RestTemplate restTemplate) {
54+
this.endpoint = endpoint;
55+
this.restTemplate = restTemplate;
56+
}
57+
58+
@Override
59+
public Encoding encoding() {
60+
return Encoding.JSON;
61+
}
62+
63+
@Override
64+
public int messageMaxBytes() {
65+
return MESSAGE_MAX_BYTES;
66+
}
67+
68+
@Override
69+
public int messageSizeInBytes(List<byte[]> encodedSpans) {
70+
return encoding().listSizeInBytes(encodedSpans);
71+
}
72+
73+
@Override
74+
public int messageSizeInBytes(int encodedSizeInBytes) {
75+
return encoding().listSizeInBytes(encodedSizeInBytes);
76+
}
77+
78+
@Override
79+
public Call<Void> sendSpans(List<byte[]> encodedSpans) {
80+
if (this.closed) {
81+
throw new ClosedSenderException();
82+
}
83+
return new HttpCall(this.endpoint, BytesMessageEncoder.JSON.encode(encodedSpans), this.restTemplate);
84+
}
85+
86+
@Override
87+
public CheckResult check() {
88+
try {
89+
sendSpans(List.of()).execute();
90+
return CheckResult.OK;
91+
}
92+
catch (IOException | RuntimeException e) {
93+
return CheckResult.failed(e);
94+
}
95+
}
96+
97+
@Override
98+
public void close() throws IOException {
99+
this.closed = true;
100+
}
101+
102+
private static class HttpCall extends Call.Base<Void> {
103+
104+
/**
105+
* Only use gzip compression on data which is bigger than this in bytes.
106+
*/
107+
private static final int COMPRESSION_THRESHOLD = 1024;
108+
109+
private final String endpoint;
110+
111+
private final byte[] body;
112+
113+
private final RestTemplate restTemplate;
114+
115+
public HttpCall(String endpoint, byte[] body, RestTemplate restTemplate) {
116+
this.endpoint = endpoint;
117+
this.body = body;
118+
this.restTemplate = restTemplate;
119+
}
120+
121+
@Override
122+
protected Void doExecute() throws IOException {
123+
HttpHeaders headers = new HttpHeaders();
124+
headers.set("b3", "0");
125+
headers.set("Content-Type", "application/json");
126+
byte[] body;
127+
if (needsCompression(this.body)) {
128+
headers.set("Content-Encoding", "gzip");
129+
body = compress(this.body);
130+
}
131+
else {
132+
body = this.body;
133+
}
134+
HttpEntity<byte[]> request = new HttpEntity<>(body, headers);
135+
this.restTemplate.exchange(this.endpoint, HttpMethod.POST, request, Void.class);
136+
return null;
137+
}
138+
139+
private boolean needsCompression(byte[] body) {
140+
return body.length > COMPRESSION_THRESHOLD;
141+
}
142+
143+
@Override
144+
protected void doEnqueue(Callback<Void> callback) {
145+
try {
146+
doExecute();
147+
callback.onSuccess(null);
148+
}
149+
catch (IOException | RuntimeException e) {
150+
callback.onError(e);
151+
}
152+
}
153+
154+
@Override
155+
public Call<Void> clone() {
156+
return new HttpCall(this.endpoint, this.body, this.restTemplate);
157+
}
158+
159+
private byte[] compress(byte[] input) throws IOException {
160+
ByteArrayOutputStream result = new ByteArrayOutputStream();
161+
try (GZIPOutputStream gzip = new GZIPOutputStream(result)) {
162+
gzip.write(input);
163+
}
164+
return result.toByteArray();
165+
}
166+
167+
}
168+
169+
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ org.springframework.boot.actuate.autoconfigure.trace.http.HttpTraceEndpointAutoC
9898
org.springframework.boot.actuate.autoconfigure.tracing.BraveAutoConfiguration
9999
org.springframework.boot.actuate.autoconfigure.tracing.MicrometerTracingAutoConfiguration
100100
org.springframework.boot.actuate.autoconfigure.tracing.OpenTelemetryAutoConfiguration
101-
org.springframework.boot.actuate.autoconfigure.tracing.ZipkinAutoConfiguration
102101
org.springframework.boot.actuate.autoconfigure.tracing.wavefront.WavefrontAutoConfiguration
102+
org.springframework.boot.actuate.autoconfigure.tracing.zipkin.ZipkinAutoConfiguration
103103
org.springframework.boot.actuate.autoconfigure.web.mappings.MappingsEndpointAutoConfiguration
104104
org.springframework.boot.actuate.autoconfigure.web.reactive.ReactiveManagementContextAutoConfiguration
105105
org.springframework.boot.actuate.autoconfigure.web.server.ManagementContextAutoConfiguration
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.actuate.autoconfigure.tracing;
17+
package org.springframework.boot.actuate.autoconfigure.tracing.zipkin;
1818

1919
import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter;
2020
import org.junit.jupiter.api.Test;
2121
import zipkin2.reporter.Sender;
2222
import zipkin2.reporter.brave.ZipkinSpanHandler;
23+
import zipkin2.reporter.urlconnection.URLConnectionSender;
2324

2425
import org.springframework.boot.autoconfigure.AutoConfigurations;
2526
import org.springframework.boot.test.context.FilteredClassLoader;
@@ -45,7 +46,26 @@ void shouldNotFailIfZipkinIsMissing() {
4546

4647
@Test
4748
void shouldNotFailIfZipkinUrlConnectionIsMissing() {
48-
this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection"))
49+
this.contextRunner.withClassLoader(new FilteredClassLoader("zipkin2.reporter.urlconnection")).run((context) -> {
50+
assertThat(context).doesNotHaveBean(URLConnectionSender.class);
51+
assertThat(context).hasSingleBean(ZipkinRestTemplateSender.class);
52+
});
53+
}
54+
55+
@Test
56+
void shouldNotFailIfRestTemplateIsMissing() {
57+
this.contextRunner.withClassLoader(new FilteredClassLoader("org.springframework.web.client.RestTemplate"))
58+
.run((context) -> {
59+
assertThat(context).doesNotHaveBean(ZipkinRestTemplateSender.class);
60+
assertThat(context).hasSingleBean(URLConnectionSender.class);
61+
});
62+
}
63+
64+
@Test
65+
void shouldNotFailIfAllSendersAreMissing() {
66+
this.contextRunner
67+
.withClassLoader(
68+
new FilteredClassLoader("org.springframework.web.client", "zipkin2.reporter.urlconnection"))
4969
.run((context) -> assertThat(context).doesNotHaveBean(Sender.class));
5070
}
5171

0 commit comments

Comments
 (0)