Skip to content

Commit

Permalink
feat(java-sdk): batch check telemetry attribute, sync back (#494)
Browse files Browse the repository at this point in the history
  • Loading branch information
jimmyjames authored Feb 18, 2025
2 parents fe6c28c + 2339f9d commit b267484
Show file tree
Hide file tree
Showing 15 changed files with 285 additions and 43 deletions.
5 changes: 4 additions & 1 deletion config/clients/java/CHANGELOG.md.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@

### [0.8.0](https://github.com/openfga/java-sdk/compare/v0.7.2...v0.8.0) (2025-02-07)

- feat!: add support for server-side `BatchCheck` method (#141) thanks @piotrooo!!
- feat!: add support for server-side [`batchCheck`](https://openfga.dev/docs/interacting/relationship-queries#batch-check) method (#141) - thanks @piotrooo!!
This is a more efficient way to check on multiple tuples than calling the existing client-side `batchCheck`. Using this method requires an OpenFGA [v1.8.0+](https://github.com/openfga/openfga/releases/tag/v1.8.0) server.
The existing `batchCheck` method has been renamed to `clientBatchCheck`.
The existing `BatchCheckResponse` has been renamed to `ClientBatchCheckResponse`.
- feat: add support for `start_time` parameter in `ReadChanges` endpoint (#137)

BREAKING CHANGES:
Expand Down
2 changes: 2 additions & 0 deletions config/clients/java/template/OpenFgaApiTest.java.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class OpenFgaApiTest {
private static final String EMPTY_RESPONSE_BODY = "{}";
private static final int DEFAULT_MAX_RETRIES = 3;
private static final Duration DEFAULT_RETRY_DELAY = Duration.ofMillis(100);
private static final TelemetryConfiguration DEFAULT_TELEMETRY_CONFIG = new TelemetryConfiguration();
private final ObjectMapper mapper = new ObjectMapper();
private OpenFgaApi fga;
Expand All @@ -62,6 +63,7 @@ public class OpenFgaApiTest {
when(mockConfiguration.getCredentials()).thenReturn(new Credentials());
when(mockConfiguration.getMaxRetries()).thenReturn(DEFAULT_MAX_RETRIES);
when(mockConfiguration.getMinimumRetryDelay()).thenReturn(DEFAULT_RETRY_DELAY);
when(mockConfiguration.getTelemetryConfiguration()).thenReturn(DEFAULT_TELEMETRY_CONFIG);
mockApiClient = mock(ApiClient.class);
when(mockApiClient.getObjectMapper()).thenReturn(mapper);
Expand Down
14 changes: 14 additions & 0 deletions config/clients/java/template/client-ApiClientTest.java.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,18 @@ class ApiClientTest {
assertNotEquals(client1, apiClient.getHttpClient());
}

@Test
public void httpClientShouldUseHttp1ByDefault() {
ApiClient apiClient = new ApiClient();
assertEquals(apiClient.getHttpClient().version(), HttpClient.Version.HTTP_1_1);
}

@Test
public void customHttpClientWithHttp2() {
HttpClient.Builder builder = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2);
ApiClient apiClient = new ApiClient(builder);
;
assertEquals(apiClient.getHttpClient().version(), HttpClient.Version.HTTP_2);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,4 +123,10 @@ public class ClientConfiguration extends Configuration {
super.minimumRetryDelay(minimumRetryDelay);
return this;
}

@Override
public ClientConfiguration telemetryConfiguration(TelemetryConfiguration telemetryConfiguration) {
super.telemetryConfiguration(telemetryConfiguration);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,64 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
* Configures the telemetry settings for the SDK.
*/
public class TelemetryConfiguration {
private Map<Metric, Map<Attribute, Optional<Object>>> metrics = new HashMap<>();
public TelemetryConfiguration() {
Map<Attribute, Optional<Object>> defaultAttributes = new HashMap<>();
defaultAttributes.put(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, Optional.empty());
defaultAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, Optional.empty());
defaultAttributes.put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, Optional.empty());
defaultAttributes.put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, Optional.empty());
defaultAttributes.put(Attributes.FGA_CLIENT_RESPONSE_MODEL_ID, Optional.empty());
defaultAttributes.put(Attributes.HTTP_HOST, Optional.empty());
defaultAttributes.put(Attributes.HTTP_REQUEST_METHOD, Optional.empty());
defaultAttributes.put(Attributes.HTTP_REQUEST_RESEND_COUNT, Optional.empty());
defaultAttributes.put(Attributes.HTTP_RESPONSE_STATUS_CODE, Optional.empty());
defaultAttributes.put(Attributes.URL_FULL, Optional.empty());
defaultAttributes.put(Attributes.URL_SCHEME, Optional.empty());
defaultAttributes.put(Attributes.USER_AGENT, Optional.empty());
private static final Map<Attribute, Optional<Object>> defaultAttributes = Map.ofEntries(
Map.entry(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, Optional.empty()),
Map.entry(Attributes.FGA_CLIENT_REQUEST_METHOD, Optional.empty()),
Map.entry(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, Optional.empty()),
Map.entry(Attributes.FGA_CLIENT_REQUEST_STORE_ID, Optional.empty()),
Map.entry(Attributes.FGA_CLIENT_RESPONSE_MODEL_ID, Optional.empty()),
Map.entry(Attributes.HTTP_HOST, Optional.empty()),
Map.entry(Attributes.HTTP_REQUEST_METHOD, Optional.empty()),
Map.entry(Attributes.HTTP_REQUEST_RESEND_COUNT, Optional.empty()),
Map.entry(Attributes.HTTP_RESPONSE_STATUS_CODE, Optional.empty()),
Map.entry(Attributes.URL_FULL, Optional.empty()),
Map.entry(Attributes.URL_SCHEME, Optional.empty()),
Map.entry(Attributes.USER_AGENT, Optional.empty()));
/**
* Constructs a TelemetryConfiguration object with the the metrics and attributes to send by default.
*/
public TelemetryConfiguration() {
metrics.put(Counters.CREDENTIALS_REQUEST, defaultAttributes);
metrics.put(Histograms.QUERY_DURATION, defaultAttributes);
metrics.put(Histograms.REQUEST_DURATION, defaultAttributes);
}

/**
* Constructs a TelemetryConfiguration object with the specified metrics.
* @param metrics the metrics to send
*/
public TelemetryConfiguration(Map<Metric, Map<Attribute, Optional<Object>>> metrics) {
this.metrics = metrics;
}

/**
* Sets the metrics to send.
* @param metrics the metrics to send
* @return this TelemetryConfiguration object
*/
public TelemetryConfiguration metrics(Map<Metric, Map<Attribute, Optional<Object>>> metrics) {
this.metrics = metrics;
return this;
}

/**
* @return the metrics to send.
*/
public Map<Metric, Map<Attribute, Optional<Object>>> metrics() {
return metrics;
}

/**
* @return the default attributes to send.
*/
public static Map<Attribute, Optional<Object>> defaultAttributes() {
return defaultAttributes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ class TelemetryConfigurationTest {
assertTrue(
defaultAttributes.containsKey(Attributes.USER_AGENT),
"The default attributes map should contain the USER_AGENT attribute.");
assertFalse(
defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_BATCH_CHECK_SIZE),
"The default attribute map should not contain the FGA_CLIENT_REQUEST_BATCH_CHECK_SIZE attribute.");
defaultAttributes = metrics.get(Histograms.QUERY_DURATION);
assertNotNull(defaultAttributes, "The default attributes map should not be null.");
Expand Down Expand Up @@ -124,6 +127,9 @@ class TelemetryConfigurationTest {
assertTrue(
defaultAttributes.containsKey(Attributes.USER_AGENT),
"The default attributes map should contain the USER_AGENT attribute.");
assertFalse(
defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_BATCH_CHECK_SIZE),
"The default attribute map should not contain the FGA_CLIENT_REQUEST_BATCH_CHECK_SIZE attribute.");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public class OAuth2Client {
this.config = new Configuration()
.apiUrl(buildApiTokenIssuer(clientCredentials.getApiTokenIssuer()))
.maxRetries(configuration.getMaxRetries())
.minimumRetryDelay(configuration.getMinimumRetryDelay());
.minimumRetryDelay(configuration.getMinimumRetryDelay())
.telemetryConfiguration(configuration.getTelemetryConfiguration());
this.telemetry = new Telemetry(this.config);
}

Expand Down
37 changes: 18 additions & 19 deletions config/clients/java/template/docs/OpenTelemetry.md.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,22 @@ In cases when metrics events are sent, they will not be viewable outside of infr

### Supported Attributes

| Attribute Name | Type | Enabled by Default | Description |
| ------------------------------ | ------ | ------------------ | --------------------------------------------------------------------------------- |
| `fga-client.request.client_id` | string | Yes | Client ID associated with the request, if any |
| `fga-client.request.method` | string | Yes | FGA method/action that was performed (e.g., Check, ListObjects) in TitleCase |
| `fga-client.request.model_id` | string | Yes | Authorization model ID that was sent as part of the request, if any |
| `fga-client.request.store_id` | string | Yes | Store ID that was sent as part of the request |
| `fga-client.response.model_id` | string | Yes | Authorization model ID that the FGA server used |
| `fga-client.user` | string | No | User associated with the action of the request for check and list users |
| `http.client.request.duration` | int | No | Duration for the SDK to complete the request, in milliseconds |
| `http.host` | string | Yes | Host identifier of the origin the request was sent to |
| `http.request.method` | string | Yes | HTTP method for the request |
| `http.request.resend_count` | int | Yes | Number of retries attempted, if any |
| `http.response.status_code` | int | Yes | Status code of the response (e.g., `200` for success) |
| `http.server.request.duration` | int | No | Time taken by the FGA server to process and evaluate the request, in milliseconds |
| `url.scheme` | string | Yes | HTTP scheme of the request (`http`/`https`) |
| `url.full` | string | Yes | Full URL of the request |
| `user_agent.original` | string | Yes | User Agent used in the query |
| Attribute Name | Type | Enabled by Default | Description |
|---------------------------------------|--------|--------------------|------------------------------------------------------------------------------|
| `fga-client.request.client_id` | string | Yes | Client ID associated with the request, if any |
| `fga-client.request.method` | string | Yes | FGA method/action that was performed (e.g., Check, ListObjects) in TitleCase |
| `fga-client.request.model_id` | string | Yes | Authorization model ID that was sent as part of the request, if any |
| `fga-client.request.store_id` | string | Yes | Store ID that was sent as part of the request |
| `fga-client.request.batch_check_size` | int | No | Number of objects in the batch check request |
| `fga-client.response.model_id` | string | Yes | Authorization model ID that the FGA server used |
| `fga-client.user` | string | No | User associated with the action of the request for check and list users |
| `http.host` | string | Yes | Host identifier of the origin the request was sent to |
| `http.request.method` | string | Yes | HTTP method for the request |
| `http.request.resend_count` | int | Yes | Number of retries attempted, if any |
| `http.response.status_code` | int | Yes | Status code of the response (e.g., `200` for success) |
| `url.scheme` | string | Yes | HTTP scheme of the request (`http`/`https`) |
| `url.full` | string | Yes | Full URL of the request |
| `user_agent.original` | string | Yes | User Agent used in the query |

## Examples

Expand Down Expand Up @@ -79,7 +78,7 @@ public class Example {
.put(ServiceAttributes.SERVICE_NAME, "example-app")
.put(ServiceAttributes.SERVICE_VERSION, "0.1.0")
.build();
SdkMeterProvider sdkMeterProvider = SdkMeterProvider.builder()
.registerMetricReader(PeriodicMetricReader.builder(OtlpGrpcMetricExporter.builder().build()).build())
.setResource(resource)
Expand Down Expand Up @@ -178,4 +177,4 @@ public class Example {
otel = openTelemetry;
}
}
```
```
20 changes: 19 additions & 1 deletion config/clients/java/template/libraries/native/ApiClient.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ public class ApiClient {
asyncResponseInterceptor = null;
}

/**
* Create an instance of ApiClient.
* <p>
* In other contexts, note that any settings in a {@link Configuration}
* will take precedence over equivalent settings in the
* {@link HttpClient.Builder} here.
*
* @param builder Http client builder.
*/
public ApiClient(HttpClient.Builder builder) {
this.builder = builder;
this.mapper = createDefaultObjectMapper();
this.client = this.builder.build();
interceptor = null;
responseInterceptor = null;
asyncResponseInterceptor = null;
}

/**
* Create an instance of ApiClient.
* <p>
Expand Down Expand Up @@ -176,7 +194,7 @@ public class ApiClient {
}

protected HttpClient.Builder createDefaultHttpClientBuilder() {
return HttpClient.newBuilder();
return HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1);
}

/**
Expand Down
10 changes: 10 additions & 0 deletions config/clients/java/template/libraries/native/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ public class OpenFgaApi {
telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, writeRequest.getAuthorizationModelId());
}
}

if (body instanceof BatchCheckRequest) {
BatchCheckRequest batchCheckRequest = (BatchCheckRequest) body;
if (batchCheckRequest.getChecks() != null) {
telemetryAttributes.put(
Attributes.FGA_CLIENT_REQUEST_BATCH_CHECK_SIZE,
String.valueOf(batchCheckRequest.getChecks().size()));
}
}
}

return telemetryAttributes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public class Attributes {
*/
public static final Attribute FGA_CLIENT_REQUEST_STORE_ID = new Attribute("fga-client.request.store_id");
/**
* The number of items to check in a batch request, if applicable.
*/
public static final Attribute FGA_CLIENT_REQUEST_BATCH_CHECK_SIZE = new Attribute("fga-client.request.batch_check_size");
/**
* The authorization model ID used by the server when evaluating the request, if applicable.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,16 @@ class AttributesTest {
// Arrange
Map<Attribute, String> attributes = new HashMap<>();
attributes.put(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, "client-id-value");
Metric metric = mock(Metric.class);
Metric metric = Histograms.QUERY_DURATION;
TelemetryConfiguration telemetryConfiguration = mock(TelemetryConfiguration.class);
Map<Metric, Map<Attribute, Optional<Object>>> metricsMap = new HashMap<>();
Map<Attribute, Optional<Object>> attributeMap = new HashMap<>();
attributeMap.put(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, Optional.of("config-value"));
metricsMap.put(metric, attributeMap);
when(telemetryConfiguration.metrics()).thenReturn(metricsMap);
TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(metricsMap);
Configuration configuration = mock(Configuration.class);
when(configuration.getTelemetryConfiguration()).thenReturn(telemetryConfiguration);
Configuration configuration = new Configuration();
configuration.telemetryConfiguration(telemetryConfiguration);
// Act
io.opentelemetry.api.common.Attributes result = Attributes.prepare(attributes, metric, configuration);
Expand All @@ -47,6 +46,43 @@ class AttributesTest {
assertEquals(expected, result);
}

@Test
void testPrepare_filtersAttributesFromDefaults() {
// Arrange
// sent by default
Map<Attribute, String> defaultAttributes = new HashMap<>();
for (Map.Entry<Attribute, Optional<Object>> entry :
TelemetryConfiguration.defaultAttributes().entrySet()) {
defaultAttributes.put(entry.getKey(), entry.getKey().toString() + "-value");
}

// not sent by default
Map<Attribute, String> nonDefaultAttributes = new HashMap<>();
nonDefaultAttributes.put(Attributes.FGA_CLIENT_USER, "user-value");

Map<Attribute, String> attributes = new HashMap<>();
attributes.putAll(defaultAttributes);
attributes.putAll(nonDefaultAttributes);

Metric metric = Histograms.QUERY_DURATION;

Configuration configuration = new Configuration();
configuration.telemetryConfiguration(new TelemetryConfiguration());

// Act
io.opentelemetry.api.common.Attributes result = Attributes.prepare(attributes, metric, configuration);

// Assert
AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder();
for (Map.Entry<Attribute, String> entry : defaultAttributes.entrySet()) {
builder.put(AttributeKey.stringKey(entry.getKey().getName()), entry.getValue());
}
io.opentelemetry.api.common.Attributes expected = builder.build();

assertEquals(expected, result);
}

@Test
void testFromHttpResponse() {
// Arrange
Expand Down
Loading

0 comments on commit b267484

Please sign in to comment.