From 6f088735e908ee75d6aeb471500ed8b818fef44f Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Tue, 11 Dec 2018 16:35:36 -0800 Subject: [PATCH 1/6] Revert "Revert "Add OpenCensus tracing instrument."" This reverts commit 9dd0d1c1479d9298cca14eccce39cedbae73bbad. --- google-http-client/pom.xml | 10 + .../google/api/client/http/HttpRequest.java | 22 ++ .../api/client/util/OpenCensusUtils.java | 157 ++++++++++++++ .../api/client/util/OpenCensusUtilsTest.java | 199 ++++++++++++++++++ 4 files changed, 388 insertions(+) create mode 100644 google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java create mode 100644 google-http-client/src/test/java/com/google/api/client/util/OpenCensusUtilsTest.java diff --git a/google-http-client/pom.xml b/google-http-client/pom.xml index 69bf450f4..177d0d865 100644 --- a/google-http-client/pom.xml +++ b/google-http-client/pom.xml @@ -177,5 +177,15 @@ j2objc-annotations 1.1 + + io.opencensus + opencensus-api + 0.11.1 + + + io.opencensus + opencensus-contrib-http-util + 0.11.1 + diff --git a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java index 856666a68..debeb3035 100644 --- a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java +++ b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java @@ -18,11 +18,16 @@ import com.google.api.client.util.IOUtils; import com.google.api.client.util.LoggingStreamingContent; import com.google.api.client.util.ObjectParser; +import com.google.api.client.util.OpenCensusUtils; import com.google.api.client.util.Preconditions; import com.google.api.client.util.Sleeper; import com.google.api.client.util.StreamingContent; import com.google.api.client.util.StringUtils; +import io.opencensus.common.Scope; +import io.opencensus.trace.Span; +import io.opencensus.trace.Tracer; + import java.io.IOException; import java.io.InputStream; import java.util.concurrent.Callable; @@ -215,6 +220,12 @@ static String executeAndGetValueOfSomeCustomHeader(HttpRequest request) { /** Sleeper. */ private Sleeper sleeper = Sleeper.DEFAULT; + /** OpenCensus tracing component. */ + private Tracer tracer = OpenCensusUtils.getTracer(); + + /** Prefix for tracing span name. */ + private static final String traceSpanNamePrefix = "Sent." + HttpRequest.class.getName() + "."; + /** * @param transport HTTP transport * @param requestMethod HTTP request method or {@code null} for none @@ -883,7 +894,9 @@ public HttpResponse execute() throws IOException { Preconditions.checkNotNull(requestMethod); Preconditions.checkNotNull(url); + Span span = tracer.spanBuilder(traceSpanNamePrefix + "execute").startSpan(); do { + span.addAnnotation("retry #" + numRetries); // Cleanup any unneeded response from a previous iteration if (response != null) { response.ignore(); @@ -927,6 +940,8 @@ public HttpResponse execute() throws IOException { headers.setUserAgent(originalUserAgent + " " + USER_AGENT_SUFFIX); } } + OpenCensusUtils.propagateTracingContext(headers); + // headers HttpHeaders.serializeHeaders(headers, logbuf, curlbuf, logger, lowLevelHttpRequest); if (!suppressUserAgentSuffix) { @@ -1007,6 +1022,10 @@ public HttpResponse execute() throws IOException { // execute lowLevelHttpRequest.setTimeout(connectTimeout, readTimeout); lowLevelHttpRequest.setWriteTimeout(writeTimeout); + + // switch tracing scope to current span + @SuppressWarnings("MustBeClosedChecker") + Scope ws = tracer.withSpan(span); try { LowLevelHttpResponse lowLevelHttpResponse = lowLevelHttpRequest.execute(); // Flag used to indicate if an exception is thrown before the response is constructed. @@ -1032,6 +1051,8 @@ public HttpResponse execute() throws IOException { if (loggable) { logger.log(Level.WARNING, "exception thrown while executing request", e); } + } finally { + ws.close(); } // Flag used to indicate if an exception is thrown before the response has completed @@ -1087,6 +1108,7 @@ public HttpResponse execute() throws IOException { } } } while (retryRequest); + span.end(OpenCensusUtils.getEndSpanOptions(response == null ? null : response.getStatusCode())); if (response == null) { // Retries did not help resolve the execute exception, re-throw it. diff --git a/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java b/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java new file mode 100644 index 000000000..836706021 --- /dev/null +++ b/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.api.client.util; + +import com.google.api.client.http.HttpHeaders; +import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpStatusCodes; + +import io.opencensus.contrib.http.util.HttpPropagationUtil; +import io.opencensus.trace.BlankSpan; +import io.opencensus.trace.EndSpanOptions; +import io.opencensus.trace.Span; +import io.opencensus.trace.Status; +import io.opencensus.trace.Tracer; +import io.opencensus.trace.Tracing; +import io.opencensus.trace.propagation.TextFormat; + +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; + +/** + * Utilities for Census monitoring and tracing. + * + * @since 1.24 + * @author Hailong Wen + */ +public class OpenCensusUtils { + + private static final Logger LOGGER = Logger.getLogger(OpenCensusUtils.class.getName()); + + /** + * OpenCensus tracing component. + * When no OpenCensus implementation is provided, it will return a no-op tracer. + */ + static Tracer tracer = Tracing.getTracer(); + + /** + * {@link TextFormat} used in tracing context propagation. + */ + @Nullable + static TextFormat propagationTextFormat = null; + + /** + * {@link TextFormat.Setter} for {@link activeTextFormat}. + */ + @Nullable + static TextFormat.Setter propagationTextFormatSetter = null; + + /** + * Sets the {@link TextFormat} used in context propagation. + * @param textFormat the text format. + */ + public static void setPropagationTextFormat(@Nullable TextFormat textFormat) { + propagationTextFormat = textFormat; + } + + /** + * Sets the {@link TextFormat.Setter} used in context propagation. + * @param textFormatSetter the {@code TextFormat.Setter} for the text format. + */ + public static void setPropagationTextFormatSetter(@Nullable TextFormat.Setter textFormatSetter) { + propagationTextFormatSetter = textFormatSetter; + } + + /** + * Returns the tracing component of OpenCensus. + * + * @return the tracing component of OpenCensus. + */ + public static Tracer getTracer() { + return tracer; + } + + /** + * Propagate information of current tracing context. This information will be injected into HTTP + * header. + */ + public static void propagateTracingContext(HttpHeaders headers) { + Preconditions.checkNotNull(headers); + if (propagationTextFormat != null && propagationTextFormatSetter != null) { + Span span = tracer.getCurrentSpan(); + if (span != null && !span.equals(BlankSpan.INSTANCE)) { + propagationTextFormat.inject(span.getContext(), headers, propagationTextFormatSetter); + } + } + } + + /** + * Returns an {@link EndSpanOptions} to end a http span according to the status code. + * + * @param statusCode the status code, can be null to represent no valid response is returned. + * @return an {@code EndSpanOptions} that best suits the status code. + */ + public static EndSpanOptions getEndSpanOptions(@Nullable Integer statusCode) { + // Always sample the span, but optionally export it. + EndSpanOptions.Builder builder = EndSpanOptions.builder().setSampleToLocalSpanStore(true); + if (statusCode == null) { + builder.setStatus(Status.UNKNOWN); + } else if (!HttpStatusCodes.isSuccess(statusCode)) { + switch (statusCode) { + case HttpStatusCodes.STATUS_CODE_BAD_REQUEST: + builder.setStatus(Status.INVALID_ARGUMENT); + break; + case HttpStatusCodes.STATUS_CODE_UNAUTHORIZED: + builder.setStatus(Status.UNAUTHENTICATED); + break; + case HttpStatusCodes.STATUS_CODE_FORBIDDEN: + builder.setStatus(Status.PERMISSION_DENIED); + break; + case HttpStatusCodes.STATUS_CODE_NOT_FOUND: + builder.setStatus(Status.NOT_FOUND); + break; + case HttpStatusCodes.STATUS_CODE_PRECONDITION_FAILED: + builder.setStatus(Status.FAILED_PRECONDITION); + break; + case HttpStatusCodes.STATUS_CODE_SERVER_ERROR: + builder.setStatus(Status.UNAVAILABLE); + break; + default: + builder.setStatus(Status.UNKNOWN); + } + } else { + builder.setStatus(Status.OK); + } + return builder.build(); + } + + static { + try { + propagationTextFormat = HttpPropagationUtil.getCloudTraceFormat(); + propagationTextFormatSetter = new TextFormat.Setter() { + @Override + public void put(HttpHeaders carrier, String key, String value) { + carrier.set(key, value); + } + }; + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Cannot initiate OpenCensus modules, tracing disabled", e); + } + } + + private OpenCensusUtils() {} +} diff --git a/google-http-client/src/test/java/com/google/api/client/util/OpenCensusUtilsTest.java b/google-http-client/src/test/java/com/google/api/client/util/OpenCensusUtilsTest.java new file mode 100644 index 000000000..7b75c0f01 --- /dev/null +++ b/google-http-client/src/test/java/com/google/api/client/util/OpenCensusUtilsTest.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2018 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.api.client.util; + +import com.google.api.client.http.HttpHeaders; + +import io.opencensus.common.Scope; +import io.opencensus.trace.EndSpanOptions; +import io.opencensus.trace.Span; +import io.opencensus.trace.SpanContext; +import io.opencensus.trace.Status; +import io.opencensus.trace.Tracer; +import io.opencensus.trace.propagation.TextFormat; +import java.util.List; +import junit.framework.TestCase; +import org.junit.Assert; +import org.junit.Rule; +import org.junit.rules.ExpectedException; + +/** + * Tests {@link OpenCensusUtils}. + * + * @author Hailong Wen + */ +public class OpenCensusUtilsTest extends TestCase { + + @Rule public ExpectedException thrown = ExpectedException.none(); + TextFormat mockTextFormat; + TextFormat.Setter mockTextFormatSetter; + HttpHeaders headers; + Tracer tracer; + + public OpenCensusUtilsTest(String testName) { + super(testName); + } + + @Override + public void setUp() { + TextFormat mockTextFormat = new TextFormat() { + @Override + public List fields() { + throw new UnsupportedOperationException("TextFormat.fields"); + } + + @Override + public void inject(SpanContext spanContext, C carrier, Setter setter) { + throw new UnsupportedOperationException("TextFormat.inject"); + } + + @Override + public SpanContext extract(C carrier, Getter getter) { + throw new UnsupportedOperationException("TextFormat.extract"); + } + }; + TextFormat.Setter mockTextFormatSetter = new TextFormat.Setter() { + @Override + public void put(HttpHeaders carrier, String key, String value) { + throw new UnsupportedOperationException("TextFormat.Setter.put"); + } + }; + headers = new HttpHeaders(); + tracer = OpenCensusUtils.getTracer(); + } + + public void testInitializatoin() { + assertNotNull(OpenCensusUtils.getTracer()); + assertNotNull(OpenCensusUtils.propagationTextFormat); + assertNotNull(OpenCensusUtils.propagationTextFormatSetter); + } + + public void testSetPropagationTextFormat() { + OpenCensusUtils.setPropagationTextFormat(mockTextFormat); + assertEquals(mockTextFormat, OpenCensusUtils.propagationTextFormat); + } + + public void testSetPropagationTextFormatSetter() { + OpenCensusUtils.setPropagationTextFormatSetter(mockTextFormatSetter); + assertEquals(mockTextFormatSetter, OpenCensusUtils.propagationTextFormatSetter); + } + + public void testPropagateTracingContextInjection() { + OpenCensusUtils.setPropagationTextFormat(mockTextFormat); + Span span = OpenCensusUtils.getTracer().spanBuilder("test").startSpan(); + Scope scope = OpenCensusUtils.getTracer().withSpan(span); + thrown.expect(UnsupportedOperationException.class); + thrown.expectMessage("TextFormat.inject"); + OpenCensusUtils.propagateTracingContext(headers); + scope.close(); + span.end(); + } + + public void testPropagateTracingContextHeader() { + OpenCensusUtils.setPropagationTextFormatSetter(mockTextFormatSetter); + Span span = OpenCensusUtils.getTracer().spanBuilder("test").startSpan(); + Scope scope = OpenCensusUtils.getTracer().withSpan(span); + thrown.expect(UnsupportedOperationException.class); + thrown.expectMessage("TextFormat.Setter.put"); + OpenCensusUtils.propagateTracingContext(headers); + scope.close(); + span.end(); + } + + public void testPropagateTracingContextInvalidSpan() { + OpenCensusUtils.setPropagationTextFormat(mockTextFormat); + // No injection. No exceptions should be thrown. + OpenCensusUtils.propagateTracingContext(headers); + } + + public void testGetEndSpanOptionsNoResponse() { + EndSpanOptions expected = + EndSpanOptions.builder().setSampleToLocalSpanStore(true).setStatus(Status.UNKNOWN).build(); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(null)); + } + + public void testGetEndSpanOptionsSuccess() { + EndSpanOptions expected = + EndSpanOptions.builder().setSampleToLocalSpanStore(true).setStatus(Status.OK).build(); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(200)); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(201)); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(202)); + } + + public void testGetEndSpanOptionsBadRequest() { + EndSpanOptions expected = EndSpanOptions + .builder() + .setSampleToLocalSpanStore(true) + .setStatus(Status.INVALID_ARGUMENT) + .build(); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(400)); + } + + public void testGetEndSpanOptionsUnauthorized() { + EndSpanOptions expected = EndSpanOptions + .builder() + .setSampleToLocalSpanStore(true) + .setStatus(Status.UNAUTHENTICATED) + .build(); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(401)); + } + + public void testGetEndSpanOptionsForbidden() { + EndSpanOptions expected = EndSpanOptions + .builder() + .setSampleToLocalSpanStore(true) + .setStatus(Status.PERMISSION_DENIED) + .build(); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(403)); + } + + public void testGetEndSpanOptionsNotFound() { + EndSpanOptions expected = EndSpanOptions + .builder() + .setSampleToLocalSpanStore(true) + .setStatus(Status.NOT_FOUND) + .build(); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(404)); + } + + public void testGetEndSpanOptionsPreconditionFailed() { + EndSpanOptions expected = EndSpanOptions + .builder() + .setSampleToLocalSpanStore(true) + .setStatus(Status.FAILED_PRECONDITION) + .build(); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(412)); + } + + public void testGetEndSpanOptionsServerError() { + EndSpanOptions expected = EndSpanOptions + .builder() + .setSampleToLocalSpanStore(true) + .setStatus(Status.UNAVAILABLE) + .build(); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(500)); + } + + public void testGetEndSpanOptionsOther() { + EndSpanOptions expected = EndSpanOptions.builder() + .setSampleToLocalSpanStore(true) + .setStatus(Status.UNKNOWN) + .build(); + // test some random unsupported statuses + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(301)); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(402)); + assertEquals(expected, OpenCensusUtils.getEndSpanOptions(501)); + } +} From 41651a9098bb8366d4036bf50496b9273e3aa107 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Tue, 11 Dec 2018 16:35:48 -0800 Subject: [PATCH 2/6] Revert "Revert "Enhance OpenCensus tracing instrumentation."" This reverts commit 36280df23e07ec4a062353f20866649559143d9c. --- .../google/api/client/http/HttpRequest.java | 16 +- .../api/client/util/OpenCensusUtils.java | 138 ++++++++++++++-- .../api/client/util/OpenCensusUtilsTest.java | 156 +++++++++++------- 3 files changed, 230 insertions(+), 80 deletions(-) diff --git a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java index debeb3035..e4befdfc2 100644 --- a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java +++ b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java @@ -223,9 +223,6 @@ static String executeAndGetValueOfSomeCustomHeader(HttpRequest request) { /** OpenCensus tracing component. */ private Tracer tracer = OpenCensusUtils.getTracer(); - /** Prefix for tracing span name. */ - private static final String traceSpanNamePrefix = "Sent." + HttpRequest.class.getName() + "."; - /** * @param transport HTTP transport * @param requestMethod HTTP request method or {@code null} for none @@ -894,9 +891,12 @@ public HttpResponse execute() throws IOException { Preconditions.checkNotNull(requestMethod); Preconditions.checkNotNull(url); - Span span = tracer.spanBuilder(traceSpanNamePrefix + "execute").startSpan(); + Span span = tracer + .spanBuilder(OpenCensusUtils.SPAN_NAME_HTTP_REQUEST_EXECUTE) + .setRecordEvents(OpenCensusUtils.isRecordEvent()) + .startSpan(); do { - span.addAnnotation("retry #" + numRetries); + span.addAnnotation("retry #" + (numRetries - retriesRemaining)); // Cleanup any unneeded response from a previous iteration if (response != null) { response.ignore(); @@ -940,7 +940,7 @@ public HttpResponse execute() throws IOException { headers.setUserAgent(originalUserAgent + " " + USER_AGENT_SUFFIX); } } - OpenCensusUtils.propagateTracingContext(headers); + OpenCensusUtils.propagateTracingContext(span, headers); // headers HttpHeaders.serializeHeaders(headers, logbuf, curlbuf, logger, lowLevelHttpRequest); @@ -1026,8 +1026,12 @@ public HttpResponse execute() throws IOException { // switch tracing scope to current span @SuppressWarnings("MustBeClosedChecker") Scope ws = tracer.withSpan(span); + OpenCensusUtils.recordSentMessageEvent(span, lowLevelHttpRequest.getContentLength()); try { LowLevelHttpResponse lowLevelHttpResponse = lowLevelHttpRequest.execute(); + if (lowLevelHttpResponse != null) { + OpenCensusUtils.recordReceivedMessageEvent(span, lowLevelHttpResponse.getContentLength()); + } // Flag used to indicate if an exception is thrown before the response is constructed. boolean responseConstructed = false; try { diff --git a/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java b/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java index 836706021..112612711 100644 --- a/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java +++ b/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java @@ -15,19 +15,22 @@ package com.google.api.client.util; import com.google.api.client.http.HttpHeaders; -import com.google.api.client.http.HttpResponse; +import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpStatusCodes; +import com.google.common.annotations.VisibleForTesting; import io.opencensus.contrib.http.util.HttpPropagationUtil; import io.opencensus.trace.BlankSpan; import io.opencensus.trace.EndSpanOptions; +import io.opencensus.trace.NetworkEvent; +import io.opencensus.trace.NetworkEvent.Type; import io.opencensus.trace.Span; import io.opencensus.trace.Status; import io.opencensus.trace.Tracer; import io.opencensus.trace.Tracing; import io.opencensus.trace.propagation.TextFormat; -import java.io.IOException; +import java.util.Collections; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -35,33 +38,56 @@ /** * Utilities for Census monitoring and tracing. * - * @since 1.24 * @author Hailong Wen + * @since 1.24 */ public class OpenCensusUtils { - private static final Logger LOGGER = Logger.getLogger(OpenCensusUtils.class.getName()); + private static final Logger logger = Logger.getLogger(OpenCensusUtils.class.getName()); + + /** + * Span name for tracing {@link HttpRequest#execute()}. + */ + public static final String SPAN_NAME_HTTP_REQUEST_EXECUTE = + "Sent." + HttpRequest.class.getName() + ".execute"; + + /** + * OpenCensus tracing component. When no OpenCensus implementation is provided, it will return a + * no-op tracer. + */ + private static Tracer tracer = Tracing.getTracer(); + + /** + * Sequence id generator for message event. + */ + private static AtomicLong idGenerator = new AtomicLong(); /** - * OpenCensus tracing component. - * When no OpenCensus implementation is provided, it will return a no-op tracer. + * Whether spans should be recorded locally. Defaults to true. */ - static Tracer tracer = Tracing.getTracer(); + private static volatile boolean isRecordEvent = true; /** * {@link TextFormat} used in tracing context propagation. */ @Nullable - static TextFormat propagationTextFormat = null; + @VisibleForTesting + static volatile TextFormat propagationTextFormat = null; /** - * {@link TextFormat.Setter} for {@link activeTextFormat}. + * {@link TextFormat.Setter} for {@link #propagationTextFormat}. */ @Nullable - static TextFormat.Setter propagationTextFormatSetter = null; + @VisibleForTesting + static volatile TextFormat.Setter propagationTextFormatSetter = null; /** * Sets the {@link TextFormat} used in context propagation. + * + *

This API allows users of google-http-client to specify other text format, or disable context + * propagation by setting it to {@code null}. It should be used along with {@link + * #setPropagationTextFormatSetter} for setting purpose.

+ * * @param textFormat the text format. */ public static void setPropagationTextFormat(@Nullable TextFormat textFormat) { @@ -70,12 +96,28 @@ public static void setPropagationTextFormat(@Nullable TextFormat textFormat) { /** * Sets the {@link TextFormat.Setter} used in context propagation. + * + *

This API allows users of google-http-client to specify other text format setter, or disable + * context propagation by setting it to {@code null}. It should be used along with {@link + * #setPropagationTextFormat} for setting purpose.

+ * * @param textFormatSetter the {@code TextFormat.Setter} for the text format. */ public static void setPropagationTextFormatSetter(@Nullable TextFormat.Setter textFormatSetter) { propagationTextFormatSetter = textFormatSetter; } + /** + * Sets whether spans should be recorded locally. + * + *

This API allows users of google-http-client to turn on/off local span collection.

+ * + * @param recordEvent record span locally if true. + */ + public static void setIsRecordEvent(boolean recordEvent) { + isRecordEvent = recordEvent; + } + /** * Returns the tracing component of OpenCensus. * @@ -85,15 +127,27 @@ public static Tracer getTracer() { return tracer; } + /** + * Returns whether spans should be recorded locally. + * + * @return whether spans should be recorded locally. + */ + public static boolean isRecordEvent() { + return isRecordEvent; + } + /** * Propagate information of current tracing context. This information will be injected into HTTP * header. + * + * @param span the span to be propagated. + * @param headers the headers used in propagation. */ - public static void propagateTracingContext(HttpHeaders headers) { - Preconditions.checkNotNull(headers); + public static void propagateTracingContext(Span span, HttpHeaders headers) { + Preconditions.checkArgument(span != null, "span should not be null."); + Preconditions.checkArgument(headers != null, "headers should not be null."); if (propagationTextFormat != null && propagationTextFormatSetter != null) { - Span span = tracer.getCurrentSpan(); - if (span != null && !span.equals(BlankSpan.INSTANCE)) { + if (!span.equals(BlankSpan.INSTANCE)) { propagationTextFormat.inject(span.getContext(), headers, propagationTextFormatSetter); } } @@ -107,7 +161,7 @@ public static void propagateTracingContext(HttpHeaders headers) { */ public static EndSpanOptions getEndSpanOptions(@Nullable Integer statusCode) { // Always sample the span, but optionally export it. - EndSpanOptions.Builder builder = EndSpanOptions.builder().setSampleToLocalSpanStore(true); + EndSpanOptions.Builder builder = EndSpanOptions.builder(); if (statusCode == null) { builder.setStatus(Status.UNKNOWN); } else if (!HttpStatusCodes.isSuccess(statusCode)) { @@ -139,6 +193,49 @@ public static EndSpanOptions getEndSpanOptions(@Nullable Integer statusCode) { return builder.build(); } + /** + * Records a new message event which contains the size of the request content. Note that the size + * represents the message size in application layer, i.e., content-length. + * + * @param span The {@code span} in which the send event occurs. + * @param size Size of the request. + */ + public static void recordSentMessageEvent(Span span, long size) { + recordMessageEvent(span, size, Type.SENT); + } + + /** + * Records a new message event which contains the size of the response content. Note that the size + * represents the message size in application layer, i.e., content-length. + * + * @param span The {@code span} in which the receive event occurs. + * @param size Size of the response. + */ + public static void recordReceivedMessageEvent(Span span, long size) { + recordMessageEvent(span, size, Type.RECV); + } + + /** + * Records a message event of a certain {@link NetowrkEvent.Type}. This method is package + * protected since {@link NetworkEvent} might be deprecated in future releases. + * + * @param span The {@code span} in which the event occurs. + * @param size Size of the message. + * @param eventType The {@code NetworkEvent.Type} of the message event. + */ + @VisibleForTesting + static void recordMessageEvent(Span span, long size, Type eventType) { + Preconditions.checkArgument(span != null, "span should not be null."); + if (size < 0) { + size = 0; + } + NetworkEvent event = NetworkEvent + .builder(eventType, idGenerator.getAndIncrement()) + .setUncompressedMessageSize(size) + .build(); + span.addNetworkEvent(event); + } + static { try { propagationTextFormat = HttpPropagationUtil.getCloudTraceFormat(); @@ -149,7 +246,16 @@ public void put(HttpHeaders carrier, String key, String value) { } }; } catch (Exception e) { - LOGGER.log(Level.WARNING, "Cannot initiate OpenCensus modules, tracing disabled", e); + logger.log( + Level.WARNING, "Cannot initialize default OpenCensus HTTP propagation text format.", e); + } + + try { + Tracing.getExportComponent().getSampledSpanStore().registerSpanNamesForCollection( + Collections.singletonList(SPAN_NAME_HTTP_REQUEST_EXECUTE)); + } catch (Exception e) { + logger.log( + Level.WARNING, "Cannot register default OpenCensus span names for collection.", e); } } diff --git a/google-http-client/src/test/java/com/google/api/client/util/OpenCensusUtilsTest.java b/google-http-client/src/test/java/com/google/api/client/util/OpenCensusUtilsTest.java index 7b75c0f01..069b051ec 100644 --- a/google-http-client/src/test/java/com/google/api/client/util/OpenCensusUtilsTest.java +++ b/google-http-client/src/test/java/com/google/api/client/util/OpenCensusUtilsTest.java @@ -16,18 +16,22 @@ import com.google.api.client.http.HttpHeaders; -import io.opencensus.common.Scope; +import io.opencensus.trace.BlankSpan; import io.opencensus.trace.EndSpanOptions; +import io.opencensus.trace.Annotation; +import io.opencensus.trace.AttributeValue; +import io.opencensus.trace.Link; +import io.opencensus.trace.NetworkEvent; import io.opencensus.trace.Span; import io.opencensus.trace.SpanContext; import io.opencensus.trace.Status; import io.opencensus.trace.Tracer; import io.opencensus.trace.propagation.TextFormat; import java.util.List; +import java.util.Map; import junit.framework.TestCase; import org.junit.Assert; import org.junit.Rule; -import org.junit.rules.ExpectedException; /** * Tests {@link OpenCensusUtils}. @@ -36,9 +40,11 @@ */ public class OpenCensusUtilsTest extends TestCase { - @Rule public ExpectedException thrown = ExpectedException.none(); TextFormat mockTextFormat; TextFormat.Setter mockTextFormatSetter; + TextFormat originTextFormat; + TextFormat.Setter originTextFormatSetter; + Span mockSpan; HttpHeaders headers; Tracer tracer; @@ -48,7 +54,7 @@ public OpenCensusUtilsTest(String testName) { @Override public void setUp() { - TextFormat mockTextFormat = new TextFormat() { + mockTextFormat = new TextFormat() { @Override public List fields() { throw new UnsupportedOperationException("TextFormat.fields"); @@ -64,7 +70,7 @@ public SpanContext extract(C carrier, Getter getter) { throw new UnsupportedOperationException("TextFormat.extract"); } }; - TextFormat.Setter mockTextFormatSetter = new TextFormat.Setter() { + mockTextFormatSetter = new TextFormat.Setter() { @Override public void put(HttpHeaders carrier, String key, String value) { throw new UnsupportedOperationException("TextFormat.Setter.put"); @@ -72,6 +78,33 @@ public void put(HttpHeaders carrier, String key, String value) { }; headers = new HttpHeaders(); tracer = OpenCensusUtils.getTracer(); + mockSpan = new Span(tracer.getCurrentSpan().getContext(), null) { + + @Override + public void addAnnotation(String description, Map attributes) {} + + @Override + public void addAnnotation(Annotation annotation) {} + + @Override + public void addNetworkEvent(NetworkEvent event) { + throw new UnsupportedOperationException("Span.addNetworkEvent"); + } + + @Override + public void addLink(Link link) {} + + @Override + public void end(EndSpanOptions options) {} + }; + originTextFormat = OpenCensusUtils.propagationTextFormat; + originTextFormatSetter = OpenCensusUtils.propagationTextFormatSetter; + } + + @Override + public void tearDown() { + OpenCensusUtils.setPropagationTextFormat(originTextFormat); + OpenCensusUtils.setPropagationTextFormatSetter(originTextFormatSetter); } public void testInitializatoin() { @@ -92,108 +125,115 @@ public void testSetPropagationTextFormatSetter() { public void testPropagateTracingContextInjection() { OpenCensusUtils.setPropagationTextFormat(mockTextFormat); - Span span = OpenCensusUtils.getTracer().spanBuilder("test").startSpan(); - Scope scope = OpenCensusUtils.getTracer().withSpan(span); - thrown.expect(UnsupportedOperationException.class); - thrown.expectMessage("TextFormat.inject"); - OpenCensusUtils.propagateTracingContext(headers); - scope.close(); - span.end(); + try { + OpenCensusUtils.propagateTracingContext(mockSpan, headers); + fail("expected " + UnsupportedOperationException.class); + } catch (UnsupportedOperationException e) { + assertEquals(e.getMessage(), "TextFormat.inject"); + } } public void testPropagateTracingContextHeader() { OpenCensusUtils.setPropagationTextFormatSetter(mockTextFormatSetter); - Span span = OpenCensusUtils.getTracer().spanBuilder("test").startSpan(); - Scope scope = OpenCensusUtils.getTracer().withSpan(span); - thrown.expect(UnsupportedOperationException.class); - thrown.expectMessage("TextFormat.Setter.put"); - OpenCensusUtils.propagateTracingContext(headers); - scope.close(); - span.end(); + try { + OpenCensusUtils.propagateTracingContext(mockSpan, headers); + fail("expected " + UnsupportedOperationException.class); + } catch (UnsupportedOperationException e) { + assertEquals(e.getMessage(), "TextFormat.Setter.put"); + } + } + + public void testPropagateTracingContextNullSpan() { + OpenCensusUtils.setPropagationTextFormat(mockTextFormat); + try { + OpenCensusUtils.propagateTracingContext(null, headers); + fail("expected " + IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "span should not be null."); + } + } + + public void testPropagateTracingContextNullHeaders() { + OpenCensusUtils.setPropagationTextFormat(mockTextFormat); + try { + OpenCensusUtils.propagateTracingContext(mockSpan, null); + fail("expected " + IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "headers should not be null."); + } } public void testPropagateTracingContextInvalidSpan() { OpenCensusUtils.setPropagationTextFormat(mockTextFormat); // No injection. No exceptions should be thrown. - OpenCensusUtils.propagateTracingContext(headers); + OpenCensusUtils.propagateTracingContext(BlankSpan.INSTANCE, headers); } public void testGetEndSpanOptionsNoResponse() { - EndSpanOptions expected = - EndSpanOptions.builder().setSampleToLocalSpanStore(true).setStatus(Status.UNKNOWN).build(); + EndSpanOptions expected = EndSpanOptions.builder().setStatus(Status.UNKNOWN).build(); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(null)); } public void testGetEndSpanOptionsSuccess() { - EndSpanOptions expected = - EndSpanOptions.builder().setSampleToLocalSpanStore(true).setStatus(Status.OK).build(); + EndSpanOptions expected = EndSpanOptions.builder().setStatus(Status.OK).build(); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(200)); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(201)); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(202)); } public void testGetEndSpanOptionsBadRequest() { - EndSpanOptions expected = EndSpanOptions - .builder() - .setSampleToLocalSpanStore(true) - .setStatus(Status.INVALID_ARGUMENT) - .build(); + EndSpanOptions expected = EndSpanOptions.builder().setStatus(Status.INVALID_ARGUMENT).build(); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(400)); } public void testGetEndSpanOptionsUnauthorized() { - EndSpanOptions expected = EndSpanOptions - .builder() - .setSampleToLocalSpanStore(true) - .setStatus(Status.UNAUTHENTICATED) - .build(); + EndSpanOptions expected = EndSpanOptions.builder().setStatus(Status.UNAUTHENTICATED).build(); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(401)); } public void testGetEndSpanOptionsForbidden() { - EndSpanOptions expected = EndSpanOptions - .builder() - .setSampleToLocalSpanStore(true) - .setStatus(Status.PERMISSION_DENIED) - .build(); + EndSpanOptions expected = EndSpanOptions.builder().setStatus(Status.PERMISSION_DENIED).build(); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(403)); } public void testGetEndSpanOptionsNotFound() { - EndSpanOptions expected = EndSpanOptions - .builder() - .setSampleToLocalSpanStore(true) - .setStatus(Status.NOT_FOUND) - .build(); + EndSpanOptions expected = EndSpanOptions.builder().setStatus(Status.NOT_FOUND).build(); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(404)); } public void testGetEndSpanOptionsPreconditionFailed() { - EndSpanOptions expected = EndSpanOptions - .builder() - .setSampleToLocalSpanStore(true) - .setStatus(Status.FAILED_PRECONDITION) - .build(); + EndSpanOptions expected = EndSpanOptions.builder().setStatus(Status.FAILED_PRECONDITION).build(); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(412)); } public void testGetEndSpanOptionsServerError() { - EndSpanOptions expected = EndSpanOptions - .builder() - .setSampleToLocalSpanStore(true) - .setStatus(Status.UNAVAILABLE) - .build(); + EndSpanOptions expected = EndSpanOptions.builder().setStatus(Status.UNAVAILABLE).build(); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(500)); } public void testGetEndSpanOptionsOther() { - EndSpanOptions expected = EndSpanOptions.builder() - .setSampleToLocalSpanStore(true) - .setStatus(Status.UNKNOWN) - .build(); + EndSpanOptions expected = EndSpanOptions.builder().setStatus(Status.UNKNOWN).build(); // test some random unsupported statuses assertEquals(expected, OpenCensusUtils.getEndSpanOptions(301)); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(402)); assertEquals(expected, OpenCensusUtils.getEndSpanOptions(501)); } + + public void testRecordMessageEventInNullSpan() { + try { + OpenCensusUtils.recordMessageEvent(null, 0, NetworkEvent.Type.SENT); + fail("expected " + IllegalArgumentException.class); + } catch (IllegalArgumentException e) { + assertEquals(e.getMessage(), "span should not be null."); + } + } + + public void testRecordMessageEvent() { + try { + OpenCensusUtils.recordMessageEvent(mockSpan, 0, NetworkEvent.Type.SENT); + fail("expected " + UnsupportedOperationException.class); + } catch (UnsupportedOperationException e) { + assertEquals(e.getMessage(), "Span.addNetworkEvent"); + } + } } From cf77d68f436105422ee7a08d27b790ccacc3bfd5 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Tue, 11 Dec 2018 16:39:05 -0800 Subject: [PATCH 3/6] Update opencensus version and use dependencyManagement section --- google-http-client/pom.xml | 3 --- pom.xml | 11 +++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/google-http-client/pom.xml b/google-http-client/pom.xml index 177d0d865..81414b925 100644 --- a/google-http-client/pom.xml +++ b/google-http-client/pom.xml @@ -175,17 +175,14 @@ com.google.j2objc j2objc-annotations - 1.1 io.opencensus opencensus-api - 0.11.1 io.opencensus opencensus-contrib-http-util - 0.11.1 diff --git a/pom.xml b/pom.xml index 3794f581a..56c870e1b 100644 --- a/pom.xml +++ b/pom.xml @@ -268,6 +268,16 @@ j2objc-annotations 1.1 + + io.opencensus + opencensus-api + ${project.opencensus.version} + + + io.opencensus + opencensus-contrib-http-util + ${project.opencensus.version} + @@ -550,6 +560,7 @@ 3.2.1 3.2.1 4.0.3 + 0.18.0 From 89a725fee27507a94d995a40271099d0645a1f20 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Wed, 12 Dec 2018 09:16:08 -0800 Subject: [PATCH 4/6] Fix missing import --- .../main/java/com/google/api/client/util/OpenCensusUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java b/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java index 112612711..ff38312b1 100644 --- a/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java +++ b/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java @@ -31,6 +31,7 @@ import io.opencensus.trace.propagation.TextFormat; import java.util.Collections; +import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; From bf729172c3b1de8aa3a15a856472d320e1e6e9a0 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Wed, 12 Dec 2018 16:54:02 -0800 Subject: [PATCH 5/6] Set span attributes --- .../java/com/google/api/client/http/HttpRequest.java | 12 +++++++++++- .../com/google/api/client/util/OpenCensusUtils.java | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java index e4befdfc2..24f28a4dd 100644 --- a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java +++ b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java @@ -25,6 +25,8 @@ import com.google.api.client.util.StringUtils; import io.opencensus.common.Scope; +import io.opencensus.contrib.http.util.HttpTraceAttributeConstants; +import io.opencensus.trace.AttributeValue; import io.opencensus.trace.Span; import io.opencensus.trace.Tracer; @@ -911,6 +913,11 @@ public HttpResponse execute() throws IOException { } // build low-level HTTP request String urlString = url.build(); + span.putAttribute(HttpTraceAttributeConstants.HTTP_METHOD, AttributeValue.stringAttributeValue(requestMethod)); + span.putAttribute(HttpTraceAttributeConstants.HTTP_HOST, AttributeValue.stringAttributeValue(url.getHost())); + span.putAttribute(HttpTraceAttributeConstants.HTTP_PATH, AttributeValue.stringAttributeValue(url.getRawPath())); + span.putAttribute(HttpTraceAttributeConstants.HTTP_URL, AttributeValue.stringAttributeValue(urlString)); + LowLevelHttpRequest lowLevelHttpRequest = transport.buildRequest(requestMethod, urlString); Logger logger = HttpTransport.LOGGER; boolean loggable = loggingEnabled && logger.isLoggable(Level.CONFIG); @@ -936,8 +943,11 @@ public HttpResponse execute() throws IOException { if (!suppressUserAgentSuffix) { if (originalUserAgent == null) { headers.setUserAgent(USER_AGENT_SUFFIX); + span.putAttribute(HttpTraceAttributeConstants.HTTP_USER_AGENT, AttributeValue.stringAttributeValue(USER_AGENT_SUFFIX)); } else { - headers.setUserAgent(originalUserAgent + " " + USER_AGENT_SUFFIX); + String newUserAgent = originalUserAgent + " " + USER_AGENT_SUFFIX; + headers.setUserAgent(newUserAgent); + span.putAttribute(HttpTraceAttributeConstants.HTTP_USER_AGENT, AttributeValue.stringAttributeValue(newUserAgent)); } } OpenCensusUtils.propagateTracingContext(span, headers); diff --git a/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java b/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java index ff38312b1..c2a2cc000 100644 --- a/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java +++ b/google-http-client/src/main/java/com/google/api/client/util/OpenCensusUtils.java @@ -217,7 +217,7 @@ public static void recordReceivedMessageEvent(Span span, long size) { } /** - * Records a message event of a certain {@link NetowrkEvent.Type}. This method is package + * Records a message event of a certain {@link NetworkEvent.Type}. This method is package * protected since {@link NetworkEvent} might be deprecated in future releases. * * @param span The {@code span} in which the event occurs. From fabb6d735323c65d12d757a4917e71d05e4f3682 Mon Sep 17 00:00:00 2001 From: Jeff Ching Date: Thu, 13 Dec 2018 10:53:49 -0800 Subject: [PATCH 6/6] Fix checkstyle and NPE for missing attributes --- .../google/api/client/http/HttpRequest.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java index 24f28a4dd..1c68d9036 100644 --- a/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java +++ b/google-http-client/src/main/java/com/google/api/client/http/HttpRequest.java @@ -913,10 +913,10 @@ public HttpResponse execute() throws IOException { } // build low-level HTTP request String urlString = url.build(); - span.putAttribute(HttpTraceAttributeConstants.HTTP_METHOD, AttributeValue.stringAttributeValue(requestMethod)); - span.putAttribute(HttpTraceAttributeConstants.HTTP_HOST, AttributeValue.stringAttributeValue(url.getHost())); - span.putAttribute(HttpTraceAttributeConstants.HTTP_PATH, AttributeValue.stringAttributeValue(url.getRawPath())); - span.putAttribute(HttpTraceAttributeConstants.HTTP_URL, AttributeValue.stringAttributeValue(urlString)); + addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_METHOD, requestMethod); + addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_HOST, url.getHost()); + addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_PATH, url.getRawPath()); + addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_URL, urlString); LowLevelHttpRequest lowLevelHttpRequest = transport.buildRequest(requestMethod, urlString); Logger logger = HttpTransport.LOGGER; @@ -943,11 +943,11 @@ public HttpResponse execute() throws IOException { if (!suppressUserAgentSuffix) { if (originalUserAgent == null) { headers.setUserAgent(USER_AGENT_SUFFIX); - span.putAttribute(HttpTraceAttributeConstants.HTTP_USER_AGENT, AttributeValue.stringAttributeValue(USER_AGENT_SUFFIX)); + addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_USER_AGENT, USER_AGENT_SUFFIX); } else { String newUserAgent = originalUserAgent + " " + USER_AGENT_SUFFIX; headers.setUserAgent(newUserAgent); - span.putAttribute(HttpTraceAttributeConstants.HTTP_USER_AGENT, AttributeValue.stringAttributeValue(newUserAgent)); + addSpanAttribute(span, HttpTraceAttributeConstants.HTTP_USER_AGENT, newUserAgent); } } OpenCensusUtils.propagateTracingContext(span, headers); @@ -1237,4 +1237,10 @@ public HttpRequest setSleeper(Sleeper sleeper) { this.sleeper = Preconditions.checkNotNull(sleeper); return this; } + + private static void addSpanAttribute(Span span, String key, String value) { + if (value != null) { + span.putAttribute(key, AttributeValue.stringAttributeValue(value)); + } + } }