Skip to content

Commit b466ac3

Browse files
committed
Adding the client side integration of otel
Signed-off-by: Emmanuel Hugonnet <[email protected]>
1 parent b787fd5 commit b466ac3

File tree

12 files changed

+429
-33
lines changed

12 files changed

+429
-33
lines changed

client/base/src/main/java/io/a2a/client/ClientBuilder.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.a2a.spec.AgentCard;
1010
import io.a2a.spec.AgentInterface;
1111
import io.a2a.spec.TransportProtocol;
12+
import java.lang.reflect.InvocationTargetException;
1213

1314
import java.util.ArrayList;
1415
import java.util.HashMap;
@@ -37,7 +38,8 @@ public class ClientBuilder {
3738
private final AgentCard agentCard;
3839

3940
private final List<BiConsumer<ClientEvent, AgentCard>> consumers = new ArrayList<>();
40-
private @Nullable Consumer<Throwable> streamErrorHandler;
41+
private @Nullable
42+
Consumer<Throwable> streamErrorHandler;
4143
private ClientConfig clientConfig = new ClientConfig.Builder().build();
4244

4345
private final Map<Class<? extends ClientTransport>, ClientTransportConfig<? extends ClientTransport>> clientTransports = new LinkedHashMap<>();
@@ -105,7 +107,7 @@ private ClientTransport buildClientTransport() throws A2AClientException {
105107
throw new A2AClientException("Missing required TransportConfig for " + agentInterface.transport());
106108
}
107109

108-
return clientTransportProvider.create(clientTransportConfig, agentCard, agentInterface.url());
110+
return wrap(clientTransportProvider.create(clientTransportConfig, agentCard, agentInterface.url()), clientTransportConfig);
109111
}
110112

