Skip to content

Commit e9142ba

Browse files
Evan Simsevansims
authored andcommitted
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 e9142ba

8 files changed

Lines changed: 91 additions & 26 deletions

File tree

CHANGELOG.md

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## [Unreleased](https://github.com/openfga/python-sdk/compare/v0.9.1...HEAD)
44

5+
- fix: fixes for telemetry attributes and metrics tracking (#176)
6+
57
## v0.9.1
68

79
### [0.9.1](https://github.com/openfga/python-sdk/compare/v0.9.0...v0.9.1) (2025-01-23)
@@ -78,16 +80,19 @@ Please note that if you use third-party OpenTelemetry tooling to visualize the a
7880
- feat: enhancements to OpenTelemetry support (#120)
7981

8082
Note this introduces some breaking changes to our metrics:
83+
8184
1. `fga-client.request.method` is now in TitleCase to match the naming conventions in the Protos, e.g. `Check`, `ListObjects`, etc..
8285
2. Due to possible high costs for attributes with high cardinality, we are no longer including the following attributes by default:
83-
* `fga-client.user`
84-
* `http.client.request.duration`
85-
* `http.server.request.duration`
86+
87+
- `fga-client.user`
88+
- `http.client.request.duration`
89+
- `http.server.request.duration`
8690
We added configuration options to allow you to set which specific metrics and attributes you care about in case the defaults don't work for your use-case
8791

8892
## v0.6.1
8993

9094
### [0.6.1](https://github.com/openfga/python-sdk/compare/v0.6.0...v0.6.1) (2024-07-31)
95+
9196
- feat: add support for specifying consistency when evaluating or reading (#129)
9297
Note: To use this feature, you need to be running OpenFGA v1.5.7+ with the experimental flag
9398
`enable-consistency-params` enabled. See the [v1.5.7 release notes](https://github.com/openfga/openfga/releases/tag/v1.5.7) for details.
@@ -155,21 +160,22 @@ You will have to modify some parts of your code, but we hope this will be to the
155160
and so the Pointer-to-String conversion is no longer needed.
156161

157162
Some of the changes to expect:
163+
158164
- The following request interfaces changed:
159-
- `CheckRequest`: the `TupleKey` field is now of interface `CheckRequestTupleKey`, you can also now pass in `Context`
160-
- `ExpandRequest`: the `TupleKey` field is now of interface `ExpandRequestTupleKey`
161-
- `ReadRequest`: the `TupleKey` field is now of interface `ReadRequestTupleKey`
162-
- `WriteRequest`: now takes `WriteRequestWrites` and `WriteRequestDeletes`, the latter of which accepts `TupleKeyWithoutCondition`
163-
- And more
165+
- `CheckRequest`: the `TupleKey` field is now of interface `CheckRequestTupleKey`, you can also now pass in `Context`
166+
- `ExpandRequest`: the `TupleKey` field is now of interface `ExpandRequestTupleKey`
167+
- `ReadRequest`: the `TupleKey` field is now of interface `ReadRequestTupleKey`
168+
- `WriteRequest`: now takes `WriteRequestWrites` and `WriteRequestDeletes`, the latter of which accepts `TupleKeyWithoutCondition`
169+
- And more
164170
- The following interfaces had fields that were optional are are now required:
165-
- `CreateStoreResponse`
166-
- `GetStoreResponse`
167-
- `ListStoresResponse`
168-
- `ListObjectsResponse`
169-
- `ReadChangesResponse`
170-
- `ReadResponse`
171-
- `AuthorizationModel`
172-
- And more
171+
- `CreateStoreResponse`
172+
- `GetStoreResponse`
173+
- `ListStoresResponse`
174+
- `ListObjectsResponse`
175+
- `ReadChangesResponse`
176+
- `ReadResponse`
177+
- `AuthorizationModel`
178+
- And more
173179

174180
Take a look at the changes in models in https://github.com/openfga/python-sdk/commit/9ed1f70d64db71451de2eb26e330bbd511625c5c and https://github.com/openfga/python-sdk/pull/59/files for more.
175181

@@ -182,31 +188,36 @@ Note: `v0.3.4` has been re-released as `v0.4.0` due to breaking changes
182188
## v0.3.3
183189

184190
### [0.3.3](https://github.com/openfga/python-sdk/compare/v0.3.2...v0.3.3) (2024-01-02)
191+
185192
- fix: correct type hints for list_relations
186193
- fix: handle empty TupleKey in read
187194
- chore: add example project
188195

189196
## v0.3.2
190197

191198
### [0.3.2](https://github.com/openfga/python-sdk/compare/v0.3.1...v0.3.2) (2023-12-15)
199+
192200
- feat: allow passing ssl certs to client configuration
193201
- feat: setup openfga_sdk.help for bug info
194202

195203
## v0.3.1
196204

197205
### [0.3.1](https://github.com/openfga/python-sdk/compare/v0.3.0...v0.3.1) (2023-12-01)
206+
198207
- chore(deps): reduce min urllib3 to 1.25.11, add dependabot & bump deps
199208

200209
## v0.3.0
201210

202211
### [0.3.0](https://github.com/openfga/python-sdk/compare/v0.2.1...v0.3.0) (2023-11-02)
212+
203213
- feat(client): introduce synchronous OpenFgaClient (https://github.com/openfga/python-sdk/commit/c92b436543e263f2c1af6af15f1c4fda1c9dad21)
204214
- refactor(config): extract oauth2 from credentials, removing logic from credentials configuration (https://github.com/openfga/python-sdk/commit/f91d14b25f86dd3f2e4d48229bb53cc7d9b20f1b)
205215
- feat(client): performance improvements to batch_check (https://github.com/openfga/python-sdk/commit/d8f2d429d2c279c0e56d5ef2a6172df8bfadd82b)
206216

207217
## v0.2.1
208218

209219
### [0.2.1](https://github.com/openfga/python-sdk/compare/v0.2.0...v0.2.1) (2023-09-05)
220+
210221
- fix(client): fix a crash when calling check with contextual tuples (https://github.com/openfga/python-sdk/commit/dded83f9a75dc1f01c1cfbd8385a25654129f78f)
211222
- chore(docs): update README and fix a few typos (https://github.com/openfga/python-sdk/pull/21, https://github.com/openfga/python-sdk/pull/31, https://github.com/openfga/python-sdk/pull/32, https://github.com/openfga/python-sdk/pull/33, https://github.com/openfga/python-sdk/pull/34, https://github.com/openfga/python-sdk/pull/37)
212223

@@ -215,6 +226,7 @@ Note: `v0.3.4` has been re-released as `v0.4.0` due to breaking changes
215226
### [0.2.0](https://github.com/openfga/python-sdk/compare/v0.1.1...v0.2.0) (2023-05-25)
216227

217228
Changes:
229+
218230
- [BREAKING] feat!: `schema_version` is now required when calling `write_authorization_model`
219231
- [BREAKING] chore!: drop support for python < 3.10
220232
- feat(client): add OpenFgaClient wrapper see [docs](https://github.com/openfga/python-sdk/tree/main#readme), see the `v0.1.1` docs for [the OpenFgaApi docs](https://github.com/openfga/python-sdk/tree/v0.1.1#readme)
@@ -238,18 +250,20 @@ Changes:
238250
Updated to include support for [OpenFGA 0.3.0](https://github.com/openfga/openfga/releases/tag/v0.3.0)
239251

240252
Changes:
253+
241254
- [BREAKING] feat(list-objects)!: response has been changed to include the object type
242-
e.g. response that was `{"object_ids":["roadmap"]}`, will now be `{"objects":["document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"]}`
255+
e.g. response that was `{"object_ids":["roadmap"]}`, will now be `{"objects":["document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"]}`
243256

244257
Fixes:
245-
- fix(models): update interfaces that had incorrectly optional fields to make them required
246258

259+
- fix(models): update interfaces that had incorrectly optional fields to make them required
247260

248261
## v0.0.1
249262

250263
### [0.0.1](https://github.com/openfga/python-sdk/releases/tag/v0.0.1) (2022-08-31)
251264

252265
Initial OpenFGA Python SDK release
266+
253267
- Support for [OpenFGA](https://github.com/openfga/openfga) API
254268
- CRUD stores
255269
- Create, read & list authorization models

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)