Skip to content

Commit a44de5c

Browse files
✨ instrument (opentelemetry) httpx clients (#6715)
1 parent a4b7c7a commit a44de5c

File tree

58 files changed

+395
-139
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+395
-139
lines changed

packages/aws-library/requirements/_base.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ arrow==1.3.0
4444
# -r requirements/../../../packages/service-library/requirements/../../../packages/models-library/requirements/_base.in
4545
# -r requirements/../../../packages/service-library/requirements/_base.in
4646
# -r requirements/_base.in
47-
async-timeout==4.0.3
48-
# via redis
4947
attrs==24.2.0
5048
# via
5149
# aiohttp

packages/notifications-library/requirements/_base.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ attrs==24.2.0
1616
# referencing
1717
click==8.1.7
1818
# via typer
19+
deprecated==1.2.14
20+
# via
21+
# opentelemetry-api
22+
# opentelemetry-semantic-conventions
1923
dnspython==2.6.1
2024
# via email-validator
2125
email-validator==2.2.0
@@ -26,6 +30,8 @@ idna==3.10
2630
# via
2731
# email-validator
2832
# yarl
33+
importlib-metadata==8.5.0
34+
# via opentelemetry-api
2935
jinja2==3.1.4
3036
# via
3137
# -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt
@@ -54,13 +60,28 @@ mdurl==0.1.2
5460
# via markdown-it-py
5561
multidict==6.1.0
5662
# via yarl
63+
opentelemetry-api==1.28.1
64+
# via
65+
# opentelemetry-instrumentation
66+
# opentelemetry-instrumentation-asyncpg
67+
# opentelemetry-semantic-conventions
68+
opentelemetry-instrumentation==0.49b1
69+
# via opentelemetry-instrumentation-asyncpg
70+
opentelemetry-instrumentation-asyncpg==0.49b1
71+
# via -r requirements/../../../packages/postgres-database/requirements/_base.in
72+
opentelemetry-semantic-conventions==0.49b1
73+
# via
74+
# opentelemetry-instrumentation
75+
# opentelemetry-instrumentation-asyncpg
5776
orjson==3.10.7
5877
# via
5978
# -c requirements/../../../packages/models-library/requirements/../../../requirements/constraints.txt
6079
# -c requirements/../../../packages/postgres-database/requirements/../../../requirements/constraints.txt
6180
# -c requirements/../../../packages/settings-library/requirements/../../../requirements/constraints.txt
6281
# -c requirements/../../../requirements/constraints.txt
6382
# -r requirements/../../../packages/models-library/requirements/_base.in
83+
packaging==24.2
84+
# via opentelemetry-instrumentation
6485
psycopg2-binary==2.9.9
6586
# via sqlalchemy
6687
pydantic==1.10.18
@@ -109,5 +130,11 @@ typing-extensions==4.12.2
109130
# alembic
110131
# pydantic
111132
# typer
133+
wrapt==1.16.0
134+
# via
135+
# deprecated
136+
# opentelemetry-instrumentation
112137
yarl==1.12.1
113138
# via -r requirements/../../../packages/postgres-database/requirements/_base.in
139+
zipp==3.21.0
140+
# via importlib-metadata

packages/notifications-library/requirements/_test.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ mypy==1.12.0
2828
# via sqlalchemy
2929
mypy-extensions==1.0.0
3030
# via mypy
31-
packaging==24.1
31+
packaging==24.2
3232
# via
33+
# -c requirements/_base.txt
3334
# pytest
3435
# pytest-sugar
3536
pluggy==1.5.0

packages/notifications-library/requirements/_tools.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@ mypy-extensions==1.0.0
3838
# mypy
3939
nodeenv==1.9.1
4040
# via pre-commit
41-
packaging==24.1
41+
packaging==24.2
4242
# via
43+
# -c requirements/_base.txt
4344
# -c requirements/_test.txt
4445
# black
4546
# build

packages/service-library/requirements/_base.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,6 @@ arrow==1.3.0
2828
# via
2929
# -r requirements/../../../packages/models-library/requirements/_base.in
3030
# -r requirements/_base.in
31-
async-timeout==4.0.3
32-
# via redis
3331
attrs==24.2.0
3432
# via
3533
# aiohttp

packages/service-library/requirements/_fastapi.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
fastapi
1010
httpx
1111
opentelemetry-instrumentation-fastapi
12+
opentelemetry-instrumentation-httpx
1213
prometheus-client
1314
prometheus-fastapi-instrumentator
1415
uvicorn

packages/service-library/requirements/_fastapi.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,23 +47,29 @@ opentelemetry-api==1.27.0
4747
# opentelemetry-instrumentation
4848
# opentelemetry-instrumentation-asgi
4949
# opentelemetry-instrumentation-fastapi
50+
# opentelemetry-instrumentation-httpx
5051
# opentelemetry-semantic-conventions
5152
opentelemetry-instrumentation==0.48b0
5253
# via
5354
# opentelemetry-instrumentation-asgi
5455
# opentelemetry-instrumentation-fastapi
56+
# opentelemetry-instrumentation-httpx
5557
opentelemetry-instrumentation-asgi==0.48b0
5658
# via opentelemetry-instrumentation-fastapi
5759
opentelemetry-instrumentation-fastapi==0.48b0
5860
# via -r requirements/_fastapi.in
61+
opentelemetry-instrumentation-httpx==0.48b0
62+
# via -r requirements/_fastapi.in
5963
opentelemetry-semantic-conventions==0.48b0
6064
# via
6165
# opentelemetry-instrumentation-asgi
6266
# opentelemetry-instrumentation-fastapi
67+
# opentelemetry-instrumentation-httpx
6368
opentelemetry-util-http==0.48b0
6469
# via
6570
# opentelemetry-instrumentation-asgi
6671
# opentelemetry-instrumentation-fastapi
72+
# opentelemetry-instrumentation-httpx
6773
prometheus-client==0.21.0
6874
# via
6975
# -r requirements/_fastapi.in

packages/service-library/src/servicelib/fastapi/http_client_thin.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from httpx import AsyncClient, ConnectError, HTTPError, PoolTimeout, Response
99
from httpx._types import TimeoutTypes, URLTypes
1010
from pydantic.errors import PydanticErrorMixin
11+
from servicelib.fastapi.tracing import setup_httpx_client_tracing
12+
from settings_library.tracing import TracingSettings
1113
from tenacity import RetryCallState
1214
from tenacity.asyncio import AsyncRetrying
1315
from tenacity.before_sleep import before_sleep_log
@@ -201,6 +203,7 @@ def __init__(
201203
base_url: URLTypes | None = None,
202204
default_http_client_timeout: TimeoutTypes | None = None,
203205
extra_allowed_method_names: set[str] | None = None,
206+
tracing_settings: TracingSettings | None,
204207
) -> None:
205208
_assert_public_interface(self, extra_allowed_method_names)
206209

@@ -220,7 +223,10 @@ def __init__(
220223
if default_http_client_timeout:
221224
client_args["timeout"] = default_http_client_timeout
222225

223-
super().__init__(client=AsyncClient(**client_args))
226+
client = AsyncClient(**client_args)
227+
if tracing_settings:
228+
setup_httpx_client_tracing(client)
229+
super().__init__(client=client)
224230

225231
async def __aenter__(self):
226232
await self.setup_client()

packages/service-library/src/servicelib/fastapi/tracing.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import logging
66

77
from fastapi import FastAPI
8+
from httpx import AsyncClient, Client
89
from opentelemetry import trace
910
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
1011
OTLPSpanExporter as OTLPSpanExporterHTTP,
1112
)
1213
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
14+
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
1315
from opentelemetry.sdk.resources import Resource
1416
from opentelemetry.sdk.trace import TracerProvider
1517
from opentelemetry.sdk.trace.export import BatchSpanProcessor
@@ -121,3 +123,7 @@ def setup_tracing(
121123
msg="Attempting to add requests opentelemetry autoinstrumentation...",
122124
):
123125
RequestsInstrumentor().instrument()
126+
127+
128+
def setup_httpx_client_tracing(client: AsyncClient | Client):
129+
HTTPXClientInstrumentor.instrument_client(client)

packages/service-library/tests/fastapi/test_http_client_thin.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ def request_timeout() -> int:
7171

7272
@pytest.fixture
7373
async def thick_client(request_timeout: int) -> AsyncIterable[FakeThickClient]:
74-
async with FakeThickClient(total_retry_interval=request_timeout) as client:
74+
async with FakeThickClient(
75+
total_retry_interval=request_timeout, tracing_settings=None
76+
) as client:
7577
yield client
7678

7779

@@ -95,7 +97,9 @@ async def test_retry_on_errors(
9597
test_url: AnyHttpUrl,
9698
caplog_info_level: pytest.LogCaptureFixture,
9799
) -> None:
98-
client = FakeThickClient(total_retry_interval=request_timeout)
100+
client = FakeThickClient(
101+
total_retry_interval=request_timeout, tracing_settings=None
102+
)
99103

100104
with pytest.raises(ClientHttpError):
101105
await client.get_provided_url(test_url)
@@ -119,7 +123,7 @@ async def raises_request_error(self) -> Response:
119123
request=Request(method="GET", url=test_url),
120124
)
121125

