Skip to content

Commit efa751c

Browse files
committed
add lifespan to tracing in fastapi case
1 parent 91a0f99 commit efa751c

File tree

18 files changed

+159
-91
lines changed

18 files changed

+159
-91
lines changed

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

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
"""Adds fastapi middleware for tracing using opentelemetry instrumentation."""
22

33
import logging
4+
from collections.abc import AsyncIterator
45

56
from fastapi import FastAPI
7+
from fastapi_lifespan_manager import State
68
from httpx import AsyncClient, Client
79
from opentelemetry import trace
810
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
@@ -68,7 +70,7 @@
6870
HAS_AIOPIKA_INSTRUMENTOR = False
6971

7072

71-
def initialize_tracing(
73+
def _startup(
7274
app: FastAPI, tracing_settings: TracingSettings, service_name: str
7375
) -> None:
7476
if (
@@ -147,5 +149,66 @@ def initialize_tracing(
147149
RequestsInstrumentor().instrument()
148150

149151

152+
def _shutdown() -> None:
153+
"""Uninstruments all opentelemetry instrumentors that were instrumented."""
154+
if HAS_AIOPG:
155+
try:
156+
AiopgInstrumentor().uninstrument()
157+
except Exception:
158+
_logger.exception("Failed to uninstrument AiopgInstrumentor")
159+
if HAS_AIOPIKA_INSTRUMENTOR:
160+
try:
161+
AioPikaInstrumentor().uninstrument()
162+
except Exception:
163+
_logger.exception("Failed to uninstrument AioPikaInstrumentor")
164+
if HAS_ASYNCPG:
165+
try:
166+
AsyncPGInstrumentor().uninstrument()
167+
except Exception:
168+
_logger.exception("Failed to uninstrument AsyncPGInstrumentor")
169+
if HAS_REDIS:
170+
try:
171+
RedisInstrumentor().uninstrument()
172+
except Exception:
173+
_logger.exception("Failed to uninstrument RedisInstrumentor")
174+
if HAS_BOTOCORE:
175+
try:
176+
BotocoreInstrumentor().uninstrument()
177+
except Exception:
178+
_logger.exception("Failed to uninstrument BotocoreInstrumentor")
179+
if HAS_REQUESTS:
180+
try:
181+
RequestsInstrumentor().uninstrument()
182+
except Exception:
183+
_logger.exception("Failed to uninstrument RequestsInstrumentor")
184+
185+
150186
def setup_httpx_client_tracing(client: AsyncClient | Client):
151187
HTTPXClientInstrumentor.instrument_client(client)
188+
189+
190+
def setup_tracing(
191+
app: FastAPI, tracing_settings: TracingSettings, service_name: str
192+
) -> None:
193+
194+
_startup(app, tracing_settings, service_name)
195+
196+
def _on_shutdown() -> None:
197+
_shutdown()
198+
199+
app.add_event_handler("shutdown", _on_shutdown)
200+
201+
202+
async def tracing_instrumentation_lifespan(
203+
*,
204+
app: FastAPI,
205+
state: State,
206+
tracing_settings: TracingSettings,
207+
service_name: str,
208+
) -> AsyncIterator[State]:
209+
210+
_startup(app, tracing_settings, service_name)
211+
212+
yield state
213+
214+
_shutdown()

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

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import pytest
1212
from fastapi import FastAPI
1313
from pydantic import ValidationError
14-
from servicelib.fastapi.tracing import initialize_tracing
14+
from servicelib.fastapi.tracing import tracing_instrumentation_lifespan
1515
from settings_library.tracing import TracingSettings
1616

1717

@@ -63,17 +63,19 @@ async def test_valid_tracing_settings(
6363
uninstrument_opentelemetry: Iterator[None],
6464
):
6565
tracing_settings = TracingSettings()
66-
initialize_tracing(
67-
mocked_app,
66+
async for state in tracing_instrumentation_lifespan(
67+
app=mocked_app,
68+
state={},
6869
tracing_settings=tracing_settings,
6970
service_name="Mock-Openetlemetry-Pytest",
70-
)
71-
# idempotency
72-
initialize_tracing(
73-
mocked_app,
74-
tracing_settings=tracing_settings,
75-
service_name="Mock-Openetlemetry-Pytest",
76-
)
71+
):
72+
async for _ in tracing_instrumentation_lifespan(
73+
app=mocked_app,
74+
state=state,
75+
tracing_settings=tracing_settings,
76+
service_name="Mock-Openetlemetry-Pytest",
77+
):
78+
pass
7779

7880

7981
@pytest.mark.parametrize(
@@ -101,11 +103,13 @@ async def test_invalid_tracing_settings(
101103
app = mocked_app
102104
with pytest.raises((BaseException, ValidationError, TypeError)): # noqa: PT012
103105
tracing_settings = TracingSettings()
104-
initialize_tracing(
105-
app,
106+
async for _ in tracing_instrumentation_lifespan(
107+
app=app,
108+
state={},
106109
tracing_settings=tracing_settings,
107110
service_name="Mock-Openetlemetry-Pytest",
108-
)
111+
):
112+
pass
109113

110114

111115
def install_package(package):
@@ -156,16 +160,19 @@ async def test_tracing_setup_package_detection(
156160
):
157161
package_name = manage_package
158162
importlib.import_module(package_name)
159-
#
160163
tracing_settings = TracingSettings()
161-
initialize_tracing(
162-
mocked_app,
164+
# Use tracing_instrumentation_lifespan instead of _startup
165+
async for _ in tracing_instrumentation_lifespan(
166+
app=mocked_app,
167+
state={},
163168
tracing_settings=tracing_settings,
164169
service_name="Mock-Openetlemetry-Pytest",
165-
)
166-
# idempotency
167-
initialize_tracing(
168-
mocked_app,
169-
tracing_settings=tracing_settings,
170-
service_name="Mock-Openetlemetry-Pytest",
171-
)
170+
):
171+
# idempotency: call again
172+
async for _ in tracing_instrumentation_lifespan(
173+
app=mocked_app,
174+
state={},
175+
tracing_settings=tracing_settings,
176+
service_name="Mock-Openetlemetry-Pytest",
177+
):
178+
pass

services/agent/src/simcore_service_agent/core/application.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
get_common_oas_options,
66
override_fastapi_openapi_method,
77
)
8-
from servicelib.fastapi.tracing import initialize_tracing
8+
from servicelib.fastapi.tracing import setup_tracing
99
from servicelib.logging_utils import config_all_loggers
1010

1111
from .._meta import (
@@ -55,6 +55,9 @@ def create_app() -> FastAPI:
5555
override_fastapi_openapi_method(app)
5656
app.state.settings = settings
5757

58+
if settings.AGENT_TRACING:
59+
setup_tracing(app, settings.AGENT_TRACING, APP_NAME)
60+
5861
setup_instrumentation(app)
5962

6063
setup_rabbitmq(app)
@@ -63,9 +66,6 @@ def create_app() -> FastAPI:
6366
setup_rest_api(app)
6467
setup_rpc_api_routes(app)
6568

66-
if settings.AGENT_TRACING:
67-
initialize_tracing(app, settings.AGENT_TRACING, APP_NAME)
68-
6969
async def _on_startup() -> None:
7070
print(APP_STARTED_BANNER_MSG, flush=True) # noqa: T201
7171

services/api-server/src/simcore_service_api_server/core/application.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from models_library.basic_types import BootModeEnum
66
from packaging.version import Version
77
from servicelib.fastapi.profiler import initialize_profiler
8-
from servicelib.fastapi.tracing import initialize_tracing
8+
from servicelib.fastapi.tracing import setup_tracing
99
from servicelib.logging_utils import config_all_loggers
1010

1111
from .. import exceptions
@@ -82,6 +82,9 @@ def init_app(settings: ApplicationSettings | None = None) -> FastAPI:
8282

8383
app.state.settings = settings
8484

85+
if settings.API_SERVER_TRACING:
86+
setup_tracing(app, settings.API_SERVER_TRACING, APP_NAME)
87+
8588
if settings.API_SERVER_POSTGRES:
8689
setup_postgres(app)
8790

@@ -90,9 +93,6 @@ def init_app(settings: ApplicationSettings | None = None) -> FastAPI:
9093
if app.state.settings.API_SERVER_PROMETHEUS_INSTRUMENTATION_ENABLED:
9194
setup_prometheus_instrumentation(app)
9295

93-
if settings.API_SERVER_TRACING:
94-
initialize_tracing(app, settings.API_SERVER_TRACING, APP_NAME)
95-
9696
if settings.API_SERVER_WEBSERVER:
9797
webserver.setup(
9898
app,

services/autoscaling/src/simcore_service_autoscaling/core/application.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
22

33
from fastapi import FastAPI
4-
from servicelib.fastapi.tracing import initialize_tracing
4+
from servicelib.fastapi.tracing import setup_tracing
55

66
from .._meta import (
77
API_VERSION,
@@ -60,6 +60,9 @@ def create_app(settings: ApplicationSettings) -> FastAPI:
6060
assert app.state.settings.API_VERSION == API_VERSION # nosec
6161

6262
# PLUGINS SETUP
63+
if app.state.settings.AUTOSCALING_TRACING:
64+
setup_tracing(app, app.state.settings.AUTOSCALING_TRACING, APP_NAME)
65+
6366
setup_instrumentation(app)
6467
setup_api_routes(app)
6568
setup_docker(app)
@@ -70,8 +73,6 @@ def create_app(settings: ApplicationSettings) -> FastAPI:
7073

7174
setup_auto_scaler_background_task(app)
7275
setup_buffer_machines_pool_task(app)
73-
if app.state.settings.AUTOSCALING_TRACING:
74-
initialize_tracing(app, app.state.settings.AUTOSCALING_TRACING, APP_NAME)
7576

7677
# ERROR HANDLERS
7778

services/catalog/src/simcore_service_catalog/core/application.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
from models_library.basic_types import BootModeEnum
66
from servicelib.fastapi import timing_middleware
77
from servicelib.fastapi.monitoring import (
8-
initialize_prometheus_instrumentation,
8+
setup_prometheus_instrumentation,
99
)
1010
from servicelib.fastapi.openapi import override_fastapi_openapi_method
11-
from servicelib.fastapi.tracing import initialize_tracing
11+
from servicelib.fastapi.tracing import setup_tracing
1212
from starlette.middleware.base import BaseHTTPMiddleware
1313

1414
from .._meta import (
@@ -63,10 +63,10 @@ def create_app() -> FastAPI:
6363
app.state.settings = settings
6464

6565
# MIDDLEWARES
66-
if settings.CATALOG_PROMETHEUS_INSTRUMENTATION_ENABLED:
67-
initialize_prometheus_instrumentation(app)
6866
if settings.CATALOG_TRACING:
69-
initialize_tracing(app, settings.CATALOG_TRACING, APP_NAME)
67+
setup_tracing(app, settings.CATALOG_TRACING, APP_NAME)
68+
if settings.CATALOG_PROMETHEUS_INSTRUMENTATION_ENABLED:
69+
setup_prometheus_instrumentation(app)
7070

7171
if settings.SC_BOOT_MODE != BootModeEnum.PRODUCTION:
7272
# middleware to time requests (ONLY for development)

services/clusters-keeper/src/simcore_service_clusters_keeper/core/application.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from servicelib.fastapi.monitoring import (
55
setup_prometheus_instrumentation,
66
)
7-
from servicelib.fastapi.tracing import initialize_tracing
7+
from servicelib.fastapi.tracing import setup_tracing
88

99
from .._meta import (
1010
API_VERSION,
@@ -58,14 +58,14 @@ def create_app(settings: ApplicationSettings) -> FastAPI:
5858
app.state.settings = settings
5959
assert app.state.settings.API_VERSION == API_VERSION # nosec
6060

61-
if app.state.settings.CLUSTERS_KEEPER_PROMETHEUS_INSTRUMENTATION_ENABLED:
62-
setup_prometheus_instrumentation(app)
6361
if app.state.settings.CLUSTERS_KEEPER_TRACING:
64-
initialize_tracing(
62+
setup_tracing(
6563
app,
6664
app.state.settings.CLUSTERS_KEEPER_TRACING,
6765
APP_NAME,
6866
)
67+
if app.state.settings.CLUSTERS_KEEPER_PROMETHEUS_INSTRUMENTATION_ENABLED:
68+
setup_prometheus_instrumentation(app)
6969

7070
# PLUGINS SETUP
7171
setup_api_routes(app)

services/datcore-adapter/src/simcore_service_datcore_adapter/core/application.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
setup_prometheus_instrumentation,
1111
)
1212
from servicelib.fastapi.openapi import override_fastapi_openapi_method
13-
from servicelib.fastapi.tracing import initialize_tracing
13+
from servicelib.fastapi.tracing import setup_tracing
1414
from starlette.middleware.base import BaseHTTPMiddleware
1515

1616
from .._meta import API_VERSION, API_VTAG, APP_NAME
@@ -60,14 +60,14 @@ def create_app(settings: ApplicationSettings) -> FastAPI:
6060

6161
app.state.settings = settings
6262

63-
if app.state.settings.DATCORE_ADAPTER_PROMETHEUS_INSTRUMENTATION_ENABLED:
64-
setup_prometheus_instrumentation(app)
6563
if app.state.settings.DATCORE_ADAPTER_TRACING:
66-
initialize_tracing(
64+
setup_tracing(
6765
app,
6866
app.state.settings.DATCORE_ADAPTER_TRACING,
6967
APP_NAME,
7068
)
69+
if app.state.settings.DATCORE_ADAPTER_PROMETHEUS_INSTRUMENTATION_ENABLED:
70+
setup_prometheus_instrumentation(app)
7171

7272
if settings.SC_BOOT_MODE != BootModeEnum.PRODUCTION:
7373
# middleware to time requests (ONLY for development)

services/director-v2/src/simcore_service_director_v2/core/application.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
override_fastapi_openapi_method,
88
)
99
from servicelib.fastapi.profiler import initialize_profiler
10-
from servicelib.fastapi.tracing import initialize_tracing
10+
from servicelib.fastapi.tracing import setup_tracing
1111
from servicelib.logging_utils import config_all_loggers
1212

1313
from .._meta import API_VERSION, API_VTAG, APP_NAME, PROJECT_NAME, SUMMARY
@@ -145,12 +145,12 @@ def init_app(settings: AppSettings | None = None) -> FastAPI:
145145

146146
substitutions.setup(app)
147147

148+
if settings.DIRECTOR_V2_TRACING:
149+
setup_tracing(app, settings.DIRECTOR_V2_TRACING, APP_NAME)
150+
148151
if settings.DIRECTOR_V2_PROMETHEUS_INSTRUMENTATION_ENABLED:
149152
instrumentation.setup(app)
150153

151-
if settings.DIRECTOR_V2_TRACING:
152-
initialize_tracing(app, settings.DIRECTOR_V2_TRACING, APP_NAME)
153-
154154
if settings.DIRECTOR_V0.DIRECTOR_ENABLED:
155155
director_v0.setup(
156156
app,

services/director/src/simcore_service_director/core/application.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from fastapi import FastAPI
55
from servicelib.async_utils import cancel_sequential_workers
66
from servicelib.fastapi.client_session import setup_client_session
7-
from servicelib.fastapi.tracing import initialize_tracing
7+
from servicelib.fastapi.tracing import setup_tracing
88

99
from .._meta import (
1010
API_VERSION,
@@ -48,13 +48,13 @@ def create_app(settings: ApplicationSettings) -> FastAPI:
4848
assert app.state.settings.API_VERSION == API_VERSION # nosec
4949

5050
# PLUGINS SETUP
51+
if app.state.settings.DIRECTOR_TRACING:
52+
setup_tracing(app, app.state.settings.DIRECTOR_TRACING, APP_NAME)
53+
5154
setup_api_routes(app)
5255

5356
setup_instrumentation(app)
5457

55-
if app.state.settings.DIRECTOR_TRACING:
56-
initialize_tracing(app, app.state.settings.DIRECTOR_TRACING, APP_NAME)
57-
5858
setup_client_session(
5959
app,
6060
max_keepalive_connections=settings.DIRECTOR_REGISTRY_CLIENT_MAX_KEEPALIVE_CONNECTIONS,

0 commit comments

Comments
 (0)