Skip to content

Commit

Permalink
fix: OpenTelemetry bugfixes (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
evansims authored Sep 13, 2024
2 parents 4ecb08f + b3225c0 commit ce12fcf
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 20 deletions.
4 changes: 2 additions & 2 deletions openfga_sdk/sync/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,12 @@ def __call_api(

self._telemetry.metrics().queryDuration(
attributes=_telemetry_attributes,
configuration=self.configuration,
configuration=self.configuration.telemetry,
)

self._telemetry.metrics().requestDuration(
attributes=_telemetry_attributes,
configuration=self.configuration,
configuration=self.configuration.telemetry,
)

if not _preload_content:
Expand Down
73 changes: 62 additions & 11 deletions openfga_sdk/telemetry/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,53 +11,69 @@

class TelemetryAttribute(NamedTuple):
name: str
format: str = None


class TelemetryAttributes:
fga_client_request_client_id: TelemetryAttribute = TelemetryAttribute(
name="fga-client.request.client_id",
format="string",
)
fga_client_request_method: TelemetryAttribute = TelemetryAttribute(
name="fga-client.request.method",
format="string",
)
fga_client_request_model_id: TelemetryAttribute = TelemetryAttribute(
name="fga-client.request.model_id",
format="string",
)
fga_client_request_store_id: TelemetryAttribute = TelemetryAttribute(
name="fga-client.request.store_id",
format="string",
)
fga_client_response_model_id: TelemetryAttribute = TelemetryAttribute(
name="fga-client.response.model_id",
format="string",
)
fga_client_user: TelemetryAttribute = TelemetryAttribute(
name="fga-client.user",
format="string",
)
http_client_request_duration: TelemetryAttribute = TelemetryAttribute(
name="http.client.request.duration",
format="int",
)
http_host: TelemetryAttribute = TelemetryAttribute(
name="http.host",
format="string",
)
http_request_method: TelemetryAttribute = TelemetryAttribute(
name="http.request.method",
format="string",
)
http_request_resend_count: TelemetryAttribute = TelemetryAttribute(
name="http.request.resend_count",
format="int",
)
http_response_status_code: TelemetryAttribute = TelemetryAttribute(
name="http.response.status_code",
format="int",
)
http_server_request_duration: TelemetryAttribute = TelemetryAttribute(
name="http.server.request.duration",
format="int",
)
url_scheme: TelemetryAttribute = TelemetryAttribute(
name="url.scheme",
format="string",
)
url_full: TelemetryAttribute = TelemetryAttribute(
name="url.full",
format="string",
)
user_agent_original: TelemetryAttribute = TelemetryAttribute(
name="user_agent.original",
format="string",
)

def prepare(
Expand All @@ -67,23 +83,58 @@ def prepare(
) -> dict[str, str | int]:
response = {}

if filter is None or filter == []:
return response

if attributes is not None:
for attribute, value in attributes.items():
if isinstance(attribute, TelemetryAttribute):
if filter is not None and attribute not in filter:
continue
if value is None:
continue

if isinstance(attribute, str):
attributeTranslated = (
attribute.lower().replace("-", "_").replace(".", "_")
)
attributeInstance = getattr(self, attributeTranslated, None)

if attributeInstance is None:
raise ValueError("Invalid attribute specified: %s" % attribute)

response[attribute.name] = value
attribute = attributeInstance

if not isinstance(attribute, TelemetryAttribute):
raise ValueError(
"Invalid attribute specified: %s" % type(attribute)
)

if (
filter is not None
and attribute.name not in filter
and attribute not in filter
):
continue

if attribute in self.__dict__:
if filter is not None and self.__dict__[attribute] not in filter:
if attribute.format == "string":
if not isinstance(value, str):
try:
value = str(value)
except ValueError:
continue

if value == "":
continue

response[self.__dict__[attribute].name] = value
continue
if attribute.format == "int":
if not isinstance(value, int):
try:
value = int(value)
except ValueError:
continue

response[attribute.name] = value
continue

return dict(sorted(response.items()))
return response

def fromRequest(
self,
Expand Down Expand Up @@ -114,8 +165,8 @@ def fromRequest(
attributes[self.url_full.name] = url

if start is not None and start > 0:
attributes[self.http_client_request_duration.name] = float(
time.time() - start
attributes[self.http_client_request_duration.name] = int(
(time.time() - start) * 1000
)

if resend_count is not None:
Expand Down
46 changes: 39 additions & 7 deletions openfga_sdk/telemetry/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,12 +122,28 @@ def requestDuration(
configuration = TelemetryConfiguration()

if (
isinstance(configuration, TelemetryMetricsConfiguration) is False
isinstance(configuration, TelemetryConfiguration) is False
or configuration.metrics.histogram_request_duration.enabled is False
or configuration.metrics.histogram_request_duration.attributes() == {}
):
return self.histogram(TelemetryHistograms.request_duration)

if (
value is None
and TelemetryAttributes.http_client_request_duration.name in attributes
):
value = attributes[TelemetryAttributes.http_client_request_duration.name]
attributes.pop(TelemetryAttributes.http_client_request_duration.name, None)

if value is not None:
try:
value = int(value)
attributes[TelemetryAttributes.http_client_request_duration.name] = (
value
)
except ValueError:
value = None

attributes = TelemetryAttributes().prepare(
attributes,
filter=configuration.metrics.histogram_request_duration.attributes(),
Expand All @@ -139,6 +155,9 @@ def requestDuration(
):
value = attributes[TelemetryAttributes.http_client_request_duration.name]

if value is None:
return self.histogram(TelemetryHistograms.request_duration)

return self.histogram(TelemetryHistograms.request_duration, value, attributes)

def queryDuration(
Expand All @@ -151,21 +170,34 @@ def queryDuration(
configuration = TelemetryConfiguration()

if (
isinstance(configuration, TelemetryMetricsConfiguration) is False
isinstance(configuration, TelemetryConfiguration) is False
or configuration.metrics.histogram_query_duration.enabled is False
or configuration.metrics.histogram_query_duration.attributes() == {}
):
return self.histogram(TelemetryHistograms.query_duration)

attributes = TelemetryAttributes().prepare(
attributes,
filter=configuration.metrics.histogram_query_duration.attributes(),
)

if (
value is None
and TelemetryAttributes.http_server_request_duration.name in attributes
):
value = attributes[TelemetryAttributes.http_server_request_duration.name]
attributes.pop(TelemetryAttributes.http_server_request_duration.name, None)

if value is not None:
try:
value = int(value)
attributes[TelemetryAttributes.http_server_request_duration.name] = (
value
)
except ValueError:
value = None

attributes = TelemetryAttributes().prepare(
attributes,
filter=configuration.metrics.histogram_query_duration.attributes(),
)

if value is None:
return self.histogram(TelemetryHistograms.query_duration)

return self.histogram(TelemetryHistograms.query_duration, value, attributes)
10 changes: 10 additions & 0 deletions test/telemetry/attributes_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@ def test_prepare_with_none_attributes(telemetry_attributes):
assert prepared == {}


def test_prepare_with_invalid_attributes(telemetry_attributes):
attributes = {
"invalid_attribute": "value",
}
filters = [telemetry_attributes.fga_client_request_client_id]

with pytest.raises(ValueError):
telemetry_attributes.prepare(attributes, filter=filters)


def test_from_request_with_all_params(telemetry_attributes):
credentials = Credentials(
method="client_credentials",
Expand Down

0 comments on commit ce12fcf

Please sign in to comment.