Skip to content

Commit 26591ea

Browse files
authored
Support tracing the ClickHouse Java HTTP client (#11660)
1 parent 2884795 commit 26591ea

File tree

10 files changed

+620
-0
lines changed

10 files changed

+620
-0
lines changed

docs/supported-libraries.md

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ These are the supported libraries and frameworks:
4949
| [AWS SDK](https://aws.amazon.com/sdk-for-java/) | 1.11.x and 2.2+ | [opentelemetry-aws-sdk-1.11](../instrumentation/aws-sdk/aws-sdk-1.11/library),<br>[opentelemetry-aws-sdk-1.11-autoconfigure](../instrumentation/aws-sdk/aws-sdk-1.11/library-autoconfigure),<br>[opentelemetry-aws-sdk-2.2](../instrumentation/aws-sdk/aws-sdk-2.2/library),<br>[opentelemetry-aws-sdk-2.2-autoconfigure](../instrumentation/aws-sdk/aws-sdk-2.2/library-autoconfigure) | [Messaging Spans], [Database Client Spans], [HTTP Client Spans] |
5050
| [Azure Core](https://docs.microsoft.com/en-us/java/api/overview/azure/core-readme) | 1.14+ | N/A | Context propagation |
5151
| [Cassandra Driver](https://github.com/datastax/java-driver) | 3.0+ | [opentelemetry-cassandra-4.4](../instrumentation/cassandra/cassandra-4.4/library) | [Database Client Spans] |
52+
| [Clickhouse Client](https://github.com/ClickHouse/clickhouse-java) | 0.5+ | N/A | [Database Client Spans] |
5253
| [Couchbase Client](https://github.com/couchbase/couchbase-java-client) | 2.0+ and 3.1+ | N/A | [Database Client Spans] |
5354
| [c3p0](https://github.com/swaldman/c3p0) | 0.9.2+ | [opentelemetry-c3p0-0.9](../instrumentation/c3p0-0.9/library) | [Database Pool Metrics] |
5455
| [Dropwizard Metrics](https://metrics.dropwizard.io/) | 4.0+ (disabled by default) | N/A | none |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
plugins {
2+
id("otel.javaagent-instrumentation")
3+
}
4+
5+
muzzle {
6+
pass {
7+
group.set("com.clickhouse.client")
8+
module.set("clickhouse-client")
9+
versions.set("[0.5.0,)")
10+
assertInverse.set(true)
11+
}
12+
}
13+
14+
dependencies {
15+
compileOnly("com.clickhouse:clickhouse-client:0.5.0")
16+
compileOnly("com.google.auto.value:auto-value-annotations")
17+
annotationProcessor("com.google.auto.value:auto-value")
18+
19+
testLibrary("com.clickhouse:clickhouse-client:0.5.0")
20+
testLibrary("com.clickhouse:clickhouse-http-client:0.5.0")
21+
testLibrary("org.apache.httpcomponents.client5:httpclient5:5.2.3")
22+
}
23+
24+
tasks {
25+
test {
26+
usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service)
27+
}
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.clickhouse;
7+
8+
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesGetter;
9+
import io.opentelemetry.semconv.incubating.DbIncubatingAttributes;
10+
import javax.annotation.Nullable;
11+
12+
final class ClickHouseAttributesGetter implements DbClientAttributesGetter<ClickHouseDbRequest> {
13+
@Nullable
14+
@Override
15+
public String getStatement(ClickHouseDbRequest request) {
16+
if (request.getSqlStatementInfo() == null) {
17+
return null;
18+
}
19+
return request.getSqlStatementInfo().getFullStatement();
20+
}
21+
22+
@Nullable
23+
@Override
24+
public String getOperation(ClickHouseDbRequest request) {
25+
if (request.getSqlStatementInfo() == null) {
26+
return null;
27+
}
28+
return request.getSqlStatementInfo().getOperation();
29+
}
30+
31+
@Nullable
32+
@Override
33+
public String getSystem(ClickHouseDbRequest request) {
34+
return DbIncubatingAttributes.DbSystemValues.CLICKHOUSE;
35+
}
36+
37+
@Nullable
38+
@Override
39+
public String getUser(ClickHouseDbRequest request) {
40+
return null;
41+
}
42+
43+
@Nullable
44+
@Override
45+
public String getName(ClickHouseDbRequest request) {
46+
String dbName = request.getDbName();
47+
if (dbName == null || dbName.isEmpty()) {
48+
return null;
49+
}
50+
return dbName;
51+
}
52+
53+
@Nullable
54+
@Override
55+
public String getConnectionString(ClickHouseDbRequest request) {
56+
return null;
57+
}
58+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.clickhouse;
7+
8+
import static io.opentelemetry.javaagent.bootstrap.Java8BytecodeBridge.currentContext;
9+
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
10+
import static io.opentelemetry.javaagent.instrumentation.clickhouse.ClickHouseSingletons.instrumenter;
11+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
12+
import static net.bytebuddy.matcher.ElementMatchers.named;
13+
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
14+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
15+
16+
import com.clickhouse.client.ClickHouseClient;
17+
import com.clickhouse.client.ClickHouseRequest;
18+
import io.opentelemetry.context.Context;
19+
import io.opentelemetry.context.Scope;
20+
import io.opentelemetry.javaagent.bootstrap.CallDepth;
21+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
22+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
23+
import net.bytebuddy.asm.Advice;
24+
import net.bytebuddy.description.type.TypeDescription;
25+
import net.bytebuddy.matcher.ElementMatcher;
26+
27+
public class ClickHouseClientInstrumentation implements TypeInstrumentation {
28+
@Override
29+
public ElementMatcher<TypeDescription> typeMatcher() {
30+
return implementsInterface(named("com.clickhouse.client.ClickHouseClient"));
31+
}
32+
33+
@Override
34+
public void transform(TypeTransformer transformer) {
35+
transformer.applyAdviceToMethod(
36+
isMethod()
37+
.and(namedOneOf("executeAndWait", "execute"))
38+
.and(takesArgument(0, named("com.clickhouse.client.ClickHouseRequest"))),
39+
this.getClass().getName() + "$ClickHouseExecuteAndWaitAdvice");
40+
}
41+
42+
@SuppressWarnings("unused")
43+
public static class ClickHouseExecuteAndWaitAdvice {
44+
@Advice.OnMethodEnter(suppress = Throwable.class)
45+
public static void onEnter(
46+
@Advice.Argument(0) ClickHouseRequest<?> clickHouseRequest,
47+
@Advice.Local("otelContext") Context context,
48+
@Advice.Local("otelScope") Scope scope,
49+
@Advice.Local("otelCallDepth") CallDepth callDepth) {
50+
51+
callDepth = CallDepth.forClass(ClickHouseClient.class);
52+
if (callDepth.getAndIncrement() > 0) {
53+
return;
54+
}
55+
56+
if (clickHouseRequest == null) {
57+
return;
58+
}
59+
60+
Context parentContext = currentContext();
61+
62+
ClickHouseDbRequest request =
63+
ClickHouseDbRequest.create(
64+
clickHouseRequest.getServer().getHost(),
65+
clickHouseRequest.getServer().getPort(),
66+
clickHouseRequest.getServer().getDatabase().get(),
67+
clickHouseRequest.getPreparedQuery().getOriginalQuery());
68+
69+
if (!instrumenter().shouldStart(parentContext, request)) {
70+
return;
71+
}
72+
73+
context = instrumenter().start(parentContext, request);
74+
scope = context.makeCurrent();
75+
}
76+
77+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
78+
public static void onExit(
79+
@Advice.Thrown Throwable throwable,
80+
@Advice.Local("otelRequest") ClickHouseDbRequest clickHouseRequest,
81+
@Advice.Local("otelContext") Context context,
82+
@Advice.Local("otelScope") Scope scope,
83+
@Advice.Local("otelCallDepth") CallDepth callDepth) {
84+
85+
if (callDepth.decrementAndGet() > 0) {
86+
return;
87+
}
88+
89+
if (scope == null) {
90+
return;
91+
}
92+
93+
scope.close();
94+
instrumenter().end(context, clickHouseRequest, null, throwable);
95+
}
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.clickhouse;
7+
8+
import com.google.auto.value.AutoValue;
9+
import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlStatementInfo;
10+
import io.opentelemetry.instrumentation.api.incubator.semconv.db.SqlStatementSanitizer;
11+
import io.opentelemetry.javaagent.bootstrap.internal.CommonConfig;
12+
13+
@AutoValue
14+
public abstract class ClickHouseDbRequest {
15+
16+
private static final SqlStatementSanitizer sanitizer =
17+
SqlStatementSanitizer.create(CommonConfig.get().isStatementSanitizationEnabled());
18+
19+
public static ClickHouseDbRequest create(String host, Integer port, String dbName, String sql) {
20+
return new AutoValue_ClickHouseDbRequest(host, port, dbName, sanitizer.sanitize(sql));
21+
}
22+
23+
public abstract String getHost();
24+
25+
public abstract Integer getPort();
26+
27+
public abstract String getDbName();
28+
29+
public abstract SqlStatementInfo getSqlStatementInfo();
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.clickhouse;
7+
8+
import static java.util.Collections.singletonList;
9+
10+
import com.google.auto.service.AutoService;
11+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import java.util.List;
14+
15+
@AutoService(InstrumentationModule.class)
16+
public class ClickHouseInstrumentationModule extends InstrumentationModule {
17+
18+
public ClickHouseInstrumentationModule() {
19+
super("clickhouse", "clickhouse-client-0.5");
20+
}
21+
22+
@Override
23+
public List<TypeInstrumentation> typeInstrumentations() {
24+
return singletonList(new ClickHouseClientInstrumentation());
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.clickhouse;
7+
8+
import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesGetter;
9+
10+
final class ClickHouseNetworkAttributesGetter
11+
implements ServerAttributesGetter<ClickHouseDbRequest> {
12+
13+
@Override
14+
public String getServerAddress(ClickHouseDbRequest request) {
15+
return request.getHost();
16+
}
17+
18+
@Override
19+
public Integer getServerPort(ClickHouseDbRequest request) {
20+
return request.getPort();
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.clickhouse;
7+
8+
import io.opentelemetry.api.GlobalOpenTelemetry;
9+
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientAttributesExtractor;
10+
import io.opentelemetry.instrumentation.api.incubator.semconv.db.DbClientSpanNameExtractor;
11+
import io.opentelemetry.instrumentation.api.instrumenter.Instrumenter;
12+
import io.opentelemetry.instrumentation.api.instrumenter.SpanKindExtractor;
13+
import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesExtractor;
14+
15+
public final class ClickHouseSingletons {
16+
17+
private static final Instrumenter<ClickHouseDbRequest, Void> INSTRUMENTER;
18+
19+
static {
20+
ClickHouseAttributesGetter dbAttributesGetter = new ClickHouseAttributesGetter();
21+
22+
INSTRUMENTER =
23+
Instrumenter.<ClickHouseDbRequest, Void>builder(
24+
GlobalOpenTelemetry.get(),
25+
"io.opentelemetry.clickhouse-client-0.5",
26+
DbClientSpanNameExtractor.create(dbAttributesGetter))
27+
.addAttributesExtractor(DbClientAttributesExtractor.create(dbAttributesGetter))
28+
.addAttributesExtractor(
29+
ServerAttributesExtractor.create(new ClickHouseNetworkAttributesGetter()))
30+
.buildInstrumenter(SpanKindExtractor.alwaysClient());
31+
}
32+
33+
public static Instrumenter<ClickHouseDbRequest, Void> instrumenter() {
34+
return INSTRUMENTER;
35+
}
36+
37+
private ClickHouseSingletons() {}
38+
}

0 commit comments

Comments
 (0)