From 58775ccf941a8720a2296c1e607b061053e17534 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 16 Jun 2025 07:01:28 -0400 Subject: [PATCH 1/5] span parser --- .../docs/internal/EmittedMetrics.java | 43 +---- .../docs/internal/EmittedSpans.java | 126 +++++++++++++ .../docs/internal/TelemetryAttribute.java | 36 ++++ .../docs/parsers/MetricParser.java | 6 +- .../docs/parsers/SpanParser.java | 167 ++++++++++++++++++ .../docs/utils/YamlHelper.java | 8 +- .../docs/parsers/SpanParserTest.java | 95 ++++++++++ .../docs/utils/YamlHelperTest.java | 11 +- .../testing/AgentTestRunner.java | 6 +- .../testing/InstrumentationTestRunner.java | 41 +++++ .../testing/internal/MetaDataCollector.java | 62 ++++++- 11 files changed, 549 insertions(+), 52 deletions(-) create mode 100644 instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedSpans.java create mode 100644 instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java create mode 100644 instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/SpanParser.java create mode 100644 instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/SpanParserTest.java diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java index c13c11247410..c45e65e79c8f 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java @@ -53,10 +53,10 @@ public static class Metric { private String description; private String type; private String unit; - private List attributes; + private List attributes; public Metric( - String name, String description, String type, String unit, List attributes) { + String name, String description, String type, String unit, List attributes) { this.name = name; this.description = description; this.type = type; @@ -104,47 +104,12 @@ public void setUnit(String unit) { this.unit = unit; } - public List getAttributes() { + public List getAttributes() { return attributes; } - public void setAttributes(List attributes) { + public void setAttributes(List attributes) { this.attributes = attributes; } } - - /** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - */ - public static class Attribute { - private String name; - private String type; - - public Attribute() { - this.name = ""; - this.type = ""; - } - - public Attribute(String name, String type) { - this.name = name; - this.type = type; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getType() { - return type; - } - - public void setType(String type) { - this.type = type; - } - } } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedSpans.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedSpans.java new file mode 100644 index 000000000000..0dbb310daf69 --- /dev/null +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedSpans.java @@ -0,0 +1,126 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.docs.internal; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; +import java.util.List; + +import static java.util.Collections.emptyList; + +/** + * Representation of spans emitted by an instrumentation. Includes context about whether emitted + * by default or via a configuration option. This class is internal and is hence not for public use. + * Its APIs are unstable and can change at any time. + */ +public class EmittedSpans { + // Condition in which the telemetry is emitted (ex: default, or configuration option names). + private String when; + + @JsonProperty("spans_by_scope") private List spansByScope; + + public EmittedSpans() { + this.when = ""; + this.spansByScope = emptyList(); + } + + public EmittedSpans(String when, List spansByScope) { + this.when = when; + this.spansByScope = spansByScope; + } + + public String getWhen() { + return when; + } + + public void setWhen(String when) { + this.when = when; + } + + @JsonProperty("spans_by_scope") + public List getSpansByScope() { + return spansByScope; + } + + @JsonProperty("spans_by_scope") + public void setSpansByScope(List spans) { + this.spansByScope = spans; + } + + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static class SpansByScope { + private String scope; + private List spans; + + public SpansByScope(String scopeName, List spans) { + this.scope = scopeName; + this.spans = spans; + } + + public SpansByScope() { + this.scope = ""; + this.spans = emptyList(); + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public List getSpans() { + return spans; + } + + public void setSpans(List spans) { + this.spans = spans; + } + } + + /** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ + public static class Span { + @JsonProperty("span_kind") private String spanKind; + private List attributes; + + public Span( + String spanKind, List attributes) { + this.spanKind = spanKind; + this.attributes = attributes; + } + + public Span() { + this.spanKind = ""; + this.attributes = new ArrayList<>(); + } + + @JsonProperty("span_kind") + public String getSpanKind() { + return spanKind; + } + + @JsonProperty("span_kind") + public void setSpanKind(String spanKind) { + this.spanKind = spanKind; + } + + public List getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + } +} diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java new file mode 100644 index 000000000000..fb7beb1e2e59 --- /dev/null +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java @@ -0,0 +1,36 @@ +package io.opentelemetry.instrumentation.docs.internal; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class TelemetryAttribute { + private String name; + private String type; + + public TelemetryAttribute() { + this.name = ""; + this.type = ""; + } + + public TelemetryAttribute(String name, String type) { + this.name = name; + this.type = type; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } +} 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 50ae9fd5b6c9..c32efcc187de 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 @@ -23,8 +23,8 @@ public class MetricParser { private static final Logger logger = Logger.getLogger(MetricParser.class.getName()); /** - * Looks for metric files in the .telemetry directory, and combines them into a single list of - * metrics. + * Looks for metric files in the .telemetry directory, and combines them into a map where the key + * represents the "when", and the value is a list of metrics emitted under that condition. * * @param instrumentationDirectory the directory to traverse * @return contents of aggregated files @@ -67,7 +67,7 @@ public static Map getMetricsFromFiles( /** * Takes in a raw string representation of the aggregated EmittedMetrics yaml map, separated by * the `when`, indicating the conditions under which the metrics are emitted. deduplicates the - * metrics by name and then returns a new map EmittedMetrics objects. + * metrics by name and then returns a new map of EmittedMetrics objects. * * @param input raw string representation of EmittedMetrics yaml * @return {@code Map} where the key is the `when` condition 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 new file mode 100644 index 000000000000..01ca5f7978f2 --- /dev/null +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/SpanParser.java @@ -0,0 +1,167 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.docs.parsers; + +import com.fasterxml.jackson.core.JsonProcessingException; +import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; +import io.opentelemetry.instrumentation.docs.utils.FileManager; +import io.opentelemetry.instrumentation.docs.utils.YamlHelper; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import java.util.stream.Stream; + +public class SpanParser { + private static final Logger logger = Logger.getLogger(SpanParser.class.getName()); + + /** + * Looks for span files in the .telemetry directory, and combines them into a single map. + * + * @param instrumentationDirectory the directory to traverse + * @return contents of aggregated files + */ + public static Map getSpansByScopeFromFiles( + String rootDir, String instrumentationDirectory) throws JsonProcessingException { + Map spansByScope = new HashMap<>(); + Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry"); + + if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) { + try (Stream files = Files.list(telemetryDir)) { + files + .filter(path -> path.getFileName().toString().startsWith("spans-")) + .forEach( + path -> { + String content = FileManager.readFileToString(path.toString()); + if (content != null) { + String when = content.substring(0, content.indexOf('\n')); + String whenKey = when.replace("when: ", ""); + + spansByScope.putIfAbsent(whenKey, new StringBuilder("spans_by_scope:\n")); + + // Skip the metric spans_by_scope ("metrics:") so we can aggregate into one list + int metricsIndex = content.indexOf("spans_by_scope:\n"); + if (metricsIndex != -1) { + String contentAfterMetrics = + content.substring(metricsIndex + "spans_by_scope:\n".length()); + spansByScope.get(whenKey).append(contentAfterMetrics); + } + } + }); + } catch (IOException e) { + logger.severe("Error reading span files: " + e.getMessage()); + } + } + + return parseSpans(spansByScope); + } + + /** + * Takes in a raw string representation of the aggregated EmittedSpan yaml map, separated by + * the `when`, indicating the conditions under which the telemetry is emitted. deduplicates by + * name and then returns a new map. + * + * @param input raw string representation of EmittedMetrics yaml + * @return {@code Map} where the key is the `when` condition + */ + // visible for testing + public static Map parseSpans(Map input) throws JsonProcessingException { + Map result = new HashMap<>(); + + for (Map.Entry entry : input.entrySet()) { + String when = entry.getKey().strip(); + StringBuilder content = entry.getValue(); + + EmittedSpans spans = YamlHelper.emittedSpansParser(content.toString()); + if (spans.getSpansByScope().isEmpty()) { + continue; + } + + Map>> attributesByScopeAndSpanKind = new HashMap<>(); + + for (EmittedSpans.SpansByScope spansByScopeEntry : spans.getSpansByScope()) { + String scope = spansByScopeEntry.getScope(); + + attributesByScopeAndSpanKind.putIfAbsent(scope, new HashMap<>()); + Map> attributesBySpanKind = attributesByScopeAndSpanKind.get(scope); + + for (EmittedSpans.Span span : spansByScopeEntry.getSpans()) { + String spanKind = span.getSpanKind(); + + attributesBySpanKind.putIfAbsent(spanKind, new HashSet<>()); + Set attributeSet = attributesBySpanKind.get(spanKind); + + if (span.getAttributes() != null) { + for (TelemetryAttribute attr : span.getAttributes()) { + attributeSet.add(new TelemetryAttribute(attr.getName(), attr.getType())); + } + } + } + } + + EmittedSpans deduplicatedEmittedSpans = getEmittedSpans(attributesByScopeAndSpanKind, when); + result.put(when, deduplicatedEmittedSpans); + } + + return result; + } + + /** + * Takes in a map of attributes by scope and span kind, and returns an {@link EmittedSpans} object + * with deduplicated spans. + * + * @param attributesByScopeAndSpanKind the map of attributes by scope and span kind + * @param when the condition under which the telemetry is emitted + * @return an {@link EmittedSpans} object with deduplicated spans + */ + private static EmittedSpans getEmittedSpans( + Map>> attributesByScopeAndSpanKind, String when) { + List deduplicatedSpansByScope = new ArrayList<>(); + + for (Map.Entry>> scopeEntry : attributesByScopeAndSpanKind.entrySet()) { + String scope = scopeEntry.getKey(); + EmittedSpans.SpansByScope deduplicatedScope = getSpansByScope(scopeEntry, scope); + deduplicatedSpansByScope.add(deduplicatedScope); + } + + return new EmittedSpans(when, deduplicatedSpansByScope); + } + + /** + * Converts a map entry of scope and its attributes into an {@link EmittedSpans.SpansByScope} object. + * + * @param scopeEntry the map entry containing scope and its attributes + * @param scope the name of the scope + * @return an {@link EmittedSpans.SpansByScope} object with deduplicated spans + */ + private static EmittedSpans.SpansByScope getSpansByScope( + Map.Entry>> scopeEntry, String scope) { + Map> spanKindMap = scopeEntry.getValue(); + + List deduplicatedSpans = new ArrayList<>(); + + for (Map.Entry> spanKindEntry : spanKindMap.entrySet()) { + String spanKind = spanKindEntry.getKey(); + Set attributes = spanKindEntry.getValue(); + + List attributeList = new ArrayList<>(attributes); + EmittedSpans.Span deduplicatedSpan = new EmittedSpans.Span(spanKind, attributeList); + deduplicatedSpans.add(deduplicatedSpan); + } + + return new EmittedSpans.SpansByScope(scope, deduplicatedSpans); + } + + private SpanParser() {} +} diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java index 30743e2e2148..c84e12f6e5a1 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java @@ -11,6 +11,7 @@ import io.opentelemetry.instrumentation.docs.internal.ConfigurationOption; import io.opentelemetry.instrumentation.docs.internal.ConfigurationType; import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; +import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; import io.opentelemetry.instrumentation.docs.internal.InstrumentationClassification; import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; @@ -25,6 +26,7 @@ import java.util.TreeMap; import java.util.logging.Logger; import java.util.stream.Collectors; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; @@ -229,7 +231,7 @@ private static Map getMetricsMap(EmittedMetrics.Metric metric) { innerMetricMap.put("unit", metric.getUnit()); List> attributes = new ArrayList<>(); - for (EmittedMetrics.Attribute attribute : metric.getAttributes()) { + for (TelemetryAttribute attribute : metric.getAttributes()) { Map attributeMap = new LinkedHashMap<>(); attributeMap.put("name", attribute.getName()); attributeMap.put("type", attribute.getType()); @@ -248,5 +250,9 @@ public static EmittedMetrics emittedMetricsParser(String input) { return new Yaml().loadAs(input, EmittedMetrics.class); } + public static EmittedSpans emittedSpansParser(String input) throws JsonProcessingException { + return mapper.readValue(input, EmittedSpans.class); + } + private YamlHelper() {} } 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 new file mode 100644 index 000000000000..a37869b098be --- /dev/null +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/parsers/SpanParserTest.java @@ -0,0 +1,95 @@ +package io.opentelemetry.instrumentation.docs.parsers; + +import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; +import io.opentelemetry.instrumentation.docs.utils.FileManager; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.MockedStatic; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mockStatic; + +@SuppressWarnings("NullAway") +class SpanParserTest { + + @Test + void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOException { + Path telemetryDir = Files.createDirectories(tempDir.resolve(".telemetry")); + + String file1Content = """ + when: default + spans_by_scope: + - scope: test + spans: + - span_kind: INTERNAL + attributes: + - scope: io.opentelemetry.clickhouse-client-0.5 + spans: + - span_kind: SERVER + attributes: + - name: server.address + type: STRING + """; + + String file2Content = """ + when: default + spans_by_scope: + - scope: test + spans: + - span_kind: INTERNAL + attributes: + - scope: io.opentelemetry.clickhouse-client-0.5 + spans: + - span_kind: CLIENT + attributes: + - name: db.statement + type: STRING + - name: server.port + type: LONG + - name: db.system + type: STRING + - name: server.address + type: STRING + - name: db.name + type: STRING + - name: db.operation + type: STRING + """; + + Files.writeString(telemetryDir.resolve("spans-1.yaml"), file1Content); + Files.writeString(telemetryDir.resolve("spans-2.yaml"), file2Content); + + try (MockedStatic fileManagerMock = mockStatic(FileManager.class)) { + fileManagerMock + .when( + () -> FileManager.readFileToString(telemetryDir.resolve("spans-1.yaml").toString())) + .thenReturn(file1Content); + fileManagerMock + .when( + () -> FileManager.readFileToString(telemetryDir.resolve("spans-2.yaml").toString())) + .thenReturn(file2Content); + + Map result = SpanParser.getSpansByScopeFromFiles(tempDir.toString(), ""); + + EmittedSpans spans = result.get("default"); + assertThat(spans.getSpansByScope()).hasSize(2); + + List clickHouseSpans = + spans.getSpansByScope().stream().filter(item -> item.getScope().equals("io.opentelemetry.clickhouse-client-0.5")).map( + EmittedSpans.SpansByScope::getSpans).findFirst().orElse(null); + assertThat(clickHouseSpans).hasSize(2); + + + List testSpans = + spans.getSpansByScope().stream().filter(item -> item.getScope().equals("test")).map( + EmittedSpans.SpansByScope::getSpans).findFirst().orElse(null); + // deduped should have only one span + assertThat(testSpans).hasSize(1); + } + } +} diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java index b23467aa891e..d09005e9bdab 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import org.junit.jupiter.api.Test; class YamlHelperTest { @@ -297,11 +298,11 @@ void testMetricsParsing() throws Exception { "HISTOGRAM", "s", List.of( - new EmittedMetrics.Attribute("db.namespace", "STRING"), - new EmittedMetrics.Attribute("db.operation.name", "STRING"), - new EmittedMetrics.Attribute("db.system.name", "STRING"), - new EmittedMetrics.Attribute("server.address", "STRING"), - new EmittedMetrics.Attribute("server.port", "LONG"))); + new TelemetryAttribute("db.namespace", "STRING"), + new TelemetryAttribute("db.operation.name", "STRING"), + new TelemetryAttribute("db.system.name", "STRING"), + new TelemetryAttribute("server.address", "STRING"), + new TelemetryAttribute("server.port", "LONG"))); targetVersions.put( InstrumentationType.LIBRARY, new HashSet<>(List.of("org.apache.mylib:mylib-core:2.3.0"))); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java index 5099b58bfd9b..f2cd0e7998fc 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java @@ -66,15 +66,15 @@ public void afterTestClass() throws IOException { // Generates files in a `.telemetry` directory within the instrumentation module with all // captured emitted metadata to be used by the instrumentation-docs Doc generator. - if (Boolean.getBoolean("collectMetadata")) { +// if (Boolean.getBoolean("collectMetadata")) { URL resource = this.getClass().getClassLoader().getResource(""); if (resource == null) { return; } String path = Paths.get(resource.getPath()).toString(); - MetaDataCollector.writeTelemetryToFiles(path, metrics); - } + MetaDataCollector.writeTelemetryToFiles(path, metrics, tracesByScope); +// } // additional library ignores are ignored during tests, because they can make it really // confusing for contributors wondering why their instrumentation is not applied diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java index 8cae71b0684c..7f38424a8469 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java @@ -9,9 +9,14 @@ import static org.awaitility.Awaitility.await; import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributeType; +import io.opentelemetry.api.internal.InternalAttributeKeyImpl; +import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil; import io.opentelemetry.instrumentation.testing.util.ThrowingRunnable; import io.opentelemetry.instrumentation.testing.util.ThrowingSupplier; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.testing.assertj.MetricAssert; @@ -48,6 +53,12 @@ public abstract class InstrumentationTestRunner { private final TestInstrumenters testInstrumenters; protected Map metrics = new HashMap<>(); + /** + * Stores traces by scope, where each scope contains a map of span kinds to a map of attribute + * keys to their types. This is used to collect metadata about the spans emitted during tests. + */ + protected Map, AttributeType>>> tracesByScope = new HashMap<>(); + protected InstrumentationTestRunner(OpenTelemetry openTelemetry) { testInstrumenters = new TestInstrumenters(openTelemetry); } @@ -139,6 +150,10 @@ private > void doAssertTraces( traces.sort(traceComparator); } TracesAssert.assertThat(traces).hasTracesSatisfyingExactly(assertionsList); + +// if (Boolean.getBoolean("collectMetadata")) { + collectEmittedSpans(traces); +// } } /** @@ -194,6 +209,32 @@ private void collectEmittedMetrics(List metrics) { } } + private void collectEmittedSpans(List> spans) { + for (List spanList : spans) { + for (SpanData span : spanList) { + String spanName = span.getName(); + System.out.println(spanName); + Map, AttributeType>> scopeMap = + this.tracesByScope.computeIfAbsent(span.getInstrumentationScopeInfo(), m -> new HashMap<>()); + + Map, AttributeType> spanKindMap = scopeMap.computeIfAbsent(span.getKind(), s -> new HashMap<>()); + + for (Map.Entry, Object> key : span.getAttributes().asMap().entrySet()) { + if (!(key.getKey() instanceof InternalAttributeKeyImpl)) { + // We only collect internal attributes, so skip any non-internal attributes. + continue; + } + InternalAttributeKeyImpl keyImpl = (InternalAttributeKeyImpl) key.getKey(); + if (!spanKindMap.containsKey(keyImpl)) { + spanKindMap.put(keyImpl, key.getValue() != null ? key.getKey().getType() : null); + } + } + + } + } + } + + public final List waitForLogRecords(int numberOfLogRecords) { awaitUntilAsserted( () -> assertThat(getExportedLogRecords().size()).isEqualTo(numberOfLogRecords), diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java index 76ddac93b6d9..cf117466a79e 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java @@ -7,6 +7,10 @@ import static java.nio.charset.StandardCharsets.UTF_8; +import io.opentelemetry.api.common.AttributeType; +import io.opentelemetry.api.internal.InternalAttributeKeyImpl; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.metrics.data.MetricData; import java.io.BufferedWriter; import java.io.IOException; @@ -33,11 +37,15 @@ public final class MetaDataCollector { private static final Pattern MODULE_PATTERN = Pattern.compile("(.*?/instrumentation/.*?)(/javaagent/|/library/)"); - public static void writeTelemetryToFiles(String path, Map metrics) + public static void writeTelemetryToFiles( + String path, + Map metrics, + Map, AttributeType>>> spansByScopeAndKind) throws IOException { String moduleRoot = extractInstrumentationPath(path); writeMetricData(moduleRoot, metrics); + writeSpanData(moduleRoot, spansByScopeAndKind); } private static String extractInstrumentationPath(String path) { @@ -60,6 +68,58 @@ private static String extractInstrumentationPath(String path) { return instrumentationPath; } + private static void writeSpanData( + String instrumentationPath, + Map, AttributeType>>> spansByScopeAndKind) + throws IOException { + + if (spansByScopeAndKind.isEmpty()) { + return; + } + + Path spansPath = + Paths.get(instrumentationPath, TMP_DIR, "spans-" + UUID.randomUUID() + ".yaml"); + + try (BufferedWriter writer = Files.newBufferedWriter(spansPath.toFile().toPath(), UTF_8)) { + String config = System.getProperty("metaDataConfig"); + String when = "default"; + if (config != null && !config.isEmpty()) { + when = config; + } + + writer.write("when: " + when + "\n"); + + writer.write("spans_by_scope:\n"); + + for (Map.Entry, AttributeType>>> entry : + spansByScopeAndKind.entrySet()) { + InstrumentationScopeInfo scope = entry.getKey(); + Map, AttributeType>> spansByKind = entry.getValue(); + + writer.write(" - scope: " + scope.getName() + "\n"); + writer.write(" spans:\n"); + + for (Map.Entry, AttributeType>> kindEntry : + spansByKind.entrySet()) { + SpanKind spanKind = kindEntry.getKey(); + Map, AttributeType> attributes = kindEntry.getValue(); + + writer.write(" - span_kind: " + spanKind.toString() + "\n"); + writer.write(" attributes:\n"); + attributes.forEach( + (key, value) -> { + try { + writer.write(" - name: " + key.getKey() + "\n"); + writer.write(" type: " + key.getType().toString() + "\n"); + } catch (IOException e) { + throw new IllegalStateException(e); + } + }); + } + } + } + } + private static void writeMetricData(String instrumentationPath, Map metrics) throws IOException { From 597516721abad6c9128d37a88c07f024d9aa9b79 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Mon, 16 Jun 2025 16:55:47 -0400 Subject: [PATCH 2/5] Implement span interceptor and gatherer --- docs/instrumentation-list.yaml | 651 +++++++++++++++++- .../docs/InstrumentationAnalyzer.java | 79 ++- .../docs/internal/EmittedMetrics.java | 6 +- .../docs/internal/EmittedSpans.java | 19 +- .../docs/internal/InstrumentationModule.java | 17 + .../docs/internal/TelemetryAttribute.java | 5 + .../docs/parsers/SpanParser.java | 21 +- .../docs/utils/YamlHelper.java | 41 +- .../docs/InstrumentationAnalyzerTest.java | 44 ++ .../docs/parsers/SpanParserTest.java | 47 +- .../docs/utils/YamlHelperTest.java | 67 +- .../testing/AgentTestRunner.java | 18 +- .../testing/InstrumentationTestRunner.java | 18 +- .../testing/internal/MetaDataCollector.java | 15 +- 14 files changed, 969 insertions(+), 79 deletions(-) diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index f711f11a6f5a..2f1ecaeb52b9 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -24,6 +24,39 @@ libraries: - com.typesafe.akka:akka-http_2.12:[10,) - com.typesafe.akka:akka-http_2.13:[10,) - com.typesafe.akka:akka-http_2.11:[10,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: http.route + type: STRING + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING - name: akka-actor-fork-join-2.5 source_path: instrumentation/akka/akka-actor-fork-join-2.5 scope: @@ -163,6 +196,24 @@ libraries: target_versions: javaagent: - org.apache.httpcomponents:httpasyncclient:[4.1,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: apache-httpclient-4.3 source_path: instrumentation/apache-httpclient/apache-httpclient-4.3 scope: @@ -404,6 +455,22 @@ libraries: description: Allows configuring headers to capture as span attributes. type: list default: '' + telemetry: + - when: otel.semconv-stability.opt-in=database + metrics: + - name: db.client.operation.duration + description: Duration of database client operations. + type: HISTOGRAM + unit: s + attributes: + - name: db.operation.name + type: STRING + - name: db.system.name + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: aws-sdk-2.2 source_path: instrumentation/aws-sdk/aws-sdk-2.2 scope: @@ -450,6 +517,44 @@ libraries: should be recorded as events for the SDK span. type: boolean default: false + telemetry: + - when: default + metrics: + - name: gen_ai.client.operation.duration + description: GenAI operation duration. + type: HISTOGRAM + unit: s + attributes: + - name: gen_ai.operation.name + type: STRING + - name: gen_ai.request.model + type: STRING + - name: gen_ai.system + type: STRING + - name: gen_ai.client.token.usage + description: Measures number of input and output tokens used. + type: HISTOGRAM + unit: token + attributes: + - name: gen_ai.operation.name + type: STRING + - name: gen_ai.request.model + type: STRING + - name: gen_ai.system + type: STRING + - name: gen_ai.token.type + type: STRING + - when: otel.semconv-stability.opt-in=database + metrics: + - name: db.client.operation.duration + description: Duration of database client operations. + type: HISTOGRAM + unit: s + attributes: + - name: db.operation.name + type: STRING + - name: db.system.name + type: STRING azure: - name: azure-core-1.36 source_path: instrumentation/azure-core/azure-core-1.36 @@ -600,6 +705,22 @@ libraries: type: boolean default: true telemetry: + - when: default + spans: + - span_kind: CLIENT + attributes: + - name: db.operation + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: db.name + type: STRING + - name: db.system + type: STRING + - name: db.statement + type: STRING - when: otel.semconv-stability.opt-in=database metrics: - name: db.client.operation.duration @@ -617,6 +738,25 @@ libraries: type: STRING - name: server.port type: LONG + spans: + - span_kind: CLIENT + attributes: + - name: db.system.name + type: STRING + - name: db.namespace + type: STRING + - name: error.type + type: STRING + - name: db.operation.name + type: STRING + - name: server.port + type: LONG + - name: db.query.text + type: STRING + - name: db.response.status_code + type: STRING + - name: server.address + type: STRING couchbase: - name: couchbase-3.1.6 description: | @@ -814,6 +954,22 @@ libraries: target_versions: javaagent: - com.google.http-client:google-http-client:[1.19.0,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: server.address + type: STRING + - name: server.port + type: LONG grails: - name: grails-3.0 source_path: instrumentation/grails-3.0 @@ -1062,6 +1218,24 @@ libraries: target_versions: javaagent: - Java 8+ + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG hystrix: - name: hystrix-1.4 source_path: instrumentation/hystrix-1.4 @@ -1094,6 +1268,24 @@ libraries: target_versions: javaagent: - Java 11+ + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG javalin: - name: javalin-5.0 source_path: instrumentation/javalin-5.0 @@ -1299,6 +1491,24 @@ libraries: - org.eclipse.jetty:jetty-client:[12,) library: - org.eclipse.jetty:jetty-client:12.0.0 + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: jetty-12.0 source_path: instrumentation/jetty/jetty-12.0 minimum_java_version: 17 @@ -1323,6 +1533,24 @@ libraries: - org.eclipse.jetty:jetty-client:[9.2,10) library: - org.eclipse.jetty:jetty-client:[9.2.0.v20140526,9.+) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: jetty-11.0 source_path: instrumentation/jetty/jetty-11.0 minimum_java_version: 11 @@ -1357,6 +1585,24 @@ libraries: target_versions: javaagent: - org.jodd:jodd-http:[4.2.0,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG jsf: - name: jsf-myfaces-3.0 source_path: instrumentation/jsf/jsf-myfaces-3.0 @@ -1648,6 +1894,37 @@ libraries: target_versions: javaagent: - io.netty:netty:[3.8.0.Final,4) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING - name: netty-4.0 source_path: instrumentation/netty/netty-4.0 scope: @@ -1656,16 +1933,78 @@ libraries: javaagent: - io.netty:netty-all:[4.0.0.Final,4.1.0.Final) - io.netty:netty-codec-http:[4.0.0.Final,4.1.0.Final) - - name: netty-4.1 - source_path: instrumentation/netty/netty-4.1 - scope: - name: io.opentelemetry.netty-4.1 - target_versions: - javaagent: - - io.netty:netty-codec-http:[4.1.0.Final,5.0.0) - - io.netty:netty-all:[4.1.0.Final,5.0.0) - library: + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING + - name: netty-4.1 + source_path: instrumentation/netty/netty-4.1 + scope: + name: io.opentelemetry.netty-4.1 + target_versions: + javaagent: + - io.netty:netty-codec-http:[4.1.0.Final,5.0.0) + - io.netty:netty-all:[4.1.0.Final,5.0.0) + library: - io.netty:netty-codec-http:4.1.0.Final + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING okhttp: - name: okhttp-3.0 source_path: instrumentation/okhttp/okhttp-3.0 @@ -1676,6 +2015,24 @@ libraries: - com.squareup.okhttp3:okhttp:[3.0,) library: - com.squareup.okhttp3:okhttp:3.0.0 + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: okhttp-2.2 source_path: instrumentation/okhttp/okhttp-2.2 scope: @@ -1683,6 +2040,24 @@ libraries: target_versions: javaagent: - com.squareup.okhttp:okhttp:[2.2,3) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG opensearch: - name: opensearch-rest-3.0 source_path: instrumentation/opensearch/opensearch-rest-3.0 @@ -1949,6 +2324,39 @@ libraries: - org.apache.pekko:pekko-http_3:[1.0,) - com.softwaremill.sttp.tapir:tapir-pekko-http-server_2.13:[1.7,) - org.apache.pekko:pekko-http_2.13:[1.0,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: http.route + type: STRING + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING play: - name: play-ws-1.0 source_path: instrumentation/play/play-ws/play-ws-1.0 @@ -1958,6 +2366,22 @@ libraries: javaagent: - com.typesafe.play:play-ahc-ws-standalone_2.12:[1.0.0,2.0.0) - com.typesafe.play:play-ahc-ws-standalone_2.11:[1.0.0,2.0.0) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: server.address + type: STRING + - name: server.port + type: LONG - name: play-mvc-2.6 source_path: instrumentation/play/play-mvc/play-mvc-2.6 scope: @@ -1983,6 +2407,22 @@ libraries: - com.typesafe.play:play-ahc-ws-standalone_2.12:[2.0.0,2.1.0) - com.typesafe.play:play-ahc-ws-standalone_2.13:[2.0.6,2.1.0) - com.typesafe.play:play-ahc-ws-standalone_2.11:[2.0.0,] + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: server.address + type: STRING + - name: server.port + type: LONG - name: play-ws-2.1 source_path: instrumentation/play/play-ws/play-ws-2.1 scope: @@ -1991,6 +2431,22 @@ libraries: javaagent: - com.typesafe.play:play-ahc-ws-standalone_2.13:[2.1.0,] - com.typesafe.play:play-ahc-ws-standalone_2.12:[2.1.0,] + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: server.address + type: STRING + - name: server.port + type: LONG powerjob: - name: powerjob-4.0 source_path: instrumentation/powerjob-4.0 @@ -2089,6 +2545,24 @@ libraries: target_versions: javaagent: - io.projectreactor.netty:reactor-netty:[0.8.2.RELEASE,1.0.0) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: reactor-netty-1.0 source_path: instrumentation/reactor/reactor-netty/reactor-netty-1.0 scope: @@ -2097,6 +2571,24 @@ libraries: javaagent: - io.projectreactor.netty:reactor-netty-http:[1.0.0,) - io.projectreactor.netty:reactor-netty:[1.0.0,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG rediscala: - name: rediscala-1.8 source_path: instrumentation/rediscala-1.8 @@ -2303,6 +2795,37 @@ libraries: - io.projectreactor.ipc:reactor-netty:[0.7.0.RELEASE,) - org.springframework:spring-webflux:[5.0.0.RELEASE,) - io.projectreactor.netty:reactor-netty:[0.8.0.RELEASE,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: http.route + type: STRING + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING - name: spring-webflux-5.3 source_path: instrumentation/spring/spring-webflux/spring-webflux-5.3 scope: @@ -2619,6 +3142,28 @@ libraries: target_versions: javaagent: - io.vertx:vertx-redis-client:[4.0.0,) + telemetry: + - when: otel.semconv-stability.opt-in=database + metrics: + - name: db.client.operation.duration + description: Duration of database client operations. + type: HISTOGRAM + unit: s + attributes: + - name: db.namespace + type: STRING + - name: db.operation.name + type: STRING + - name: db.system.name + type: STRING + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: server.address + type: STRING + - name: server.port + type: LONG - name: vertx-sql-client-5.0 source_path: instrumentation/vertx/vertx-sql-client/vertx-sql-client-5.0 minimum_java_version: 11 @@ -2627,6 +3172,24 @@ libraries: target_versions: javaagent: - io.vertx:vertx-sql-client:[5.0.0,) + telemetry: + - when: otel.semconv-stability.opt-in=database + metrics: + - name: db.client.operation.duration + description: Duration of database client operations. + type: HISTOGRAM + unit: s + attributes: + - name: db.collection.name + type: STRING + - name: db.namespace + type: STRING + - name: db.operation.name + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: vertx-http-client-5.0 source_path: instrumentation/vertx/vertx-http-client/vertx-http-client-5.0 minimum_java_version: 11 @@ -2635,6 +3198,24 @@ libraries: target_versions: javaagent: - io.vertx:vertx-core:[5.0.0,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: vertx-web-3.0 source_path: instrumentation/vertx/vertx-web-3.0 scope: @@ -2649,6 +3230,24 @@ libraries: target_versions: javaagent: - io.vertx:vertx-sql-client:[4.0.0,5) + telemetry: + - when: otel.semconv-stability.opt-in=database + metrics: + - name: db.client.operation.duration + description: Duration of database client operations. + type: HISTOGRAM + unit: s + attributes: + - name: db.collection.name + type: STRING + - name: db.namespace + type: STRING + - name: db.operation.name + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: vertx-http-client-4.0 source_path: instrumentation/vertx/vertx-http-client/vertx-http-client-4.0 scope: @@ -2656,6 +3255,24 @@ libraries: target_versions: javaagent: - io.vertx:vertx-core:[4.0.0,5) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - name: vertx-rx-java-3.5 source_path: instrumentation/vertx/vertx-rx-java-3.5 scope: @@ -2670,6 +3287,22 @@ libraries: target_versions: javaagent: - io.vertx:vertx-core:[3.0.0,4.0.0) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: server.address + type: STRING + - name: server.port + type: LONG vibur: - name: vibur-dbcp-11.0 description: Instrumentation for the vibur-dbcp library, which provides connection diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java index 074d14b1a61c..35e26ee3958a 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java @@ -7,12 +7,16 @@ import static io.opentelemetry.instrumentation.docs.parsers.GradleParser.parseGradleFile; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.exc.ValueInstantiationException; import io.opentelemetry.instrumentation.docs.internal.DependencyInfo; import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; +import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import io.opentelemetry.instrumentation.docs.parsers.MetricParser; +import io.opentelemetry.instrumentation.docs.parsers.SpanParser; import io.opentelemetry.instrumentation.docs.utils.FileManager; import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath; import io.opentelemetry.instrumentation.docs.utils.YamlHelper; @@ -92,17 +96,78 @@ List analyze() throws IOException { } } - Map metrics = - MetricParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath()); + processMetrics(module); + processSpans(module); + } + return modules; + } + + private void processMetrics(InstrumentationModule module) { + Map metrics = + MetricParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath()); + for (Map.Entry entry : metrics.entrySet()) { + if (entry.getValue() == null || entry.getValue().getMetrics() == null) { + continue; + } + module.getMetrics().put(entry.getKey(), entry.getValue().getMetrics()); + } + } + + private void processSpans(InstrumentationModule module) throws JsonProcessingException { + Map spans = + SpanParser.getSpansByScopeFromFiles(fileManager.rootDir(), module.getSrcPath()); + if (!spans.isEmpty()) { + Map> filtered = filterSpansByScope(spans, module); + module.setSpans(filtered); + } + } + + Map> filterSpansByScope( + Map spansByScope, InstrumentationModule module) { + + Map>> raw = new HashMap<>(); - for (Map.Entry entry : metrics.entrySet()) { - if (entry.getValue() == null || entry.getValue().getMetrics() == null) { - continue; + for (Map.Entry entry : spansByScope.entrySet()) { + if (entry.getValue() == null || entry.getValue().getSpansByScope() == null) { + continue; + } + + String when = entry.getValue().getWhen(); + Map> kind = raw.computeIfAbsent(when, m -> new HashMap<>()); + + for (EmittedSpans.SpansByScope theseSpans : entry.getValue().getSpansByScope()) { + + if (theseSpans.getScope().equals(module.getScopeInfo().getName())) { + for (EmittedSpans.Span span : theseSpans.getSpans()) { + String spanKind = span.getSpanKind(); + Set attributes = + kind.computeIfAbsent(spanKind, k -> new HashSet<>()); + + if (span.getAttributes() != null) { + for (TelemetryAttribute attr : span.getAttributes()) { + attributes.add(new TelemetryAttribute(attr.getName(), attr.getType())); + } + } + } } - module.getMetrics().put(entry.getKey(), entry.getValue().getMetrics()); } } - return modules; + + Map> newSpans = new HashMap<>(); + for (Map.Entry>> entry : raw.entrySet()) { + String when = entry.getKey(); + Map> attributesByKind = entry.getValue(); + + for (Map.Entry> kindEntry : attributesByKind.entrySet()) { + String spanKind = kindEntry.getKey(); + Set attributes = kindEntry.getValue(); + + List spans = newSpans.computeIfAbsent(when, k -> new ArrayList<>()); + spans.add(new EmittedSpans.Span(spanKind, new ArrayList<>(attributes))); + } + } + + return newSpans; } void analyzeVersions(List files, InstrumentationModule module) { diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java index c45e65e79c8f..8e1d055d36a1 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedMetrics.java @@ -56,7 +56,11 @@ public static class Metric { private List attributes; public Metric( - String name, String description, String type, String unit, List attributes) { + String name, + String description, + String type, + String unit, + List attributes) { this.name = name; this.description = description; this.type = type; diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedSpans.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedSpans.java index 0dbb310daf69..61e6087b164f 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedSpans.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/EmittedSpans.java @@ -5,22 +5,23 @@ package io.opentelemetry.instrumentation.docs.internal; +import static java.util.Collections.emptyList; + import com.fasterxml.jackson.annotation.JsonProperty; import java.util.ArrayList; import java.util.List; -import static java.util.Collections.emptyList; - /** - * Representation of spans emitted by an instrumentation. Includes context about whether emitted - * by default or via a configuration option. This class is internal and is hence not for public use. + * Representation of spans emitted by an instrumentation. Includes context about whether emitted by + * default or via a configuration option. This class is internal and is hence not for public use. * Its APIs are unstable and can change at any time. */ public class EmittedSpans { // Condition in which the telemetry is emitted (ex: default, or configuration option names). private String when; - @JsonProperty("spans_by_scope") private List spansByScope; + @JsonProperty("spans_by_scope") + private List spansByScope; public EmittedSpans() { this.when = ""; @@ -50,7 +51,6 @@ public void setSpansByScope(List spans) { this.spansByScope = spans; } - /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. @@ -91,11 +91,12 @@ public void setSpans(List spans) { * any time. */ public static class Span { - @JsonProperty("span_kind") private String spanKind; + @JsonProperty("span_kind") + private String spanKind; + private List attributes; - public Span( - String spanKind, List attributes) { + public Span(String spanKind, List attributes) { this.spanKind = spanKind; this.attributes = attributes; } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java index 437cecc52380..3910e291b890 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/InstrumentationModule.java @@ -29,6 +29,7 @@ public class InstrumentationModule { private final String group; private final InstrumentationScopeInfo scopeInfo; private Map> metrics; + private Map> spans; @Nullable private Map> targetVersions; @@ -47,6 +48,7 @@ public InstrumentationModule(Builder builder) { requireNonNull(builder.group, "group required"); this.metrics = Objects.requireNonNullElseGet(builder.metrics, HashMap::new); + this.spans = Objects.requireNonNullElseGet(builder.spans, HashMap::new); this.srcPath = builder.srcPath; this.instrumentationName = builder.instrumentationName; this.namespace = builder.namespace; @@ -99,6 +101,10 @@ public Map> getMetrics() { return metrics; } + public Map> getSpans() { + return spans; + } + public void setTargetVersions(Map> targetVersions) { this.targetVersions = targetVersions; } @@ -115,6 +121,10 @@ public void setMetrics(Map> metrics) { this.metrics = metrics; } + public void setSpans(Map> spans) { + this.spans = spans; + } + /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. @@ -128,6 +138,7 @@ public static class Builder { @Nullable private InstrumentationMetaData metadata; @Nullable private Map> targetVersions; @Nullable private Map> metrics; + @Nullable private Map> spans; @CanIgnoreReturnValue public Builder srcPath(String srcPath) { @@ -177,6 +188,12 @@ public Builder metrics(Map> metrics) { return this; } + @CanIgnoreReturnValue + public Builder spans(Map> spans) { + this.spans = spans; + return this; + } + public InstrumentationModule build() { return new InstrumentationModule(this); } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java index fb7beb1e2e59..8f1de8aa617b 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java @@ -1,3 +1,8 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.docs.internal; /** 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 01ca5f7978f2..af6766a8f67a 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 @@ -68,15 +68,16 @@ public static Map getSpansByScopeFromFiles( } /** - * Takes in a raw string representation of the aggregated EmittedSpan yaml map, separated by - * the `when`, indicating the conditions under which the telemetry is emitted. deduplicates by - * name and then returns a new map. + * Takes in a raw string representation of the aggregated EmittedSpan yaml map, separated by the + * `when`, indicating the conditions under which the telemetry is emitted. deduplicates by name + * and then returns a new map. * * @param input raw string representation of EmittedMetrics yaml * @return {@code Map} where the key is the `when` condition */ // visible for testing - public static Map parseSpans(Map input) throws JsonProcessingException { + public static Map parseSpans(Map input) + throws JsonProcessingException { Map result = new HashMap<>(); for (Map.Entry entry : input.entrySet()) { @@ -88,13 +89,15 @@ public static Map parseSpans(Map in continue; } - Map>> attributesByScopeAndSpanKind = new HashMap<>(); + Map>> attributesByScopeAndSpanKind = + new HashMap<>(); for (EmittedSpans.SpansByScope spansByScopeEntry : spans.getSpansByScope()) { String scope = spansByScopeEntry.getScope(); attributesByScopeAndSpanKind.putIfAbsent(scope, new HashMap<>()); - Map> attributesBySpanKind = attributesByScopeAndSpanKind.get(scope); + Map> attributesBySpanKind = + attributesByScopeAndSpanKind.get(scope); for (EmittedSpans.Span span : spansByScopeEntry.getSpans()) { String spanKind = span.getSpanKind(); @@ -129,7 +132,8 @@ private static EmittedSpans getEmittedSpans( Map>> attributesByScopeAndSpanKind, String when) { List deduplicatedSpansByScope = new ArrayList<>(); - for (Map.Entry>> scopeEntry : attributesByScopeAndSpanKind.entrySet()) { + for (Map.Entry>> scopeEntry : + attributesByScopeAndSpanKind.entrySet()) { String scope = scopeEntry.getKey(); EmittedSpans.SpansByScope deduplicatedScope = getSpansByScope(scopeEntry, scope); deduplicatedSpansByScope.add(deduplicatedScope); @@ -139,7 +143,8 @@ private static EmittedSpans getEmittedSpans( } /** - * Converts a map entry of scope and its attributes into an {@link EmittedSpans.SpansByScope} object. + * Converts a map entry of scope and its attributes into an {@link EmittedSpans.SpansByScope} + * object. * * @param scopeEntry the map entry containing scope and its attributes * @param scope the name of the scope diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java index c84e12f6e5a1..24158a99bfc9 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java @@ -15,6 +15,7 @@ import io.opentelemetry.instrumentation.docs.internal.InstrumentationClassification; import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import java.io.BufferedWriter; import java.util.ArrayList; import java.util.Collections; @@ -26,7 +27,6 @@ import java.util.TreeMap; import java.util.logging.Logger; import java.util.stream.Collectors; -import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; @@ -133,8 +133,9 @@ private static Map baseProperties(InstrumentationModule module) addTargetVersions(module, moduleMap); addConfigurations(module, moduleMap); - // Get telemetry grouping list - Set telemetryGroups = module.getMetrics().keySet(); + // Get telemetry grouping lists + Set telemetryGroups = new java.util.HashSet<>(module.getMetrics().keySet()); + telemetryGroups.addAll(module.getSpans().keySet()); if (!telemetryGroups.isEmpty()) { List> telemetryList = new ArrayList<>(); @@ -151,7 +152,24 @@ private static Map baseProperties(InstrumentationModule module) for (EmittedMetrics.Metric metric : metrics) { metricsList.add(getMetricsMap(metric)); } - telemetryEntry.put("metrics", metricsList); + if (!metricsList.isEmpty()) { + telemetryEntry.put("metrics", metricsList); + } + + List spans = + new ArrayList<>(module.getSpans().getOrDefault(group, Collections.emptyList())); + List> spanList = new ArrayList<>(); + + // sort metrics by name for some determinism in the order + spans.sort(Comparator.comparing(EmittedSpans.Span::getSpanKind)); + + for (EmittedSpans.Span span : spans) { + spanList.add(getSpanMap(span)); + } + if (!spanList.isEmpty()) { + telemetryEntry.put("spans", spanList); + } + telemetryList.add(telemetryEntry); } moduleMap.put("telemetry", telemetryList); @@ -241,6 +259,21 @@ private static Map getMetricsMap(EmittedMetrics.Metric metric) { return innerMetricMap; } + private static Map getSpanMap(EmittedSpans.Span span) { + Map innerMetricMap = new LinkedHashMap<>(); + innerMetricMap.put("span_kind", span.getSpanKind()); + + List> attributes = new ArrayList<>(); + for (TelemetryAttribute attribute : span.getAttributes()) { + Map attributeMap = new LinkedHashMap<>(); + attributeMap.put("name", attribute.getName()); + attributeMap.put("type", attribute.getType()); + attributes.add(attributeMap); + } + innerMetricMap.put("attributes", attributes); + return innerMetricMap; + } + public static InstrumentationMetaData metaDataParser(String input) throws JsonProcessingException { return mapper.readValue(input, InstrumentationMetaData.class); diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java index ee39e6c1d84c..411c77f53ee7 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java @@ -7,11 +7,14 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath; import java.util.Arrays; import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; @SuppressWarnings("NullAway") @@ -70,4 +73,45 @@ void testConvertToInstrumentationModule() { assertThat(springModule.getSrcPath()).isEqualTo("instrumentation/spring/spring-web"); assertThat(springModule.getScopeInfo().getName()).isEqualTo("io.opentelemetry.spring-web"); } + + @Test + void testFilterSpansByScopeFiltersCorrectly() { + InstrumentationAnalyzer analyzer = new InstrumentationAnalyzer(null); + String scopeName = "my-instrumentation-scope"; + EmittedSpans.Span span1 = + new EmittedSpans.Span("CLIENT", List.of(new TelemetryAttribute("my.operation", "STRING"))); + EmittedSpans.Span span2 = + new EmittedSpans.Span("SERVER", List.of(new TelemetryAttribute("my.operation", "STRING"))); + + EmittedSpans.Span testSpan = + new EmittedSpans.Span( + "INTERNAL", List.of(new TelemetryAttribute("my.operation", "STRING"))); + + EmittedSpans.SpansByScope spansByScope = + new EmittedSpans.SpansByScope(scopeName, List.of(span1, span2)); + EmittedSpans.SpansByScope spansByScope2 = + new EmittedSpans.SpansByScope("test", List.of(testSpan)); + Map spans = + Map.of( + scopeName, + new EmittedSpans("default", List.of(spansByScope)), + "test", + new EmittedSpans("default", List.of(spansByScope2))); + + InstrumentationModule module = + new InstrumentationModule.Builder() + .instrumentationName(scopeName) + .group("test-group") + .namespace("test") + .srcPath("test/") + .build(); + + analyzer.filterSpansByScope(spans, module); + + Map> filtered = module.getSpans(); + assertThat(filtered.size()).isEqualTo(1); + + // filters out the "test" scope + assertThat(filtered.get("default").size()).isEqualTo(2); + } } 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 a37869b098be..2c087b22a11e 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 @@ -1,18 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + package io.opentelemetry.instrumentation.docs.parsers; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mockStatic; + import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; import io.opentelemetry.instrumentation.docs.utils.FileManager; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.MockedStatic; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mockStatic; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.MockedStatic; @SuppressWarnings("NullAway") class SpanParserTest { @@ -21,7 +26,8 @@ class SpanParserTest { void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOException { Path telemetryDir = Files.createDirectories(tempDir.resolve(".telemetry")); - String file1Content = """ + String file1Content = + """ when: default spans_by_scope: - scope: test @@ -36,7 +42,8 @@ void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOExc type: STRING """; - String file2Content = """ + String file2Content = + """ when: default spans_by_scope: - scope: test @@ -66,28 +73,32 @@ void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOExc try (MockedStatic fileManagerMock = mockStatic(FileManager.class)) { fileManagerMock - .when( - () -> FileManager.readFileToString(telemetryDir.resolve("spans-1.yaml").toString())) + .when(() -> FileManager.readFileToString(telemetryDir.resolve("spans-1.yaml").toString())) .thenReturn(file1Content); fileManagerMock - .when( - () -> FileManager.readFileToString(telemetryDir.resolve("spans-2.yaml").toString())) + .when(() -> FileManager.readFileToString(telemetryDir.resolve("spans-2.yaml").toString())) .thenReturn(file2Content); - Map result = SpanParser.getSpansByScopeFromFiles(tempDir.toString(), ""); + Map result = + SpanParser.getSpansByScopeFromFiles(tempDir.toString(), ""); EmittedSpans spans = result.get("default"); assertThat(spans.getSpansByScope()).hasSize(2); List clickHouseSpans = - spans.getSpansByScope().stream().filter(item -> item.getScope().equals("io.opentelemetry.clickhouse-client-0.5")).map( - EmittedSpans.SpansByScope::getSpans).findFirst().orElse(null); + spans.getSpansByScope().stream() + .filter(item -> item.getScope().equals("io.opentelemetry.clickhouse-client-0.5")) + .map(EmittedSpans.SpansByScope::getSpans) + .findFirst() + .orElse(null); assertThat(clickHouseSpans).hasSize(2); - List testSpans = - spans.getSpansByScope().stream().filter(item -> item.getScope().equals("test")).map( - EmittedSpans.SpansByScope::getSpans).findFirst().orElse(null); + spans.getSpansByScope().stream() + .filter(item -> item.getScope().equals("test")) + .map(EmittedSpans.SpansByScope::getSpans) + .findFirst() + .orElse(null); // deduped should have only one span assertThat(testSpans).hasSize(1); } diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java index d09005e9bdab..384a7725b69e 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/utils/YamlHelperTest.java @@ -11,10 +11,12 @@ import io.opentelemetry.instrumentation.docs.internal.ConfigurationOption; import io.opentelemetry.instrumentation.docs.internal.ConfigurationType; import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; +import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; import io.opentelemetry.instrumentation.docs.internal.InstrumentationClassification; import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import java.io.BufferedWriter; import java.io.StringWriter; import java.util.ArrayList; @@ -23,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.Set; -import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import org.junit.jupiter.api.Test; class YamlHelperTest { @@ -355,4 +356,68 @@ void testMetricsParsing() throws Exception { assertThat(expectedYaml).isEqualTo(stringWriter.toString()); } + + @Test + void testSpanParsing() throws Exception { + List modules = new ArrayList<>(); + Map> targetVersions = new HashMap<>(); + + EmittedSpans.Span span = + new EmittedSpans.Span( + "CLIENT", + List.of( + new TelemetryAttribute("db.namespace", "STRING"), + new TelemetryAttribute("db.operation.name", "STRING"), + new TelemetryAttribute("db.system.name", "STRING"), + new TelemetryAttribute("server.address", "STRING"), + new TelemetryAttribute("server.port", "LONG"))); + + targetVersions.put( + InstrumentationType.LIBRARY, new HashSet<>(List.of("org.apache.mylib:mylib-core:2.3.0"))); + modules.add( + new InstrumentationModule.Builder() + .srcPath("instrumentation/mylib/mylib-core-2.3") + .instrumentationName("mylib-2.3") + .namespace("mylib") + .group("mylib") + .targetVersions(targetVersions) + .spans(Map.of("default", List.of(span))) + .build()); + + StringWriter stringWriter = new StringWriter(); + BufferedWriter writer = new BufferedWriter(stringWriter); + + YamlHelper.generateInstrumentationYaml(modules, writer); + writer.flush(); + + String expectedYaml = + """ + libraries: + mylib: + - name: mylib-2.3 + source_path: instrumentation/mylib/mylib-core-2.3 + scope: + name: io.opentelemetry.mylib-2.3 + target_versions: + library: + - org.apache.mylib:mylib-core:2.3.0 + telemetry: + - when: default + spans: + - span_kind: CLIENT + attributes: + - name: db.namespace + type: STRING + - name: db.operation.name + type: STRING + - name: db.system.name + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + """; + + assertThat(expectedYaml).isEqualTo(stringWriter.toString()); + } } diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java index f2cd0e7998fc..c03bf0d9621d 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java @@ -66,15 +66,15 @@ public void afterTestClass() throws IOException { // Generates files in a `.telemetry` directory within the instrumentation module with all // captured emitted metadata to be used by the instrumentation-docs Doc generator. -// if (Boolean.getBoolean("collectMetadata")) { - URL resource = this.getClass().getClassLoader().getResource(""); - if (resource == null) { - return; - } - String path = Paths.get(resource.getPath()).toString(); - - MetaDataCollector.writeTelemetryToFiles(path, metrics, tracesByScope); -// } + // if (Boolean.getBoolean("collectMetadata")) { + URL resource = this.getClass().getClassLoader().getResource(""); + if (resource == null) { + return; + } + String path = Paths.get(resource.getPath()).toString(); + + MetaDataCollector.writeTelemetryToFiles(path, metrics, tracesByScope); + // } // additional library ignores are ignored during tests, because they can make it really // confusing for contributors wondering why their instrumentation is not applied diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java index 7f38424a8469..bfe5b254706f 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java @@ -57,7 +57,9 @@ public abstract class InstrumentationTestRunner { * Stores traces by scope, where each scope contains a map of span kinds to a map of attribute * keys to their types. This is used to collect metadata about the spans emitted during tests. */ - protected Map, AttributeType>>> tracesByScope = new HashMap<>(); + protected Map< + InstrumentationScopeInfo, Map, AttributeType>>> + tracesByScope = new HashMap<>(); protected InstrumentationTestRunner(OpenTelemetry openTelemetry) { testInstrumenters = new TestInstrumenters(openTelemetry); @@ -151,9 +153,9 @@ private > void doAssertTraces( } TracesAssert.assertThat(traces).hasTracesSatisfyingExactly(assertionsList); -// if (Boolean.getBoolean("collectMetadata")) { - collectEmittedSpans(traces); -// } + // if (Boolean.getBoolean("collectMetadata")) { + collectEmittedSpans(traces); + // } } /** @@ -215,9 +217,11 @@ private void collectEmittedSpans(List> spans) { String spanName = span.getName(); System.out.println(spanName); Map, AttributeType>> scopeMap = - this.tracesByScope.computeIfAbsent(span.getInstrumentationScopeInfo(), m -> new HashMap<>()); + this.tracesByScope.computeIfAbsent( + span.getInstrumentationScopeInfo(), m -> new HashMap<>()); - Map, AttributeType> spanKindMap = scopeMap.computeIfAbsent(span.getKind(), s -> new HashMap<>()); + Map, AttributeType> spanKindMap = + scopeMap.computeIfAbsent(span.getKind(), s -> new HashMap<>()); for (Map.Entry, Object> key : span.getAttributes().asMap().entrySet()) { if (!(key.getKey() instanceof InternalAttributeKeyImpl)) { @@ -229,12 +233,10 @@ private void collectEmittedSpans(List> spans) { spanKindMap.put(keyImpl, key.getValue() != null ? key.getKey().getType() : null); } } - } } } - public final List waitForLogRecords(int numberOfLogRecords) { awaitUntilAsserted( () -> assertThat(getExportedLogRecords().size()).isEqualTo(numberOfLogRecords), diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java index cf117466a79e..c60f71c3007c 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/internal/MetaDataCollector.java @@ -40,7 +40,8 @@ public final class MetaDataCollector { public static void writeTelemetryToFiles( String path, Map metrics, - Map, AttributeType>>> spansByScopeAndKind) + Map, AttributeType>>> + spansByScopeAndKind) throws IOException { String moduleRoot = extractInstrumentationPath(path); @@ -70,7 +71,8 @@ private static String extractInstrumentationPath(String path) { private static void writeSpanData( String instrumentationPath, - Map, AttributeType>>> spansByScopeAndKind) + Map, AttributeType>>> + spansByScopeAndKind) throws IOException { if (spansByScopeAndKind.isEmpty()) { @@ -91,10 +93,13 @@ private static void writeSpanData( writer.write("spans_by_scope:\n"); - for (Map.Entry, AttributeType>>> entry : - spansByScopeAndKind.entrySet()) { + for (Map.Entry< + InstrumentationScopeInfo, + Map, AttributeType>>> + entry : spansByScopeAndKind.entrySet()) { InstrumentationScopeInfo scope = entry.getKey(); - Map, AttributeType>> spansByKind = entry.getValue(); + Map, AttributeType>> spansByKind = + entry.getValue(); writer.write(" - scope: " + scope.getName() + "\n"); writer.write(" spans:\n"); From deeead88451f587811c74ed46b60f5c8968b8729 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 18 Jun 2025 08:15:48 -0400 Subject: [PATCH 3/5] refactor instrumentation analyzer --- docs/instrumentation-list.yaml | 54 ++--- .../docs/InstrumentationAnalyzer.java | 196 +++++------------- ...cParser.java => EmittedMetricsParser.java} | 10 +- .../docs/parsers/EmittedSpanParser.java | 176 ++++++++++++++++ .../docs/parsers/GradleParser.java | 57 +++++ .../docs/parsers/ModuleParser.java | 56 +++++ .../docs/parsers/SpanParser.java | 195 +++++++---------- .../docs/utils/YamlHelper.java | 34 ++- .../docs/InstrumentationAnalyzerTest.java | 71 +++---- .../docs/ModuleConverterTest.java | 75 +++++++ .../docs/parsers/MetricParserTest.java | 10 +- .../docs/parsers/SpanParserTest.java | 43 +++- 12 files changed, 628 insertions(+), 349 deletions(-) rename instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/{MetricParser.java => EmittedMetricsParser.java} (91%) create mode 100644 instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/EmittedSpanParser.java create mode 100644 instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/ModuleParser.java create mode 100644 instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/ModuleConverterTest.java diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index 2f1ecaeb52b9..5674f528955d 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -709,18 +709,18 @@ libraries: spans: - span_kind: CLIENT attributes: - - name: db.operation + - name: db.name type: STRING - - name: server.address + - name: db.operation type: STRING - - name: server.port - type: LONG - - name: db.name + - name: db.statement type: STRING - name: db.system type: STRING - - name: db.statement + - name: server.address type: STRING + - name: server.port + type: LONG - when: otel.semconv-stability.opt-in=database metrics: - name: db.client.operation.duration @@ -741,22 +741,22 @@ libraries: spans: - span_kind: CLIENT attributes: - - name: db.system.name - type: STRING - name: db.namespace type: STRING - - name: error.type - type: STRING - name: db.operation.name type: STRING - - name: server.port - type: LONG - name: db.query.text type: STRING - name: db.response.status_code type: STRING + - name: db.system.name + type: STRING + - name: error.type + type: STRING - name: server.address type: STRING + - name: server.port + type: LONG couchbase: - name: couchbase-3.1.6 description: | @@ -1524,6 +1524,14 @@ libraries: target_versions: javaagent: - org.eclipse.jetty:jetty-server:[8.0.0.v20110901,11) + - name: jetty-11.0 + source_path: instrumentation/jetty/jetty-11.0 + minimum_java_version: 11 + scope: + name: io.opentelemetry.jetty-11.0 + target_versions: + javaagent: + - org.eclipse.jetty:jetty-server:[11, 12) - name: jetty-httpclient-9.2 source_path: instrumentation/jetty-httpclient/jetty-httpclient-9.2 scope: @@ -1551,14 +1559,6 @@ libraries: type: STRING - name: server.port type: LONG - - name: jetty-11.0 - source_path: instrumentation/jetty/jetty-11.0 - minimum_java_version: 11 - scope: - name: io.opentelemetry.jetty-11.0 - target_versions: - javaagent: - - org.eclipse.jetty:jetty-server:[11, 12) jms: - name: jms-3.0 source_path: instrumentation/jms/jms-3.0 @@ -2905,6 +2905,13 @@ libraries: source_path: instrumentation/spring/spring-webmvc/spring-webmvc-5.3 scope: name: io.opentelemetry.spring-webmvc-5.3 + - name: spring-cloud-gateway-2.0 + source_path: instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0 + scope: + name: io.opentelemetry.spring-cloud-gateway-2.0 + target_versions: + javaagent: + - org.springframework.cloud:spring-cloud-starter-gateway:[2.0.0.RELEASE,] - name: spring-core-2.0 source_path: instrumentation/spring/spring-core-2.0 minimum_java_version: 17 @@ -2913,13 +2920,6 @@ libraries: target_versions: javaagent: - org.springframework:spring-core:[2.0,] - - name: spring-cloud-gateway-2.0 - source_path: instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0 - scope: - name: io.opentelemetry.spring-cloud-gateway-2.0 - target_versions: - javaagent: - - org.springframework.cloud:spring-cloud-starter-gateway:[2.0.0.RELEASE,] - name: spring-security-config-6.0 source_path: instrumentation/spring/spring-security-config-6.0 minimum_java_version: 17 diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java index 35e26ee3958a..2e7bbc9fd5cc 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzer.java @@ -5,30 +5,31 @@ package io.opentelemetry.instrumentation.docs; -import static io.opentelemetry.instrumentation.docs.parsers.GradleParser.parseGradleFile; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.exc.ValueInstantiationException; -import io.opentelemetry.instrumentation.docs.internal.DependencyInfo; import io.opentelemetry.instrumentation.docs.internal.EmittedMetrics; -import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; +import io.opentelemetry.instrumentation.docs.internal.InstrumentationMetaData; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; -import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; -import io.opentelemetry.instrumentation.docs.parsers.MetricParser; +import io.opentelemetry.instrumentation.docs.parsers.EmittedMetricsParser; +import io.opentelemetry.instrumentation.docs.parsers.GradleParser; +import io.opentelemetry.instrumentation.docs.parsers.ModuleParser; import io.opentelemetry.instrumentation.docs.parsers.SpanParser; import io.opentelemetry.instrumentation.docs.utils.FileManager; import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath; import io.opentelemetry.instrumentation.docs.utils.YamlHelper; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; +import javax.annotation.Nullable; +/** + * Analyzes instrumentation modules by extracting version information, metrics, spans, and metadata + * from various source files. + */ class InstrumentationAnalyzer { private static final Logger logger = Logger.getLogger(InstrumentationAnalyzer.class.getName()); @@ -40,161 +41,74 @@ class InstrumentationAnalyzer { } /** - * Converts a list of {@link InstrumentationPath} into a list of {@link InstrumentationModule}, - * - * @param paths the list of {@link InstrumentationPath} objects to be converted - * @return a list of {@link InstrumentationModule} objects with aggregated types - */ - public static List convertToInstrumentationModules( - String rootPath, List paths) { - Map moduleMap = new HashMap<>(); - - for (InstrumentationPath path : paths) { - String key = path.group() + ":" + path.namespace() + ":" + path.instrumentationName(); - if (!moduleMap.containsKey(key)) { - moduleMap.put( - key, - new InstrumentationModule.Builder() - .srcPath(sanitizePathName(rootPath, path.srcPath())) - .instrumentationName(path.instrumentationName()) - .namespace(path.namespace()) - .group(path.group()) - .build()); - } - } - - return new ArrayList<>(moduleMap.values()); - } - - private static String sanitizePathName(String rootPath, String path) { - return path.replace(rootPath, "").replace("/javaagent", "").replace("/library", ""); - } - - /** - * Traverses the given root directory to find all instrumentation paths and then analyzes them. - * Extracts version information from each instrumentation's build.gradle file, metric data from - * files in the .telemetry directories, and other information from metadata.yaml files. + * Analyzes all instrumentation modules found in the root directory. * - * @return a list of {@link InstrumentationModule} + * @return a list of analyzed {@link InstrumentationModule} + * @throws IOException if file operations fail */ - List analyze() throws IOException { + public List analyze() throws IOException { List paths = fileManager.getInstrumentationPaths(); List modules = - convertToInstrumentationModules(fileManager.rootDir(), paths); + ModuleParser.convertToModules(fileManager.rootDir(), paths); for (InstrumentationModule module : modules) { - List gradleFiles = fileManager.findBuildGradleFiles(module.getSrcPath()); - analyzeVersions(gradleFiles, module); - - String metadataFile = fileManager.getMetaDataFile(module.getSrcPath()); - if (metadataFile != null) { - try { - module.setMetadata(YamlHelper.metaDataParser(metadataFile)); - } catch (ValueInstantiationException e) { - logger.severe("Error parsing metadata file for " + module.getInstrumentationName()); - throw e; - } - } - - processMetrics(module); - processSpans(module); + enrichModule(module); } + return modules; } - private void processMetrics(InstrumentationModule module) { - Map metrics = - MetricParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath()); - for (Map.Entry entry : metrics.entrySet()) { - if (entry.getValue() == null || entry.getValue().getMetrics() == null) { - continue; - } - module.getMetrics().put(entry.getKey(), entry.getValue().getMetrics()); + private void enrichModule(InstrumentationModule module) throws IOException { + InstrumentationMetaData metaData = getMetadata(module); + if (metaData != null) { + module.setMetadata(metaData); } + + module.setTargetVersions(getVersionInformation(module)); + module.setMetrics(MetricsProcessor.getMetrics(module, fileManager)); + module.setSpans(SpanParser.getSpans(module, fileManager)); } - private void processSpans(InstrumentationModule module) throws JsonProcessingException { - Map spans = - SpanParser.getSpansByScopeFromFiles(fileManager.rootDir(), module.getSrcPath()); - if (!spans.isEmpty()) { - Map> filtered = filterSpansByScope(spans, module); - module.setSpans(filtered); + @Nullable + private InstrumentationMetaData getMetadata(InstrumentationModule module) + throws JsonProcessingException { + String metadataFile = fileManager.getMetaDataFile(module.getSrcPath()); + if (metadataFile == null) { + return null; + } + try { + return YamlHelper.metaDataParser(metadataFile); + } catch (ValueInstantiationException e) { + logger.severe("Error parsing metadata file for " + module.getInstrumentationName()); + throw e; } } - Map> filterSpansByScope( - Map spansByScope, InstrumentationModule module) { - - Map>> raw = new HashMap<>(); - - for (Map.Entry entry : spansByScope.entrySet()) { - if (entry.getValue() == null || entry.getValue().getSpansByScope() == null) { - continue; - } - - String when = entry.getValue().getWhen(); - Map> kind = raw.computeIfAbsent(when, m -> new HashMap<>()); + private Map> getVersionInformation( + InstrumentationModule module) { + List gradleFiles = fileManager.findBuildGradleFiles(module.getSrcPath()); + return GradleParser.extractVersions(gradleFiles, module); + } - for (EmittedSpans.SpansByScope theseSpans : entry.getValue().getSpansByScope()) { + /** Handles processing of metrics data for instrumentation modules. */ + static class MetricsProcessor { - if (theseSpans.getScope().equals(module.getScopeInfo().getName())) { - for (EmittedSpans.Span span : theseSpans.getSpans()) { - String spanKind = span.getSpanKind(); - Set attributes = - kind.computeIfAbsent(spanKind, k -> new HashSet<>()); + public static Map> getMetrics( + InstrumentationModule module, FileManager fileManager) { + Map metrics = + EmittedMetricsParser.getMetricsFromFiles(fileManager.rootDir(), module.getSrcPath()); - if (span.getAttributes() != null) { - for (TelemetryAttribute attr : span.getAttributes()) { - attributes.add(new TelemetryAttribute(attr.getName(), attr.getType())); - } - } - } - } - } + Map> result = new HashMap<>(); + metrics.entrySet().stream() + .filter(MetricsProcessor::hasValidMetrics) + .forEach(entry -> result.put(entry.getKey(), entry.getValue().getMetrics())); + return result; } - Map> newSpans = new HashMap<>(); - for (Map.Entry>> entry : raw.entrySet()) { - String when = entry.getKey(); - Map> attributesByKind = entry.getValue(); - - for (Map.Entry> kindEntry : attributesByKind.entrySet()) { - String spanKind = kindEntry.getKey(); - Set attributes = kindEntry.getValue(); - - List spans = newSpans.computeIfAbsent(when, k -> new ArrayList<>()); - spans.add(new EmittedSpans.Span(spanKind, new ArrayList<>(attributes))); - } + private static boolean hasValidMetrics(Map.Entry entry) { + return entry.getValue() != null && entry.getValue().getMetrics() != null; } - return newSpans; - } - - void analyzeVersions(List files, InstrumentationModule module) { - Map> versions = new HashMap<>(); - for (String file : files) { - String fileContents = FileManager.readFileToString(file); - if (fileContents == null) { - continue; - } - - DependencyInfo results = null; - - if (file.contains("/javaagent/")) { - results = parseGradleFile(fileContents, InstrumentationType.JAVAAGENT); - versions - .computeIfAbsent(InstrumentationType.JAVAAGENT, k -> new HashSet<>()) - .addAll(results.versions()); - } else if (file.contains("/library/")) { - results = parseGradleFile(fileContents, InstrumentationType.LIBRARY); - versions - .computeIfAbsent(InstrumentationType.LIBRARY, k -> new HashSet<>()) - .addAll(results.versions()); - } - if (results != null && results.minJavaVersionSupported() != null) { - module.setMinJavaVersion(results.minJavaVersionSupported()); - } - } - module.setTargetVersions(versions); + private MetricsProcessor() {} } } 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/EmittedMetricsParser.java similarity index 91% rename from instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/MetricParser.java rename to instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/EmittedMetricsParser.java index c32efcc187de..6374f8954039 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/EmittedMetricsParser.java @@ -19,8 +19,12 @@ import java.util.logging.Logger; import java.util.stream.Stream; -public class MetricParser { - private static final Logger logger = Logger.getLogger(MetricParser.class.getName()); +/** + * This class is responsible for parsing metric-* files from the `.telemetry` directory of an + * instrumentation module and converting them into the {@link EmittedMetrics} format. + */ +public class EmittedMetricsParser { + private static final Logger logger = Logger.getLogger(EmittedMetricsParser.class.getName()); /** * Looks for metric files in the .telemetry directory, and combines them into a map where the key @@ -95,5 +99,5 @@ public static Map parseMetrics(Map getSpansByScopeFromFiles( + String rootDir, String instrumentationDirectory) throws JsonProcessingException { + Map spansByScope = new HashMap<>(); + Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry"); + + if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) { + try (Stream files = Files.list(telemetryDir)) { + files + .filter(path -> path.getFileName().toString().startsWith("spans-")) + .forEach( + path -> { + String content = FileManager.readFileToString(path.toString()); + if (content != null) { + String when = content.substring(0, content.indexOf('\n')); + String whenKey = when.replace("when: ", ""); + + spansByScope.putIfAbsent(whenKey, new StringBuilder("spans_by_scope:\n")); + + // Skip the metric spans_by_scope ("metrics:") so we can aggregate into one list + int metricsIndex = content.indexOf("spans_by_scope:\n"); + if (metricsIndex != -1) { + String contentAfterMetrics = + content.substring(metricsIndex + "spans_by_scope:\n".length()); + spansByScope.get(whenKey).append(contentAfterMetrics); + } + } + }); + } catch (IOException e) { + logger.severe("Error reading span files: " + e.getMessage()); + } + } + + return parseSpans(spansByScope); + } + + /** + * Takes in a raw string representation of the aggregated EmittedSpan yaml map, separated by the + * `when`, indicating the conditions under which the telemetry is emitted. deduplicates by name + * and then returns a new map. + * + * @param input raw string representation of EmittedMetrics yaml + * @return {@code Map} where the key is the `when` condition + */ + // visible for testing + public static Map parseSpans(Map input) + throws JsonProcessingException { + Map result = new HashMap<>(); + + for (Map.Entry entry : input.entrySet()) { + String when = entry.getKey().strip(); + StringBuilder content = entry.getValue(); + + EmittedSpans spans = YamlHelper.emittedSpansParser(content.toString()); + if (spans.getSpansByScope().isEmpty()) { + continue; + } + + Map>> attributesByScopeAndSpanKind = + new HashMap<>(); + + for (EmittedSpans.SpansByScope spansByScopeEntry : spans.getSpansByScope()) { + String scope = spansByScopeEntry.getScope(); + + attributesByScopeAndSpanKind.putIfAbsent(scope, new HashMap<>()); + Map> attributesBySpanKind = + attributesByScopeAndSpanKind.get(scope); + + for (EmittedSpans.Span span : spansByScopeEntry.getSpans()) { + String spanKind = span.getSpanKind(); + + attributesBySpanKind.putIfAbsent(spanKind, new HashSet<>()); + Set attributeSet = attributesBySpanKind.get(spanKind); + + if (span.getAttributes() != null) { + for (TelemetryAttribute attr : span.getAttributes()) { + attributeSet.add(new TelemetryAttribute(attr.getName(), attr.getType())); + } + } + } + } + + EmittedSpans deduplicatedEmittedSpans = getEmittedSpans(attributesByScopeAndSpanKind, when); + result.put(when, deduplicatedEmittedSpans); + } + + return result; + } + + /** + * Takes in a map of attributes by scope and span kind, and returns an {@link EmittedSpans} object + * with deduplicated spans. + * + * @param attributesByScopeAndSpanKind the map of attributes by scope and span kind + * @param when the condition under which the telemetry is emitted + * @return an {@link EmittedSpans} object with deduplicated spans + */ + private static EmittedSpans getEmittedSpans( + Map>> attributesByScopeAndSpanKind, String when) { + List deduplicatedSpansByScope = new ArrayList<>(); + + for (Map.Entry>> scopeEntry : + attributesByScopeAndSpanKind.entrySet()) { + String scope = scopeEntry.getKey(); + EmittedSpans.SpansByScope deduplicatedScope = getSpansByScope(scopeEntry, scope); + deduplicatedSpansByScope.add(deduplicatedScope); + } + + return new EmittedSpans(when, deduplicatedSpansByScope); + } + + /** + * Converts a map entry of scope and its attributes into an {@link EmittedSpans.SpansByScope} + * object. + * + * @param scopeEntry the map entry containing scope and its attributes + * @param scope the name of the scope + * @return an {@link EmittedSpans.SpansByScope} object with deduplicated spans + */ + private static EmittedSpans.SpansByScope getSpansByScope( + Map.Entry>> scopeEntry, String scope) { + Map> spanKindMap = scopeEntry.getValue(); + + List deduplicatedSpans = new ArrayList<>(); + + for (Map.Entry> spanKindEntry : spanKindMap.entrySet()) { + String spanKind = spanKindEntry.getKey(); + Set attributes = spanKindEntry.getValue(); + + List attributeList = new ArrayList<>(attributes); + EmittedSpans.Span deduplicatedSpan = new EmittedSpans.Span(spanKind, attributeList); + deduplicatedSpans.add(deduplicatedSpan); + } + + return new EmittedSpans.SpansByScope(scope, deduplicatedSpans); + } + + private EmittedSpanParser() {} +} diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/GradleParser.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/GradleParser.java index 4fe246a4aaba..278463c8cdd9 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/GradleParser.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/GradleParser.java @@ -6,17 +6,21 @@ package io.opentelemetry.instrumentation.docs.parsers; import io.opentelemetry.instrumentation.docs.internal.DependencyInfo; +import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; +import io.opentelemetry.instrumentation.docs.utils.FileManager; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nullable; +/** Handles parsing of Gradle build files to extract muzzle and dependency information. */ public class GradleParser { private static final Pattern variablePattern = @@ -241,5 +245,58 @@ private static String extractValue(String text, String regex) { return null; } + public static Map> extractVersions( + List gradleFiles, InstrumentationModule module) { + Map> versionsByType = new HashMap<>(); + gradleFiles.forEach(file -> processGradleFile(file, versionsByType, module)); + return versionsByType; + } + + private static void processGradleFile( + String filePath, + Map> versionsByType, + InstrumentationModule module) { + String fileContents = FileManager.readFileToString(filePath); + if (fileContents == null) { + return; + } + + Optional type = determineInstrumentationType(filePath); + if (type.isEmpty()) { + return; + } + + DependencyInfo dependencyInfo = parseGradleFile(fileContents, type.get()); + if (dependencyInfo == null) { + return; + } + + addVersions(versionsByType, type.get(), dependencyInfo.versions()); + setMinJavaVersionIfPresent(module, dependencyInfo); + } + + private static Optional determineInstrumentationType(String filePath) { + if (filePath.contains("/javaagent/")) { + return Optional.of(InstrumentationType.JAVAAGENT); + } else if (filePath.contains("/library/")) { + return Optional.of(InstrumentationType.LIBRARY); + } + return Optional.empty(); + } + + private static void addVersions( + Map> versionsByType, + InstrumentationType type, + Set versions) { + versionsByType.computeIfAbsent(type, k -> new HashSet<>()).addAll(versions); + } + + private static void setMinJavaVersionIfPresent( + InstrumentationModule module, DependencyInfo dependencyInfo) { + if (dependencyInfo.minJavaVersionSupported() != null) { + module.setMinJavaVersion(dependencyInfo.minJavaVersionSupported()); + } + } + private GradleParser() {} } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/ModuleParser.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/ModuleParser.java new file mode 100644 index 000000000000..ce4298d6ef95 --- /dev/null +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/ModuleParser.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.docs.parsers; + +import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; +import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Handles conversion of InstrumentationPath objects to InstrumentationModule objects. */ +public class ModuleParser { + + /** + * Converts a list of {@link InstrumentationPath} into a list of {@link InstrumentationModule}. + * + * @param rootPath the root path for sanitization + * @param paths the list of {@link InstrumentationPath} objects to be converted + * @return a list of {@link InstrumentationModule} objects + */ + public static List convertToModules( + String rootPath, List paths) { + Map moduleMap = new HashMap<>(); + + for (InstrumentationPath path : paths) { + String moduleKey = createModuleKey(path); + moduleMap.computeIfAbsent(moduleKey, k -> createModule(rootPath, path)); + } + + return new ArrayList<>(moduleMap.values()); + } + + private static String createModuleKey(InstrumentationPath path) { + return String.join(":", path.group(), path.namespace(), path.instrumentationName()); + } + + private static InstrumentationModule createModule(String rootPath, InstrumentationPath path) { + return new InstrumentationModule.Builder() + .srcPath(sanitizePathName(rootPath, path.srcPath())) + .instrumentationName(path.instrumentationName()) + .namespace(path.namespace()) + .group(path.group()) + .build(); + } + + // visible for testing + public static String sanitizePathName(String rootPath, String path) { + return path.replace(rootPath, "").replace("/javaagent", "").replace("/library", ""); + } + + private ModuleParser() {} +} 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 af6766a8f67a..ccb24b45911b 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 @@ -7,165 +7,130 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; +import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import io.opentelemetry.instrumentation.docs.utils.FileManager; -import io.opentelemetry.instrumentation.docs.utils.YamlHelper; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.logging.Logger; -import java.util.stream.Stream; +/** + * This class is responsible for parsing span files from the `.telemetry` directory of an + * instrumentation module and filtering them by scope. + */ public class SpanParser { - private static final Logger logger = Logger.getLogger(SpanParser.class.getName()); /** - * Looks for span files in the .telemetry directory, and combines them into a single map. + * Pull spans from the `.telemetry` directory, filter them by scope, and set them in the module. * - * @param instrumentationDirectory the directory to traverse - * @return contents of aggregated files + * @param module the instrumentation module to extract spans for + * @param fileManager the file manager to access the filesystem + * @throws JsonProcessingException if there is an error processing the JSON from the span files */ - public static Map getSpansByScopeFromFiles( - String rootDir, String instrumentationDirectory) throws JsonProcessingException { - Map spansByScope = new HashMap<>(); - Path telemetryDir = Paths.get(rootDir + "/" + instrumentationDirectory, ".telemetry"); - - if (Files.exists(telemetryDir) && Files.isDirectory(telemetryDir)) { - try (Stream files = Files.list(telemetryDir)) { - files - .filter(path -> path.getFileName().toString().startsWith("spans-")) - .forEach( - path -> { - String content = FileManager.readFileToString(path.toString()); - if (content != null) { - String when = content.substring(0, content.indexOf('\n')); - String whenKey = when.replace("when: ", ""); - - spansByScope.putIfAbsent(whenKey, new StringBuilder("spans_by_scope:\n")); - - // Skip the metric spans_by_scope ("metrics:") so we can aggregate into one list - int metricsIndex = content.indexOf("spans_by_scope:\n"); - if (metricsIndex != -1) { - String contentAfterMetrics = - content.substring(metricsIndex + "spans_by_scope:\n".length()); - spansByScope.get(whenKey).append(contentAfterMetrics); - } - } - }); - } catch (IOException e) { - logger.severe("Error reading span files: " + e.getMessage()); - } + public static Map> getSpans( + InstrumentationModule module, FileManager fileManager) throws JsonProcessingException { + Map spans = + EmittedSpanParser.getSpansByScopeFromFiles(fileManager.rootDir(), module.getSrcPath()); + + if (spans.isEmpty()) { + return new HashMap<>(); } - return parseSpans(spansByScope); + String scopeName = module.getScopeInfo().getName(); + return filterSpansByScope(spans, scopeName); } /** - * Takes in a raw string representation of the aggregated EmittedSpan yaml map, separated by the - * `when`, indicating the conditions under which the telemetry is emitted. deduplicates by name - * and then returns a new map. + * Filters spans by scope and aggregates attributes for each span kind. * - * @param input raw string representation of EmittedMetrics yaml - * @return {@code Map} where the key is the `when` condition + * @param spansByScope the map of spans by scope + * @param scopeName the name of the scope to filter spans for + * @return a map of filtered spans by `when` */ - // visible for testing - public static Map parseSpans(Map input) - throws JsonProcessingException { - Map result = new HashMap<>(); + private static Map> filterSpansByScope( + Map spansByScope, String scopeName) { - for (Map.Entry entry : input.entrySet()) { - String when = entry.getKey().strip(); - StringBuilder content = entry.getValue(); + Map>> aggregatedAttributes = new HashMap<>(); - EmittedSpans spans = YamlHelper.emittedSpansParser(content.toString()); - if (spans.getSpansByScope().isEmpty()) { + for (Map.Entry entry : spansByScope.entrySet()) { + if (!hasValidSpans(entry.getValue())) { continue; } - Map>> attributesByScopeAndSpanKind = - new HashMap<>(); + String when = entry.getValue().getWhen(); + Map>> result = + SpanAggregator.aggregateSpans(when, entry.getValue(), scopeName); + aggregatedAttributes.putAll(result); + } - for (EmittedSpans.SpansByScope spansByScopeEntry : spans.getSpansByScope()) { - String scope = spansByScopeEntry.getScope(); + return SpanAggregator.buildFilteredSpans(aggregatedAttributes); + } - attributesByScopeAndSpanKind.putIfAbsent(scope, new HashMap<>()); - Map> attributesBySpanKind = - attributesByScopeAndSpanKind.get(scope); + private static boolean hasValidSpans(EmittedSpans spans) { + return spans != null && spans.getSpansByScope() != null; + } - for (EmittedSpans.Span span : spansByScopeEntry.getSpans()) { - String spanKind = span.getSpanKind(); + /** Helper class to aggregate span attributes by scope and kind. */ + static class SpanAggregator { - attributesBySpanKind.putIfAbsent(spanKind, new HashSet<>()); - Set attributeSet = attributesBySpanKind.get(spanKind); + public static Map>> aggregateSpans( + String when, EmittedSpans spans, String targetScopeName) { + Map>> aggregatedAttributes = new HashMap<>(); + Map> spanKindMap = + aggregatedAttributes.computeIfAbsent(when, k -> new HashMap<>()); - if (span.getAttributes() != null) { - for (TelemetryAttribute attr : span.getAttributes()) { - attributeSet.add(new TelemetryAttribute(attr.getName(), attr.getType())); - } - } + for (EmittedSpans.SpansByScope spansByScope : spans.getSpansByScope()) { + if (spansByScope.getScope().equals(targetScopeName)) { + processSpansForScope(spansByScope, spanKindMap); } } - - EmittedSpans deduplicatedEmittedSpans = getEmittedSpans(attributesByScopeAndSpanKind, when); - result.put(when, deduplicatedEmittedSpans); + return aggregatedAttributes; } - return result; - } + private static void processSpansForScope( + EmittedSpans.SpansByScope spansByScope, Map> spanKindMap) { + for (EmittedSpans.Span span : spansByScope.getSpans()) { + Set attributes = + spanKindMap.computeIfAbsent(span.getSpanKind(), k -> new HashSet<>()); - /** - * Takes in a map of attributes by scope and span kind, and returns an {@link EmittedSpans} object - * with deduplicated spans. - * - * @param attributesByScopeAndSpanKind the map of attributes by scope and span kind - * @param when the condition under which the telemetry is emitted - * @return an {@link EmittedSpans} object with deduplicated spans - */ - private static EmittedSpans getEmittedSpans( - Map>> attributesByScopeAndSpanKind, String when) { - List deduplicatedSpansByScope = new ArrayList<>(); - - for (Map.Entry>> scopeEntry : - attributesByScopeAndSpanKind.entrySet()) { - String scope = scopeEntry.getKey(); - EmittedSpans.SpansByScope deduplicatedScope = getSpansByScope(scopeEntry, scope); - deduplicatedSpansByScope.add(deduplicatedScope); + addSpanAttributes(span, attributes); + } } - return new EmittedSpans(when, deduplicatedSpansByScope); - } + private static void addSpanAttributes( + EmittedSpans.Span span, Set attributes) { + if (span.getAttributes() == null) { + return; + } - /** - * Converts a map entry of scope and its attributes into an {@link EmittedSpans.SpansByScope} - * object. - * - * @param scopeEntry the map entry containing scope and its attributes - * @param scope the name of the scope - * @return an {@link EmittedSpans.SpansByScope} object with deduplicated spans - */ - private static EmittedSpans.SpansByScope getSpansByScope( - Map.Entry>> scopeEntry, String scope) { - Map> spanKindMap = scopeEntry.getValue(); + for (TelemetryAttribute attr : span.getAttributes()) { + attributes.add(new TelemetryAttribute(attr.getName(), attr.getType())); + } + } + + public static Map> buildFilteredSpans( + Map>> aggregatedAttributes) { + Map> result = new HashMap<>(); - List deduplicatedSpans = new ArrayList<>(); + for (Map.Entry>> entry : + aggregatedAttributes.entrySet()) { + String when = entry.getKey(); + List spans = result.computeIfAbsent(when, k -> new ArrayList<>()); - for (Map.Entry> spanKindEntry : spanKindMap.entrySet()) { - String spanKind = spanKindEntry.getKey(); - Set attributes = spanKindEntry.getValue(); + for (Map.Entry> kindEntry : entry.getValue().entrySet()) { + String spanKind = kindEntry.getKey(); + Set attributes = kindEntry.getValue(); + spans.add(new EmittedSpans.Span(spanKind, new ArrayList<>(attributes))); + } + } - List attributeList = new ArrayList<>(attributes); - EmittedSpans.Span deduplicatedSpan = new EmittedSpans.Span(spanKind, attributeList); - deduplicatedSpans.add(deduplicatedSpan); + return result; } - return new EmittedSpans.SpansByScope(scope, deduplicatedSpans); + private SpanAggregator() {} } private SpanParser() {} diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java index 24158a99bfc9..f2d5bce3b1cb 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java @@ -241,36 +241,34 @@ private static Map configurationToMap(ConfigurationOption config return conf; } + private static List> getSortedAttributeMaps( + List attributes) { + List sortedAttributes = new ArrayList<>(attributes); + sortedAttributes.sort(Comparator.comparing(TelemetryAttribute::getName)); + List> attributeMaps = new ArrayList<>(); + for (TelemetryAttribute attribute : sortedAttributes) { + Map attributeMap = new LinkedHashMap<>(); + attributeMap.put("name", attribute.getName()); + attributeMap.put("type", attribute.getType()); + attributeMaps.add(attributeMap); + } + return attributeMaps; + } + private static Map getMetricsMap(EmittedMetrics.Metric metric) { Map innerMetricMap = new LinkedHashMap<>(); innerMetricMap.put("name", metric.getName()); innerMetricMap.put("description", metric.getDescription()); innerMetricMap.put("type", metric.getType()); innerMetricMap.put("unit", metric.getUnit()); - - List> attributes = new ArrayList<>(); - for (TelemetryAttribute attribute : metric.getAttributes()) { - Map attributeMap = new LinkedHashMap<>(); - attributeMap.put("name", attribute.getName()); - attributeMap.put("type", attribute.getType()); - attributes.add(attributeMap); - } - innerMetricMap.put("attributes", attributes); + innerMetricMap.put("attributes", getSortedAttributeMaps(metric.getAttributes())); return innerMetricMap; } private static Map getSpanMap(EmittedSpans.Span span) { Map innerMetricMap = new LinkedHashMap<>(); innerMetricMap.put("span_kind", span.getSpanKind()); - - List> attributes = new ArrayList<>(); - for (TelemetryAttribute attribute : span.getAttributes()) { - Map attributeMap = new LinkedHashMap<>(); - attributeMap.put("name", attribute.getName()); - attributeMap.put("type", attribute.getType()); - attributes.add(attributeMap); - } - innerMetricMap.put("attributes", attributes); + innerMetricMap.put("attributes", getSortedAttributeMaps(span.getAttributes())); return innerMetricMap; } diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java index 411c77f53ee7..37dfebfe36e5 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java @@ -7,14 +7,12 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; import io.opentelemetry.instrumentation.docs.internal.InstrumentationType; -import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; +import io.opentelemetry.instrumentation.docs.parsers.ModuleParser; import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath; import java.util.Arrays; import java.util.List; -import java.util.Map; import org.junit.jupiter.api.Test; @SuppressWarnings("NullAway") @@ -43,8 +41,7 @@ void testConvertToInstrumentationModule() { "spring", InstrumentationType.LIBRARY)); - List modules = - InstrumentationAnalyzer.convertToInstrumentationModules("test", paths); + List modules = ModuleParser.convertToModules("test", paths); assertThat(modules.size()).isEqualTo(2); @@ -75,43 +72,37 @@ void testConvertToInstrumentationModule() { } @Test - void testFilterSpansByScopeFiltersCorrectly() { - InstrumentationAnalyzer analyzer = new InstrumentationAnalyzer(null); - String scopeName = "my-instrumentation-scope"; - EmittedSpans.Span span1 = - new EmittedSpans.Span("CLIENT", List.of(new TelemetryAttribute("my.operation", "STRING"))); - EmittedSpans.Span span2 = - new EmittedSpans.Span("SERVER", List.of(new TelemetryAttribute("my.operation", "STRING"))); - - EmittedSpans.Span testSpan = - new EmittedSpans.Span( - "INTERNAL", List.of(new TelemetryAttribute("my.operation", "STRING"))); - - EmittedSpans.SpansByScope spansByScope = - new EmittedSpans.SpansByScope(scopeName, List.of(span1, span2)); - EmittedSpans.SpansByScope spansByScope2 = - new EmittedSpans.SpansByScope("test", List.of(testSpan)); - Map spans = - Map.of( - scopeName, - new EmittedSpans("default", List.of(spansByScope)), - "test", - new EmittedSpans("default", List.of(spansByScope2))); - - InstrumentationModule module = - new InstrumentationModule.Builder() - .instrumentationName(scopeName) - .group("test-group") - .namespace("test") - .srcPath("test/") - .build(); + void testModuleConverterCreatesUniqueModules() { + List paths = + Arrays.asList( + new InstrumentationPath( + "same-name", + "instrumentation/test1/same-name/library", + "group1", + "namespace1", + InstrumentationType.LIBRARY), + new InstrumentationPath( + "same-name", + "instrumentation/test2/same-name/library", + "group2", + "namespace2", + InstrumentationType.LIBRARY)); - analyzer.filterSpansByScope(spans, module); + List modules = ModuleParser.convertToModules("test", paths); - Map> filtered = module.getSpans(); - assertThat(filtered.size()).isEqualTo(1); + // Should create 2 separate modules because they have different group/namespace combinations + assertThat(modules.size()).isEqualTo(2); - // filters out the "test" scope - assertThat(filtered.get("default").size()).isEqualTo(2); + // Verify both modules exist with correct properties + assertThat( + modules.stream() + .anyMatch( + m -> m.getGroup().equals("group1") && m.getNamespace().equals("namespace1"))) + .isTrue(); + assertThat( + modules.stream() + .anyMatch( + m -> m.getGroup().equals("group2") && m.getNamespace().equals("namespace2"))) + .isTrue(); } } diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/ModuleConverterTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/ModuleConverterTest.java new file mode 100644 index 000000000000..197293e047c5 --- /dev/null +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/ModuleConverterTest.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.docs; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.opentelemetry.instrumentation.docs.internal.InstrumentationModule; +import io.opentelemetry.instrumentation.docs.parsers.ModuleParser; +import io.opentelemetry.instrumentation.docs.utils.InstrumentationPath; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; + +class ModuleConverterTest { + + @Test + void testConvertToModulesCreatesUniqueModules() { + InstrumentationPath path1 = mock(InstrumentationPath.class); + when(path1.group()).thenReturn("g1"); + when(path1.namespace()).thenReturn("n1"); + when(path1.instrumentationName()).thenReturn("i1"); + when(path1.srcPath()).thenReturn("/root/javaagent/foo"); + + InstrumentationPath path2 = mock(InstrumentationPath.class); + when(path2.group()).thenReturn("g1"); + when(path2.namespace()).thenReturn("n1"); + when(path2.instrumentationName()).thenReturn("i1"); + when(path2.srcPath()).thenReturn("/root/library/bar"); + + InstrumentationPath path3 = mock(InstrumentationPath.class); + when(path3.group()).thenReturn("g2"); + when(path3.namespace()).thenReturn("n2"); + when(path3.instrumentationName()).thenReturn("i2"); + when(path3.srcPath()).thenReturn("/root/javaagent/baz"); + + List modules = + ModuleParser.convertToModules("/root", Arrays.asList(path1, path2, path3)); + + assertThat(modules.size()).isEqualTo(2); + assertThat(modules) + .extracting(InstrumentationModule::getGroup) + .containsExactlyInAnyOrder("g1", "g2"); + } + + @Test + void testSanitizePathNameRemovesRootAndKnownFolders() throws Exception { + String sanitized = ModuleParser.sanitizePathName("/root", "/root/javaagent/foo/bar"); + assertThat(sanitized).isEqualTo("/foo/bar"); + + sanitized = ModuleParser.sanitizePathName("/root", "/root/library/baz"); + assertThat(sanitized).isEqualTo("/baz"); + + sanitized = ModuleParser.sanitizePathName("/root", "/root/other"); + assertThat(sanitized).isEqualTo("/other"); + } + + @Test + void testCreateModuleKeyIsConsistent() throws Exception { + InstrumentationPath path = mock(InstrumentationPath.class); + when(path.group()).thenReturn("g"); + when(path.namespace()).thenReturn("n"); + when(path.instrumentationName()).thenReturn("i"); + + var method = ModuleParser.class.getDeclaredMethod("createModuleKey", InstrumentationPath.class); + method.setAccessible(true); + + String key = (String) method.invoke(null, path); + assertThat(key).isEqualTo("g:n:i"); + } +} 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 da068a8f5ca8..088747876cb4 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 @@ -39,7 +39,7 @@ void parseMetricsDeduplicatesMetricsByName() { Map metricMap = new HashMap<>(); metricMap.put("default", new StringBuilder(input)); - Map result = MetricParser.parseMetrics(metricMap); + Map result = EmittedMetricsParser.parseMetrics(metricMap); List metricNames = result.get("default").getMetrics().stream() .map(EmittedMetrics.Metric::getName) @@ -56,7 +56,7 @@ void parseMetricsHandlesEmptyInput() { Map metricMap = new HashMap<>(); metricMap.put("default", new StringBuilder(input)); - Map result = MetricParser.parseMetrics(metricMap); + Map result = EmittedMetricsParser.parseMetrics(metricMap); assertThat(result).isEmpty(); } @@ -83,7 +83,8 @@ void getMetricsFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOE () -> FileManager.readFileToString(telemetryDir.resolve("metrics-2.yaml").toString())) .thenReturn(file2Content); - Map result = MetricParser.getMetricsFromFiles(tempDir.toString(), ""); + Map result = + EmittedMetricsParser.getMetricsFromFiles(tempDir.toString(), ""); EmittedMetrics metrics = result.get("default"); @@ -96,7 +97,8 @@ void getMetricsFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOE @Test void getMetricsFromFilesHandlesNonexistentDirectory() { - Map result = MetricParser.getMetricsFromFiles("/nonexistent", "path"); + Map result = + EmittedMetricsParser.getMetricsFromFiles("/nonexistent", "path"); assertThat(result).isEmpty(); } } 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 2c087b22a11e..0a25ddf7b753 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 @@ -9,12 +9,14 @@ import static org.mockito.Mockito.mockStatic; import io.opentelemetry.instrumentation.docs.internal.EmittedSpans; +import io.opentelemetry.instrumentation.docs.internal.TelemetryAttribute; import io.opentelemetry.instrumentation.docs.utils.FileManager; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.Set; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import org.mockito.MockedStatic; @@ -80,7 +82,7 @@ void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOExc .thenReturn(file2Content); Map result = - SpanParser.getSpansByScopeFromFiles(tempDir.toString(), ""); + EmittedSpanParser.getSpansByScopeFromFiles(tempDir.toString(), ""); EmittedSpans spans = result.get("default"); assertThat(spans.getSpansByScope()).hasSize(2); @@ -103,4 +105,43 @@ void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOExc assertThat(testSpans).hasSize(1); } } + + @Test + void testSpanAggregatorFiltersAndAggregatesCorrectly() { + String targetScopeName = "my-instrumentation-scope"; + + EmittedSpans.Span span1 = + new EmittedSpans.Span("CLIENT", List.of(new TelemetryAttribute("my.operation", "STRING"))); + EmittedSpans.Span span2 = + new EmittedSpans.Span("SERVER", List.of(new TelemetryAttribute("my.operation", "STRING"))); + + // Create test span for a different scope (should be filtered out) + EmittedSpans.Span testSpan = + new EmittedSpans.Span( + "INTERNAL", List.of(new TelemetryAttribute("my.operation", "STRING"))); + + EmittedSpans.SpansByScope targetSpansByScope = + new EmittedSpans.SpansByScope(targetScopeName, List.of(span1, span2)); + EmittedSpans.SpansByScope otherSpansByScope = + new EmittedSpans.SpansByScope("other-scope", List.of(testSpan)); + + EmittedSpans emittedSpans = + new EmittedSpans("default", List.of(targetSpansByScope, otherSpansByScope)); + + // Aggregate spans - only target scope should be included + Map>> spans = + SpanParser.SpanAggregator.aggregateSpans("default", emittedSpans, targetScopeName); + + Map> result = + SpanParser.SpanAggregator.buildFilteredSpans(spans); + + assertThat(result.size()).isEqualTo(1); + assertThat(result.get("default")).isNotNull(); + assertThat(result.get("default").size()).isEqualTo(2); // CLIENT and SERVER spans + + // Verify span kinds are preserved + List spanKinds = + result.get("default").stream().map(EmittedSpans.Span::getSpanKind).toList(); + assertThat(spanKinds).containsExactlyInAnyOrder("CLIENT", "SERVER"); + } } From d80e36a070b41867c251f137add2adf90085035d Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 18 Jun 2025 16:19:08 -0400 Subject: [PATCH 4/5] sort modules and enable spans for a few more exmaples --- docs/instrumentation-list.yaml | 1419 +++++++++-------- instrumentation-docs/collect.sh | 1 + .../docs/internal/TelemetryAttribute.java | 20 + .../docs/parsers/SpanParser.java | 8 +- .../docs/utils/YamlHelper.java | 15 +- .../docs/InstrumentationAnalyzerTest.java | 5 +- .../docs/ModuleConverterTest.java | 14 - .../docs/parsers/SpanParserTest.java | 55 +- .../akka-http-10.0/javaagent/build.gradle.kts | 1 + .../javaagent/build.gradle.kts | 3 + .../javaagent/build.gradle.kts | 5 +- .../testing/AgentTestRunner.java | 19 +- .../testing/InstrumentationTestRunner.java | 6 +- 13 files changed, 877 insertions(+), 694 deletions(-) diff --git a/docs/instrumentation-list.yaml b/docs/instrumentation-list.yaml index dc7235c848b8..f0689db59089 100644 --- a/docs/instrumentation-list.yaml +++ b/docs/instrumentation-list.yaml @@ -15,6 +15,24 @@ libraries: javaagent: - io.activej:activej-http:[6.0,) akka: + - name: akka-actor-2.3 + source_path: instrumentation/akka/akka-actor-2.3 + scope: + name: io.opentelemetry.akka-actor-2.3 + target_versions: + javaagent: + - com.typesafe.akka:akka-actor_2.11:[2.3,) + - com.typesafe.akka:akka-actor_2.12:[2.3,) + - com.typesafe.akka:akka-actor_2.13:[2.3,) + - name: akka-actor-fork-join-2.5 + source_path: instrumentation/akka/akka-actor-fork-join-2.5 + scope: + name: io.opentelemetry.akka-actor-fork-join-2.5 + target_versions: + javaagent: + - com.typesafe.akka:akka-actor_2.12:[2.5,2.6) + - com.typesafe.akka:akka-actor_2.13:[2.5.23,2.6) + - com.typesafe.akka:akka-actor_2.11:[2.5,) - name: akka-http-10.0 source_path: instrumentation/akka/akka-http-10.0 scope: @@ -57,24 +75,49 @@ libraries: type: STRING - name: url.scheme type: STRING - - name: akka-actor-fork-join-2.5 - source_path: instrumentation/akka/akka-actor-fork-join-2.5 - scope: - name: io.opentelemetry.akka-actor-fork-join-2.5 - target_versions: - javaagent: - - com.typesafe.akka:akka-actor_2.12:[2.5,2.6) - - com.typesafe.akka:akka-actor_2.13:[2.5.23,2.6) - - com.typesafe.akka:akka-actor_2.11:[2.5,) - - name: akka-actor-2.3 - source_path: instrumentation/akka/akka-actor-2.3 - scope: - name: io.opentelemetry.akka-actor-2.3 - target_versions: - javaagent: - - com.typesafe.akka:akka-actor_2.11:[2.3,) - - com.typesafe.akka:akka-actor_2.12:[2.3,) - - com.typesafe.akka:akka-actor_2.13:[2.3,) + spans: + - span_kind: CLIENT + attributes: + - name: error.type + type: STRING + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: url.full + type: STRING + - span_kind: SERVER + attributes: + - name: client.address + type: STRING + - name: error.type + type: STRING + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: http.route + type: STRING + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: url.path + type: STRING + - name: url.query + type: STRING + - name: url.scheme + type: STRING + - name: user_agent.original + type: STRING alibaba: - name: alibaba-druid-1.0 description: | @@ -170,114 +213,6 @@ libraries: - name: db.client.connection.pool.name type: STRING apache: - - name: apache-shenyu-2.4 - source_path: instrumentation/apache-shenyu-2.4 - scope: - name: io.opentelemetry.apache-shenyu-2.4 - target_versions: - javaagent: - - org.apache.shenyu:shenyu-web:[2.4.0,) - configurations: - - name: otel.instrumentation.apache-shenyu.experimental-span-attributes - description: Enables experimental span attributes for Apache Shenyu instrumentation. - type: boolean - default: false - - name: apache-httpclient-2.0 - source_path: instrumentation/apache-httpclient/apache-httpclient-2.0 - scope: - name: io.opentelemetry.apache-httpclient-2.0 - target_versions: - javaagent: - - commons-httpclient:commons-httpclient:[2.0,4.0) - - name: apache-httpasyncclient-4.1 - source_path: instrumentation/apache-httpasyncclient-4.1 - scope: - name: io.opentelemetry.apache-httpasyncclient-4.1 - target_versions: - javaagent: - - org.apache.httpcomponents:httpasyncclient:[4.1,) - telemetry: - - when: default - metrics: - - name: http.client.request.duration - description: Duration of HTTP client requests. - type: HISTOGRAM - unit: s - attributes: - - name: http.request.method - type: STRING - - name: http.response.status_code - type: LONG - - name: network.protocol.version - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - - name: apache-httpclient-4.3 - source_path: instrumentation/apache-httpclient/apache-httpclient-4.3 - scope: - name: io.opentelemetry.apache-httpclient-4.3 - target_versions: - library: - - org.apache.httpcomponents:httpclient:[4.3,4.+) - - name: apache-httpclient-4.0 - source_path: instrumentation/apache-httpclient/apache-httpclient-4.0 - scope: - name: io.opentelemetry.apache-httpclient-4.0 - target_versions: - javaagent: - - io.dropwizard:dropwizard-client:(,3.0.0) - - org.apache.httpcomponents:httpclient:[4.0,) - - name: apache-dubbo-2.7 - description: The Apache Dubbo instrumentation provides client and server spans - for Apache Dubbo RPC calls. Each call produces a span named after the Dubbo - method, enriched with standard RPC attributes (system, service, method), network - attributes, and error details if an exception occurs. - source_path: instrumentation/apache-dubbo-2.7 - scope: - name: io.opentelemetry.apache-dubbo-2.7 - target_versions: - javaagent: - - org.apache.dubbo:dubbo:[2.7,) - configurations: - - name: otel.instrumentation.common.peer-service-mapping - description: Used to specify a mapping from host names or IP addresses to peer - services. - type: map - default: '' - - name: apache-httpclient-5.2 - source_path: instrumentation/apache-httpclient/apache-httpclient-5.2 - scope: - name: io.opentelemetry.apache-httpclient-5.2 - target_versions: - library: - - org.apache.httpcomponents.client5:httpclient5:5.2.1 - - name: apache-httpclient-5.0 - source_path: instrumentation/apache-httpclient/apache-httpclient-5.0 - scope: - name: io.opentelemetry.apache-httpclient-5.0 - target_versions: - javaagent: - - org.apache.httpcomponents.client5:httpclient5:[5.0,) - telemetry: - - when: default - metrics: - - name: http.client.request.duration - description: Duration of HTTP client requests. - type: HISTOGRAM - unit: s - attributes: - - name: http.request.method - type: STRING - - name: http.response.status_code - type: LONG - - name: network.protocol.version - type: STRING - - name: server.address - type: STRING - - name: server.port - type: LONG - name: apache-dbcp-2.0 source_path: instrumentation/apache-dbcp-2.0 scope: @@ -354,6 +289,141 @@ libraries: attributes: - name: db.client.connection.pool.name type: STRING + - name: apache-dubbo-2.7 + description: The Apache Dubbo instrumentation provides client and server spans + for Apache Dubbo RPC calls. Each call produces a span named after the Dubbo + method, enriched with standard RPC attributes (system, service, method), network + attributes, and error details if an exception occurs. + source_path: instrumentation/apache-dubbo-2.7 + scope: + name: io.opentelemetry.apache-dubbo-2.7 + target_versions: + javaagent: + - org.apache.dubbo:dubbo:[2.7,) + configurations: + - name: otel.instrumentation.common.peer-service-mapping + description: Used to specify a mapping from host names or IP addresses to peer + services. + type: map + default: '' + telemetry: + - when: default + spans: + - span_kind: CLIENT + attributes: + - 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: network.peer.address + type: STRING + - name: network.peer.port + type: LONG + - name: rpc.method + type: STRING + - name: rpc.service + type: STRING + - name: rpc.system + type: STRING + - name: apache-httpasyncclient-4.1 + source_path: instrumentation/apache-httpasyncclient-4.1 + scope: + name: io.opentelemetry.apache-httpasyncclient-4.1 + target_versions: + javaagent: + - org.apache.httpcomponents:httpasyncclient:[4.1,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: apache-httpclient-2.0 + source_path: instrumentation/apache-httpclient/apache-httpclient-2.0 + scope: + name: io.opentelemetry.apache-httpclient-2.0 + target_versions: + javaagent: + - commons-httpclient:commons-httpclient:[2.0,4.0) + - name: apache-httpclient-4.0 + source_path: instrumentation/apache-httpclient/apache-httpclient-4.0 + scope: + name: io.opentelemetry.apache-httpclient-4.0 + target_versions: + javaagent: + - io.dropwizard:dropwizard-client:(,3.0.0) + - org.apache.httpcomponents:httpclient:[4.0,) + - name: apache-httpclient-4.3 + source_path: instrumentation/apache-httpclient/apache-httpclient-4.3 + scope: + name: io.opentelemetry.apache-httpclient-4.3 + target_versions: + library: + - org.apache.httpcomponents:httpclient:[4.3,4.+) + - name: apache-httpclient-5.0 + source_path: instrumentation/apache-httpclient/apache-httpclient-5.0 + scope: + name: io.opentelemetry.apache-httpclient-5.0 + target_versions: + javaagent: + - org.apache.httpcomponents.client5:httpclient5:[5.0,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: network.protocol.version + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: apache-httpclient-5.2 + source_path: instrumentation/apache-httpclient/apache-httpclient-5.2 + scope: + name: io.opentelemetry.apache-httpclient-5.2 + target_versions: + library: + - org.apache.httpcomponents.client5:httpclient5:5.2.1 + - name: apache-shenyu-2.4 + source_path: instrumentation/apache-shenyu-2.4 + scope: + name: io.opentelemetry.apache-shenyu-2.4 + target_versions: + javaagent: + - org.apache.shenyu:shenyu-web:[2.4.0,) + configurations: + - name: otel.instrumentation.apache-shenyu.experimental-span-attributes + description: Enables experimental span attributes for Apache Shenyu instrumentation. + type: boolean + default: false armeria: - name: armeria-1.3 source_path: instrumentation/armeria/armeria-1.3 @@ -398,33 +468,33 @@ libraries: javaagent: - io.avaje:avaje-jex:[3.0,) aws: - - name: aws-lambda-events-2.2 + - name: aws-lambda-core-1.0 description: | - Provides full instrumentation of the Lambda library, including standard and custom event types, from `aws-lambda-java-events` 2.2+. - source_path: instrumentation/aws-lambda/aws-lambda-events-2.2 + Provides lightweight instrumentation of the Lambda core library, supporting all versions. Use this package if you only use `RequestStreamHandler` or know you don't use any event classes from `aws-lambda-java-events`. This also includes when you are using `aws-serverless-java-container` to run e.g., a Spring Boot application on Lambda. + source_path: instrumentation/aws-lambda/aws-lambda-core-1.0 scope: - name: io.opentelemetry.aws-lambda-events-2.2 + name: io.opentelemetry.aws-lambda-core-1.0 target_versions: javaagent: - com.amazonaws:aws-lambda-java-core:[1.0.0,) library: - - com.amazonaws:aws-lambda-java-events:2.2.1 - com.amazonaws:aws-lambda-java-core:1.0.0 configurations: - name: otel.instrumentation.aws-lambda.flush-timeout description: Flush timeout in milliseconds. type: int default: 10000 - - name: aws-lambda-core-1.0 + - name: aws-lambda-events-2.2 description: | - Provides lightweight instrumentation of the Lambda core library, supporting all versions. Use this package if you only use `RequestStreamHandler` or know you don't use any event classes from `aws-lambda-java-events`. This also includes when you are using `aws-serverless-java-container` to run e.g., a Spring Boot application on Lambda. - source_path: instrumentation/aws-lambda/aws-lambda-core-1.0 + Provides full instrumentation of the Lambda library, including standard and custom event types, from `aws-lambda-java-events` 2.2+. + source_path: instrumentation/aws-lambda/aws-lambda-events-2.2 scope: - name: io.opentelemetry.aws-lambda-core-1.0 + name: io.opentelemetry.aws-lambda-events-2.2 target_versions: javaagent: - com.amazonaws:aws-lambda-java-core:[1.0.0,) library: + - com.amazonaws:aws-lambda-java-events:2.2.1 - com.amazonaws:aws-lambda-java-core:1.0.0 configurations: - name: otel.instrumentation.aws-lambda.flush-timeout @@ -556,13 +626,13 @@ libraries: - name: db.system.name type: STRING azure: - - name: azure-core-1.36 - source_path: instrumentation/azure-core/azure-core-1.36 + - name: azure-core-1.14 + source_path: instrumentation/azure-core/azure-core-1.14 scope: - name: io.opentelemetry.azure-core-1.36 + name: io.opentelemetry.azure-core-1.14 target_versions: javaagent: - - com.azure:azure-core:[1.36.0,) + - com.azure:azure-core:[1.14.0,1.19.0) - name: azure-core-1.19 source_path: instrumentation/azure-core/azure-core-1.19 scope: @@ -570,13 +640,13 @@ libraries: target_versions: javaagent: - com.azure:azure-core:[1.19.0,1.36.0) - - name: azure-core-1.14 - source_path: instrumentation/azure-core/azure-core-1.14 + - name: azure-core-1.36 + source_path: instrumentation/azure-core/azure-core-1.36 scope: - name: io.opentelemetry.azure-core-1.14 + name: io.opentelemetry.azure-core-1.36 target_versions: javaagent: - - com.azure:azure-core:[1.14.0,1.19.0) + - com.azure:azure-core:[1.36.0,) c3p0: - name: c3p0-0.9 description: The c3p0 instrumentation provides connection pool metrics for c3p0 @@ -645,45 +715,45 @@ libraries: type: boolean default: false cassandra: - - name: cassandra-4.0 + - name: cassandra-3.0 description: | Instruments the Cassandra database client, providing database client spans and metrics for Cassandra queries. - source_path: instrumentation/cassandra/cassandra-4.0 + source_path: instrumentation/cassandra/cassandra-3.0 scope: - name: io.opentelemetry.cassandra-4.0 + name: io.opentelemetry.cassandra-3.0 target_versions: javaagent: - - com.datastax.oss:java-driver-core:[4.0,4.4) + - com.datastax.cassandra:cassandra-driver-core:[3.0,4.0) configurations: - name: otel.instrumentation.common.db-statement-sanitizer.enabled description: Enables statement sanitization for database queries. type: boolean default: true - - name: cassandra-4.4 + - name: cassandra-4.0 description: | Instruments the Cassandra database client, providing database client spans and metrics for Cassandra queries. - source_path: instrumentation/cassandra/cassandra-4.4 + source_path: instrumentation/cassandra/cassandra-4.0 scope: - name: io.opentelemetry.cassandra-4.4 + name: io.opentelemetry.cassandra-4.0 target_versions: javaagent: - - com.datastax.oss:java-driver-core:[4.4,] - library: - - com.datastax.oss:java-driver-core:4.4.0 + - com.datastax.oss:java-driver-core:[4.0,4.4) configurations: - name: otel.instrumentation.common.db-statement-sanitizer.enabled description: Enables statement sanitization for database queries. type: boolean default: true - - name: cassandra-3.0 + - name: cassandra-4.4 description: | Instruments the Cassandra database client, providing database client spans and metrics for Cassandra queries. - source_path: instrumentation/cassandra/cassandra-3.0 + source_path: instrumentation/cassandra/cassandra-4.4 scope: - name: io.opentelemetry.cassandra-3.0 + name: io.opentelemetry.cassandra-4.4 target_versions: javaagent: - - com.datastax.cassandra:cassandra-driver-core:[3.0,4.0) + - com.datastax.oss:java-driver-core:[4.4,] + library: + - com.datastax.oss:java-driver-core:4.4.0 configurations: - name: otel.instrumentation.common.db-statement-sanitizer.enabled description: Enables statement sanitization for database queries. @@ -705,6 +775,22 @@ libraries: type: boolean default: true telemetry: + - when: default + spans: + - span_kind: CLIENT + attributes: + - name: db.name + type: STRING + - name: db.operation + type: STRING + - name: db.statement + type: STRING + - name: db.system + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG - when: otel.semconv-stability.opt-in=database metrics: - name: db.client.operation.duration @@ -722,16 +808,39 @@ libraries: type: STRING - name: server.port type: LONG + spans: + - span_kind: CLIENT + attributes: + - name: db.namespace + type: STRING + - name: db.operation.name + type: STRING + - name: db.query.text + type: STRING + - name: db.response.status_code + type: STRING + - name: db.system.name + type: STRING + - name: error.type + type: STRING + - name: server.address + type: STRING + - name: server.port + type: LONG couchbase: - - name: couchbase-3.1.6 - description: | - Couchbase instrumentation is owned by the Couchbase project. This instrumentation automatically configures the instrumentation provided by the Couchbase library. - source_path: instrumentation/couchbase/couchbase-3.1.6 + - name: couchbase-2.0 + source_path: instrumentation/couchbase/couchbase-2.0 scope: - name: io.opentelemetry.couchbase-3.1.6 + name: io.opentelemetry.couchbase-2.0 target_versions: javaagent: - - com.couchbase.client:java-client:[3.1.6,3.2.0) + - com.couchbase.client:java-client:[2,3) + configurations: + - name: otel.instrumentation.couchbase.experimental-span-attributes + description: Enables experimental span attributes couchbase.operation_id and + couchbase.local.address + type: boolean + default: false - name: couchbase-2.6 source_path: instrumentation/couchbase/couchbase-2.6 scope: @@ -745,37 +854,33 @@ libraries: couchbase.local.address type: boolean default: false - - name: couchbase-2.0 - source_path: instrumentation/couchbase/couchbase-2.0 + - name: couchbase-3.1 + description: | + Couchbase instrumentation is owned by the Couchbase project. This instrumentation automatically configures the instrumentation provided by the Couchbase library. + source_path: instrumentation/couchbase/couchbase-3.1 scope: - name: io.opentelemetry.couchbase-2.0 + name: io.opentelemetry.couchbase-3.1 target_versions: javaagent: - - com.couchbase.client:java-client:[2,3) - configurations: - - name: otel.instrumentation.couchbase.experimental-span-attributes - description: Enables experimental span attributes couchbase.operation_id and - couchbase.local.address - type: boolean - default: false - - name: couchbase-3.2 + - com.couchbase.client:java-client:[3.1,3.1.6) + - name: couchbase-3.1.6 description: | Couchbase instrumentation is owned by the Couchbase project. This instrumentation automatically configures the instrumentation provided by the Couchbase library. - source_path: instrumentation/couchbase/couchbase-3.2 + source_path: instrumentation/couchbase/couchbase-3.1.6 scope: - name: io.opentelemetry.couchbase-3.2 + name: io.opentelemetry.couchbase-3.1.6 target_versions: javaagent: - - com.couchbase.client:java-client:[3.2.0,) - - name: couchbase-3.1 + - com.couchbase.client:java-client:[3.1.6,3.2.0) + - name: couchbase-3.2 description: | Couchbase instrumentation is owned by the Couchbase project. This instrumentation automatically configures the instrumentation provided by the Couchbase library. - source_path: instrumentation/couchbase/couchbase-3.1 + source_path: instrumentation/couchbase/couchbase-3.2 scope: - name: io.opentelemetry.couchbase-3.1 + name: io.opentelemetry.couchbase-3.2 target_versions: javaagent: - - com.couchbase.client:java-client:[3.1,3.1.6) + - com.couchbase.client:java-client:[3.2.0,) dropwizard: - name: dropwizard-metrics-4.0 description: | @@ -810,19 +915,6 @@ libraries: type: boolean default: false elasticsearch: - - name: elasticsearch-rest-6.4 - source_path: instrumentation/elasticsearch/elasticsearch-rest-6.4 - scope: - name: io.opentelemetry.elasticsearch-rest-6.4 - target_versions: - javaagent: - - org.elasticsearch.client:elasticsearch-rest-client:[6.4,7.0) - configurations: - - name: otel.instrumentation.elasticsearch.capture-search-query - description: | - Enable the capture of search query bodies. It is important to note that Elasticsearch queries may contain personal or sensitive information. - type: boolean - default: false - name: elasticsearch-api-client-7.16 source_path: instrumentation/elasticsearch/elasticsearch-api-client-7.16 scope: @@ -846,6 +938,19 @@ libraries: may contain personal or sensitive information. type: boolean default: false + - name: elasticsearch-rest-6.4 + source_path: instrumentation/elasticsearch/elasticsearch-rest-6.4 + scope: + name: io.opentelemetry.elasticsearch-rest-6.4 + target_versions: + javaagent: + - org.elasticsearch.client:elasticsearch-rest-client:[6.4,7.0) + configurations: + - name: otel.instrumentation.elasticsearch.capture-search-query + description: | + Enable the capture of search query bodies. It is important to note that Elasticsearch queries may contain personal or sensitive information. + type: boolean + default: false - name: elasticsearch-rest-7.0 source_path: instrumentation/elasticsearch/elasticsearch-rest-7.0 scope: @@ -861,14 +966,6 @@ libraries: Enable the capture of search query bodies. It is important to note that Elasticsearch queries may contain personal or sensitive information. type: boolean default: false - - name: elasticsearch-transport-6.0 - source_path: instrumentation/elasticsearch/elasticsearch-transport-6.0 - scope: - name: io.opentelemetry.elasticsearch-transport-6.0 - target_versions: - javaagent: - - org.elasticsearch:elasticsearch:[6.0.0,8.0.0) - - org.elasticsearch.client:transport:[6.0.0,) - name: elasticsearch-transport-5.0 source_path: instrumentation/elasticsearch/elasticsearch-transport-5.0 scope: @@ -885,6 +982,14 @@ libraries: javaagent: - org.elasticsearch.client:transport:[5.3.0,6.0.0) - org.elasticsearch:elasticsearch:[5.3.0,6.0.0) + - name: elasticsearch-transport-6.0 + source_path: instrumentation/elasticsearch/elasticsearch-transport-6.0 + scope: + name: io.opentelemetry.elasticsearch-transport-6.0 + target_versions: + javaagent: + - org.elasticsearch:elasticsearch:[6.0.0,8.0.0) + - org.elasticsearch.client:transport:[6.0.0,) executors: - name: executors description: | @@ -1020,20 +1125,6 @@ libraries: - com.google.gwt:gwt-servlet:[2.0.0,) - org.gwtproject:gwt-servlet:[2.10.0,) hibernate: - - name: hibernate-4.0 - source_path: instrumentation/hibernate/hibernate-4.0 - scope: - name: io.opentelemetry.hibernate-4.0 - target_versions: - javaagent: - - org.hibernate:hibernate-core:[4.0.0.Final,6) - - name: hibernate-procedure-call-4.3 - source_path: instrumentation/hibernate/hibernate-procedure-call-4.3 - scope: - name: io.opentelemetry.hibernate-procedure-call-4.3 - target_versions: - javaagent: - - org.hibernate:hibernate-core:[4.3.0.Final,) - name: hibernate-3.3 source_path: instrumentation/hibernate/hibernate-3.3 scope: @@ -1041,6 +1132,13 @@ libraries: target_versions: javaagent: - org.hibernate:hibernate-core:[3.3.0.GA,4.0.0.Final) + - name: hibernate-4.0 + source_path: instrumentation/hibernate/hibernate-4.0 + scope: + name: io.opentelemetry.hibernate-4.0 + target_versions: + javaagent: + - org.hibernate:hibernate-core:[4.0.0.Final,6) - name: hibernate-6.0 source_path: instrumentation/hibernate/hibernate-6.0 minimum_java_version: 11 @@ -1049,6 +1147,13 @@ libraries: target_versions: javaagent: - org.hibernate:hibernate-core:[6.0.0.Final,) + - name: hibernate-procedure-call-4.3 + source_path: instrumentation/hibernate/hibernate-procedure-call-4.3 + scope: + name: io.opentelemetry.hibernate-procedure-call-4.3 + target_versions: + javaagent: + - org.hibernate:hibernate-core:[4.3.0.Final,) - name: hibernate-reactive-1.0 source_path: instrumentation/hibernate/hibernate-reactive-1.0 scope: @@ -1237,13 +1342,6 @@ libraries: javaagent: - org.influxdb:influxdb-java:[2.4,) java: - - name: java-http-server - source_path: instrumentation/java-http-server - scope: - name: io.opentelemetry.java-http-server - target_versions: - javaagent: - - Java 8+ - name: java-http-client source_path: instrumentation/java-http-client minimum_java_version: 11 @@ -1270,6 +1368,13 @@ libraries: type: STRING - name: server.port type: LONG + - name: java-http-server + source_path: instrumentation/java-http-server + scope: + name: io.opentelemetry.java-http-server + target_versions: + javaagent: + - Java 8+ javalin: - name: javalin-5.0 source_path: instrumentation/javalin-5.0 @@ -1280,6 +1385,21 @@ libraries: javaagent: - io.javalin:javalin:[5.0.0,) jaxrs: + - name: jaxrs-1.0 + disabled_by_default: true + source_path: instrumentation/jaxrs/jaxrs-1.0 + scope: + name: io.opentelemetry.jaxrs-1.0 + target_versions: + javaagent: + - javax.ws.rs:jsr311-api:[0.5,) + - name: jaxrs-2.0-annotations + source_path: instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations + scope: + name: io.opentelemetry.jaxrs-2.0-annotations + target_versions: + javaagent: + - javax.ws.rs:javax.ws.rs-api:[,] - name: jaxrs-2.0-cxf-3.2 source_path: instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-cxf-3.2 scope: @@ -1288,13 +1408,6 @@ libraries: javaagent: - org.apache.tomee:openejb-cxf-rs:(8,) - org.apache.cxf:cxf-rt-frontend-jaxrs:[3.2,4) - - name: jaxrs-3.0-annotations - source_path: instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations - scope: - name: io.opentelemetry.jaxrs-3.0-annotations - target_versions: - javaagent: - - jakarta.ws.rs:jakarta.ws.rs-api:[3.0.0,) - name: jaxrs-2.0-jersey-2.0 source_path: instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-jersey-2.0 scope: @@ -1303,14 +1416,14 @@ libraries: javaagent: - org.glassfish.jersey.core:jersey-server:[2.0,3.0.0) - org.glassfish.jersey.containers:jersey-container-servlet:[2.0,3.0.0) - - name: jaxrs-3.0-jersey-3.0 - source_path: instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0 - minimum_java_version: 11 + - name: jaxrs-2.0-resteasy-3.0 + source_path: instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0 scope: - name: io.opentelemetry.jaxrs-3.0-jersey-3.0 + name: io.opentelemetry.jaxrs-2.0-resteasy-3.0 target_versions: javaagent: - - org.glassfish.jersey.core:jersey-server:[3.0.0,) + - org.jboss.resteasy:resteasy-jaxrs:[3.0.0.Final,3.1.0.Final) + - org.jboss.resteasy:resteasy-jaxrs:[3.5.0.Final,4) - name: jaxrs-2.0-resteasy-3.1 source_path: instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.1 scope: @@ -1319,21 +1432,21 @@ libraries: javaagent: - org.jboss.resteasy:resteasy-jaxrs:[3.1.0.Final,3.5.0.Final) - org.jboss.resteasy:resteasy-core:[4.0.0.Final,6) - - name: jaxrs-2.0-resteasy-3.0 - source_path: instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-resteasy-3.0 + - name: jaxrs-3.0-annotations + source_path: instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-annotations scope: - name: io.opentelemetry.jaxrs-2.0-resteasy-3.0 + name: io.opentelemetry.jaxrs-3.0-annotations target_versions: javaagent: - - org.jboss.resteasy:resteasy-jaxrs:[3.0.0.Final,3.1.0.Final) - - org.jboss.resteasy:resteasy-jaxrs:[3.5.0.Final,4) - - name: jaxrs-2.0-annotations - source_path: instrumentation/jaxrs/jaxrs-2.0/jaxrs-2.0-annotations + - jakarta.ws.rs:jakarta.ws.rs-api:[3.0.0,) + - name: jaxrs-3.0-jersey-3.0 + source_path: instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-jersey-3.0 + minimum_java_version: 11 scope: - name: io.opentelemetry.jaxrs-2.0-annotations + name: io.opentelemetry.jaxrs-3.0-jersey-3.0 target_versions: javaagent: - - javax.ws.rs:javax.ws.rs-api:[,] + - org.glassfish.jersey.core:jersey-server:[3.0.0,) - name: jaxrs-3.0-resteasy-6.0 source_path: instrumentation/jaxrs/jaxrs-3.0/jaxrs-3.0-resteasy-6.0 minimum_java_version: 11 @@ -1342,23 +1455,7 @@ libraries: target_versions: javaagent: - org.jboss.resteasy:resteasy-core:[6.0.0.Final,) - - name: jaxrs-1.0 - disabled_by_default: true - source_path: instrumentation/jaxrs/jaxrs-1.0 - scope: - name: io.opentelemetry.jaxrs-1.0 - target_versions: - javaagent: - - javax.ws.rs:jsr311-api:[0.5,) jaxws: - - name: jaxws-jws-api-1.1 - disabled_by_default: true - source_path: instrumentation/jaxws/jaxws-jws-api-1.1 - scope: - name: io.opentelemetry.jaxws-jws-api-1.1 - target_versions: - javaagent: - - javax.jws:javax.jws-api:[1.1,] - name: jaxws-2.0 source_path: instrumentation/jaxws/jaxws-2.0 scope: @@ -1366,6 +1463,13 @@ libraries: target_versions: javaagent: - javax.xml.ws:jaxws-api:[2.0,] + - name: jaxws-2.0-axis2-1.6 + source_path: instrumentation/jaxws/jaxws-2.0-axis2-1.6 + scope: + name: io.opentelemetry.jaxws-2.0-axis2-1.6 + target_versions: + javaagent: + - org.apache.axis2:axis2-jaxws:[1.6.0,) - name: jaxws-cxf-3.0 source_path: instrumentation/jaxws/jaxws-cxf-3.0 scope: @@ -1373,13 +1477,14 @@ libraries: target_versions: javaagent: - org.apache.cxf:cxf-rt-frontend-jaxws:[3.0.0,) - - name: jaxws-2.0-axis2-1.6 - source_path: instrumentation/jaxws/jaxws-2.0-axis2-1.6 + - name: jaxws-jws-api-1.1 + disabled_by_default: true + source_path: instrumentation/jaxws/jaxws-jws-api-1.1 scope: - name: io.opentelemetry.jaxws-2.0-axis2-1.6 + name: io.opentelemetry.jaxws-jws-api-1.1 target_versions: javaagent: - - org.apache.axis2:axis2-jaxws:[1.6.0,) + - javax.jws:javax.jws-api:[1.1,] - name: jaxws-metro-2.2 source_path: instrumentation/jaxws/jaxws-metro-2.2 scope: @@ -1450,21 +1555,44 @@ libraries: target_versions: javaagent: - redis.clients:jedis:[1.4.0,3.0.0) + - name: jedis-3.0 + source_path: instrumentation/jedis/jedis-3.0 + scope: + name: io.opentelemetry.jedis-3.0 + target_versions: + javaagent: + - redis.clients:jedis:[3.0.0,4) - name: jedis-4.0 source_path: instrumentation/jedis/jedis-4.0 scope: - name: io.opentelemetry.jedis-4.0 + name: io.opentelemetry.jedis-4.0 + target_versions: + javaagent: + - redis.clients:jedis:[4.0.0-beta1,) + jetty: + - name: jetty-11.0 + source_path: instrumentation/jetty/jetty-11.0 + minimum_java_version: 11 + scope: + name: io.opentelemetry.jetty-11.0 + target_versions: + javaagent: + - org.eclipse.jetty:jetty-server:[11, 12) + - name: jetty-12.0 + source_path: instrumentation/jetty/jetty-12.0 + minimum_java_version: 17 + scope: + name: io.opentelemetry.jetty-12.0 target_versions: javaagent: - - redis.clients:jedis:[4.0.0-beta1,) - - name: jedis-3.0 - source_path: instrumentation/jedis/jedis-3.0 + - org.eclipse.jetty:jetty-server:[12,) + - name: jetty-8.0 + source_path: instrumentation/jetty/jetty-8.0 scope: - name: io.opentelemetry.jedis-3.0 + name: io.opentelemetry.jetty-8.0 target_versions: javaagent: - - redis.clients:jedis:[3.0.0,4) - jetty: + - org.eclipse.jetty:jetty-server:[8.0.0.v20110901,11) - name: jetty-httpclient-12.0 source_path: instrumentation/jetty-httpclient/jetty-httpclient-12.0 minimum_java_version: 17 @@ -1493,21 +1621,6 @@ libraries: type: STRING - name: server.port type: LONG - - name: jetty-12.0 - source_path: instrumentation/jetty/jetty-12.0 - minimum_java_version: 17 - scope: - name: io.opentelemetry.jetty-12.0 - target_versions: - javaagent: - - org.eclipse.jetty:jetty-server:[12,) - - name: jetty-8.0 - source_path: instrumentation/jetty/jetty-8.0 - scope: - name: io.opentelemetry.jetty-8.0 - target_versions: - javaagent: - - org.eclipse.jetty:jetty-server:[8.0.0.v20110901,11) - name: jetty-httpclient-9.2 source_path: instrumentation/jetty-httpclient/jetty-httpclient-9.2 scope: @@ -1535,15 +1648,16 @@ libraries: type: STRING - name: server.port type: LONG - - name: jetty-11.0 - source_path: instrumentation/jetty/jetty-11.0 - minimum_java_version: 11 + jms: + - name: jms-1.1 + source_path: instrumentation/jms/jms-1.1 scope: - name: io.opentelemetry.jetty-11.0 + name: io.opentelemetry.jms-1.1 target_versions: javaagent: - - org.eclipse.jetty:jetty-server:[11, 12) - jms: + - javax.jms:javax.jms-api:(,) + - jakarta.jms:jakarta.jms-api:(,3) + - javax.jms:jms-api:(,) - name: jms-3.0 source_path: instrumentation/jms/jms-3.0 minimum_java_version: 11 @@ -1552,15 +1666,6 @@ libraries: target_versions: javaagent: - jakarta.jms:jakarta.jms-api:[3.0.0,) - - name: jms-1.1 - source_path: instrumentation/jms/jms-1.1 - scope: - name: io.opentelemetry.jms-1.1 - target_versions: - javaagent: - - javax.jms:javax.jms-api:(,) - - jakarta.jms:jakarta.jms-api:(,3) - - javax.jms:jms-api:(,) jodd: - name: jodd-http-4.2 source_path: instrumentation/jodd-http-4.2 @@ -1588,14 +1693,17 @@ libraries: - name: server.port type: LONG jsf: - - name: jsf-myfaces-3.0 - source_path: instrumentation/jsf/jsf-myfaces-3.0 - minimum_java_version: 11 + - name: jsf-mojarra-1.2 + source_path: instrumentation/jsf/jsf-mojarra-1.2 scope: - name: io.opentelemetry.jsf-myfaces-3.0 + name: io.opentelemetry.jsf-mojarra-1.2 target_versions: javaagent: - - org.apache.myfaces.core:myfaces-impl:[3,) + - com.sun.faces:jsf-impl:[2.1,2.2) + - org.glassfish:jakarta.faces:[2.3.9,3) + - com.sun.faces:jsf-impl:[2.0,2.1) + - org.glassfish:javax.faces:[2.0.7,3) + - javax.faces:jsf-impl:[1.2,2) - name: jsf-mojarra-3.0 source_path: instrumentation/jsf/jsf-mojarra-3.0 minimum_java_version: 11 @@ -1611,17 +1719,14 @@ libraries: target_versions: javaagent: - org.apache.myfaces.core:myfaces-impl:[1.2,3) - - name: jsf-mojarra-1.2 - source_path: instrumentation/jsf/jsf-mojarra-1.2 + - name: jsf-myfaces-3.0 + source_path: instrumentation/jsf/jsf-myfaces-3.0 + minimum_java_version: 11 scope: - name: io.opentelemetry.jsf-mojarra-1.2 + name: io.opentelemetry.jsf-myfaces-3.0 target_versions: javaagent: - - com.sun.faces:jsf-impl:[2.1,2.2) - - org.glassfish:jakarta.faces:[2.3.9,3) - - com.sun.faces:jsf-impl:[2.0,2.1) - - org.glassfish:javax.faces:[2.0.7,3) - - javax.faces:jsf-impl:[1.2,2) + - org.apache.myfaces.core:myfaces-impl:[3,) jsp: - name: jsp-2.3 source_path: instrumentation/jsp-2.3 @@ -1631,20 +1736,6 @@ libraries: javaagent: - org.apache.tomcat:tomcat-jasper:[7.0.19,10) kafka: - - name: kafka-streams-0.11 - source_path: instrumentation/kafka/kafka-streams-0.11 - scope: - name: io.opentelemetry.kafka-streams-0.11 - target_versions: - javaagent: - - org.apache.kafka:kafka-streams:[0.11.0.0,) - - name: kafka-clients-2.6 - source_path: instrumentation/kafka/kafka-clients/kafka-clients-2.6 - scope: - name: io.opentelemetry.kafka-clients-2.6 - target_versions: - library: - - org.apache.kafka:kafka-clients:2.6.0 - name: kafka-clients-0.11 source_path: instrumentation/kafka/kafka-clients/kafka-clients-0.11 scope: @@ -1661,6 +1752,20 @@ libraries: description: Enables the capture of the experimental consumer attribute "kafka.record.queue_time_ms" type: boolean default: false + - name: kafka-clients-2.6 + source_path: instrumentation/kafka/kafka-clients/kafka-clients-2.6 + scope: + name: io.opentelemetry.kafka-clients-2.6 + target_versions: + library: + - org.apache.kafka:kafka-clients:2.6.0 + - name: kafka-streams-0.11 + source_path: instrumentation/kafka/kafka-streams-0.11 + scope: + name: io.opentelemetry.kafka-streams-0.11 + target_versions: + javaagent: + - org.apache.kafka:kafka-streams:[0.11.0.0,) kotlinx: - name: kotlinx-coroutines source_path: instrumentation/kotlinx-coroutines @@ -1688,6 +1793,13 @@ libraries: - org.jetbrains.kotlinx:kotlinx-coroutines-core:[1.3.0,1.3.8) - org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:[1.3.9,) ktor: + - name: ktor-1.0 + source_path: instrumentation/ktor/ktor-1.0 + scope: + name: io.opentelemetry.ktor-1.0 + target_versions: + library: + - io.ktor:ktor-server-core:[1.0.0,1.+) - name: ktor-2.0 source_path: instrumentation/ktor/ktor-2.0 scope: @@ -1710,13 +1822,6 @@ libraries: library: - io.ktor:ktor-server-core:3.0.0 - io.ktor:ktor-client-core:3.0.0 - - name: ktor-1.0 - source_path: instrumentation/ktor/ktor-1.0 - scope: - name: io.opentelemetry.ktor-1.0 - target_versions: - library: - - io.ktor:ktor-server-core:[1.0.0,1.+) kubernetes: - name: kubernetes-client-7.0 source_path: instrumentation/kubernetes-client-7.0 @@ -1726,15 +1831,13 @@ libraries: javaagent: - io.kubernetes:client-java-api:[7.0.0,) lettuce: - - name: lettuce-5.1 - source_path: instrumentation/lettuce/lettuce-5.1 + - name: lettuce-4.0 + source_path: instrumentation/lettuce/lettuce-4.0 scope: - name: io.opentelemetry.lettuce-5.1 + name: io.opentelemetry.lettuce-4.0 target_versions: javaagent: - - io.lettuce:lettuce-core:[5.1.0.RELEASE,) - library: - - io.lettuce:lettuce-core:5.1.0.RELEASE + - biz.paluch.redis:lettuce:[4.0.Final,) - name: lettuce-5.0 source_path: instrumentation/lettuce/lettuce-5.0 scope: @@ -1742,30 +1845,32 @@ libraries: target_versions: javaagent: - io.lettuce:lettuce-core:[5.0.0.RELEASE,5.1.0.RELEASE) - - name: lettuce-4.0 - source_path: instrumentation/lettuce/lettuce-4.0 + - name: lettuce-5.1 + source_path: instrumentation/lettuce/lettuce-5.1 scope: - name: io.opentelemetry.lettuce-4.0 + name: io.opentelemetry.lettuce-5.1 target_versions: javaagent: - - biz.paluch.redis:lettuce:[4.0.Final,) + - io.lettuce:lettuce-core:[5.1.0.RELEASE,) + library: + - io.lettuce:lettuce-core:5.1.0.RELEASE liberty: - - name: liberty-dispatcher-20.0 - source_path: instrumentation/liberty/liberty-dispatcher-20.0 - scope: - name: io.opentelemetry.liberty-dispatcher-20.0 - name: liberty-20.0 source_path: instrumentation/liberty/liberty-20.0 scope: name: io.opentelemetry.liberty-20.0 + - name: liberty-dispatcher-20.0 + source_path: instrumentation/liberty/liberty-dispatcher-20.0 + scope: + name: io.opentelemetry.liberty-dispatcher-20.0 log4j: - - name: log4j-context-data-2.7 - source_path: instrumentation/log4j/log4j-context-data/log4j-context-data-2.7 + - name: log4j-appender-1.2 + source_path: instrumentation/log4j/log4j-appender-1.2 scope: - name: io.opentelemetry.log4j-context-data-2.7 + name: io.opentelemetry.log4j-appender-1.2 target_versions: javaagent: - - org.apache.logging.log4j:log4j-core:[2.7,2.17.0) + - log4j:log4j:[1.2,) - name: log4j-appender-2.17 source_path: instrumentation/log4j/log4j-appender-2.17 scope: @@ -1775,13 +1880,20 @@ libraries: - org.apache.logging.log4j:log4j-core:[2.0,) library: - org.apache.logging.log4j:log4j-core:2.17.0 - - name: log4j-appender-1.2 - source_path: instrumentation/log4j/log4j-appender-1.2 + - name: log4j-context-data-2.17 + source_path: instrumentation/log4j/log4j-context-data/log4j-context-data-2.17 scope: - name: io.opentelemetry.log4j-appender-1.2 + name: io.opentelemetry.log4j-context-data-2.17 target_versions: javaagent: - - log4j:log4j:[1.2,) + - org.apache.logging.log4j:log4j-core:[2.17.0,) + - name: log4j-context-data-2.7 + source_path: instrumentation/log4j/log4j-context-data/log4j-context-data-2.7 + scope: + name: io.opentelemetry.log4j-context-data-2.7 + target_versions: + javaagent: + - org.apache.logging.log4j:log4j-core:[2.7,2.17.0) - name: log4j-mdc-1.2 source_path: instrumentation/log4j/log4j-mdc-1.2 scope: @@ -1789,14 +1901,18 @@ libraries: target_versions: javaagent: - log4j:log4j:[1.2,) - - name: log4j-context-data-2.17 - source_path: instrumentation/log4j/log4j-context-data/log4j-context-data-2.17 + logback: + - name: logback-appender-1.0 + source_path: instrumentation/logback/logback-appender-1.0 scope: - name: io.opentelemetry.log4j-context-data-2.17 + name: io.opentelemetry.logback-appender-1.0 target_versions: javaagent: - - org.apache.logging.log4j:log4j-core:[2.17.0,) - logback: + - ch.qos.logback:logback-classic:[0.9.16,) + library: + - net.logstash.logback:logstash-logback-encoder:3.0 + - org.slf4j:slf4j-api:2.0.0 + - ch.qos.logback:logback-classic:1.3.0 - name: logback-mdc-1.0 source_path: instrumentation/logback/logback-mdc-1.0 scope: @@ -1807,17 +1923,6 @@ libraries: library: - ch.qos.logback:logback-classic:1.0.0 - org.slf4j:slf4j-api:1.6.4 - - name: logback-appender-1.0 - source_path: instrumentation/logback/logback-appender-1.0 - scope: - name: io.opentelemetry.logback-appender-1.0 - target_versions: - javaagent: - - ch.qos.logback:logback-classic:[0.9.16,) - library: - - net.logstash.logback:logstash-logback-encoder:3.0 - - org.slf4j:slf4j-api:2.0.0 - - ch.qos.logback:logback-classic:1.3.0 micrometer: - name: micrometer-1.5 disabled_by_default: true @@ -1830,13 +1935,6 @@ libraries: library: - io.micrometer:micrometer-core:1.5.0 mongo: - - name: mongo-4.0 - source_path: instrumentation/mongo/mongo-4.0 - scope: - name: io.opentelemetry.mongo-4.0 - target_versions: - javaagent: - - org.mongodb:mongodb-driver-core:[4.0,) - name: mongo-3.1 source_path: instrumentation/mongo/mongo-3.1 scope: @@ -1854,6 +1952,13 @@ libraries: javaagent: - org.mongodb:mongodb-driver-core:[3.7, 4.0) - org.mongodb:mongo-java-driver:[3.7, 4.0) + - name: mongo-4.0 + source_path: instrumentation/mongo/mongo-4.0 + scope: + name: io.opentelemetry.mongo-4.0 + target_versions: + javaagent: + - org.mongodb:mongodb-driver-core:[4.0,) - name: mongo-async-3.3 source_path: instrumentation/mongo/mongo-async-3.3 scope: @@ -2008,15 +2113,13 @@ libraries: - name: url.scheme type: STRING okhttp: - - name: okhttp-3.0 - source_path: instrumentation/okhttp/okhttp-3.0 + - name: okhttp-2.2 + source_path: instrumentation/okhttp/okhttp-2.2 scope: - name: io.opentelemetry.okhttp-3.0 + name: io.opentelemetry.okhttp-2.2 target_versions: javaagent: - - com.squareup.okhttp3:okhttp:[3.0,) - library: - - com.squareup.okhttp3:okhttp:3.0.0 + - com.squareup.okhttp:okhttp:[2.2,3) telemetry: - when: default metrics: @@ -2035,13 +2138,15 @@ libraries: type: STRING - name: server.port type: LONG - - name: okhttp-2.2 - source_path: instrumentation/okhttp/okhttp-2.2 + - name: okhttp-3.0 + source_path: instrumentation/okhttp/okhttp-3.0 scope: - name: io.opentelemetry.okhttp-2.2 + name: io.opentelemetry.okhttp-3.0 target_versions: javaagent: - - com.squareup.okhttp:okhttp:[2.2,3) + - com.squareup.okhttp3:okhttp:[3.0,) + library: + - com.squareup.okhttp3:okhttp:3.0.0 telemetry: - when: default metrics: @@ -2061,14 +2166,6 @@ libraries: - name: server.port type: LONG opensearch: - - name: opensearch-rest-3.0 - source_path: instrumentation/opensearch/opensearch-rest-3.0 - minimum_java_version: 11 - scope: - name: io.opentelemetry.opensearch-rest-3.0 - target_versions: - javaagent: - - org.opensearch.client:opensearch-rest-client:[3.0,) - name: opensearch-rest-1.0 source_path: instrumentation/opensearch/opensearch-rest-1.0 minimum_java_version: 11 @@ -2077,6 +2174,14 @@ libraries: target_versions: javaagent: - org.opensearch.client:opensearch-rest-client:[1.0,3.0) + - name: opensearch-rest-3.0 + source_path: instrumentation/opensearch/opensearch-rest-3.0 + minimum_java_version: 11 + scope: + name: io.opentelemetry.opensearch-rest-3.0 + target_versions: + javaagent: + - org.opensearch.client:opensearch-rest-client:[3.0,) oracle: - name: oracle-ucp-11.2 description: The Oracle Universal Connection Pool (UCP) instrumentation generates @@ -2360,6 +2465,22 @@ libraries: - name: url.scheme type: STRING play: + - name: play-mvc-2.4 + source_path: instrumentation/play/play-mvc/play-mvc-2.4 + scope: + name: io.opentelemetry.play-mvc-2.4 + target_versions: + javaagent: + - com.typesafe.play:play_2.11:[2.4.0,2.6) + - name: play-mvc-2.6 + source_path: instrumentation/play/play-mvc/play-mvc-2.6 + scope: + name: io.opentelemetry.play-mvc-2.6 + target_versions: + javaagent: + - com.typesafe.play:play_$scalaVersion:[2.6.0,) + - com.typesafe.play:play_2.12:[2.6.0,) + - com.typesafe.play:play_2.13:[2.6.0,) - name: play-ws-1.0 source_path: instrumentation/play/play-ws/play-ws-1.0 scope: @@ -2384,22 +2505,6 @@ libraries: type: STRING - name: server.port type: LONG - - name: play-mvc-2.6 - source_path: instrumentation/play/play-mvc/play-mvc-2.6 - scope: - name: io.opentelemetry.play-mvc-2.6 - target_versions: - javaagent: - - com.typesafe.play:play_$scalaVersion:[2.6.0,) - - com.typesafe.play:play_2.12:[2.6.0,) - - com.typesafe.play:play_2.13:[2.6.0,) - - name: play-mvc-2.4 - source_path: instrumentation/play/play-mvc/play-mvc-2.4 - scope: - name: io.opentelemetry.play-mvc-2.4 - target_versions: - javaagent: - - com.typesafe.play:play_2.11:[2.4.0,2.6) - name: play-ws-2.0 source_path: instrumentation/play/play-ws/play-ws-2.0 scope: @@ -2519,13 +2624,6 @@ libraries: library: - io.ratpack:ratpack-core:1.7.0 reactor: - - name: reactor-kafka-1.0 - source_path: instrumentation/reactor/reactor-kafka-1.0 - scope: - name: io.opentelemetry.reactor-kafka-1.0 - target_versions: - javaagent: - - io.projectreactor.kafka:reactor-kafka:[1.0.0,) - name: reactor-3.1 source_path: instrumentation/reactor/reactor-3.1 scope: @@ -2540,6 +2638,13 @@ libraries: target_versions: javaagent: - io.projectreactor:reactor-core:[3.4.0,) + - name: reactor-kafka-1.0 + source_path: instrumentation/reactor/reactor-kafka-1.0 + scope: + name: io.opentelemetry.reactor-kafka-1.0 + target_versions: + javaagent: + - io.projectreactor.kafka:reactor-kafka:[1.0.0,) - name: reactor-netty-0.9 source_path: instrumentation/reactor/reactor-netty/reactor-netty-0.9 scope: @@ -2611,13 +2716,6 @@ libraries: - com.github.etaty:rediscala_2.13:[1.9.0,) - com.github.Ma27:rediscala_2.12:[1.8.1,) redisson: - - name: redisson-3.17 - source_path: instrumentation/redisson/redisson-3.17 - scope: - name: io.opentelemetry.redisson-3.17 - target_versions: - javaagent: - - org.redisson:redisson:[3.17.0,) - name: redisson-3.0 source_path: instrumentation/redisson/redisson-3.0 scope: @@ -2625,6 +2723,13 @@ libraries: target_versions: javaagent: - org.redisson:redisson:[3.0.0,3.17.0) + - name: redisson-3.17 + source_path: instrumentation/redisson/redisson-3.17 + scope: + name: io.opentelemetry.redisson-3.17 + target_versions: + javaagent: + - org.redisson:redisson:[3.17.0,) resources: - name: resources source_path: instrumentation/resources @@ -2659,13 +2764,6 @@ libraries: javaagent: - Java 8+ rocketmq: - - name: rocketmq-client-5.0 - source_path: instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0 - scope: - name: io.opentelemetry.rocketmq-client-5.0 - target_versions: - javaagent: - - org.apache.rocketmq:rocketmq-client-java:[5.0.0,) - name: rocketmq-client-4.8 source_path: instrumentation/rocketmq/rocketmq-client/rocketmq-client-4.8 scope: @@ -2675,6 +2773,13 @@ libraries: - org.apache.rocketmq:rocketmq-client:[4.0.0,) library: - org.apache.rocketmq:rocketmq-client:4.8.0 + - name: rocketmq-client-5.0 + source_path: instrumentation/rocketmq/rocketmq-client/rocketmq-client-5.0 + scope: + name: io.opentelemetry.rocketmq-client-5.0 + target_versions: + javaagent: + - org.apache.rocketmq:rocketmq-client-java:[5.0.0,) runtime: - name: runtime-telemetry-java17 source_path: instrumentation/runtime-telemetry/runtime-telemetry-java17 @@ -2693,15 +2798,6 @@ libraries: target_versions: library: - io.reactivex:rxjava:1.0.7 - - name: rxjava-3.1.1 - source_path: instrumentation/rxjava/rxjava-3.1.1 - scope: - name: io.opentelemetry.rxjava-3.1.1 - target_versions: - javaagent: - - io.reactivex.rxjava3:rxjava:[3.1.1,) - library: - - io.reactivex.rxjava3:rxjava:3.1.1 - name: rxjava-2.0 source_path: instrumentation/rxjava/rxjava-2.0 scope: @@ -2720,6 +2816,15 @@ libraries: - io.reactivex.rxjava3:rxjava:[3.0.0,3.1.0] library: - io.reactivex.rxjava3:rxjava:[3.0.12,3.1.0) + - name: rxjava-3.1.1 + source_path: instrumentation/rxjava/rxjava-3.1.1 + scope: + name: io.opentelemetry.rxjava-3.1.1 + target_versions: + javaagent: + - io.reactivex.rxjava3:rxjava:[3.1.1,) + library: + - io.reactivex.rxjava3:rxjava:3.1.1 scala: - name: scala-fork-join-2.8 source_path: instrumentation/scala-fork-join-2.8 @@ -2729,13 +2834,6 @@ libraries: javaagent: - org.scala-lang:scala-library:[2.8.0,2.12.0) servlet: - - name: servlet-5.0 - source_path: instrumentation/servlet/servlet-5.0 - scope: - name: io.opentelemetry.servlet-5.0 - target_versions: - javaagent: - - jakarta.servlet:jakarta.servlet-api:[5.0.0,) - name: servlet-2.2 source_path: instrumentation/servlet/servlet-2.2 scope: @@ -2750,6 +2848,13 @@ libraries: target_versions: javaagent: - javax.servlet:javax.servlet-api:[3.0,) + - name: servlet-5.0 + source_path: instrumentation/servlet/servlet-5.0 + scope: + name: io.opentelemetry.servlet-5.0 + target_versions: + javaagent: + - jakarta.servlet:jakarta.servlet-api:[5.0.0,) spark: - name: spark-2.3 source_path: instrumentation/spark-2.3 @@ -2759,32 +2864,26 @@ libraries: javaagent: - com.sparkjava:spark-core:[2.3,) spring: - - name: spring-rabbit-1.0 - source_path: instrumentation/spring/spring-rabbit-1.0 + - name: spring-batch-3.0 + disabled_by_default: true + source_path: instrumentation/spring/spring-batch-3.0 scope: - name: io.opentelemetry.spring-rabbit-1.0 + name: io.opentelemetry.spring-batch-3.0 target_versions: javaagent: - - org.springframework.amqp:spring-rabbit:(,) - - name: spring-scheduling-3.1 - source_path: instrumentation/spring/spring-scheduling-3.1 + - org.springframework.batch:spring-batch-core:[3.0.0.RELEASE,5) + - name: spring-boot-actuator-autoconfigure-2.0 + disabled_by_default: true + source_path: instrumentation/spring/spring-boot-actuator-autoconfigure-2.0 scope: - name: io.opentelemetry.spring-scheduling-3.1 + name: io.opentelemetry.spring-boot-actuator-autoconfigure-2.0 target_versions: javaagent: - - org.springframework:spring-context:[3.1.0.RELEASE,] + - org.springframework.boot:spring-boot-actuator-autoconfigure:[2.0.0.RELEASE,) - name: spring-boot-resources source_path: instrumentation/spring/spring-boot-resources scope: name: io.opentelemetry.spring-boot-resources - - name: spring-batch-3.0 - disabled_by_default: true - source_path: instrumentation/spring/spring-batch-3.0 - scope: - name: io.opentelemetry.spring-batch-3.0 - target_versions: - javaagent: - - org.springframework.batch:spring-batch-core:[3.0.0.RELEASE,5) - name: spring-cloud-aws-3.0 source_path: instrumentation/spring/spring-cloud-aws-3.0 minimum_java_version: 17 @@ -2793,99 +2892,60 @@ libraries: target_versions: javaagent: - io.awspring.cloud:spring-cloud-aws-sqs:[3.0.0,) - - name: spring-webflux-5.0 - source_path: instrumentation/spring/spring-webflux/spring-webflux-5.0 + - name: spring-cloud-gateway-2.0 + source_path: instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0 scope: - name: io.opentelemetry.spring-webflux-5.0 + name: io.opentelemetry.spring-cloud-gateway-2.0 target_versions: javaagent: - - io.projectreactor.ipc:reactor-netty:[0.7.0.RELEASE,) - - org.springframework:spring-webflux:[5.0.0.RELEASE,) - - io.projectreactor.netty:reactor-netty:[0.8.0.RELEASE,) - telemetry: - - when: default - metrics: - - name: http.client.request.duration - description: Duration of HTTP client requests. - type: HISTOGRAM - unit: s - attributes: - - name: http.request.method - type: STRING - - name: http.response.status_code - type: LONG - - name: server.address - type: STRING - - name: server.port - type: LONG - - name: http.server.request.duration - description: Duration of HTTP server requests. - type: HISTOGRAM - unit: s - attributes: - - name: http.request.method - type: STRING - - name: http.response.status_code - type: LONG - - name: http.route - type: STRING - - name: network.protocol.version - type: STRING - - name: url.scheme - type: STRING - - name: spring-webflux-5.3 - source_path: instrumentation/spring/spring-webflux/spring-webflux-5.3 - scope: - name: io.opentelemetry.spring-webflux-5.3 - target_versions: - library: - - org.springframework:spring-webflux:5.3.0 - - name: spring-jms-6.0 - source_path: instrumentation/spring/spring-jms/spring-jms-6.0 + - org.springframework.cloud:spring-cloud-starter-gateway:[2.0.0.RELEASE,] + - name: spring-core-2.0 + source_path: instrumentation/spring/spring-core-2.0 minimum_java_version: 17 scope: - name: io.opentelemetry.spring-jms-6.0 + name: io.opentelemetry.spring-core-2.0 target_versions: javaagent: - - org.springframework:spring-jms:[6.0.0,) - - name: spring-boot-actuator-autoconfigure-2.0 - disabled_by_default: true - source_path: instrumentation/spring/spring-boot-actuator-autoconfigure-2.0 + - org.springframework:spring-core:[2.0,] + - name: spring-data-1.8 + source_path: instrumentation/spring/spring-data/spring-data-1.8 scope: - name: io.opentelemetry.spring-boot-actuator-autoconfigure-2.0 + name: io.opentelemetry.spring-data-1.8 target_versions: javaagent: - - org.springframework.boot:spring-boot-actuator-autoconfigure:[2.0.0.RELEASE,) - - name: spring-rmi-4.0 - source_path: instrumentation/spring/spring-rmi-4.0 + - org.springframework:spring-aop:[1.2,] + - org.springframework.data:spring-data-commons:[1.8.0.RELEASE,] + - name: spring-integration-4.1 + source_path: instrumentation/spring/spring-integration-4.1 scope: - name: io.opentelemetry.spring-rmi-4.0 + name: io.opentelemetry.spring-integration-4.1 target_versions: javaagent: - - org.springframework:spring-context:[4.0.0.RELEASE,6) - - name: spring-webmvc-3.1 - source_path: instrumentation/spring/spring-webmvc/spring-webmvc-3.1 + - org.springframework.integration:spring-integration-core:[4.1.0.RELEASE,) + library: + - org.springframework.integration:spring-integration-core:[4.1.0.RELEASE,5.+) + - name: spring-jms-2.0 + source_path: instrumentation/spring/spring-jms/spring-jms-2.0 scope: - name: io.opentelemetry.spring-webmvc-3.1 + name: io.opentelemetry.spring-jms-2.0 target_versions: javaagent: - - org.springframework:spring-webmvc:[3.1.0.RELEASE,6) - - name: spring-webmvc-6.0 - source_path: instrumentation/spring/spring-webmvc/spring-webmvc-6.0 + - org.springframework:spring-jms:[2.0,6) + - name: spring-jms-6.0 + source_path: instrumentation/spring/spring-jms/spring-jms-6.0 minimum_java_version: 17 scope: - name: io.opentelemetry.spring-webmvc-6.0 + name: io.opentelemetry.spring-jms-6.0 target_versions: javaagent: - - org.springframework:spring-webmvc:[6.0.0,) - - name: spring-data-1.8 - source_path: instrumentation/spring/spring-data/spring-data-1.8 + - org.springframework:spring-jms:[6.0.0,) + - name: spring-kafka-2.7 + source_path: instrumentation/spring/spring-kafka-2.7 scope: - name: io.opentelemetry.spring-data-1.8 + name: io.opentelemetry.spring-kafka-2.7 target_versions: javaagent: - - org.springframework:spring-aop:[1.2,] - - org.springframework.data:spring-data-commons:[1.8.0.RELEASE,] + - org.springframework.kafka:spring-kafka:[2.7.0,) - name: spring-pulsar-1.0 source_path: instrumentation/spring/spring-pulsar-1.0 minimum_java_version: 17 @@ -2894,39 +2954,27 @@ libraries: target_versions: javaagent: - org.springframework.pulsar:spring-pulsar:[1.0.0,) - - name: spring-web-3.1 - source_path: instrumentation/spring/spring-web/spring-web-3.1 - scope: - name: io.opentelemetry.spring-web-3.1 - target_versions: - javaagent: - - org.springframework:spring-web:[3.1.0.RELEASE,6) - - name: spring-kafka-2.7 - source_path: instrumentation/spring/spring-kafka-2.7 + - name: spring-rabbit-1.0 + source_path: instrumentation/spring/spring-rabbit-1.0 scope: - name: io.opentelemetry.spring-kafka-2.7 + name: io.opentelemetry.spring-rabbit-1.0 target_versions: javaagent: - - org.springframework.kafka:spring-kafka:[2.7.0,) - - name: spring-webmvc-5.3 - source_path: instrumentation/spring/spring-webmvc/spring-webmvc-5.3 - scope: - name: io.opentelemetry.spring-webmvc-5.3 - - name: spring-core-2.0 - source_path: instrumentation/spring/spring-core-2.0 - minimum_java_version: 17 + - org.springframework.amqp:spring-rabbit:(,) + - name: spring-rmi-4.0 + source_path: instrumentation/spring/spring-rmi-4.0 scope: - name: io.opentelemetry.spring-core-2.0 + name: io.opentelemetry.spring-rmi-4.0 target_versions: javaagent: - - org.springframework:spring-core:[2.0,] - - name: spring-cloud-gateway-2.0 - source_path: instrumentation/spring/spring-cloud-gateway/spring-cloud-gateway-2.0 + - org.springframework:spring-context:[4.0.0.RELEASE,6) + - name: spring-scheduling-3.1 + source_path: instrumentation/spring/spring-scheduling-3.1 scope: - name: io.opentelemetry.spring-cloud-gateway-2.0 + name: io.opentelemetry.spring-scheduling-3.1 target_versions: javaagent: - - org.springframework.cloud:spring-cloud-starter-gateway:[2.0.0.RELEASE,] + - org.springframework:spring-context:[3.1.0.RELEASE,] - name: spring-security-config-6.0 source_path: instrumentation/spring/spring-security-config-6.0 minimum_java_version: 17 @@ -2941,22 +2989,86 @@ libraries: - org.springframework:spring-web:6.0.0 - jakarta.servlet:jakarta.servlet-api:6.0.0 - org.springframework.security:spring-security-web:6.0.0 - - name: spring-integration-4.1 - source_path: instrumentation/spring/spring-integration-4.1 + - name: spring-web-3.1 + source_path: instrumentation/spring/spring-web/spring-web-3.1 scope: - name: io.opentelemetry.spring-integration-4.1 + name: io.opentelemetry.spring-web-3.1 target_versions: javaagent: - - org.springframework.integration:spring-integration-core:[4.1.0.RELEASE,) + - org.springframework:spring-web:[3.1.0.RELEASE,6) + - name: spring-web-6.0 + source_path: instrumentation/spring/spring-web/spring-web-6.0 + scope: + name: io.opentelemetry.spring-web-6.0 + target_versions: + javaagent: + - org.springframework:spring-web:[6.0.0,) + - name: spring-webflux-5.0 + source_path: instrumentation/spring/spring-webflux/spring-webflux-5.0 + scope: + name: io.opentelemetry.spring-webflux-5.0 + target_versions: + javaagent: + - io.projectreactor.ipc:reactor-netty:[0.7.0.RELEASE,) + - org.springframework:spring-webflux:[5.0.0.RELEASE,) + - io.projectreactor.netty:reactor-netty:[0.8.0.RELEASE,) + telemetry: + - when: default + metrics: + - name: http.client.request.duration + description: Duration of HTTP client requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: server.address + type: STRING + - name: server.port + type: LONG + - name: http.server.request.duration + description: Duration of HTTP server requests. + type: HISTOGRAM + unit: s + attributes: + - name: http.request.method + type: STRING + - name: http.response.status_code + type: LONG + - name: http.route + type: STRING + - name: network.protocol.version + type: STRING + - name: url.scheme + type: STRING + - name: spring-webflux-5.3 + source_path: instrumentation/spring/spring-webflux/spring-webflux-5.3 + scope: + name: io.opentelemetry.spring-webflux-5.3 + target_versions: library: - - org.springframework.integration:spring-integration-core:[4.1.0.RELEASE,5.+) - - name: spring-jms-2.0 - source_path: instrumentation/spring/spring-jms/spring-jms-2.0 + - org.springframework:spring-webflux:5.3.0 + - name: spring-webmvc-3.1 + source_path: instrumentation/spring/spring-webmvc/spring-webmvc-3.1 scope: - name: io.opentelemetry.spring-jms-2.0 + name: io.opentelemetry.spring-webmvc-3.1 target_versions: javaagent: - - org.springframework:spring-jms:[2.0,6) + - org.springframework:spring-webmvc:[3.1.0.RELEASE,6) + - name: spring-webmvc-5.3 + source_path: instrumentation/spring/spring-webmvc/spring-webmvc-5.3 + scope: + name: io.opentelemetry.spring-webmvc-5.3 + - name: spring-webmvc-6.0 + source_path: instrumentation/spring/spring-webmvc/spring-webmvc-6.0 + minimum_java_version: 17 + scope: + name: io.opentelemetry.spring-webmvc-6.0 + target_versions: + javaagent: + - org.springframework:spring-webmvc:[6.0.0,) - name: spring-ws-2.0 disabled_by_default: true source_path: instrumentation/spring/spring-ws-2.0 @@ -2965,13 +3077,6 @@ libraries: target_versions: javaagent: - org.springframework.ws:spring-ws-core:[2.0.0.RELEASE,] - - name: spring-web-6.0 - source_path: instrumentation/spring/spring-web/spring-web-6.0 - scope: - name: io.opentelemetry.spring-web-6.0 - target_versions: - javaagent: - - org.springframework:spring-web:[6.0.0,) spymemcached: - name: spymemcached-2.12 source_path: instrumentation/spymemcached-2.12 @@ -3135,63 +3240,49 @@ libraries: - com.vaadin:flow-server:[2.2.0,3) - com.vaadin:flow-server:[3.1.0,) vertx: - - name: vertx-kafka-client-3.6 - source_path: instrumentation/vertx/vertx-kafka-client-3.6 - scope: - name: io.opentelemetry.vertx-kafka-client-3.6 - target_versions: - javaagent: - - io.vertx:vertx-kafka-client:[3.5.1,) - - name: vertx-redis-client-4.0 - source_path: instrumentation/vertx/vertx-redis-client-4.0 + - name: vertx-http-client-3.0 + source_path: instrumentation/vertx/vertx-http-client/vertx-http-client-3.0 scope: - name: io.opentelemetry.vertx-redis-client-4.0 + name: io.opentelemetry.vertx-http-client-3.0 target_versions: javaagent: - - io.vertx:vertx-redis-client:[4.0.0,) + - io.vertx:vertx-core:[3.0.0,4.0.0) telemetry: - - when: otel.semconv-stability.opt-in=database + - when: default metrics: - - name: db.client.operation.duration - description: Duration of database client operations. + - name: http.client.request.duration + description: Duration of HTTP client requests. type: HISTOGRAM unit: s attributes: - - name: db.namespace - type: STRING - - name: db.operation.name - type: STRING - - name: db.system.name - type: STRING - - name: network.peer.address + - name: http.request.method type: STRING - - name: network.peer.port + - name: http.response.status_code type: LONG - name: server.address type: STRING - name: server.port type: LONG - - name: vertx-sql-client-5.0 - source_path: instrumentation/vertx/vertx-sql-client/vertx-sql-client-5.0 - minimum_java_version: 11 + - name: vertx-http-client-4.0 + source_path: instrumentation/vertx/vertx-http-client/vertx-http-client-4.0 scope: - name: io.opentelemetry.vertx-sql-client-5.0 + name: io.opentelemetry.vertx-http-client-4.0 target_versions: javaagent: - - io.vertx:vertx-sql-client:[5.0.0,) + - io.vertx:vertx-core:[4.0.0,5) telemetry: - - when: otel.semconv-stability.opt-in=database + - when: default metrics: - - name: db.client.operation.duration - description: Duration of database client operations. + - name: http.client.request.duration + description: Duration of HTTP client requests. type: HISTOGRAM unit: s attributes: - - name: db.collection.name - type: STRING - - name: db.namespace + - name: http.request.method type: STRING - - name: db.operation.name + - name: http.response.status_code + type: LONG + - name: network.protocol.version type: STRING - name: server.address type: STRING @@ -3223,20 +3314,20 @@ libraries: type: STRING - name: server.port type: LONG - - name: vertx-web-3.0 - source_path: instrumentation/vertx/vertx-web-3.0 + - name: vertx-kafka-client-3.6 + source_path: instrumentation/vertx/vertx-kafka-client-3.6 scope: - name: io.opentelemetry.vertx-web-3.0 + name: io.opentelemetry.vertx-kafka-client-3.6 target_versions: javaagent: - - io.vertx:vertx-web:[3.0.0,) - - name: vertx-sql-client-4.0 - source_path: instrumentation/vertx/vertx-sql-client/vertx-sql-client-4.0 + - io.vertx:vertx-kafka-client:[3.5.1,) + - name: vertx-redis-client-4.0 + source_path: instrumentation/vertx/vertx-redis-client-4.0 scope: - name: io.opentelemetry.vertx-sql-client-4.0 + name: io.opentelemetry.vertx-redis-client-4.0 target_versions: javaagent: - - io.vertx:vertx-sql-client:[4.0.0,5) + - io.vertx:vertx-redis-client:[4.0.0,) telemetry: - when: otel.semconv-stability.opt-in=database metrics: @@ -3245,71 +3336,85 @@ libraries: type: HISTOGRAM unit: s attributes: - - name: db.collection.name - type: STRING - name: db.namespace type: STRING - name: db.operation.name type: STRING + - name: db.system.name + type: STRING + - name: network.peer.address + type: STRING + - name: network.peer.port + type: LONG - name: server.address type: STRING - name: server.port type: LONG - - name: vertx-http-client-4.0 - source_path: instrumentation/vertx/vertx-http-client/vertx-http-client-4.0 + - name: vertx-rx-java-3.5 + source_path: instrumentation/vertx/vertx-rx-java-3.5 scope: - name: io.opentelemetry.vertx-http-client-4.0 + name: io.opentelemetry.vertx-rx-java-3.5 target_versions: javaagent: - - io.vertx:vertx-core:[4.0.0,5) + - io.vertx:vertx-rx-java2:[3.5.0,) + - name: vertx-sql-client-4.0 + source_path: instrumentation/vertx/vertx-sql-client/vertx-sql-client-4.0 + scope: + name: io.opentelemetry.vertx-sql-client-4.0 + target_versions: + javaagent: + - io.vertx:vertx-sql-client:[4.0.0,5) telemetry: - - when: default + - when: otel.semconv-stability.opt-in=database metrics: - - name: http.client.request.duration - description: Duration of HTTP client requests. + - name: db.client.operation.duration + description: Duration of database client operations. type: HISTOGRAM unit: s attributes: - - name: http.request.method + - name: db.collection.name type: STRING - - name: http.response.status_code - type: LONG - - name: network.protocol.version + - name: db.namespace + type: STRING + - name: db.operation.name type: STRING - name: server.address type: STRING - name: server.port type: LONG - - name: vertx-rx-java-3.5 - source_path: instrumentation/vertx/vertx-rx-java-3.5 - scope: - name: io.opentelemetry.vertx-rx-java-3.5 - target_versions: - javaagent: - - io.vertx:vertx-rx-java2:[3.5.0,) - - name: vertx-http-client-3.0 - source_path: instrumentation/vertx/vertx-http-client/vertx-http-client-3.0 + - name: vertx-sql-client-5.0 + source_path: instrumentation/vertx/vertx-sql-client/vertx-sql-client-5.0 + minimum_java_version: 11 scope: - name: io.opentelemetry.vertx-http-client-3.0 + name: io.opentelemetry.vertx-sql-client-5.0 target_versions: javaagent: - - io.vertx:vertx-core:[3.0.0,4.0.0) + - io.vertx:vertx-sql-client:[5.0.0,) telemetry: - - when: default + - when: otel.semconv-stability.opt-in=database metrics: - - name: http.client.request.duration - description: Duration of HTTP client requests. + - name: db.client.operation.duration + description: Duration of database client operations. type: HISTOGRAM unit: s attributes: - - name: http.request.method + - name: db.collection.name + type: STRING + - name: db.namespace + type: STRING + - name: db.operation.name type: STRING - - name: http.response.status_code - type: LONG - name: server.address type: STRING - name: server.port type: LONG + - name: vertx-web-3.0 + source_path: instrumentation/vertx/vertx-web-3.0 + scope: + name: io.opentelemetry.vertx-web-3.0 + target_versions: + javaagent: + - io.vertx:vertx-web:[3.0.0,) vibur: - name: vibur-dbcp-11.0 description: Instrumentation for the vibur-dbcp library, which provides connection @@ -3370,13 +3475,13 @@ libraries: javaagent: - org.apache.wicket:wicket:[8.0.0,] xxl: - - name: xxl-job-2.3.0 - source_path: instrumentation/xxl-job/xxl-job-2.3.0 + - name: xxl-job-1.9.2 + source_path: instrumentation/xxl-job/xxl-job-1.9.2 scope: - name: io.opentelemetry.xxl-job-2.3.0 + name: io.opentelemetry.xxl-job-1.9.2 target_versions: javaagent: - - com.xuxueli:xxl-job-core:[2.3.0,) + - com.xuxueli:xxl-job-core:[1.9.2, 2.1.2) - name: xxl-job-2.1.2 source_path: instrumentation/xxl-job/xxl-job-2.1.2 scope: @@ -3384,13 +3489,13 @@ libraries: target_versions: javaagent: - com.xuxueli:xxl-job-core:[2.1.2,2.3.0) - - name: xxl-job-1.9.2 - source_path: instrumentation/xxl-job/xxl-job-1.9.2 + - name: xxl-job-2.3.0 + source_path: instrumentation/xxl-job/xxl-job-2.3.0 scope: - name: io.opentelemetry.xxl-job-1.9.2 + name: io.opentelemetry.xxl-job-2.3.0 target_versions: javaagent: - - com.xuxueli:xxl-job-core:[1.9.2, 2.1.2) + - com.xuxueli:xxl-job-core:[2.3.0,) zio: - name: zio-2.0 source_path: instrumentation/zio/zio-2.0 diff --git a/instrumentation-docs/collect.sh b/instrumentation-docs/collect.sh index df8bf9e0d47d..dea252b28f84 100755 --- a/instrumentation-docs/collect.sh +++ b/instrumentation-docs/collect.sh @@ -21,6 +21,7 @@ readonly INSTRUMENTATIONS=( "apache-dbcp-2.0:javaagent:test" "apache-dbcp-2.0:javaagent:testStableSemconv" "apache-httpclient:apache-httpclient-5.0:javaagent:test" + "apache-dubbo-2.7:javaagent:testDubbo" "c3p0-0.9:javaagent:test" "c3p0-0.9:javaagent:testStableSemconv" "clickhouse-client-0.5:javaagent:test" diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java index 8f1de8aa617b..190817ce6e1d 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/internal/TelemetryAttribute.java @@ -5,6 +5,8 @@ package io.opentelemetry.instrumentation.docs.internal; +import java.util.Objects; + /** * This class is internal and is hence not for public use. Its APIs are unstable and can change at * any time. @@ -38,4 +40,22 @@ public String getType() { public void setType(String type) { this.type = type; } + + // Overriding equals and hashCode to compare TelemetryAttribute objects based on name and type + // This is important for our deduplication logic in the documentation generation process. + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TelemetryAttribute that)) { + return false; + } + return Objects.equals(name, that.name) && Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(name, type); + } } 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 ccb24b45911b..02f8a8d636f2 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 @@ -23,6 +23,9 @@ */ public class SpanParser { + // We want to ignore test related attributes + private static final List EXCLUDED_ATTRIBUTES = List.of("x-test-", "test-baggage-"); + /** * Pull spans from the `.telemetry` directory, filter them by scope, and set them in the module. * @@ -107,7 +110,10 @@ private static void addSpanAttributes( } for (TelemetryAttribute attr : span.getAttributes()) { - attributes.add(new TelemetryAttribute(attr.getName(), attr.getType())); + boolean excluded = EXCLUDED_ATTRIBUTES.stream().anyMatch(ex -> attr.getName().contains(ex)); + if (!excluded) { + attributes.add(new TelemetryAttribute(attr.getName(), attr.getType())); + } } } diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java index f2d5bce3b1cb..73a8b40c2c62 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/utils/YamlHelper.java @@ -77,7 +77,16 @@ private static Map getLibraryInstrumentations(List + modules.stream() + .sorted( + Comparator.comparing( + InstrumentationModule::getInstrumentationName)) + .collect(Collectors.toList())))); Map output = new TreeMap<>(); libraryInstrumentations.forEach( @@ -146,7 +155,7 @@ private static Map baseProperties(InstrumentationModule module) new ArrayList<>(module.getMetrics().getOrDefault(group, Collections.emptyList())); List> metricsList = new ArrayList<>(); - // sort metrics by name for some determinism in the order + // sort by name for determinism in the order metrics.sort(Comparator.comparing(EmittedMetrics.Metric::getName)); for (EmittedMetrics.Metric metric : metrics) { @@ -160,7 +169,7 @@ private static Map baseProperties(InstrumentationModule module) new ArrayList<>(module.getSpans().getOrDefault(group, Collections.emptyList())); List> spanList = new ArrayList<>(); - // sort metrics by name for some determinism in the order + // sort by name for determinism in the order spans.sort(Comparator.comparing(EmittedSpans.Span::getSpanKind)); for (EmittedSpans.Span span : spans) { diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java index 37dfebfe36e5..115cc8e29297 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/InstrumentationAnalyzerTest.java @@ -78,14 +78,14 @@ void testModuleConverterCreatesUniqueModules() { new InstrumentationPath( "same-name", "instrumentation/test1/same-name/library", - "group1", "namespace1", + "group1", InstrumentationType.LIBRARY), new InstrumentationPath( "same-name", "instrumentation/test2/same-name/library", - "group2", "namespace2", + "group2", InstrumentationType.LIBRARY)); List modules = ModuleParser.convertToModules("test", paths); @@ -93,7 +93,6 @@ void testModuleConverterCreatesUniqueModules() { // Should create 2 separate modules because they have different group/namespace combinations assertThat(modules.size()).isEqualTo(2); - // Verify both modules exist with correct properties assertThat( modules.stream() .anyMatch( diff --git a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/ModuleConverterTest.java b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/ModuleConverterTest.java index 197293e047c5..08203929793a 100644 --- a/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/ModuleConverterTest.java +++ b/instrumentation-docs/src/test/java/io/opentelemetry/instrumentation/docs/ModuleConverterTest.java @@ -58,18 +58,4 @@ void testSanitizePathNameRemovesRootAndKnownFolders() throws Exception { sanitized = ModuleParser.sanitizePathName("/root", "/root/other"); assertThat(sanitized).isEqualTo("/other"); } - - @Test - void testCreateModuleKeyIsConsistent() throws Exception { - InstrumentationPath path = mock(InstrumentationPath.class); - when(path.group()).thenReturn("g"); - when(path.namespace()).thenReturn("n"); - when(path.instrumentationName()).thenReturn("i"); - - var method = ModuleParser.class.getDeclaredMethod("createModuleKey", InstrumentationPath.class); - method.setAccessible(true); - - String key = (String) method.invoke(null, path); - assertThat(key).isEqualTo("g:n:i"); - } } 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 0a25ddf7b753..c050af7a9ba2 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 @@ -72,6 +72,8 @@ void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOExc Files.writeString(telemetryDir.resolve("spans-1.yaml"), file1Content); Files.writeString(telemetryDir.resolve("spans-2.yaml"), file2Content); + // duplicate span contents to test deduplication + Files.writeString(telemetryDir.resolve("spans-3.yaml"), file2Content); try (MockedStatic fileManagerMock = mockStatic(FileManager.class)) { fileManagerMock @@ -80,6 +82,9 @@ void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOExc fileManagerMock .when(() -> FileManager.readFileToString(telemetryDir.resolve("spans-2.yaml").toString())) .thenReturn(file2Content); + fileManagerMock + .when(() -> FileManager.readFileToString(telemetryDir.resolve("spans-3.yaml").toString())) + .thenReturn(file2Content); Map result = EmittedSpanParser.getSpansByScopeFromFiles(tempDir.toString(), ""); @@ -95,6 +100,20 @@ void getSpansFromFilesCombinesFilesCorrectly(@TempDir Path tempDir) throws IOExc .orElse(null); assertThat(clickHouseSpans).hasSize(2); + EmittedSpans.Span serverSpan = + clickHouseSpans.stream() + .filter(item -> item.getSpanKind().equals("SERVER")) + .findFirst() + .orElse(null); + EmittedSpans.Span clientSpan = + clickHouseSpans.stream() + .filter(item -> item.getSpanKind().equals("CLIENT")) + .findFirst() + .orElse(null); + + assertThat(serverSpan.getAttributes()).hasSize(1); + assertThat(clientSpan.getAttributes()).hasSize(6); + List testSpans = spans.getSpansByScope().stream() .filter(item -> item.getScope().equals("test")) @@ -111,9 +130,19 @@ void testSpanAggregatorFiltersAndAggregatesCorrectly() { String targetScopeName = "my-instrumentation-scope"; EmittedSpans.Span span1 = - new EmittedSpans.Span("CLIENT", List.of(new TelemetryAttribute("my.operation", "STRING"))); + new EmittedSpans.Span( + "CLIENT", + List.of( + new TelemetryAttribute("my.operation", "STRING"), + new TelemetryAttribute("http.request.header.x-test-request", "STRING_ARRAY"), + new TelemetryAttribute("http.response.header.x-test-response", "STRING_ARRAY"))); EmittedSpans.Span span2 = - new EmittedSpans.Span("SERVER", List.of(new TelemetryAttribute("my.operation", "STRING"))); + new EmittedSpans.Span( + "SERVER", + List.of( + new TelemetryAttribute("my.operation", "STRING"), + new TelemetryAttribute("test-baggage-key-1", "STRING"), + new TelemetryAttribute("test-baggage-key-2", "STRING"))); // Create test span for a different scope (should be filtered out) EmittedSpans.Span testSpan = @@ -143,5 +172,27 @@ void testSpanAggregatorFiltersAndAggregatesCorrectly() { List spanKinds = result.get("default").stream().map(EmittedSpans.Span::getSpanKind).toList(); assertThat(spanKinds).containsExactlyInAnyOrder("CLIENT", "SERVER"); + + // Verify test attributes are filtered out + List clientAttributes = + result.get("default").stream() + .filter(span -> span.getSpanKind().equals("CLIENT")) + .flatMap(span -> span.getAttributes().stream()) + .toList(); + assertThat(clientAttributes) + .hasSize(1) + .extracting(TelemetryAttribute::getName) + .containsExactly("my.operation"); + + List serverAttributes = + result.get("default").stream() + .filter(span -> span.getSpanKind().equals("SERVER")) + .flatMap(span -> span.getAttributes().stream()) + .toList(); + + assertThat(serverAttributes) + .hasSize(1) + .extracting(TelemetryAttribute::getName) + .containsExactly("my.operation"); } } diff --git a/instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts b/instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts index b29d53d34cd7..6a4ab4e1d5e3 100644 --- a/instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts +++ b/instrumentation/akka/akka-http-10.0/javaagent/build.gradle.kts @@ -66,6 +66,7 @@ tasks { systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean) systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") + systemProperty("collectSpans", true) } check { diff --git a/instrumentation/apache-dubbo-2.7/javaagent/build.gradle.kts b/instrumentation/apache-dubbo-2.7/javaagent/build.gradle.kts index f5187e6fdaf4..d9884c998684 100644 --- a/instrumentation/apache-dubbo-2.7/javaagent/build.gradle.kts +++ b/instrumentation/apache-dubbo-2.7/javaagent/build.gradle.kts @@ -45,6 +45,9 @@ tasks.withType().configureEach { jvmArgs("--add-opens=java.base/java.math=ALL-UNNAMED") // required on jdk17 jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + + systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false") + systemProperty("collectSpans", true) } tasks { diff --git a/instrumentation/clickhouse-client-0.5/javaagent/build.gradle.kts b/instrumentation/clickhouse-client-0.5/javaagent/build.gradle.kts index fc5d940a534e..f61f1b46296f 100644 --- a/instrumentation/clickhouse-client-0.5/javaagent/build.gradle.kts +++ b/instrumentation/clickhouse-client-0.5/javaagent/build.gradle.kts @@ -28,12 +28,15 @@ tasks { test { usesService(gradle.sharedServices.registrations["testcontainersBuildService"].service) systemProperty("collectMetadata", collectMetadata) + systemProperty("collectSpans", true) } val testStableSemconv by registering(Test::class) { jvmArgs("-Dotel.semconv-stability.opt-in=database") - systemProperty("collectMetadata", collectMetadata) + systemProperty("metaDataConfig", "otel.semconv-stability.opt-in=database") + systemProperty("collectMetadata", collectMetadata) + systemProperty("collectSpans", true) } check { diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java index c03bf0d9621d..f608ec9adc24 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/AgentTestRunner.java @@ -66,19 +66,18 @@ public void afterTestClass() throws IOException { // Generates files in a `.telemetry` directory within the instrumentation module with all // captured emitted metadata to be used by the instrumentation-docs Doc generator. - // if (Boolean.getBoolean("collectMetadata")) { - URL resource = this.getClass().getClassLoader().getResource(""); - if (resource == null) { - return; + if (Boolean.getBoolean("collectMetadata")) { + URL resource = this.getClass().getClassLoader().getResource(""); + if (resource == null) { + return; + } + String path = Paths.get(resource.getPath()).toString(); + + MetaDataCollector.writeTelemetryToFiles(path, metrics, tracesByScope); } - String path = Paths.get(resource.getPath()).toString(); - - MetaDataCollector.writeTelemetryToFiles(path, metrics, tracesByScope); - // } // additional library ignores are ignored during tests, because they can make it really - // confusing for contributors wondering why their instrumentation is not applied - // + // confusing for contributors wondering why their instrumentation is not applied, // but we then need to make sure that the additional library ignores won't then silently prevent // the instrumentation from being applied in real life outside of these tests assert TestAgentListenerAccess.getIgnoredButTransformedClassNames().isEmpty() diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java index bfe5b254706f..862b5514d2e0 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java @@ -153,9 +153,9 @@ private > void doAssertTraces( } TracesAssert.assertThat(traces).hasTracesSatisfyingExactly(assertionsList); - // if (Boolean.getBoolean("collectMetadata")) { - collectEmittedSpans(traces); - // } + if (Boolean.getBoolean("collectMetadata") && Boolean.getBoolean("collectSpans")) { + collectEmittedSpans(traces); + } } /** From 4a2cbf831cff5f44ecc4693e52d0c93b16496881 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 18 Jun 2025 17:49:53 -0400 Subject: [PATCH 5/5] fix naming --- .../docs/parsers/EmittedSpanParser.java | 31 +++++++++---------- .../testing/InstrumentationTestRunner.java | 2 -- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/EmittedSpanParser.java b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/EmittedSpanParser.java index ec95c3949b5c..fd01ba6dbe7a 100644 --- a/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/EmittedSpanParser.java +++ b/instrumentation-docs/src/main/java/io/opentelemetry/instrumentation/docs/parsers/EmittedSpanParser.java @@ -54,12 +54,12 @@ public static Map getSpansByScopeFromFiles( spansByScope.putIfAbsent(whenKey, new StringBuilder("spans_by_scope:\n")); - // Skip the metric spans_by_scope ("metrics:") so we can aggregate into one list - int metricsIndex = content.indexOf("spans_by_scope:\n"); - if (metricsIndex != -1) { - String contentAfterMetrics = - content.substring(metricsIndex + "spans_by_scope:\n".length()); - spansByScope.get(whenKey).append(contentAfterMetrics); + // Skip the spans_by_scope line so we can aggregate into one list + int spanIndex = content.indexOf("spans_by_scope:\n"); + if (spanIndex != -1) { + String contentAfter = + content.substring(spanIndex + "spans_by_scope:\n".length()); + spansByScope.get(whenKey).append(contentAfter); } } }); @@ -76,11 +76,10 @@ public static Map getSpansByScopeFromFiles( * `when`, indicating the conditions under which the telemetry is emitted. deduplicates by name * and then returns a new map. * - * @param input raw string representation of EmittedMetrics yaml - * @return {@code Map} where the key is the `when` condition + * @param input raw string representation of EmittedSpans yaml + * @return {@code Map} where the key is the `when` condition */ - // visible for testing - public static Map parseSpans(Map input) + private static Map parseSpans(Map input) throws JsonProcessingException { Map result = new HashMap<>(); @@ -139,7 +138,8 @@ private static EmittedSpans getEmittedSpans( for (Map.Entry>> scopeEntry : attributesByScopeAndSpanKind.entrySet()) { String scope = scopeEntry.getKey(); - EmittedSpans.SpansByScope deduplicatedScope = getSpansByScope(scopeEntry, scope); + Map> spanKindMap = scopeEntry.getValue(); + EmittedSpans.SpansByScope deduplicatedScope = getSpansByScope(scope, spanKindMap); deduplicatedSpansByScope.add(deduplicatedScope); } @@ -147,16 +147,15 @@ private static EmittedSpans getEmittedSpans( } /** - * Converts a map entry of scope and its attributes into an {@link EmittedSpans.SpansByScope} - * object. + * Converts a map of attributes by spanKind into an {@link EmittedSpans.SpansByScope} object. + * Deduplicates spans by their kind and collects their attributes. * - * @param scopeEntry the map entry containing scope and its attributes * @param scope the name of the scope + * @param spanKindMap a map where the key is the span kind and the value is set of attributes * @return an {@link EmittedSpans.SpansByScope} object with deduplicated spans */ private static EmittedSpans.SpansByScope getSpansByScope( - Map.Entry>> scopeEntry, String scope) { - Map> spanKindMap = scopeEntry.getValue(); + String scope, Map> spanKindMap) { List deduplicatedSpans = new ArrayList<>(); diff --git a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java index 862b5514d2e0..03479afb38db 100644 --- a/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java +++ b/testing-common/src/main/java/io/opentelemetry/instrumentation/testing/InstrumentationTestRunner.java @@ -214,8 +214,6 @@ private void collectEmittedMetrics(List metrics) { private void collectEmittedSpans(List> spans) { for (List spanList : spans) { for (SpanData span : spanList) { - String spanName = span.getName(); - System.out.println(spanName); Map, AttributeType>> scopeMap = this.tracesByScope.computeIfAbsent( span.getInstrumentationScopeInfo(), m -> new HashMap<>());