diff --git a/README.md b/README.md index 209ac16..b07b8a0 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ This is an autogenerated Java SDK for OpenFGA. It provides a wrapper around the - [Retries](#retries) - [API Endpoints](#api-endpoints) - [Models](#models) + - [OpenTelemetry](#models) - [Contributing](#contributing) - [Issues](#issues) - [Pull Requests](#pull-requests) @@ -1058,7 +1059,10 @@ public class Example { - [WriteRequestWrites](https://github.com/openfga/java-sdk/blob/main/docs/WriteRequestWrites.md) +### OpenTelemetry +This SDK supports producing metrics that can be consumed as part of an [OpenTelemetry](https://opentelemetry.io/) setup. +For more information, please see [the documentation](https://github.com/openfga/java-sdk/blob/main/OpenTelemetry.md) ## Contributing diff --git a/build.gradle b/build.gradle index c5e9d2a..434ad74 100644 --- a/build.gradle +++ b/build.gradle @@ -65,8 +65,8 @@ dependencies { implementation "com.fasterxml.jackson.core:jackson-annotations:$jackson_version" implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version" implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version" - implementation "org.openapitools:jackson-databind-nullable:0.2.6" - implementation platform("io.opentelemetry:opentelemetry-bom:1.40.0") + implementation "org.openapitools:jackson-databind-nullable:0.2.+" + implementation platform("io.opentelemetry:opentelemetry-bom:1.40.+") implementation "io.opentelemetry:opentelemetry-api" } diff --git a/docs/OpenTelemetry.md b/docs/OpenTelemetry.md new file mode 100644 index 0000000..59b2a36 --- /dev/null +++ b/docs/OpenTelemetry.md @@ -0,0 +1,44 @@ +# OpenTelemetry + +This SDK produces [metrics](https://opentelemetry.io/docs/concepts/signals/metrics/) using [OpenTelemetry](https://opentelemetry.io/) that allow you to view data such as request timings. These metrics also include attributes for the model and store ID, as well as the API called to allow you to build reporting. + +When an OpenTelemetry SDK instance is configured, the metrics will be exported and sent to the collector configured as part of your applications configuration. If you are not using OpenTelemetry, the metric functionality is a no-op and the events are never sent. + +In cases when metrics events are sent, they will not be viewable outside of infrastructure configured in your application, and are never available to the OpenFGA team or contributors. + +## Metrics + +### Supported Metrics + +| Metric Name | Type | Description | +| --------------------------------- | --------- | ------------------------------------------------------------------------------------ | +| `fga-client.request.duration` | Histogram | The total request time for FGA requests | +| `fga-client.query.duration` | Histogram | The amount of time the FGA server took to internally process nd evaluate the request | +| ` fga-client.credentials.request` | Counter | The total number of times a new token was requested when using ClientCredentials | + +### Supported attributes + +| Attribute Name | Type | Description | +| ------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------- | +| `fga-client.response.model_id` | `string` | The authorization model ID that the FGA server used | +| `fga-client.request.method` | `string` | The FGA method/action that was performed (e.g. `check`, `listObjects`, ...) in camelCase | +| `fga-client.request.store_id` | `string` | The store ID that was sent as part of the request | +| `fga-client.request.model_id` | `string` | The authorization model ID that was sent as part of the request, if any | +| `fga-client.request.client_id` | `string` | The client ID associated with the request, if any | +| `fga-client.user` | `string` | The user that is associated with the action of the request for check and list objects | +| `fga-client.request.retries` | `int` | The number of retries attempted (starting from 1 for the original request). Deprecated, use `http.request.resend_count` | +| `http.request.resend_count` | `int` | The number of retries attempted (starting from 1 for the original request) | +| `http.status_code` | `int` | The status code of the response. Deprecated, use `http.response.status_code` | +| `http.response.status_code` | `int` | The status code of the response | +| `http.method` | `string` | The HTTP method for the request. Deprecated, use `http.request.method` | +| `http.request.method` | `string` | The HTTP method for the request | +| `http.host` | `string` | Host identifier of the origin the request was sent to | +| `url.scheme` | `string` | HTTP Scheme of the request (`http`/`https`) | +| `url.full` | `string` | Full URL of the request | +| `user_agent.original` | `string` | User Agent used in the query | +| `http.client.request.duration` | `int` | The total request time for FGA requests | +| `http.server.request.duration` | `int` | The amount of time the FGA server took to internally process nd evaluate the request | + +## Example + +There is an [example project](https://github.com/openfga/java-sdk/blob/main/example/opentelemetry) that provides some guidance on how to configure OpenTelemetry available in the examples directory. diff --git a/example/example1/build.gradle b/example/example1/build.gradle index 9ea6879..3bf9dbd 100644 --- a/example/example1/build.gradle +++ b/example/example1/build.gradle @@ -19,11 +19,11 @@ repositories { } ext { - jacksonVersion = "2.16.0" + jacksonVersion = "2.17.1" } dependencies { - implementation("dev.openfga:openfga-sdk:0.4.+") + implementation("dev.openfga:openfga-sdk:0.5.+") // Serialization implementation("com.fasterxml.jackson.core:jackson-core:$jacksonVersion") diff --git a/src/main/java/dev/openfga/sdk/api/OpenFgaApi.java b/src/main/java/dev/openfga/sdk/api/OpenFgaApi.java index 069b4eb..8402c21 100644 --- a/src/main/java/dev/openfga/sdk/api/OpenFgaApi.java +++ b/src/main/java/dev/openfga/sdk/api/OpenFgaApi.java @@ -47,6 +47,7 @@ import dev.openfga.sdk.api.model.WriteRequest; import dev.openfga.sdk.errors.ApiException; import dev.openfga.sdk.errors.FgaInvalidParameterException; +import dev.openfga.sdk.telemetry.Attribute; import dev.openfga.sdk.telemetry.Attributes; import dev.openfga.sdk.telemetry.Telemetry; import dev.openfga.sdk.util.Pair; @@ -54,6 +55,8 @@ import java.net.URI; import java.net.http.HttpRequest; import java.time.Duration; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -129,11 +132,32 @@ private CompletableFuture> check( String path = "/stores/{store_id}/check".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "Check"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + + try { + if (!isNullOrWhitespace(body.getTupleKey().getUser())) { + telemetryAttributes.put( + Attributes.FGA_CLIENT_USER, body.getTupleKey().getUser()); + } + } catch (Exception e) { + } + + try { + if (!isNullOrWhitespace(body.getAuthorizationModelId())) { + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, body.getAuthorizationModelId()); + } + } catch (Exception e) { + } + try { HttpRequest request = buildHttpRequest("POST", path, body, configuration); return new HttpRequestAttempt<>(request, "check", CheckResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "check") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -173,10 +197,16 @@ private CompletableFuture> createStore( String path = "/stores"; + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "CreateStore"); + } + }; + try { HttpRequest request = buildHttpRequest("POST", path, body, configuration); return new HttpRequestAttempt<>(request, "createStore", CreateStoreResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "createStore") + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -215,11 +245,17 @@ private CompletableFuture> deleteStore(String storeId, Configu String path = "/stores/{store_id}".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "DeleteStore"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + try { HttpRequest request = buildHttpRequest("DELETE", path, configuration); return new HttpRequestAttempt<>(request, "deleteStore", Void.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "deleteStore") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -264,11 +300,24 @@ private CompletableFuture> expand( String path = "/stores/{store_id}/expand".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "Expand"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + + try { + if (!isNullOrWhitespace(body.getAuthorizationModelId())) { + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, body.getAuthorizationModelId()); + } + } catch (Exception e) { + } + try { HttpRequest request = buildHttpRequest("POST", path, body, configuration); return new HttpRequestAttempt<>(request, "expand", ExpandResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "expand") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -308,11 +357,17 @@ private CompletableFuture> getStore(String storeId String path = "/stores/{store_id}".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "GetStore"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + try { HttpRequest request = buildHttpRequest("GET", path, configuration); return new HttpRequestAttempt<>(request, "getStore", GetStoreResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "getStore") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -357,11 +412,31 @@ private CompletableFuture> listObjects( String path = "/stores/{store_id}/list-objects".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ListObjects"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + + try { + if (!isNullOrWhitespace(body.getUser())) { + telemetryAttributes.put(Attributes.FGA_CLIENT_USER, body.getUser()); + } + } catch (Exception e) { + } + + try { + if (!isNullOrWhitespace(body.getAuthorizationModelId())) { + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, body.getAuthorizationModelId()); + } + } catch (Exception e) { + } + try { HttpRequest request = buildHttpRequest("POST", path, body, configuration); return new HttpRequestAttempt<>(request, "listObjects", ListObjectsResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "listObjects") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -403,10 +478,16 @@ private CompletableFuture> listStores( String path = "/stores"; path = pathWithParams(path, "page_size", pageSize, "continuation_token", continuationToken); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ListStores"); + } + }; + try { HttpRequest request = buildHttpRequest("GET", path, configuration); return new HttpRequestAttempt<>(request, "listStores", ListStoresResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "listStores") + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -451,11 +532,24 @@ private CompletableFuture> listUsers( String path = "/stores/{store_id}/list-users".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ListUsers"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + + try { + if (!isNullOrWhitespace(body.getAuthorizationModelId())) { + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, body.getAuthorizationModelId()); + } + } catch (Exception e) { + } + try { HttpRequest request = buildHttpRequest("POST", path, body, configuration); return new HttpRequestAttempt<>(request, "listUsers", ListUsersResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "listUsers") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -500,11 +594,17 @@ private CompletableFuture> read( String path = "/stores/{store_id}/read".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "Read"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + try { HttpRequest request = buildHttpRequest("POST", path, body, configuration); return new HttpRequestAttempt<>(request, "read", ReadResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "read") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -551,13 +651,19 @@ private CompletableFuture> readAssertions( .replace("{store_id}", ApiClient.urlEncode(storeId.toString())) .replace("{authorization_model_id}", ApiClient.urlEncode(authorizationModelId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ReadAssertions"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, authorizationModelId); + } + }; + try { HttpRequest request = buildHttpRequest("GET", path, configuration); return new HttpRequestAttempt<>( request, "readAssertions", ReadAssertionsResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "readAssertions") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) - .addTelemetryAttribute(Attributes.REQUEST_MODEL_ID, authorizationModelId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -603,6 +709,14 @@ private CompletableFuture> readAutho .replace("{store_id}", ApiClient.urlEncode(storeId.toString())) .replace("{id}", ApiClient.urlEncode(id.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ReadAuthorizationModel"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, id); + } + }; + try { HttpRequest request = buildHttpRequest("GET", path, configuration); return new HttpRequestAttempt<>( @@ -611,9 +725,7 @@ private CompletableFuture> readAutho ReadAuthorizationModelResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "readAuthorizationModel") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) - .addTelemetryAttribute(Attributes.REQUEST_MODEL_ID, id) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -662,6 +774,13 @@ private CompletableFuture> readAuth .replace("{store_id}", ApiClient.urlEncode(storeId.toString())); path = pathWithParams(path, "page_size", pageSize, "continuation_token", continuationToken); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ReadAuthorizationModels"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + try { HttpRequest request = buildHttpRequest("GET", path, configuration); return new HttpRequestAttempt<>( @@ -670,8 +789,7 @@ private CompletableFuture> readAuth ReadAuthorizationModelsResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "readAuthorizationModels") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -725,11 +843,17 @@ private CompletableFuture> readChanges( String path = "/stores/{store_id}/changes".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); path = pathWithParams(path, "type", type, "page_size", pageSize, "continuation_token", continuationToken); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ReadChanges"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + try { HttpRequest request = buildHttpRequest("GET", path, configuration); return new HttpRequestAttempt<>(request, "readChanges", ReadChangesResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "readChanges") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -773,11 +897,24 @@ private CompletableFuture> write(String storeId, WriteReques String path = "/stores/{store_id}/write".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "Write"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + + try { + if (!isNullOrWhitespace(body.getAuthorizationModelId())) { + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, body.getAuthorizationModelId()); + } + } catch (Exception e) { + } + try { HttpRequest request = buildHttpRequest("POST", path, body, configuration); return new HttpRequestAttempt<>(request, "write", Object.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "write") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -832,12 +969,18 @@ private CompletableFuture> writeAssertions( .replace("{store_id}", ApiClient.urlEncode(storeId.toString())) .replace("{authorization_model_id}", ApiClient.urlEncode(authorizationModelId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "WriteAssertions"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, authorizationModelId); + } + }; + try { HttpRequest request = buildHttpRequest("PUT", path, body, configuration); return new HttpRequestAttempt<>(request, "writeAssertions", Void.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "writeAssertions") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) - .addTelemetryAttribute(Attributes.REQUEST_MODEL_ID, authorizationModelId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); @@ -883,6 +1026,13 @@ private CompletableFuture> writeAut String path = "/stores/{store_id}/authorization-models" .replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map telemetryAttributes = new HashMap() { + { + put(Attributes.FGA_CLIENT_REQUEST_METHOD, "WriteAuthorizationModel"); + put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId); + } + }; + try { HttpRequest request = buildHttpRequest("POST", path, body, configuration); return new HttpRequestAttempt<>( @@ -891,8 +1041,7 @@ private CompletableFuture> writeAut WriteAuthorizationModelResponse.class, apiClient, configuration) - .addTelemetryAttribute(Attributes.REQUEST_METHOD, "writeAuthorizationModel") - .addTelemetryAttribute(Attributes.REQUEST_STORE_ID, storeId) + .addTelemetryAttributes(telemetryAttributes) .attemptHttpRequest(); } catch (ApiException e) { return CompletableFuture.failedFuture(e); diff --git a/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java b/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java index ad13da0..2990e1a 100644 --- a/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java +++ b/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java @@ -12,6 +12,8 @@ package dev.openfga.sdk.api.auth; +import static dev.openfga.sdk.util.StringUtil.isNullOrWhitespace; + import dev.openfga.sdk.api.client.ApiClient; import dev.openfga.sdk.api.client.ApiResponse; import dev.openfga.sdk.api.client.HttpRequestAttempt; @@ -71,9 +73,12 @@ public CompletableFuture getAccessToken() throws FgaInvalidParameterExce Map attributesMap = new HashMap<>(); try { - attributesMap.put( - dev.openfga.sdk.telemetry.Attributes.REQUEST_CLIENT_ID, - config.getCredentials().getClientCredentials().getClientId()); + if (!isNullOrWhitespace( + config.getCredentials().getClientCredentials().getClientId())) { + attributesMap.put( + dev.openfga.sdk.telemetry.Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, + config.getCredentials().getClientCredentials().getClientId()); + } } catch (Exception e) { } diff --git a/src/main/java/dev/openfga/sdk/api/client/HttpRequestAttempt.java b/src/main/java/dev/openfga/sdk/api/client/HttpRequestAttempt.java index f4a1c58..ed3ba99 100644 --- a/src/main/java/dev/openfga/sdk/api/client/HttpRequestAttempt.java +++ b/src/main/java/dev/openfga/sdk/api/client/HttpRequestAttempt.java @@ -79,13 +79,19 @@ public CompletableFuture> attemptHttpRequest() throws ApiExceptio requestBodyPublisher.subscribe(new BodyLogger(System.err, "request"))); } - addTelemetryAttribute(Attributes.HTTP_HOST, configuration.getApiUrl()); - addTelemetryAttribute(Attributes.HTTP_METHOD, request.method()); + addTelemetryAttribute(Attributes.HTTP_HOST, request.uri().getHost()); + addTelemetryAttribute(Attributes.URL_SCHEME, request.uri().getScheme()); + addTelemetryAttribute(Attributes.URL_FULL, request.uri().toString()); + addTelemetryAttribute(Attributes.HTTP_REQUEST_METHOD, request.method()); + addTelemetryAttribute(Attributes.USER_AGENT, configuration.getUserAgent()); try { - addTelemetryAttribute( - Attributes.REQUEST_CLIENT_ID, - configuration.getCredentials().getClientCredentials().getClientId()); + if (!isNullOrWhitespace( + configuration.getCredentials().getClientCredentials().getClientId())) { + addTelemetryAttribute( + Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, + configuration.getCredentials().getClientCredentials().getClientId()); + } } catch (Exception e) { } @@ -119,20 +125,26 @@ private CompletableFuture> attemptHttpRequest( } addTelemetryAttributes(Attributes.fromHttpResponse(response, this.configuration.getCredentials())); - addTelemetryAttribute(Attributes.REQUEST_RETRIES, String.valueOf(retryNumber)); + addTelemetryAttribute(Attributes.HTTP_REQUEST_RESEND_COUNT, String.valueOf(retryNumber)); if (response.headers().firstValue("fga-query-duration-ms").isPresent()) { - double queryDuration = Double.parseDouble(response.headers() + String queryDuration = response.headers() .firstValue("fga-query-duration-ms") - .get()); - telemetry.metrics().queryDuration(queryDuration, this.getTelemetryAttributes()); + .orElse(null); + + if (!isNullOrWhitespace(queryDuration)) { + addTelemetryAttribute(Attributes.HTTP_SERVER_REQUEST_DURATION, queryDuration); + + double queryDurationDouble = Double.parseDouble(queryDuration); + telemetry.metrics().queryDuration(queryDurationDouble, this.getTelemetryAttributes()); + } } - telemetry - .metrics() - .requestDuration( - (double) (System.currentTimeMillis() - this.requestStarted), - this.getTelemetryAttributes()); + Double requestDuration = (double) (System.currentTimeMillis() - requestStarted); + + telemetry.metrics().requestDuration(requestDuration, this.getTelemetryAttributes()); + + addTelemetryAttribute(Attributes.HTTP_CLIENT_REQUEST_DURATION, String.valueOf(requestDuration)); return deserializeResponse(response) .thenApply(modeledResponse -> new ApiResponse<>( diff --git a/src/main/java/dev/openfga/sdk/telemetry/Attributes.java b/src/main/java/dev/openfga/sdk/telemetry/Attributes.java index 65c9e11..861a161 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Attributes.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Attributes.java @@ -1,5 +1,7 @@ package dev.openfga.sdk.telemetry; +import static dev.openfga.sdk.util.StringUtil.isNullOrWhitespace; + import dev.openfga.sdk.api.client.ApiResponse; import dev.openfga.sdk.api.configuration.Credentials; import dev.openfga.sdk.api.configuration.CredentialsMethod; @@ -13,55 +15,81 @@ * This class represents a collection of attributes used for telemetry purposes. */ public class Attributes { + /** - * Attribute representing the model ID of a request. + * The client ID used in the request, if applicable. */ - public static final Attribute REQUEST_MODEL_ID = new Attribute("fga-client.request.model_id"); + public static final Attribute FGA_CLIENT_REQUEST_CLIENT_ID = new Attribute("fga-client.request.client_id"); /** - * Attribute representing the method of a request. + * The FGA method/action of the request. */ - public static final Attribute REQUEST_METHOD = new Attribute("fga-client.request.method"); + public static final Attribute FGA_CLIENT_REQUEST_METHOD = new Attribute("fga-client.request.method"); /** - * Attribute representing the store ID of a request. + * The authorization model ID used in the request, if applicable. */ - public static final Attribute REQUEST_STORE_ID = new Attribute("fga-client.request.store_id"); + public static final Attribute FGA_CLIENT_REQUEST_MODEL_ID = new Attribute("fga-client.request.model_id"); /** - * Attribute representing the client ID of a request. + * The store ID used in the request, if applicable. */ - public static final Attribute REQUEST_CLIENT_ID = new Attribute("fga-client.request.client_id"); + public static final Attribute FGA_CLIENT_REQUEST_STORE_ID = new Attribute("fga-client.request.store_id"); /** - * Attribute representing the number of retries for a request. + * The authorization model ID used by the server when evaluating the request, if applicable. */ - public static final Attribute REQUEST_RETRIES = new Attribute("fga-client.request.retries"); + public static final Attribute FGA_CLIENT_RESPONSE_MODEL_ID = new Attribute("fga-client.response.model_id"); /** - * Attribute representing the model ID of a response. + * The user associated with the action of the request, if applicable. */ - public static final Attribute RESPONSE_MODEL_ID = new Attribute("fga-client.response.model_id"); + public static final Attribute FGA_CLIENT_USER = new Attribute("fga-client.user"); /** - * Attribute representing the user of a client. + * The total time (in milliseconds) it took for the request to complete, including the time it took to send the request and receive the response. */ - public static final Attribute CLIENT_USER = new Attribute("fga-client.user"); + public static final Attribute HTTP_CLIENT_REQUEST_DURATION = new Attribute("http.client.request.duration"); /** - * Attribute representing the host of an HTTP request. + * The HTTP host used in the request. */ public static final Attribute HTTP_HOST = new Attribute("http.host"); /** - * Attribute representing the method of an HTTP request. + * The HTTP method used in the request. + */ + public static final Attribute HTTP_REQUEST_METHOD = new Attribute("http.request.method"); + + /** + * The number of times the request was retried. */ - public static final Attribute HTTP_METHOD = new Attribute("http.method"); + public static final Attribute HTTP_REQUEST_RESEND_COUNT = new Attribute("http.request.resend_count"); /** - * Attribute representing the status code of an HTTP response. + * The HTTP status code returned by the server for the request. */ - public static final Attribute HTTP_STATUS_CODE = new Attribute("http.status_code"); + public static final Attribute HTTP_RESPONSE_STATUS_CODE = new Attribute("http.response.status_code"); + + /** + * The total time it took (in milliseconds) for the FGA server to process and evaluate the request. + */ + public static final Attribute HTTP_SERVER_REQUEST_DURATION = new Attribute("http.server.request.duration"); + + /** + * The scheme used in the request. + */ + public static final Attribute URL_SCHEME = new Attribute("url.scheme"); + + /** + * The complete URL used in the request. + */ + public static final Attribute URL_FULL = new Attribute("url.full"); + + /** + * The user agent used in the request. + */ + public static final Attribute USER_AGENT = new Attribute("user_agent.original"); /** * Prepares the attributes for OpenTelemetry publishing by converting them into the expected format. @@ -92,19 +120,23 @@ public static Map fromHttpResponse(HttpResponse response, Map attributes = new HashMap<>(); if (response != null) { - attributes.put(HTTP_STATUS_CODE, String.valueOf(response.statusCode())); + attributes.put(HTTP_RESPONSE_STATUS_CODE, String.valueOf(response.statusCode())); String responseModelId = response.headers() .firstValue("openfga-authorization-model-id") .orElse(null); - if (responseModelId != null) { - attributes.put(RESPONSE_MODEL_ID, responseModelId); + if (!isNullOrWhitespace(responseModelId)) { + attributes.put(FGA_CLIENT_RESPONSE_MODEL_ID, responseModelId); } } if (credentials != null && credentials.getCredentialsMethod() == CredentialsMethod.CLIENT_CREDENTIALS) { - attributes.put(REQUEST_CLIENT_ID, credentials.getClientCredentials().getClientId()); + if (!isNullOrWhitespace(credentials.getClientCredentials().getClientId())) { + attributes.put( + FGA_CLIENT_REQUEST_CLIENT_ID, + credentials.getClientCredentials().getClientId()); + } } return attributes; @@ -122,19 +154,23 @@ public static Map fromApiResponse(ApiResponse response, Cr Map attributes = new HashMap<>(); if (response != null) { - attributes.put(HTTP_STATUS_CODE, String.valueOf(response.getStatusCode())); + attributes.put(HTTP_RESPONSE_STATUS_CODE, String.valueOf(response.getStatusCode())); List responseModelIdList = response.getHeaders().getOrDefault("openfga-authorization-model-id", null); String responseModelId = responseModelIdList != null ? responseModelIdList.get(0) : null; - if (responseModelId != null) { - attributes.put(RESPONSE_MODEL_ID, responseModelId); + if (!isNullOrWhitespace(responseModelId)) { + attributes.put(FGA_CLIENT_RESPONSE_MODEL_ID, responseModelId); } } if (credentials != null && credentials.getCredentialsMethod() == CredentialsMethod.CLIENT_CREDENTIALS) { - attributes.put(REQUEST_CLIENT_ID, credentials.getClientCredentials().getClientId()); + if (!isNullOrWhitespace(credentials.getClientCredentials().getClientId())) { + attributes.put( + FGA_CLIENT_REQUEST_CLIENT_ID, + credentials.getClientCredentials().getClientId()); + } } return attributes; diff --git a/src/main/java/dev/openfga/sdk/telemetry/Counter.java b/src/main/java/dev/openfga/sdk/telemetry/Counter.java index 3a88c34..0921d2a 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Counter.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Counter.java @@ -5,19 +5,16 @@ */ public class Counter { private final String name; - private final String unit; private final String description; /** * Constructs a new Counter with the specified name, unit, and description. * * @param name the name of the counter - * @param unit the unit of measurement for the counter * @param description the description of the counter */ - public Counter(String name, String unit, String description) { + public Counter(String name, String description) { this.name = name; - this.unit = unit; this.description = description; } @@ -30,15 +27,6 @@ public String getName() { return name; } - /** - * Returns the unit of measurement for the counter. - * - * @return the unit of measurement for the counter - */ - public String getUnit() { - return unit; - } - /** * Returns the description of the counter. * diff --git a/src/main/java/dev/openfga/sdk/telemetry/Counters.java b/src/main/java/dev/openfga/sdk/telemetry/Counters.java index 1d52807..81b02e6 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Counters.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Counters.java @@ -8,5 +8,6 @@ public class Counters { * The CREDENTIALS_REQUEST counter represents the number of times an access token is requested. */ public static final Counter CREDENTIALS_REQUEST = new Counter( - "fga-client.credentials.request", "milliseconds", "The number of times an access token is requested."); + "fga-client.credentials.request", + "The total number of times new access tokens have been requested using ClientCredentials."); } diff --git a/src/main/java/dev/openfga/sdk/telemetry/Histogram.java b/src/main/java/dev/openfga/sdk/telemetry/Histogram.java index d8f6b7a..ca8e56c 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Histogram.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Histogram.java @@ -12,7 +12,6 @@ public class Histogram { * Constructs a Histogram object with the specified name, unit, and description. * * @param name the name of the histogram - * @param unit the unit of measurement for the histogram * @param description the description of the histogram */ public Histogram(String name, String unit, String description) { @@ -21,6 +20,19 @@ public Histogram(String name, String unit, String description) { this.description = description; } + /** + * Constructs a Histogram object with the specified name and description. The unit of measurement is set to "milliseconds" by default. + * + * @param name the name of the histogram + * @param unit the unit of measurement for the histogram + * @param description the description of the histogram + */ + public Histogram(String name, String description) { + this.name = name; + this.unit = "milliseconds"; + this.description = description; + } + /** * Returns the name of the histogram. */ diff --git a/src/main/java/dev/openfga/sdk/telemetry/Histograms.java b/src/main/java/dev/openfga/sdk/telemetry/Histograms.java index 82a7217..4a83eb4 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Histograms.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Histograms.java @@ -5,14 +5,16 @@ */ public class Histograms { /** - * A histogram for measuring the duration of a request. + * A histogram for measuring the total time (in milliseconds) it took for the request to complete, including the time it took to send the request and receive the response. */ public static final Histogram REQUEST_DURATION = new Histogram( - "fga-client.request.duration", "milliseconds", "How long it took for a request to be fulfilled."); + "fga-client.request.duration", + "The otal time (in milliseconds) it took for the request to complete, including the time it took to send the request and receive the response."); /** - * A histogram for measuring the duration of a query request. + * A histogram for measuring the total time it took (in milliseconds) for the FGA server to process and evaluate the request. */ - public static final Histogram QUERY_DURATION = - new Histogram("fga-client.query.duration", "milliseconds", "How long it took to perform a query request."); + public static final Histogram QUERY_DURATION = new Histogram( + "fga-client.query.duration", + "The total time it took (in milliseconds) for the FGA server to process and evaluate the request."); } diff --git a/src/main/java/dev/openfga/sdk/telemetry/Metrics.java b/src/main/java/dev/openfga/sdk/telemetry/Metrics.java index 3b3d564..cf648f4 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Metrics.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Metrics.java @@ -46,7 +46,6 @@ public LongCounter getCounter(Counter counter, Long value, Map attribu } /** - * Returns a DoubleHistogram metric instance, for publishing the duration of requests. + * Returns a DoubleHistogram histogram for measuring the total roundtrip time it took to process a request, including the time it took to send the request and receive the response. * * @param value The value to be recorded in the histogram. * @param attributes A map of attributes associated with the metric. @@ -108,7 +107,7 @@ public DoubleHistogram requestDuration(Double value, Map attr } /** - * Returns a DoubleHistogram metric instance, for publishing the duration of queries. + * Returns a DoubleHistogram for measuring how long the FGA server took to process and evaluate a request. * * @param value The value to be recorded in the histogram. * @param attributes A map of attributes associated with the metric.