Skip to content

Commit 916442d

Browse files
committed
feat: add per-HTTP request counter metric
1 parent 41d5c1b commit 916442d

10 files changed

Lines changed: 120 additions & 25 deletions

File tree

docs/opentelemetry.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ If you configure the OpenTelemetry SDK, these metrics will be exported and sent
2626
| `fga-client.request.duration` | Histogram | Yes | Total request time for FGA requests, in milliseconds |
2727
| `fga-client.query.duration` | Histogram | Yes | Time taken by the FGA server to process and evaluate the request, in milliseconds |
2828
| `fga-client.credentials.request` | Counter | Yes | Total number of new token requests initiated using the Client Credentials flow |
29+
| `fga-client.request` | Counter | No | Total number of requests made to the FGA server |
2930

3031
### Supported Attributes
3132

openfga_sdk/api_client.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ async def __call_api(
269269

270270
for retry in range(max_retry + 1):
271271
_telemetry_attributes[TelemetryAttributes.http_request_resend_count] = retry
272+
272273
try:
273274
# perform request and return response
274275
response_data = await self.request(
@@ -283,6 +284,17 @@ async def __call_api(
283284
)
284285
except (RateLimitExceededError, ServiceException) as e:
285286
if retry < max_retry and e.status != 501:
287+
_telemetry_attributes = TelemetryAttributes.fromResponse(
288+
response=e.body.decode("utf-8"),
289+
credentials=self.configuration.credentials,
290+
attributes=_telemetry_attributes,
291+
)
292+
293+
self._telemetry.metrics.request(
294+
attributes=_telemetry_attributes,
295+
configuration=self.configuration.telemetry,
296+
)
297+
286298
await asyncio.sleep(random_time(retry, min_wait_in_ms))
287299

288300
continue
@@ -309,6 +321,11 @@ async def __call_api(
309321
attributes=_telemetry_attributes,
310322
)
311323

324+
self._telemetry.metrics.request(
325+
attributes=_telemetry_attributes,
326+
configuration=self.configuration.telemetry,
327+
)
328+
312329
self._telemetry.metrics.queryDuration(
313330
attributes=_telemetry_attributes,
314331
configuration=self.configuration.telemetry,
@@ -330,6 +347,11 @@ async def __call_api(
330347
attributes=_telemetry_attributes,
331348
)
332349

350+
self._telemetry.metrics.request(
351+
attributes=_telemetry_attributes,
352+
configuration=self.configuration.telemetry,
353+
)
354+
333355
self._telemetry.metrics.queryDuration(
334356
attributes=_telemetry_attributes,
335357
configuration=self.configuration.telemetry,

openfga_sdk/oauth2.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,10 @@ async def _obtain_token(self, client):
127127
)
128128
self._access_token = api_response.get("access_token")
129129
self._telemetry.metrics.credentialsRequest(
130-
1,
131-
{
130+
attributes={
132131
TelemetryAttributes.fga_client_request_client_id: configuration.client_id
133132
},
134-
self.configuration.telemetry,
133+
configuration=self.configuration.telemetry,
135134
)
136135
break
137136

openfga_sdk/sync/api_client.py

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,21 @@ def __call_api(
253253
max_retry = _retry_params.max_retry
254254
if _retry_params.min_wait_in_ms is not None:
255255
max_retry = _retry_params.min_wait_in_ms
256+
257+
_telemetry_attributes = TelemetryAttributes.fromRequest(
258+
user_agent=self.user_agent,
259+
fga_method=resource_path,
260+
http_method=method,
261+
url=url,
262+
resend_count=0,
263+
start=start,
264+
credentials=self.configuration.credentials,
265+
attributes=_telemetry_attributes,
266+
)
267+
256268
for retry in range(max_retry + 1):
269+
_telemetry_attributes[TelemetryAttributes.http_request_resend_count] = retry
270+
257271
try:
258272
# perform request and return response
259273
response_data = self.request(
@@ -268,7 +282,19 @@ def __call_api(
268282
)
269283
except (RateLimitExceededError, ServiceException) as e:
270284
if retry < max_retry and e.status != 501:
285+
_telemetry_attributes = TelemetryAttributes.fromResponse(
286+
response=e.body.decode("utf-8"),
287+
credentials=self.configuration.credentials,
288+
attributes=_telemetry_attributes,
289+
)
290+
291+
self._telemetry.metrics.request(
292+
attributes=_telemetry_attributes,
293+
configuration=self.configuration.telemetry,
294+
)
295+
271296
time.sleep(random_time(retry, min_wait_in_ms))
297+
272298
continue
273299
e.body = e.body.decode("utf-8")
274300
response_type = response_types_map.get(e.status, None)
@@ -293,6 +319,11 @@ def __call_api(
293319
attributes=_telemetry_attributes,
294320
)
295321

322+
self._telemetry.metrics.request(
323+
attributes=_telemetry_attributes,
324+
configuration=self.configuration.telemetry,
325+
)
326+
296327
self._telemetry.metrics.queryDuration(
297328
attributes=_telemetry_attributes,
298329
configuration=self.configuration.telemetry,
@@ -308,21 +339,15 @@ def __call_api(
308339

309340
return_data = response_data
310341

311-
_telemetry_attributes = TelemetryAttributes.fromRequest(
312-
user_agent=self.user_agent,
313-
fga_method=resource_path,
314-
http_method=method,
315-
url=url,
316-
resend_count=retry,
317-
start=start,
342+
_telemetry_attributes = TelemetryAttributes.fromResponse(
343+
response=response_data,
318344
credentials=self.configuration.credentials,
319345
attributes=_telemetry_attributes,
320346
)
321347

322-
_telemetry_attributes = TelemetryAttributes.fromResponse(
323-
response=response_data,
324-
credentials=self.configuration.credentials,
348+
self._telemetry.metrics.request(
325349
attributes=_telemetry_attributes,
350+
configuration=self.configuration.telemetry,
326351
)
327352

328353
self._telemetry.metrics.queryDuration(

openfga_sdk/sync/oauth2.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,10 @@ def _obtain_token(self, client):
127127
)
128128
self._access_token = api_response.get("access_token")
129129
self._telemetry.metrics.credentialsRequest(
130-
1,
131-
{
130+
attributes={
132131
TelemetryAttributes.fga_client_request_client_id: configuration.client_id
133132
},
134-
self.configuration.telemetry,
133+
configuration=self.configuration.telemetry,
135134
)
136135
break
137136

openfga_sdk/telemetry/configuration.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,7 @@ def __init__(
613613
fga_client_credentials_request: Optional[TelemetryMetricConfiguration] = None,
614614
fga_client_request_duration: Optional[TelemetryMetricConfiguration] = None,
615615
fga_client_query_duration: Optional[TelemetryMetricConfiguration] = None,
616+
fga_client_request: Optional[TelemetryMetricConfiguration] = None,
616617
):
617618
"""
618619
Initialize a new instance of the `TelemetryMetricsConfiguration` class.
@@ -621,6 +622,7 @@ def __init__(
621622
:param fga_client_credentials_request: The `fga-client.credentials.request` counter collects the number of times a new token is requested using ClientCredentials.
622623
:param fga_client_request_duration: The `fga-client.query.duration` histogram tracks how long requests take to complete from the client's perspective.
623624
:param fga_client_query_duration: The `fga-client.request.duration` histogram tracks how long requests take to process from the server's perspective.
625+
:param fga_client_request: The `fga-client.request` counter collects the number of requests made to the FGA server.
624626
"""
625627

626628
# Instantiate with default state, and apply the incoming configuration, if one was provided
@@ -641,9 +643,33 @@ def __init__(
641643
fga_client_query_duration
642644
)
643645

646+
if fga_client_request is not None:
647+
self._state[TelemetryCounters.fga_client_request] = fga_client_request
648+
644649
# Reset the validation state
645650
self._valid = None
646651

652+
@property
653+
def fga_client_request(self) -> TelemetryMetricConfiguration | None:
654+
"""
655+
Get the configuration for the `fga-client.request` counter.
656+
657+
:return: The configuration for the `fga-client.request` counter.
658+
"""
659+
660+
return self._state[TelemetryCounters.fga_client_request]
661+
662+
@fga_client_request.setter
663+
def fga_client_request(self, value: TelemetryMetricConfiguration | None):
664+
"""
665+
Set the configuration for the `fga-client.request` counter.
666+
667+
:param value: The configuration for the `fga-client.request` counter.
668+
"""
669+
670+
self._valid = None # Reset the validation state
671+
self._state[TelemetryCounters.fga_client_request] = value
672+
647673
@property
648674
def fga_client_credentials_request(self) -> TelemetryMetricConfiguration | None:
649675
"""
@@ -714,6 +740,7 @@ def clear(self) -> None:
714740
Reset the configuration to the default state (all attributes disabled).
715741
"""
716742
self._state = {
743+
TelemetryCounters.fga_client_request: None,
717744
TelemetryCounters.fga_client_credentials_request: None,
718745
TelemetryHistograms.fga_client_request_duration: None,
719746
TelemetryHistograms.fga_client_query_duration: None,

openfga_sdk/telemetry/counters.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,24 @@
33

44
class TelemetryCounter(NamedTuple):
55
name: str
6-
unit: str
76
description: str
7+
unit: str = ""
88

99

1010
class TelemetryCounters:
1111
fga_client_credentials_request: TelemetryCounter = TelemetryCounter(
1212
name="fga-client.credentials.request",
13-
unit="milliseconds",
1413
description="Total number of new token requests initiated using the Client Credentials flow.",
1514
)
1615

16+
fga_client_request: TelemetryCounter = TelemetryCounter(
17+
name="fga-client.request",
18+
description="Total number of requests made to the FGA server.",
19+
)
20+
1721
_counters: list[TelemetryCounter] = [
1822
fga_client_credentials_request,
23+
fga_client_request,
1924
]
2025

2126
@staticmethod

openfga_sdk/telemetry/histograms.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,17 @@
33

44
class TelemetryHistogram(NamedTuple):
55
name: str
6-
unit: str
76
description: str
7+
unit: str = "milliseconds"
88

99

1010
class TelemetryHistograms:
1111
fga_client_request_duration: TelemetryHistogram = TelemetryHistogram(
1212
name="fga-client.request.duration",
13-
unit="milliseconds",
1413
description="Total request time for FGA requests, in milliseconds.",
1514
)
1615
fga_client_query_duration: TelemetryHistogram = TelemetryHistogram(
1716
name="fga-client.query.duration",
18-
unit="milliseconds",
1917
description="Time taken by the FGA server to process and evaluate the request, in milliseconds.",
2018
)
2119

openfga_sdk/telemetry/metrics.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,31 @@ def histogram(self, histogram: TelemetryHistogram) -> Histogram:
6363

6464
return self._histograms[histogram.name]
6565

66+
def request(
67+
self,
68+
value: int = 1,
69+
attributes: dict[TelemetryAttribute, str | int] | None = None,
70+
configuration: TelemetryConfiguration | None = None,
71+
) -> Counter:
72+
"""
73+
Record a request made by the client.
74+
"""
75+
counter = self.counter(TelemetryCounters.fga_client_request)
76+
77+
if isMetricEnabled(configuration, TelemetryCounters.fga_client_request):
78+
attributes = TelemetryAttributes.prepare(
79+
attributes,
80+
filter=configuration.metrics.fga_client_request.getAttributes(),
81+
)
82+
83+
if value is not None:
84+
counter.add(amount=value, attributes=attributes)
85+
86+
return counter
87+
6688
def credentialsRequest(
6789
self,
68-
value: int,
90+
value: int = 1,
6991
attributes: dict[TelemetryAttribute, str | int] | None = None,
7092
configuration: TelemetryConfiguration | None = None,
7193
) -> Counter:

test/telemetry/counters_test.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,10 @@
44
def test_telemetry_counter_initialization():
55
counter = TelemetryCounter(
66
name="fga-client.test.counter",
7-
unit="seconds",
87
description="A test counter for unit testing.",
98
)
109

1110
assert counter.name == "fga-client.test.counter"
12-
assert counter.unit == "seconds"
1311
assert counter.description == "A test counter for unit testing."
1412

1513

@@ -19,7 +17,6 @@ def test_telemetry_counters_default_values():
1917
assert (
2018
counters.fga_client_credentials_request.name == "fga-client.credentials.request"
2119
)
22-
assert counters.fga_client_credentials_request.unit == "milliseconds"
2320
assert (
2421
counters.fga_client_credentials_request.description
2522
== "Total number of new token requests initiated using the Client Credentials flow."

0 commit comments

Comments
 (0)