-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add OpenTelemetry metrics reporting (#107)
- Loading branch information
Showing
22 changed files
with
844 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
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" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# OpenTelemetry usage with OpenFGA's Python SDK | ||
|
||
This example demonstrates how you can use OpenTelemetry with OpenFGA's Python SDK. | ||
|
||
## Prerequisites | ||
|
||
If you do not already have an OpenFGA instance running, you can start one using the following command: | ||
|
||
```bash | ||
docker run -d -p 8080:8080 openfga/openfga | ||
``` | ||
|
||
You need to have an OpenTelemetry collector running to receive data. A pre-configured collector is available using Docker: | ||
|
||
```bash | ||
git clone https://github.com/ewanharris/opentelemetry-collector-dev-setup.git | ||
cd opentelemetry-collector-dev-setup | ||
docker-compose up -d | ||
``` | ||
|
||
## Configure the example | ||
|
||
You need to configure the example for your environment: | ||
|
||
```bash | ||
cp .env.example .env | ||
``` | ||
|
||
Now edit the `.env` file and set the values as appropriate. | ||
|
||
## Running the example | ||
|
||
Begin by installing the required dependencies: | ||
|
||
```bash | ||
pip install -r requirements.txt | ||
``` | ||
|
||
Next, run the example: | ||
|
||
```bash | ||
python main.py | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import asyncio | ||
import os | ||
import sys | ||
from operator import attrgetter | ||
from random import randint | ||
from typing import Any | ||
|
||
from dotenv import load_dotenv | ||
from opentelemetry import metrics | ||
from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter | ||
from opentelemetry.sdk.metrics import MeterProvider | ||
from opentelemetry.sdk.metrics.export import ( | ||
ConsoleMetricExporter, | ||
PeriodicExportingMetricReader, | ||
) | ||
from opentelemetry.sdk.resources import SERVICE_NAME, Resource | ||
|
||
# For usage convenience of this example, we will import the OpenFGA SDK from the parent directory. | ||
sdk_path = os.path.realpath(os.path.join(os.path.abspath(__file__), "..", "..", "..")) | ||
sys.path.insert(0, sdk_path) | ||
|
||
from openfga_sdk import ( | ||
ClientConfiguration, | ||
OpenFgaClient, | ||
ReadRequestTupleKey, | ||
) | ||
from openfga_sdk.client.models import ClientCheckRequest | ||
from openfga_sdk.credentials import ( | ||
CredentialConfiguration, | ||
Credentials, | ||
) | ||
from openfga_sdk.exceptions import FgaValidationException | ||
|
||
|
||
class app: | ||
""" | ||
An example class to demonstrate how to implement the OpenFGA SDK with OpenTelemetry. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
client: OpenFgaClient = None, | ||
credentials: Credentials = None, | ||
configuration: ClientConfiguration = None, | ||
): | ||
""" | ||
Initialize the example with the provided client, credentials, and configuration. | ||
""" | ||
|
||
self._client = client | ||
self._credentials = credentials | ||
self._configuration = configuration | ||
|
||
async def fga_client(self, env: dict[str, str] = {}) -> OpenFgaClient: | ||
""" | ||
Build an OpenFGA client with the provided credentials and configuration. If not provided, load from environment variables. | ||
""" | ||
|
||
if not self._client or not self._credentials or not self._configuration: | ||
load_dotenv() | ||
|
||
if not self._credentials: | ||
self._credentials = Credentials( | ||
method="client_credentials", | ||
configuration=CredentialConfiguration( | ||
client_id=os.getenv("FGA_CLIENT_ID"), | ||
client_secret=os.getenv("FGA_CLIENT_SECRET"), | ||
api_issuer=os.getenv("FGA_API_TOKEN_ISSUER"), | ||
api_audience=os.getenv("FGA_API_AUDIENCE"), | ||
), | ||
) | ||
|
||
if not self._configuration: | ||
self._configuration = ClientConfiguration( | ||
api_url=os.getenv("FGA_API_URL"), | ||
store_id=os.getenv("FGA_STORE_ID"), | ||
authorization_model_id=os.getenv("FGA_AUTHORIZATION_MODEL_ID"), | ||
credentials=self._credentials, | ||
) | ||
|
||
if not self._client: | ||
return OpenFgaClient(self._configuration) | ||
|
||
return self._client | ||
|
||
def configure_telemetry(self) -> None: | ||
""" | ||
Configure OpenTelemetry with the provided meter provider. | ||
""" | ||
|
||
exporters = [] | ||
exporters.append(PeriodicExportingMetricReader(OTLPMetricExporter())) | ||
|
||
if os.getenv("OTEL_EXPORTER_CONSOLE") == "true": | ||
exporters.append(PeriodicExportingMetricReader(ConsoleMetricExporter())) | ||
|
||
metrics.set_meter_provider( | ||
MeterProvider( | ||
resource=Resource(attributes={SERVICE_NAME: "openfga-python-example"}), | ||
metric_readers=[exporter for exporter in exporters], | ||
) | ||
) | ||
|
||
def unpack( | ||
self, | ||
response, | ||
attr: str, | ||
) -> Any: | ||
""" | ||
Shortcut to unpack a FGA response and return the desired attribute. | ||
Note: This is a simple example and does not handle errors or exceptions. | ||
""" | ||
|
||
return attrgetter(attr)(response) | ||
|
||
|
||
async def main(): | ||
app().configure_telemetry() | ||
|
||
async with await app().fga_client() as fga_client: | ||
print("Client created successfully.") | ||
|
||
print("Reading authorization model ...", end=" ") | ||
authorization_models = app().unpack( | ||
await fga_client.read_authorization_models(), "authorization_models" | ||
) | ||
print(f"Done! Models Count: {len(authorization_models)}") | ||
|
||
print("Reading tuples ...", end=" ") | ||
tuples = app().unpack(await fga_client.read(ReadRequestTupleKey()), "tuples") | ||
print(f"Done! Tuples Count: {len(tuples)}") | ||
|
||
checks_requests = randint(1, 10) | ||
|
||
print(f"Making {checks_requests} checks ...", end=" ") | ||
for _ in range(checks_requests): | ||
try: | ||
allowed = app().unpack( | ||
await fga_client.check( | ||
body=ClientCheckRequest( | ||
user="user:anne", relation="owner", object="folder:foo" | ||
), | ||
), | ||
"allowed", | ||
) | ||
except FgaValidationException as error: | ||
print(f"Checked failed due to validation exception: {error}") | ||
print("Done!") | ||
|
||
|
||
asyncio.run(main()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
python-dotenv >= 1, <2 | ||
opentelemetry-sdk >= 1, <2 | ||
opentelemetry-exporter-otlp-proto-grpc >= 1.25, <2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
[flake8] | ||
max-line-length=99 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
""" | ||
Python SDK for OpenFGA | ||
API version: 0.1 | ||
Website: https://openfga.dev | ||
Documentation: https://openfga.dev/docs | ||
Support: https://discord.gg/8naAwJfWN6 | ||
License: [Apache-2.0](https://github.com/openfga/python-sdk/blob/main/LICENSE) | ||
NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). DO NOT EDIT. | ||
""" | ||
|
||
from setuptools import find_packages, setup | ||
|
||
NAME = "openfga-sdk" | ||
VERSION = "0.0.1" | ||
REQUIRES = [""] | ||
|
||
setup( | ||
name=NAME, | ||
version=VERSION, | ||
description="An example of using the OpenFGA Python SDK with OpenTelemetry", | ||
author="OpenFGA (https://openfga.dev)", | ||
author_email="community@openfga.dev", | ||
url="https://github.com/openfga/python-sdk", | ||
python_requires=">=3.10", | ||
packages=find_packages(exclude=["test", "tests"]), | ||
include_package_data=True, | ||
license="Apache-2.0", | ||
) |
Oops, something went wrong.