aboutsummaryrefslogtreecommitdiff
path: root/exporters/trace/stackdriver
diff options
context:
space:
mode:
Diffstat (limited to 'exporters/trace/stackdriver')
-rw-r--r--exporters/trace/stackdriver/README.md127
-rw-r--r--exporters/trace/stackdriver/build.gradle31
-rw-r--r--exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverExporter.java148
-rw-r--r--exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceConfiguration.java118
-rw-r--r--exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceExporter.java141
-rw-r--r--exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandler.java501
-rw-r--r--exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceConfigurationTest.java54
-rw-r--r--exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceExporterTest.java53
-rw-r--r--exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerExportTest.java64
-rw-r--r--exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerProtoTest.java489
10 files changed, 1726 insertions, 0 deletions
diff --git a/exporters/trace/stackdriver/README.md b/exporters/trace/stackdriver/README.md
new file mode 100644
index 00000000..9186a47c
--- /dev/null
+++ b/exporters/trace/stackdriver/README.md
@@ -0,0 +1,127 @@
+# OpenCensus Stackdriver Trace Exporter
+[![Build Status][travis-image]][travis-url]
+[![Windows Build Status][appveyor-image]][appveyor-url]
+[![Maven Central][maven-image]][maven-url]
+
+The *OpenCensus Stackdriver Trace Exporter* is a trace exporter that exports data to
+Stackdriver Trace. [Stackdriver Trace][stackdriver-trace] is a distributed
+tracing system that collects latency data from your applications and displays it in the Google
+Cloud Platform Console. You can track how requests propagate through your application and receive
+detailed near real-time performance insights.
+
+## Quickstart
+
+### Prerequisites
+
+To use this exporter, you must have an application that you'd like to trace. The app can be on
+Google Cloud Platform, on-premise, or another cloud platform.
+
+In order to be able to push your traces to [Stackdriver Trace][stackdriver-trace], you must:
+
+1. [Create a Cloud project](https://support.google.com/cloud/answer/6251787?hl=en).
+2. [Enable billing](https://support.google.com/cloud/answer/6288653#new-billing).
+3. [Enable the Stackdriver Trace API](https://console.cloud.google.com/apis/api/cloudtrace.googleapis.com/overview).
+
+These steps enable the API but don't require that your app is hosted on Google Cloud Platform.
+
+### Hello "Stackdriver Trace"
+
+#### Add the dependencies to your project
+
+For Maven add to your `pom.xml`:
+```xml
+<dependencies>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-api</artifactId>
+ <version>0.16.1</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-exporter-trace-stackdriver</artifactId>
+ <version>0.16.1</version>
+ </dependency>
+ <dependency>
+ <groupId>io.opencensus</groupId>
+ <artifactId>opencensus-impl</artifactId>
+ <version>0.16.1</version>
+ <scope>runtime</scope>
+ </dependency>
+</dependencies>
+```
+
+For Gradle add to your dependencies:
+```groovy
+compile 'io.opencensus:opencensus-api:0.16.1'
+compile 'io.opencensus:opencensus-exporter-trace-stackdriver:0.16.1'
+runtime 'io.opencensus:opencensus-impl:0.16.1'
+```
+
+#### Register the exporter
+
+This uses the default configuration for authentication and project ID.
+
+```java
+public class MyMainClass {
+ public static void main(String[] args) throws Exception {
+ StackdriverTraceExporter.createAndRegister(
+ StackdriverTraceConfiguration.builder().build());
+ // ...
+ }
+}
+```
+
+#### Authentication
+
+This exporter uses [google-cloud-java](https://github.com/GoogleCloudPlatform/google-cloud-java),
+for details about how to configure the authentication see [here](https://github.com/GoogleCloudPlatform/google-cloud-java#authentication).
+
+If you prefer to manually set the credentials use:
+```
+StackdriverTraceExporter.createAndRegisterWithCredentialsAndProjectId(
+ new GoogleCredentials(new AccessToken(accessToken, expirationTime)),
+ "MyStackdriverProjectId");
+```
+
+#### Specifying a Project ID
+
+This exporter uses [google-cloud-java](https://github.com/GoogleCloudPlatform/google-cloud-java),
+for details about how to configure the project ID see [here](https://github.com/GoogleCloudPlatform/google-cloud-java#specifying-a-project-id).
+
+If you prefer to manually set the project ID use:
+```
+StackdriverTraceExporter.createAndRegisterWithProjectId("MyStackdriverProjectId");
+```
+
+#### Enable Stackdriver Trace API access scope on Google Cloud Platform
+If your Stackdriver Trace Exporter is running on Kubernetes Engine or Compute Engine,
+you might need additional setup to explicitly enable the ```trace.append``` Stackdriver
+Trace API access scope. To do that, please follow the instructions for
+[GKE](https://cloud.google.com/trace/docs/setup/java#kubernetes_engine) or
+[GCE](https://cloud.google.com/trace/docs/setup/java#compute_engine).
+
+#### Java Versions
+
+Java 7 or above is required for using this exporter.
+
+## FAQ
+### Why do I not see some trace events in Stackdriver?
+In all the versions before '0.9.1' the Stackdriver Trace exporter was implemented using the [v1
+API][stackdriver-v1-api-url] which is not fully compatible with the OpenCensus data model. Trace
+events like Annotations and NetworkEvents will be dropped.
+
+### Why do I get a "StatusRuntimeException: NOT_FOUND: Requested entity was not found"?
+One of the possible reasons is you are using a project id with bad format for the exporter.
+Please double check the project id associated with the Stackdriver Trace exporter first.
+Stackdriver Trace backend will not do any sanitization or trimming on the incoming project id.
+Project id with leading or trailing spaces will be treated as a separate non-existing project
+(e.g "project-id" vs "project-id "), and will cause a NOT_FOUND exception.
+
+[travis-image]: https://travis-ci.org/census-instrumentation/opencensus-java.svg?branch=master
+[travis-url]: https://travis-ci.org/census-instrumentation/opencensus-java
+[appveyor-image]: https://ci.appveyor.com/api/projects/status/hxthmpkxar4jq4be/branch/master?svg=true
+[appveyor-url]: https://ci.appveyor.com/project/opencensusjavateam/opencensus-java/branch/master
+[maven-image]: https://maven-badges.herokuapp.com/maven-central/io.opencensus/opencensus-exporter-trace-stackdriver/badge.svg
+[maven-url]: https://maven-badges.herokuapp.com/maven-central/io.opencensus/opencensus-exporter-trace-stackdriver
+[stackdriver-trace]: https://cloud.google.com/trace/
+[stackdriver-v1-api-url]: https://cloud.google.com/trace/docs/reference/v1/rpc/google.devtools.cloudtrace.v1#google.devtools.cloudtrace.v1.TraceSpan
diff --git a/exporters/trace/stackdriver/build.gradle b/exporters/trace/stackdriver/build.gradle
new file mode 100644
index 00000000..83dc970e
--- /dev/null
+++ b/exporters/trace/stackdriver/build.gradle
@@ -0,0 +1,31 @@
+description = 'OpenCensus Trace Stackdriver Exporter'
+
+[compileJava, compileTestJava].each() {
+ it.sourceCompatibility = 1.7
+ it.targetCompatibility = 1.7
+}
+
+dependencies {
+ compileOnly libraries.auto_value
+
+ compile project(':opencensus-api'),
+ project(':opencensus-contrib-monitored-resource-util'),
+ libraries.google_auth,
+ libraries.guava
+
+ compile (libraries.google_cloud_trace) {
+ // Prefer library version.
+ exclude group: 'com.google.guava', module: 'guava'
+
+ // Prefer library version.
+ exclude group: 'com.google.code.findbugs', module: 'jsr305'
+
+ // We will always be more up to date.
+ exclude group: 'io.opencensus', module: 'opencensus-api'
+ }
+
+ testCompile project(':opencensus-api')
+
+ signature "org.codehaus.mojo.signature:java17:1.0@signature"
+ signature "net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature"
+}
diff --git a/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverExporter.java b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverExporter.java
new file mode 100644
index 00000000..8797cc77
--- /dev/null
+++ b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverExporter.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * 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 io.opencensus.exporter.trace.stackdriver;
+
+import com.google.auth.Credentials;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.cloud.ServiceOptions;
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.trace.export.SpanExporter;
+import io.opencensus.trace.export.SpanExporter.Handler;
+import java.io.IOException;
+
+/**
+ * An OpenCensus span exporter implementation which exports data to Stackdriver Trace.
+ *
+ * <p>Example of usage on Google Cloud VMs:
+ *
+ * <pre>{@code
+ * public static void main(String[] args) {
+ * StackdriverExporter.createAndRegisterWithProjectId("MyStackdriverProjectId");
+ * ... // Do work.
+ * }
+ * }</pre>
+ *
+ * @deprecated Deprecated due to inconsistent naming. Use {@link StackdriverTraceExporter}.
+ * @since 0.6
+ */
+@Deprecated
+public final class StackdriverExporter {
+
+ /**
+ * Creates and registers the Stackdriver Trace exporter to the OpenCensus library for an explicit
+ * project ID and using explicit credentials. Only one Stackdriver exporter can be registered at
+ * any point.
+ *
+ * @param credentials a credentials used to authenticate API calls.
+ * @param projectId the cloud project id.
+ * @throws IllegalStateException if a Stackdriver exporter is already registered.
+ * @since 0.6
+ */
+ public static void createAndRegisterWithCredentialsAndProjectId(
+ Credentials credentials, String projectId) throws IOException {
+ StackdriverTraceExporter.createAndRegister(
+ StackdriverTraceConfiguration.builder()
+ .setCredentials(credentials)
+ .setProjectId(projectId)
+ .build());
+ }
+
+ /**
+ * Creates and registers the Stackdriver Trace exporter to the OpenCensus library for an explicit
+ * project ID. Only one Stackdriver exporter can be registered at any point.
+ *
+ * <p>This uses the default application credentials see {@link
+ * GoogleCredentials#getApplicationDefault}.
+ *
+ * <p>This is equivalent with:
+ *
+ * <pre>{@code
+ * StackdriverExporter.createAndRegisterWithCredentialsAndProjectId(
+ * GoogleCredentials.getApplicationDefault(), projectId);
+ * }</pre>
+ *
+ * @param projectId the cloud project id.
+ * @throws IllegalStateException if a Stackdriver exporter is already registered.
+ * @since 0.6
+ */
+ public static void createAndRegisterWithProjectId(String projectId) throws IOException {
+ StackdriverTraceExporter.createAndRegister(
+ StackdriverTraceConfiguration.builder()
+ .setCredentials(GoogleCredentials.getApplicationDefault())
+ .setProjectId(projectId)
+ .build());
+ }
+
+ /**
+ * Creates and registers the Stackdriver Trace exporter to the OpenCensus library. Only one
+ * Stackdriver exporter can be registered at any point.
+ *
+ * <p>This uses the default application credentials see {@link
+ * GoogleCredentials#getApplicationDefault}.
+ *
+ * <p>This uses the default project ID configured see {@link ServiceOptions#getDefaultProjectId}.
+ *
+ * <p>This is equivalent with:
+ *
+ * <pre>{@code
+ * StackdriverExporter.createAndRegisterWithProjectId(ServiceOptions.getDefaultProjectId());
+ * }</pre>
+ *
+ * @throws IllegalStateException if a Stackdriver exporter is already registered.
+ * @since 0.6
+ */
+ public static void createAndRegister() throws IOException {
+ StackdriverTraceExporter.createAndRegister(
+ StackdriverTraceConfiguration.builder()
+ .setCredentials(GoogleCredentials.getApplicationDefault())
+ .setProjectId(ServiceOptions.getDefaultProjectId())
+ .build());
+ }
+
+ /**
+ * Registers the {@code StackdriverExporter}.
+ *
+ * @param spanExporter the instance of the {@code SpanExporter} where this service is registered.
+ */
+ @VisibleForTesting
+ static void register(SpanExporter spanExporter, Handler handler) {
+ StackdriverTraceExporter.register(spanExporter, handler);
+ }
+
+ /**
+ * Unregisters the Stackdriver Trace exporter from the OpenCensus library.
+ *
+ * @throws IllegalStateException if a Stackdriver exporter is not registered.
+ * @since 0.6
+ */
+ public static void unregister() {
+ StackdriverTraceExporter.unregister();
+ }
+
+ /**
+ * Unregisters the {@code StackdriverExporter}.
+ *
+ * @param spanExporter the instance of the {@code SpanExporter} from where this service is
+ * unregistered.
+ */
+ @VisibleForTesting
+ static void unregister(SpanExporter spanExporter) {
+ StackdriverTraceExporter.unregister(spanExporter);
+ }
+
+ private StackdriverExporter() {}
+}
diff --git a/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceConfiguration.java b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceConfiguration.java
new file mode 100644
index 00000000..f78832d0
--- /dev/null
+++ b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceConfiguration.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * 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 io.opencensus.exporter.trace.stackdriver;
+
+import com.google.auth.Credentials;
+import com.google.auto.value.AutoValue;
+import com.google.cloud.trace.v2.stub.TraceServiceStub;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+
+/**
+ * Configurations for {@link StackdriverTraceExporter}.
+ *
+ * @since 0.12
+ */
+@AutoValue
+@Immutable
+public abstract class StackdriverTraceConfiguration {
+
+ StackdriverTraceConfiguration() {}
+
+ /**
+ * Returns the {@link Credentials}.
+ *
+ * @return the {@code Credentials}.
+ * @since 0.12
+ */
+ @Nullable
+ public abstract Credentials getCredentials();
+
+ /**
+ * Returns the cloud project id.
+ *
+ * @return the cloud project id.
+ * @since 0.12
+ */
+ @Nullable
+ public abstract String getProjectId();
+
+ /**
+ * Returns a TraceServiceStub instance used to make RPC calls.
+ *
+ * @return the trace service stub.
+ * @since 0.16
+ */
+ @Nullable
+ public abstract TraceServiceStub getTraceServiceStub();
+
+ /**
+ * Returns a new {@link Builder}.
+ *
+ * @return a {@code Builder}.
+ * @since 0.12
+ */
+ public static Builder builder() {
+ return new AutoValue_StackdriverTraceConfiguration.Builder();
+ }
+
+ /**
+ * Builder for {@link StackdriverTraceConfiguration}.
+ *
+ * @since 0.12
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ Builder() {}
+
+ /**
+ * Sets the {@link Credentials} used to authenticate API calls.
+ *
+ * @param credentials the {@code Credentials}.
+ * @return this.
+ * @since 0.12
+ */
+ public abstract Builder setCredentials(Credentials credentials);
+
+ /**
+ * Sets the cloud project id.
+ *
+ * @param projectId the cloud project id.
+ * @return this.
+ * @since 0.12
+ */
+ public abstract Builder setProjectId(String projectId);
+
+ /**
+ * Sets the trace service stub used to send gRPC calls.
+ *
+ * @param traceServiceStub the {@code TraceServiceStub}.
+ * @return this.
+ * @since 0.16
+ */
+ public abstract Builder setTraceServiceStub(TraceServiceStub traceServiceStub);
+
+ /**
+ * Builds a {@link StackdriverTraceConfiguration}.
+ *
+ * @return a {@code StackdriverTraceConfiguration}.
+ * @since 0.12
+ */
+ public abstract StackdriverTraceConfiguration build();
+ }
+}
diff --git a/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceExporter.java b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceExporter.java
new file mode 100644
index 00000000..0182ae94
--- /dev/null
+++ b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceExporter.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * 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 io.opencensus.exporter.trace.stackdriver;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.auth.Credentials;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.cloud.ServiceOptions;
+import com.google.cloud.trace.v2.TraceServiceClient;
+import com.google.cloud.trace.v2.stub.TraceServiceStub;
+import com.google.common.annotations.VisibleForTesting;
+import io.opencensus.trace.Tracing;
+import io.opencensus.trace.export.SpanExporter;
+import io.opencensus.trace.export.SpanExporter.Handler;
+import java.io.IOException;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * An OpenCensus span exporter implementation which exports data to Stackdriver Trace.
+ *
+ * <p>Example of usage on Google Cloud VMs:
+ *
+ * <pre>{@code
+ * public static void main(String[] args) {
+ * StackdriverTraceExporter.createAndRegister(
+ * StackdriverTraceConfiguration.builder()
+ * .setProjectId("MyStackdriverProjectId")
+ * .build());
+ * ... // Do work.
+ * }
+ * }</pre>
+ *
+ * @since 0.12
+ */
+public final class StackdriverTraceExporter {
+
+ private static final String REGISTER_NAME = StackdriverTraceExporter.class.getName();
+ private static final Object monitor = new Object();
+
+ @GuardedBy("monitor")
+ @Nullable
+ private static Handler handler = null;
+
+ /**
+ * Creates and registers the Stackdriver Trace exporter to the OpenCensus library. Only one
+ * Stackdriver exporter can be registered at any point.
+ *
+ * <p>If the {@code credentials} in the provided {@link StackdriverTraceConfiguration} is not set,
+ * the exporter will use the default application credentials. See {@link
+ * GoogleCredentials#getApplicationDefault}.
+ *
+ * <p>If the {@code projectId} in the provided {@link StackdriverTraceConfiguration} is not set,
+ * the exporter will use the default project ID. See {@link ServiceOptions#getDefaultProjectId}.
+ *
+ * @param configuration the {@code StackdriverTraceConfiguration} used to create the exporter.
+ * @throws IllegalStateException if a Stackdriver exporter is already registered.
+ * @since 0.12
+ */
+ public static void createAndRegister(StackdriverTraceConfiguration configuration)
+ throws IOException {
+ synchronized (monitor) {
+ checkState(handler == null, "Stackdriver exporter is already registered.");
+ Credentials credentials = configuration.getCredentials();
+ String projectId = configuration.getProjectId();
+ projectId = projectId != null ? projectId : ServiceOptions.getDefaultProjectId();
+
+ StackdriverV2ExporterHandler handler;
+ TraceServiceStub stub = configuration.getTraceServiceStub();
+ if (stub == null) {
+ handler =
+ StackdriverV2ExporterHandler.createWithCredentials(
+ credentials != null ? credentials : GoogleCredentials.getApplicationDefault(),
+ projectId);
+ } else {
+ handler = new StackdriverV2ExporterHandler(projectId, TraceServiceClient.create(stub));
+ }
+
+ registerInternal(handler);
+ }
+ }
+
+ private static void registerInternal(Handler newHandler) {
+ synchronized (monitor) {
+ handler = newHandler;
+ register(Tracing.getExportComponent().getSpanExporter(), newHandler);
+ }
+ }
+
+ /**
+ * Registers the {@code StackdriverTraceExporter}.
+ *
+ * @param spanExporter the instance of the {@code SpanExporter} where this service is registered.
+ */
+ @VisibleForTesting
+ static void register(SpanExporter spanExporter, Handler handler) {
+ spanExporter.registerHandler(REGISTER_NAME, handler);
+ }
+
+ /**
+ * Unregisters the Stackdriver Trace exporter from the OpenCensus library.
+ *
+ * @throws IllegalStateException if a Stackdriver exporter is not registered.
+ * @since 0.12
+ */
+ public static void unregister() {
+ synchronized (monitor) {
+ checkState(handler != null, "Stackdriver exporter is not registered.");
+ unregister(Tracing.getExportComponent().getSpanExporter());
+ handler = null;
+ }
+ }
+
+ /**
+ * Unregisters the {@code StackdriverTraceExporter}.
+ *
+ * @param spanExporter the instance of the {@code SpanExporter} from where this service is
+ * unregistered.
+ */
+ @VisibleForTesting
+ static void unregister(SpanExporter spanExporter) {
+ spanExporter.unregisterHandler(REGISTER_NAME);
+ }
+
+ private StackdriverTraceExporter() {}
+}
diff --git a/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandler.java b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandler.java
new file mode 100644
index 00000000..de022c3f
--- /dev/null
+++ b/exporters/trace/stackdriver/src/main/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandler.java
@@ -0,0 +1,501 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * 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 io.opencensus.exporter.trace.stackdriver;
+
+import static com.google.api.client.util.Preconditions.checkNotNull;
+
+import com.google.api.gax.core.FixedCredentialsProvider;
+import com.google.auth.Credentials;
+import com.google.cloud.trace.v2.TraceServiceClient;
+import com.google.cloud.trace.v2.TraceServiceSettings;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.cloudtrace.v2.AttributeValue;
+import com.google.devtools.cloudtrace.v2.AttributeValue.Builder;
+import com.google.devtools.cloudtrace.v2.ProjectName;
+import com.google.devtools.cloudtrace.v2.Span;
+import com.google.devtools.cloudtrace.v2.Span.Attributes;
+import com.google.devtools.cloudtrace.v2.Span.Link;
+import com.google.devtools.cloudtrace.v2.Span.Links;
+import com.google.devtools.cloudtrace.v2.Span.TimeEvent;
+import com.google.devtools.cloudtrace.v2.Span.TimeEvent.MessageEvent;
+import com.google.devtools.cloudtrace.v2.SpanName;
+import com.google.devtools.cloudtrace.v2.TruncatableString;
+import com.google.protobuf.Int32Value;
+import com.google.rpc.Status;
+import io.opencensus.common.Function;
+import io.opencensus.common.Functions;
+import io.opencensus.common.OpenCensusLibraryInformation;
+import io.opencensus.common.Scope;
+import io.opencensus.common.Timestamp;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource.AwsEc2InstanceMonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource.GcpGceInstanceMonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource.GcpGkeContainerMonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResourceUtils;
+import io.opencensus.contrib.monitoredresource.util.ResourceType;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.MessageEvent.Type;
+import io.opencensus.trace.Sampler;
+import io.opencensus.trace.Span.Kind;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.Tracer;
+import io.opencensus.trace.Tracing;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanData.TimedEvent;
+import io.opencensus.trace.export.SpanData.TimedEvents;
+import io.opencensus.trace.export.SpanExporter;
+import io.opencensus.trace.samplers.Samplers;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+/*>>>
+import org.checkerframework.checker.nullness.qual.Nullable;
+*/
+
+/** Exporter to Stackdriver Trace API v2. */
+final class StackdriverV2ExporterHandler extends SpanExporter.Handler {
+
+ private static final Tracer tracer = Tracing.getTracer();
+ private static final Sampler probabilitySampler = Samplers.probabilitySampler(0.0001);
+ private static final String AGENT_LABEL_KEY = "g.co/agent";
+ private static final String AGENT_LABEL_VALUE_STRING =
+ "opencensus-java [" + OpenCensusLibraryInformation.VERSION + "]";
+ private static final String SERVER_PREFIX = "Recv.";
+ private static final String CLIENT_PREFIX = "Sent.";
+ private static final AttributeValue AGENT_LABEL_VALUE =
+ AttributeValue.newBuilder()
+ .setStringValue(toTruncatableStringProto(AGENT_LABEL_VALUE_STRING))
+ .build();
+
+ private static final ImmutableMap<String, String> HTTP_ATTRIBUTE_MAPPING =
+ ImmutableMap.<String, String>builder()
+ .put("http.host", "/http/host")
+ .put("http.method", "/http/method")
+ .put("http.path", "/http/path")
+ .put("http.route", "/http/route")
+ .put("http.user_agent", "/http/user_agent")
+ .put("http.status_code", "/http/status_code")
+ .build();
+
+ @javax.annotation.Nullable
+ private static final MonitoredResource RESOURCE = MonitoredResourceUtils.getDefaultResource();
+
+ // Only initialize once.
+ private static final Map<String, AttributeValue> RESOURCE_LABELS = getResourceLabels(RESOURCE);
+
+ // Constant functions for AttributeValue.
+ private static final Function<String, /*@Nullable*/ AttributeValue> stringAttributeValueFunction =
+ new Function<String, /*@Nullable*/ AttributeValue>() {
+ @Override
+ public AttributeValue apply(String stringValue) {
+ Builder attributeValueBuilder = AttributeValue.newBuilder();
+ attributeValueBuilder.setStringValue(toTruncatableStringProto(stringValue));
+ return attributeValueBuilder.build();
+ }
+ };
+ private static final Function<Boolean, /*@Nullable*/ AttributeValue>
+ booleanAttributeValueFunction =
+ new Function<Boolean, /*@Nullable*/ AttributeValue>() {
+ @Override
+ public AttributeValue apply(Boolean booleanValue) {
+ Builder attributeValueBuilder = AttributeValue.newBuilder();
+ attributeValueBuilder.setBoolValue(booleanValue);
+ return attributeValueBuilder.build();
+ }
+ };
+ private static final Function<Long, /*@Nullable*/ AttributeValue> longAttributeValueFunction =
+ new Function<Long, /*@Nullable*/ AttributeValue>() {
+ @Override
+ public AttributeValue apply(Long longValue) {
+ Builder attributeValueBuilder = AttributeValue.newBuilder();
+ attributeValueBuilder.setIntValue(longValue);
+ return attributeValueBuilder.build();
+ }
+ };
+ private static final Function<Double, /*@Nullable*/ AttributeValue> doubleAttributeValueFunction =
+ new Function<Double, /*@Nullable*/ AttributeValue>() {
+ @Override
+ public AttributeValue apply(Double doubleValue) {
+ Builder attributeValueBuilder = AttributeValue.newBuilder();
+ // TODO: set double value if Stackdriver Trace support it in the future.
+ attributeValueBuilder.setStringValue(
+ toTruncatableStringProto(String.valueOf(doubleValue)));
+ return attributeValueBuilder.build();
+ }
+ };
+
+ private final String projectId;
+ private final TraceServiceClient traceServiceClient;
+ private final ProjectName projectName;
+
+ @VisibleForTesting
+ StackdriverV2ExporterHandler(String projectId, TraceServiceClient traceServiceClient) {
+ this.projectId = checkNotNull(projectId, "projectId");
+ this.traceServiceClient = traceServiceClient;
+ projectName = ProjectName.of(this.projectId);
+
+ Tracing.getExportComponent()
+ .getSampledSpanStore()
+ .registerSpanNamesForCollection(Collections.singletonList("ExportStackdriverTraces"));
+ }
+
+ static StackdriverV2ExporterHandler createWithCredentials(
+ Credentials credentials, String projectId) throws IOException {
+ checkNotNull(credentials, "credentials");
+ TraceServiceSettings traceServiceSettings =
+ TraceServiceSettings.newBuilder()
+ .setCredentialsProvider(FixedCredentialsProvider.create(credentials))
+ .build();
+ return new StackdriverV2ExporterHandler(
+ projectId, TraceServiceClient.create(traceServiceSettings));
+ }
+
+ @VisibleForTesting
+ Span generateSpan(SpanData spanData, Map<String, AttributeValue> resourceLabels) {
+ SpanContext context = spanData.getContext();
+ final String spanIdHex = context.getSpanId().toLowerBase16();
+ SpanName spanName =
+ SpanName.newBuilder()
+ .setProject(projectId)
+ .setTrace(context.getTraceId().toLowerBase16())
+ .setSpan(spanIdHex)
+ .build();
+ Span.Builder spanBuilder =
+ Span.newBuilder()
+ .setName(spanName.toString())
+ .setSpanId(spanIdHex)
+ .setDisplayName(
+ toTruncatableStringProto(toDisplayName(spanData.getName(), spanData.getKind())))
+ .setStartTime(toTimestampProto(spanData.getStartTimestamp()))
+ .setAttributes(toAttributesProto(spanData.getAttributes(), resourceLabels))
+ .setTimeEvents(
+ toTimeEventsProto(spanData.getAnnotations(), spanData.getMessageEvents()));
+ io.opencensus.trace.Status status = spanData.getStatus();
+ if (status != null) {
+ spanBuilder.setStatus(toStatusProto(status));
+ }
+ Timestamp end = spanData.getEndTimestamp();
+ if (end != null) {
+ spanBuilder.setEndTime(toTimestampProto(end));
+ }
+ spanBuilder.setLinks(toLinksProto(spanData.getLinks()));
+ Integer childSpanCount = spanData.getChildSpanCount();
+ if (childSpanCount != null) {
+ spanBuilder.setChildSpanCount(Int32Value.newBuilder().setValue(childSpanCount).build());
+ }
+ if (spanData.getParentSpanId() != null && spanData.getParentSpanId().isValid()) {
+ spanBuilder.setParentSpanId(spanData.getParentSpanId().toLowerBase16());
+ }
+
+ return spanBuilder.build();
+ }
+
+ private static Span.TimeEvents toTimeEventsProto(
+ TimedEvents<Annotation> annotationTimedEvents,
+ TimedEvents<io.opencensus.trace.MessageEvent> messageEventTimedEvents) {
+ Span.TimeEvents.Builder timeEventsBuilder = Span.TimeEvents.newBuilder();
+ timeEventsBuilder.setDroppedAnnotationsCount(annotationTimedEvents.getDroppedEventsCount());
+ for (TimedEvent<Annotation> annotation : annotationTimedEvents.getEvents()) {
+ timeEventsBuilder.addTimeEvent(toTimeAnnotationProto(annotation));
+ }
+ timeEventsBuilder.setDroppedMessageEventsCount(messageEventTimedEvents.getDroppedEventsCount());
+ for (TimedEvent<io.opencensus.trace.MessageEvent> networkEvent :
+ messageEventTimedEvents.getEvents()) {
+ timeEventsBuilder.addTimeEvent(toTimeMessageEventProto(networkEvent));
+ }
+ return timeEventsBuilder.build();
+ }
+
+ private static TimeEvent toTimeAnnotationProto(TimedEvent<Annotation> timedEvent) {
+ TimeEvent.Builder timeEventBuilder =
+ TimeEvent.newBuilder().setTime(toTimestampProto(timedEvent.getTimestamp()));
+ Annotation annotation = timedEvent.getEvent();
+ timeEventBuilder.setAnnotation(
+ TimeEvent.Annotation.newBuilder()
+ .setDescription(toTruncatableStringProto(annotation.getDescription()))
+ .setAttributes(toAttributesBuilderProto(annotation.getAttributes(), 0))
+ .build());
+ return timeEventBuilder.build();
+ }
+
+ private static TimeEvent toTimeMessageEventProto(
+ TimedEvent<io.opencensus.trace.MessageEvent> timedEvent) {
+ TimeEvent.Builder timeEventBuilder =
+ TimeEvent.newBuilder().setTime(toTimestampProto(timedEvent.getTimestamp()));
+ io.opencensus.trace.MessageEvent messageEvent = timedEvent.getEvent();
+ timeEventBuilder.setMessageEvent(
+ TimeEvent.MessageEvent.newBuilder()
+ .setId(messageEvent.getMessageId())
+ .setCompressedSizeBytes(messageEvent.getCompressedMessageSize())
+ .setUncompressedSizeBytes(messageEvent.getUncompressedMessageSize())
+ .setType(toMessageEventTypeProto(messageEvent))
+ .build());
+ return timeEventBuilder.build();
+ }
+
+ private static TimeEvent.MessageEvent.Type toMessageEventTypeProto(
+ io.opencensus.trace.MessageEvent messageEvent) {
+ if (messageEvent.getType() == Type.RECEIVED) {
+ return MessageEvent.Type.RECEIVED;
+ } else {
+ return MessageEvent.Type.SENT;
+ }
+ }
+
+ // These are the attributes of the Span, where usually we may add more attributes like the agent.
+ private static Attributes toAttributesProto(
+ io.opencensus.trace.export.SpanData.Attributes attributes,
+ Map<String, AttributeValue> resourceLabels) {
+ Attributes.Builder attributesBuilder =
+ toAttributesBuilderProto(
+ attributes.getAttributeMap(), attributes.getDroppedAttributesCount());
+ attributesBuilder.putAttributeMap(AGENT_LABEL_KEY, AGENT_LABEL_VALUE);
+ for (Entry<String, AttributeValue> entry : resourceLabels.entrySet()) {
+ attributesBuilder.putAttributeMap(entry.getKey(), entry.getValue());
+ }
+ return attributesBuilder.build();
+ }
+
+ private static Attributes.Builder toAttributesBuilderProto(
+ Map<String, io.opencensus.trace.AttributeValue> attributes, int droppedAttributesCount) {
+ Attributes.Builder attributesBuilder =
+ Attributes.newBuilder().setDroppedAttributesCount(droppedAttributesCount);
+ for (Map.Entry<String, io.opencensus.trace.AttributeValue> label : attributes.entrySet()) {
+ AttributeValue value = toAttributeValueProto(label.getValue());
+ if (value != null) {
+ attributesBuilder.putAttributeMap(mapKey(label.getKey()), value);
+ }
+ }
+ return attributesBuilder;
+ }
+
+ @VisibleForTesting
+ static Map<String, AttributeValue> getResourceLabels(
+ @javax.annotation.Nullable MonitoredResource resource) {
+ if (resource == null) {
+ return Collections.emptyMap();
+ }
+ Map<String, AttributeValue> resourceLabels = new HashMap<String, AttributeValue>();
+ ResourceType resourceType = resource.getResourceType();
+ switch (resourceType) {
+ case AWS_EC2_INSTANCE:
+ AwsEc2InstanceMonitoredResource awsEc2InstanceMonitoredResource =
+ (AwsEc2InstanceMonitoredResource) resource;
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "aws_account",
+ awsEc2InstanceMonitoredResource.getAccount());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "instance_id",
+ awsEc2InstanceMonitoredResource.getInstanceId());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "region",
+ "aws:" + awsEc2InstanceMonitoredResource.getRegion());
+ return Collections.unmodifiableMap(resourceLabels);
+ case GCP_GCE_INSTANCE:
+ GcpGceInstanceMonitoredResource gcpGceInstanceMonitoredResource =
+ (GcpGceInstanceMonitoredResource) resource;
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "project_id",
+ gcpGceInstanceMonitoredResource.getAccount());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "instance_id",
+ gcpGceInstanceMonitoredResource.getInstanceId());
+ putToResourceAttributeMap(
+ resourceLabels, resourceType, "zone", gcpGceInstanceMonitoredResource.getZone());
+ return Collections.unmodifiableMap(resourceLabels);
+ case GCP_GKE_CONTAINER:
+ GcpGkeContainerMonitoredResource gcpGkeContainerMonitoredResource =
+ (GcpGkeContainerMonitoredResource) resource;
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "project_id",
+ gcpGkeContainerMonitoredResource.getAccount());
+ putToResourceAttributeMap(
+ resourceLabels, resourceType, "location", gcpGkeContainerMonitoredResource.getZone());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "cluster_name",
+ gcpGkeContainerMonitoredResource.getClusterName());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "container_name",
+ gcpGkeContainerMonitoredResource.getContainerName());
+ putToResourceAttributeMap(
+ resourceLabels,
+ resourceType,
+ "namespace_name",
+ gcpGkeContainerMonitoredResource.getNamespaceId());
+ putToResourceAttributeMap(
+ resourceLabels, resourceType, "pod_name", gcpGkeContainerMonitoredResource.getPodId());
+ return Collections.unmodifiableMap(resourceLabels);
+ }
+ return Collections.emptyMap();
+ }
+
+ private static void putToResourceAttributeMap(
+ Map<String, AttributeValue> map,
+ ResourceType resourceType,
+ String attributeName,
+ String attributeValue) {
+ map.put(
+ createResourceLabelKey(resourceType, attributeName),
+ toStringAttributeValueProto(attributeValue));
+ }
+
+ @VisibleForTesting
+ static String createResourceLabelKey(ResourceType resourceType, String resourceAttribute) {
+ return String.format("g.co/r/%s/%s", mapToStringResourceType(resourceType), resourceAttribute);
+ }
+
+ private static String mapToStringResourceType(ResourceType resourceType) {
+ switch (resourceType) {
+ case GCP_GCE_INSTANCE:
+ return "gce_instance";
+ case GCP_GKE_CONTAINER:
+ return "k8s_container";
+ case AWS_EC2_INSTANCE:
+ return "aws_ec2_instance";
+ }
+ throw new IllegalArgumentException("Unknown resource type.");
+ }
+
+ @VisibleForTesting
+ static AttributeValue toStringAttributeValueProto(String value) {
+ return AttributeValue.newBuilder().setStringValue(toTruncatableStringProto(value)).build();
+ }
+
+ private static String mapKey(String key) {
+ if (HTTP_ATTRIBUTE_MAPPING.containsKey(key)) {
+ return HTTP_ATTRIBUTE_MAPPING.get(key);
+ } else {
+ return key;
+ }
+ }
+
+ private static Status toStatusProto(io.opencensus.trace.Status status) {
+ Status.Builder statusBuilder = Status.newBuilder().setCode(status.getCanonicalCode().value());
+ if (status.getDescription() != null) {
+ statusBuilder.setMessage(status.getDescription());
+ }
+ return statusBuilder.build();
+ }
+
+ private static TruncatableString toTruncatableStringProto(String string) {
+ return TruncatableString.newBuilder().setValue(string).setTruncatedByteCount(0).build();
+ }
+
+ private static com.google.protobuf.Timestamp toTimestampProto(Timestamp timestamp) {
+ return com.google.protobuf.Timestamp.newBuilder()
+ .setSeconds(timestamp.getSeconds())
+ .setNanos(timestamp.getNanos())
+ .build();
+ }
+
+ @javax.annotation.Nullable
+ private static AttributeValue toAttributeValueProto(
+ io.opencensus.trace.AttributeValue attributeValue) {
+ return attributeValue.match(
+ stringAttributeValueFunction,
+ booleanAttributeValueFunction,
+ longAttributeValueFunction,
+ doubleAttributeValueFunction,
+ Functions.</*@Nullable*/ AttributeValue>returnNull());
+ }
+
+ private static Link.Type toLinkTypeProto(io.opencensus.trace.Link.Type type) {
+ if (type == io.opencensus.trace.Link.Type.PARENT_LINKED_SPAN) {
+ return Link.Type.PARENT_LINKED_SPAN;
+ } else {
+ return Link.Type.CHILD_LINKED_SPAN;
+ }
+ }
+
+ private static String toDisplayName(String spanName, @javax.annotation.Nullable Kind spanKind) {
+ if (spanKind == Kind.SERVER && !spanName.startsWith(SERVER_PREFIX)) {
+ return SERVER_PREFIX + spanName;
+ }
+
+ if (spanKind == Kind.CLIENT && !spanName.startsWith(CLIENT_PREFIX)) {
+ return CLIENT_PREFIX + spanName;
+ }
+
+ return spanName;
+ }
+
+ private static Link toLinkProto(io.opencensus.trace.Link link) {
+ checkNotNull(link);
+ return Link.newBuilder()
+ .setTraceId(link.getTraceId().toLowerBase16())
+ .setSpanId(link.getSpanId().toLowerBase16())
+ .setType(toLinkTypeProto(link.getType()))
+ .setAttributes(toAttributesBuilderProto(link.getAttributes(), 0))
+ .build();
+ }
+
+ private static Links toLinksProto(io.opencensus.trace.export.SpanData.Links links) {
+ final Links.Builder linksBuilder =
+ Links.newBuilder().setDroppedLinksCount(links.getDroppedLinksCount());
+ for (io.opencensus.trace.Link link : links.getLinks()) {
+ linksBuilder.addLink(toLinkProto(link));
+ }
+ return linksBuilder.build();
+ }
+
+ @Override
+ public void export(Collection<SpanData> spanDataList) {
+ // Start a new span with explicit 1/10000 sampling probability to avoid the case when user
+ // sets the default sampler to always sample and we get the gRPC span of the stackdriver
+ // export call always sampled and go to an infinite loop.
+ Scope scope =
+ tracer
+ .spanBuilder("ExportStackdriverTraces")
+ .setSampler(probabilitySampler)
+ .setRecordEvents(true)
+ .startScopedSpan();
+ try {
+ List<Span> spans = new ArrayList<>(spanDataList.size());
+ for (SpanData spanData : spanDataList) {
+ spans.add(generateSpan(spanData, RESOURCE_LABELS));
+ }
+ // Sync call because it is already called for a batch of data, and on a separate thread.
+ // TODO(bdrutu): Consider to make this async in the future.
+ traceServiceClient.batchWriteSpans(projectName, spans);
+ } finally {
+ scope.close();
+ }
+ }
+}
diff --git a/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceConfigurationTest.java b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceConfigurationTest.java
new file mode 100644
index 00000000..6926e869
--- /dev/null
+++ b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceConfigurationTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * 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 io.opencensus.exporter.trace.stackdriver;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.auth.Credentials;
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import java.util.Date;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link StackdriverTraceConfiguration}. */
+@RunWith(JUnit4.class)
+public class StackdriverTraceConfigurationTest {
+
+ private static final Credentials FAKE_CREDENTIALS =
+ GoogleCredentials.newBuilder().setAccessToken(new AccessToken("fake", new Date(100))).build();
+ private static final String PROJECT_ID = "project";
+
+ @Test
+ public void defaultConfiguration() {
+ StackdriverTraceConfiguration configuration = StackdriverTraceConfiguration.builder().build();
+ assertThat(configuration.getCredentials()).isNull();
+ assertThat(configuration.getProjectId()).isNull();
+ }
+
+ @Test
+ public void updateAll() {
+ StackdriverTraceConfiguration configuration =
+ StackdriverTraceConfiguration.builder()
+ .setCredentials(FAKE_CREDENTIALS)
+ .setProjectId(PROJECT_ID)
+ .build();
+ assertThat(configuration.getCredentials()).isEqualTo(FAKE_CREDENTIALS);
+ assertThat(configuration.getProjectId()).isEqualTo(PROJECT_ID);
+ }
+}
diff --git a/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceExporterTest.java b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceExporterTest.java
new file mode 100644
index 00000000..6a12a899
--- /dev/null
+++ b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverTraceExporterTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2017, OpenCensus Authors
+ *
+ * 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 io.opencensus.exporter.trace.stackdriver;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.verify;
+
+import io.opencensus.trace.export.SpanExporter;
+import io.opencensus.trace.export.SpanExporter.Handler;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link StackdriverTraceExporter}. */
+@RunWith(JUnit4.class)
+public class StackdriverTraceExporterTest {
+ @Mock private SpanExporter spanExporter;
+ @Mock private Handler handler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void registerUnregisterStackdriverExporter() {
+ StackdriverTraceExporter.register(spanExporter, handler);
+ verify(spanExporter)
+ .registerHandler(
+ eq("io.opencensus.exporter.trace.stackdriver.StackdriverTraceExporter"), same(handler));
+ StackdriverTraceExporter.unregister(spanExporter);
+ verify(spanExporter)
+ .unregisterHandler(eq("io.opencensus.exporter.trace.stackdriver.StackdriverTraceExporter"));
+ }
+}
diff --git a/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerExportTest.java b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerExportTest.java
new file mode 100644
index 00000000..32458597
--- /dev/null
+++ b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerExportTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * 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 io.opencensus.exporter.trace.stackdriver;
+
+import static org.mockito.Mockito.when;
+
+import com.google.cloud.trace.v2.TraceServiceClient;
+import com.google.cloud.trace.v2.stub.TraceServiceStub;
+import io.opencensus.trace.export.SpanData;
+import java.util.Collection;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for exporting in {@link StackdriverV2ExporterHandler}. */
+@RunWith(JUnit4.class)
+public final class StackdriverV2ExporterHandlerExportTest {
+ private static final String PROJECT_ID = "PROJECT_ID";
+ // mock the service stub to provide a fake trace service.
+ @Mock private TraceServiceStub traceServiceStub;
+ private TraceServiceClient traceServiceClient;
+ @Rule public final ExpectedException thrown = ExpectedException.none();
+
+ private StackdriverV2ExporterHandler handler;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ // TODO(@Hailong): TraceServiceClient.create(TraceServiceStub) is a beta API and might change
+ // in the future.
+ traceServiceClient = TraceServiceClient.create(traceServiceStub);
+ handler = new StackdriverV2ExporterHandler(PROJECT_ID, traceServiceClient);
+ }
+
+ @Test
+ public void export() {
+ when(traceServiceStub.batchWriteSpansCallable())
+ .thenThrow(new RuntimeException("TraceServiceStub called"));
+ Collection<SpanData> spanDataList = Collections.<SpanData>emptyList();
+ thrown.expect(RuntimeException.class);
+ thrown.expectMessage("TraceServiceStub called");
+ handler.export(spanDataList);
+ }
+}
diff --git a/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerProtoTest.java b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerProtoTest.java
new file mode 100644
index 00000000..8b28dc06
--- /dev/null
+++ b/exporters/trace/stackdriver/src/test/java/io/opencensus/exporter/trace/stackdriver/StackdriverV2ExporterHandlerProtoTest.java
@@ -0,0 +1,489 @@
+/*
+ * Copyright 2018, OpenCensus Authors
+ *
+ * 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 io.opencensus.exporter.trace.stackdriver;
+
+import static com.google.common.truth.Truth.assertThat;
+import static io.opencensus.contrib.monitoredresource.util.ResourceType.AWS_EC2_INSTANCE;
+import static io.opencensus.contrib.monitoredresource.util.ResourceType.GCP_GCE_INSTANCE;
+import static io.opencensus.contrib.monitoredresource.util.ResourceType.GCP_GKE_CONTAINER;
+import static io.opencensus.exporter.trace.stackdriver.StackdriverV2ExporterHandler.createResourceLabelKey;
+import static io.opencensus.exporter.trace.stackdriver.StackdriverV2ExporterHandler.toStringAttributeValueProto;
+
+import com.google.auth.Credentials;
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.cloudtrace.v2.AttributeValue;
+import com.google.devtools.cloudtrace.v2.Span;
+import com.google.devtools.cloudtrace.v2.Span.TimeEvent;
+import com.google.devtools.cloudtrace.v2.Span.TimeEvent.MessageEvent;
+import com.google.devtools.cloudtrace.v2.StackTrace;
+import com.google.devtools.cloudtrace.v2.TruncatableString;
+import com.google.protobuf.Int32Value;
+import io.opencensus.common.Timestamp;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource.AwsEc2InstanceMonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource.GcpGceInstanceMonitoredResource;
+import io.opencensus.contrib.monitoredresource.util.MonitoredResource.GcpGkeContainerMonitoredResource;
+import io.opencensus.trace.Annotation;
+import io.opencensus.trace.Link;
+import io.opencensus.trace.Span.Kind;
+import io.opencensus.trace.SpanContext;
+import io.opencensus.trace.SpanId;
+import io.opencensus.trace.Status;
+import io.opencensus.trace.TraceId;
+import io.opencensus.trace.TraceOptions;
+import io.opencensus.trace.export.SpanData;
+import io.opencensus.trace.export.SpanData.TimedEvent;
+import io.opencensus.trace.export.SpanData.TimedEvents;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for proto conversions in {@link StackdriverV2ExporterHandler}. */
+@RunWith(JUnit4.class)
+public final class StackdriverV2ExporterHandlerProtoTest {
+
+ private static final Credentials FAKE_CREDENTIALS =
+ GoogleCredentials.newBuilder().setAccessToken(new AccessToken("fake", new Date(100))).build();
+ // OpenCensus constants
+ private static final Timestamp startTimestamp = Timestamp.create(123, 456);
+ private static final Timestamp eventTimestamp1 = Timestamp.create(123, 457);
+ private static final Timestamp eventTimestamp2 = Timestamp.create(123, 458);
+ private static final Timestamp eventTimestamp3 = Timestamp.create(123, 459);
+ private static final Timestamp endTimestamp = Timestamp.create(123, 460);
+
+ private static final String PROJECT_ID = "PROJECT_ID";
+ private static final String TRACE_ID = "4bf92f3577b34da6a3ce929d0e0e4736";
+ private static final String SPAN_ID = "24aa0b2d371f48c9";
+ private static final String PARENT_SPAN_ID = "71da8d631536f5f1";
+ private static final String SPAN_NAME = "MySpanName";
+ private static final String SD_SPAN_NAME =
+ String.format("projects/%s/traces/%s/spans/%s", PROJECT_ID, TRACE_ID, SPAN_ID);
+ private static final String ANNOTATION_TEXT = "MyAnnotationText";
+ private static final String ATTRIBUTE_KEY_1 = "MyAttributeKey1";
+ private static final String ATTRIBUTE_KEY_2 = "MyAttributeKey2";
+
+ private static final int DROPPED_ATTRIBUTES_COUNT = 1;
+ private static final int DROPPED_ANNOTATIONS_COUNT = 2;
+ private static final int DROPPED_NETWORKEVENTS_COUNT = 3;
+ private static final int DROPPED_LINKS_COUNT = 4;
+ private static final int CHILD_SPAN_COUNT = 13;
+
+ private static final Annotation annotation = Annotation.fromDescription(ANNOTATION_TEXT);
+ private static final io.opencensus.trace.MessageEvent recvMessageEvent =
+ io.opencensus.trace.MessageEvent.builder(io.opencensus.trace.MessageEvent.Type.RECEIVED, 1)
+ .build();
+ private static final io.opencensus.trace.MessageEvent sentMessageEvent =
+ io.opencensus.trace.MessageEvent.builder(io.opencensus.trace.MessageEvent.Type.SENT, 1)
+ .build();
+ private static final Status status = Status.DEADLINE_EXCEEDED.withDescription("TooSlow");
+ private static final SpanId parentSpanId = SpanId.fromLowerBase16(PARENT_SPAN_ID);
+ private static final SpanId spanId = SpanId.fromLowerBase16(SPAN_ID);
+ private static final TraceId traceId = TraceId.fromLowerBase16(TRACE_ID);
+ private static final TraceOptions traceOptions = TraceOptions.DEFAULT;
+ private static final SpanContext spanContext = SpanContext.create(traceId, spanId, traceOptions);
+
+ private static final List<TimedEvent<Annotation>> annotationsList =
+ ImmutableList.of(
+ SpanData.TimedEvent.create(eventTimestamp1, annotation),
+ SpanData.TimedEvent.create(eventTimestamp3, annotation));
+ private static final List<TimedEvent<io.opencensus.trace.MessageEvent>> networkEventsList =
+ ImmutableList.of(
+ SpanData.TimedEvent.create(eventTimestamp1, recvMessageEvent),
+ SpanData.TimedEvent.create(eventTimestamp2, sentMessageEvent));
+ private static final List<Link> linksList =
+ ImmutableList.of(Link.fromSpanContext(spanContext, Link.Type.CHILD_LINKED_SPAN));
+
+ private static final SpanData.Attributes attributes =
+ SpanData.Attributes.create(
+ ImmutableMap.of(
+ ATTRIBUTE_KEY_1,
+ io.opencensus.trace.AttributeValue.longAttributeValue(10L),
+ ATTRIBUTE_KEY_2,
+ io.opencensus.trace.AttributeValue.booleanAttributeValue(true)),
+ DROPPED_ATTRIBUTES_COUNT);
+ private static final TimedEvents<Annotation> annotations =
+ TimedEvents.create(annotationsList, DROPPED_ANNOTATIONS_COUNT);
+ private static final TimedEvents<io.opencensus.trace.MessageEvent> messageEvents =
+ TimedEvents.create(networkEventsList, DROPPED_NETWORKEVENTS_COUNT);
+ private static final SpanData.Links links = SpanData.Links.create(linksList, DROPPED_LINKS_COUNT);
+ private static final Map<String, AttributeValue> EMPTY_RESOURCE_LABELS = Collections.emptyMap();
+ private static final AwsEc2InstanceMonitoredResource AWS_EC2_INSTANCE_MONITORED_RESOURCE =
+ AwsEc2InstanceMonitoredResource.create("my-project", "my-instance", "us-east-1");
+ private static final GcpGceInstanceMonitoredResource GCP_GCE_INSTANCE_MONITORED_RESOURCE =
+ GcpGceInstanceMonitoredResource.create("my-project", "my-instance", "us-east1");
+ private static final GcpGkeContainerMonitoredResource GCP_GKE_CONTAINER_MONITORED_RESOURCE =
+ GcpGkeContainerMonitoredResource.create(
+ "my-project", "cluster", "container", "namespace", "my-instance", "pod", "us-east1");
+ private static final ImmutableMap<String, AttributeValue> AWS_RESOURCE_LABELS =
+ ImmutableMap.of(
+ createResourceLabelKey(AWS_EC2_INSTANCE, "aws_account"),
+ toStringAttributeValueProto("my-project"),
+ createResourceLabelKey(AWS_EC2_INSTANCE, "instance_id"),
+ toStringAttributeValueProto("my-instance"),
+ createResourceLabelKey(AWS_EC2_INSTANCE, "region"),
+ toStringAttributeValueProto("aws:us-east-1"));
+ private static final ImmutableMap<String, AttributeValue> GCE_RESOURCE_LABELS =
+ ImmutableMap.of(
+ createResourceLabelKey(GCP_GCE_INSTANCE, "project_id"),
+ toStringAttributeValueProto("my-project"),
+ createResourceLabelKey(GCP_GCE_INSTANCE, "instance_id"),
+ toStringAttributeValueProto("my-instance"),
+ createResourceLabelKey(GCP_GCE_INSTANCE, "zone"),
+ toStringAttributeValueProto("us-east1"));
+ private static final ImmutableMap<String, AttributeValue> GKE_RESOURCE_LABELS =
+ ImmutableMap.<String, AttributeValue>builder()
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "project_id"),
+ toStringAttributeValueProto("my-project"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "cluster_name"),
+ toStringAttributeValueProto("cluster"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "container_name"),
+ toStringAttributeValueProto("container"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "namespace_name"),
+ toStringAttributeValueProto("namespace"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "pod_name"),
+ toStringAttributeValueProto("pod"))
+ .put(
+ createResourceLabelKey(GCP_GKE_CONTAINER, "location"),
+ toStringAttributeValueProto("us-east1"))
+ .build();
+
+ private StackdriverV2ExporterHandler handler;
+
+ @Before
+ public void setUp() throws IOException {
+ handler = StackdriverV2ExporterHandler.createWithCredentials(FAKE_CREDENTIALS, PROJECT_ID);
+ }
+
+ @Test
+ public void generateSpan() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ /* hasRemoteParent= */ true,
+ SPAN_NAME,
+ null,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ TimeEvent annotationTimeEvent1 =
+ TimeEvent.newBuilder()
+ .setAnnotation(
+ TimeEvent.Annotation.newBuilder()
+ .setDescription(
+ TruncatableString.newBuilder().setValue(ANNOTATION_TEXT).build())
+ .setAttributes(Span.Attributes.newBuilder().build())
+ .build())
+ .setTime(
+ com.google.protobuf.Timestamp.newBuilder()
+ .setSeconds(eventTimestamp1.getSeconds())
+ .setNanos(eventTimestamp1.getNanos())
+ .build())
+ .build();
+ TimeEvent annotationTimeEvent2 =
+ TimeEvent.newBuilder()
+ .setAnnotation(
+ TimeEvent.Annotation.newBuilder()
+ .setDescription(
+ TruncatableString.newBuilder().setValue(ANNOTATION_TEXT).build())
+ .setAttributes(Span.Attributes.newBuilder().build())
+ .build())
+ .setTime(
+ com.google.protobuf.Timestamp.newBuilder()
+ .setSeconds(eventTimestamp3.getSeconds())
+ .setNanos(eventTimestamp3.getNanos())
+ .build())
+ .build();
+
+ TimeEvent sentTimeEvent =
+ TimeEvent.newBuilder()
+ .setMessageEvent(
+ TimeEvent.MessageEvent.newBuilder()
+ .setType(MessageEvent.Type.SENT)
+ .setId(sentMessageEvent.getMessageId()))
+ .setTime(
+ com.google.protobuf.Timestamp.newBuilder()
+ .setSeconds(eventTimestamp2.getSeconds())
+ .setNanos(eventTimestamp2.getNanos())
+ .build())
+ .build();
+ TimeEvent recvTimeEvent =
+ TimeEvent.newBuilder()
+ .setMessageEvent(
+ TimeEvent.MessageEvent.newBuilder()
+ .setType(MessageEvent.Type.RECEIVED)
+ .setId(recvMessageEvent.getMessageId()))
+ .setTime(
+ com.google.protobuf.Timestamp.newBuilder()
+ .setSeconds(eventTimestamp1.getSeconds())
+ .setNanos(eventTimestamp1.getNanos())
+ .build())
+ .build();
+
+ Span.Links spanLinks =
+ Span.Links.newBuilder()
+ .setDroppedLinksCount(DROPPED_LINKS_COUNT)
+ .addLink(
+ Span.Link.newBuilder()
+ .setType(Span.Link.Type.CHILD_LINKED_SPAN)
+ .setTraceId(TRACE_ID)
+ .setSpanId(SPAN_ID)
+ .setAttributes(Span.Attributes.newBuilder().build())
+ .build())
+ .build();
+
+ com.google.rpc.Status spanStatus =
+ com.google.rpc.Status.newBuilder()
+ .setCode(com.google.rpc.Code.DEADLINE_EXCEEDED.getNumber())
+ .setMessage("TooSlow")
+ .build();
+
+ com.google.protobuf.Timestamp startTime =
+ com.google.protobuf.Timestamp.newBuilder()
+ .setSeconds(startTimestamp.getSeconds())
+ .setNanos(startTimestamp.getNanos())
+ .build();
+ com.google.protobuf.Timestamp endTime =
+ com.google.protobuf.Timestamp.newBuilder()
+ .setSeconds(endTimestamp.getSeconds())
+ .setNanos(endTimestamp.getNanos())
+ .build();
+
+ Span span = handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS);
+ assertThat(span.getName()).isEqualTo(SD_SPAN_NAME);
+ assertThat(span.getSpanId()).isEqualTo(SPAN_ID);
+ assertThat(span.getParentSpanId()).isEqualTo(PARENT_SPAN_ID);
+ assertThat(span.getDisplayName())
+ .isEqualTo(TruncatableString.newBuilder().setValue(SPAN_NAME).build());
+ assertThat(span.getStartTime()).isEqualTo(startTime);
+ assertThat(span.getEndTime()).isEqualTo(endTime);
+ assertThat(span.getAttributes().getDroppedAttributesCount())
+ .isEqualTo(DROPPED_ATTRIBUTES_COUNT);
+ // The generated attributes map contains more values (e.g. agent). We only test what we added.
+ assertThat(span.getAttributes().getAttributeMapMap())
+ .containsEntry(ATTRIBUTE_KEY_1, AttributeValue.newBuilder().setIntValue(10L).build());
+ assertThat(span.getAttributes().getAttributeMapMap())
+ .containsEntry(ATTRIBUTE_KEY_2, AttributeValue.newBuilder().setBoolValue(true).build());
+ // TODO(@Hailong): add stack trace test in the future.
+ assertThat(span.getStackTrace()).isEqualTo(StackTrace.newBuilder().build());
+ assertThat(span.getTimeEvents().getDroppedMessageEventsCount())
+ .isEqualTo(DROPPED_NETWORKEVENTS_COUNT);
+ assertThat(span.getTimeEvents().getDroppedAnnotationsCount())
+ .isEqualTo(DROPPED_ANNOTATIONS_COUNT);
+ assertThat(span.getTimeEvents().getTimeEventList())
+ .containsAllOf(annotationTimeEvent1, annotationTimeEvent2, sentTimeEvent, recvTimeEvent);
+ assertThat(span.getLinks()).isEqualTo(spanLinks);
+ assertThat(span.getStatus()).isEqualTo(spanStatus);
+ assertThat(span.getSameProcessAsParentSpan())
+ .isEqualTo(com.google.protobuf.BoolValue.newBuilder().build());
+ assertThat(span.getChildSpanCount())
+ .isEqualTo(Int32Value.newBuilder().setValue(CHILD_SPAN_COUNT).build());
+ }
+
+ @Test
+ public void getResourceLabels_AwsEc2ResourceLabels() {
+ testGetResourceLabels(AWS_EC2_INSTANCE_MONITORED_RESOURCE, AWS_RESOURCE_LABELS);
+ }
+
+ @Test
+ public void getResourceLabels_GceResourceLabels() {
+ testGetResourceLabels(GCP_GCE_INSTANCE_MONITORED_RESOURCE, GCE_RESOURCE_LABELS);
+ }
+
+ @Test
+ public void getResourceLabels_GkeResourceLabels() {
+ testGetResourceLabels(GCP_GKE_CONTAINER_MONITORED_RESOURCE, GKE_RESOURCE_LABELS);
+ }
+
+ private static void testGetResourceLabels(
+ MonitoredResource resource, Map<String, AttributeValue> expectedLabels) {
+ Map<String, AttributeValue> actualLabels =
+ StackdriverV2ExporterHandler.getResourceLabels(resource);
+ assertThat(actualLabels).containsExactlyEntriesIn(expectedLabels);
+ }
+
+ @Test
+ public void generateSpan_WithResourceLabels() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ /* hasRemoteParent= */ true,
+ SPAN_NAME,
+ null,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ Span span = handler.generateSpan(spanData, AWS_RESOURCE_LABELS);
+ Map<String, AttributeValue> attributeMap = span.getAttributes().getAttributeMapMap();
+ assertThat(attributeMap.entrySet()).containsAllIn(AWS_RESOURCE_LABELS.entrySet());
+ }
+
+ @Test
+ public void mapHttpAttributes() {
+ Map<String, io.opencensus.trace.AttributeValue> attributesMap =
+ new HashMap<String, io.opencensus.trace.AttributeValue>();
+
+ attributesMap.put("http.host", io.opencensus.trace.AttributeValue.stringAttributeValue("host"));
+ attributesMap.put(
+ "http.method", io.opencensus.trace.AttributeValue.stringAttributeValue("method"));
+ attributesMap.put("http.path", io.opencensus.trace.AttributeValue.stringAttributeValue("path"));
+ attributesMap.put(
+ "http.route", io.opencensus.trace.AttributeValue.stringAttributeValue("route"));
+ attributesMap.put(
+ "http.user_agent", io.opencensus.trace.AttributeValue.stringAttributeValue("user_agent"));
+ attributesMap.put(
+ "http.status_code", io.opencensus.trace.AttributeValue.longAttributeValue(200L));
+ SpanData.Attributes httpAttributes = SpanData.Attributes.create(attributesMap, 0);
+
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ /* hasRemoteParent= */ true,
+ SPAN_NAME,
+ startTimestamp,
+ httpAttributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+
+ Span span = handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS);
+ Map<String, AttributeValue> attributes = span.getAttributes().getAttributeMapMap();
+
+ assertThat(attributes).containsEntry("/http/host", toStringAttributeValueProto("host"));
+ assertThat(attributes).containsEntry("/http/method", toStringAttributeValueProto("method"));
+ assertThat(attributes).containsEntry("/http/path", toStringAttributeValueProto("path"));
+ assertThat(attributes).containsEntry("/http/route", toStringAttributeValueProto("route"));
+ assertThat(attributes)
+ .containsEntry("/http/user_agent", toStringAttributeValueProto("user_agent"));
+ assertThat(attributes)
+ .containsEntry("/http/status_code", AttributeValue.newBuilder().setIntValue(200L).build());
+ }
+
+ @Test
+ public void generateSpanName_ForServer() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ /* hasRemoteParent= */ true,
+ SPAN_NAME,
+ Kind.SERVER,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ assertThat(handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS).getDisplayName().getValue())
+ .isEqualTo("Recv." + SPAN_NAME);
+ }
+
+ @Test
+ public void generateSpanName_ForServerWithRecv() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ /* hasRemoteParent= */ true,
+ "Recv." + SPAN_NAME,
+ Kind.SERVER,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ assertThat(handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS).getDisplayName().getValue())
+ .isEqualTo("Recv." + SPAN_NAME);
+ }
+
+ @Test
+ public void generateSpanName_ForClient() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ /* hasRemoteParent= */ true,
+ SPAN_NAME,
+ Kind.CLIENT,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ assertThat(handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS).getDisplayName().getValue())
+ .isEqualTo("Sent." + SPAN_NAME);
+ }
+
+ @Test
+ public void generateSpanName_ForClientWithSent() {
+ SpanData spanData =
+ SpanData.create(
+ spanContext,
+ parentSpanId,
+ /* hasRemoteParent= */ true,
+ "Sent." + SPAN_NAME,
+ Kind.CLIENT,
+ startTimestamp,
+ attributes,
+ annotations,
+ messageEvents,
+ links,
+ CHILD_SPAN_COUNT,
+ status,
+ endTimestamp);
+ assertThat(handler.generateSpan(spanData, EMPTY_RESOURCE_LABELS).getDisplayName().getValue())
+ .isEqualTo("Sent." + SPAN_NAME);
+ }
+}