Gather more span metadata (#14105)

This commit is contained in:
Jay DeLuca 2025-06-25 00:05:17 -04:00 committed by GitHub
parent d8723a4d32
commit 86f94fcbe6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 322 additions and 17 deletions

View File

@ -14,6 +14,49 @@ libraries:
target_versions:
javaagent:
- io.activej:activej-http:[6.0,)
telemetry:
- when: default
metrics:
- 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
spans:
- 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: network.peer.address
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
akka:
- name: akka-actor-2.3
source_path: instrumentation/akka/akka-actor-2.3
@ -214,6 +257,9 @@ libraries:
type: STRING
apache:
- name: apache-dbcp-2.0
description: |
This instrumentation provides database connection pools metrics for Apache DBCP.
The instrumentation uses `MBeanRegistration` methods for lifecycle detection, therefore it only activates if the `BasicDataSource` is registered to an `MBeanServer`. If using Spring Boot, this happens automatically as all Spring beans that support JMX registration are automatically registered by default.
source_path: instrumentation/apache-dbcp-2.0
scope:
name: io.opentelemetry.apache-dbcp-2.0
@ -334,6 +380,8 @@ libraries:
- name: rpc.system
type: STRING
- name: apache-httpasyncclient-4.1
description: This instrumentation provides CLIENT spans and metrics for the Apache
HttpAsyncClient.
source_path: instrumentation/apache-httpasyncclient-4.1
scope:
name: io.opentelemetry.apache-httpasyncclient-4.1
@ -358,14 +406,72 @@ libraries:
type: STRING
- name: server.port
type: LONG
spans:
- span_kind: CLIENT
attributes:
- name: error.type
type: STRING
- name: http.request.method
type: STRING
- name: http.request.method_original
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
- name: apache-httpclient-2.0
description: This instrumentation provides CLIENT spans and metrics for versions
2 and 3 of the Apache HttpClient.
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)
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
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
- name: apache-httpclient-4.0
description: This instrumentation provides CLIENT spans and metrics for version
4 of the Apache HttpClient.
source_path: instrumentation/apache-httpclient/apache-httpclient-4.0
scope:
name: io.opentelemetry.apache-httpclient-4.0
@ -373,13 +479,91 @@ libraries:
javaagent:
- io.dropwizard:dropwizard-client:(,3.0.0)
- org.apache.httpcomponents:httpclient:[4.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
spans:
- span_kind: CLIENT
attributes:
- name: error.type
type: STRING
- name: http.request.method
type: STRING
- name: http.request.method_original
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
- name: apache-httpclient-4.3
description: This instrumentation provides a library integration that enables
CLIENT spans and metrics for the Apache HttpClient.
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.+)
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
spans:
- span_kind: CLIENT
attributes:
- name: error.type
type: STRING
- name: http.request.method
type: STRING
- name: http.request.method_original
type: STRING
- name: http.request.resend_count
type: LONG
- 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
- name: apache-httpclient-5.0
source_path: instrumentation/apache-httpclient/apache-httpclient-5.0
scope:
@ -2857,6 +3041,8 @@ libraries:
- jakarta.servlet:jakarta.servlet-api:[5.0.0,)
spark:
- name: spark-2.3
description: |
This instrumentation does not emit telemetry on its own. Instead, it extracts the HTTP route and attaches it to SERVER spans and HTTP server metrics.
source_path: instrumentation/spark-2.3
scope:
name: io.opentelemetry.spark-2.3

View File

