Skip to content
This repository was archived by the owner on Dec 23, 2023. It is now read-only.

Commit bfe1ad3

Browse files
authored
add instrumentation for spring AsyncRestTemplate. (#1919)
* add support for spring Web Async Client instrumentation. * fix review comments. * fix build error. * define explicit library version in build.gradle in root dir. * remove this. * consolidate version string and remove commented code.
1 parent 72d915c commit bfe1ad3

8 files changed

Lines changed: 514 additions & 7 deletions

File tree

build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ subprojects {
168168
googleCloudGaVersion = '1.65.0'
169169
log4j2Version = '2.11.1'
170170
signalfxVersion = '0.0.48'
171+
springBoot2Version = '2.1.5.RELEASE'
171172
springBootVersion = '1.5.15.RELEASE'
172173
springBootTestVersion = '2.1.1.RELEASE'
173174
springCloudVersion = '1.3.4.RELEASE'
@@ -180,6 +181,8 @@ subprojects {
180181
gsonVersion = '2.8.5'
181182
dropwizardVersion = '3.1.2'
182183
dropwizard5Version = '5.0.0'
184+
javaxServletVersion = "3.1.0"
185+
httpcomponentsVersion = "4.5.8"
183186

184187
libraries = [
185188
appengine_api: "com.google.appengine:appengine-api-1.0-sdk:${appengineVersion}",
@@ -210,6 +213,7 @@ subprojects {
210213
signalfx_java: "com.signalfx.public:signalfx-java:${signalfxVersion}",
211214
spring_aspects: "org.springframework:spring-aspects:${springVersion}",
212215
spring_boot_starter_web: "org.springframework.boot:spring-boot-starter-web:${springBootVersion}",
216+
spring_boot_starter_web2: "org.springframework.boot:spring-boot-starter-web:${springBoot2Version}",
213217
spring_cloud_build: "org.springframework.cloud:spring-cloud-build:${springCloudVersion}",
214218
spring_cloud_starter_sleuth: "org.springframework.cloud:spring-cloud-starter-sleuth:${springCloudVersion}",
215219
spring_context: "org.springframework:spring-context:${springVersion}",
@@ -218,6 +222,7 @@ subprojects {
218222
protobuf: "com.google.protobuf:protobuf-java:${protobufVersion}",
219223
opencensus_proto: "io.opencensus:opencensus-proto:${opencensusProtoVersion}",
220224
gson: "com.google.code.gson:gson:${gsonVersion}",
225+
httpcomponents: "org.apache.httpcomponents:httpclient:${httpcomponentsVersion}",
221226

222227
// Test dependencies.
223228
guava_testlib: "com.google.guava:guava-testlib:${guavaVersion}",
@@ -226,8 +231,12 @@ subprojects {
226231
spring_test: "org.springframework:spring-test:${springVersion}",
227232
truth: 'com.google.truth:truth:0.44',
228233
spring_boot_test: "org.springframework.boot:spring-boot-starter-test:${springBootTestVersion}",
234+
spring_boot_test2: "org.springframework.boot:spring-boot-starter-test:${springBoot2Version}",
229235
dropwizard: "io.dropwizard.metrics:metrics-core:${dropwizardVersion}",
230236
dropwizard5: "io.dropwizard.metrics5:metrics-core:${dropwizard5Version}",
237+
sprint_boot_starter_tomcat: "org.springframework.boot:spring-boot-starter-tomcat:${springBoot2Version}",
238+
javax_servlet: "javax.servlet:javax.servlet-api:${javaxServletVersion}",
239+
231240
]
232241
}
233242

buildscripts/import-control.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ General guidelines on imports:
136136
<subpackage name="spring">
137137
<allow pkg="edu.umd.cs.findbugs.annotations"/>
138138
<allow pkg="io.opencensus.trace"/>
139+
<allow pkg="io.opencensus.contrib.http"/>
139140
<allow pkg="io.opencensus.contrib.http.servlet"/>
140141
<allow pkg="io.opencensus.contrib.spring"/>
141142
<allow pkg="org.aspectj.lang"/>
@@ -147,7 +148,10 @@ General guidelines on imports:
147148
<allow pkg="org.springframework.boot.context"/>
148149
<allow pkg="org.springframework.context.annotation"/>
149150
<allow pkg="org.springframework.core"/>
151+
<allow pkg="org.springframework.http"/>
150152
<allow pkg="org.springframework.stereotype"/>
153+
<allow pkg="org.springframework.util.concurrent"/>
154+
<allow pkg="org.springframework.web.client"/>
151155
<subpackage name="sleuth">
152156
<allow pkg="io.opencensus.trace"/>
153157
<allow pkg="org.apache.commons.logging"/>

contrib/spring/build.gradle

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ description = 'OpenCensus Spring'
33
apply plugin: 'java'
44

55
[compileJava, compileTestJava].each() {
6-
it.sourceCompatibility = 1.6
7-
it.targetCompatibility = 1.6
6+
it.sourceCompatibility = 1.8
7+
it.targetCompatibility = 1.8
88
}
99

1010
dependencies {
@@ -13,15 +13,18 @@ dependencies {
1313
project(':opencensus-contrib-http-servlet'),
1414
libraries.spring_aspects,
1515
libraries.spring_context,
16-
libraries.spring_boot_starter_web,
1716
libraries.spring_cloud_build,
18-
libraries.findbugs_annotations
17+
libraries.findbugs_annotations,
18+
libraries.spring_boot_starter_web2,
19+
libraries.httpcomponents
1920

2021
testCompile project(':opencensus-impl'),
2122
project(':opencensus-testing'),
2223
libraries.aspectj,
2324
libraries.spring_test,
24-
libraries.spring_boot_test
25+
libraries.sprint_boot_starter_tomcat,
26+
libraries.spring_boot_test2,
27+
libraries.javax_servlet
2528

2629
signature "org.codehaus.mojo.signature:java17:1.0@signature"
2730
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright 2019, OpenCensus 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+
* http://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 io.opencensus.contrib.spring.autoconfig;
18+
19+
import io.opencensus.common.ExperimentalApi;
20+
import io.opencensus.contrib.spring.instrument.web.client.TracingAsyncClientHttpRequestInterceptor;
21+
import java.util.ArrayList;
22+
import java.util.Collection;
23+
import java.util.List;
24+
import javax.annotation.PostConstruct;
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
27+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
28+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
29+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
30+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
31+
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.ComponentScan;
33+
import org.springframework.context.annotation.Configuration;
34+
35+
/**
36+
* {@link org.springframework.boot.autoconfigure.EnableAutoConfiguration Auto-configuration} enables
37+
* span information propagation for {@link org.springframework.web.client.AsyncRestTemplate}.
38+
*
39+
* @since 0.23.0
40+
*/
41+
@Configuration
42+
@ComponentScan(basePackages = "io.opencensus")
43+
@ConditionalOnProperty(value = "opencensus.spring.enabled", matchIfMissing = true)
44+
@ConditionalOnClass(org.springframework.web.client.AsyncRestTemplate.class)
45+
@EnableConfigurationProperties(OpenCensusProperties.class)
46+
@AutoConfigureAfter(OpenCensusAutoConfiguration.class)
47+
@ExperimentalApi
48+
@SuppressWarnings("deprecation")
49+
public class TraceWebAsyncClientAutoConfiguration {
50+
@Configuration
51+
@ConditionalOnBean(org.springframework.web.client.AsyncRestTemplate.class)
52+
static class AsyncRestTemplateCfg {
53+
54+
@Bean
55+
public TracingAsyncClientHttpRequestInterceptor asyncTracingClientHttpRequestInterceptor() {
56+
return (TracingAsyncClientHttpRequestInterceptor)
57+
TracingAsyncClientHttpRequestInterceptor.create();
58+
}
59+
}
60+
61+
@Configuration
62+
protected static class TraceInterceptorConfiguration {
63+
64+
@Autowired(required = false)
65+
@SuppressWarnings("initialization.fields.uninitialized")
66+
private Collection<org.springframework.web.client.AsyncRestTemplate> restTemplates;
67+
68+
@Autowired
69+
@SuppressWarnings("initialization.fields.uninitialized")
70+
private TracingAsyncClientHttpRequestInterceptor clientInterceptor;
71+
72+
@PostConstruct
73+
public void init() {
74+
if (restTemplates != null) {
75+
for (org.springframework.web.client.AsyncRestTemplate restTemplate : restTemplates) {
76+
List<org.springframework.http.client.AsyncClientHttpRequestInterceptor> interceptors =
77+
new ArrayList<org.springframework.http.client.AsyncClientHttpRequestInterceptor>(
78+
restTemplate.getInterceptors());
79+
interceptors.add(clientInterceptor);
80+
restTemplate.setInterceptors(interceptors);
81+
}
82+
}
83+
}
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright 2019, OpenCensus 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+
* http://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 io.opencensus.contrib.spring.instrument.web.client;
18+
19+
import io.opencensus.common.ExperimentalApi;
20+
import io.opencensus.common.Scope;
21+
import io.opencensus.contrib.http.HttpClientHandler;
22+
import io.opencensus.contrib.http.HttpExtractor;
23+
import io.opencensus.contrib.http.HttpRequestContext;
24+
import io.opencensus.trace.Tracer;
25+
import io.opencensus.trace.Tracing;
26+
import io.opencensus.trace.propagation.TextFormat.Setter;
27+
import java.io.IOException;
28+
import java.net.MalformedURLException;
29+
import javax.annotation.Nullable;
30+
import org.springframework.http.HttpHeaders;
31+
import org.springframework.http.HttpMethod;
32+
import org.springframework.http.HttpRequest;
33+
import org.springframework.http.client.ClientHttpResponse;
34+
import org.springframework.util.concurrent.ListenableFuture;
35+
import org.springframework.util.concurrent.ListenableFutureCallback;
36+
37+
/**
38+
* This class intercepts {@link org.springframework.web.client.AsyncRestTemplate}.
39+
*
40+
* @since 0.23.0
41+
*/
42+
@SuppressWarnings("deprecation")
43+
public final class TracingAsyncClientHttpRequestInterceptor
44+
implements org.springframework.http.client.AsyncClientHttpRequestInterceptor {
45+
final Tracer tracer;
46+
final HttpClientHandler<HttpRequest, ClientHttpResponse, HttpRequest> handler;
47+
48+
private static final Setter<HttpRequest> setter =
49+
new Setter<HttpRequest>() {
50+
@Override
51+
public void put(HttpRequest carrier, String key, String value) {
52+
HttpHeaders hdrs = carrier.getHeaders();
53+
hdrs.set(key, value);
54+
}
55+
};
56+
57+
/**
58+
* Create an instance of {@code TracingAsyncClientHttpRequestInterceptor}.
59+
*
60+
* @return {@code TracingAsyncClientHttpRequestInterceptor}
61+
* @since 0.23.0
62+
*/
63+
public static TracingAsyncClientHttpRequestInterceptor create() {
64+
return new TracingAsyncClientHttpRequestInterceptor();
65+
}
66+
67+
TracingAsyncClientHttpRequestInterceptor() {
68+
69+
tracer = Tracing.getTracer();
70+
handler =
71+
new HttpClientHandler<HttpRequest, ClientHttpResponse, HttpRequest>(
72+
Tracing.getTracer(),
73+
new HttpClientExtractor(),
74+
Tracing.getPropagationComponent().getTraceContextFormat(),
75+
setter);
76+
}
77+
78+
/**
79+
* It intercepts http requests and starts a span.
80+
*
81+
* @since 0.23.0
82+
*/
83+
public ListenableFuture<ClientHttpResponse> intercept(
84+
HttpRequest request,
85+
byte[] body,
86+
org.springframework.http.client.AsyncClientHttpRequestExecution execution)
87+
throws IOException {
88+
HttpRequestContext context = handler.handleStart(tracer.getCurrentSpan(), request, request);
89+
90+
Scope ws = tracer.withSpan(handler.getSpanFromContext(context));
91+
try {
92+
ListenableFuture<ClientHttpResponse> result = execution.executeAsync(request, body);
93+
result.addCallback(
94+
new TracingAsyncClientHttpRequestInterceptor.TraceListenableFutureCallback(
95+
context, handler));
96+
return result;
97+
} catch (IOException e) {
98+
handler.handleEnd(context, null, null, e);
99+
throw e;
100+
} finally {
101+
if (ws != null) {
102+
ws.close();
103+
}
104+
}
105+
}
106+
107+
static final class TraceListenableFutureCallback
108+
implements ListenableFutureCallback<ClientHttpResponse> {
109+
HttpRequestContext context;
110+
final HttpClientHandler<HttpRequest, ClientHttpResponse, HttpRequest> handler;
111+
112+
TraceListenableFutureCallback(
113+
HttpRequestContext context,
114+
HttpClientHandler<HttpRequest, ClientHttpResponse, HttpRequest> handler) {
115+
this.context = context;
116+
this.handler = handler;
117+
}
118+
119+
public void onFailure(Throwable ex) {
120+
handler.handleEnd(context, null, null, ex);
121+
}
122+
123+
public void onSuccess(@Nullable ClientHttpResponse result) {
124+
handler.handleEnd(context, null, result, (Throwable) null);
125+
}
126+
}
127+
128+
/** This class extracts attributes from {@link HttpRequest} and {@link ClientHttpResponse}. */
129+
@ExperimentalApi
130+
static final class HttpClientExtractor extends HttpExtractor<HttpRequest, ClientHttpResponse> {
131+
@Override
132+
public String getHost(HttpRequest request) {
133+
return request.getURI().getHost();
134+
}
135+
136+
@Override
137+
@Nullable
138+
public String getMethod(HttpRequest request) {
139+
HttpMethod method = request.getMethod();
140+
if (method != null) {
141+
return method.toString();
142+
}
143+
return null;
144+
}
145+
146+
@Override
147+
public String getPath(HttpRequest request) {
148+
return request.getURI().getPath();
149+
}
150+
151+
@Override
152+
@Nullable
153+
public String getUserAgent(HttpRequest request) {
154+
return request.getHeaders().getFirst("User-Agent");
155+
}
156+
157+
@Override
158+
public int getStatusCode(@Nullable ClientHttpResponse response) {
159+
if (response != null) {
160+
try {
161+
return response.getStatusCode().value();
162+
} catch (Exception e) {
163+
return 0;
164+
}
165+
}
166+
return 0;
167+
}
168+
169+
@Override
170+
public String getRoute(HttpRequest request) {
171+
return "";
172+
}
173+
174+
@Override
175+
public String getUrl(HttpRequest request) {
176+
try {
177+
return request.getURI().toURL().toString();
178+
} catch (MalformedURLException e) {
179+
return "";
180+
}
181+
}
182+
}
183+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
# Auto Configuration
22
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
3-
io.opencensus.contrib.spring.autoconfig.OpenCensusAutoConfiguration
3+
io.opencensus.contrib.spring.autoconfig.OpenCensusAutoConfiguration,\
4+
io.opencensus.contrib.spring.autoconfig.TraceWebAsyncClientAutoConfiguration

0 commit comments

Comments
 (0)