diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 5a916f6..06ad99e 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -8,6 +8,14 @@ updates: dependencies: patterns: - "*" + - package-ecosystem: "gradle" + directory: "/example/example1" + schedule: + interval: "monthly" + groups: + dependencies: + patterns: + - "*" - package-ecosystem: "github-actions" directory: "/" schedule: diff --git a/.gitignore b/.gitignore index 3e11b85..c3784f2 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ VERSION.txt # JetBrains IDEs .idea/ *.iml + +# VSCode IDE +/.vscode diff --git a/.openapi-generator/FILES b/.openapi-generator/FILES index a59ca74..683c204 100644 --- a/.openapi-generator/FILES +++ b/.openapi-generator/FILES @@ -54,6 +54,7 @@ docs/NotFoundErrorCode.md docs/NullValue.md docs/ObjectRelation.md docs/OpenFgaApi.md +docs/OpenTelemetry.md docs/PathUnknownErrorMessageResponse.md docs/ReadAssertionsResponse.md docs/ReadAuthorizationModelResponse.md @@ -184,6 +185,7 @@ src/main/java/dev/openfga/sdk/api/configuration/Configuration.java src/main/java/dev/openfga/sdk/api/configuration/ConfigurationOverride.java src/main/java/dev/openfga/sdk/api/configuration/Credentials.java src/main/java/dev/openfga/sdk/api/configuration/CredentialsMethod.java +src/main/java/dev/openfga/sdk/api/configuration/TelemetryConfiguration.java src/main/java/dev/openfga/sdk/api/model/AbortedMessageResponse.java src/main/java/dev/openfga/sdk/api/model/AbstractOpenApiSchema.java src/main/java/dev/openfga/sdk/api/model/Any.java @@ -272,6 +274,15 @@ src/main/java/dev/openfga/sdk/errors/FgaApiValidationError.java src/main/java/dev/openfga/sdk/errors/FgaError.java src/main/java/dev/openfga/sdk/errors/FgaInvalidParameterException.java src/main/java/dev/openfga/sdk/errors/HttpStatusCode.java +src/main/java/dev/openfga/sdk/telemetry/Attribute.java +src/main/java/dev/openfga/sdk/telemetry/Attributes.java +src/main/java/dev/openfga/sdk/telemetry/Counter.java +src/main/java/dev/openfga/sdk/telemetry/Counters.java +src/main/java/dev/openfga/sdk/telemetry/Histogram.java +src/main/java/dev/openfga/sdk/telemetry/Histograms.java +src/main/java/dev/openfga/sdk/telemetry/Metric.java +src/main/java/dev/openfga/sdk/telemetry/Metrics.java +src/main/java/dev/openfga/sdk/telemetry/Telemetry.java src/main/java/dev/openfga/sdk/util/Pair.java src/main/java/dev/openfga/sdk/util/StringUtil.java src/main/java/dev/openfga/sdk/util/Validation.java @@ -290,4 +301,14 @@ src/test/java/dev/openfga/sdk/api/client/ApiClientTest.java src/test/java/dev/openfga/sdk/api/client/OpenFgaClientTest.java src/test/java/dev/openfga/sdk/api/configuration/ClientCredentialsTest.java src/test/java/dev/openfga/sdk/api/configuration/ConfigurationTest.java +src/test/java/dev/openfga/sdk/api/configuration/TelemetryConfigurationTest.java +src/test/java/dev/openfga/sdk/telemetry/AttributeTest.java +src/test/java/dev/openfga/sdk/telemetry/AttributesTest.java +src/test/java/dev/openfga/sdk/telemetry/CounterTest.java +src/test/java/dev/openfga/sdk/telemetry/CountersTest.java +src/test/java/dev/openfga/sdk/telemetry/HistogramTest.java +src/test/java/dev/openfga/sdk/telemetry/HistogramsTest.java +src/test/java/dev/openfga/sdk/telemetry/MetricTest.java +src/test/java/dev/openfga/sdk/telemetry/MetricsTest.java +src/test/java/dev/openfga/sdk/telemetry/TelemetryTest.java src/test/java/dev/openfga/sdk/util/StringUtilTest.java diff --git a/README.md b/README.md index 209ac16..b5a7dab 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](#opentelemetry) - [Contributing](#contributing) - [Issues](#issues) - [Pull Requests](#pull-requests) @@ -1060,6 +1061,10 @@ public class Example { +### 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/docs/opentelemetry.md) + ## Contributing ### Issues diff --git a/build.gradle b/build.gradle index c5e9d2a..3845b03 100644 --- a/build.gradle +++ b/build.gradle @@ -60,13 +60,13 @@ ext { } dependencies { - implementation "com.google.code.findbugs:jsr305:3.0.2" + implementation "com.google.code.findbugs:jsr305:3.0.+" implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version" 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" } @@ -80,7 +80,7 @@ testing { implementation "org.junit.jupiter:junit-jupiter:$junit_version" implementation "org.mockito:mockito-core:5.+" runtimeOnly "org.junit.platform:junit-platform-launcher" - implementation "org.wiremock:wiremock:3.8.0" + implementation "org.wiremock:wiremock:3.8.+" // This test-only dependency is convenient but not widely used. // Review project activity before updating the version here. @@ -106,8 +106,8 @@ testing { dependencies { implementation "com.fasterxml.jackson.core:jackson-core:$jackson_version" implementation "com.fasterxml.jackson.core:jackson-databind:$jackson_version" - implementation "org.testcontainers:junit-jupiter:1.19.8" - implementation "org.testcontainers:openfga:1.19.8" + implementation "org.testcontainers:junit-jupiter:1.19.+" + implementation "org.testcontainers:openfga:1.19.+" implementation project() } diff --git a/docs/OpenTelemetry.md b/docs/OpenTelemetry.md new file mode 100644 index 0000000..d4b2df9 --- /dev/null +++ b/docs/OpenTelemetry.md @@ -0,0 +1,35 @@ +# 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.credentials.request` | Counter | The total number of times a new token was requested when using ClientCredentials | +| `fga-client.query.duration` | Histogram | The amount of time the FGA server took to internally process nd evaluate the request | +| `fga-client.request.duration` | Histogram | The total request time for FGA requests | + +### Supported attributes + +| Attribute Name | Type | Description | +| ------------------------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `fga-client.request.client_id` | `string` | The client ID associated with the request, if any | +| `fga-client.request.method` | `string` | The FGA method/action that was performed (e.g. `Check`, `ListObjects`, ...) in TitleCase | +| `fga-client.request.model_id` | `string` | The authorization model ID that was sent as part of the request, if any | +| `fga-client.request.store_id` | `string` | The store ID that was sent as part of the request | +| `fga-client.response.model_id` | `string` | The authorization model ID that the FGA server used | +| `fga-client.user` | `string` | The user that is associated with the action of the request for check and list objects | +| `http.host` | `string` | Host identifier of the origin the request was sent to | +| `http.request.method` | `string` | The HTTP method for the request | +| `http.request.resend_count` | `int` | The number of retries attempted (Only sent if the request was retried. Count of `1` means the request was retried once in addition to the original request) | +| `http.response.status_code` | `int` | The status code of the response | +| `url.full` | `string` | Full URL of the request | +| `url.scheme` | `string` | HTTP Scheme of the request (`http`/`https`) | +| `user_agent.original` | `string` | User Agent used in the query | diff --git a/example/example1/build.gradle b/example/example1/build.gradle index 9ea6879..a64d5fe 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.0") // 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..ea40ec5 100644 --- a/src/main/java/dev/openfga/sdk/api/OpenFgaApi.java +++ b/src/main/java/dev/openfga/sdk/api/OpenFgaApi.java @@ -15,14 +15,9 @@ import static dev.openfga.sdk.util.StringUtil.isNullOrWhitespace; import static dev.openfga.sdk.util.Validation.assertParamExists; -import dev.openfga.sdk.api.auth.OAuth2Client; -import dev.openfga.sdk.api.client.ApiClient; -import dev.openfga.sdk.api.client.ApiResponse; -import dev.openfga.sdk.api.client.HttpRequestAttempt; -import dev.openfga.sdk.api.client.OpenFgaClient; -import dev.openfga.sdk.api.configuration.Configuration; -import dev.openfga.sdk.api.configuration.ConfigurationOverride; -import dev.openfga.sdk.api.configuration.CredentialsMethod; +import dev.openfga.sdk.api.auth.*; +import dev.openfga.sdk.api.client.*; +import dev.openfga.sdk.api.configuration.*; import dev.openfga.sdk.api.model.CheckRequest; import dev.openfga.sdk.api.model.CheckResponse; import dev.openfga.sdk.api.model.CreateStoreRequest; @@ -45,8 +40,8 @@ import dev.openfga.sdk.api.model.WriteAuthorizationModelRequest; import dev.openfga.sdk.api.model.WriteAuthorizationModelResponse; import dev.openfga.sdk.api.model.WriteRequest; -import dev.openfga.sdk.errors.ApiException; -import dev.openfga.sdk.errors.FgaInvalidParameterException; +import dev.openfga.sdk.errors.*; +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 +49,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; @@ -77,7 +74,7 @@ public OpenFgaApi(Configuration configuration) throws FgaInvalidParameterExcepti public OpenFgaApi(Configuration configuration, ApiClient apiClient) throws FgaInvalidParameterException { this.apiClient = apiClient; this.configuration = configuration; - this.telemetry = new Telemetry(); + this.telemetry = new Telemetry(this.configuration); if (configuration.getCredentials().getCredentialsMethod() == CredentialsMethod.CLIENT_CREDENTIALS) { this.oAuth2Client = new OAuth2Client(configuration, apiClient); @@ -129,11 +126,18 @@ private CompletableFuture> check( String path = "/stores/{store_id}/check".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("body", body); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "Check"); + 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 +177,17 @@ private CompletableFuture> createStore( String path = "/stores"; + Map methodParameters = new HashMap<>(); + methodParameters.put("body", body); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.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 +226,17 @@ private CompletableFuture> deleteStore(String storeId, Configu String path = "/stores/{store_id}".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "DeleteStore"); + 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 +281,18 @@ private CompletableFuture> expand( String path = "/stores/{store_id}/expand".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("body", body); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "Expand"); + 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 +332,17 @@ private CompletableFuture> getStore(String storeId String path = "/stores/{store_id}".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "GetStore"); + 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 +387,18 @@ private CompletableFuture> listObjects( String path = "/stores/{store_id}/list-objects".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("body", body); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ListObjects"); + 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 +440,16 @@ private CompletableFuture> listStores( String path = "/stores"; path = pathWithParams(path, "page_size", pageSize, "continuation_token", continuationToken); + Map methodParameters = new HashMap<>(); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.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 +494,18 @@ private CompletableFuture> listUsers( String path = "/stores/{store_id}/list-users".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("body", body); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ListUsers"); + 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 +550,18 @@ private CompletableFuture> read( String path = "/stores/{store_id}/read".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("body", body); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "Read"); + 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 +608,19 @@ private CompletableFuture> readAssertions( .replace("{store_id}", ApiClient.urlEncode(storeId.toString())) .replace("{authorization_model_id}", ApiClient.urlEncode(authorizationModelId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("authorizationModelId", authorizationModelId); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ReadAssertions"); + 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 +666,14 @@ private CompletableFuture> readAutho .replace("{store_id}", ApiClient.urlEncode(storeId.toString())) .replace("{id}", ApiClient.urlEncode(id.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("id", id); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ReadAuthorizationModel"); + try { HttpRequest request = buildHttpRequest("GET", path, configuration); return new HttpRequestAttempt<>( @@ -611,9 +682,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 +731,13 @@ private CompletableFuture> readAuth .replace("{store_id}", ApiClient.urlEncode(storeId.toString())); path = pathWithParams(path, "page_size", pageSize, "continuation_token", continuationToken); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ReadAuthorizationModels"); + try { HttpRequest request = buildHttpRequest("GET", path, configuration); return new HttpRequestAttempt<>( @@ -670,8 +746,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 +800,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 methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "ReadChanges"); + 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 +854,18 @@ private CompletableFuture> write(String storeId, WriteReques String path = "/stores/{store_id}/write".replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("body", body); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "Write"); + 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 +920,19 @@ private CompletableFuture> writeAssertions( .replace("{store_id}", ApiClient.urlEncode(storeId.toString())) .replace("{authorization_model_id}", ApiClient.urlEncode(authorizationModelId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("authorizationModelId", authorizationModelId); + methodParameters.put("body", body); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "WriteAssertions"); + 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 +978,14 @@ private CompletableFuture> writeAut String path = "/stores/{store_id}/authorization-models" .replace("{store_id}", ApiClient.urlEncode(storeId.toString())); + Map methodParameters = new HashMap<>(); + methodParameters.put("storeId", storeId); + methodParameters.put("body", body); + + Map telemetryAttributes = buildTelemetryAttributes(methodParameters); + + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_METHOD, "WriteAuthorizationModel"); + try { HttpRequest request = buildHttpRequest("POST", path, body, configuration); return new HttpRequestAttempt<>( @@ -891,14 +994,85 @@ 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); } } + private Map buildTelemetryAttributes(Map attributes) { + Map telemetryAttributes = new HashMap<>(); + + Object storeId = attributes.get("storeId"); + Object authorizationModelId = attributes.get("authorizationModelId"); + Object body = attributes.get("body"); + + if (storeId != null) { + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_STORE_ID, storeId.toString()); + } + + if (authorizationModelId != null) { + telemetryAttributes.put(Attributes.FGA_CLIENT_REQUEST_MODEL_ID, authorizationModelId.toString()); + } + + if (body != null) { + if (body instanceof CheckRequest) { + CheckRequest checkRequest = (CheckRequest) body; + + if (checkRequest.getTupleKey() != null + && !isNullOrWhitespace(checkRequest.getTupleKey().getUser())) { + telemetryAttributes.put( + Attributes.FGA_CLIENT_USER, + checkRequest.getTupleKey().getUser()); + } + + if (!isNullOrWhitespace(checkRequest.getAuthorizationModelId())) { + telemetryAttributes.put( + Attributes.FGA_CLIENT_REQUEST_MODEL_ID, checkRequest.getAuthorizationModelId()); + } + } + + if (body instanceof ExpandRequest) { + ExpandRequest expandRequest = (ExpandRequest) body; + + if (!isNullOrWhitespace(expandRequest.getAuthorizationModelId())) { + telemetryAttributes.put( + Attributes.FGA_CLIENT_REQUEST_MODEL_ID, expandRequest.getAuthorizationModelId()); + } + } + + if (body instanceof ListObjectsRequest) { + ListObjectsRequest listObjectsRequest = (ListObjectsRequest) body; + + if (!isNullOrWhitespace(listObjectsRequest.getAuthorizationModelId())) { + telemetryAttributes.put( + Attributes.FGA_CLIENT_REQUEST_MODEL_ID, listObjectsRequest.getAuthorizationModelId()); + } + } + + if (body instanceof ListUsersRequest) { + ListUsersRequest listUsersRequest = (ListUsersRequest) body; + + if (!isNullOrWhitespace(listUsersRequest.getAuthorizationModelId())) { + telemetryAttributes.put( + Attributes.FGA_CLIENT_REQUEST_MODEL_ID, listUsersRequest.getAuthorizationModelId()); + } + } + + if (body instanceof WriteRequest) { + WriteRequest writeRequest = (WriteRequest) body; + + if (!isNullOrWhitespace(writeRequest.getAuthorizationModelId())) { + telemetryAttributes.put( + Attributes.FGA_CLIENT_REQUEST_MODEL_ID, writeRequest.getAuthorizationModelId()); + } + } + } + + return telemetryAttributes; + } + private HttpRequest buildHttpRequest(String method, String path, Configuration configuration) throws ApiException, FgaInvalidParameterException { return buildHttpRequestWithPublisher(method, path, HttpRequest.BodyPublishers.noBody(), configuration); 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..8a347f1 100644 --- a/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java +++ b/src/main/java/dev/openfga/sdk/api/auth/OAuth2Client.java @@ -12,10 +12,8 @@ package dev.openfga.sdk.api.auth; -import dev.openfga.sdk.api.client.ApiClient; -import dev.openfga.sdk.api.client.ApiResponse; -import dev.openfga.sdk.api.client.HttpRequestAttempt; -import dev.openfga.sdk.api.configuration.Configuration; +import dev.openfga.sdk.api.client.*; +import dev.openfga.sdk.api.configuration.*; import dev.openfga.sdk.errors.ApiException; import dev.openfga.sdk.errors.FgaInvalidParameterException; import dev.openfga.sdk.telemetry.Attribute; @@ -53,7 +51,7 @@ public OAuth2Client(Configuration configuration, ApiClient apiClient) throws Fga .apiUrl(buildApiTokenIssuer(clientCredentials.getApiTokenIssuer())) .maxRetries(configuration.getMaxRetries()) .minimumRetryDelay(configuration.getMinimumRetryDelay()); - this.telemetry = new Telemetry(); + this.telemetry = new Telemetry(this.config); } /** @@ -70,13 +68,6 @@ public CompletableFuture getAccessToken() throws FgaInvalidParameterExce Map attributesMap = new HashMap<>(); - try { - attributesMap.put( - dev.openfga.sdk.telemetry.Attributes.REQUEST_CLIENT_ID, - config.getCredentials().getClientCredentials().getClientId()); - } catch (Exception e) { - } - telemetry.metrics().credentialsRequest(1L, attributesMap); return CompletableFuture.completedFuture(token.getToken()); 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..74ccbf7 100644 --- a/src/main/java/dev/openfga/sdk/api/client/HttpRequestAttempt.java +++ b/src/main/java/dev/openfga/sdk/api/client/HttpRequestAttempt.java @@ -4,10 +4,7 @@ import static dev.openfga.sdk.util.Validation.assertParamExists; import dev.openfga.sdk.api.configuration.Configuration; -import dev.openfga.sdk.errors.ApiException; -import dev.openfga.sdk.errors.FgaError; -import dev.openfga.sdk.errors.FgaInvalidParameterException; -import dev.openfga.sdk.errors.HttpStatusCode; +import dev.openfga.sdk.errors.*; import dev.openfga.sdk.telemetry.Attribute; import dev.openfga.sdk.telemetry.Attributes; import dev.openfga.sdk.telemetry.Telemetry; @@ -22,9 +19,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Flow; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; public class HttpRequestAttempt { private final ApiClient apiClient; @@ -32,7 +27,7 @@ public class HttpRequestAttempt { private final Class clazz; private final String name; private final HttpRequest request; - private final Telemetry telemetry = new Telemetry(); + private final Telemetry telemetry; private Long requestStarted; private Map telemetryAttributes; @@ -48,6 +43,7 @@ public HttpRequestAttempt( this.name = name; this.request = request; this.clazz = clazz; + this.telemetry = new Telemetry(configuration); this.telemetryAttributes = new HashMap<>(); } @@ -79,15 +75,11 @@ public CompletableFuture> attemptHttpRequest() throws ApiExceptio requestBodyPublisher.subscribe(new BodyLogger(System.err, "request"))); } - addTelemetryAttribute(Attributes.HTTP_HOST, configuration.getApiUrl()); - addTelemetryAttribute(Attributes.HTTP_METHOD, request.method()); - - try { - addTelemetryAttribute( - Attributes.REQUEST_CLIENT_ID, - configuration.getCredentials().getClientCredentials().getClientId()); - } catch (Exception e) { - } + 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()); return attemptHttpRequest(createClient(), 0, null); } @@ -119,20 +111,25 @@ private CompletableFuture> attemptHttpRequest( } addTelemetryAttributes(Attributes.fromHttpResponse(response, this.configuration.getCredentials())); - addTelemetryAttribute(Attributes.REQUEST_RETRIES, String.valueOf(retryNumber)); + + if (retryNumber > 0) { + 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)) { + 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()); return deserializeResponse(response) .thenApply(modeledResponse -> new ApiResponse<>( diff --git a/src/main/java/dev/openfga/sdk/api/configuration/Configuration.java b/src/main/java/dev/openfga/sdk/api/configuration/Configuration.java index ce536a1..84cc97c 100644 --- a/src/main/java/dev/openfga/sdk/api/configuration/Configuration.java +++ b/src/main/java/dev/openfga/sdk/api/configuration/Configuration.java @@ -45,6 +45,7 @@ public class Configuration implements BaseConfiguration { private int maxRetries; private Duration minimumRetryDelay; private Map defaultHeaders; + private TelemetryConfiguration telemetryConfiguration; public Configuration() { this.apiUrl = DEFAULT_API_URL; @@ -124,6 +125,10 @@ public Configuration override(ConfigurationOverride configurationOverride) { } result.defaultHeaders(headers); + TelemetryConfiguration overrideTelemetryConfiguration = configurationOverride.getTelemetryConfiguration(); + result.telemetryConfiguration = + overrideTelemetryConfiguration != null ? overrideTelemetryConfiguration : telemetryConfiguration; + return result; } @@ -290,4 +295,13 @@ public Map getDefaultHeaders() { } return this.defaultHeaders; } + + public TelemetryConfiguration getTelemetryConfiguration() { + return telemetryConfiguration; + } + + public Configuration telemetryConfiguration(TelemetryConfiguration telemetryConfiguration) { + this.telemetryConfiguration = telemetryConfiguration; + return this; + } } diff --git a/src/main/java/dev/openfga/sdk/api/configuration/ConfigurationOverride.java b/src/main/java/dev/openfga/sdk/api/configuration/ConfigurationOverride.java index 0f06a52..ca73254 100644 --- a/src/main/java/dev/openfga/sdk/api/configuration/ConfigurationOverride.java +++ b/src/main/java/dev/openfga/sdk/api/configuration/ConfigurationOverride.java @@ -33,6 +33,7 @@ public class ConfigurationOverride implements BaseConfiguration { private Integer maxRetries; private Duration minimumRetryDelay; private Map additionalHeaders; + private TelemetryConfiguration telemetryConfiguration; public ConfigurationOverride() { this.apiUrl = null; @@ -41,6 +42,7 @@ public ConfigurationOverride() { this.readTimeout = null; this.connectTimeout = null; this.additionalHeaders = null; + this.telemetryConfiguration = null; } /** @@ -206,4 +208,13 @@ public ConfigurationOverride addHeaders(AdditionalHeadersSupplier supplier) { public Map getAdditionalHeaders() { return this.additionalHeaders; } + + public ConfigurationOverride telemetryConfiguration(TelemetryConfiguration telemetryConfiguration) { + this.telemetryConfiguration = telemetryConfiguration; + return this; + } + + public TelemetryConfiguration getTelemetryConfiguration() { + return telemetryConfiguration; + } } diff --git a/src/main/java/dev/openfga/sdk/api/configuration/TelemetryConfiguration.java b/src/main/java/dev/openfga/sdk/api/configuration/TelemetryConfiguration.java new file mode 100644 index 0000000..f8e82f7 --- /dev/null +++ b/src/main/java/dev/openfga/sdk/api/configuration/TelemetryConfiguration.java @@ -0,0 +1,42 @@ +package dev.openfga.sdk.api.configuration; + +import dev.openfga.sdk.telemetry.Attribute; +import dev.openfga.sdk.telemetry.Attributes; +import dev.openfga.sdk.telemetry.Counters; +import dev.openfga.sdk.telemetry.Histograms; +import dev.openfga.sdk.telemetry.Metric; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class TelemetryConfiguration { + private Map>> metrics = new HashMap<>(); + + public TelemetryConfiguration() { + Map> 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_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()); + + metrics.put(Counters.CREDENTIALS_REQUEST, defaultAttributes); + metrics.put(Histograms.QUERY_DURATION, defaultAttributes); + metrics.put(Histograms.REQUEST_DURATION, defaultAttributes); + } + + public TelemetryConfiguration metrics(Map>> metrics) { + this.metrics = metrics; + return this; + } + + public Map>> metrics() { + return metrics; + } +} diff --git a/src/main/java/dev/openfga/sdk/telemetry/Attributes.java b/src/main/java/dev/openfga/sdk/telemetry/Attributes.java index 65c9e11..6860870 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Attributes.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Attributes.java @@ -1,6 +1,9 @@ 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.Configuration; import dev.openfga.sdk.api.configuration.Credentials; import dev.openfga.sdk.api.configuration.CredentialsMethod; import io.opentelemetry.api.common.AttributeKey; @@ -8,60 +11,77 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; /** * 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 HTTP host used in the request. */ - public static final Attribute CLIENT_USER = new Attribute("fga-client.user"); + public static final Attribute HTTP_HOST = new Attribute("http.host"); /** - * Attribute representing the host of an HTTP request. + * The HTTP method used in the request. */ - public static final Attribute HTTP_HOST = new Attribute("http.host"); + 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_REQUEST_RESEND_COUNT = new Attribute("http.request.resend_count"); + + /** + * The HTTP status code returned by the server for the request. + */ + public static final Attribute HTTP_RESPONSE_STATUS_CODE = new Attribute("http.response.status_code"); + + /** + * The scheme used in the request. + */ + public static final Attribute URL_SCHEME = new Attribute("url.scheme"); /** - * Attribute representing the method of an HTTP request. + * The complete URL used in the request. */ - public static final Attribute HTTP_METHOD = new Attribute("http.method"); + public static final Attribute URL_FULL = new Attribute("url.full"); /** - * Attribute representing the status code of an HTTP response. + * The user agent used in the request. */ - public static final Attribute HTTP_STATUS_CODE = new Attribute("http.status_code"); + public static final Attribute USER_AGENT = new Attribute("user_agent.original"); /** * Prepares the attributes for OpenTelemetry publishing by converting them into the expected format. @@ -70,12 +90,41 @@ public class Attributes { * * @return the prepared attributes */ - public static io.opentelemetry.api.common.Attributes prepare(Map attributes) { + public static io.opentelemetry.api.common.Attributes prepare( + Map attributes, Metric metric, Configuration configuration) { + if (attributes == null + || attributes.isEmpty() + || configuration == null + || configuration.getTelemetryConfiguration() == null + || configuration.getTelemetryConfiguration().metrics() == null + || configuration.getTelemetryConfiguration().metrics().isEmpty() + || !configuration.getTelemetryConfiguration().metrics().containsKey(metric) + || configuration + .getTelemetryConfiguration() + .metrics() + .get(metric) + .isEmpty()) { + return io.opentelemetry.api.common.Attributes.empty(); + } + + Map> configAllowedAttributes = + configuration.getTelemetryConfiguration().metrics().get(metric); + io.opentelemetry.api.common.AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder(); - attributes.forEach((key, value) -> { - builder.put(AttributeKey.stringKey(key.getName()), value); - }); + for (Map.Entry> configAllowedAttr : configAllowedAttributes.entrySet()) { + Attribute attr = configAllowedAttr.getKey(); + + if (!attributes.containsKey(attr)) { + continue; + } + + String attrVal = attributes.getOrDefault(attr, ""); + + if (!isNullOrWhitespace(attrVal)) { + builder.put(AttributeKey.stringKey(attr.getName()), attrVal); + } + } return builder.build(); } @@ -92,19 +141,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 +175,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..ef7a966 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Counter.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Counter.java @@ -3,48 +3,14 @@ /** * Represents a counter used for telemetry purposes. */ -public class Counter { - private final String name; - private final String unit; - private final String description; - +public class Counter extends Metric { /** * 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) { - this.name = name; - this.unit = unit; - this.description = description; - } - - /** - * Returns the name of the counter. - * - * @return the name of the counter - */ - 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. - * - * @return the description of the counter - */ - public String getDescription() { - return description; + public Counter(String name, String description) { + super(name, description); } } 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..e6963c9 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Histogram.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Histogram.java @@ -3,10 +3,8 @@ /** * Represents a histogram for telemetry data. */ -public class Histogram { - private final String name; +public class Histogram extends Metric { private final String unit; - private final String description; /** * Constructs a Histogram object with the specified name, unit, and description. @@ -16,16 +14,19 @@ public class Histogram { * @param description the description of the histogram */ public Histogram(String name, String unit, String description) { - this.name = name; + super(name, description); this.unit = unit; - this.description = description; } /** - * Returns the name of the histogram. + * 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 description the description of the histogram */ - public String getName() { - return name; + public Histogram(String name, String description) { + super(name, description); + this.unit = "milliseconds"; } /** @@ -34,11 +35,4 @@ public String getName() { public String getUnit() { return unit; } - - /** - * Returns the description of the histogram. - */ - public String getDescription() { - return description; - } } diff --git a/src/main/java/dev/openfga/sdk/telemetry/Histograms.java b/src/main/java/dev/openfga/sdk/telemetry/Histograms.java index 82a7217..ebc84d6 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 total 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/Metric.java b/src/main/java/dev/openfga/sdk/telemetry/Metric.java new file mode 100644 index 0000000..5a0cd1b --- /dev/null +++ b/src/main/java/dev/openfga/sdk/telemetry/Metric.java @@ -0,0 +1,31 @@ +package dev.openfga.sdk.telemetry; + +public class Metric { + protected final String name; + protected final String description; + + /** + * Constructs a new metric with the specified name and description. + * + * @param name the name of the counter + * @param description the description of the counter + */ + public Metric(String name, String description) { + this.name = name; + this.description = description; + } + + /** + * Returns the name of the metric. + */ + public String getName() { + return name; + } + + /** + * Returns the description of the metric. + */ + public String getDescription() { + return description; + } +} diff --git a/src/main/java/dev/openfga/sdk/telemetry/Metrics.java b/src/main/java/dev/openfga/sdk/telemetry/Metrics.java index 3b3d564..4705715 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Metrics.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Metrics.java @@ -1,5 +1,6 @@ package dev.openfga.sdk.telemetry; +import dev.openfga.sdk.api.configuration.Configuration; import io.opentelemetry.api.OpenTelemetry; import io.opentelemetry.api.metrics.DoubleHistogram; import io.opentelemetry.api.metrics.LongCounter; @@ -15,11 +16,20 @@ public class Metrics { private final Meter meter; private final Map counters; private final Map histograms; + private final Configuration configuration; public Metrics() { this.meter = OpenTelemetry.noop().getMeterProvider().get("openfga-sdk/0.5.0"); this.counters = new HashMap<>(); this.histograms = new HashMap<>(); + this.configuration = new Configuration(); + } + + public Metrics(Configuration configuration) { + this.meter = OpenTelemetry.noop().getMeterProvider().get("openfga-sdk/0.5.0"); + this.counters = new HashMap<>(); + this.histograms = new HashMap<>(); + this.configuration = configuration; } /** @@ -46,14 +56,13 @@ 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 +117,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. diff --git a/src/main/java/dev/openfga/sdk/telemetry/Telemetry.java b/src/main/java/dev/openfga/sdk/telemetry/Telemetry.java index 9e0d669..eb3b507 100644 --- a/src/main/java/dev/openfga/sdk/telemetry/Telemetry.java +++ b/src/main/java/dev/openfga/sdk/telemetry/Telemetry.java @@ -1,18 +1,25 @@ package dev.openfga.sdk.telemetry; +import dev.openfga.sdk.api.configuration.Configuration; + /** * The Telemetry class provides access to telemetry-related functionality. */ public class Telemetry { + private Configuration configuration = new Configuration(); private Metrics metrics = null; + public Telemetry(Configuration configuration) { + this.configuration = configuration; + } + /** - * Returns the Metrics object for collecting telemetry data. - * If the Metrics object has not been initialized, it will be created. + * Returns a Metrics singleton for collecting telemetry data. + * If the Metrics singleton has not previously been initialized, it will be created. */ public Metrics metrics() { if (metrics == null) { - metrics = new Metrics(); + metrics = new Metrics(this.configuration); } return metrics; diff --git a/src/test/java/dev/openfga/sdk/api/configuration/TelemetryConfigurationTest.java b/src/test/java/dev/openfga/sdk/api/configuration/TelemetryConfigurationTest.java new file mode 100644 index 0000000..534ef30 --- /dev/null +++ b/src/test/java/dev/openfga/sdk/api/configuration/TelemetryConfigurationTest.java @@ -0,0 +1,147 @@ +package dev.openfga.sdk.api.configuration; + +import static org.junit.jupiter.api.Assertions.*; + +import dev.openfga.sdk.telemetry.Attribute; +import dev.openfga.sdk.telemetry.Attributes; +import dev.openfga.sdk.telemetry.Counters; +import dev.openfga.sdk.telemetry.Histograms; +import dev.openfga.sdk.telemetry.Metric; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +class TelemetryConfigurationTest { + + @Test + void testSetAndGetMetrics() { + // Arrange + TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(); + Map>> metrics = new HashMap<>(); + Metric metric = new Metric("testMetric", "A metric for testing"); + Map> attributes = new HashMap<>(); + Attribute attribute = new Attribute("testAttribute"); + attributes.put(attribute, Optional.of("testValue")); + metrics.put(metric, attributes); + + // Act + telemetryConfiguration.metrics(metrics); + + // Assert + assertEquals(metrics, telemetryConfiguration.metrics(), "The metrics map should match the one set."); + } + + @Test + void testDefaultMetrics() { + // Arrange + TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(); + Map>> metrics = telemetryConfiguration.metrics(); + + // Assert + assertNotNull(metrics, "The metrics map should not be null."); + assertFalse(metrics.isEmpty(), "The metrics map should not be empty."); + assertTrue( + metrics.containsKey(Counters.CREDENTIALS_REQUEST), + "The metrics map should contain the CREDENTIALS_REQUEST counter."); + assertTrue( + metrics.containsKey(Histograms.QUERY_DURATION), + "The metrics map should contain the QUERY_DURATION histogram."); + assertTrue( + metrics.containsKey(Histograms.REQUEST_DURATION), + "The metrics map should contain the REQUEST_DURATION histogram."); + + Map> defaultAttributes = metrics.get(Counters.CREDENTIALS_REQUEST); + assertNotNull(defaultAttributes, "The default attributes map should not be null."); + assertFalse(defaultAttributes.isEmpty(), "The default attributes map should not be empty."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID), + "The default attributes map should contain the FGA_CLIENT_REQUEST_CLIENT_ID attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_METHOD), + "The default attributes map should contain the FGA_CLIENT_REQUEST_METHOD attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_MODEL_ID), + "The default attributes map should contain the FGA_CLIENT_REQUEST_MODEL_ID attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_STORE_ID), + "The default attributes map should contain the FGA_CLIENT_REQUEST_STORE_ID attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_RESPONSE_MODEL_ID), + "The default attributes map should contain the FGA_CLIENT_RESPONSE_MODEL_ID attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.HTTP_HOST), + "The default attributes map should contain the HTTP_HOST attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.HTTP_REQUEST_RESEND_COUNT), + "The default attributes map should contain the HTTP_REQUEST_RESEND_COUNT attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.HTTP_RESPONSE_STATUS_CODE), + "The default attributes map should contain the HTTP_RESPONSE_STATUS_CODE attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.URL_FULL), + "The default attributes map should contain the URL_FULL attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.URL_SCHEME), + "The default attributes map should contain the URL_SCHEME attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.USER_AGENT), + "The default attributes map should contain the USER_AGENT attribute."); + + defaultAttributes = metrics.get(Histograms.QUERY_DURATION); + assertNotNull(defaultAttributes, "The default attributes map should not be null."); + assertFalse(defaultAttributes.isEmpty(), "The default attributes map should not be empty."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID), + "The default attributes map should contain the FGA_CLIENT_REQUEST_CLIENT_ID attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_METHOD), + "The default attributes map should contain the FGA_CLIENT_REQUEST_METHOD attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_MODEL_ID), + "The default attributes map should contain the FGA_CLIENT_REQUEST_MODEL_ID attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_REQUEST_STORE_ID), + "The default attributes map should contain the FGA_CLIENT_REQUEST_STORE_ID attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.FGA_CLIENT_RESPONSE_MODEL_ID), + "The default attributes map should contain the FGA_CLIENT_RESPONSE_MODEL_ID attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.HTTP_HOST), + "The default attributes map should contain the HTTP_HOST attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.HTTP_REQUEST_RESEND_COUNT), + "The default attributes map should contain the HTTP_REQUEST_RESEND_COUNT attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.HTTP_RESPONSE_STATUS_CODE), + "The default attributes map should contain the HTTP_RESPONSE_STATUS_CODE attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.URL_FULL), + "The default attributes map should contain the URL_FULL attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.URL_SCHEME), + "The default attributes map should contain the URL_SCHEME attribute."); + assertTrue( + defaultAttributes.containsKey(Attributes.USER_AGENT), + "The default attributes map should contain the USER_AGENT attribute."); + } + + @Test + void testOverridingDefaultMetrics() { + // Arrange + TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(); + Map>> metrics = telemetryConfiguration.metrics(); + Metric metric = new Metric("testMetric", "A metric for testing"); + Map> attributes = new HashMap<>(); + Attribute attribute = new Attribute("testAttribute"); + attributes.put(attribute, Optional.of("testValue")); + metrics.put(metric, attributes); + + // Assert + assertTrue(metrics.containsKey(metric), "The metrics map should contain the testMetric metric."); + assertEquals( + attributes, + metrics.get(metric), + "The attributes map for the testMetric metric should match the one set."); + } +} diff --git a/src/test/java/dev/openfga/sdk/telemetry/AttributeTest.java b/src/test/java/dev/openfga/sdk/telemetry/AttributeTest.java new file mode 100644 index 0000000..775ee7d --- /dev/null +++ b/src/test/java/dev/openfga/sdk/telemetry/AttributeTest.java @@ -0,0 +1,21 @@ +package dev.openfga.sdk.telemetry; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class AttributeTest { + + @Test + void testGetName() { + // Arrange + String attributeName = "testAttribute"; + Attribute attribute = new Attribute(attributeName); + + // Act + String result = attribute.getName(); + + // Assert + assertEquals(attributeName, result, "The name should match the one provided in the constructor."); + } +} diff --git a/src/test/java/dev/openfga/sdk/telemetry/AttributesTest.java b/src/test/java/dev/openfga/sdk/telemetry/AttributesTest.java new file mode 100644 index 0000000..c86c199 --- /dev/null +++ b/src/test/java/dev/openfga/sdk/telemetry/AttributesTest.java @@ -0,0 +1,103 @@ +package dev.openfga.sdk.telemetry; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import dev.openfga.sdk.api.client.ApiResponse; +import dev.openfga.sdk.api.configuration.ClientCredentials; +import dev.openfga.sdk.api.configuration.Configuration; +import dev.openfga.sdk.api.configuration.Credentials; +import dev.openfga.sdk.api.configuration.CredentialsMethod; +import dev.openfga.sdk.api.configuration.TelemetryConfiguration; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributesBuilder; +import java.net.http.HttpHeaders; +import java.net.http.HttpResponse; +import java.util.*; +import org.junit.jupiter.api.Test; + +class AttributesTest { + + @Test + void testPrepare() { + // Arrange + Map attributes = new HashMap<>(); + attributes.put(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, "client-id-value"); + Metric metric = mock(Metric.class); + + TelemetryConfiguration telemetryConfiguration = mock(TelemetryConfiguration.class); + Map>> metricsMap = new HashMap<>(); + Map> attributeMap = new HashMap<>(); + attributeMap.put(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, Optional.of("config-value")); + metricsMap.put(metric, attributeMap); + when(telemetryConfiguration.metrics()).thenReturn(metricsMap); + + Configuration configuration = mock(Configuration.class); + when(configuration.getTelemetryConfiguration()).thenReturn(telemetryConfiguration); + + // Act + io.opentelemetry.api.common.Attributes result = Attributes.prepare(attributes, metric, configuration); + + // Assert + AttributesBuilder builder = io.opentelemetry.api.common.Attributes.builder(); + builder.put(AttributeKey.stringKey(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID.getName()), "client-id-value"); + io.opentelemetry.api.common.Attributes expected = builder.build(); + + assertEquals(expected, result); + } + + @Test + void testFromHttpResponse() { + // Arrange + HttpResponse response = mock(HttpResponse.class); + HttpHeaders headers = mock(HttpHeaders.class); + when(response.headers()).thenReturn(headers); + when(headers.firstValue("openfga-authorization-model-id")).thenReturn(Optional.of("model-id-value")); + when(response.statusCode()).thenReturn(200); + + Credentials credentials = mock(Credentials.class); + ClientCredentials clientCredentials = mock(ClientCredentials.class); + when(credentials.getCredentialsMethod()).thenReturn(CredentialsMethod.CLIENT_CREDENTIALS); + when(credentials.getClientCredentials()).thenReturn(clientCredentials); + when(clientCredentials.getClientId()).thenReturn("client-id-value"); + + // Act + Map result = Attributes.fromHttpResponse(response, credentials); + + // Assert + Map expected = new HashMap<>(); + expected.put(Attributes.HTTP_RESPONSE_STATUS_CODE, "200"); + expected.put(Attributes.FGA_CLIENT_RESPONSE_MODEL_ID, "model-id-value"); + expected.put(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, "client-id-value"); + + assertEquals(expected, result); + } + + @Test + void testFromApiResponse() { + // Arrange + ApiResponse response = mock(ApiResponse.class); + Map> headers = new HashMap<>(); + headers.put("openfga-authorization-model-id", Collections.singletonList("model-id-value")); + when(response.getHeaders()).thenReturn(headers); + when(response.getStatusCode()).thenReturn(200); + + Credentials credentials = mock(Credentials.class); + ClientCredentials clientCredentials = mock(ClientCredentials.class); + when(credentials.getCredentialsMethod()).thenReturn(CredentialsMethod.CLIENT_CREDENTIALS); + when(credentials.getClientCredentials()).thenReturn(clientCredentials); + when(clientCredentials.getClientId()).thenReturn("client-id-value"); + + // Act + Map result = Attributes.fromApiResponse(response, credentials); + + // Assert + Map expected = new HashMap<>(); + expected.put(Attributes.HTTP_RESPONSE_STATUS_CODE, "200"); + expected.put(Attributes.FGA_CLIENT_RESPONSE_MODEL_ID, "model-id-value"); + expected.put(Attributes.FGA_CLIENT_REQUEST_CLIENT_ID, "client-id-value"); + + assertEquals(expected, result); + } +} diff --git a/src/test/java/dev/openfga/sdk/telemetry/CounterTest.java b/src/test/java/dev/openfga/sdk/telemetry/CounterTest.java new file mode 100644 index 0000000..4c849bd --- /dev/null +++ b/src/test/java/dev/openfga/sdk/telemetry/CounterTest.java @@ -0,0 +1,25 @@ +package dev.openfga.sdk.telemetry; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class CounterTest { + + @Test + void testCounterConstructor() { + // Arrange + String name = "testCounter"; + String description = "A counter for testing"; + + // Act + Counter counter = new Counter(name, description); + + // Assert + assertEquals(name, counter.getName(), "The name should match the one provided in the constructor."); + assertEquals( + description, + counter.getDescription(), + "The description should match the one provided in the constructor."); + } +} diff --git a/src/test/java/dev/openfga/sdk/telemetry/CountersTest.java b/src/test/java/dev/openfga/sdk/telemetry/CountersTest.java new file mode 100644 index 0000000..ab99add --- /dev/null +++ b/src/test/java/dev/openfga/sdk/telemetry/CountersTest.java @@ -0,0 +1,23 @@ +package dev.openfga.sdk.telemetry; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class CountersTest { + + @Test + void testCredentialsRequestCounter() { + // Arrange + String expectedName = "fga-client.credentials.request"; + String expectedDescription = + "The total number of times new access tokens have been requested using ClientCredentials."; + + // Act + Counter counter = Counters.CREDENTIALS_REQUEST; + + // Assert + assertEquals(expectedName, counter.getName(), "The name should match the expected value."); + assertEquals(expectedDescription, counter.getDescription(), "The description should match the expected value."); + } +} diff --git a/src/test/java/dev/openfga/sdk/telemetry/HistogramTest.java b/src/test/java/dev/openfga/sdk/telemetry/HistogramTest.java new file mode 100644 index 0000000..f344601 --- /dev/null +++ b/src/test/java/dev/openfga/sdk/telemetry/HistogramTest.java @@ -0,0 +1,45 @@ +package dev.openfga.sdk.telemetry; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class HistogramTest { + + @Test + void testHistogramConstructorWithUnit() { + // Arrange + String name = "testHistogram"; + String unit = "seconds"; + String description = "A histogram for testing"; + + // Act + Histogram histogram = new Histogram(name, unit, description); + + // Assert + assertEquals(name, histogram.getName(), "The name should match the one provided in the constructor."); + assertEquals(unit, histogram.getUnit(), "The unit should match the one provided in the constructor."); + assertEquals( + description, + histogram.getDescription(), + "The description should match the one provided in the constructor."); + } + + @Test + void testHistogramConstructorWithoutUnit() { + // Arrange + String name = "testHistogram"; + String description = "A histogram for testing"; + + // Act + Histogram histogram = new Histogram(name, description); + + // Assert + assertEquals(name, histogram.getName(), "The name should match the one provided in the constructor."); + assertEquals("milliseconds", histogram.getUnit(), "The default unit should be 'milliseconds'."); + assertEquals( + description, + histogram.getDescription(), + "The description should match the one provided in the constructor."); + } +} diff --git a/src/test/java/dev/openfga/sdk/telemetry/HistogramsTest.java b/src/test/java/dev/openfga/sdk/telemetry/HistogramsTest.java new file mode 100644 index 0000000..c1a3bc4 --- /dev/null +++ b/src/test/java/dev/openfga/sdk/telemetry/HistogramsTest.java @@ -0,0 +1,42 @@ +package dev.openfga.sdk.telemetry; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class HistogramsTest { + + @Test + void testRequestDurationHistogram() { + // Arrange + String expectedName = "fga-client.request.duration"; + String expectedDescription = + "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."; + + // Act + Histogram histogram = Histograms.REQUEST_DURATION; + + // Assert + assertEquals(expectedName, histogram.getName(), "The name should match the expected value."); + assertEquals("milliseconds", histogram.getUnit(), "The default unit should be 'milliseconds'."); + assertEquals( + expectedDescription, histogram.getDescription(), "The description should match the expected value."); + } + + @Test + void testQueryDurationHistogram() { + // Arrange + String expectedName = "fga-client.query.duration"; + String expectedDescription = + "The total time it took (in milliseconds) for the FGA server to process and evaluate the request."; + + // Act + Histogram histogram = Histograms.QUERY_DURATION; + + // Assert + assertEquals(expectedName, histogram.getName(), "The name should match the expected value."); + assertEquals("milliseconds", histogram.getUnit(), "The default unit should be 'milliseconds'."); + assertEquals( + expectedDescription, histogram.getDescription(), "The description should match the expected value."); + } +} diff --git a/src/test/java/dev/openfga/sdk/telemetry/MetricTest.java b/src/test/java/dev/openfga/sdk/telemetry/MetricTest.java new file mode 100644 index 0000000..018571b --- /dev/null +++ b/src/test/java/dev/openfga/sdk/telemetry/MetricTest.java @@ -0,0 +1,25 @@ +package dev.openfga.sdk.telemetry; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class MetricTest { + + @Test + void testMetricConstructor() { + // Arrange + String name = "testMetric"; + String description = "A metric for testing"; + + // Act + Metric metric = new Metric(name, description); + + // Assert + assertEquals(name, metric.getName(), "The name should match the one provided in the constructor."); + assertEquals( + description, + metric.getDescription(), + "The description should match the one provided in the constructor."); + } +} diff --git a/src/test/java/dev/openfga/sdk/telemetry/MetricsTest.java b/src/test/java/dev/openfga/sdk/telemetry/MetricsTest.java new file mode 100644 index 0000000..f335c31 --- /dev/null +++ b/src/test/java/dev/openfga/sdk/telemetry/MetricsTest.java @@ -0,0 +1,119 @@ +package dev.openfga.sdk.telemetry; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import dev.openfga.sdk.api.configuration.Configuration; +import io.opentelemetry.api.metrics.DoubleHistogram; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import java.util.HashMap; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class MetricsTest { + + private Metrics metrics; + private Configuration configuration; + + @BeforeEach + void setUp() { + configuration = mock(Configuration.class); + metrics = new Metrics(configuration); + } + + @Test + void testConstructorWithConfiguration() { + // Act + Metrics metricsWithConfig = new Metrics(configuration); + + // Assert + assertNotNull(metricsWithConfig.getMeter(), "The Meter object should not be null."); + } + + @Test + void testConstructorWithoutConfiguration() { + // Act + Metrics metricsWithoutConfig = new Metrics(); + + // Assert + assertNotNull(metricsWithoutConfig.getMeter(), "The Meter object should not be null."); + } + + @Test + void testGetMeter() { + // Act + Meter meter = metrics.getMeter(); + + // Assert + assertNotNull(meter, "The Meter object should not be null."); + } + + @Test + void testGetCounter() { + // Arrange + Counter counter = Counters.CREDENTIALS_REQUEST; + Long value = 10L; + Map attributes = new HashMap<>(); + + // Act + LongCounter longCounter = metrics.getCounter(counter, value, attributes); + + // Assert + assertNotNull(longCounter, "The LongCounter object should not be null."); + } + + @Test + void testGetHistogram() { + // Arrange + Histogram histogram = Histograms.REQUEST_DURATION; + Double value = 100.0; + Map attributes = new HashMap<>(); + + // Act + DoubleHistogram doubleHistogram = metrics.getHistogram(histogram, value, attributes); + + // Assert + assertNotNull(doubleHistogram, "The DoubleHistogram object should not be null."); + } + + @Test + void testCredentialsRequest() { + // Arrange + Long value = 5L; + Map attributes = new HashMap<>(); + + // Act + LongCounter longCounter = metrics.credentialsRequest(value, attributes); + + // Assert + assertNotNull(longCounter, "The LongCounter object should not be null."); + } + + @Test + void testRequestDuration() { + // Arrange + Double value = 200.0; + Map attributes = new HashMap<>(); + + // Act + DoubleHistogram doubleHistogram = metrics.requestDuration(value, attributes); + + // Assert + assertNotNull(doubleHistogram, "The DoubleHistogram object should not be null."); + } + + @Test + void testQueryDuration() { + // Arrange + Double value = 150.0; + Map attributes = new HashMap<>(); + + // Act + DoubleHistogram doubleHistogram = metrics.queryDuration(value, attributes); + + // Assert + assertNotNull(doubleHistogram, "The DoubleHistogram object should not be null."); + } +} diff --git a/src/test/java/dev/openfga/sdk/telemetry/TelemetryTest.java b/src/test/java/dev/openfga/sdk/telemetry/TelemetryTest.java new file mode 100644 index 0000000..71f8f8f --- /dev/null +++ b/src/test/java/dev/openfga/sdk/telemetry/TelemetryTest.java @@ -0,0 +1,25 @@ +package dev.openfga.sdk.telemetry; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +import dev.openfga.sdk.api.configuration.Configuration; +import org.junit.jupiter.api.Test; + +class TelemetryTest { + + @Test + void testMetricsInitialization() { + // Arrange + Configuration configuration = mock(Configuration.class); + Telemetry telemetry = new Telemetry(configuration); + + // Act + Metrics firstCall = telemetry.metrics(); + Metrics secondCall = telemetry.metrics(); + + // Assert + assertNotNull(firstCall, "The Metrics object should not be null after initialization."); + assertSame(firstCall, secondCall, "The same Metrics object should be returned on subsequent calls."); + } +}