@ -14,12 +14,16 @@ fi
readonly INSTRUMENTATIONS=(
# <module path (colon-separated)> : <javaagent|library> : [ gradle-task-suffix ]
"activej-http-6.0:javaagent:test"
"akka:akka-http-10.0:javaagent:test"
"apache-httpasyncclient-4.1:javaagent:test"
"alibaba-druid-1.0:javaagent:test"
"alibaba-druid-1.0:javaagent:testStableSemconv"
"apache-dbcp-2.0:javaagent:test"
"apache-dbcp-2.0:javaagent:testStableSemconv"
"apache-httpclient:apache-httpclient-2.0:javaagent:test"
"apache-httpclient:apache-httpclient-4.0:javaagent:test"
"apache-httpclient:apache-httpclient-4.3:library:test"
"apache-httpclient:apache-httpclient-5.0:javaagent:test"
"apache-dubbo-2.7:javaagent:testDubbo"
"c3p0-0.9:javaagent:test"
@ -170,7 +174,7 @@ echo
./gradlew "${gradle_tasks[@]}" \
-PcollectMetadata=true \
--rerun-tasks
--rerun-tasks --continue
# uncomment the next line to remove all .telemetry directories
#find_and_remove_all_telemetry

View File

@ -8,10 +8,11 @@ Run the analysis to update the instrumentation-list.yaml:
`./gradlew :instrumentation-docs:runAnalysis`
### Metric collection
### Telemetry collection
Until this process is ready for all instrumentations, each module will be modified to include a
system property feature flag configured for when the tests run:
system property feature flag configured for when the tests run. By enabling the following flag you
will enable metric collection:
```kotlin
tasks {
@ -22,6 +23,17 @@ tasks {
}
```
In order to collect spans, add the `collectSpans` property (along with `collectMetadata`):
```kotlin
tasks {
test {
systemProperty("collectMetadata", collectMetadata)
systemProperty("collectSpans", true)
}
}
```
Sometimes instrumentation will behave differently based on configuration options, and we can
differentiate between these configurations by using the `metaDataConfig` system property. When the
telemetry is written to a file, the value of this property will be included, or it will default to
@ -130,6 +142,9 @@ public class SpringWebInstrumentationModule extends InstrumentationModule
* metrics
* List of metrics that the instrumentation module collects, including the metric name, description, type, and attributes.
* Separate lists for the metrics emitted by default vs via configuration options.
* spans
* List of spans kinds the instrumentation module generates, including the attributes and their types.
* Separate lists for the spans emitted by default vs via configuration options.
## Methodology
@ -168,16 +183,16 @@ name is determined by the instrumentation module name: `io.opentelemetry.{instr
We will implement gatherers for the schemaUrl and scope attributes when instrumentations start
implementing them.
### Metrics
### Spans and Metrics
In order to identify what metrics are emitted from instrumentations, we can hook into the
`InstrumentationTestRunner` class and collect the metrics generated during runs. We can then
In order to identify what telemetry is emitted from instrumentations, we can hook into the
`InstrumentationTestRunner` class and collect the metrics and spans generated during runs. We can then
leverage the `afterTestClass()` in the Agent and library test runners to then write this information
into temporary files. When we analyze the instrumentation modules, we can read these files and
generate the metrics section of the instrumentation-list.yaml file.
generate the telemetry section of the instrumentation-list.yaml file.
The data is written into a `.telemetry` directory in the root of each instrumentation module. This
data will be excluded from git and just generated on demand.
Each file has a `when` value along with the list of metrics that indicates whether the telemetry is emitted by default or via a
configuration option.
Each file has a `when` value along with the list of metrics that indicates whether the telemetry is
emitted by default or via a configuration option.

View File

@ -14,6 +14,7 @@ import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.TreeMap;
@ -107,5 +108,48 @@ public class DocGeneratorApplication {
+ "%)";
}
@SuppressWarnings("unused") // temporary helper method used for project tracking
private static String listAllModules(List<InstrumentationModule> modules) {
return modules.stream()
.map(InstrumentationModule::getInstrumentationName)
.sorted()
.map(name -> "- [ ] " + name)
.collect(Collectors.joining("\n"));
}
@SuppressWarnings("unused") // temporary helper method used for project tracking
private static String modulesWithDescriptions(List<InstrumentationModule> modules) {
// checklist of all modules sorted by name, with a check if description is set
return modules.stream()
.sorted(Comparator.comparing(InstrumentationModule::getInstrumentationName))
.map(
module -> {
boolean hasDescription =
module.getMetadata() != null
&& module.getMetadata().getDescription() != null
&& !module.getMetadata().getDescription().isEmpty();
String checkbox = hasDescription ? "- [x] " : "- [ ] ";
return checkbox + module.getInstrumentationName();
})
.collect(Collectors.joining("\n"));
}
@SuppressWarnings("unused") // temporary helper method used for project tracking
private static String modulesWithConfigs(List<InstrumentationModule> modules) {
// checklist of all modules sorted by name, with a check if config is set
return modules.stream()
.sorted(Comparator.comparing(InstrumentationModule::getInstrumentationName))
.map(
module -> {
boolean hasDescription =
module.getMetadata() != null
&& module.getMetadata().getConfigurations() != null
&& !module.getMetadata().getConfigurations().isEmpty();
String checkbox = hasDescription ? "- [x] " : "- [ ] ";
return checkbox + module.getInstrumentationName();
})
.collect(Collectors.joining("\n"));
}
private DocGeneratorApplication() {}
}

View File

@ -179,9 +179,14 @@ public class YamlHelper {
telemetryEntry.put("spans", spanList);
}
telemetryList.add(telemetryEntry);
if (!spanList.isEmpty() || !metricsList.isEmpty()) {
telemetryList.add(telemetryEntry);
}
}
if (!telemetryList.isEmpty()) {
moduleMap.put("telemetry", telemetryList);
}
moduleMap.put("telemetry", telemetryList);
}
return moduleMap;
}

View File

@ -19,3 +19,10 @@ dependencies {
otelJava {
minJavaVersionSupported.set(JavaVersion.VERSION_17)
}
tasks {
test {
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -5,11 +5,6 @@
package io.opentelemetry.instrumentation.akkaactor
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props}
import akka.pattern.ask
import akka.util.Timeout

View File

@ -27,10 +27,12 @@ tasks {
systemProperty("collectMetadata", collectMetadata)
systemProperty("metaDataConfig", "otel.semconv-stability.opt-in=database")
systemProperty("collectSpans", true)
}
test {
systemProperty("collectMetadata", collectMetadata)
systemProperty("collectSpans", true)
}
check {

View File

@ -0,0 +1,7 @@
description: >
This instrumentation provides database connection pools metrics for Apache DBCP.
The instrumentation uses `MBeanRegistration` methods for lifecycle detection, therefore it
only activates if the `BasicDataSource` is registered to an `MBeanServer`. If using Spring Boot,
this happens automatically as all Spring beans that support JMX registration are automatically
registered by default.

View File

@ -20,5 +20,6 @@ dependencies {
tasks {
test {
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -0,0 +1 @@
description: This instrumentation provides CLIENT spans and metrics for the Apache HttpAsyncClient.

View File

@ -19,5 +19,7 @@ dependencies {
tasks {
withType<Test>().configureEach {
systemProperty("testLatestDeps", findProperty("testLatestDeps") as Boolean)
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -0,0 +1 @@
description: This instrumentation provides CLIENT spans and metrics for versions 2 and 3 of the Apache HttpClient.

View File

@ -29,3 +29,10 @@ dependencies {
library("org.apache.httpcomponents:httpclient:4.0")
testCompileOnly("net.jcip:jcip-annotations:1.0")
}
tasks {
test {
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -0,0 +1 @@
description: This instrumentation provides CLIENT spans and metrics for version 4 of the Apache HttpClient.

View File

@ -11,3 +11,10 @@ dependencies {
latestDepTestLibrary("org.apache.httpcomponents:httpclient:4.+") // see apache-httpclient-5.0 module
}
tasks {
test {
systemProperty("collectMetadata", findProperty("collectMetadata")?.toString() ?: "false")
systemProperty("collectSpans", true)
}
}

View File

@ -0,0 +1 @@
description: This instrumentation provides a library integration that enables CLIENT spans and metrics for the Apache HttpClient.

View File

@ -0,0 +1,3 @@
description: >
This instrumentation does not emit telemetry on its own. Instead, it extracts the HTTP route and
attaches it to SERVER spans and HTTP server metrics.

View File

@ -20,6 +20,7 @@ import io.opentelemetry.context.propagation.ContextPropagators;
import io.opentelemetry.context.propagation.TextMapPropagator;
import io.opentelemetry.contrib.baggage.processor.BaggageSpanProcessor;
import io.opentelemetry.exporter.logging.LoggingSpanExporter;
import io.opentelemetry.instrumentation.testing.internal.MetaDataCollector;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
@ -39,6 +40,9 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider;
import io.opentelemetry.sdk.trace.SpanProcessor;
import io.opentelemetry.sdk.trace.data.SpanData;
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -116,7 +120,19 @@ public final class LibraryTestRunner extends InstrumentationTestRunner {
}
@Override
public void afterTestClass() {}
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);
}
}
@Override
public void clearAllExportedData() {