Skip to content

Commit d51b54f

Browse files
author
Mateusz Rzeszutek
authored
Merge pull request #27 from signalfx/do-not-use-db.statement-as-span-name-for-sql-statements
Rename JDBC spans to lower cardinality names (operation name instead of statement)
2 parents 9f32cb0 + 9223dce commit d51b54f

File tree

6 files changed

+265
-13
lines changed

6 files changed

+265
-13
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ System property values take priority over corresponding environment variables.
112112
| System property | Environment variable | Default value | Notes |
113113
| -------------------------------------------------- | ------------------------------------------------- | --------------| --------------------------------------------------------------------------------------------------------------------------------------------- |
114114
| splunk.otel.config.span.processor.instrlib.enabled | SPLUNK_OTEL_CONFIG_SPAN_PROCESSOR_INSTRLIB_ENABLED| `false` | Enables span processing adding library instrumentation properties. Deprecated feature for customers not on the newest OpenTelemetry Collector |
115+
| splunk.jdbc.low.cardinality.span.name.enabled | SPLUNK_JDBC_LOW_CARDINALITY_SPAN_NAME_ENABLED | `true` | Enables low cardinality span names for spans generated by the JDBC auto-instrumentation. By default JDBC spans use full SQL queries as their names; when this property is on just the SQL statement type should be used (e.g. `SELECT`, `INSERT`). |
115116

116117
## Manually instrument a Java application
117118

build.gradle.kts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ subprojects {
3737
}
3838

3939
dependencies {
40-
testImplementation("org.mockito:mockito-core:3.3.3")
40+
testImplementation("org.mockito:mockito-core:3.5.15")
41+
testImplementation("org.mockito:mockito-junit-jupiter:3.5.15")
4142
testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.2")
43+
testImplementation("org.junit.jupiter:junit-jupiter-params:5.6.2")
4244
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.2")
4345
}
4446

custom/src/main/java/com/splunk/opentelemetry/InstrumentationLibraryTracerCustomizer.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ public class InstrumentationLibraryTracerCustomizer implements TracerCustomizer
2424
static final String PROPERTY_SPAN_PROCESSOR_INSTR_LIB_ENABLED =
2525
"splunk.otel.config.span.processor.instrlib.enabled";
2626

27+
static final String ENABLE_JDBC_SPAN_LOW_CARDINALITY_NAME_PROPERTY =
28+
"splunk.jdbc.low.cardinality.span.name.enabled";
29+
2730
private static String propertyToEnv(String property) {
2831
return property.replace(".", "_").toUpperCase();
2932
}
@@ -33,14 +36,25 @@ private static boolean spanProcessorInstrumentationLibraryEnabled() {
3336
if (value == null) {
3437
value = System.getenv(propertyToEnv(PROPERTY_SPAN_PROCESSOR_INSTR_LIB_ENABLED));
3538
}
36-
return ("true".equalsIgnoreCase(value));
39+
return Boolean.parseBoolean(value);
40+
}
41+
42+
private static boolean jdbcSpanLowCardinalityNameEnabled() {
43+
String value = System.getProperty(ENABLE_JDBC_SPAN_LOW_CARDINALITY_NAME_PROPERTY);
44+
if (value == null) {
45+
value = System.getenv(propertyToEnv(ENABLE_JDBC_SPAN_LOW_CARDINALITY_NAME_PROPERTY));
46+
}
47+
// enabled by default
48+
return value == null || Boolean.parseBoolean(value);
3749
}
3850

