Skip to content

Commit 86c1bda

Browse files
authored
Parse db.statement attribute for database instrumentation (#755)
* Parse db.statement attribute for database instrumentation * reverted file change * fixed tests * fixed formating * applied comments * fixed ordering
1 parent 56c20f2 commit 86c1bda

File tree

6 files changed

+998
-2
lines changed

6 files changed

+998
-2
lines changed

awsagentprovider/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ dependencies {
3434
implementation("io.opentelemetry.contrib:opentelemetry-aws-xray")
3535
// AWS Resource Detectors
3636
implementation("io.opentelemetry.contrib:opentelemetry-aws-resources")
37+
// Json file reader
38+
implementation("com.fasterxml.jackson.core:jackson-databind:2.16.1")
3739
// Export configuration
3840
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
3941

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsMetricAttributeGenerator.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static io.opentelemetry.semconv.ResourceAttributes.SERVICE_NAME;
1919
import static io.opentelemetry.semconv.SemanticAttributes.DB_OPERATION;
20+
import static io.opentelemetry.semconv.SemanticAttributes.DB_STATEMENT;
2021
import static io.opentelemetry.semconv.SemanticAttributes.DB_SYSTEM;
2122
import static io.opentelemetry.semconv.SemanticAttributes.FAAS_INVOKED_NAME;
2223
import static io.opentelemetry.semconv.SemanticAttributes.FAAS_TRIGGER;
@@ -44,6 +45,8 @@
4445
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_SPAN_KIND;
4546
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_STREAM_NAME;
4647
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_TABLE_NAME;
48+
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.MAX_KEYWORD_LENGTH;
49+
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.SQL_DIALECT_PATTERN;
4750
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_OPERATION;
4851
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_OPERATION;
4952
import static software.amazon.opentelemetry.javaagent.providers.AwsSpanProcessingUtil.UNKNOWN_REMOTE_SERVICE;
@@ -68,6 +71,7 @@
6871
import java.util.Optional;
6972
import java.util.logging.Level;
7073
import java.util.logging.Logger;
74+
import java.util.regex.Matcher;
7175
import javax.annotation.Nullable;
7276

7377
/**
@@ -266,9 +270,15 @@ private static void setRemoteServiceAndOperation(SpanData span, AttributesBuilde
266270
} else if (isKeyPresent(span, RPC_SERVICE) || isKeyPresent(span, RPC_METHOD)) {
267271
remoteService = normalizeServiceName(span, getRemoteService(span, RPC_SERVICE));
268272
remoteOperation = getRemoteOperation(span, RPC_METHOD);
269-
} else if (isKeyPresent(span, DB_SYSTEM) || isKeyPresent(span, DB_OPERATION)) {
273+
} else if (isKeyPresent(span, DB_SYSTEM)
274+
|| isKeyPresent(span, DB_OPERATION)
275+
|| isKeyPresent(span, DB_STATEMENT)) {
270276
remoteService = getRemoteService(span, DB_SYSTEM);
271-
remoteOperation = getRemoteOperation(span, DB_OPERATION);
277+
if (isKeyPresent(span, DB_OPERATION)) {
278+
remoteOperation = getRemoteOperation(span, DB_OPERATION);
279+
} else {
280+
remoteOperation = getDBStatementRemoteOperation(span, DB_STATEMENT);
281+
}
272282
} else if (isKeyPresent(span, FAAS_INVOKED_NAME) || isKeyPresent(span, FAAS_TRIGGER)) {
273283
remoteService = getRemoteService(span, FAAS_INVOKED_NAME);
274284
remoteOperation = getRemoteOperation(span, FAAS_TRIGGER);
@@ -449,6 +459,36 @@ private static String getRemoteOperation(SpanData span, AttributeKey<String> rem
449459
return remoteOperation;
450460
}
451461

462+
/**
463+
* If no db.operation attribute provided in the span, we use db.statement to compute a valid
464+
* remote operation in a best-effort manner. To do this, we take the first substring of the
465+
* statement and compare to a regex list of known SQL keywords. The substring length is determined
466+
* by the longest known SQL keywords.
467+
*/
468+
private static String getDBStatementRemoteOperation(
469+
SpanData span, AttributeKey<String> remoteOperationKey) {
470+
String remoteOperation = span.getAttributes().get(remoteOperationKey);
471+
if (remoteOperation == null) {
472+
remoteOperation = UNKNOWN_REMOTE_OPERATION;
473+
}
474+
475+
// Remove all whitespace and newline characters from the beginning of remote_operation
476+
// and retrieve the first MAX_KEYWORD_LENGTH characters
477+
remoteOperation = remoteOperation.stripLeading();
478+
if (remoteOperation.length() > MAX_KEYWORD_LENGTH) {
479+
remoteOperation = remoteOperation.substring(0, MAX_KEYWORD_LENGTH);
480+
}
481+
482+
Matcher matcher = SQL_DIALECT_PATTERN.matcher(remoteOperation.toUpperCase());
483+
if (matcher.find() && !matcher.group(0).isEmpty()) {
484+
remoteOperation = matcher.group(0);
485+
} else {
486+
remoteOperation = UNKNOWN_REMOTE_OPERATION;
487+
}
488+
489+
return remoteOperation;
490+
}
491+
452492
private static void logUnknownAttribute(AttributeKey<String> attributeKey, SpanData span) {
453493
String[] params = {
454494
attributeKey.getKey(), span.getKind().name(), span.getSpanContext().getSpanId()

awsagentprovider/src/main/java/software/amazon/opentelemetry/javaagent/providers/AwsSpanProcessingUtil.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,20 @@
2222
import static io.opentelemetry.semconv.SemanticAttributes.RPC_SYSTEM;
2323
import static software.amazon.opentelemetry.javaagent.providers.AwsAttributeKeys.AWS_LOCAL_OPERATION;
2424

25+
import com.fasterxml.jackson.core.type.TypeReference;
26+
import com.fasterxml.jackson.databind.JsonNode;
27+
import com.fasterxml.jackson.databind.ObjectMapper;
28+
import com.fasterxml.jackson.databind.ObjectReader;
2529
import io.opentelemetry.api.common.AttributeKey;
2630
import io.opentelemetry.api.trace.SpanContext;
2731
import io.opentelemetry.api.trace.SpanKind;
2832
import io.opentelemetry.sdk.common.InstrumentationScopeInfo;
2933
import io.opentelemetry.sdk.trace.data.SpanData;
34+
import java.io.IOException;
35+
import java.io.InputStream;
36+
import java.util.ArrayList;
37+
import java.util.List;
38+
import java.util.regex.Pattern;
3039

3140
/** Utility class designed to support shared logic across AWS Span Processors. */
3241
final class AwsSpanProcessingUtil {
@@ -40,6 +49,29 @@ final class AwsSpanProcessingUtil {
4049
static final String LOCAL_ROOT = "LOCAL_ROOT";
4150
static final String SQS_RECEIVE_MESSAGE_SPAN_NAME = "Sqs.ReceiveMessage";
4251
static final String AWS_SDK_INSTRUMENTATION_SCOPE_PREFIX = "io.opentelemetry.aws-sdk-";
52+
// Max keyword length supported by parsing into remote_operation from DB_STATEMENT.
53+
// The current longest command word is DATETIME_INTERVAL_PRECISION at 27 characters.
54+
// If we add a longer keyword to the sql dialect keyword list, need to update the constant below.
55+
static final int MAX_KEYWORD_LENGTH = 27;
56+
static final Pattern SQL_DIALECT_PATTERN =
57+
Pattern.compile("^(?:" + String.join("|", getDialectKeywords()) + ")\\b");
58+
59+
private static final String SQL_DIALECT_KEYWORDS_JSON = "configuration/sql_dialect_keywords.json";
60+
61+
static List<String> getDialectKeywords() {
62+
try (InputStream jsonFile =
63+
AwsSpanProcessingUtil.class
64+
.getClassLoader()
65+
.getResourceAsStream(SQL_DIALECT_KEYWORDS_JSON)) {
66+
ObjectMapper mapper = new ObjectMapper();
67+
JsonNode jsonNode = mapper.readValue(jsonFile, JsonNode.class);
68+
JsonNode arrayNode = jsonNode.get("keywords");
69+
ObjectReader reader = mapper.readerFor(new TypeReference<List<String>>() {});
70+
return reader.readValue(arrayNode);
71+
} catch (IOException e) {
72+
return new ArrayList<>();
73+
}
74+
}
4375

4476
/**
4577
* Ingress operation (i.e. operation for Server and Consumer spans) will be generated from

0 commit comments

Comments
 (0)