Skip to content

Commit 70e3b08

Browse files
authored
Fix trace exporter (GoogleCloudPlatform#193)
* Add initial implementation for deferred trace exporter * Update the trace config to lazy initialize Project ID * Udpate return types to interface types * Refactor: Simplify user facing API * Fix IllegalStateException when calling getProjectId * Fix header check * Make 'export' method thread safe * Add unit tests for TraceExporter * Run spotlessApply to fix style issues * Update documentation for the deferred approach * Make the TraceExporter thread-safe * Change copyright year in file header * Refactor: create a separate class for no-op TraceExporter * Update documentation for TraceExporter * Add class documentation for InternalTraceExporter * fix style issues * Fix locally failing TraceConfiguration test * Remove opencensus failure test * Update license headers for 2023 * Removes the use of ThrottlingLogger * Refactor: update deferred execution to prevent API changes * Fix locally failing tests * Fix unit tests via mocking * Add opencensus-shim as a test dependency to verify fix * Mock ServiceOptions for getDefaultProjectId * Fix missing credentials on non-GCP environment * Refactor: address comments * Fix syncronization logic and thread-safety * Simplify thread safe logic by moving to memoized Supplier * Unit test TraceExporter failure case * Update javadoc for Noop exporter * Update exception type for null or empty project ID * Rename tests and add additional context in logs * Make noop exporter return success code * Reduce scope of unit tests to TraceConfiguration only
1 parent e6b8b1e commit 70e3b08

File tree

10 files changed

+466
-164
lines changed

10 files changed

+466
-164
lines changed

build.gradle

+3-1
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ subprojects {
120120
springOtelVersion = '1.0.0-M8'
121121
cloudEventsCoreVersion = '2.4.0'
122122
cloudFunctionsFrameworkApiVersion = '1.0.4'
123+
opencensusShimVersion = '1.23.1'
123124

124125
libraries = [
125126
auto_service_annotations : "com.google.auto.service:auto-service-annotations:${autoServiceVersion}",
@@ -160,7 +161,8 @@ subprojects {
160161
slf4j_simple: "org.slf4j:slf4j-simple:${slf4jVersion}",
161162
opentelemetry_sdk_testing: "io.opentelemetry:opentelemetry-sdk-testing:${openTelemetryVersion}",
162163
test_containers: "org.testcontainers:testcontainers:${testContainersVersion}",
163-
wiremock : "com.github.tomakehurst:wiremock-jre8:${wiremockVersion}"
164+
wiremock : "com.github.tomakehurst:wiremock-jre8:${wiremockVersion}",
165+
opencensus_shim: "io.opentelemetry:opentelemetry-opencensus-shim:${opencensusShimVersion}-alpha",
164166
]
165167
}
166168

examples/trace/src/main/java/com/google/cloud/opentelemetry/example/trace/TraceExporterExample.java

+8-14
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import io.opentelemetry.sdk.trace.SdkTracerProvider;
2626
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
2727
import io.opentelemetry.sdk.trace.export.SpanExporter;
28-
import java.io.IOException;
2928
import java.time.Duration;
3029
import java.util.Random;
3130

@@ -40,19 +39,14 @@ private static OpenTelemetrySdk setupTraceExporter() {
4039
TraceConfiguration configuration =
4140
TraceConfiguration.builder().setDeadline(Duration.ofMillis(30000)).build();
4241

43-
try {
44-
SpanExporter traceExporter = TraceExporter.createWithConfiguration(configuration);
45-
// Register the TraceExporter with OpenTelemetry
46-
return OpenTelemetrySdk.builder()
47-
.setTracerProvider(
48-
SdkTracerProvider.builder()
49-
.addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build())
50-
.build())
51-
.buildAndRegisterGlobal();
52-
} catch (IOException e) {
53-
System.out.println("Uncaught Exception");
54-
return null;
55-
}
42+
SpanExporter traceExporter = TraceExporter.createWithConfiguration(configuration);
43+
// Register the TraceExporter with OpenTelemetry
44+
return OpenTelemetrySdk.builder()
45+
.setTracerProvider(
46+
SdkTracerProvider.builder()
47+
.addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build())
48+
.build())
49+
.buildAndRegisterGlobal();
5650
}
5751

