Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add OpenTelemetry metrics reporting #107

Merged
merged 17 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion example/example1/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

NAME = "example1"
VERSION = "0.0.1"
REQUIRES = ["openfga-sdk >= 0.3"]
REQUIRES = ["openfga-sdk >= 0.5"]

setup(
name=NAME,
Expand Down
7 changes: 7 additions & 0 deletions example/opentelemetry/.env.example
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"
1 change: 1 addition & 0 deletions example/opentelemetry/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.env
43 changes: 43 additions & 0 deletions example/opentelemetry/README.md
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
```
151 changes: 151 additions & 0 deletions example/opentelemetry/main.py
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
evansims marked this conversation as resolved.
Show resolved Hide resolved
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())
3 changes: 3 additions & 0 deletions example/opentelemetry/requirements.txt
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
2 changes: 2 additions & 0 deletions example/opentelemetry/setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[flake8]
max-line-length=99
30 changes: 30 additions & 0 deletions example/opentelemetry/setup.py
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",
)
Loading