-
Notifications
You must be signed in to change notification settings - Fork 1k
Add sqlcommenter support for jdbc and r2dbc #13714
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
152657e
b901ff0
7109f37
8ca85ed
ee15119
112a937
370a516
c6b15e7
7e223e3
a10672d
51cc03a
ff5aa12
11dde45
fd8d93f
bb065bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.api.incubator.semconv.db.internal; | ||
|
||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; | ||
import io.opentelemetry.context.Context; | ||
import java.io.UnsupportedEncodingException; | ||
import java.net.URLEncoder; | ||
|
||
/** | ||
* This class is internal and experimental. Its APIs are unstable and can change at any time. Its | ||
* APIs (or a version of them) may be promoted to the public stable API in the future, but no | ||
* guarantees are made. | ||
*/ | ||
public final class SqlCommenterUtil { | ||
|
||
/** | ||
* Append comment containing tracing information at the end of the query. See <a | ||
* href="https://google.github.io/sqlcommenter/spec/">sqlcommenter</a> for the description of the | ||
* algorithm. | ||
*/ | ||
public static String processQuery(String query) { | ||
if (!Span.current().getSpanContext().isValid()) { | ||
return query; | ||
} | ||
// skip queries that contain comments | ||
if (containsSqlComment(query)) { | ||
return query; | ||
} | ||
|
||
class State { | ||
String traceparent; | ||
String tracestate; | ||
} | ||
|
||
State state = new State(); | ||
|
||
W3CTraceContextPropagator.getInstance() | ||
.inject( | ||
Context.current(), | ||
state, | ||
(carrier, key, value) -> { | ||
if ("traceparent".equals(key)) { | ||
carrier.traceparent = value; | ||
} else if ("tracestate".equals(key)) { | ||
carrier.tracestate = value; | ||
} | ||
}); | ||
try { | ||
// we know that the traceparent doesn't contain anything that needs to be encoded | ||
query += " /*traceparent='" + state.traceparent + "'"; | ||
if (state.tracestate != null) { | ||
query += ", tracestate=" + serialize(state.tracestate); | ||
} | ||
query += "*/"; | ||
} catch (UnsupportedEncodingException exception) { | ||
// this exception should never happen as UTF-8 encoding is always available | ||
} | ||
return query; | ||
} | ||
|
||
private static boolean containsSqlComment(String query) { | ||
return query.contains("--") || query.contains("/*"); | ||
} | ||
|
||
private static String serialize(String value) throws UnsupportedEncodingException { | ||
// specification requires percent encoding, here we use the java build in url encoder that | ||
// encodes space as '+' instead of '%20' as required | ||
String result = URLEncoder.encode(value, "UTF-8").replace("+", "%20"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the |
||
// specification requires escaping ' with a backslash, we skip this because URLEncoder already | ||
// encodes the ' | ||
return "'" + result + "'"; | ||
} | ||
|
||
private SqlCommenterUtil() {} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
* Copyright The OpenTelemetry Authors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package io.opentelemetry.instrumentation.api.incubator.semconv.db.internal; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.api.trace.SpanContext; | ||
import io.opentelemetry.api.trace.TraceFlags; | ||
import io.opentelemetry.api.trace.TraceState; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.Scope; | ||
import org.junit.jupiter.params.ParameterizedTest; | ||
import org.junit.jupiter.params.provider.ValueSource; | ||
|
||
class SqlCommenterUtilTest { | ||
|
||
@ParameterizedTest | ||
@ValueSource(strings = {"SELECT /**/ 1", "SELECT 1 --", "SELECT '/*'"}) | ||
void skipQueriesWithComments(String query) { | ||
Context parent = | ||
Context.root() | ||
.with( | ||
Span.wrap( | ||
SpanContext.create( | ||
"ff01020304050600ff0a0b0c0d0e0f00", | ||
"090a0b0c0d0e0f00", | ||
TraceFlags.getSampled(), | ||
TraceState.getDefault()))); | ||
|
||
try (Scope ignore = parent.makeCurrent()) { | ||
assertThat(SqlCommenterUtil.processQuery(query)).isEqualTo(query); | ||
} | ||
} | ||
|
||
@ParameterizedTest | ||
@ValueSource(booleans = {true, false}) | ||
void sqlCommenter(boolean hasTraceState) { | ||
TraceState state = | ||
hasTraceState ? TraceState.builder().put("test", "test'").build() : TraceState.getDefault(); | ||
Context parent = | ||
Context.root() | ||
.with( | ||
Span.wrap( | ||
SpanContext.create( | ||
"ff01020304050600ff0a0b0c0d0e0f00", | ||
"090a0b0c0d0e0f00", | ||
TraceFlags.getSampled(), | ||
state))); | ||
|
||
try (Scope ignore = parent.makeCurrent()) { | ||
assertThat(SqlCommenterUtil.processQuery("SELECT 1")) | ||
.isEqualTo( | ||
hasTraceState | ||
? "SELECT 1 /*traceparent='00-ff01020304050600ff0a0b0c0d0e0f00-090a0b0c0d0e0f00-01', tracestate='test%3Dtest%27'*/" | ||
: "SELECT 1 /*traceparent='00-ff01020304050600ff0a0b0c0d0e0f00-090a0b0c0d0e0f00-01'*/"); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
# Settings for the JDBC instrumentation | ||
|
||
| System property | Type | Default | Description | | ||
|---------------------------------------------------------|---------|---------|----------------------------------------| | ||
| `otel.instrumentation.jdbc.statement-sanitizer.enabled` | Boolean | `true` | Enables the DB statement sanitization. | | ||
| System property | Type | Default | Description | | ||
|---------------------------------------------------------|---------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | ||
| `otel.instrumentation.jdbc.statement-sanitizer.enabled` | Boolean | `true` | Enables the DB statement sanitization. | | ||
| `otel.instrumentation.jdbc.sqlcommenter.enabled` | Boolean | `false` | Enables augmenting queries with a comment containing the tracing information. See [sqlcommenter](https://google.github.io/sqlcommenter/) for more info. WARNING: augmenting queries with tracing context will make query texts unique, which may have adverse impact on database performance. Consult with database experts before enabling. | |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,7 +65,7 @@ public static class StatementAdvice { | |
|
||
@Advice.OnMethodEnter(suppress = Throwable.class) | ||
public static void onEnter( | ||
@Advice.Argument(0) String sql, | ||
@Advice.Argument(value = 0, readOnly = false) String sql, | ||
@Advice.This Statement statement, | ||
@Advice.Local("otelCallDepth") CallDepth callDepth, | ||
@Advice.Local("otelRequest") DbRequest request, | ||
|
@@ -86,6 +86,8 @@ public static void onEnter( | |
Context parentContext = currentContext(); | ||
request = DbRequest.create(statement, sql); | ||
|
||
sql = JdbcSingletons.processSql(sql); | ||
|
||
|
||
if (request == null || !statementInstrumenter().shouldStart(parentContext, request)) { | ||
return; | ||
} | ||
|
@@ -115,12 +117,15 @@ public static void stopSpan( | |
@SuppressWarnings("unused") | ||
public static class AddBatchAdvice { | ||
|
||
@Advice.OnMethodExit(suppress = Throwable.class) | ||
public static void addBatch(@Advice.This Statement statement, @Advice.Argument(0) String sql) { | ||
@Advice.OnMethodEnter(suppress = Throwable.class) | ||
public static void addBatch( | ||
@Advice.This Statement statement, | ||
@Advice.Argument(value = 0, readOnly = false) String sql) { | ||
if (statement instanceof PreparedStatement) { | ||
return; | ||
} | ||
JdbcData.addStatementBatch(statement, sql); | ||
sql = JdbcSingletons.processSql(sql); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I chose to use
sqlcommenter
instead ofsql-commenter
because https://google.github.io/sqlcommenter/ also spells it as one word.