Skip to content

Commit 6f89edc

Browse files
authored
Re-add OpenCensus integration (#545)
* Revert "Revert "Add OpenCensus tracing instrument."" This reverts commit 9dd0d1c. * Revert "Revert "Enhance OpenCensus tracing instrumentation."" This reverts commit 36280df. * Update opencensus version and use dependencyManagement section * Fix missing import * Set span attributes * Fix checkstyle and NPE for missing attributes
1 parent 2635d5a commit 6f89edc

File tree

5 files changed

+565
-2
lines changed

5 files changed

+565
-2
lines changed

google-http-client/pom.xml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,14 @@
175175
<dependency>
176176
<groupId>com.google.j2objc</groupId>
177177
<artifactId>j2objc-annotations</artifactId>
178-
<version>1.1</version>
178+
</dependency>
179+
<dependency>
180+
<groupId>io.opencensus</groupId>
181+
<artifactId>opencensus-api</artifactId>
182+
</dependency>
183+
<dependency>
184+
<groupId>io.opencensus</groupId>
185+
<artifactId>opencensus-contrib-http-util</artifactId>
179186
</dependency>
180187
</dependencies>
181188
</project>

google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,18 @@
1818
import com.google.api.client.util.IOUtils;
1919
import com.google.api.client.util.LoggingStreamingContent;
2020
import com.google.api.client.util.ObjectParser;
21+
import com.google.api.client.util.OpenCensusUtils;
2122
import com.google.api.client.util.Preconditions;
2223
import com.google.api.client.util.Sleeper;
2324
import com.google.api.client.util.StreamingContent;
2425
import com.google.api.client.util.StringUtils;
2526

27+
import io.opencensus.common.Scope;
28+
import io.opencensus.contrib.http.util.HttpTraceAttributeConstants;
29+
import io.opencensus.trace.AttributeValue;
30+
import io.opencensus.trace.Span;
31+
import io.opencensus.trace.Tracer;
32+
2633
import java.io.IOException;
2734
import java.io.InputStream;
2835
import java.util.concurrent.Callable;
@@ -215,6 +222,9 @@ static String executeAndGetValueOfSomeCustomHeader(HttpRequest request) {
215222
/** Sleeper. */
216223
private Sleeper sleeper = Sleeper.DEFAULT;
217224

225+
/** OpenCensus tracing component. */
226+
private Tracer tracer = OpenCensusUtils.getTracer();
227+
218228
/**
219229
* @param transport HTTP transport
220230
* @param requestMethod HTTP request method or {@code null} for none
@@ -883,7 +893,12 @@ public HttpResponse execute() throws IOException {
883893
Preconditions.checkNotNull(requestMethod);
884894
Preconditions.checkNotNull(url);
885895

896+
Span span = tracer
897+
.spanBuilder(OpenCensusUtils.SPAN_NAME_HTTP_REQUEST_EXECUTE)
898+
.setRecordEvents(OpenCensusUtils.isRecordEvent())
899+
.startSpan();
886900
do {
901+
span.addAnnotation("retry #" + (numRetries - retriesRemaining));
887902
// Cleanup any unneeded response from a previous iteration
888903
if (response != null) {
889904
response.ignore();
@@ -898,6 +913,11 @@ public HttpResponse execute() throws IOException {
898913
}
899914
// build low-level HTTP request
900915
String urlString = url.build();
916+
addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_METHOD, requestMethod);
917+
addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_HOST, url.getHost());
918+
addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_PATH, url.getRawPath());
919+
addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_URL, urlString);
920+
901921
LowLevelHttpRequest lowLevelHttpRequest = transport.buildRequest(requestMethod, urlString);
902922
Logger logger = HttpTransport.LOGGER;
903923
boolean loggable = loggingEnabled && logger.isLoggable(Level.CONFIG);
@@ -923,10 +943,15 @@ public HttpResponse execute() throws IOException {
923943
if (!suppressUserAgentSuffix) {
924944
if (originalUserAgent == null) {
925945
headers.setUserAgent(USER_AGENT_SUFFIX);
946+
addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_USER_AGENT, USER_AGENT_SUFFIX);
926947
} else {
927-
headers.setUserAgent(originalUserAgent + " " + USER_AGENT_SUFFIX);
948+
String newUserAgent = originalUserAgent + " " + USER_AGENT_SUFFIX;
949+
headers.setUserAgent(newUserAgent);
950+
addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_USER_AGENT, newUserAgent);
928951
}
929952
}
953+
OpenCensusUtils.propagateTracingContext(span, headers);
954+
930955
// headers
931956
HttpHeaders.serializeHeaders(headers, logbuf, curlbuf, logger, lowLevelHttpRequest);
932957
if (!suppressUserAgentSuffix) {
@@ -1007,8 +1032,16 @@ public HttpResponse execute() throws IOException {
10071032
// execute
10081033
lowLevelHttpRequest.setTimeout(connectTimeout, readTimeout);
10091034
lowLevelHttpRequest.setWriteTimeout(writeTimeout);
1035+
1036+
// switch tracing scope to current span
1037+
@SuppressWarnings("MustBeClosedChecker")
1038+
Scope ws = tracer.withSpan(span);
1039+
OpenCensusUtils.recordSentMessageEvent(span, lowLevelHttpRequest.getContentLength());
10101040
try {
10111041
LowLevelHttpResponse lowLevelHttpResponse = lowLevelHttpRequest.execute();
1042+
if (lowLevelHttpResponse != null) {
1043+
OpenCensusUtils.recordReceivedMessageEvent(span, lowLevelHttpResponse.getContentLength());
1044+
}
10121045
// Flag used to indicate if an exception is thrown before the response is constructed.
10131046
boolean responseConstructed = false;
10141047
try {
@@ -1032,6 +1065,8 @@ public HttpResponse execute() throws IOException {
10321065
if (loggable) {
10331066
logger.log(Level.WARNING, "exception thrown while executing request", e);
10341067
}
1068+
} finally {
1069+
ws.close();
10351070
}
10361071

10371072
// Flag used to indicate if an exception is thrown before the response has completed
@@ -1087,6 +1122,7 @@ public HttpResponse execute() throws IOException {
10871122
}
10881123
}
10891124
} while (retryRequest);
1125+
span.end(OpenCensusUtils.getEndSpanOptions(response == null ? null : response.getStatusCode()));
10901126

10911127
if (response == null) {
10921128
// Retries did not help resolve the execute exception, re-throw it.
@@ -1201,4 +1237,10 @@ public HttpRequest setSleeper(Sleeper sleeper) {
12011237
this.sleeper = Preconditions.checkNotNull(sleeper);
12021238
return this;
12031239
}
1240+
1241+
private static void addSpanAttribute(Span span, String key, String value) {
1242+
if (value != null) {
1243+
span.putAttribute(key, AttributeValue.stringAttributeValue(value));
1244+
}
1245+
}
12041246
}
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
/*
2+
* Copyright (c) 2018 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.api.client.util;
16+
17+
import com.google.api.client.http.HttpHeaders;
18+
import com.google.api.client.http.HttpRequest;
19+
import com.google.api.client.http.HttpStatusCodes;
20+
import com.google.common.annotations.VisibleForTesting;
21+
22+
import io.opencensus.contrib.http.util.HttpPropagationUtil;
23+
import io.opencensus.trace.BlankSpan;
24+
import io.opencensus.trace.EndSpanOptions;
25+
import io.opencensus.trace.NetworkEvent;
26+
import io.opencensus.trace.NetworkEvent.Type;
27+
import io.opencensus.trace.Span;
28+
import io.opencensus.trace.Status;
29+
import io.opencensus.trace.Tracer;
30+
import io.opencensus.trace.Tracing;
31+
import io.opencensus.trace.propagation.TextFormat;
32+
33+
import java.util.Collections;
34+
import java.util.concurrent.atomic.AtomicLong;
35+
import java.util.logging.Level;
36+
import java.util.logging.Logger;
37+
import javax.annotation.Nullable;
38+
39+
/**
40+
* Utilities for Census monitoring and tracing.
41+
*
42+
* @author Hailong Wen
43+
* @since 1.24
44+
*/
45+
public class OpenCensusUtils {
46+
47+
private static final Logger logger = Logger.getLogger(OpenCensusUtils.class.getName());
48+
49+
/**
50+
* Span name for tracing {@link HttpRequest#execute()}.
51+
*/
52+
public static final String SPAN_NAME_HTTP_REQUEST_EXECUTE =
53+
"Sent." + HttpRequest.class.getName() + ".execute";
54+
55+
/**
56+
* OpenCensus tracing component. When no OpenCensus implementation is provided, it will return a
57+
* no-op tracer.
58+
*/
59+
private static Tracer tracer = Tracing.getTracer();
60+
61+
/**
62+
* Sequence id generator for message event.
63+
*/
64+
private static AtomicLong idGenerator = new AtomicLong();
65+
66+
/**
67+
* Whether spans should be recorded locally. Defaults to true.
68+
*/
69+
private static volatile boolean isRecordEvent = true;
70+
71+
/**
72+
* {@link TextFormat} used in tracing context propagation.
73+
*/
74+
@Nullable
75+
@VisibleForTesting
76+
static volatile TextFormat propagationTextFormat = null;
77+
78+
/**
79+
* {@link TextFormat.Setter} for {@link #propagationTextFormat}.
80+
*/
81+
@Nullable
82+
@VisibleForTesting
83+
static volatile TextFormat.Setter propagationTextFormatSetter = null;
84+
85+
/**
86+
* Sets the {@link TextFormat} used in context propagation.
87+
*
88+
* <p>This API allows users of google-http-client to specify other text format, or disable context
89+
* propagation by setting it to {@code null}. It should be used along with {@link
90+
* #setPropagationTextFormatSetter} for setting purpose. </p>
91+
*
92+
* @param textFormat the text format.
93+
*/
94+
public static void setPropagationTextFormat(@Nullable TextFormat textFormat) {
95+
propagationTextFormat = textFormat;
96+
}
97+
98+
/**
99+
* Sets the {@link TextFormat.Setter} used in context propagation.
100+
*
101+
* <p>This API allows users of google-http-client to specify other text format setter, or disable
102+
* context propagation by setting it to {@code null}. It should be used along with {@link
103+
* #setPropagationTextFormat} for setting purpose. </p>
104+
*
105+
* @param textFormatSetter the {@code TextFormat.Setter} for the text format.
106+
*/
107+
public static void setPropagationTextFormatSetter(@Nullable TextFormat.Setter textFormatSetter) {
108+
propagationTextFormatSetter = textFormatSetter;
109+
}
110+
111+
/**
112+
* Sets whether spans should be recorded locally.
113+
*
114+
* <p> This API allows users of google-http-client to turn on/off local span collection. </p>
115+
*
116+
* @param recordEvent record span locally if true.
117+
*/
118+
public static void setIsRecordEvent(boolean recordEvent) {
119+
isRecordEvent = recordEvent;
120+
}
121+
122+
/**
123+
* Returns the tracing component of OpenCensus.
124+
*
125+
* @return the tracing component of OpenCensus.
126+
*/
127+
public static Tracer getTracer() {
128+
return tracer;
129+
}
130+
131+
/**
132+
* Returns whether spans should be recorded locally.
133+
*
134+
* @return whether spans should be recorded locally.
135+
*/
136+
public static boolean isRecordEvent() {
137+
return isRecordEvent;
138+
}
139+
140+
/**
141+
* Propagate information of current tracing context. This information will be injected into HTTP
142+
* header.
143+
*
144+
* @param span the span to be propagated.
145+
* @param headers the headers used in propagation.
146+
*/
147+
public static void propagateTracingContext(Span span, HttpHeaders headers) {
148+
Preconditions.checkArgument(span != null, "span should not be null.");
149+
Preconditions.checkArgument(headers != null, "headers should not be null.");
150+
if (propagationTextFormat != null && propagationTextFormatSetter != null) {
151+
if (!span.equals(BlankSpan.INSTANCE)) {
152+
propagationTextFormat.inject(span.getContext(), headers, propagationTextFormatSetter);
153+
}
154+
}
155+
}
156+
157+
/**
158+
* Returns an {@link EndSpanOptions} to end a http span according to the status code.
159+
*
160+
* @param statusCode the status code, can be null to represent no valid response is returned.
161+
* @return an {@code EndSpanOptions} that best suits the status code.
162+
*/
163+
public static EndSpanOptions getEndSpanOptions(@Nullable Integer statusCode) {
164+
// Always sample the span, but optionally export it.
165+
EndSpanOptions.Builder builder = EndSpanOptions.builder();
166+
if (statusCode == null) {
167+
builder.setStatus(Status.UNKNOWN);
168+
} else if (!HttpStatusCodes.isSuccess(statusCode)) {
169+
switch (statusCode) {
170+
case HttpStatusCodes.STATUS_CODE_BAD_REQUEST:
171+
builder.setStatus(Status.INVALID_ARGUMENT);
172+
break;
173+
case HttpStatusCodes.STATUS_CODE_UNAUTHORIZED:
174+
builder.setStatus(Status.UNAUTHENTICATED);
175+
break;
176+
case HttpStatusCodes.STATUS_CODE_FORBIDDEN:
177+
builder.setStatus(Status.PERMISSION_DENIED);
178+
break;
179+
case HttpStatusCodes.STATUS_CODE_NOT_FOUND:
180+
builder.setStatus(Status.NOT_FOUND);
181+
break;
182+
case HttpStatusCodes.STATUS_CODE_PRECONDITION_FAILED:
183+
builder.setStatus(Status.FAILED_PRECONDITION);
184+
break;
185+
case HttpStatusCodes.STATUS_CODE_SERVER_ERROR:
186+
builder.setStatus(Status.UNAVAILABLE);
187+
break;
188+
default:
189+
builder.setStatus(Status.UNKNOWN);
190+
}
191+
} else {
192+
builder.setStatus(Status.OK);
193+
}
194+
return builder.build();
195+
}
196+
197+
/**
198+
* Records a new message event which contains the size of the request content. Note that the size
199+
* represents the message size in application layer, i.e., content-length.
200+
*
201+
* @param span The {@code span} in which the send event occurs.
202+
* @param size Size of the request.
203+
*/
204+
public static void recordSentMessageEvent(Span span, long size) {
205+
recordMessageEvent(span, size, Type.SENT);
206+
}
207+
208+
/**
209+
* Records a new message event which contains the size of the response content. Note that the size
210+
* represents the message size in application layer, i.e., content-length.
211+
*
212+
* @param span The {@code span} in which the receive event occurs.
213+
* @param size Size of the response.
214+
*/
215+
public static void recordReceivedMessageEvent(Span span, long size) {
216+
recordMessageEvent(span, size, Type.RECV);
217+
}
218+
219+
/**
220+
* Records a message event of a certain {@link NetworkEvent.Type}. This method is package
221+
* protected since {@link NetworkEvent} might be deprecated in future releases.
222+
*
223+
* @param span The {@code span} in which the event occurs.
224+
* @param size Size of the message.
225+
* @param eventType The {@code NetworkEvent.Type} of the message event.
226+
*/
227+
@VisibleForTesting
228+
static void recordMessageEvent(Span span, long size, Type eventType) {
229+
Preconditions.checkArgument(span != null, "span should not be null.");
230+
if (size < 0) {
231+
size = 0;
232+
}
233+
NetworkEvent event = NetworkEvent
234+
.builder(eventType, idGenerator.getAndIncrement())
235+
.setUncompressedMessageSize(size)
236+
.build();
237+
span.addNetworkEvent(event);
238+
}
239+
240+
static {
241+
try {
242+
propagationTextFormat = HttpPropagationUtil.getCloudTraceFormat();
243+
propagationTextFormatSetter = new TextFormat.Setter<HttpHeaders>() {
244+
@Override
245+
public void put(HttpHeaders carrier, String key, String value) {
246+
carrier.set(key, value);
247+
}
248+
};
249+
} catch (Exception e) {
250+
logger.log(
251+
Level.WARNING, "Cannot initialize default OpenCensus HTTP propagation text format.", e);
252+
}
253+
254+
try {
255+
Tracing.getExportComponent().getSampledSpanStore().registerSpanNamesForCollection(
256+
Collections.<String>singletonList(SPAN_NAME_HTTP_REQUEST_EXECUTE));
257+
} catch (Exception e) {
258+
logger.log(
259+
Level.WARNING, "Cannot register default OpenCensus span names for collection.", e);
260+
}
261+
}
262+
263+
private OpenCensusUtils() {}
264+
}

0 commit comments

Comments
 (0)