122-
client = ATestClient(total_retry_interval=request_timeout)
126+
client = ATestClient(total_retry_interval=request_timeout, tracing_settings=None)
123127

124128
with pytest.raises(ClientHttpError):
125129
await client.raises_request_error()
@@ -145,7 +149,7 @@ async def raises_http_error(self) -> Response:
145149
msg = "mock_http_error"
146150
raise HTTPError(msg)
147151

148-
client = ATestClient(total_retry_interval=request_timeout)
152+
client = ATestClient(total_retry_interval=request_timeout, tracing_settings=None)
149153

150154
with pytest.raises(ClientHttpError):
151155
await client.raises_http_error()
@@ -159,21 +163,25 @@ async def public_method_ok(self) -> Response: # type: ignore
159163
"""this method will be ok even if no code is used"""
160164

161165
# OK
162-
OKTestClient(total_retry_interval=request_timeout)
166+
OKTestClient(total_retry_interval=request_timeout, tracing_settings=None)
163167

164168
class FailWrongAnnotationTestClient(BaseThinClient):
165169
async def public_method_wrong_annotation(self) -> None:
166170
"""this method will raise an error"""
167171

168172
with pytest.raises(AssertionError, match="should return an instance"):
169-
FailWrongAnnotationTestClient(total_retry_interval=request_timeout)
173+
FailWrongAnnotationTestClient(
174+
total_retry_interval=request_timeout, tracing_settings=None
175+
)
170176

