Skip to content

Commit

Permalink
feat: add support for exporting metrics (#69)
Browse files Browse the repository at this point in the history
<!-- Thanks for opening a PR! Here are some quick tips:
If this is your first time contributing, [read our Contributing
Guidelines](https://github.com/openfga/.github/blob/main/CONTRIBUTING.md)
to learn how to create an acceptable PR for this repo.
By submitting a PR to this repository, you agree to the terms within the
[OpenFGA Code of
Conduct](https://github.com/openfga/.github/blob/main/CODE_OF_CONDUCT.md)

If your PR is under active development, please submit it as a "draft".
Once it's ready, open it up for review.
-->

<!-- Provide a brief summary of the changes -->

## Description

Introduces [OpenTelemetry](https://opentelemetry.io/) metrics reporting
into the SDK for specific actions. We're intentionally starting with
only a few metric events that we can then grow over time.

We don't have any formal documentation for this currently. We'll write
this as we roll it out across all SDKs, so here is a summary of the
events and the associated attributes:

## Metrics

### Supported Metrics

| Metric Name | Type | Enabled by Default | Description |

|---------------------------------|-----------|--------------------|--------------------------------------------------------------------------------------|
| `fga-client.request.duration` | Histogram | Yes | The total request
time for FGA requests |
| `fga-client.query.duration` | Histogram | Yes | The amount of time the
FGA server took to internally process nd evaluate the request |
|` fga-client.credentials.request`| Counter | Yes | The total number of
times a new token was requested when using ClientCredentials |
| `fga-client.request.count` | Counter | No | The total number of
requests made to the FGA server |

### Supported attributes

| Attribute Name | Type | Enabled by Default | Description |

|--------------------------------|----------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `fga-client.response.model_id` | `string` | Yes | The authorization
model ID that the FGA server used |
| `fga-client.request.method` | `string` | Yes | The FGA method/action
that was performed (e.g. `Check`, `ListObjects`, ...) in TitleCase |
| `fga-client.request.store_id` | `string` | Yes | The store ID that was
sent as part of the request |
| `fga-client.request.model_id` | `string` | Yes | The authorization
model ID that was sent as part of the request, if any |
| `fga-client.request.client_id` | `string` | Yes | The client ID
associated with the request, if any |
| `fga-client.user` | `string` | No | The user that is associated with
the action of the request for check and list objects |
| `http.request.resend_count` | `int` | Yes | 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` | Yes | The status code of the
response |
| `http.request.method` | `string` | No | The HTTP method for the
request |
| `http.host` | `string` | Yes | Host identifier of the origin the
request was sent to |
| `url.scheme` | `string` | No | HTTP Scheme of the request
(`http`/`https`) |
| `url.full` | `string` | No | Full URL of the request |
| `user_agent.original` | `string` | Yes | User Agent used in the query
|

## Testing

Spin up Prometheus, build the project and then run the
OpenTelemetryExample app.

You should see metrics in Prometheus such as:
```
fga_client_query_duration_bucket{exported_instance="28790af8-2116-4e47-baaf-319f9f23f518", exported_job=""openfga-otel-dotnet-example"", fga_client_request_client_id="3DSvMhkJwjoSBMTliPEdPjybbVewfVes", fga_client_request_method="Check", fga_client_request_model_id="01J41WCA3KBA9SP2M350FPDEJ0", fga_client_request_store_id="01J40DW2J9NNYNNVEC430S3Z97", fga_client_response_model_id="01J41WCA3KBA9SP2M350FPDEJ0", fga_client_user="user:anne", http_client_request_duration="40", http_host="api.us1.fga.dev", http_request_method="POST", http_response_status_code="200", http_server_request_duration="2", instance="otel-collector:8889", job="otel-collector", label1="value1", le="5", url_full="https://api.us1.fga.dev/stores/01J40DW2J9NNYNNVEC430S3Z97/check", url_scheme="https", user_agent_original="openfga-sdk dotnet/0.4.0"}
---
fga_client_query_duration_bucket{exported_instance="fbee572d-0bff-4957-95a2-c532fe7312c1", exported_job=""openfga-otel-dotnet-example"", fga_client_request_client_id="3DSvMhkJwjoSBMTliPEdPjybbVewfVes", fga_client_request_method="WriteAuthorizationModel", fga_client_request_store_id="01J40DW2J9NNYNNVEC430S3Z97", http_client_request_duration="139", http_host="api.us1.fga.dev", http_request_method="POST", http_request_resend_count="1", http_response_status_code="201", http_server_request_duration="69", instance="otel-collector:8889", job="otel-collector", label1="value1", le="75", url_full="https://api.us1.fga.dev/stores/01J40DW2J9NNYNNVEC430S3Z97/authorization-models", url_scheme="https", user_agent_original="openfga-sdk dotnet/0.4.0"}
---
fga_client_query_duration_bucket{exported_instance="fbee572d-0bff-4957-95a2-c532fe7312c1", exported_job=""openfga-otel-dotnet-example"", fga_client_request_client_id="3DSvMhkJwjoSBMTliPEdPjybbVewfVes", fga_client_request_method="Write", fga_client_request_model_id="01J41WCA3KBA9SP2M350FPDEJ0", fga_client_request_store_id="01J40DW2J9NNYNNVEC430S3Z97", fga_client_response_model_id="01J41WCA3KBA9SP2M350FPDEJ0", http_client_request_duration="68", http_host="api.us1.fga.dev", http_request_method="POST", http_request_resend_count="1", http_response_status_code="200", http_server_request_duration="30", instance="otel-collector:8889", job="otel-collector", label1="value1", le="5000", url_full="https://api.us1.fga.dev/stores/01J40DW2J9NNYNNVEC430S3Z97/write", url_scheme="https", user_agent_original="openfga-sdk dotnet/0.4.0"}
```

Also for client credential exchange, you should see metrics such as:
```
{__name__="fga_client_credentials_request", exported_instance="28790af8-2116-4e47-baaf-319f9f23f518", exported_job=""openfga-otel-dotnet-example"", fga_client_request_client_id="3DSvMhkJwjoSBMTliPEdPjybbVewfVes", fga_client_request_method="ClientCredentialsExchange", http_client_request_duration="1461", http_host="fga.us.auth0.com", http_request_method="POST", http_response_status_code="200", instance="otel-collector:8889", job="otel-collector", label1="value1", url_full="https://fga.us.auth0.com/oauth/token", url_scheme="https", user_agent_original="openfga-sdk dotnet/0.4.0"}
```

## References
<!-- Provide a list of any applicable references here (GitHub Issue,
[OpenFGA RFC](https://github.com/openfga/rfcs), other PRs, etc..) -->

Closes #68

Follows the implementations in JS
(openfga/js-sdk#117), Python
(openfga/python-sdk#107) and Java
(openfga/java-sdk#94)

## Review Checklist
- [x] I have clicked on ["allow edits by
maintainers"](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork).
- [ ] I have added documentation for new/changed functionality in this
PR or in a PR to [openfga.dev](https://github.com/openfga/openfga.dev)
[Provide a link to any relevant PRs in the references section above]
- [x] The correct base branch is being used, if not `main`
- [ ] I have added tests to validate that the change in functionality is
working as expected
  • Loading branch information
rhamzeh authored Sep 10, 2024
2 parents 87e221d + a3489ed commit ba3c108
Show file tree
Hide file tree
Showing 27 changed files with 1,631 additions and 339 deletions.
9 changes: 8 additions & 1 deletion .openapi-generator/FILES
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
.github/ISSUE_TEMPLATE/config.yaml
.github/ISSUE_TEMPLATE/feature_request.yaml
.github/dependabot.yaml
.github/workflows/main.yaml
.github/workflows/semgrep.yaml
.gitignore
CHANGELOG.md
CONTRIBUTING.md
LICENSE
NOTICE.txt
OpenFga.Sdk.sln
OpenTelemetry.md
README.md
VERSION.txt
assets/FGAIcon.png
Expand Down Expand Up @@ -100,6 +100,9 @@ docs/WriteRequestWrites.md
example/Example1/Example1.cs
example/Example1/Example1.csproj
example/Makefile
example/OpenTelemetryExample/.env.example
example/OpenTelemetryExample/OpenTelemetryExample.cs
example/OpenTelemetryExample/OpenTelemetryExample.csproj
example/README.md
src/OpenFga.Sdk.Test/Api/OpenFgaApiTests.cs
src/OpenFga.Sdk.Test/Client/OpenFgaClientTests.cs
Expand Down Expand Up @@ -250,3 +253,7 @@ src/OpenFga.Sdk/Model/WriteRequest.cs
src/OpenFga.Sdk/Model/WriteRequestDeletes.cs
src/OpenFga.Sdk/Model/WriteRequestWrites.cs
src/OpenFga.Sdk/OpenFga.Sdk.csproj
src/OpenFga.Sdk/Telemetry/Attributes.cs
src/OpenFga.Sdk/Telemetry/Counters.cs
src/OpenFga.Sdk/Telemetry/Histograms.cs
src/OpenFga.Sdk/Telemetry/Metrics.cs
9 changes: 7 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# Changelog

## v0.5.1

### [0.5.1](https://github.com/openfga/dotnet-sdk/compare/v0.5.0...v0.5.1) (2024-09-09)
- feat: export OpenTelemetry metrics. Refer to the [https://github.com/openfga/dotnet-sdk/blob/main/OpenTelemetry.md](documentation) for more.

## v0.5.0

### [0.5.0](https://github.com/openfga/dotnet-sdk/compare/v0.4.0...v0.5.0) (2024-08-28)

- feat: support consistency parameter (#70)
Note: To use this feature, you need to be running OpenFGA v1.5.7+ with the experimental flag `enable-consistency-params` enabled.
See the [v1.5.7 release notes](https://github.com/openfga/openfga/releases/tag/v1.5.7) for details.
Note: To use this feature, you need to be running OpenFGA v1.5.7+ with the experimental flag `enable-consistency-params` enabled.
See the [v1.5.7 release notes](https://github.com/openfga/openfga/releases/tag/v1.5.7) for details.

## v0.4.0

Expand Down
157 changes: 157 additions & 0 deletions OpenTelemetry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# 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 | Enabled by Default | Description |
|---------------------------------|-----------|--------------------|--------------------------------------------------------------------------------------|
| `fga-client.request.duration` | Histogram | Yes | The total request time for FGA requests |
| `fga-client.query.duration` | Histogram | Yes | The amount of time the FGA server took to internally process nd evaluate the request |
|` fga-client.credentials.request`| Counter | Yes | The total number of times a new token was requested when using ClientCredentials |
| `fga-client.request.count` | Counter | No | The total number of requests made to the FGA server |

### Supported attributes

| Attribute Name | Type | Enabled by Default | Description |
|--------------------------------|----------|--------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `fga-client.response.model_id` | `string` | Yes | The authorization model ID that the FGA server used |
| `fga-client.request.method` | `string` | Yes | The FGA method/action that was performed (e.g. `Check`, `ListObjects`, ...) in TitleCase |
| `fga-client.request.store_id` | `string` | Yes | The store ID that was sent as part of the request |
| `fga-client.request.model_id` | `string` | Yes | The authorization model ID that was sent as part of the request, if any |
| `fga-client.request.client_id` | `string` | Yes | The client ID associated with the request, if any |
| `fga-client.user` | `string` | No | The user that is associated with the action of the request for check and list objects |
| `http.request.resend_count` | `int` | Yes | 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` | Yes | The status code of the response |
| `http.request.method` | `string` | No | The HTTP method for the request |
| `http.host` | `string` | Yes | Host identifier of the origin the request was sent to |
| `url.scheme` | `string` | No | HTTP Scheme of the request (`http`/`https`) |
| `url.full` | `string` | No | Full URL of the request |
| `user_agent.original` | `string` | Yes | User Agent used in the query |

### Default Metrics

Not all metrics and attributes are enabled by default.

Some attributes, like `fga-client.user` have been disabled by default due to their high cardinality, which may result for very high costs when using some SaaS metric collectors.
If you expect to have a high cardinality for a specific attribute, you can disable it by updating the `TelemetryConfig` accordingly.

```csharp

## Configuration

See the OpenTelemetry docs on [Customizing the SDK](https://github.com/open-telemetry/opentelemetry-dotnet/blob/main/docs/metrics/customizing-the-sdk/README.md).
```csharp
using OpenFga.Sdk.Client;
using OpenFga.Sdk.Client.Model;
using OpenFga.Sdk.Model;
using OpenFga.Sdk.Telemetry;
using OpenTelemetry;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using System.Diagnostics;

namespace Example {
public class Example {
public static async Task Main() {
try {
// Setup OpenTelemetry Metrics
using var meterProvider = Sdk.CreateMeterProviderBuilder()
.AddHttpClientInstrumentation() // To instrument the default http client
.AddMeter(Metrics.Name) // .AddMeter("OpenFga.Sdk") also works
.ConfigureResource(resourceBuilder => resourceBuilder.AddService("openfga-dotnet-example"))
.AddOtlpExporter() // Required to export to an OTLP compatible endpoint
.AddConsoleExporter() // Only needed to export the metrics to the console (e.g. when debugging)
.Build();

// Configure the OpenFGA SDK with default configuration (default metrics and attributes will be enabled)
var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"),
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"),
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"),
// Credentials = ... // If needed
};
var fgaClient = new OpenFgaClient(configuration);

// Call the SDK normally
var response = await fgaClient.ReadAuthorizationModels();
} catch (ApiException e) {
Debug.Print("Error: "+ e);
}
}
}
}
```

#### Customize metrics
You can can customize the metrics that are enabled and the attributes that are included in the metrics by setting the `TelemetryConfig` property on the `ClientConfiguration` object.
If you do set the `Telemetry` property to anything other than `null`, the default configuration will be overridden.

```csharp
TelemetryConfig telemetryConfig = new () {
Metrics = new Dictionary<string, MetricConfig> {
[TelemetryMeters.TokenExchangeCount] = new () {
Attributes = new HashSet<string> {
TelemetryAttribute.HttpScheme,
TelemetryAttribute.HttpMethod,
TelemetryAttribute.HttpHost,
TelemetryAttribute.HttpStatus,
TelemetryAttribute.HttpUserAgent,
TelemetryAttribute.RequestMethod,
TelemetryAttribute.RequestClientId,
TelemetryAttribute.RequestStoreId,
TelemetryAttribute.RequestModelId,
TelemetryAttribute.RequestRetryCount,
TelemetryAttribute.ResponseModelId
}
},
[TelemetryMeters.QueryDuration] = new () {
Attributes = new HashSet<string> {
TelemetryAttribute.HttpStatus,
TelemetryAttribute.HttpUserAgent,
TelemetryAttribute.RequestMethod,
TelemetryAttribute.RequestClientId,
TelemetryAttribute.RequestStoreId,
TelemetryAttribute.RequestModelId,
TelemetryAttribute.RequestRetryCount,
}
},
[TelemetryMeters.QueryDuration] = new () {
Attributes = new HashSet<string> {
TelemetryAttribute.HttpStatus,
TelemetryAttribute.HttpUserAgent,
TelemetryAttribute.RequestMethod,
TelemetryAttribute.RequestClientId,
TelemetryAttribute.RequestStoreId,
TelemetryAttribute.RequestModelId,
TelemetryAttribute.RequestRetryCount,
}
},
}
};

var configuration = new ClientConfiguration() {
ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"),
StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"),
AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"),
// Credentials = ... // If needed
Telemetry = telemetryConfig
};
```

### More Resources
* [OpenTelemetry.Instrumentation.Http](https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/main/src/OpenTelemetry.Instrumentation.Http/README.md) for instrumenting the HttpClient.
* If you are using .NET 8+, checkout the built-in metrics.

A number of these metrics are baked into .NET 8+ as well:

## Example

There is an [example project](https://github.com/openfga/dotnet-sdk/blob/main/example/OpenTelemetryExample) that provides some guidance on how to configure OpenTelemetry available in the examples directory.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ This is an autogenerated SDK for OpenFGA. It provides a wrapper around the [Open
- [Retries](#retries)
- [API Endpoints](#api-endpoints)
- [Models](#models)
- [OpenTelemetry](#opentelemetry)
- [Contributing](#contributing)
- [Issues](#issues)
- [Pull Requests](#pull-requests)
Expand Down Expand Up @@ -928,6 +929,10 @@ namespace 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/dotnet-sdk/blob/main/OpenTelemetry.md)

## Contributing

### Issues
Expand Down
2 changes: 1 addition & 1 deletion example/Example1/Example1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static async Task Main() {
credentials.Method = CredentialsMethod.ClientCredentials;
credentials.Config = new CredentialsConfig() {
ApiAudience = Environment.GetEnvironmentVariable("FGA_API_AUDIENCE"),
ApiTokenIssuer = Environment.GetEnvironmentVariable("FGA_TOKEN_ISSUER"),
ApiTokenIssuer = Environment.GetEnvironmentVariable("FGA_API_TOKEN_ISSUER"),
ClientId = Environment.GetEnvironmentVariable("FGA_CLIENT_ID"),
ClientSecret = Environment.GetEnvironmentVariable("FGA_CLIENT_SECRET")
};
Expand Down
11 changes: 11 additions & 0 deletions example/OpenTelemetryExample/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Configuration for OpenFGA
FGA_CLIENT_ID=
FGA_API_TOKEN_ISSUER=
FGA_API_AUDIENCE=
FGA_CLIENT_SECRET=
FGA_STORE_ID=
FGA_AUTHORIZATION_MODEL_ID=
FGA_API_URL="http://localhost:8080"

# Configuration for OpenTelemetry
OTEL_SERVICE_NAME="openfga-otel-dotnet-example"
Loading

0 comments on commit ba3c108

Please sign in to comment.