Skip to content

Commit e661133

Browse files
authored
Add Couchbase 3.x stable db semconv support (#18926)
1 parent ad5dcc3 commit e661133

23 files changed

Lines changed: 905 additions & 26 deletions

File tree

.github/scripts/instrumentations.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,17 @@ readonly INSTRUMENTATIONS=(
6666
"couchbase:couchbase-2.6:javaagent:testExperimental"
6767
"couchbase:couchbase-2.6:javaagent:testStableSemconv"
6868
"couchbase:couchbase-3.1:javaagent:test"
69+
"couchbase:couchbase-3.1:javaagent:testStableSemconv"
70+
"couchbase:couchbase-3.1:javaagent:testStableSemconvExperimental"
6971
"couchbase:couchbase-3.1.6:javaagent:test"
72+
"couchbase:couchbase-3.1.6:javaagent:testStableSemconv"
73+
"couchbase:couchbase-3.1.6:javaagent:testStableSemconvExperimental"
7074
"couchbase:couchbase-3.2:javaagent:test"
75+
"couchbase:couchbase-3.2:javaagent:testStableSemconv"
76+
"couchbase:couchbase-3.2:javaagent:testStableSemconvExperimental"
7177
"couchbase:couchbase-3.4:javaagent:test"
78+
"couchbase:couchbase-3.4:javaagent:testStableSemconv"
79+
"couchbase:couchbase-3.4:javaagent:testStableSemconvExperimental"
7280
"dropwizard:dropwizard-views-0.7:javaagent:test"
7381
"elasticsearch:elasticsearch-api-client-7.16:javaagent:test"
7482
"elasticsearch:elasticsearch-api-client-7.16:javaagent:testStableSemconv"

instrumentation/couchbase/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
| System property | Type | Default | Description |
44
|---------------------------------------------------------------|---------|---------|-------------------------------------------------------------------------|
5-
| `otel.instrumentation.couchbase.experimental-span-attributes` | Boolean | `false` | Enables the capture of experimental span attributes (for 2.x versions). |
5+
| `otel.instrumentation.couchbase.experimental-span-attributes` | Boolean | `false` | Enables experimental span attributes emitted from the underlying Couchbase library. |

instrumentation/couchbase/couchbase-3.1.6/javaagent/build.gradle.kts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,21 @@ tasks {
5454
systemProperty("metadataConfig", "otel.semconv-stability.opt-in=database")
5555
}
5656

57+
val testStableSemconvExperimental by registering(Test::class) {
58+
testClassesDirs = sourceSets.test.get().output.classesDirs
59+
classpath = sourceSets.test.get().runtimeClasspath
60+
jvmArgs(
61+
"-Dotel.semconv-stability.opt-in=database",
62+
"-Dotel.instrumentation.couchbase.experimental-span-attributes=true",
63+
)
64+
systemProperty(
65+
"metadataConfig",
66+
"otel.semconv-stability.opt-in=database,otel.instrumentation.couchbase.experimental-span-attributes=true",
67+
)
68+
}
69+
5770
check {
58-
dependsOn(testStableSemconv)
71+
dependsOn(testStableSemconv, testStableSemconvExperimental)
5972
}
6073

6174
if (otelProps.denyUnsafe) {

instrumentation/couchbase/couchbase-3.1.6/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_1_6/CouchbaseEnvironmentInstrumentation.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import io.opentelemetry.api.GlobalOpenTelemetry;
1313
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1414
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
15-
import io.opentelemetry.javaagent.instrumentation.couchbase.v3_1_6.shaded.com.couchbase.client.tracing.opentelemetry.OpenTelemetryRequestTracer;
1615
import net.bytebuddy.asm.Advice;
1716
import net.bytebuddy.description.type.TypeDescription;
1817
import net.bytebuddy.matcher.ElementMatcher;
@@ -34,7 +33,7 @@ public static class ConstructorAdvice {
3433

3534
@Advice.OnMethodExit(suppress = Throwable.class, inline = false)
3635
public static void onExit(@Advice.This CoreEnvironment.Builder<?> builder) {
37-
builder.requestTracer(OpenTelemetryRequestTracer.wrap(GlobalOpenTelemetry.get()));
36+
builder.requestTracer(CouchbaseRequestTracer.create(GlobalOpenTelemetry.get()));
3837
}
3938
}
4039
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.couchbase.v3_1_6;
7+
8+
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldDatabaseSemconv;
9+
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableDatabaseSemconv;
10+
import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME;
11+
import static io.opentelemetry.semconv.DbAttributes.DB_NAMESPACE;
12+
import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME;
13+
import static io.opentelemetry.semconv.DbAttributes.DB_QUERY_TEXT;
14+
import static io.opentelemetry.semconv.DbAttributes.DB_SYSTEM_NAME;
15+
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_ADDRESS;
16+
import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT;
17+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME;
18+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION;
19+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_STATEMENT;
20+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM;
21+
22+
import com.couchbase.client.core.cnc.RequestSpan;
23+
import com.couchbase.client.core.cnc.RequestTracer;
24+
import com.couchbase.client.core.msg.RequestContext;
25+
import io.opentelemetry.api.GlobalOpenTelemetry;
26+
import io.opentelemetry.api.OpenTelemetry;
27+
import io.opentelemetry.instrumentation.api.incubator.config.internal.DeclarativeConfigUtil;
28+
import io.opentelemetry.javaagent.instrumentation.couchbase.v3_1_6.shaded.com.couchbase.client.tracing.opentelemetry.OpenTelemetryRequestTracer;
29+
import java.time.Duration;
30+
import java.time.Instant;
31+
import reactor.core.publisher.Mono;
32+
33+
public final class CouchbaseRequestTracer implements RequestTracer {
34+
35+
private static final String DB_COUCHBASE_COLLECTION = "db.couchbase.collection";
36+
private static final String NET_PEER_NAME = "net.peer.name";
37+
private static final String NET_PEER_PORT = "net.peer.port";
38+
39+
private static final boolean captureExperimentalAttributes =
40+
DeclarativeConfigUtil.getInstrumentationConfig(GlobalOpenTelemetry.get(), "couchbase")
41+
.getBoolean("experimental_span_attributes/development", false);
42+
43+
private final RequestTracer delegate;
44+
45+
public static RequestTracer create(OpenTelemetry openTelemetry) {
46+
return new CouchbaseRequestTracer(OpenTelemetryRequestTracer.wrap(openTelemetry));
47+
}
48+
49+
private CouchbaseRequestTracer(RequestTracer delegate) {
50+
this.delegate = delegate;
51+
}
52+
53+
@Override
54+
public RequestSpan requestSpan(String name, RequestSpan parent) {
55+
RequestSpan unwrappedParent = parent;
56+
if (parent instanceof TranslatingRequestSpan) {
57+
unwrappedParent = ((TranslatingRequestSpan) parent).delegate;
58+
}
59+
return new TranslatingRequestSpan(delegate.requestSpan(name, unwrappedParent));
60+
}
61+
62+
@Override
63+
public Mono<Void> start() {
64+
return delegate.start();
65+
}
66+
67+
@Override
68+
public Mono<Void> stop(Duration timeout) {
69+
return delegate.stop(timeout);
70+
}
71+
72+
private static final class TranslatingRequestSpan implements RequestSpan {
73+
74+
private final RequestSpan delegate;
75+
76+
private TranslatingRequestSpan(RequestSpan delegate) {
77+
this.delegate = delegate;
78+
}
79+
80+
@Override
81+
public void attribute(String key, String value) {
82+
if (emitStableDatabaseSemconv()) {
83+
String stableKey = stableKey(key);
84+
if (stableKey != null) {
85+
delegate.attribute(stableKey, value);
86+
} else if (captureExperimentalAttribute(key)) {
87+
delegate.attribute(key, value);
88+
}
89+
}
90+
if (emitOldDatabaseSemconv()) {
91+
delegate.attribute(key, value);
92+
}
93+
}
94+
95+
@Override
96+
public void attribute(String key, boolean value) {
97+
if (emitStableDatabaseSemconv()) {
98+
String stableKey = stableKey(key);
99+
if (stableKey != null) {
100+
delegate.attribute(stableKey, value);
101+
} else if (captureExperimentalAttribute(key)) {
102+
delegate.attribute(key, value);
103+
}
104+
}
105+
if (emitOldDatabaseSemconv()) {
106+
delegate.attribute(key, value);
107+
}
108+
}
109+
110+
@Override
111+
public void attribute(String key, long value) {
112+
if (emitStableDatabaseSemconv()) {
113+
String stableKey = stableKey(key);
114+
if (stableKey != null) {
115+
delegate.attribute(stableKey, value);
116+
} else if (captureExperimentalAttribute(key)) {
117+
delegate.attribute(key, value);
118+
}
119+
}
120+
if (emitOldDatabaseSemconv()) {
121+
delegate.attribute(key, value);
122+
}
123+
}
124+
125+
@Override
126+
public void event(String name, Instant timestamp) {
127+
delegate.event(name, timestamp);
128+
}
129+
130+
@Override
131+
public void end() {
132+
delegate.end();
133+
}
134+
135+
@Override
136+
public void requestContext(RequestContext requestContext) {
137+
delegate.requestContext(requestContext);
138+
}
139+
140+
@SuppressWarnings("deprecation") // using deprecated semconv
141+
private static String stableKey(String key) {
142+
if (key.equals(DB_COUCHBASE_COLLECTION)) {
143+
return DB_COLLECTION_NAME.getKey();
144+
}
145+
if (key.equals(DB_NAME.getKey())) {
146+
return DB_NAMESPACE.getKey();
147+
}
148+
if (key.equals(DB_OPERATION.getKey())) {
149+
return DB_OPERATION_NAME.getKey();
150+
}
151+
if (key.equals(DB_STATEMENT.getKey())) {
152+
return DB_QUERY_TEXT.getKey();
153+
}
154+
if (key.equals(DB_SYSTEM.getKey())) {
155+
return DB_SYSTEM_NAME.getKey();
156+
}
157+
if (key.equals(NET_PEER_NAME)) {
158+
return NETWORK_PEER_ADDRESS.getKey();
159+
}
160+
if (key.equals(NET_PEER_PORT)) {
161+
return NETWORK_PEER_PORT.getKey();
162+
}
163+
return null;
164+
}
165+
166+
private static boolean captureExperimentalAttribute(String key) {
167+
return captureExperimentalAttributes && key.startsWith("db.couchbase.");
168+
}
169+
}
170+
}

instrumentation/couchbase/couchbase-3.1.6/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_1_6/CouchbaseClient316Test.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,15 @@
55

66
package io.opentelemetry.javaagent.instrumentation.couchbase.v3_1_6;
77

8+
import static io.opentelemetry.api.common.AttributeKey.longKey;
9+
import static io.opentelemetry.api.common.AttributeKey.stringKey;
810
import static io.opentelemetry.api.trace.SpanKind.INTERNAL;
11+
import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldDatabaseSemconv;
12+
import static io.opentelemetry.instrumentation.testing.junit.db.SemconvStabilityUtil.maybeStable;
13+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
14+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_NAME;
15+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_OPERATION;
16+
import static io.opentelemetry.semconv.incubating.DbIncubatingAttributes.DB_SYSTEM;
917

1018
import com.couchbase.client.core.env.TimeoutConfig;
1119
import com.couchbase.client.core.error.DocumentNotFoundException;
@@ -29,10 +37,12 @@
2937
import org.testcontainers.couchbase.CouchbaseContainer;
3038
import org.testcontainers.couchbase.CouchbaseService;
3139

32-
// Couchbase instrumentation is owned upstream, so we don't assert on the contents of the spans,
33-
// only that the instrumentation is properly registered by the agent, meaning some spans were
34-
// generated.
40+
// Couchbase instrumentation is owned upstream, so limited testing is performed here.
41+
@SuppressWarnings("deprecation") // using deprecated semconv
3542
class CouchbaseClient316Test {
43+
private static final boolean EXPERIMENTAL_ATTRIBUTES =
44+
Boolean.getBoolean("otel.instrumentation.couchbase.experimental-span-attributes");
45+
3646
@RegisterExtension
3747
private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
3848

@@ -93,8 +103,20 @@ void testEmitsSpans() {
93103
span.hasKind(INTERNAL) // later version of couchbase gives correct behavior
94104
.hasName("get")
95105
.hasStatus(
96-
StatusData.unset()); // later version of couchbase gives correct behavior
106+
StatusData.unset()) // later version of couchbase gives correct behavior
107+
.hasAttributesSatisfyingExactly(
108+
equalTo(maybeStable(DB_SYSTEM), "couchbase"),
109+
equalTo(maybeStable(DB_NAME), "test"),
110+
equalTo(maybeStable(DB_OPERATION), "get"),
111+
equalTo(maybeStable(stringKey("db.couchbase.collection")), "_default"),
112+
equalTo(stringKey("db.couchbase.scope"), oldOrExperimental("_default")),
113+
equalTo(longKey("db.couchbase.retries"), oldOrExperimental(0L)),
114+
equalTo(stringKey("db.couchbase.service"), oldOrExperimental("kv")));
97115
},
98116
span -> span.hasName("dispatch_to_server")));
99117
}
118+
119+
private static <T> T oldOrExperimental(T value) {
120+
return emitOldDatabaseSemconv() || EXPERIMENTAL_ATTRIBUTES ? value : null;
121+
}
100122
}

