Skip to content

Commit 338cb9e

Browse files
authored
Trace exporter improvements (GoogleCloudPlatform#147)
* Fixes GoogleCloudPlatform#142 - Trace attributes now include list-based attributes in JSON format. * Update trace translator to include resource + instrumentation library labels, in addition to other specified special labels. * Update metrics exporter to leverage shared trace translator. * Give user configuration flexibility over attribute renaming in the trace exporter. * Ensure resource attributes are copied to span attribtues as in go exporter. Update tests. * Update documentation to represent new trace exporer behavior. * Add basic propagator e2e test, and remove Resource defaults from other e2e tests. * Bump to latest test server version.
1 parent 273a9ac commit 338cb9e

File tree

21 files changed

+703
-187
lines changed

21 files changed

+703
-187
lines changed

cloudbuild-e2e-gce.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ steps:
3333

3434
logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
3535
substitutions:
36-
_TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.9.0
36+
_TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.10.0
3737
_TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-java-e2e-test-server:${SHORT_SHA}

cloudbuild-e2e-gke.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,5 @@ steps:
3232

3333
logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
3434
substitutions:
35-
_TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.9.0
35+
_TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.10.0
3636
_TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-java-e2e-test-server:${SHORT_SHA}

cloudbuild-e2e-local.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@ steps:
3434

3535
logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs
3636
substitutions:
37-
_TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.9.0
37+
_TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.10.0
3838
_TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-java-e2e-test-server:${SHORT_SHA}

e2e-test-server/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ application {
2626
description = 'End-To-End integration testing server'
2727

2828
dependencies {
29+
compile(libraries.opentelemetry_autoconfigure)
2930
compile(libraries.opentelemetry_api)
3031
compile(libraries.opentelemetry_sdk)
3132
compile(libraries.google_cloud_trace)

e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/ScenarioHandlerManager.java

+40-3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import io.opentelemetry.context.propagation.TextMapGetter;
2929
import io.opentelemetry.context.propagation.TextMapPropagator;
3030
import io.opentelemetry.sdk.OpenTelemetrySdk;
31+
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
32+
import io.opentelemetry.sdk.resources.Resource;
3133
import io.opentelemetry.sdk.trace.SdkTracerProvider;
3234
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
3335
import java.io.IOException;
@@ -52,6 +54,7 @@ public ScenarioHandlerManager() {
5254
register("/health", this::health);
5355
register("/basicTrace", this::basicTrace);
5456
register("/basicPropagator", this::basicPropagator);
57+
register("/detectResource", this::detectResource);
5558
}
5659

5760
/** Health check test. */
@@ -77,6 +80,34 @@ private Response basicTrace(Request request) {
7780
});
7881
}
7982

83+
/** Test where we include resource detection */
84+
private Response detectResource(Request request) {
85+
LOGGER.info("Running detectResource test, request: " + request);
86+
// TODO - this may fail to just pull the resource.
87+
Resource resource =
88+
AutoConfiguredOpenTelemetrySdk.builder()
89+
.setResultAsGlobal(false)
90+
.addPropertiesSupplier(() -> Map.of("otel.traces.exporter", "none"))
91+
.registerShutdownHook(false)
92+
.build()
93+
.getResource();
94+
return withTemporaryOtel(
95+
resource,
96+
(sdk) -> {
97+
Tracer tracer = sdk.getTestTracer();
98+
Span span =
99+
tracer
100+
.spanBuilder("detectResource")
101+
.setAttribute(Constants.TEST_ID, request.testId())
102+
.startSpan();
103+
try {
104+
return Response.ok(Map.of(Constants.TRACE_ID, span.getSpanContext().getTraceId()));
105+
} finally {
106+
span.end();
107+
}
108+
});
109+
}
110+
80111
/** Basic trace test. */
81112
private Response basicPropagator(Request request) {
82113
LOGGER.info(
@@ -132,12 +163,17 @@ private static <R> R withTemporaryTracer(Function<Tracer, R> handler) {
132163
return withTemporaryOtel(ctx -> handler.apply(ctx.getTestTracer()));
133164
}
134165

166+
private static <R> R withTemporaryOtel(Function<OtelContext, R> handler) {
167+
return withTemporaryOtel(Resource.empty(), handler);
168+
}
169+
135170
/**
136171
* Helper to configure an OTel SDK exporting to cloud trace within the context of a function call.
137172
*/
138-
private static <R> R withTemporaryOtel(Function<OtelContext, R> handler) {
173+
private static <R> R withTemporaryOtel(Resource resource, Function<OtelContext, R> handler) {
139174
try {
140-
OpenTelemetrySdk sdk = setupTraceExporter();
175+
// TODO - Use resource detectors for
176+
OpenTelemetrySdk sdk = setupTraceExporter(resource);
141177
try {
142178
OtelContext context =
143179
new OtelContext() {
@@ -171,7 +207,7 @@ interface OtelContext {
171207
}
172208

173209
/** Set up an OpenTelemetrySDK w/ export to google cloud. */
174-
private static OpenTelemetrySdk setupTraceExporter() throws IOException {
210+
private static OpenTelemetrySdk setupTraceExporter(Resource resource) throws IOException {
175211
// Using default project ID and Credentials
176212
TraceConfiguration configuration =
177213
TraceConfiguration.builder()
@@ -191,6 +227,7 @@ private static OpenTelemetrySdk setupTraceExporter() throws IOException {
191227
.setTracerProvider(
192228
SdkTracerProvider.builder()
193229
.addSpanProcessor(BatchSpanProcessor.builder(traceExporter).build())
230+
.setResource(resource)
194231
.build())
195232
.build();
196233
}

exporters/metrics/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ dependencies {
2323
api(libraries.opentelemetry_sdk_metrics)
2424
api(libraries.google_cloud_core)
2525
api(libraries.google_cloud_monitoring)
26+
implementation(project(':shared-resourcemapping'))
2627
implementation(libraries.opentelemetry_semconv)
2728
testImplementation(testLibraries.junit)
2829
testImplementation(testLibraries.mockito)

exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/ResourceTranslator.java

+5-99
Original file line numberDiff line numberDiff line change
@@ -16,114 +16,20 @@
1616
package com.google.cloud.opentelemetry.metric;
1717

1818
import com.google.api.MonitoredResource;
19-
import io.opentelemetry.api.common.AttributeKey;
20-
import io.opentelemetry.api.common.Attributes;
19+
import com.google.cloud.opentelemetry.resource.GcpResource;
2120
import io.opentelemetry.sdk.resources.Resource;
22-
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
23-
import java.util.List;
24-
import java.util.Optional;
2521

2622
/** Translates from OpenTelemetry Resource into Google Cloud Monitoring's MonitoredResource. */
2723
public class ResourceTranslator {
2824
private ResourceTranslator() {}
2925

30-
@com.google.auto.value.AutoValue
31-
public abstract static class AttributeMapping {
32-
/** The label name used in GCP's MonitoredResource. */
33-
public abstract String getLabelName();
34-
/** The list of OTEL keys that can be used for this resource label, in priority order. */
35-
public abstract java.util.List<AttributeKey<?>> getOtelKeys();
36-
/** A fallback value to set the resource. */
37-
public abstract Optional<String> fallbackLiteral();
38-
39-
public void fill(Resource resource, MonitoredResource.Builder builder) {
40-
for (AttributeKey<?> key : getOtelKeys()) {
41-
Object value = resource.getAttribute(key);
42-
if (value != null) {
43-
builder.putLabels(getLabelName(), value.toString());
44-
return;
45-
}
46-
}
47-
fallbackLiteral().ifPresent(value -> builder.putLabels(getLabelName(), value));
48-
}
49-
50-
public static AttributeMapping create(String labelName, AttributeKey<?> otelKey) {
51-
return new AutoValue_ResourceTranslator_AttributeMapping(
52-
labelName, java.util.Collections.singletonList(otelKey), Optional.empty());
53-
}
54-
55-
public static AttributeMapping create(
56-
String labelName, AttributeKey<?> otelKey, String fallbackLiteral) {
57-
return new AutoValue_ResourceTranslator_AttributeMapping(
58-
labelName, java.util.Collections.singletonList(otelKey), Optional.of(fallbackLiteral));
59-
}
60-
61-
public static AttributeMapping create(
62-
String labelName, java.util.List<AttributeKey<?>> otelKeys) {
63-
return new AutoValue_ResourceTranslator_AttributeMapping(
64-
labelName, otelKeys, Optional.empty());
65-
}
66-
67-
public static AttributeMapping create(
68-
String labelName, java.util.List<AttributeKey<?>> otelKeys, String fallbackLiteral) {
69-
return new AutoValue_ResourceTranslator_AttributeMapping(
70-
labelName, otelKeys, Optional.of(fallbackLiteral));
71-
}
72-
}
73-
74-
private static List<AttributeMapping> GCE_INSTANCE_LABELS =
75-
java.util.Arrays.asList(
76-
AttributeMapping.create("zone", ResourceAttributes.CLOUD_AVAILABILITY_ZONE),
77-
AttributeMapping.create("instance_id", ResourceAttributes.HOST_ID));
78-
private static List<AttributeMapping> K8S_CONTAINER_LABELS =
79-
java.util.Arrays.asList(
80-
AttributeMapping.create("location", ResourceAttributes.CLOUD_AVAILABILITY_ZONE),
81-
AttributeMapping.create("cluster_name", ResourceAttributes.K8S_CLUSTER_NAME),
82-
AttributeMapping.create("namespace_name", ResourceAttributes.K8S_NAMESPACE_NAME),
83-
AttributeMapping.create("container_name", ResourceAttributes.K8S_CONTAINER_NAME),
84-
AttributeMapping.create("pod_name", ResourceAttributes.K8S_POD_NAME));
85-
private static List<AttributeMapping> AWS_EC2_INSTANCE_LABELS =
86-
java.util.Arrays.asList(
87-
AttributeMapping.create("instance_id", ResourceAttributes.HOST_ID),
88-
AttributeMapping.create("region", ResourceAttributes.CLOUD_AVAILABILITY_ZONE),
89-
AttributeMapping.create("aws_account", ResourceAttributes.CLOUD_ACCOUNT_ID));
90-
private static List<AttributeMapping> GENERIC_TASK_LABELS =
91-
java.util.Arrays.asList(
92-
AttributeMapping.create(
93-
"location",
94-
java.util.Arrays.asList(
95-
ResourceAttributes.CLOUD_AVAILABILITY_ZONE, ResourceAttributes.CLOUD_REGION),
96-
"global"),
97-
AttributeMapping.create("namespace", ResourceAttributes.SERVICE_NAMESPACE, ""),
98-
AttributeMapping.create("job", ResourceAttributes.SERVICE_NAME, ""),
99-
AttributeMapping.create("task_id", ResourceAttributes.SERVICE_INSTANCE_ID, ""));
100-
10126
/** Converts a Java OpenTelemetyr SDK resoruce into a MonitoredResource from GCP. */
10227
public static MonitoredResource mapResource(Resource resource) {
103-
String platform = resource.getAttribute(ResourceAttributes.CLOUD_PLATFORM);
104-
if (platform == null) {
105-
return mapBase(resource, "generic_task", GENERIC_TASK_LABELS);
106-
}
107-
switch (platform) {
108-
case ResourceAttributes.CloudPlatformValues.GCP_COMPUTE_ENGINE:
109-
return mapBase(resource, "gce_instance", GCE_INSTANCE_LABELS);
110-
case ResourceAttributes.CloudPlatformValues.GCP_KUBERNETES_ENGINE:
111-
return mapBase(resource, "k8s_container", K8S_CONTAINER_LABELS);
112-
case ResourceAttributes.CloudPlatformValues.AWS_EC2:
113-
return mapBase(resource, "aws_ec2_instance", AWS_EC2_INSTANCE_LABELS);
114-
default:
115-
return mapBase(resource, "generic_task", GENERIC_TASK_LABELS);
116-
}
117-
}
118-
119-
private static MonitoredResource mapBase(
120-
Resource resource, String mrType, List<AttributeMapping> mappings) {
28+
GcpResource gcpResource =
29+
com.google.cloud.opentelemetry.resource.ResourceTranslator.mapResource(resource);
12130
MonitoredResource.Builder mr = MonitoredResource.newBuilder();
122-
mr.setType(mrType);
123-
io.opentelemetry.api.common.AttributesBuilder unused = Attributes.builder();
124-
for (AttributeMapping mapping : mappings) {
125-
mapping.fill(resource, mr);
126-
}
31+
mr.setType(gcpResource.getResourceType());
32+
gcpResource.getResourceLabels().getLabels().forEach(mr::putLabels);
12733
return mr.build();
12834
}
12935
}

exporters/trace/README.md

+42-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
### Installation
2222

23-
This artifact is currently published to [Maven Central](https://search.maven.org/search?q=com.google.cloud.opentelemetry)].
23+
This artifact is currently published to [Maven Central](https://search.maven.org/search?q=com.google.cloud.opentelemetry).
2424

2525
You can pull this library in via the following maven
2626
config:
@@ -82,7 +82,47 @@ before passing it into the TraceExporter constructor
8282

8383
#### Java Versions
8484
Java 8 or above is required for using this exporter.
85-
85+
86+
87+
#### Special Attributes
88+
89+
The Trace Exporter will add the following additional attributes onto all exported spans:
90+
91+
- All `Resource` attributes that do not have a key name conflict with underlying `Span` attributes
92+
- `g.co/r/{resourcee_type}/{label}` attributes that correlate with GCP monitored resource.
93+
- A `g.co/agent` attribute that denotes the exporter used to write the Span.
94+
- `otel.scope.name` and `otel.scope.version`, as specified [by OpenTelemetry exporter requirements](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk_exporters/non-otlp.md#instrumentationscope)
95+
96+
#### Span Attribute relabelling
97+
98+
The Trace Exporter attempts to convert from OpenTelemetry semantic conventions into Cloud Trace conventions.
99+
100+
By default, the following span or event attributes will be converted as follows:
101+
102+
| OpenTelemetry Attribute | Cloud Trace Attribute |
103+
|--------------------------------|-------------------------|
104+
| `http.host` | `/http/host` |
105+
| `http.method` | `/http/method` |
106+
| `http.target` | `/http/path` |
107+
| `http.status_code` | `/http/status_code` |
108+
| `http.url` | `/http/url` |
109+
| `http.request_content_length` | `/http/request/size` |
110+
| `http_response_content_length` | `/http/response/size` |
111+
| `http.scheme` | `/http/client_protocol` |
112+
| `http.route` | `/http/route` |
113+
| `http.user_agent` | `/http/user_agent` |
114+
| `exception.type` | `/error/name` |
115+
| `exception.message` | `/error/message` |
116+
| `thread.id` | `/tid` |
117+
118+
This can be disabled by clearing out the mapping configuration:
119+
120+
```java
121+
TraceConfiguration.builder()
122+
.setAttributeMapping(ImmutableMap.of())
123+
.build()
124+
```
125+
86126

87127
## Useful Links
88128
- For more information on OpenTelemetry, visit: https://opentelemetry.io/

exporters/trace/build.gradle

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ dependencies {
2020
annotationProcessor(libraries.auto_value)
2121
api(libraries.opentelemetry_api)
2222
api(libraries.opentelemetry_sdk)
23-
api(libraries.opentelemetry_semconv)
2423
api(libraries.google_cloud_core)
2524
api(libraries.google_cloud_trace)
2625
api(libraries.google_cloud_trace_grpc)
26+
implementation(libraries.opentelemetry_semconv)
27+
implementation(project(':shared-resourcemapping'))
2728
testImplementation(testLibraries.junit)
2829
testImplementation(testLibraries.opentelemetry_sdk_testing)
2930
testImplementation(testLibraries.test_containers)

0 commit comments

Comments
 (0)