diff --git a/.snyk b/.snyk index 0d95b37a1..cf64b478f 100644 --- a/.snyk +++ b/.snyk @@ -5,5 +5,5 @@ ignore: SNYK-JAVA-IONETTY-1042268: - '*': reason: no available replacement - expires: 2021-10-31T00:00:00.000Z + expires: 2021-12-31T00:00:00.000Z patch: {} diff --git a/hypertrace-metrics-exporter/hypertrace-metrics-exporter/build.gradle.kts b/hypertrace-metrics-exporter/hypertrace-metrics-exporter/build.gradle.kts index 7ff01cc1a..c339ef430 100644 --- a/hypertrace-metrics-exporter/hypertrace-metrics-exporter/build.gradle.kts +++ b/hypertrace-metrics-exporter/hypertrace-metrics-exporter/build.gradle.kts @@ -41,10 +41,10 @@ dependencies { implementation("io.opentelemetry:opentelemetry-proto:1.6.0-alpha") // kafka - implementation("org.apache.kafka:kafka-clients:2.6.0") + implementation("org.apache.kafka:kafka-clients:2.7.2") // test testImplementation("org.junit.jupiter:junit-jupiter:5.7.1") testImplementation("org.mockito:mockito-core:3.8.0") - testImplementation("com.google.code.gson:gson:2.8.7") + testImplementation("com.google.code.gson:gson:2.8.9") } diff --git a/hypertrace-metrics-processor/hypertrace-metrics-processor/build.gradle.kts b/hypertrace-metrics-processor/hypertrace-metrics-processor/build.gradle.kts index 60b1e068e..903ace6d3 100644 --- a/hypertrace-metrics-processor/hypertrace-metrics-processor/build.gradle.kts +++ b/hypertrace-metrics-processor/hypertrace-metrics-processor/build.gradle.kts @@ -36,6 +36,15 @@ dependencies { // open telemetry proto implementation("io.opentelemetry:opentelemetry-proto:1.6.0-alpha") + constraints { + implementation("org.glassfish.jersey.core:jersey-common:2.34") { + because("introduced by org.hypertrace.core.kafkastreams.framework:" + + "kafka-streams-framework@0.1.21 > io.confluent:kafka-streams-avro-serde@6.0.1 > " + + "io.confluent:kafka-schema-registry-client@6.0.1 > " + + "org.glassfish.jersey.core:jersey-common@2.30") + } + } + // test testImplementation("org.junit.jupiter:junit-jupiter:5.7.1") testImplementation("org.mockito:mockito-core:3.8.0") diff --git a/hypertrace-trace-enricher/enriched-span-constants/build.gradle.kts b/hypertrace-trace-enricher/enriched-span-constants/build.gradle.kts index 7f85d462b..2e4136a7a 100644 --- a/hypertrace-trace-enricher/enriched-span-constants/build.gradle.kts +++ b/hypertrace-trace-enricher/enriched-span-constants/build.gradle.kts @@ -17,11 +17,11 @@ val generateLocalGoGrpcFiles = false protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.15.7" + artifact = "com.google.protobuf:protoc:3.17.3" } plugins { id("grpc_java") { - artifact = "io.grpc:protoc-gen-grpc-java:1.41.0" + artifact = "io.grpc:protoc-gen-grpc-java:1.42.0" } if (generateLocalGoGrpcFiles) { @@ -63,7 +63,7 @@ sourceSets { } dependencies { - api("com.google.protobuf:protobuf-java-util:3.15.7") + api("com.google.protobuf:protobuf-java-util:3.17.3") implementation("org.hypertrace.core.datamodel:data-model:0.1.20") implementation(project(":span-normalizer:raw-span-constants")) diff --git a/hypertrace-trace-enricher/enriched-span-constants/src/main/java/org/hypertrace/traceenricher/enrichedspan/constants/EnrichedSpanConstants.java b/hypertrace-trace-enricher/enriched-span-constants/src/main/java/org/hypertrace/traceenricher/enrichedspan/constants/EnrichedSpanConstants.java index 66efecf7f..696b3875f 100644 --- a/hypertrace-trace-enricher/enriched-span-constants/src/main/java/org/hypertrace/traceenricher/enrichedspan/constants/EnrichedSpanConstants.java +++ b/hypertrace-trace-enricher/enriched-span-constants/src/main/java/org/hypertrace/traceenricher/enrichedspan/constants/EnrichedSpanConstants.java @@ -15,6 +15,9 @@ public class EnrichedSpanConstants { public static final String HEAD_EVENT_ID = "head.event.id"; public static final String API_EXIT_CALLS_COUNT = "api.exit.calls.count"; public static final String UNIQUE_API_NODES_COUNT = "unique.apis.count"; + public static final String GRPC_REQUEST_URL = "grpc.request.url"; + public static final String GRPC_REQUEST_ENDPOINT = "grpc.request.endpoint"; + /** * Returns the constant value for the given Enum. * diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/build.gradle.kts b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/build.gradle.kts index 8f7f75548..285a81653 100644 --- a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/build.gradle.kts +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/build.gradle.kts @@ -19,9 +19,9 @@ dependencies { implementation("org.hypertrace.core.datamodel:data-model:0.1.20") implementation("org.hypertrace.entity.service:entity-service-client:0.8.5") implementation("org.hypertrace.core.serviceframework:platform-metrics:0.1.28") - implementation("org.hypertrace.core.grpcutils:grpc-client-utils:0.6.1") + implementation("org.hypertrace.core.grpcutils:grpc-client-utils:0.6.2") implementation("org.hypertrace.config.service:spaces-config-service-api:0.1.0") - implementation("org.hypertrace.core.grpcutils:grpc-context-utils:0.6.1") + implementation("org.hypertrace.core.grpcutils:grpc-context-utils:0.6.2") implementation("com.typesafe:config:1.4.1") implementation("org.apache.httpcomponents:httpclient:4.5.13") @@ -33,5 +33,5 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.7.1") testImplementation("org.mockito:mockito-core:3.8.0") testImplementation("org.mockito:mockito-junit-jupiter:3.8.0") - testImplementation("io.grpc:grpc-core:1.41.0") + testImplementation("io.grpc:grpc-core:1.42.0") } diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/main/java/org/hypertrace/traceenricher/enrichment/enrichers/GrpcAttributeEnricher.java b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/main/java/org/hypertrace/traceenricher/enrichment/enrichers/GrpcAttributeEnricher.java new file mode 100644 index 000000000..46c8f0d59 --- /dev/null +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/main/java/org/hypertrace/traceenricher/enrichment/enrichers/GrpcAttributeEnricher.java @@ -0,0 +1,48 @@ +package org.hypertrace.traceenricher.enrichment.enrichers; + +import static org.hypertrace.traceenricher.enrichedspan.constants.EnrichedSpanConstants.GRPC_REQUEST_ENDPOINT; +import static org.hypertrace.traceenricher.enrichedspan.constants.EnrichedSpanConstants.GRPC_REQUEST_URL; + +import java.util.Optional; +import org.hypertrace.core.datamodel.Event; +import org.hypertrace.core.datamodel.StructuredTrace; +import org.hypertrace.core.datamodel.shared.trace.AttributeValueCreator; +import org.hypertrace.semantic.convention.utils.rpc.RpcSemanticConventionUtils; +import org.hypertrace.traceenricher.enrichedspan.constants.utils.EnrichedSpanUtils; +import org.hypertrace.traceenricher.enrichedspan.constants.v1.Protocol; +import org.hypertrace.traceenricher.enrichment.AbstractTraceEnricher; + +public class GrpcAttributeEnricher extends AbstractTraceEnricher { + + private static final String GRPC_RECV_DOT = "Recv."; + private static final String GRPC_SENT_DOT = "Sent."; + + @Override + public void enrichEvent(StructuredTrace trace, Event event) { + // if protocol is Grpc update attribute + Protocol protocol = EnrichedSpanUtils.getProtocol(event); + if (Protocol.PROTOCOL_GRPC == protocol) { + Optional grpcRequestEndpoint = + RpcSemanticConventionUtils.getGrpcRequestEndpoint(event); + if (grpcRequestEndpoint.isPresent()) { + addEnrichedAttribute( + event, GRPC_REQUEST_ENDPOINT, AttributeValueCreator.create(grpcRequestEndpoint.get())); + + String prefix = getPrefix(event); + addEnrichedAttribute( + event, + GRPC_REQUEST_URL, + AttributeValueCreator.create(prefix.concat(grpcRequestEndpoint.get()))); + } + } + } + + private String getPrefix(Event event) { + if (EnrichedSpanUtils.isEntrySpan(event)) { + return GRPC_RECV_DOT; + } else if (EnrichedSpanUtils.isExitSpan(event)) { + return GRPC_SENT_DOT; + } + return ""; + } +} diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/GrpcAttributeEnricherTest.java b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/GrpcAttributeEnricherTest.java new file mode 100644 index 000000000..b616c2c59 --- /dev/null +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/GrpcAttributeEnricherTest.java @@ -0,0 +1,125 @@ +package org.hypertrace.traceenricher.enrichment.enrichers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; + +import org.hypertrace.core.datamodel.AttributeValue; +import org.hypertrace.core.datamodel.Event; +import org.hypertrace.core.datamodel.StructuredTrace; +import org.hypertrace.core.datamodel.shared.SpanAttributeUtils; +import org.hypertrace.traceenricher.enrichedspan.constants.EnrichedSpanConstants; +import org.hypertrace.traceenricher.enrichedspan.constants.v1.CommonAttribute; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; + +public class GrpcAttributeEnricherTest extends AbstractAttributeEnricherTest { + + @Mock private StructuredTrace mockTrace; + + private final GrpcAttributeEnricher enricher = new GrpcAttributeEnricher(); + + /* + * Covers cases of GrpcAttributeEnricher, the details cases of getGrpcRequestEndpoint + * are covered at RpcSemanticConventionUtilsTest + * */ + @Test + public void test_withAValidUrl_shouldEnrichHttpPathAndParams() { + // case 1: using event name prefixed with Recv + Event e = createMockEvent(); + when(e.getEventName()).thenReturn("Recv.TestService.GetEventEchos"); + addAttribute( + e, EnrichedSpanConstants.getValue(CommonAttribute.COMMON_ATTRIBUTE_PROTOCOL), "GRPC"); + addAttribute( + e, EnrichedSpanConstants.getValue(CommonAttribute.COMMON_ATTRIBUTE_SPAN_TYPE), "ENTRY"); + + enricher.enrichEvent(mockTrace, e); + + String grpcRequestUrl = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_URL); + assertEquals("Recv.TestService.GetEventEchos", grpcRequestUrl); + + String grpcRequestEndPoint = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_ENDPOINT); + assertEquals("TestService.GetEventEchos", grpcRequestEndPoint); + + // case 2: using grpc.path + e = createMockEvent(); + when(e.getEventName()).thenReturn("TestService.GetEventEchos"); + addAttribute( + e, EnrichedSpanConstants.getValue(CommonAttribute.COMMON_ATTRIBUTE_PROTOCOL), "GRPC"); + addAttribute( + e, EnrichedSpanConstants.getValue(CommonAttribute.COMMON_ATTRIBUTE_SPAN_TYPE), "ENTRY"); + addAttribute(e, "grpc.path", "/TestGrpcService/GetGrpcPathEchos"); + + enricher.enrichEvent(mockTrace, e); + + grpcRequestUrl = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_URL); + assertEquals("Recv.TestGrpcService.GetGrpcPathEchos", grpcRequestUrl); + + grpcRequestEndPoint = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_ENDPOINT); + assertEquals("TestGrpcService.GetGrpcPathEchos", grpcRequestEndPoint); + + // case 3: no grpc protocol + e = createMockEvent(); + when(e.getEventName()).thenReturn("TestService.GetEventEchos"); + addAttribute( + e, EnrichedSpanConstants.getValue(CommonAttribute.COMMON_ATTRIBUTE_SPAN_TYPE), "ENTRY"); + addAttribute(e, "grpc.path", "/TestGrpcService/GetGrpcPathEchos"); + + enricher.enrichEvent(mockTrace, e); + + grpcRequestUrl = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_URL); + assertNull(grpcRequestUrl); + + grpcRequestEndPoint = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_ENDPOINT); + assertNull(grpcRequestEndPoint); + + // case 4: client call - EXIT + e = createMockEvent(); + when(e.getEventName()).thenReturn("TestService.GetEventEchos"); + addAttribute( + e, EnrichedSpanConstants.getValue(CommonAttribute.COMMON_ATTRIBUTE_PROTOCOL), "GRPC"); + addAttribute( + e, EnrichedSpanConstants.getValue(CommonAttribute.COMMON_ATTRIBUTE_SPAN_TYPE), "EXIT"); + addAttribute(e, "grpc.path", "/TestGrpcService/GetGrpcPathEchos"); + + enricher.enrichEvent(mockTrace, e); + + grpcRequestUrl = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_URL); + assertEquals("Sent.TestGrpcService.GetGrpcPathEchos", grpcRequestUrl); + + grpcRequestEndPoint = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_ENDPOINT); + assertEquals("TestGrpcService.GetGrpcPathEchos", grpcRequestEndPoint); + + // case 5: no entry or exsit span (internal span) - not prefix sent / recv + e = createMockEvent(); + when(e.getEventName()).thenReturn("TestService.GetEventEchos"); + addAttribute( + e, EnrichedSpanConstants.getValue(CommonAttribute.COMMON_ATTRIBUTE_PROTOCOL), "GRPC"); + addAttribute(e, "grpc.path", "/TestGrpcService/GetGrpcPathEchos"); + + enricher.enrichEvent(mockTrace, e); + + grpcRequestUrl = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_URL); + assertEquals("TestGrpcService.GetGrpcPathEchos", grpcRequestUrl); + + grpcRequestEndPoint = + SpanAttributeUtils.getStringAttribute(e, EnrichedSpanConstants.GRPC_REQUEST_ENDPOINT); + assertEquals("TestGrpcService.GetGrpcPathEchos", grpcRequestEndPoint); + } + + private void addAttribute(Event event, String key, String val) { + event + .getAttributes() + .getAttributeMap() + .put(key, AttributeValue.newBuilder().setValue(val).build()); + } +} diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/resources/enricher.conf b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/resources/enricher.conf index d37697e31..ea6b6bd98 100644 --- a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/resources/enricher.conf +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/resources/enricher.conf @@ -56,4 +56,9 @@ enricher { port = 50061 } } + + GrpcAttributeEnricher { + class = "org.hypertrace.traceenricher.enrichment.enrichers.GrpcAttributeEnricher" + dependencies = ["SpanTypeAttributeEnricher", "ApiBoundaryTypeAttributeEnricher"] + } } \ No newline at end of file diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher/build.gradle.kts b/hypertrace-trace-enricher/hypertrace-trace-enricher/build.gradle.kts index e834ed848..e9175471e 100644 --- a/hypertrace-trace-enricher/hypertrace-trace-enricher/build.gradle.kts +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { } // Required for the GRPC clients. - runtimeOnly("io.grpc:grpc-netty:1.41.0") + runtimeOnly("io.grpc:grpc-netty:1.42.0") // Logging implementation("org.slf4j:slf4j-api:1.7.30") diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher/src/main/resources/configs/common/application.conf b/hypertrace-trace-enricher/hypertrace-trace-enricher/src/main/resources/configs/common/application.conf index 01acabc5f..b9c237b0b 100644 --- a/hypertrace-trace-enricher/hypertrace-trace-enricher/src/main/resources/configs/common/application.conf +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher/src/main/resources/configs/common/application.conf @@ -24,7 +24,7 @@ kafka.streams.config = { } enricher { - names = ["SpanTypeAttributeEnricher", "ApiStatusEnricher", "EndpointEnricher", "TransactionNameEnricher", "ApiBoundaryTypeAttributeEnricher", "ErrorsAndExceptionsEnricher", "BackendEntityEnricher", "HttpAttributeEnricher", "DefaultServiceEntityEnricher", "UserAgentSpanEnricher", "SpaceEnricher", "EntitySpanEnricher", "ExitCallsEnricher", "TraceStatsEnricher"] + names = ["SpanTypeAttributeEnricher", "ApiStatusEnricher", "EndpointEnricher", "TransactionNameEnricher", "ApiBoundaryTypeAttributeEnricher", "ErrorsAndExceptionsEnricher", "BackendEntityEnricher", "HttpAttributeEnricher", "DefaultServiceEntityEnricher", "UserAgentSpanEnricher", "SpaceEnricher", "EntitySpanEnricher", "ExitCallsEnricher", "TraceStatsEnricher", "GrpcAttributeEnricher"] clients = { entity.service.config = { @@ -110,6 +110,11 @@ enricher { class = "org.hypertrace.traceenricher.enrichment.enrichers.TraceStatsEnrichere" dependencies = ["EndpointEnricher"] } + + GrpcAttributeEnricher { + class = "org.hypertrace.traceenricher.enrichment.enrichers.GrpcAttributeEnricher" + dependencies = ["SpanTypeAttributeEnricher", "ApiBoundaryTypeAttributeEnricher"] + } } logger { diff --git a/hypertrace-trace-enricher/trace-reader/build.gradle.kts b/hypertrace-trace-enricher/trace-reader/build.gradle.kts index 4375dcfdf..b0ab77c7d 100644 --- a/hypertrace-trace-enricher/trace-reader/build.gradle.kts +++ b/hypertrace-trace-enricher/trace-reader/build.gradle.kts @@ -12,8 +12,8 @@ dependencies { api("org.hypertrace.entity.service:entity-data-service-rx-client:0.8.5") api("org.hypertrace.core.datamodel:data-model:0.1.20") implementation("org.hypertrace.core.attribute.service:attribute-projection-registry:0.12.3") - implementation("org.hypertrace.core.grpcutils:grpc-client-rx-utils:0.6.1") - implementation("org.hypertrace.core.grpcutils:grpc-context-utils:0.6.1") + implementation("org.hypertrace.core.grpcutils:grpc-client-rx-utils:0.6.2") + implementation("org.hypertrace.core.grpcutils:grpc-context-utils:0.6.2") implementation("io.reactivex.rxjava3:rxjava:3.0.11") annotationProcessor("org.projectlombok:lombok:1.18.20") diff --git a/hypertrace-view-generator/hypertrace-view-generator/build.gradle.kts b/hypertrace-view-generator/hypertrace-view-generator/build.gradle.kts index 27c39e1a1..cab859176 100644 --- a/hypertrace-view-generator/hypertrace-view-generator/build.gradle.kts +++ b/hypertrace-view-generator/hypertrace-view-generator/build.gradle.kts @@ -44,5 +44,5 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter:5.7.1") testImplementation("org.mockito:mockito-core:3.8.0") - testImplementation("com.google.code.gson:gson:2.8.7") + testImplementation("com.google.code.gson:gson:2.8.9") } diff --git a/hypertrace-view-generator/hypertrace-view-generator/src/main/java/org/hypertrace/viewgenerator/generators/SpanEventViewGenerator.java b/hypertrace-view-generator/hypertrace-view-generator/src/main/java/org/hypertrace/viewgenerator/generators/SpanEventViewGenerator.java index 8a3ec7cd0..11e8de973 100644 --- a/hypertrace-view-generator/hypertrace-view-generator/src/main/java/org/hypertrace/viewgenerator/generators/SpanEventViewGenerator.java +++ b/hypertrace-view-generator/hypertrace-view-generator/src/main/java/org/hypertrace/viewgenerator/generators/SpanEventViewGenerator.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import org.apache.avro.Schema; import org.hypertrace.core.datamodel.Entity; @@ -15,7 +16,6 @@ import org.hypertrace.core.datamodel.StructuredTrace; import org.hypertrace.core.datamodel.shared.SpanAttributeUtils; import org.hypertrace.semantic.convention.utils.http.HttpSemanticConventionUtils; -import org.hypertrace.semantic.convention.utils.rpc.RpcSemanticConventionUtils; import org.hypertrace.traceenricher.enrichedspan.constants.EnrichedSpanConstants; import org.hypertrace.traceenricher.enrichedspan.constants.utils.EnrichedSpanUtils; import org.hypertrace.traceenricher.enrichedspan.constants.v1.Api; @@ -318,15 +318,10 @@ String getRequestUrl(Event event, Protocol protocol) { .orElse(HttpSemanticConventionUtils.getHttpPath(event).orElse(null)); case PROTOCOL_GRPC: - /** - * For RPC methods, we show URL/URI as a combination of rpc.service and rpc.method. The same - * information is also available as Span Name - - * https://github.com/open-telemetry/opentelemetry-specification/blob/3e380e249f60c3a5f68746f5e84d10195ba41a79/specification/trace/semantic_conventions/rpc.md#span-name - * So, as part of this method, we will form Url using rpc.service and rpc.method, and - * fallback to spanName. While setting GRPC protocol from rpc attributes, it already checks - * for rpc.system. - */ - return RpcSemanticConventionUtils.getRpcPath(event).orElse(event.getEventName()); + return Optional.ofNullable( + SpanAttributeUtils.getStringAttribute( + event, EnrichedSpanConstants.GRPC_REQUEST_URL)) + .orElse(event.getEventName()); } return null; } diff --git a/hypertrace-view-generator/hypertrace-view-generator/src/test/java/org/hypertrace/viewgenerator/generators/SpanEventViewGeneratorTest.java b/hypertrace-view-generator/hypertrace-view-generator/src/test/java/org/hypertrace/viewgenerator/generators/SpanEventViewGeneratorTest.java index 112ba2aec..f53ebe5d4 100644 --- a/hypertrace-view-generator/hypertrace-view-generator/src/test/java/org/hypertrace/viewgenerator/generators/SpanEventViewGeneratorTest.java +++ b/hypertrace-view-generator/hypertrace-view-generator/src/test/java/org/hypertrace/viewgenerator/generators/SpanEventViewGeneratorTest.java @@ -80,53 +80,19 @@ public void test_getRequestUrl_grpcProctol_shouldReturnEventName() { } @Test - public void test_getRequestUrl_grpcProctol_shouldReturnRpcServiceAndMethod() { + public void test_getRequestUrl_grpcProctol_shouldReturnEnrichedAttribute() { Event event = mock(Event.class); when(event.getAttributes()) .thenReturn( Attributes.newBuilder() .setAttributeMap( Map.of( - "rpc.service", - AttributeValue.newBuilder().setValue("hipstershop.AdService").build(), - "rpc.method", - AttributeValue.newBuilder().setValue("GetEcho").build())) + "grpc.request.url", + AttributeValue.newBuilder().setValue("Recv.hipstershop.AdService").build())) .build()); when(event.getEventName()).thenReturn("Sent.hipstershop.AdService.GetAds"); assertEquals( - "hipstershop.AdService.GetEcho", - spanEventViewGenerator.getRequestUrl(event, Protocol.PROTOCOL_GRPC)); - } - - @Test - public void test_getRequestUrl_grpcProctol_shouldReturnEventNameIfOnlyRpcService() { - Event event = mock(Event.class); - when(event.getAttributes()) - .thenReturn( - Attributes.newBuilder() - .setAttributeMap( - Map.of( - "rpc.service", - AttributeValue.newBuilder().setValue("hipstershop.AdService").build())) - .build()); - when(event.getEventName()).thenReturn("Sent.hipstershop.AdService.GetAds"); - assertEquals( - "Sent.hipstershop.AdService.GetAds", - spanEventViewGenerator.getRequestUrl(event, Protocol.PROTOCOL_GRPC)); - } - - @Test - public void test_getRequestUrl_grpcProctol_shouldReturnEventNameIfOnlyRpcMethod() { - Event event = mock(Event.class); - when(event.getAttributes()) - .thenReturn( - Attributes.newBuilder() - .setAttributeMap( - Map.of("rpc.method", AttributeValue.newBuilder().setValue("GetEcho").build())) - .build()); - when(event.getEventName()).thenReturn("Sent.hipstershop.AdService.GetAds"); - assertEquals( - "Sent.hipstershop.AdService.GetAds", + "Recv.hipstershop.AdService", spanEventViewGenerator.getRequestUrl(event, Protocol.PROTOCOL_GRPC)); } diff --git a/semantic-convention-utils/src/main/java/org/hypertrace/semantic/convention/utils/rpc/RpcSemanticConventionUtils.java b/semantic-convention-utils/src/main/java/org/hypertrace/semantic/convention/utils/rpc/RpcSemanticConventionUtils.java index cc93b3985..5c507a60a 100644 --- a/semantic-convention-utils/src/main/java/org/hypertrace/semantic/convention/utils/rpc/RpcSemanticConventionUtils.java +++ b/semantic-convention-utils/src/main/java/org/hypertrace/semantic/convention/utils/rpc/RpcSemanticConventionUtils.java @@ -6,10 +6,12 @@ import static org.hypertrace.core.span.constants.v1.Envoy.ENVOY_REQUEST_SIZE; import static org.hypertrace.core.span.constants.v1.Envoy.ENVOY_RESPONSE_SIZE; import static org.hypertrace.core.span.constants.v1.Grpc.GRPC_ERROR_MESSAGE; +import static org.hypertrace.core.span.constants.v1.Grpc.GRPC_PATH; import static org.hypertrace.core.span.constants.v1.Grpc.GRPC_REQUEST_BODY; import static org.hypertrace.core.span.constants.v1.Grpc.GRPC_REQUEST_BODY_TRUNCATED; import static org.hypertrace.core.span.constants.v1.Grpc.GRPC_RESPONSE_BODY; import static org.hypertrace.core.span.constants.v1.Grpc.GRPC_RESPONSE_BODY_TRUNCATED; +import static org.hypertrace.core.span.constants.v1.Http.HTTP_REQUEST_HEADER_PATH; import static org.hypertrace.core.span.normalizer.constants.OTelSpanTag.OTEL_SPAN_TAG_RPC_METHOD; import static org.hypertrace.core.span.normalizer.constants.OTelSpanTag.OTEL_SPAN_TAG_RPC_SYSTEM; import static org.hypertrace.core.span.normalizer.constants.RpcSpanTag.RPC_ERROR_MESSAGE; @@ -55,10 +57,14 @@ */ public class RpcSemanticConventionUtils { + private static final Splitter SLASH_SPLITTER = Splitter.on("/").omitEmptyStrings().trimResults(); private static final Joiner DOT_JOINER = Joiner.on("."); private static final String LOCALHOST = "localhost"; private static final String COLON = ":"; + private static final String GRPC_RECV_DOT = "Recv."; + private static final String GRPC_SENT_DOT = "Sent."; + // otel specific attributes private static final String OTEL_RPC_SYSTEM = OTelRpcSemanticConventions.RPC_SYSTEM.getValue(); private static final String OTEL_RPC_METHOD = OTelRpcSemanticConventions.RPC_METHOD.getValue(); @@ -109,6 +115,9 @@ public class RpcSemanticConventionUtils { private static final String GRPC_RESPONSE_BODY_ATTR = RawSpanConstants.getValue(GRPC_RESPONSE_BODY); private static final String RPC_RESPONSE_BODY_ATTR = RPC_RESPONSE_BODY.getValue(); + private static final String RPC_REQUEST_METADATA_PATH_ATTR = RPC_REQUEST_METADATA_PATH.getValue(); + private static final String HTTP_REQUEST_HEADER_PATH_ATTR = + RawSpanConstants.getValue(HTTP_REQUEST_HEADER_PATH); /** * Differs from {@link @@ -446,6 +455,71 @@ public static Optional getGrpcRequestMetadataHost(Event event) { .map(AttributeValue::getValue); } + /** + * For RPC span, the span name is equivalent to Endpoint + * https://github.com/open-telemetry/opentelemetry-specification/blob/3e380e249f60c3a5f68746f5e84d10195ba41a79/specification/trace/semantic_conventions/rpc.md#span-name + * In cases if the convention is not followed, it will use a few fallback attributes. + * + *

This method assumed that it called if the protocol is GRPC, and returns endpoint in dotted + * format. Pl. check GrpcAttributeEnricher for reference. + */ + public static Optional getGrpcRequestEndpoint(Event event) { + if (isEventNamePrefixedWithRecvOrSent(event.getEventName()) + || event.getAttributes() == null + || event.getAttributes().getAttributeMap() == null) { + return stripRecvOrSent(event.getEventName()); + } + + Map attributeValueMap = event.getAttributes().getAttributeMap(); + + Optional attributeValue = + Optional.ofNullable(attributeValueMap.get(RPC_REQUEST_METADATA_PATH_ATTR)); + if (attributeValue.isPresent() && StringUtils.isNotBlank(attributeValue.get().getValue())) { + return sanitizePath(attributeValue.get().getValue()); + } + + Optional requestUrl = getRpcPath(event); + if (requestUrl.isPresent()) { + return requestUrl; + } + + attributeValue = Optional.ofNullable(attributeValueMap.get(HTTP_REQUEST_HEADER_PATH_ATTR)); + if (attributeValue.isPresent() && StringUtils.isNotBlank(attributeValue.get().getValue())) { + return sanitizePath(attributeValue.get().getValue()); + } + + attributeValue = + Optional.ofNullable(attributeValueMap.get(RawSpanConstants.getValue(GRPC_PATH))); + if (attributeValue.isPresent() && StringUtils.isNotBlank(attributeValue.get().getValue())) { + return sanitizePath(attributeValue.get().getValue()); + } + + return Optional.ofNullable(event.getEventName()); + } + + private static Optional stripRecvOrSent(String eventName) { + if (eventName.startsWith(GRPC_RECV_DOT)) { + return Optional.ofNullable( + StringUtils.trimToNull(eventName.substring(GRPC_RECV_DOT.length()))); + } else if (eventName.startsWith(GRPC_SENT_DOT)) { + return Optional.ofNullable( + StringUtils.trimToNull(eventName.substring(GRPC_SENT_DOT.length()))); + } + return Optional.of(eventName); + } + + private static boolean isEventNamePrefixedWithRecvOrSent(String eventName) { + return eventName != null + && (StringUtils.startsWith(eventName, GRPC_RECV_DOT) + || StringUtils.startsWith(eventName, GRPC_SENT_DOT)); + } + + static Optional sanitizePath(String path) { + return path.isBlank() + ? Optional.empty() + : Optional.ofNullable(DOT_JOINER.join(SLASH_SPLITTER.split(path))); + } + public static Optional getRpcPath(Event event) { String service = getRpcService(event).orElse(""); String method = getRpcMethod(event).orElse(""); diff --git a/semantic-convention-utils/src/test/java/org/hypertrace/semantic/convention/utils/rpc/RpcSemanticConventionUtilsTest.java b/semantic-convention-utils/src/test/java/org/hypertrace/semantic/convention/utils/rpc/RpcSemanticConventionUtilsTest.java index 19b9a136c..25dedf17a 100644 --- a/semantic-convention-utils/src/test/java/org/hypertrace/semantic/convention/utils/rpc/RpcSemanticConventionUtilsTest.java +++ b/semantic-convention-utils/src/test/java/org/hypertrace/semantic/convention/utils/rpc/RpcSemanticConventionUtilsTest.java @@ -910,6 +910,105 @@ public void testGetSanitizedAuthorityValue() { assertTrue(authority.isEmpty()); } + @Test + public void testGetGrpcRequestEndpoint() { + Event e = mock(Event.class); + + // case 1: event name starts with Sent. prefix + Attributes attributes = + buildAttributes( + Map.of("rpc.request.metadata.:path", "/TestGrpcService/getRpcMetadataPathEcho")); + when(e.getAttributes()).thenReturn(attributes); + when(e.getEventName()).thenReturn("Sent.TestService.getEventEchos"); + + assertEquals( + "TestService.getEventEchos", RpcSemanticConventionUtils.getGrpcRequestEndpoint(e).get()); + + // case 2: event name starts with Sent. prefix + attributes = + buildAttributes( + Map.of("rpc.request.metadata.:path", "/TestGrpcService/getRpcMetadataPathEcho")); + when(e.getAttributes()).thenReturn(attributes); + when(e.getEventName()).thenReturn("Recv.TestService.getEventEchos"); + + assertEquals( + "TestService.getEventEchos", RpcSemanticConventionUtils.getGrpcRequestEndpoint(e).get()); + + // case 3: attributes map is empty, fallback to event name + attributes = buildAttributes(Map.of()); + when(e.getAttributes()).thenReturn(attributes); + when(e.getEventName()).thenReturn("TestService.getEventEchos"); + + assertEquals( + "TestService.getEventEchos", RpcSemanticConventionUtils.getGrpcRequestEndpoint(e).get()); + + // case 4: event name doesn't start with req prefix, and rpc.system = grpc, + // uses rpc.request.metadata.:path + attributes = + buildAttributes( + Map.of( + "rpc.request.metadata.:path", "/TestGrpcService/getRpcRequestMetadataPathEcho", + "rpc.service", "TestRpcService", + "rpc.method", "getRpcMethodEcho", + "http.request.header.:path", "/TestHttpGrpcService/getHttpRequestHeaderPathEcho", + "grpc.path", "/TestGrpcService/getGrpcPathEcho")); + when(e.getAttributes()).thenReturn(attributes); + when(e.getEventName()).thenReturn("TestService.getEventEchos"); + + assertEquals( + "TestGrpcService.getRpcRequestMetadataPathEcho", + RpcSemanticConventionUtils.getGrpcRequestEndpoint(e).get()); + + // case 5: event name doesn't start with req prefix, and rpc.system = grpc, + // uses rpc.service and rpc.method + attributes = + buildAttributes( + Map.of( + "rpc.service", "TestRpcService", + "rpc.method", "getRpcMethodEcho", + "http.request.header.:path", "/TestHttpGrpcService/getHttpRequestHeaderPathEcho", + "grpc.path", "/TestGrpcService/getGrpcPathEcho")); + when(e.getAttributes()).thenReturn(attributes); + when(e.getEventName()).thenReturn("TestService.getEventEchos"); + + assertEquals( + "TestRpcService.getRpcMethodEcho", + RpcSemanticConventionUtils.getGrpcRequestEndpoint(e).get()); + + // case 6: event name doesn't start with req prefix, and rpc.system = grpc, + // uses http.request.header.:path + attributes = + buildAttributes( + Map.of( + "http.request.header.:path", "/TestHttpGrpcService/getHttpRequestHeaderPathEcho", + "grpc.path", "/TestGrpcService/getGrpcPathEcho")); + when(e.getAttributes()).thenReturn(attributes); + when(e.getEventName()).thenReturn("TestService.getEventEchos"); + + assertEquals( + "TestHttpGrpcService.getHttpRequestHeaderPathEcho", + RpcSemanticConventionUtils.getGrpcRequestEndpoint(e).get()); + + // case 7: event name doesn't start with req prefix, and rpc.system = grpc, + // uses grpc.path + attributes = buildAttributes(Map.of("grpc.path", "/TestGrpcService/getGrpcPathEcho")); + when(e.getAttributes()).thenReturn(attributes); + when(e.getEventName()).thenReturn("TestService.getEventEchos"); + + assertEquals( + "TestGrpcService.getGrpcPathEcho", + RpcSemanticConventionUtils.getGrpcRequestEndpoint(e).get()); + + // case 7: event name doesn't start with req prefix, and rpc.system = grpc, + // but all fallback attributes are missing, uses eventName + attributes = buildAttributes(Map.of()); + when(e.getAttributes()).thenReturn(attributes); + when(e.getEventName()).thenReturn("TestService.getEventEchos"); + + assertEquals( + "TestService.getEventEchos", RpcSemanticConventionUtils.getGrpcRequestEndpoint(e).get()); + } + private static Attributes buildAttributes(Map attributes) { return attributes.entrySet().stream() .collect( diff --git a/span-normalizer/raw-span-constants/build.gradle.kts b/span-normalizer/raw-span-constants/build.gradle.kts index 12c3f8ffa..b8682c551 100644 --- a/span-normalizer/raw-span-constants/build.gradle.kts +++ b/span-normalizer/raw-span-constants/build.gradle.kts @@ -15,11 +15,11 @@ val generateLocalGoGrpcFiles = false protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.15.7" + artifact = "com.google.protobuf:protoc:3.17.3" } plugins { id("grpc_java") { - artifact = "io.grpc:protoc-gen-grpc-java:1.41.0" + artifact = "io.grpc:protoc-gen-grpc-java:1.42.0" } if (generateLocalGoGrpcFiles) { @@ -57,6 +57,11 @@ sourceSets { } dependencies { - api("com.google.protobuf:protobuf-java-util:3.15.7") + api("com.google.protobuf:protobuf-java-util:3.17.3") implementation("org.slf4j:slf4j-api:1.7.30") + constraints { + implementation("com.google.code.gson:gson:2.8.9") { + because("https://snyk.io/vuln/SNYK-JAVA-COMGOOGLECODEGSON-1730327") + } + } } diff --git a/span-normalizer/raw-span-constants/src/main/proto/org/hypertrace/core/span/constants/v1/span_attribute.proto b/span-normalizer/raw-span-constants/src/main/proto/org/hypertrace/core/span/constants/v1/span_attribute.proto index c9a79dc6f..6dc5535b5 100644 --- a/span-normalizer/raw-span-constants/src/main/proto/org/hypertrace/core/span/constants/v1/span_attribute.proto +++ b/span-normalizer/raw-span-constants/src/main/proto/org/hypertrace/core/span/constants/v1/span_attribute.proto @@ -96,6 +96,7 @@ enum Grpc { GRPC_METHOD = 10 [(string_value) = "grpc.method"]; GRPC_REQUEST_BODY_TRUNCATED = 11 [(string_value) = "grpc.request.body.truncated"]; GRPC_RESPONSE_BODY_TRUNCATED = 12 [(string_value) = "grpc.response.body.truncated"]; + GRPC_PATH = 13 [(string_value) = "grpc.path"]; } // Error related attributes diff --git a/span-normalizer/span-normalizer-api/build.gradle.kts b/span-normalizer/span-normalizer-api/build.gradle.kts index c53b1161e..68b1dd61d 100644 --- a/span-normalizer/span-normalizer-api/build.gradle.kts +++ b/span-normalizer/span-normalizer-api/build.gradle.kts @@ -16,11 +16,11 @@ val generateLocalGoGrpcFiles = false protobuf { protoc { - artifact = "com.google.protobuf:protoc:3.15.7" + artifact = "com.google.protobuf:protoc:3.17.3" } plugins { id("grpc_java") { - artifact = "io.grpc:protoc-gen-grpc-java:1.41.0" + artifact = "io.grpc:protoc-gen-grpc-java:1.42.0" } if (generateLocalGoGrpcFiles) { diff --git a/span-normalizer/span-normalizer/build.gradle.kts b/span-normalizer/span-normalizer/build.gradle.kts index 8a8ab9197..da4c04ea6 100644 --- a/span-normalizer/span-normalizer/build.gradle.kts +++ b/span-normalizer/span-normalizer/build.gradle.kts @@ -40,7 +40,7 @@ dependencies { implementation("org.hypertrace.core.kafkastreams.framework:kafka-streams-framework:0.1.21") // Required for the GRPC clients. - runtimeOnly("io.grpc:grpc-netty:1.41.0") + runtimeOnly("io.grpc:grpc-netty:1.42.0") constraints { runtimeOnly("io.netty:netty-codec-http2:4.1.68.Final") { because("https://snyk.io/vuln/SNYK-JAVA-IONETTY-1083991")