3951
@Override
4052
public void configure(TracerSdkManagement tracerManagement) {
41-
4253
if (spanProcessorInstrumentationLibraryEnabled()) {
4354
tracerManagement.addSpanProcessor(new InstrumentationLibrarySpanProcessor());
4455
}
56+
if (jdbcSpanLowCardinalityNameEnabled()) {
57+
tracerManagement.addSpanProcessor(new JdbcSpanRenamingProcessor());
58+
}
4559
}
4660
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright Splunk Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.splunk.opentelemetry;
18+
19+
import io.opentelemetry.sdk.common.CompletableResultCode;
20+
import io.opentelemetry.sdk.trace.ReadWriteSpan;
21+
import io.opentelemetry.sdk.trace.ReadableSpan;
22+
import io.opentelemetry.sdk.trace.SpanProcessor;
23+
import io.opentelemetry.trace.attributes.SemanticAttributes;
24+
import java.util.HashSet;
25+
import java.util.Set;
26+
27+
/**
28+
* This span processor is responsible for renaming spans produced by the JDBC auto-instrumentation,
29+
* which have the full SQL query in their names. To lower the cardinality and make those spans
30+
* easier to analyze this processor renames spans so that they only use the SQL operation type (e.g.
31+
* {@code SELECT}, {@code INSERT}, ...) as their names. Full SQL query is still available in the
32+
* {@code db.statement} attribute.
33+
*/
34+
public class JdbcSpanRenamingProcessor implements SpanProcessor {
35+
private static final Set<String> SQL_SYSTEMS;
36+
37+
static {
38+
Set<String> sqlSystems = new HashSet<>();
39+
sqlSystems.add("db2");
40+
sqlSystems.add("derby");
41+
sqlSystems.add("h2");
42+
sqlSystems.add("hsqldb");
43+
sqlSystems.add("mariadb");
44+
sqlSystems.add("mssql");
45+
sqlSystems.add("mysql");
46+
sqlSystems.add("oracle");
47+
sqlSystems.add("other_sql");
48+
sqlSystems.add("postgresql");
49+
SQL_SYSTEMS = sqlSystems;
50+
}
51+
52+
@Override
53+
public void onStart(ReadWriteSpan span) {
54+
String dbSystem = span.toSpanData().getAttributes().get(SemanticAttributes.DB_SYSTEM);
55+
if (isJdbcSpan(dbSystem)) {
56+
String query = span.getName().trim().toUpperCase();
57+
span.updateName(getStatementType(query));
58+
}
59+
}
60+
61+
private boolean isJdbcSpan(String dbSystem) {
62+
return SQL_SYSTEMS.contains(dbSystem);
63+
}
64+
65+
private String getStatementType(String query) {
66+
if (query.startsWith("SELECT")) {
67+
return "SELECT";
68+
} else if (query.startsWith("UPDATE")) {
69+
return "UPDATE";
70+
} else if (query.startsWith("DELETE")) {
71+
return "DELETE";
72+
} else if (query.startsWith("INSERT")) {
73+
return "INSERT";
74+
} else if (query.startsWith("MERGE")) {
75+
return "MERGE";
76+
}
77+
78+
return "JDBC";
79+
}
80+
81+
@Override
82+
public boolean isStartRequired() {
83+
return true;
84+
}
85+
86+
@Override
87+
public void onEnd(ReadableSpan span) {}
88+
89+
@Override
90+
public boolean isEndRequired() {
91+
return false;
92+
}
93+
94+
@Override
95+
public CompletableResultCode shutdown() {
96+
return CompletableResultCode.ofSuccess();
97+
}
98+
99+
@Override
100+
public CompletableResultCode forceFlush() {
101+
return CompletableResultCode.ofSuccess();
102+
}
103+
}

custom/src/test/java/com/splunk/opentelemetry/InstrumentationLibraryTracerCustomizerTest.java

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,24 +16,29 @@
1616

1717
package com.splunk.opentelemetry;
1818

19+
import static com.splunk.opentelemetry.InstrumentationLibraryTracerCustomizer.ENABLE_JDBC_SPAN_LOW_CARDINALITY_NAME_PROPERTY;
1920
import static com.splunk.opentelemetry.InstrumentationLibraryTracerCustomizer.PROPERTY_SPAN_PROCESSOR_INSTR_LIB_ENABLED;
2021
import static org.mockito.ArgumentMatchers.isA;
2122
import static org.mockito.BDDMockito.then;
22-
import static org.mockito.Mockito.mock;
2323
import static org.mockito.Mockito.never;
2424

