Skip to content

Commit 98fa1aa

Browse files
committed
Implement db.query.summary for SqlClientAttributesExtractor
1 parent b25317d commit 98fa1aa

File tree

21 files changed

+591
-143
lines changed

21 files changed

+591
-143
lines changed

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/DbClientSpanNameExtractor.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,18 @@ public String extract(REQUEST request) {
111111
namespace, sanitizedStatement.getOperation(), sanitizedStatement.getMainIdentifier());
112112
}
113113

114+
// For stable semconv, use query summary as span name if available
114115
if (rawQueryTexts.size() == 1) {
115116
SqlStatementInfo sanitizedStatement =
116117
SqlStatementSanitizerUtil.sanitize(rawQueryTexts.iterator().next());
118+
String querySummary = sanitizedStatement.getQuerySummary();
119+
if (querySummary != null) {
120+
if (isBatch(request)) {
121+
return "BATCH " + querySummary;
122+
}
123+
return querySummary;
124+
}
125+
// Fall back to old behavior if no query summary
117126
String operation = sanitizedStatement.getOperation();
118127
if (isBatch(request)) {
119128
operation = "BATCH " + operation;
@@ -122,6 +131,11 @@ public String extract(REQUEST request) {
122131
}
123132

124133
MultiQuery multiQuery = MultiQuery.analyze(rawQueryTexts, false);
134+
String querySummary = multiQuery.getQuerySummary();
135+
if (querySummary != null) {
136+
return "BATCH " + querySummary;
137+
}
138+
// Fall back to old behavior if no query summary
125139
return computeSpanName(
126140
namespace,
127141
multiQuery.getOperation() != null ? "BATCH " + multiQuery.getOperation() : "BATCH",

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/MultiQuery.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,43 @@ class MultiQuery {
1414

1515
@Nullable private final String mainIdentifier;
1616
@Nullable private final String operation;
17+
@Nullable private final String querySummary;
1718
private final Set<String> statements;
1819

1920
private MultiQuery(
20-
@Nullable String mainIdentifier, @Nullable String operation, Set<String> statements) {
21+
@Nullable String mainIdentifier,
22+
@Nullable String operation,
23+
@Nullable String querySummary,
24+
Set<String> statements) {
2125
this.mainIdentifier = mainIdentifier;
2226
this.operation = operation;
27+
this.querySummary = querySummary;
2328
this.statements = statements;
2429
}
2530

2631
static MultiQuery analyze(
2732
Collection<String> rawQueryTexts, boolean statementSanitizationEnabled) {
2833
UniqueValue uniqueMainIdentifier = new UniqueValue();
2934
UniqueValue uniqueOperation = new UniqueValue();
35+
UniqueValue uniqueQuerySummary = new UniqueValue();
3036
Set<String> uniqueStatements = new LinkedHashSet<>();
3137
for (String rawQueryText : rawQueryTexts) {
3238
SqlStatementInfo sanitizedStatement = SqlStatementSanitizerUtil.sanitize(rawQueryText);
3339
String mainIdentifier = sanitizedStatement.getMainIdentifier();
3440
uniqueMainIdentifier.set(mainIdentifier);
3541
String operation = sanitizedStatement.getOperation();
3642
uniqueOperation.set(operation);
43+
String querySummary = sanitizedStatement.getQuerySummary();
44+
uniqueQuerySummary.set(querySummary);
3745
uniqueStatements.add(
3846
statementSanitizationEnabled ? sanitizedStatement.getFullStatement() : rawQueryText);
3947
}
4048

4149
return new MultiQuery(
42-
uniqueMainIdentifier.getValue(), uniqueOperation.getValue(), uniqueStatements);
50+
uniqueMainIdentifier.getValue(),
51+
uniqueOperation.getValue(),
52+
uniqueQuerySummary.getValue(),
53+
uniqueStatements);
4354
}
4455

4556
@Nullable
@@ -52,6 +63,11 @@ public String getOperation() {
5263
return operation;
5364
}
5465

66+
@Nullable
67+
public String getQuerySummary() {
68+
return querySummary;
69+
}
70+
5571
public Set<String> getStatements() {
5672
return statements;
5773
}

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlClientAttributesExtractor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static io.opentelemetry.semconv.DbAttributes.DB_COLLECTION_NAME;
1010
import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_BATCH_SIZE;
1111
import static io.opentelemetry.semconv.DbAttributes.DB_OPERATION_NAME;
12+
import static io.opentelemetry.semconv.DbAttributes.DB_QUERY_SUMMARY;
1213
import static io.opentelemetry.semconv.DbAttributes.DB_QUERY_TEXT;
1314

1415
import io.opentelemetry.api.common.AttributeKey;
@@ -119,6 +120,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST
119120
DB_QUERY_TEXT,
120121
statementSanitizationEnabled ? sanitizedStatement.getFullStatement() : rawQueryText);
121122
internalSet(attributes, DB_OPERATION_NAME, isBatch ? "BATCH " + operation : operation);
123+
internalSet(attributes, DB_QUERY_SUMMARY, sanitizedStatement.getQuerySummary());
122124
if (!SQL_CALL.equals(operation)) {
123125
internalSet(attributes, DB_COLLECTION_NAME, sanitizedStatement.getMainIdentifier());
124126
}
@@ -130,6 +132,7 @@ public void onStart(AttributesBuilder attributes, Context parentContext, REQUEST
130132
String operation =
131133
multiQuery.getOperation() != null ? "BATCH " + multiQuery.getOperation() : "BATCH";
132134
internalSet(attributes, DB_OPERATION_NAME, operation);
135+
internalSet(attributes, DB_QUERY_SUMMARY, multiQuery.getQuerySummary());
133136

134137
if (multiQuery.getMainIdentifier() != null
135138
&& (multiQuery.getOperation() == null || !SQL_CALL.equals(multiQuery.getOperation()))) {

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementInfo.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,34 @@
1111
@AutoValue
1212
public abstract class SqlStatementInfo {
1313

14+
private static final int QUERY_SUMMARY_MAX_LENGTH = 255;
15+
1416
public static SqlStatementInfo create(
15-
@Nullable String fullStatement, @Nullable String operation, @Nullable String identifier) {
16-
return new AutoValue_SqlStatementInfo(fullStatement, operation, identifier);
17+
@Nullable String fullStatement,
18+
@Nullable String operation,
19+
@Nullable String identifier,
20+
@Nullable String querySummary) {
21+
String truncatedQuerySummary = truncateQuerySummary(querySummary);
22+
return new AutoValue_SqlStatementInfo(
23+
fullStatement, operation, identifier, truncatedQuerySummary);
24+
}
25+
26+
/**
27+
* Truncates the query summary to {@link #QUERY_SUMMARY_MAX_LENGTH} characters, ensuring
28+
* truncation does not occur within an operation name or target.
29+
*/
30+
@Nullable
31+
private static String truncateQuerySummary(@Nullable String querySummary) {
32+
if (querySummary == null || querySummary.length() <= QUERY_SUMMARY_MAX_LENGTH) {
33+
return querySummary;
34+
}
35+
// Truncate at the last space before the limit to avoid cutting in the middle of an identifier
36+
int lastSpace = querySummary.lastIndexOf(' ', QUERY_SUMMARY_MAX_LENGTH);
37+
if (lastSpace > 0) {
38+
return querySummary.substring(0, lastSpace);
39+
}
40+
// If no space found, truncate at the limit
41+
return querySummary.substring(0, QUERY_SUMMARY_MAX_LENGTH);
1742
}
1843

1944
@Nullable
@@ -24,4 +49,24 @@ public static SqlStatementInfo create(
2449

2550
@Nullable
2651
public abstract String getMainIdentifier();
52+
53+
/**
54+
* Returns a low cardinality summary of the database query suitable for use as a span name or
55+
* metric attribute.
56+
*
57+
* <p>The summary contains operations (e.g., SELECT, INSERT) and their targets (e.g., table names)
58+
* in the order they appear in the query. For example:
59+
*
60+
* <ul>
61+
* <li>{@code SELECT wuser_table}
62+
* <li>{@code INSERT shipping_details SELECT orders}
63+
* <li>{@code SELECT songs artists} (multiple tables)
64+
* </ul>
65+
*
66+
* @see <a
67+
* href="https://github.com/open-telemetry/semantic-conventions/blob/main/docs/db/database-spans.md#generating-a-summary-of-the-query">Generating
68+
* a summary of the query</a>
69+
*/
70+
@Nullable
71+
public abstract String getQuerySummary();
2772
}

instrumentation-api-incubator/src/main/java/io/opentelemetry/instrumentation/api/incubator/semconv/db/SqlStatementSanitizer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public SqlStatementInfo sanitize(@Nullable String statement) {
3939

4040
public SqlStatementInfo sanitize(@Nullable String statement, SqlDialect dialect) {
4141
if (!statementSanitizationEnabled || statement == null) {
42-
return SqlStatementInfo.create(statement, null, null);
42+
return SqlStatementInfo.create(statement, null, null, null);
4343
}
4444
// sanitization result will not be cached for statements larger than the threshold to avoid
4545
// cache growing too large

0 commit comments

Comments
 (0)