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

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,34 @@
<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>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package org.a2aproject.sdk.common.logging;

/**
* Enumeration of A2A protocol payload event types for structured logging.
* <p>
* Each event type corresponds to a category of protocol message that can be
* logged with structured fields for observability and debugging.
* </p>
*/
public enum A2AEventType {

Check warning on line 10 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 10 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 10 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: message/send, message/stream, getTask, listTasks, cancelTask, etc.
*/
TASK_REQUEST("task.request"),

/**
* Task state change: TaskStatusUpdateEvent and status transitions.
*/
STATE_CHANGE("task.state_change"),

/**
* Result artifact: TaskArtifactUpdateEvent and artifact payloads.
*/
ARTIFACT("task.artifact"),

/**
* Task result: final Task snapshot returned to client.
*/
TASK_RESULT("task.result"),

/**
* Negotiation payload: extension negotiation, version negotiation, AgentCard capabilities.
*/
NEGOTIATION("negotiation"),

/**
* Authorization decision: allow/deny from TaskAuthorizationProvider.
*/
AUTHORIZATION("authorization"),

/**
* Notification subscription: push config create/get/list/delete, subscribeToTask.
*/
NOTIFICATION_SUBSCRIPTION("notification.subscription"),

/**
* Notification delivery: push webhook dispatch to external URL.
*/
NOTIFICATION_DELIVERY("notification.delivery"),

/**
* Streaming event: per-event SSE streaming payload.
*/
STREAMING_EVENT("streaming.event"),

/**
* Generic payload for any other structured log.
*/
GENERIC("generic");

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,174 @@
package org.a2aproject.sdk.common.logging;

import java.util.Set;

import org.jspecify.annotations.Nullable;

/**
* Configuration for structured payload logging.
* <p>
* Supports both system properties and programmatic configuration via builder.
* </p>
* <h2>System Properties</h2>
* <ul>
* <li>{@code a2a.logging.payload.enabled} (boolean, default false) — enable payload logging</li>
* <li>{@code a2a.logging.payload.max-size} (int, default 4096) — max payload size before truncation</li>
* <li>{@code a2a.logging.payload.masking.enabled} (boolean, default true) — enable sensitive masking</li>
* <li>{@code a2a.logging.payload.masking.strategy} (class name) — custom masking strategy</li>
* </ul>
*/
public final class A2APayloadLoggingConfig {

Check warning on line 20 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 20 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 20 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 String ENABLED_PROPERTY = "a2a.logging.payload.enabled";
public static final String MAX_SIZE_PROPERTY = "a2a.logging.payload.max-size";
public static final String MASKING_ENABLED_PROPERTY = "a2a.logging.payload.masking.enabled";
public static final String MASKING_STRATEGY_PROPERTY = "a2a.logging.payload.masking.strategy";
public static final String MASKING_SENSITIVE_KEYS_PROPERTY = "a2a.logging.payload.masking.sensitive-keys";
public static final int DEFAULT_MAX_SIZE = 4096;

private final boolean enabled;
private final int maxSize;
private final boolean maskingEnabled;
private final @Nullable PayloadMaskingStrategy maskingStrategy;
private final @Nullable Set<String> sensitiveKeys;

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

public boolean isEnabled() {
return enabled;
}

public int getMaxSize() {
return maxSize;
}

public boolean isMaskingEnabled() {
return maskingEnabled;
}

public @Nullable PayloadMaskingStrategy getMaskingStrategy() {
return maskingStrategy;
}

public @Nullable Set<String> getSensitiveKeys() {
return sensitiveKeys;
}

/**
* Creates config from system properties.
*/
public static A2APayloadLoggingConfig fromSystemProperties() {
Builder builder = new Builder();
String enabledProp = System.getProperty(ENABLED_PROPERTY);
if (enabledProp != null) {
builder.enabled(Boolean.parseBoolean(enabledProp));
}
String maxSizeProp = System.getProperty(MAX_SIZE_PROPERTY);
if (maxSizeProp != null) {
try {
builder.maxSize(Integer.parseInt(maxSizeProp.trim()));
} catch (NumberFormatException ignored) {
// use default
}
}
String maskingEnabledProp = System.getProperty(MASKING_ENABLED_PROPERTY);
if (maskingEnabledProp != null) {
builder.maskingEnabled(Boolean.parseBoolean(maskingEnabledProp));
}
// Strategy loaded separately by logger via ServiceLoader / reflection;
// we don't instantiate here to avoid class loading issues in fromSystemProperties
String strategyClass = System.getProperty(MASKING_STRATEGY_PROPERTY);
if (strategyClass != null && !strategyClass.isBlank()) {
try {
Class<?> clazz = Class.forName(strategyClass.trim());
Object instance = clazz.getDeclaredConstructor().newInstance();
if (instance instanceof PayloadMaskingStrategy pms) {
builder.maskingStrategy(pms);
}
} catch (Exception ignored) {
}
}
String sensitiveKeysProp = System.getProperty(MASKING_SENSITIVE_KEYS_PROPERTY);
if (sensitiveKeysProp != null && !sensitiveKeysProp.isBlank()) {
Set<String> keys = Set.of(sensitiveKeysProp.split(","));
// Trim each key
Set<String> trimmed = new java.util.HashSet<>();
for (String k : keys) {
if (k != null && !k.isBlank()) {
trimmed.add(k.trim());
}
}
if (!trimmed.isEmpty()) {
builder.sensitiveKeys(trimmed);
}
}
return builder.build();
}

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

public static Builder builder() {
return new Builder();
}

public static final class Builder {
private boolean enabled = false;
private int maxSize = DEFAULT_MAX_SIZE;
private boolean maskingEnabled = true;
private @Nullable PayloadMaskingStrategy maskingStrategy = null;
private @Nullable Set<String> sensitiveKeys = null;

public Builder enabled(boolean enabled) {
this.enabled = enabled;
return this;
}

public Builder maxSize(int maxSize) {
this.maxSize = maxSize;
return this;
}

public Builder maskingEnabled(boolean maskingEnabled) {
this.maskingEnabled = maskingEnabled;
return this;
}

public Builder maskingStrategy(@Nullable PayloadMaskingStrategy maskingStrategy) {
this.maskingStrategy = maskingStrategy;
return this;
}

public Builder sensitiveKeys(@Nullable Set<String> sensitiveKeys) {
this.sensitiveKeys = sensitiveKeys;
return this;
}

public A2APayloadLoggingConfig build() {
return new A2APayloadLoggingConfig(this);
}
}

@Override
public String toString() {
return "A2APayloadLoggingConfig{"
+ "enabled=" + enabled
+ ", maxSize=" + maxSize
+ ", maskingEnabled=" + maskingEnabled
+ ", maskingStrategy=" + (maskingStrategy != null ? maskingStrategy.getClass().getName() : "default")
+ '}';
}
}
Loading
Loading