Skip to content

Commit f5fd953

Browse files
committed
Add JDBI 3 plugin
1 parent 24e5d41 commit f5fd953

File tree

7 files changed

+443
-0
lines changed

7 files changed

+443
-0
lines changed

brave-bom/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@
162162
<artifactId>brave-instrumentation-jersey-server-jakarta</artifactId>
163163
<version>${project.version}</version>
164164
</dependency>
165+
<dependency>
166+
<groupId>${project.groupId}</groupId>
167+
<artifactId>brave-instrumentation-jdbi3</artifactId>
168+
<version>${project.version}</version>
169+
</dependency>
165170
<dependency>
166171
<groupId>${project.groupId}</groupId>
167172
<artifactId>brave-instrumentation-jms</artifactId>

instrumentation/jdbi3/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# brave-instrumentation-jdbi3
2+
3+
This includes a JDBI 3 plugin will report to Zipkin how long each statement
4+
takes, along with relevant tags like the query.
5+
6+
To use it, call the `installPlugin` on the `Jdbi` instance you want to
7+
instrument, with a suitably configured `Jdbi3BravePlugin`.
8+
9+
The remote service name of the span is set to the hostname and port number of
10+
the database server, if available, and the URL scheme if not. If the database
11+
URL format allows it, you can add the `zipkinServiceName` query parameter to
12+
override the remote service name.
13+
14+
By default, bind variable values are not included in the traces, for security
15+
reasons. If you want to include them, pass `true` as `includeBindVariables` in
16+
the `Jdbi3BravePlugin` constructor.

