Skip to content

Commit e97092c

Browse files
author
Evan Sims
committed
fix(telemetry): fixes for telemetry attributes and metrics tracking
- Improved client request duration handling. - Fixed attribute filtering issue in some configurations. - Updated `ClientConfiguration` to include telemetry configuration (like `Configuration`.) - Enhanced tests to validate new telemetry attributes and ensure correct duration calculations.
1 parent cefa972 commit e97092c

File tree

7 files changed

+59
-8
lines changed

7 files changed

+59
-8
lines changed

openfga_sdk/api_client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ async def __call_api(
292292
response=e.body.decode("utf-8"),
293293
credentials=self.configuration.credentials,
294294
attributes=_telemetry_attributes,
295+
start=start,
295296
)
296297

297298
self._telemetry.metrics.request(
@@ -323,6 +324,7 @@ async def __call_api(
323324
response=e,
324325
credentials=self.configuration.credentials,
325326
attributes=_telemetry_attributes,
327+
start=start,
326328
)
327329

328330
self._telemetry.metrics.request(
@@ -349,6 +351,7 @@ async def __call_api(
349351
response=response_data,
350352
credentials=self.configuration.credentials,
351353
attributes=_telemetry_attributes,
354+
start=start,
352355
)
353356

354357
self._telemetry.metrics.request(

openfga_sdk/client/configuration.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@
1212

1313
from openfga_sdk.configuration import Configuration
1414
from openfga_sdk.exceptions import FgaValidationException
15+
from openfga_sdk.telemetry.attributes import TelemetryAttribute
16+
from openfga_sdk.telemetry.configuration import (
17+
TelemetryConfigurationType,
18+
TelemetryMetricConfiguration,
19+
TelemetryMetricsConfiguration,
20+
)
21+
from openfga_sdk.telemetry.counters import TelemetryCounter
22+
from openfga_sdk.telemetry.histograms import TelemetryHistogram
1523
from openfga_sdk.validation import is_well_formed_ulid_string
1624

1725

@@ -31,6 +39,20 @@ def __init__(
3139
ssl_ca_cert=None,
3240
api_url=None, # TODO: restructure when removing api_scheme/api_host
3341
timeout_millisec: int | None = None,
42+
telemetry: (
43+
dict[
44+
TelemetryConfigurationType | str,
45+
TelemetryMetricsConfiguration
46+
| dict[
47+
TelemetryHistogram | TelemetryCounter | str,
48+
TelemetryMetricConfiguration
49+
| dict[TelemetryAttribute | str, bool]
50+
| None,
51+
]
52+
| None,
53+
]
54+
| None
55+
) = None,
3456
):
3557
super().__init__(
3658
api_scheme,
@@ -41,6 +63,7 @@ def __init__(
4163
ssl_ca_cert=ssl_ca_cert,
4264
api_url=api_url,
4365
timeout_millisec=timeout_millisec,
66+
telemetry=telemetry,
4467
)
4568
self._authorization_model_id = authorization_model_id
4669

openfga_sdk/sync/api_client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,7 @@ def __call_api(
292292
response=e.body.decode("utf-8"),
293293
credentials=self.configuration.credentials,
294294
attributes=_telemetry_attributes,
295+
start=start,
295296
)
296297

297298
self._telemetry.metrics.request(
@@ -323,6 +324,7 @@ def __call_api(
323324
response=e,
324325
credentials=self.configuration.credentials,
325326
attributes=_telemetry_attributes,
327+
start=start,
326328
)
327329

328330
self._telemetry.metrics.request(
@@ -349,6 +351,7 @@ def __call_api(
349351
response=response_data,
350352
credentials=self.configuration.credentials,
351353
attributes=_telemetry_attributes,
354+
start=start,
352355
)
353356

354357
self._telemetry.metrics.request(

openfga_sdk/telemetry/attributes.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,7 @@ def fromResponse(
287287
) = None,
288288
credentials: Credentials | None = None,
289289
attributes: dict[TelemetryAttribute, str | bool | int | float] | None = None,
290+
start: float | None = None,
290291
) -> dict[TelemetryAttribute, str | bool | int | float]:
291292
response_model_id = None
292293
response_query_duration = None
@@ -295,6 +296,11 @@ def fromResponse(
295296
if attributes is not None:
296297
_attributes = attributes
297298

299+
if start is not None and start > 0:
300+
_attributes[TelemetryAttributes.http_client_request_duration] = int(
301+
(time.time() - start) * 1000
302+
)
303+
298304
if isinstance(response, ApiException):
299305
if response.status is not None:
300306
_attributes[TelemetryAttributes.http_response_status_code] = int(

openfga_sdk/telemetry/metrics.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from openfga_sdk.telemetry.configuration import (
2020
TelemetryConfiguration,
2121
TelemetryMetricConfiguration,
22+
TelemetryMetricsConfiguration,
2223
isMetricEnabled,
2324
)
2425
from openfga_sdk.telemetry.counters import TelemetryCounter, TelemetryCounters
@@ -90,7 +91,7 @@ def request(
9091

9192
if (
9293
isinstance(configuration, TelemetryConfiguration)
93-
and isinstance(configuration.metrics, TelemetryMetricConfiguration)
94+
and isinstance(configuration.metrics, TelemetryMetricsConfiguration)
9495
and isinstance(
9596
configuration.metrics.fga_client_request,
9697
TelemetryMetricConfiguration,
@@ -127,7 +128,7 @@ def credentialsRequest(
127128

128129
if (
129130
isinstance(configuration, TelemetryConfiguration)
130-
and isinstance(configuration.metrics, TelemetryMetricConfiguration)
131+
and isinstance(configuration.metrics, TelemetryMetricsConfiguration)
131132
and isinstance(
132133
configuration.metrics.fga_client_credentials_request,
133134
TelemetryMetricConfiguration,
@@ -178,7 +179,7 @@ def requestDuration(
178179

179180
if (
180181
isinstance(configuration, TelemetryConfiguration)
181-
and isinstance(configuration.metrics, TelemetryMetricConfiguration)
182+
and type(configuration.metrics) is TelemetryMetricsConfiguration
182183
and isinstance(
183184
configuration.metrics.fga_client_request_duration,
184185
TelemetryMetricConfiguration,

test/rest_test.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,9 @@ async def test_stream_exception_in_chunks():
339339

340340
class FakeContent:
341341
async def iter_chunks(self):
342-
raise ValueError("Boom!")
342+
if True: # This ensures the coroutine is actually created and awaited
343+
raise ValueError("Boom!")
344+
yield (b"", None) # This line is never reached
343345

344346
mock_response = MagicMock()
345347
mock_response.status = 200
@@ -357,8 +359,11 @@ async def iter_chunks(self):
357359
client.close = AsyncMock()
358360

359361
results = []
360-
async for item in client.stream("GET", "http://example.com"):
361-
results.append(item)
362+
try:
363+
async for item in client.stream("GET", "http://example.com"):
364+
results.append(item)
365+
except ValueError:
366+
pass
362367

363368
assert results == []
364369
client.handle_response_exception.assert_awaited_once()

test/telemetry/attributes_test.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def test_from_request_without_optional_params(telemetry_attributes):
123123

124124

125125
def test_from_response_with_http_response(telemetry_attributes):
126+
start_time = time.time() - 5
126127
response = MagicMock(spec=HTTPResponse)
127128
response.status = 200
128129
response.getheader.side_effect = lambda header: {
@@ -135,16 +136,21 @@ def test_from_response_with_http_response(telemetry_attributes):
135136
configuration=CredentialConfiguration(client_id="client_123"),
136137
)
137138
attributes = telemetry_attributes.fromResponse(
138-
response=response, credentials=credentials
139+
response=response,
140+
credentials=credentials,
141+
start=start_time,
139142
)
140143

141144
assert attributes[TelemetryAttributes.http_response_status_code] == 200
142145
assert attributes[TelemetryAttributes.fga_client_response_model_id] == "model_123"
143146
assert attributes[TelemetryAttributes.http_server_request_duration] == "50"
144147
assert attributes[TelemetryAttributes.fga_client_request_client_id] == "client_123"
148+
assert TelemetryAttributes.http_client_request_duration in attributes
149+
assert attributes[TelemetryAttributes.http_client_request_duration] > 0
145150

146151

147152
def test_from_response_with_rest_response(telemetry_attributes):
153+
start_time = time.time() - 5
148154
response = MagicMock(spec=RESTResponse)
149155
response.status = 404
150156
response.headers = {
@@ -159,13 +165,17 @@ def test_from_response_with_rest_response(telemetry_attributes):
159165
configuration=CredentialConfiguration(client_id="client_456"),
160166
)
161167
attributes = telemetry_attributes.fromResponse(
162-
response=response, credentials=credentials
168+
response=response,
169+
credentials=credentials,
170+
start=start_time,
163171
)
164172

165173
assert attributes[TelemetryAttributes.http_response_status_code] == 404
166174
assert attributes[TelemetryAttributes.fga_client_response_model_id] == "model_404"
167175
assert attributes[TelemetryAttributes.http_server_request_duration] == "100"
168176
assert attributes[TelemetryAttributes.fga_client_request_client_id] == "client_456"
177+
assert TelemetryAttributes.http_client_request_duration in attributes
178+
assert attributes[TelemetryAttributes.http_client_request_duration] > 0
169179

170180

171181
def test_from_body_with_batch_check(telemetry_attributes):

0 commit comments

Comments
 (0)