2525
import io.opentelemetry.sdk.trace.TracerSdkProvider;
2626
import org.junit.jupiter.api.Test;
27+
import org.junit.jupiter.api.extension.ExtendWith;
28+
import org.mockito.Mock;
29+
import org.mockito.junit.jupiter.MockitoExtension;
2730

31+
@ExtendWith(MockitoExtension.class)
2832
public class InstrumentationLibraryTracerCustomizerTest {
33+
@Mock private TracerSdkProvider tracerSdkProvider;
2934

3035
@Test
31-
public void shouldAddSpanProcessorIfPropertySetToTrue() {
36+
public void shouldAddSpanProcessorsIfPropertiesAreSetToTrue() {
3237

3338
// given
34-
TracerSdkProvider tracerSdkProvider = mock(TracerSdkProvider.class);
3539
InstrumentationLibraryTracerCustomizer underTest = new InstrumentationLibraryTracerCustomizer();
3640
System.setProperty(PROPERTY_SPAN_PROCESSOR_INSTR_LIB_ENABLED, "true");
41+
System.setProperty(ENABLE_JDBC_SPAN_LOW_CARDINALITY_NAME_PROPERTY, "true");
3742

3843
// when
3944
underTest.configure(tracerSdkProvider);
@@ -42,32 +47,32 @@ public void shouldAddSpanProcessorIfPropertySetToTrue() {
4247
then(tracerSdkProvider)
4348
.should()
4449
.addSpanProcessor(isA(InstrumentationLibrarySpanProcessor.class));
50+
then(tracerSdkProvider).should().addSpanProcessor(isA(JdbcSpanRenamingProcessor.class));
51+
then(tracerSdkProvider).shouldHaveNoMoreInteractions();
4552
}
4653

4754
@Test
48-
public void shouldNotAddSpanProcessorIfPropertySetToAnythingElse() {
55+
public void shouldNotAddSpanProcessorsIfPropertiesAreSetToAnythingElse() {
4956

5057
// given
51-
TracerSdkProvider tracerSdkProvider = mock(TracerSdkProvider.class);
5258
InstrumentationLibraryTracerCustomizer underTest = new InstrumentationLibraryTracerCustomizer();
5359
System.setProperty(PROPERTY_SPAN_PROCESSOR_INSTR_LIB_ENABLED, "enabled");
60+
System.setProperty(ENABLE_JDBC_SPAN_LOW_CARDINALITY_NAME_PROPERTY, "whatever");
5461

5562
// when
5663
underTest.configure(tracerSdkProvider);
5764

5865
// then
59-
then(tracerSdkProvider)
60-
.should(never())
61-
.addSpanProcessor(isA(InstrumentationLibrarySpanProcessor.class));
66+
then(tracerSdkProvider).shouldHaveNoInteractions();
6267
}
6368

6469
@Test
65-
public void shouldNotAddSpanProcessorIfPropertyNotSet() {
70+
public void shouldConfigureTracerSdkForDefaultValues() {
6671

6772
// given
68-
TracerSdkProvider tracerSdkProvider = mock(TracerSdkProvider.class);
6973
InstrumentationLibraryTracerCustomizer underTest = new InstrumentationLibraryTracerCustomizer();
7074
System.clearProperty(PROPERTY_SPAN_PROCESSOR_INSTR_LIB_ENABLED);
75+
System.clearProperty(ENABLE_JDBC_SPAN_LOW_CARDINALITY_NAME_PROPERTY);
7176

7277
// when
7378
underTest.configure(tracerSdkProvider);
@@ -76,5 +81,7 @@ public void shouldNotAddSpanProcessorIfPropertyNotSet() {
7681
then(tracerSdkProvider)
7782
.should(never())
7883
.addSpanProcessor(isA(InstrumentationLibrarySpanProcessor.class));
84+
then(tracerSdkProvider).should().addSpanProcessor(isA(JdbcSpanRenamingProcessor.class));
85+
then(tracerSdkProvider).shouldHaveNoMoreInteractions();
7986
}
8087
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/*
2+
* Copyright Splunk Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.splunk.opentelemetry;
18+
19+
import static io.opentelemetry.trace.attributes.SemanticAttributes.DB_SYSTEM;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
21+
import static org.junit.jupiter.params.provider.Arguments.arguments;
22+
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
23+
import static org.mockito.ArgumentMatchers.anyString;
24+
import static org.mockito.BDDMockito.given;
25+
import static org.mockito.BDDMockito.then;
26+
import static org.mockito.Mockito.never;
27+
28+
import io.opentelemetry.sdk.trace.ReadWriteSpan;
29+
import io.opentelemetry.sdk.trace.SpanProcessor;
30+
import java.util.stream.Stream;
31+
import org.junit.jupiter.api.Test;
32+
import org.junit.jupiter.api.extension.ExtendWith;
33+
import org.junit.jupiter.params.ParameterizedTest;
34+
import org.junit.jupiter.params.provider.Arguments;
35+
import org.junit.jupiter.params.provider.MethodSource;
36+
import org.junit.jupiter.params.provider.ValueSource;
37+
import org.mockito.Mock;
38+
import org.mockito.junit.jupiter.MockitoExtension;
39+
40+
@ExtendWith(MockitoExtension.class)
41+
class JdbcSpanRenamingProcessorTest {
42+
@Mock(answer = RETURNS_DEEP_STUBS)
43+
ReadWriteSpan span;
44+
45+
SpanProcessor processor = new JdbcSpanRenamingProcessor();
46+
47+
@Test
48+
void shouldRequireOnStart() {
49+
assertTrue(processor.isStartRequired());
50+
}
51+
52+
@Test
53+
void shouldIgnoreSpansThatDoNotHaveDbSystemAttribute() {
54+
// given
55+
given(span.toSpanData().getAttributes().get(DB_SYSTEM)).willReturn(null);
56+
57+
// when
58+
processor.onStart(span);
59+
60+
// then
61+
then(span).should(never()).updateName(anyString());
62+
}
63+
64+
@Test
65+
void shouldIgnoreSpansThatAreNotSql() {
66+
// given
67+
given(span.toSpanData().getAttributes().get(DB_SYSTEM)).willReturn("mongodb");
68+
69+
// when
70+
processor.onStart(span);
71+
72+
// then
73+
then(span).should(never()).updateName(anyString());
74+
}
75+
76+
@ParameterizedTest
77+
@ValueSource(
78+
strings = {
79+
"db2",
80+
"derby",
81+
"h2",
82+
"hsqldb",
83+
"mariadb",
84+
"mssql",
85+
"mysql",
86+
"oracle",
87+
"other_sql",
88+
"postgresql"
89+
})
90+
void shouldUpdateJdbcSpans(String dbSystem) {
91+
// given
92+
given(span.toSpanData().getAttributes().get(DB_SYSTEM)).willReturn(dbSystem);
93+
given(span.getName()).willReturn("select * from table");
94+
95+
// when
96+
processor.onStart(span);
97+
98+
// then
99+
then(span).should().updateName("SELECT");
100+
}
101+
102+
@ParameterizedTest
103+
@MethodSource("spanNameArgs")
104+
void shouldGetSqlStatementTypeFromSpanName(String query, String sqlStatementType) {
105+
// given
106+
given(span.toSpanData().getAttributes().get(DB_SYSTEM)).willReturn("other_sql");
107+
given(span.getName()).willReturn(query);
108+
109+
// when
110+
processor.onStart(span);
111+
112+
// then
113+
then(span).should().updateName(sqlStatementType);
114+
}
115+
116+
private static Stream<Arguments> spanNameArgs() {
117+
return Stream.of(
118+
arguments(" Select * From Table", "SELECT"),
119+
arguments(" \tINSert into TAble", "INSERT"),
120+
arguments(" UPDATE table", "UPDATE"),
121+
arguments("delete from table", "DELETE"),
122+
arguments(" merge into table", "MERGE"),
123+
arguments("alter table table add column", "JDBC"));
124+
}
125+
}

0 commit comments

Comments
 (0)