Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
import org.a2aproject.sdk.spec.TaskPushNotificationConfig;
import org.a2aproject.sdk.spec.TaskQueryParams;
import org.a2aproject.sdk.spec.util.Utils;
import org.a2aproject.sdk.jsonrpc.common.logging.JsonPayloadLoggingHelper;
import org.jspecify.annotations.Nullable;

public class JSONRPCTransport implements ClientTransport {
Expand Down Expand Up @@ -94,12 +95,14 @@ public JSONRPCTransport(@Nullable A2AHttpClient httpClient, @Nullable AgentCard
@Override
public EventKind sendMessage(MessageSendParams request, @Nullable ClientCallContext context) throws A2AClientException {
checkNotNullParam("request", request);
JsonPayloadLoggingHelper.logTaskRequest(SEND_MESSAGE_METHOD, request);
PayloadAndHeaders payloadAndHeaders = applyInterceptors(SEND_MESSAGE_METHOD, ProtoUtils.ToProto.sendMessageRequest(request),
agentCard, context);

try {
String httpResponseBody = sendPostRequest(agentInterface.url(), payloadAndHeaders, SEND_MESSAGE_METHOD);
SendMessageResponse response = unmarshalResponse(httpResponseBody, SEND_MESSAGE_METHOD);
JsonPayloadLoggingHelper.logTaskResult(SEND_MESSAGE_METHOD, response.getResult());
return response.getResult();
} catch (A2AClientException e) {
throw e;
Expand Down
8 changes: 8 additions & 0 deletions common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.a2aproject.sdk.common.logging;

public enum A2AEventType {

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/A2AEventType.java

View workflow job for this annotation

GitHub Actions / verify-grpc-generation

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/A2AEventType.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/A2AEventType.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.
TASK_REQUEST("task.request"),
TASK_STATE_CHANGE("task.state_change"),
TASK_ARTIFACT("task.artifact"),
TASK_RESULT("task.result"),
NEGOTIATION("negotiation"),
AUTHORIZATION("authorization"),
NOTIFICATION_SUBSCRIPTION("notification.subscription"),
NOTIFICATION_DELIVERY("notification.delivery"),
STREAMING_EVENT("streaming.event");

private final String value;
A2AEventType(String value) { this.value = value; }
public String value() { return value; }
@Override public String toString() { return value; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.a2aproject.sdk.common.logging;

import org.jspecify.annotations.Nullable;

public final class A2APayloadLoggingConfig {

Check warning on line 5 in common/src/main/java/org/a2aproject/sdk/common/logging/A2APayloadLoggingConfig.java

View workflow job for this annotation

GitHub Actions / verify-grpc-generation

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 5 in common/src/main/java/org/a2aproject/sdk/common/logging/A2APayloadLoggingConfig.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 5 in common/src/main/java/org/a2aproject/sdk/common/logging/A2APayloadLoggingConfig.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.
public static final int DEFAULT_MAX_SIZE = 4096;
public static final boolean DEFAULT_ENABLED = false;
public static final boolean DEFAULT_MASKING_ENABLED = true;

private final boolean enabled;
private final int maxSize;
private final boolean maskingEnabled;
@Nullable private final PayloadMaskingStrategy maskingStrategy;

private A2APayloadLoggingConfig(Builder b) {
this.enabled = b.enabled; this.maxSize = b.maxSize;
this.maskingEnabled = b.maskingEnabled; this.maskingStrategy = b.maskingStrategy;
}

public boolean isEnabled() { return enabled; }
public int getMaxSize() { return maxSize; }
public boolean isMaskingEnabled() { return maskingEnabled; }
@Nullable public PayloadMaskingStrategy getMaskingStrategy() { return maskingStrategy; }

public static Builder builder() { return new Builder(); }
public static A2APayloadLoggingConfig defaults() { return builder().build(); }

public static A2APayloadLoggingConfig fromSystemProperties() {
Builder b = builder();
String en = System.getProperty("a2a.logging.payload.enabled");
if (en != null) b.enabled(Boolean.parseBoolean(en));
String ms = System.getProperty("a2a.logging.payload.max-size");
if (ms != null) try { b.maxSize(Integer.parseInt(ms)); } catch (NumberFormatException ignored) {}
String me = System.getProperty("a2a.logging.payload.masking.enabled");
if (me != null) b.maskingEnabled(Boolean.parseBoolean(me));
String keys = System.getProperty("a2a.logging.payload.masking.sensitive-keys");
if (keys != null && !keys.isBlank()) {
String[] parts = keys.split(",");
java.util.Set<String> set = new java.util.HashSet<>();
for (String p : parts) set.add(p.trim());
b.maskingStrategy(new DefaultPayloadMaskingStrategy(set));
}
String strat = System.getProperty("a2a.logging.payload.masking.strategy");
if (strat != null && !strat.isBlank()) try {
Class<?> cl = Class.forName(strat);
Object inst = cl.getDeclaredConstructor().newInstance();
if (inst instanceof PayloadMaskingStrategy) b.maskingStrategy((PayloadMaskingStrategy) inst);
} catch (Exception ignored) {}
return b.build();
}

public Builder toBuilder() {
Builder b = new Builder();
b.enabled = enabled; b.maxSize = maxSize; b.maskingEnabled = maskingEnabled; b.maskingStrategy = maskingStrategy;
return b;
}

public static final class Builder {
private boolean enabled = DEFAULT_ENABLED;
private int maxSize = DEFAULT_MAX_SIZE;
private boolean maskingEnabled = DEFAULT_MASKING_ENABLED;
@Nullable private PayloadMaskingStrategy maskingStrategy = null;
public Builder enabled(boolean v) { enabled = v; return this; }
public Builder maxSize(int v) { maxSize = v; return this; }
public Builder maskingEnabled(boolean v) { maskingEnabled = v; return this; }
public Builder maskingStrategy(PayloadMaskingStrategy v) { maskingStrategy = v; return this; }
public A2APayloadLoggingConfig build() { return new A2APayloadLoggingConfig(this); }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.a2aproject.sdk.common.logging;

import java.util.HashSet;
import java.util.Locale;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;

public class DefaultPayloadMaskingStrategy implements PayloadMaskingStrategy {

Check warning on line 15 in common/src/main/java/org/a2aproject/sdk/common/logging/DefaultPayloadMaskingStrategy.java

View workflow job for this annotation

GitHub Actions / verify-grpc-generation

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 15 in common/src/main/java/org/a2aproject/sdk/common/logging/DefaultPayloadMaskingStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 15 in common/src/main/java/org/a2aproject/sdk/common/logging/DefaultPayloadMaskingStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

private static final Set<String> DEFAULT_SENSITIVE_KEYS = Set.of(
"token", "credentials", "password", "secret", "apikey", "api_key",
"privatekey", "private_key", "authorization", "authentication",
"accesstoken", "access_token", "refreshtoken", "refresh_token",
"clientsecret", "client_secret", "url");

private final Set<String> sensitiveKeysLower;

public DefaultPayloadMaskingStrategy() { this(DEFAULT_SENSITIVE_KEYS); }

public DefaultPayloadMaskingStrategy(Set<String> sensitiveKeys) {
Set<String> lower = new HashSet<>();
for (String k : sensitiveKeys) lower.add(k.toLowerCase(Locale.ROOT));
this.sensitiveKeysLower = Set.copyOf(lower);
}

private boolean isSensitive(String key) {
String lower = key.toLowerCase(Locale.ROOT);
for (String s : sensitiveKeysLower) if (lower.contains(s)) return true;
return false;
}

@Override
public String mask(String payload) {
if (payload == null || payload.isEmpty()) return payload;
try {
JsonElement el = JsonParser.parseString(payload);
if (el.isJsonObject() || el.isJsonArray()) {
maskElement(el);
return el.toString();
}
} catch (JsonSyntaxException ignored) {}
String result = payload;
for (String key : sensitiveKeysLower) {
Pattern p = Pattern.compile("(\"" + Pattern.quote(key) + "\"\\s*:\\s*\")([^\"]+)(\")", Pattern.CASE_INSENSITIVE);
result = p.matcher(result).replaceAll("$1***$3");
}
return result;
}

private void maskElement(JsonElement el) {
if (el.isJsonObject()) {
JsonObject obj = el.getAsJsonObject();
for (String key : Set.copyOf(obj.keySet())) {
JsonElement val = obj.get(key);
if (isSensitive(key)) {
obj.addProperty(key, MASKED);
} else {
maskElement(val);
}
}
} else if (el.isJsonArray()) {
for (JsonElement item : el.getAsJsonArray()) maskElement(item);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package org.a2aproject.sdk.common.logging;

public class NoOpPayloadMaskingStrategy implements PayloadMaskingStrategy {

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/NoOpPayloadMaskingStrategy.java

View workflow job for this annotation

GitHub Actions / verify-grpc-generation

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/NoOpPayloadMaskingStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/NoOpPayloadMaskingStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.
@Override public String mask(String payload) { return payload; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.a2aproject.sdk.common.logging;

public interface PayloadMaskingStrategy {

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/PayloadMaskingStrategy.java

View workflow job for this annotation

GitHub Actions / verify-grpc-generation

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/PayloadMaskingStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/PayloadMaskingStrategy.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.
String MASKED = "***";
String mask(String payload);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.a2aproject.sdk.common.logging;

public final class PayloadTruncator {

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/PayloadTruncator.java

View workflow job for this annotation

GitHub Actions / verify-grpc-generation

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/PayloadTruncator.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 3 in common/src/main/java/org/a2aproject/sdk/common/logging/PayloadTruncator.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.
public static final String TRUNCATED_SUFFIX = " [truncated]";
private static final int SUFFIX_LEN = TRUNCATED_SUFFIX.length();
private PayloadTruncator() {}
public static String truncate(String payload, int maxSize) {
if (payload == null) return null;
if (maxSize <= 0) return TRUNCATED_SUFFIX;
if (payload.length() <= maxSize) return payload;
int cut = Math.max(0, maxSize - SUFFIX_LEN);
return payload.substring(0, cut) + TRUNCATED_SUFFIX;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package org.a2aproject.sdk.common.logging;

import java.util.concurrent.atomic.AtomicReference;

import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class StructuredPayloadLogger {

Check warning on line 9 in common/src/main/java/org/a2aproject/sdk/common/logging/StructuredPayloadLogger.java

View workflow job for this annotation

GitHub Actions / verify-grpc-generation

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 9 in common/src/main/java/org/a2aproject/sdk/common/logging/StructuredPayloadLogger.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 9 in common/src/main/java/org/a2aproject/sdk/common/logging/StructuredPayloadLogger.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.
private static final Logger PAYLOAD_LOGGER = LoggerFactory.getLogger("org.a2aproject.sdk.payload");
private static final AtomicReference<A2APayloadLoggingConfig> CONFIG = new AtomicReference<>(A2APayloadLoggingConfig.fromSystemProperties());
private static final AtomicReference<PayloadMaskingStrategy> STRATEGY = new AtomicReference<>(new DefaultPayloadMaskingStrategy());
private StructuredPayloadLogger() {}
public static void setConfig(A2APayloadLoggingConfig cfg) {
CONFIG.set(cfg);
if (cfg.getMaskingStrategy() != null) STRATEGY.set(cfg.getMaskingStrategy());
}
public static void setMaskingStrategy(PayloadMaskingStrategy s) { STRATEGY.set(s); }
public static void resetToSystemProperties() {
CONFIG.set(A2APayloadLoggingConfig.fromSystemProperties());
STRATEGY.set(new DefaultPayloadMaskingStrategy());
}
public static boolean isEnabled() {
A2APayloadLoggingConfig c = CONFIG.get();
return c.isEnabled() && PAYLOAD_LOGGER.isDebugEnabled();
}
public static void log(A2AEventType type, String payload, String... kvs) {
if (!isEnabled()) return;
A2APayloadLoggingConfig cfg = CONFIG.get();
String masked = cfg.isMaskingEnabled() ? STRATEGY.get().mask(payload) : payload;
int max = cfg.getMaxSize();
boolean truncated = masked != null && masked.length() > max;
String out = PayloadTruncator.truncate(masked, max);
int originalSize = masked != null ? masked.length() : 0;
String traceId = TraceCorrelation.currentTraceId();
String spanId = TraceCorrelation.currentSpanId();
try {
var builder = PAYLOAD_LOGGER.atDebug();
builder.addKeyValue("a2a.event.type", type.value());
builder.addKeyValue("a2a.payload", out);
builder.addKeyValue("a2a.payload.size", String.valueOf(originalSize));
builder.addKeyValue("a2a.payload.truncated", String.valueOf(truncated));
if (traceId != null) builder.addKeyValue("trace_id", traceId);
if (spanId != null) builder.addKeyValue("span_id", spanId);
for (int i = 0; i + 1 < kvs.length; i += 2) builder.addKeyValue(kvs[i], kvs[i+1]);
builder.log("a2a.payload event_type=" + type.value());
} catch (NoSuchMethodError | Exception e) {
StringBuilder sb = new StringBuilder();
sb.append("a2a.payload event_type=").append(type.value());
sb.append(" payload=").append(out);
sb.append(" a2a.payload.size=").append(originalSize);
sb.append(" a2a.payload.truncated=").append(truncated);
if (traceId != null) sb.append(" trace_id=").append(traceId);
if (spanId != null) sb.append(" span_id=").append(spanId);
for (int i = 0; i + 1 < kvs.length; i += 2) sb.append(" ").append(kvs[i]).append("=").append(kvs[i+1]);
PAYLOAD_LOGGER.debug(sb.toString());
}
}
public static void logTaskRequest(String op, String payload, @Nullable String taskId) {
if (taskId != null) log(A2AEventType.TASK_REQUEST, payload, "a2a.operation", op, "a2a.task_id", taskId);
else log(A2AEventType.TASK_REQUEST, payload, "a2a.operation", op);
}
public static void logTaskResult(String op, String payload, @Nullable String taskId) {
if (taskId != null) log(A2AEventType.TASK_RESULT, payload, "a2a.operation", op, "a2a.task_id", taskId);
else log(A2AEventType.TASK_RESULT, payload, "a2a.operation", op);
}
public static void logTaskStateChange(String payload, @Nullable String taskId, @Nullable String status) {
if (taskId != null && status != null) log(A2AEventType.TASK_STATE_CHANGE, payload, "a2a.task_id", taskId, "a2a.task.status", status);
else if (taskId != null) log(A2AEventType.TASK_STATE_CHANGE, payload, "a2a.task_id", taskId);
else log(A2AEventType.TASK_STATE_CHANGE, payload);
}
public static void logTaskArtifact(String payload, @Nullable String taskId, @Nullable String artifactId) {
if (taskId != null) log(A2AEventType.TASK_ARTIFACT, payload, "a2a.task_id", taskId, "a2a.artifact_id", artifactId != null ? artifactId : "");
else log(A2AEventType.TASK_ARTIFACT, payload);
}
public static void logAuthDecision(String op, boolean allowed, String reason) {
log(A2AEventType.AUTHORIZATION, reason, "a2a.operation", op, "a2a.auth.allowed", String.valueOf(allowed));
}
public static void logPushSubscription(String op, String payload) {
log(A2AEventType.NOTIFICATION_SUBSCRIPTION, payload, "a2a.operation", op);
}
public static void logPushDelivery(String url, boolean success, String payload) {
log(A2AEventType.NOTIFICATION_DELIVERY, payload, "a2a.push.url", url, "a2a.push.success", String.valueOf(success));
}
public static void logNegotiation(String op, String payload) {
log(A2AEventType.NEGOTIATION, payload, "a2a.operation", op);
}
public static void logStreamingEvent(String payload, @Nullable String taskId) {
if (taskId != null) log(A2AEventType.STREAMING_EVENT, payload, "a2a.task_id", taskId);
else log(A2AEventType.STREAMING_EVENT, payload);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package org.a2aproject.sdk.common.logging;

import java.lang.reflect.Method;

import org.jspecify.annotations.Nullable;
import org.slf4j.MDC;

public final class TraceCorrelation {

Check warning on line 8 in common/src/main/java/org/a2aproject/sdk/common/logging/TraceCorrelation.java

View workflow job for this annotation

GitHub Actions / verify-grpc-generation

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 8 in common/src/main/java/org/a2aproject/sdk/common/logging/TraceCorrelation.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 25)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.

Check warning on line 8 in common/src/main/java/org/a2aproject/sdk/common/logging/TraceCorrelation.java

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 21)

[RequireExplicitNullMarking] [RequireExplicitNullMarking] Top-level classes must either be directly annotated with @NullMarked/@NullUnmarked, be in a package that is explicitly @NullMarked/@NullUnmarked, or be in a module that is explicitly @NullMarked.
private TraceCorrelation() {}
public static final String TRACE_ID_KEY = "trace_id";
public static final String SPAN_ID_KEY = "span_id";

@Nullable
public static String currentTraceId() {
String t = tryOtelTraceId();
if (t != null && !t.isEmpty()) return t;
t = MDC.get("trace_id"); if (t != null) return t;
t = MDC.get("traceId"); if (t != null) return t;
return MDC.get("X-B3-TraceId");
}

@Nullable
public static String currentSpanId() {
String s = tryOtelSpanId();
if (s != null && !s.isEmpty()) return s;
s = MDC.get("span_id"); if (s != null) return s;
s = MDC.get("spanId"); if (s != null) return s;
return MDC.get("X-B3-SpanId");
}

@Nullable
private static String tryOtelTraceId() {
try {
Class<?> spanClass = Class.forName("io.opentelemetry.api.trace.Span");
Object span = spanClass.getMethod("current").invoke(null);
Object ctx = span.getClass().getMethod("getSpanContext").invoke(span);
Boolean valid = (Boolean) ctx.getClass().getMethod("isValid").invoke(ctx);
if (valid == null || !valid) return null;
String id = (String) ctx.getClass().getMethod("getTraceId").invoke(ctx);
if (id != null && !id.equals("00000000000000000000000000000000")) return id;
} catch (Exception ignored) {}
return null;
}

@Nullable
private static String tryOtelSpanId() {
try {
Class<?> spanClass = Class.forName("io.opentelemetry.api.trace.Span");
Object span = spanClass.getMethod("current").invoke(null);
Object ctx = span.getClass().getMethod("getSpanContext").invoke(span);
Boolean valid = (Boolean) ctx.getClass().getMethod("isValid").invoke(ctx);
if (valid == null || !valid) return null;
String id = (String) ctx.getClass().getMethod("getSpanId").invoke(ctx);
if (id != null && !id.equals("0000000000000000")) return id;
} catch (Exception ignored) {}
return null;
}

public static void setMdc(@Nullable String traceId, @Nullable String spanId) {
if (traceId != null) MDC.put(TRACE_ID_KEY, traceId);
if (spanId != null) MDC.put(SPAN_ID_KEY, spanId);
}

public static void clearMdc() {
MDC.remove(TRACE_ID_KEY); MDC.remove(SPAN_ID_KEY);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** Structured payload logging for A2A. */
package org.a2aproject.sdk.common.logging;
Loading
Loading