5852
private static void myUseCase(String description) {

exporters/auto/src/main/java/com/google/cloud/opentelemetry/auto/GoogleCloudSpanExporterFactory.java

+1-6
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,13 @@
2020
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
2121
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider;
2222
import io.opentelemetry.sdk.trace.export.SpanExporter;
23-
import java.io.IOException;
2423

2524
@AutoService(ConfigurableSpanExporterProvider.class)
2625
public class GoogleCloudSpanExporterFactory implements ConfigurableSpanExporterProvider {
2726

2827
@Override
2928
public SpanExporter createExporter(ConfigProperties config) {
30-
try {
31-
return TraceExporter.createWithDefaultConfiguration();
32-
} catch (IOException ex) {
33-
throw new RuntimeException(ex);
34-
}
29+
return TraceExporter.createWithDefaultConfiguration();
3530
}
3631

3732
@Override

exporters/trace/build.gradle

+7-1
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,27 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16+
plugins {
17+
id 'com.github.johnrengelman.shadow'
18+
}
1619
description = 'Cloud Trace Exporter for OpenTelemetry'
1720

1821
dependencies {
1922
api(libraries.auto_value_annotations)
20-
annotationProcessor(libraries.auto_value)
23+
api(libraries.slf4j)
2124
api(libraries.opentelemetry_api)
2225
api(libraries.opentelemetry_sdk)
2326
api(libraries.google_cloud_core)
2427
api(libraries.google_cloud_trace)
2528
api(libraries.google_cloud_trace_grpc)
29+
annotationProcessor(libraries.auto_value)
2630
implementation(libraries.opentelemetry_semconv)
2731
implementation(project(':shared-resourcemapping'))
2832
testImplementation(testLibraries.junit)
2933
testImplementation(testLibraries.opentelemetry_sdk_testing)
3034
testImplementation(testLibraries.test_containers)
35+
testImplementation(testLibraries.mockito)
36+
testImplementation(testLibraries.opencensus_shim)
3137
testAnnotationProcessor(libraries.auto_value)
3238
}
3339

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/*
2+
* Copyright 2023 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.opentelemetry.trace;
17+
18+
import static com.google.api.client.util.Preconditions.checkNotNull;
19+
20+
import com.google.api.gax.core.FixedCredentialsProvider;
21+
import com.google.api.gax.core.NoCredentialsProvider;
22+
import com.google.api.gax.grpc.GrpcTransportChannel;
23+
import com.google.api.gax.rpc.FixedTransportChannelProvider;
24+
import com.google.api.gax.rpc.HeaderProvider;
25+
import com.google.auth.Credentials;
26+
import com.google.auth.oauth2.GoogleCredentials;
27+
import com.google.cloud.trace.v2.TraceServiceClient;
28+
import com.google.cloud.trace.v2.TraceServiceSettings;
29+
import com.google.cloud.trace.v2.stub.TraceServiceStub;
30+
import com.google.common.collect.ImmutableMap;
31+
import com.google.devtools.cloudtrace.v2.AttributeValue;
32+
import com.google.devtools.cloudtrace.v2.ProjectName;
33+
import com.google.devtools.cloudtrace.v2.Span;
34+
import io.grpc.ManagedChannelBuilder;
35+
import io.opentelemetry.sdk.common.CompletableResultCode;
36+
import io.opentelemetry.sdk.trace.data.SpanData;
37+
import io.opentelemetry.sdk.trace.export.SpanExporter;
38+
import java.io.IOException;
39+
import java.util.ArrayList;
40+
import java.util.Collection;
41+
import java.util.List;
42+
import java.util.Map;
43+
44+
/**
45+
* This class encapsulates internal implementation details for exporting spans for applications
46+
* running on Google cloud environment. This class should not be exposed to the end user for direct
47+
* usage.
48+
*/
49+
class InternalTraceExporter implements SpanExporter {
50+
51+
private final CloudTraceClient cloudTraceClient;
52+
private final ProjectName projectName;
53+
private final String projectId;
54+
private final TraceTranslator translator;
55+
56+
private static final Map<String, String> HEADERS =
57+
Map.of("User-Agent", "opentelemetry-operations-java/" + TraceVersions.EXPORTER_VERSION);
58+
private static final HeaderProvider HEADER_PROVIDER = () -> HEADERS;
59+
60+
private static InternalTraceExporter createWithClient(
61+
String projectId,
62+
CloudTraceClient cloudTraceClient,
63+
ImmutableMap<String, String> attributeMappings,
64+
Map<String, AttributeValue> fixedAttributes) {
65+
return new InternalTraceExporter(
66+
projectId, cloudTraceClient, attributeMappings, fixedAttributes);
67+
}
68+
69+
static SpanExporter createWithConfiguration(TraceConfiguration configuration) throws IOException {
70+
String projectId = configuration.getProjectId();
71+
TraceServiceStub stub = configuration.getTraceServiceStub();
72+
73+
// TODO: Remove stub - tracked in issue #198
74+
if (stub == null) {
75+
TraceServiceSettings.Builder builder = TraceServiceSettings.newBuilder();
76+
77+
// We only use the batchWriteSpans API in this exporter.
78+
builder
79+
.batchWriteSpansSettings()
80+
.setSimpleTimeoutNoRetries(
81+
org.threeten.bp.Duration.ofMillis(configuration.getDeadline().toMillis()));
82+
// For testing, we need to hack around our gRPC config.
83+
if (configuration.getInsecureEndpoint()) {
84+
builder.setCredentialsProvider(NoCredentialsProvider.create());
85+
builder.setTransportChannelProvider(
86+
FixedTransportChannelProvider.create(
87+
GrpcTransportChannel.create(
88+
ManagedChannelBuilder.forTarget(configuration.getTraceServiceEndpoint())
89+
.usePlaintext()
90+
.build())));
91+
} else {
92+
Credentials credentials =
93+
configuration.getCredentials() == null
94+
? GoogleCredentials.getApplicationDefault()
95+
: configuration.getCredentials();
96+
builder.setCredentialsProvider(
97+
FixedCredentialsProvider.create(checkNotNull(credentials, "credentials")));
98+
builder.setEndpoint(configuration.getTraceServiceEndpoint());
99+
builder.setHeaderProvider(HEADER_PROVIDER);
100+
}
101+
102+
return new InternalTraceExporter(
103+
projectId,
104+
new CloudTraceClientImpl(TraceServiceClient.create(builder.build())),
105+
configuration.getAttributeMapping(),
106+
configuration.getFixedAttributes());
107+
}
108+
return InternalTraceExporter.createWithClient(
109+
projectId,
110+
new CloudTraceClientImpl(TraceServiceClient.create(stub)),
111+
configuration.getAttributeMapping(),
112+
configuration.getFixedAttributes());
113+
}
114+
115+
InternalTraceExporter(
116+
String projectId,
117+
CloudTraceClient cloudTraceClient,
118+
ImmutableMap<String, String> attributeMappings,
119+
Map<String, AttributeValue> fixedAttributes) {
120+
this.projectId = projectId;
121+
this.cloudTraceClient = cloudTraceClient;
122+
this.projectName = ProjectName.of(projectId);
123+
this.translator = new TraceTranslator(attributeMappings, fixedAttributes);
124+
}
125+
126+
@Override
127+
public CompletableResultCode flush() {
128+
// We do no exporter buffering of spans, so we're always flushed.
129+
return CompletableResultCode.ofSuccess();
130+
}
131+
132+
@Override
133+
public CompletableResultCode export(Collection<SpanData> spanDataList) {
134+
List<Span> spans = new ArrayList<>(spanDataList.size());
135+
for (SpanData spanData : spanDataList) {
136+
spans.add(translator.generateSpan(spanData, projectId));
137+
}
138+
139+
cloudTraceClient.batchWriteSpans(projectName, spans);
140+
return CompletableResultCode.ofSuccess();
141+
}
142+
143+
@Override
144+
public CompletableResultCode shutdown() {
145+
this.cloudTraceClient.shutdown();
146+
return CompletableResultCode.ofSuccess();
147+
}
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2023 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.opentelemetry.trace;
17+
18+
import io.opentelemetry.sdk.common.CompletableResultCode;
19+
import io.opentelemetry.sdk.trace.data.SpanData;
20+
import io.opentelemetry.sdk.trace.export.SpanExporter;
21+
import java.util.Collection;
22+
import javax.annotation.Nonnull;
23+
24+
/** A noop implementation of a {@link SpanExporter}. */
25+
final class NoopSpanExporter implements SpanExporter {
26+
27+
NoopSpanExporter() {
28+
// prevent explicit public call to default constructor
29+
}
30+
31+
/**
32+
* Noop implementation for exporting spans.
33+
*
34+
* @param spans The {@link Collection} of {@link SpanData} that need to be exported.
35+
* @return a success result code indicated via {@link CompletableResultCode#ofSuccess()}.
36+
*/
37+
@Override
38+
public CompletableResultCode export(@Nonnull Collection<SpanData> spans) {
39+
return CompletableResultCode.ofSuccess();
40+
}
41+
42+
/**
43+
* Noop implementation for flushing current spans.
44+
*
45+
* @return a success result code indicated via {@link CompletableResultCode#ofSuccess()}.
46+
*/
47+
@Override
48+
public CompletableResultCode flush() {
49+
return CompletableResultCode.ofSuccess();
50+
}
51+
52+
/**
53+
* Noop implementation for shutting down the current exporter.
54+
*
55+
* @return a success result code indicated via {@link CompletableResultCode#ofSuccess()}.
56+
*/
57+
@Override
58+
public CompletableResultCode shutdown() {
59+
return CompletableResultCode.ofSuccess();
60+
}
61+
}

0 commit comments

Comments
 (0)