Skip to content

Commit 1f73bf7

Browse files
committed
add osparc-trace-id response header to api-server and webserver
1 parent 8df1e20 commit 1f73bf7

File tree

5 files changed

+57
-5
lines changed

5 files changed

+57
-5
lines changed

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

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from opentelemetry.sdk.trace import TracerProvider
1919
from opentelemetry.sdk.trace.export import BatchSpanProcessor
2020
from servicelib.logging_utils import log_context
21+
from servicelib.tracing import get_trace_id_header
2122
from settings_library.tracing import TracingSettings
2223
from yarl import URL
2324

@@ -55,6 +56,7 @@ def _startup(
5556
app: web.Application,
5657
tracing_settings: TracingSettings,
5758
service_name: str,
59+
add_response_trace_id_header: bool = False,
5860
) -> None:
5961
"""
6062
Sets up this service for a distributed tracing system (opentelemetry)
@@ -106,6 +108,8 @@ def _startup(
106108
#
107109
# Since the code that is provided (monkeypatched) in the __init__ that the opentelemetry-autoinstrumentation-library provides is only 4 lines,
108110
# just adding a middleware, we are free to simply execute this "missed call" [since we can't call the monkeypatch'ed __init__()] in this following line:
111+
if add_response_trace_id_header:
112+
app.middlewares.insert(0, ResponseTraceIdHeaderMiddleware)
109113
app.middlewares.insert(0, aiohttp_server_opentelemetry_middleware)
110114
# Code of the aiohttp server instrumentation: github.com/open-telemetry/opentelemetry-python-contrib/blob/eccb05c808a7d797ef5b6ecefed3590664426fbf/instrumentation/opentelemetry-instrumentation-aiohttp-server/src/opentelemetry/instrumentation/aiohttp_server/__init__.py#L246
111115
# For reference, the above statement was written for:
@@ -146,6 +150,15 @@ def _startup(
146150
AioPikaInstrumentor().instrument()
147151

148152

153+
@web.middleware
154+
async def ResponseTraceIdHeaderMiddleware(request: web.Request, handler):
155+
response = await handler(request)
156+
trace_id_header = get_trace_id_header()
157+
if trace_id_header:
158+
response.headers.update(trace_id_header)
159+
return response
160+
161+
149162
def _shutdown() -> None:
150163
"""Uninstruments all opentelemetry instrumentors that were instrumented."""
151164
try:
@@ -175,9 +188,17 @@ def _shutdown() -> None:
175188

176189

177190
def get_tracing_lifespan(
178-
app: web.Application, tracing_settings: TracingSettings, service_name: str
191+
app: web.Application,
192+
tracing_settings: TracingSettings,
193+
service_name: str,
194+
add_response_trace_id_header: bool = False,
179195
) -> Callable[[web.Application], AsyncIterator]:
180-
_startup(app=app, tracing_settings=tracing_settings, service_name=service_name)
196+
_startup(
197+
app=app,
198+
tracing_settings=tracing_settings,
199+
service_name=service_name,
200+
add_response_trace_id_header=add_response_trace_id_header,
201+
)
181202

182203
async def tracing_lifespan(app: web.Application):
183204
assert app # nosec

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import logging
44
from collections.abc import AsyncIterator
55

6-
from fastapi import FastAPI
6+
from fastapi import FastAPI, Request
77
from fastapi_lifespan_manager import State
88
from httpx import AsyncClient, Client
99
from opentelemetry import trace
@@ -16,7 +16,9 @@
1616
from opentelemetry.sdk.trace import TracerProvider
1717
from opentelemetry.sdk.trace.export import BatchSpanProcessor
1818
from servicelib.logging_utils import log_context
19+
from servicelib.tracing import get_trace_id_header
1920
from settings_library.tracing import TracingSettings
21+
from starlette.middleware.base import BaseHTTPMiddleware
2022
from yarl import URL
2123

2224
_logger = logging.getLogger(__name__)
@@ -180,7 +182,11 @@ def _shutdown() -> None:
180182
_logger.exception("Failed to uninstrument RequestsInstrumentor")
181183

182184

183-
def initialize_fastapi_app_tracing(app: FastAPI):
185+
def initialize_fastapi_app_tracing(
186+
app: FastAPI, *, add_response_trace_id_header: bool = False
187+
):
188+
if add_response_trace_id_header:
189+
app.add_middleware(ResponseTraceIdHeaderMiddleware)
184190
FastAPIInstrumentor.instrument_app(app)
185191

186192

@@ -216,3 +222,13 @@ async def tracing_instrumentation_lifespan(
216222
_shutdown()
217223

218224
return tracing_instrumentation_lifespan
225+
226+
227+
class ResponseTraceIdHeaderMiddleware(BaseHTTPMiddleware):
228+
229+
async def dispatch(self, request: Request, call_next):
230+
response = await call_next(request)
231+
trace_id_header = get_trace_id_header()
232+
if trace_id_header:
233+
response.headers.update(trace_id_header)
234+
return response

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
TracingContext: TypeAlias = otcontext.Context | None
1010

11+
_OSPARC_TRACE_ID_HEADER = "X-Osparc-Trace-Id"
12+
1113

1214
def _is_tracing() -> bool:
1315
return trace.get_current_span().is_recording()
@@ -34,3 +36,15 @@ def use_tracing_context(context: TracingContext):
3436
def setup_log_tracing(tracing_settings: TracingSettings):
3537
_ = tracing_settings
3638
LoggingInstrumentor().instrument(set_logging_format=False)
39+
40+
41+
def get_trace_id_header() -> dict[str, str] | None:
42+
"""Generates a dictionary containing the trace ID header if tracing is active."""
43+
span = trace.get_current_span()
44+
if span.is_recording():
45+
trace_id = span.get_span_context().trace_id
46+
trace_id_hex = format(
47+
trace_id, "032x"
48+
) # Convert trace_id to 32-character hex string
49+
return {_OSPARC_TRACE_ID_HEADER: trace_id_hex}
50+
return None

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ def init_app(settings: ApplicationSettings | None = None) -> FastAPI:
9797
setup_prometheus_instrumentation(app)
9898

9999
if settings.API_SERVER_TRACING:
100-
initialize_fastapi_app_tracing(app)
100+
initialize_fastapi_app_tracing(app, add_response_trace_id_header=True)
101101

102102
if settings.API_SERVER_WEBSERVER:
103103
webserver.setup(

services/web/server/src/simcore_service_webserver/tracing.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,6 @@ def setup_app_tracing(app: web.Application):
2828
app,
2929
tracing_settings=tracing_settings,
3030
service_name=APP_NAME,
31+
add_response_trace_id_header=True,
3132
)
3233
)

0 commit comments

Comments
 (0)