instrumentation/couchbase/couchbase-3.1.6/metadata.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,11 @@ description: >
55
semantic_conventions:
66
- DATABASE_CLIENT_SPANS
77
library_link: https://github.com/couchbase/couchbase-java-client
8+
configurations:
9+
- name: otel.instrumentation.couchbase.experimental-span-attributes
10+
declarative_name: java.couchbase.experimental_span_attributes/development
11+
description: Enables experimental span attributes emitted from the underlying Couchbase library.
12+
type: boolean
13+
default: false
814
features:
915
- AUTO_INSTRUMENTATION_SHIM

instrumentation/couchbase/couchbase-3.1/javaagent/build.gradle.kts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ sourceSets {
2222
}
2323

2424
dependencies {
25+
compileOnly(project(":muzzle")) // For @NoMuzzle
2526
compileOnly(
2627
project(
2728
path = ":instrumentation:couchbase:couchbase-3.1:tracing-opentelemetry-shaded",
@@ -55,8 +56,21 @@ tasks {
5556
systemProperty("metadataConfig", "otel.semconv-stability.opt-in=database")
5657
}
5758

59+
val testStableSemconvExperimental by registering(Test::class) {
60+
testClassesDirs = sourceSets.test.get().output.classesDirs
61+
classpath = sourceSets.test.get().runtimeClasspath
62+
jvmArgs(
63+
"-Dotel.semconv-stability.opt-in=database",
64+
"-Dotel.instrumentation.couchbase.experimental-span-attributes=true",
65+
)
66+
systemProperty(
67+
"metadataConfig",
68+
"otel.semconv-stability.opt-in=database,otel.instrumentation.couchbase.experimental-span-attributes=true",
69+
)
70+
}
71+
5872
check {
59-
dependsOn(testStableSemconv)
73+
dependsOn(testStableSemconv, testStableSemconvExperimental)
6074
}
6175

6276
if (otelProps.denyUnsafe) {

instrumentation/couchbase/couchbase-3.1/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/couchbase/v3_1/CouchbaseEnvironmentInstrumentation.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import io.opentelemetry.instrumentation.api.internal.SemconvStability;
1414
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
1515
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
16-
import io.opentelemetry.javaagent.instrumentation.couchbase.v3_1.shaded.com.couchbase.client.tracing.opentelemetry.OpenTelemetryRequestTracer;
1716
import net.bytebuddy.asm.Advice;
1817
import net.bytebuddy.description.type.TypeDescription;
1918
import net.bytebuddy.matcher.ElementMatcher;
@@ -40,7 +39,7 @@ public static void onExit(@Advice.This CoreEnvironment.Builder<?> builder) {
4039
? "io.opentelemetry.couchbase-3.1"
4140
: "io.opentelemetry.javaagent.couchbase-3.1";
4241
builder.requestTracer(
43-
OpenTelemetryRequestTracer.wrap(GlobalOpenTelemetry.getTracer(instrumentationName)));
42+
CouchbaseRequestTracer.create(GlobalOpenTelemetry.getTracer(instrumentationName)));
4443
}
4544
}
4645
}

0 commit comments

Comments
 (0)