diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index 8de61aa5b677..500a05bd898b 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -784,6 +784,37 @@ libraries: target_versions: javaagent: - com.linecorp.armeria:armeria-grpc:[1.14.0,) + telemetry: + - when: default + spans: + - span_kind: CLIENT + attributes: + - name: rpc.grpc.status_code + type: LONG + - name: rpc.method + type: STRING + - name: rpc.service + type: STRING + - name: rpc.system + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - span_kind: SERVER + attributes: + - name: rpc.grpc.status_code + type: LONG + - name: rpc.method + type: STRING + - name: rpc.service + type: STRING + - name: rpc.system + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG async: - name: async-http-client-1.9 description: This instrumentation enables CLIENT spans and metrics for version diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/MetricParser.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/MetricParser.java index b1563555c5ca..e167de0dd458 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/MetricParser.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/MetricParser.java @@ -109,7 +109,7 @@ public static Map> aggregateMetrics( aggregatedMetrics.computeIfAbsent(when, k -> new HashMap<>()); for (EmittedMetrics.MetricsByScope metricsByScope : metrics.getMetricsByScope()) { - if (metricsByScope.getScope().equals(targetScopeName)) { + if (TelemetryParser.scopeIsValid(metricsByScope.getScope(), targetScopeName)) { for (EmittedMetrics.Metric metric : metricsByScope.getMetrics()) { AggregatedMetricInfo aggInfo = metricKindMap.computeIfAbsent( diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/SpanParser.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/SpanParser.java index cd4b3fd71362..4cb5aa32c70d 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/SpanParser.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/SpanParser.java @@ -87,7 +87,7 @@ public static Map>> aggregateSpans( aggregatedAttributes.computeIfAbsent(when, k -> new HashMap<>()); for (EmittedSpans.SpansByScope spansByScope : spans.getSpansByScope()) { - if (spansByScope.getScope().equals(targetScopeName)) { + if (TelemetryParser.scopeIsValid(spansByScope.getScope(), targetScopeName)) { processSpansForScope(spansByScope, spanKindMap); } } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/TelemetryParser.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/TelemetryParser.java new file mode 100644 index 000000000000..a49fa551c87e --- /dev/null +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/TelemetryParser.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.docs.parsers; + +import java.util.Map; +import java.util.Set; + +class TelemetryParser { + + // Key is the scope of the module being analyzed, value is a set of additional allowed scopes. + private static final Map> scopeAllowList = + Map.of("io.opentelemetry.armeria-grpc-1.14", Set.of("io.opentelemetry.grpc-1.6")); + + /** + * Checks if the given telemetry scope is valid for the specified module scope. + * + *

If an instrumentation module uses an instrumenter or telemetry class from another module, it + * might report telemetry with a different scope name, resulting in us excluding it. There are + * cases where we want to include this data, so we provide this way to override that exclusion + * filter. + * + * @param telemetryScope the scope of the telemetry signal + * @param moduleScope the scope of the module being analyzed + * @return true if the telemetry scope is valid for the module, false otherwise + */ + static boolean scopeIsValid(String telemetryScope, String moduleScope) { + return telemetryScope.equals(moduleScope) + || scopeAllowList.getOrDefault(moduleScope, Set.of()).contains(telemetryScope); + } + + private TelemetryParser() {} +} diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/MetricParserTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/MetricParserTest.java index 8d5a6e81ab64..f845c501299a 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/MetricParserTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/MetricParserTest.java @@ -107,6 +107,31 @@ void testPreservesMetricMetadata() { assertThat(foundMetric.getUnit()).isEqualTo("unit"); } + @Test + void testScopeOverrideForGrpc() { + String targetScopeName = "io.opentelemetry.armeria-grpc-1.14"; + + EmittedMetrics.Metric metric1 = + createMetric("my.metric1", "description of my.metric1", "attr1"); + + EmittedMetrics.MetricsByScope targetMetricsByScope = + new EmittedMetrics.MetricsByScope("io.opentelemetry.grpc-1.6", List.of(metric1)); + + EmittedMetrics emittedMetrics = new EmittedMetrics("default", List.of(targetMetricsByScope)); + + Map> metrics = + MetricParser.MetricAggregator.aggregateMetrics("default", emittedMetrics, targetScopeName); + + Map> result = + MetricParser.MetricAggregator.buildFilteredMetrics(metrics); + + EmittedMetrics.Metric foundMetric = result.get("default").get(0); + assertThat(foundMetric.getName()).isEqualTo("my.metric1"); + assertThat(foundMetric.getDescription()).isEqualTo("description of my.metric1"); + assertThat(foundMetric.getType()).isEqualTo("gauge"); + assertThat(foundMetric.getUnit()).isEqualTo("unit"); + } + private static EmittedMetrics.Metric createMetric( String name, String description, String attrName) { return new EmittedMetrics.Metric( diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/SpanParserTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/SpanParserTest.java index c050af7a9ba2..5e67c441b913 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/SpanParserTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/SpanParserTest.java @@ -195,4 +195,70 @@ void testSpanAggregatorFiltersAndAggregatesCorrectly() { .extracting(TelemetryAttribute::getName) .containsExactly("my.operation"); } + + @Test + void getSpansFromFilesIncludesAllowListedScopes(@TempDir Path tempDir) throws IOException { + Path telemetryDir = Files.createDirectories(tempDir.resolve(".telemetry")); + + String file1Content = + """ + when: default + spans_by_scope: + - scope: io.opentelemetry.grpc-1.6 + spans: + - span_kind: CLIENT + attributes: + - name: rpc.system + type: STRING + - name: rpc.grpc.status_code + type: LONG + - name: server.port + type: LONG + - name: rpc.method + type: STRING + - name: rpc.service + type: STRING + - name: server.address + type: STRING + - span_kind: SERVER + attributes: + - name: rpc.system + type: STRING + - name: rpc.grpc.status_code + type: LONG + - name: server.port + type: LONG + - name: rpc.method + type: STRING + - name: rpc.service + type: STRING + - name: server.address + type: STRING + """; + + Files.writeString(telemetryDir.resolve("spans-1.yaml"), file1Content); + + try (MockedStatic fileManagerMock = mockStatic(FileManager.class)) { + fileManagerMock + .when(() -> FileManager.readFileToString(telemetryDir.resolve("spans-1.yaml").toString())) + .thenReturn(file1Content); + + Map fileContents = + EmittedSpanParser.getSpansByScopeFromFiles(tempDir.toString(), ""); + + EmittedSpans emittedSpans = fileContents.get("default"); + + // Aggregate spans - only target scope should be included + Map>> spans = + SpanParser.SpanAggregator.aggregateSpans( + "default", emittedSpans, "io.opentelemetry.armeria-grpc-1.14"); + + Map> result = + SpanParser.SpanAggregator.buildFilteredSpans(spans); + + assertThat(result.size()).isEqualTo(1); + assertThat(result.get("default")).isNotNull(); + assertThat(result.get("default").size()).isEqualTo(2); + } + } }