Skip to content

Commit 4c17f00

Browse files
committed
move failsafe hook handling down to OpenTelemetryMiddleware
1 parent 0203e4b commit 4c17f00

File tree

2 files changed

+39
-20
lines changed
  • instrumentation
    • opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi
    • opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi

2 files changed

+39
-20
lines changed

instrumentation/opentelemetry-instrumentation-asgi/src/opentelemetry/instrumentation/asgi/__init__.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ def client_response_hook(span: Span, scope: Scope, message: dict[str, Any]):
268268
HTTP_SERVER_REQUEST_DURATION,
269269
)
270270
from opentelemetry.semconv.trace import SpanAttributes
271-
from opentelemetry.trace import set_span_in_context
271+
from opentelemetry.trace import Span, set_span_in_context
272272
from opentelemetry.util.http import (
273273
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SANITIZE_FIELDS,
274274
OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_SERVER_REQUEST,
@@ -646,9 +646,21 @@ def __init__(
646646
self.default_span_details = (
647647
default_span_details or get_default_span_details
648648
)
649-
self.server_request_hook = server_request_hook
650-
self.client_request_hook = client_request_hook
651-
self.client_response_hook = client_response_hook
649+
650+
def failsafe(func):
651+
@wraps(func)
652+
def wrapper(span: Span, *args, **kwargs):
653+
if func is not None:
654+
try:
655+
func(span, *args, **kwargs)
656+
except Exception as exc: # pylint: disable=broad-exception-caught
657+
span.record_exception(exc)
658+
659+
return wrapper
660+
661+
self.server_request_hook = failsafe(server_request_hook)
662+
self.client_request_hook = failsafe(client_request_hook)
663+
self.client_response_hook = failsafe(client_response_hook)
652664
self.content_length_header = None
653665
self._sem_conv_opt_in_mode = sem_conv_opt_in_mode
654666

instrumentation/opentelemetry-instrumentation-fastapi/src/opentelemetry/instrumentation/fastapi/__init__.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A
189189

190190
import fastapi
191191
from starlette.applications import Starlette
192+
from starlette.middleware.errors import ServerErrorMiddleware
192193
from starlette.routing import Match
193194
from starlette.types import ASGIApp, Receive, Scope, Send
194195

@@ -210,7 +211,6 @@ def client_response_hook(span: Span, scope: dict[str, Any], message: dict[str, A
210211
from opentelemetry.metrics import MeterProvider, get_meter
211212
from opentelemetry.semconv.attributes.http_attributes import HTTP_ROUTE
212213
from opentelemetry.trace import (
213-
Span,
214214
TracerProvider,
215215
get_current_span,
216216
get_tracer,
@@ -300,28 +300,17 @@ def instrument_app(
300300
# exceptions from user-provided hooks of tearing through, we wrap
301301
# them to return without failure unconditionally.
302302
def build_middleware_stack(self: Starlette) -> ASGIApp:
303-
def failsafe(func):
304-
@functools.wraps(func)
305-
def wrapper(span: Span, *args, **kwargs):
306-
if func is not None:
307-
try:
308-
func(span, *args, **kwargs)
309-
except Exception as exc: # pylint: disable=broad-exception-caught
310-
span.record_exception(exc)
311-
312-
return wrapper
313-
314303
inner_server_error_middleware: ASGIApp = ( # type: ignore
315304
self._original_build_middleware_stack() # type: ignore
316305
)
317306

318-
return OpenTelemetryMiddleware(
307+
otel_middleware = OpenTelemetryMiddleware(
319308
inner_server_error_middleware,
320309
excluded_urls=excluded_urls,
321310
default_span_details=_get_default_span_details,
322-
server_request_hook=failsafe(server_request_hook),
323-
client_request_hook=failsafe(client_request_hook),
324-
client_response_hook=failsafe(client_response_hook),
311+
server_request_hook=server_request_hook,
312+
client_request_hook=client_request_hook,
313+
client_response_hook=client_response_hook,
325314
# Pass in tracer/meter to get __name__and __version__ of fastapi instrumentation
326315
tracer=tracer,
327316
meter=meter,
@@ -331,6 +320,24 @@ def wrapper(span: Span, *args, **kwargs):
331320
exclude_spans=exclude_spans,
332321
)
333322

323+
# Wrap in an outer layer of ServerErrorMiddleware so that any exceptions raised in OpenTelemetryMiddleware
324+
# are handled.
325+
# This should not happen unless there is a bug in OpenTelemetryMiddleware, but if there is we don't want that
326+
# to impact the user's application just because we wrapped the middlewares in this order.
327+
if isinstance(
328+
inner_server_error_middleware, ServerErrorMiddleware
329+
): # usually true
330+
outer_server_error_middleware = ServerErrorMiddleware(
331+
app=otel_middleware,
332+
)
333+
else:
334+
# Something else seems to have patched things, or maybe Starlette changed.
335+
# Just create a default ServerErrorMiddleware.
336+
outer_server_error_middleware = ServerErrorMiddleware(
337+
app=otel_middleware
338+
)
339+
return outer_server_error_middleware
340+
334341
app._original_build_middleware_stack = app.build_middleware_stack
335342
app.build_middleware_stack = types.MethodType(
336343
functools.wraps(app.build_middleware_stack)(

0 commit comments

Comments
 (0)