171177
class FailNoAnnotationTestClient(BaseThinClient):
172178
async def public_method_no_annotation(self):
173179
"""this method will raise an error"""
174180

175181
with pytest.raises(AssertionError, match="should return an instance"):
176-
FailNoAnnotationTestClient(total_retry_interval=request_timeout)
182+
FailNoAnnotationTestClient(
183+
total_retry_interval=request_timeout, tracing_settings=None
184+
)
177185

178186

179187
async def test_expect_state_decorator(
@@ -197,7 +205,9 @@ async def get_wrong_state(self) -> Response:
197205
respx_mock.get(url_get_200_ok).mock(return_value=Response(codes.OK))
198206
respx_mock.get(get_wrong_state).mock(return_value=Response(codes.OK))
199207

200-
test_client = ATestClient(total_retry_interval=request_timeout)
208+
test_client = ATestClient(
209+
total_retry_interval=request_timeout, tracing_settings=None
210+
)
201211

202212
# OK
203213
response = await test_client.get_200_ok()
@@ -218,7 +228,9 @@ async def test_retry_timeout_overwrite(
218228
request_timeout: int,
219229
caplog_info_level: pytest.LogCaptureFixture,
220230
) -> None:
221-
client = FakeThickClient(total_retry_interval=request_timeout)
231+
client = FakeThickClient(
232+
total_retry_interval=request_timeout, tracing_settings=None
233+
)
222234

223235
caplog_info_level.clear()
224236
start = arrow.utcnow()

0 commit comments

Comments
 (0)