instrumentation/jdbi3/pom.xml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
4+
Copyright The OpenZipkin Authors
5+
SPDX-License-Identifier: Apache-2.0
6+
7+
-->
8+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
9+
<parent>
10+
<groupId>io.zipkin.brave</groupId>
11+
<artifactId>brave-instrumentation-parent</artifactId>
12+
<version>6.3.0-SNAPSHOT</version>
13+
</parent>
14+
<modelVersion>4.0.0</modelVersion>
15+
16+
<artifactId>brave-instrumentation-jdbi3</artifactId>
17+
<name>Brave Instrumentation: JDBI3</name>
18+
19+
<properties>
20+
<!-- Matches Export-Package in bnd.bnd -->
21+
<module.name>brave.jdbi3</module.name>
22+
23+
<main.basedir>${project.basedir}/../..</main.basedir>
24+
25+
<embedded-postgres.version>2.1.0</embedded-postgres.version>
26+
<embedded-postgres-binaries.version>16.4.0</embedded-postgres-binaries.version>
27+
<h2.version>2.3.232</h2.version>
28+
<jdbi3.version>3.49.1</jdbi3.version>
29+
<mysql-connector-java.version>8.0.16</mysql-connector-java.version>
30+
<sqlite-jdbc.version>3.49.1.0</sqlite-jdbc.version>
31+
</properties>
32+
33+
<dependencies>
34+
<dependency>
35+
<groupId>org.jdbi</groupId>
36+
<artifactId>jdbi3-core</artifactId>
37+
<version>${jdbi3.version}</version>
38+
</dependency>
39+
<dependency>
40+
<groupId>${project.groupId}</groupId>
41+
<artifactId>brave-tests</artifactId>
42+
<version>${project.version}</version>
43+
<scope>test</scope>
44+
</dependency>
45+
<dependency>
46+
<groupId>mysql</groupId>
47+
<artifactId>mysql-connector-java</artifactId>
48+
<version>${mysql-connector-java.version}</version>
49+
<scope>test</scope>
50+
</dependency>
51+
</dependencies>
52+
</project>
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright The OpenZipkin Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package brave.jdbi3;
6+
7+
import java.net.URI;
8+
import java.net.URISyntaxException;
9+
import java.sql.Connection;
10+
import java.sql.SQLException;
11+
import java.time.Instant;
12+
import java.util.regex.Matcher;
13+
import java.util.regex.Pattern;
14+
15+
import org.jdbi.v3.core.Jdbi;
16+
import org.jdbi.v3.core.spi.JdbiPlugin;
17+
import org.jdbi.v3.core.statement.SqlStatements;
18+
import org.jdbi.v3.core.statement.StatementContext;
19+
import org.jdbi.v3.core.statement.StatementContextListener;
20+
21+
import brave.Span;
22+
import brave.Tracing;
23+
24+
/**
25+
* Instrumentation for JDBI 3.
26+
* <p>
27+
* JDBI plugin that sends a trace span for every SQL command executed by Jdbi.
28+
* Install by calling {@code jdbi.installPlugin(new Jdbi3BravePlugin(tracing));}
29+
*/
30+
public class Jdbi3BravePlugin extends JdbiPlugin.Singleton {
31+
private final Tracing tracing;
32+
private final boolean includeBindVariables;
33+
private static final Pattern serviceNamePattern = Pattern.compile("(^|&)zipkinServiceName=(?<serviceName>[^&]*)");
34+
35+
private Jdbi3BravePlugin(Tracing tracing, boolean includeBindVariables) {
36+
this.tracing = tracing;
37+
this.includeBindVariables = includeBindVariables;
38+
}
39+
40+
public static Builder newBuilder(Tracing tracing) {
41+
return new Builder(tracing);
42+
}
43+
44+
@Override
45+
public void customizeJdbi(Jdbi jdbi) {
46+
if (tracing == null) {
47+
return;
48+
}
49+
jdbi.getConfig(SqlStatements.class)
50+
.addContextListener(new StatementContextListener() {
51+
@Override
52+
public void contextCreated(final StatementContext ctx) {
53+
String spanName = ctx.describeJdbiStatementType();
54+
Span span = tracing.tracer().nextSpan().name(spanName);
55+
56+
Instant start = ctx.getExecutionMoment();
57+
if (start != null) {
58+
span.start(start.toEpochMilli() * 1000);
59+
} else {
60+
span.start();
61+
}
62+
63+
ctx.setTraceId(span.context().traceIdString());
64+
65+
ctx.addCleanable(() -> {
66+
final SqlStatements stmtConfig = ctx.getConfig(SqlStatements.class);
67+
68+
String remoteServiceName = getRemoteServiceName(ctx);
69+
if (remoteServiceName != null && !remoteServiceName.isEmpty()) {
70+
span.remoteServiceName(remoteServiceName);
71+
}
72+
73+
span.kind(Span.Kind.CLIENT);
74+
75+
final String renderedSql = ctx.getRenderedSql();
76+
if (renderedSql != null) {
77+
String truncated = renderedSql.substring(
78+
0,
79+
Math.min(renderedSql.length(), stmtConfig.getJfrSqlMaxLength())
80+
);
81+
span.tag("sql.query", truncated);
82+
}
83+
84+
if (includeBindVariables) {
85+
String bindVariables = ctx.getBinding()
86+
.describe(stmtConfig.getJfrParamMaxLength());
87+
span.tag("sql.binding", bindVariables);
88+
}
89+
90+
span.tag("sql.rows", Long.toString(ctx.getMappedRows()));
91+
92+
if (ctx.getCompletionMoment() == null) {
93+
span.tag("error", "");
94+
}
95+
96+
if (ctx.getCompletionMoment() != null) {
97+
span.finish(ctx.getCompletionMoment().toEpochMilli() * 1000);
98+
} else if (ctx.getExceptionMoment() != null) {
99+
span.finish(ctx.getExceptionMoment().toEpochMilli() * 1000);
100+
} else {
101+
span.finish();
102+
}
103+
});
104+
}
105+
});
106+
}
107+
108+
static String getRemoteServiceName(StatementContext ctx) {
109+
Connection connection = ctx.getConnection();
110+
if (connection == null) {
111+
return null;
112+
}
113+
URI uri;
114+
try {
115+
uri = new URI(connection.getMetaData().getURL().replaceAll("^jdbc:", ""));
116+
} catch (SQLException | URISyntaxException ignored) {
117+
return null;
118+
}
119+
120+
String remoteServiceNameFromQueryParameter = getRemoteServiceNameFromQueryParameter(uri);
121+
if (remoteServiceNameFromQueryParameter != null) {
122+
return remoteServiceNameFromQueryParameter;
123+
}
124+
125+
if (uri.getAuthority() != null) {
126+
return uri.getAuthority();
127+
}
128+
129+
if (uri.getScheme() != null) {
130+
return uri.getScheme();
131+
}
132+
133+
return null;
134+
}
135+
136+
static String getRemoteServiceNameFromQueryParameter(URI uri) {
137+
String query = uri.getQuery();
138+
if (query != null) {
139+
Matcher matcher = serviceNamePattern.matcher(query);
140+
if (matcher.find()) {
141+
return matcher.group("serviceName");
142+
}
143+
}
144+
145+
try {
146+
URI schemeSpecificUri = new URI(uri.getSchemeSpecificPart());
147+
if (schemeSpecificUri.toString().length() < uri.toString().length()) {
148+
return getRemoteServiceNameFromQueryParameter(schemeSpecificUri);
149+
}
150+
} catch (URISyntaxException ignored) {
151+
}
152+
153+
return null;
154+
}
155+
156+
public static class Builder {
157+
private final Tracing tracing;
158+
private boolean includeBindVariables = false;
159+
160+
public Builder(Tracing tracing) {
161+
this.tracing = tracing;
162+
}
163+
164+
public Builder includeBindVariables(boolean includeBindVariables) {
165+
this.includeBindVariables = includeBindVariables;
166+
return this;
167+
}
168+
169+
public Jdbi3BravePlugin build() {
170+
return new Jdbi3BravePlugin(tracing, includeBindVariables);
171+
}
172+
}
173+
}

0 commit comments

Comments
 (0)