111113
private Map<String, String> getServerPreferredTransports() {
@@ -160,10 +162,23 @@ private AgentInterface findBestClientTransport() throws A2AClientException {
160162
if (transportProtocol == null || transportUrl == null) {
161163
throw new A2AClientException("No compatible transport found");
162164
}
163-
if (! transportProviderRegistry.containsKey(transportProtocol)) {
165+
if (!transportProviderRegistry.containsKey(transportProtocol)) {
164166
throw new A2AClientException("No client available for " + transportProtocol);
165167
}
166168

167169
return new AgentInterface(transportProtocol, transportUrl);
168170
}
171+
172+
private ClientTransport wrap(ClientTransport transport, ClientTransportConfig<? extends ClientTransport> clientTransportConfig) {
173+
try {
174+
Class<?> factoryClass = this.getClass().getClassLoader().loadClass("io.a2a.extras.opentelemetry.OpenTelemetryClientTransportFactory");
175+
Object openTelemetryClientTransportFactory = factoryClass.getConstructor(new Class<?>[0]).newInstance(new Object[0]);
176+
factoryClass.getDeclaredMethod("config", ClientTransportConfig.class).invoke(openTelemetryClientTransportFactory, clientTransportConfig);
177+
return (ClientTransport) factoryClass.getDeclaredMethod("wrap", ClientTransport.class).invoke(openTelemetryClientTransportFactory, transport);
178+
} catch (ClassNotFoundException ex) {
179+
180+
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
181+
}
182+
return transport;
183+
}
169184
}

client/transport/rest/src/main/java/io/a2a/client/transport/rest/RestTransportConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import io.a2a.client.transport.spi.ClientTransportConfig;
55
import org.jspecify.annotations.Nullable;
66

7-
public class RestTransportConfig extends ClientTransportConfig<RestTransport> {
7+
public class RestTransportConfig extends ClientTransportConfig<RestTransport> {
88

99
private final @Nullable A2AHttpClient httpClient;
1010

client/transport/spi/src/main/java/io/a2a/client/transport/spi/ClientTransportConfig.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@
22

33
import io.a2a.client.transport.spi.interceptors.ClientCallInterceptor;
44
import java.util.ArrayList;
5+
import java.util.HashMap;
56

67
import java.util.List;
8+
import java.util.Map;
79

810
/**
911
* Configuration for an A2A client transport.
1012
*/
1113
public abstract class ClientTransportConfig<T extends ClientTransport> {
1214

1315
protected List<ClientCallInterceptor> interceptors = new ArrayList<>();
16+
protected Map<String, ? extends Object > parameters = new HashMap<>();
1417

1518
public void setInterceptors(List<ClientCallInterceptor> interceptors) {
1619
this.interceptors = new ArrayList<>(interceptors);
@@ -19,4 +22,12 @@ public void setInterceptors(List<ClientCallInterceptor> interceptors) {
1922
public List<ClientCallInterceptor> getInterceptors() {
2023
return interceptors;
2124
}
25+
26+
public void setParameters(Map<String, ? extends Object > parameters) {
27+
this.parameters = new HashMap<>(parameters);
28+
}
29+
30+
public Map<String, ? extends Object > getParameters() {
31+
return parameters;
32+
}
2233
}

examples/helloworld/client/pom.xml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,22 @@
2020
<groupId>io.github.a2asdk</groupId>
2121
<artifactId>a2a-java-sdk-client</artifactId>
2222
</dependency>
23+
<dependency>
24+
<groupId>io.github.a2asdk</groupId>
25+
<artifactId>a2a-java-sdk-opentelemetry</artifactId>
26+
</dependency>
27+
<dependency>
28+
<groupId>io.opentelemetry</groupId>
29+
<artifactId>opentelemetry-sdk</artifactId>
30+
</dependency>
31+
<dependency>
32+
<groupId>io.opentelemetry</groupId>
33+
<artifactId>opentelemetry-exporter-otlp</artifactId>
34+
</dependency>
35+
<dependency>
36+
<groupId>io.opentelemetry</groupId>
37+
<artifactId>opentelemetry-exporter-logging</artifactId>
38+
</dependency>
2339
</dependencies>
2440

2541
<build>

examples/helloworld/client/src/main/java/io/a2a/examples/helloworld/HelloWorldClient.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import io.a2a.A2A;
1313

1414
import io.a2a.client.Client;
15-
import io.a2a.client.ClientBuilder;
1615
import io.a2a.client.ClientEvent;
1716
import io.a2a.client.MessageEvent;
1817
import io.a2a.client.http.A2ACardResolver;
@@ -22,6 +21,11 @@
2221
import io.a2a.spec.Message;
2322
import io.a2a.spec.Part;
2423
import io.a2a.spec.TextPart;
24+
import io.opentelemetry.api.OpenTelemetry;
25+
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
26+
import io.opentelemetry.sdk.OpenTelemetrySdk;
27+
import io.opentelemetry.sdk.trace.SdkTracerProvider;
28+
import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor;
2529

2630
/**
2731
* A simple example of using the A2A Java SDK to communicate with an A2A server.
@@ -35,6 +39,7 @@ public class HelloWorldClient {
3539

3640
public static void main(String[] args) {
3741
try {
42+
OpenTelemetry openTelemetrySdk = initOpenTelemetry();
3843
AgentCard finalAgentCard = null;
3944
AgentCard publicAgentCard = new A2ACardResolver("http://localhost:9999").getAgentCard();
4045
System.out.println("Successfully fetched public agent card:");
@@ -82,16 +87,18 @@ public static void main(String[] args) {
8287
error.printStackTrace();
8388
messageResponse.completeExceptionally(error);
8489
};
85-
90+
JSONRPCTransportConfig transportConfig = new JSONRPCTransportConfig();
91+
transportConfig.setParameters(Map.of("io.a2a.extras.opentelemetry.Tracer",
92+
openTelemetrySdk.getTracer("helloworld-client")));
8693
Client client = Client
8794
.builder(finalAgentCard)
8895
.addConsumers(consumers)
8996
.streamingErrorHandler(streamingErrorHandler)
90-
.withTransport(JSONRPCTransport.class, new JSONRPCTransportConfig())
97+
.withTransport(JSONRPCTransport.class, transportConfig)
9198
.build();
9299

93100
Message message = A2A.toUserMessage(MESSAGE_TEXT); // the message ID will be automatically generated for you
94-
101+
95102
System.out.println("Sending message: " + MESSAGE_TEXT);
96103
client.sendMessage(message);
97104
System.out.println("Message sent successfully. Responses will be handled by the configured consumers.");
@@ -108,4 +115,16 @@ public static void main(String[] args) {
108115
}
109116
}
110117

111-
}
118+
static OpenTelemetry initOpenTelemetry() {
119+
SdkTracerProvider sdkTracerProvider = SdkTracerProvider.builder()
120+
.addSpanProcessor(SimpleSpanProcessor.create(OtlpGrpcSpanExporter.getDefault()))
121+
.build();
122+
123+
OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder()
124+
.setTracerProvider(sdkTracerProvider)
125+
.build();
126+
127+
Runtime.getRuntime().addShutdownHook(new Thread(sdkTracerProvider::close));
128+
return openTelemetrySdk;
129+
}
130+
}

examples/helloworld/pom.xml

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,25 +34,6 @@
3434
</dependencies>
3535
</dependencyManagement>
3636

37-
<build>
38-
<plugins>
39-
<plugin>
40-
<groupId>io.quarkus</groupId>
41-
<artifactId>quarkus-maven-plugin</artifactId>
42-
<extensions>true</extensions>
43-
<executions>
44-
<execution>
45-
<goals>
46-
<goal>build</goal>
47-
<goal>generate-code</goal>
48-
<goal>generate-code-tests</goal>
49-
</goals>
50-
</execution>
51-
</executions>
52-
</plugin>
53-
</plugins>
54-
</build>
55-
5637
<modules>
5738
<module>client</module>
5839
<module>server</module>

examples/helloworld/server/pom.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
<dependency>
3838
<groupId>io.github.a2asdk</groupId>
3939
<artifactId>a2a-java-sdk-opentelemetry</artifactId>
40-
<version>0.4.0.Alpha1-SNAPSHOT</version>
4140
</dependency>
4241
<dependency>
4342
<groupId>jakarta.enterprise</groupId>

0 commit comments

Comments
 (0)