Skip to content

Commit

Permalink
feat: add OpenTelemetry metrics reporting (#107)
Browse files Browse the repository at this point in the history
  • Loading branch information
evansims authored Jun 28, 2024
2 parents 9056bb8 + 67ce370 commit 99ab813
Show file tree
Hide file tree
Showing 22 changed files with 844 additions and 9 deletions.
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
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

0 comments on commit 99ab813

Please sign in to comment.