fix: flatten React Router structure — no nested Routes, each page is a top-level route
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
from functools import wraps
|
||||
from typing import TYPE_CHECKING, Any, Optional, Sequence
|
||||
|
||||
from sentry_sdk.integrations import DidNotEnable, Integration
|
||||
from sentry_sdk.utils import parse_version
|
||||
|
||||
from .client import ClientInterceptor
|
||||
from .server import ServerInterceptor
|
||||
|
||||
try:
|
||||
import grpc
|
||||
from grpc import Channel, Server, intercept_channel
|
||||
from grpc.aio import Channel as AsyncChannel
|
||||
from grpc.aio import Server as AsyncServer
|
||||
|
||||
from .aio.client import (
|
||||
SentryUnaryStreamClientInterceptor as AsyncUnaryStreamClientIntercetor,
|
||||
)
|
||||
from .aio.client import (
|
||||
SentryUnaryUnaryClientInterceptor as AsyncUnaryUnaryClientInterceptor,
|
||||
)
|
||||
from .aio.server import ServerInterceptor as AsyncServerInterceptor
|
||||
except ImportError:
|
||||
raise DidNotEnable("grpcio is not installed.")
|
||||
|
||||
# Hack to get new Python features working in older versions
|
||||
# without introducing a hard dependency on `typing_extensions`
|
||||
# from: https://stackoverflow.com/a/71944042/300572
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, ParamSpec
|
||||
else:
|
||||
# Fake ParamSpec
|
||||
class ParamSpec:
|
||||
def __init__(self, _):
|
||||
self.args = None
|
||||
self.kwargs = None
|
||||
|
||||
# Callable[anything] will return None
|
||||
class _Callable:
|
||||
def __getitem__(self, _):
|
||||
return None
|
||||
|
||||
# Make instances
|
||||
Callable = _Callable()
|
||||
|
||||
P = ParamSpec("P")
|
||||
|
||||
GRPC_VERSION = parse_version(grpc.__version__)
|
||||
|
||||
|
||||
def _is_channel_intercepted(channel: "Channel") -> bool:
|
||||
interceptor = getattr(channel, "_interceptor", None)
|
||||
while interceptor is not None:
|
||||
if isinstance(interceptor, ClientInterceptor):
|
||||
return True
|
||||
|
||||
inner_channel = getattr(channel, "_channel", None)
|
||||
if inner_channel is None:
|
||||
return False
|
||||
|
||||
channel = inner_channel
|
||||
interceptor = getattr(channel, "_interceptor", None)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _wrap_channel_sync(func: "Callable[P, Channel]") -> "Callable[P, Channel]":
|
||||
"Wrapper for synchronous secure and insecure channel."
|
||||
|
||||
@wraps(func)
|
||||
def patched_channel(*args: "Any", **kwargs: "Any") -> "Channel":
|
||||
channel = func(*args, **kwargs)
|
||||
if not _is_channel_intercepted(channel):
|
||||
return intercept_channel(channel, ClientInterceptor())
|
||||
else:
|
||||
return channel
|
||||
|
||||
return patched_channel
|
||||
|
||||
|
||||
def _wrap_intercept_channel(func: "Callable[P, Channel]") -> "Callable[P, Channel]":
|
||||
@wraps(func)
|
||||
def patched_intercept_channel(
|
||||
channel: "Channel", *interceptors: "grpc.ServerInterceptor"
|
||||
) -> "Channel":
|
||||
if _is_channel_intercepted(channel):
|
||||
interceptors = tuple(
|
||||
[
|
||||
interceptor
|
||||
for interceptor in interceptors
|
||||
if not isinstance(interceptor, ClientInterceptor)
|
||||
]
|
||||
)
|
||||
else:
|
||||
interceptors = interceptors
|
||||
return intercept_channel(channel, *interceptors)
|
||||
|
||||
return patched_intercept_channel # type: ignore
|
||||
|
||||
|
||||
def _wrap_channel_async(
|
||||
func: "Callable[P, AsyncChannel]",
|
||||
) -> "Callable[P, AsyncChannel]":
|
||||
"Wrapper for asynchronous secure and insecure channel."
|
||||
|
||||
@wraps(func)
|
||||
def patched_channel( # type: ignore
|
||||
*args: "P.args",
|
||||
interceptors: "Optional[Sequence[grpc.aio.ClientInterceptor]]" = None,
|
||||
**kwargs: "P.kwargs",
|
||||
) -> "Channel":
|
||||
sentry_interceptors = [
|
||||
AsyncUnaryUnaryClientInterceptor(),
|
||||
AsyncUnaryStreamClientIntercetor(),
|
||||
]
|
||||
interceptors = [*sentry_interceptors, *(interceptors or [])]
|
||||
return func(*args, interceptors=interceptors, **kwargs) # type: ignore
|
||||
|
||||
return patched_channel # type: ignore
|
||||
|
||||
|
||||
def _wrap_sync_server(func: "Callable[P, Server]") -> "Callable[P, Server]":
|
||||
"""Wrapper for synchronous server."""
|
||||
|
||||
@wraps(func)
|
||||
def patched_server( # type: ignore
|
||||
*args: "P.args",
|
||||
interceptors: "Optional[Sequence[grpc.ServerInterceptor]]" = None,
|
||||
**kwargs: "P.kwargs",
|
||||
) -> "Server":
|
||||
interceptors = [
|
||||
interceptor
|
||||
for interceptor in interceptors or []
|
||||
if not isinstance(interceptor, ServerInterceptor)
|
||||
]
|
||||
server_interceptor = ServerInterceptor()
|
||||
interceptors = [server_interceptor, *(interceptors or [])]
|
||||
return func(*args, interceptors=interceptors, **kwargs) # type: ignore
|
||||
|
||||
return patched_server # type: ignore
|
||||
|
||||
|
||||
def _wrap_async_server(func: "Callable[P, AsyncServer]") -> "Callable[P, AsyncServer]":
|
||||
"""Wrapper for asynchronous server."""
|
||||
|
||||
@wraps(func)
|
||||
def patched_aio_server( # type: ignore
|
||||
*args: "P.args",
|
||||
interceptors: "Optional[Sequence[grpc.ServerInterceptor]]" = None,
|
||||
**kwargs: "P.kwargs",
|
||||
) -> "Server":
|
||||
server_interceptor = AsyncServerInterceptor()
|
||||
interceptors = [
|
||||
server_interceptor,
|
||||
*(interceptors or []),
|
||||
]
|
||||
|
||||
try:
|
||||
# We prefer interceptors as a list because of compatibility with
|
||||
# opentelemetry https://github.com/getsentry/sentry-python/issues/4389
|
||||
# However, prior to grpc 1.42.0, only tuples were accepted, so we
|
||||
# have no choice there.
|
||||
if GRPC_VERSION is not None and GRPC_VERSION < (1, 42, 0):
|
||||
interceptors = tuple(interceptors)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return func(*args, interceptors=interceptors, **kwargs) # type: ignore
|
||||
|
||||
return patched_aio_server # type: ignore
|
||||
|
||||
|
||||
class GRPCIntegration(Integration):
|
||||
identifier = "grpc"
|
||||
|
||||
@staticmethod
|
||||
def setup_once() -> None:
|
||||
import grpc
|
||||
|
||||
grpc.insecure_channel = _wrap_channel_sync(grpc.insecure_channel)
|
||||
grpc.secure_channel = _wrap_channel_sync(grpc.secure_channel)
|
||||
grpc.intercept_channel = _wrap_intercept_channel(grpc.intercept_channel)
|
||||
|
||||
grpc.aio.insecure_channel = _wrap_channel_async(grpc.aio.insecure_channel)
|
||||
grpc.aio.secure_channel = _wrap_channel_async(grpc.aio.secure_channel)
|
||||
|
||||
grpc.server = _wrap_sync_server(grpc.server)
|
||||
grpc.aio.server = _wrap_async_server(grpc.aio.server)
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
from .client import ClientInterceptor
|
||||
from .server import ServerInterceptor
|
||||
|
||||
__all__ = [
|
||||
"ClientInterceptor",
|
||||
"ServerInterceptor",
|
||||
]
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
from typing import Any, AsyncIterable, Callable, Union
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.consts import OP, SPANDATA
|
||||
from sentry_sdk.integrations import DidNotEnable
|
||||
from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN
|
||||
from sentry_sdk.tracing_utils import has_span_streaming_enabled
|
||||
|
||||
try:
|
||||
from google.protobuf.message import Message
|
||||
from grpc.aio import (
|
||||
ClientCallDetails,
|
||||
Metadata,
|
||||
UnaryStreamCall,
|
||||
UnaryStreamClientInterceptor,
|
||||
UnaryUnaryCall,
|
||||
UnaryUnaryClientInterceptor,
|
||||
)
|
||||
except ImportError:
|
||||
raise DidNotEnable("grpcio is not installed")
|
||||
|
||||
|
||||
class ClientInterceptor:
|
||||
@staticmethod
|
||||
def _update_client_call_details_metadata_from_scope(
|
||||
client_call_details: "ClientCallDetails",
|
||||
) -> "ClientCallDetails":
|
||||
if client_call_details.metadata is None:
|
||||
client_call_details = client_call_details._replace(metadata=Metadata())
|
||||
elif not isinstance(client_call_details.metadata, Metadata):
|
||||
# This is a workaround for a GRPC bug, which was fixed in grpcio v1.60.0
|
||||
# See https://github.com/grpc/grpc/issues/34298.
|
||||
client_call_details = client_call_details._replace(
|
||||
metadata=Metadata.from_tuple(client_call_details.metadata)
|
||||
)
|
||||
for (
|
||||
key,
|
||||
value,
|
||||
) in sentry_sdk.get_current_scope().iter_trace_propagation_headers():
|
||||
client_call_details.metadata.add(key, value)
|
||||
return client_call_details
|
||||
|
||||
|
||||
class SentryUnaryUnaryClientInterceptor(ClientInterceptor, UnaryUnaryClientInterceptor): # type: ignore
|
||||
async def intercept_unary_unary(
|
||||
self,
|
||||
continuation: "Callable[[ClientCallDetails, Message], UnaryUnaryCall]",
|
||||
client_call_details: "ClientCallDetails",
|
||||
request: "Message",
|
||||
) -> "Union[UnaryUnaryCall, Message]":
|
||||
method = client_call_details.method
|
||||
|
||||
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
|
||||
if span_streaming:
|
||||
if sentry_sdk.traces.get_current_span() is None:
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
return await continuation(client_call_details, request)
|
||||
with sentry_sdk.traces.start_span(
|
||||
name="unary unary call to %s" % method.decode(),
|
||||
attributes={
|
||||
"sentry.op": OP.GRPC_CLIENT,
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
SPANDATA.RPC_METHOD: method.decode(),
|
||||
},
|
||||
) as span:
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
|
||||
response = await continuation(client_call_details, request)
|
||||
status_code = await response.code()
|
||||
span.set_attribute(SPANDATA.RPC_RESPONSE_STATUS_CODE, status_code.name)
|
||||
|
||||
return response
|
||||
else:
|
||||
with sentry_sdk.start_span(
|
||||
op=OP.GRPC_CLIENT,
|
||||
name="unary unary call to %s" % method.decode(),
|
||||
origin=SPAN_ORIGIN,
|
||||
) as span:
|
||||
span.set_data("type", "unary unary")
|
||||
span.set_data("method", method)
|
||||
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
|
||||
response = await continuation(client_call_details, request)
|
||||
status_code = await response.code()
|
||||
span.set_data("code", status_code.name)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
class SentryUnaryStreamClientInterceptor(
|
||||
ClientInterceptor,
|
||||
UnaryStreamClientInterceptor, # type: ignore
|
||||
):
|
||||
async def intercept_unary_stream(
|
||||
self,
|
||||
continuation: "Callable[[ClientCallDetails, Message], UnaryStreamCall]",
|
||||
client_call_details: "ClientCallDetails",
|
||||
request: "Message",
|
||||
) -> "Union[AsyncIterable[Any], UnaryStreamCall]":
|
||||
method = client_call_details.method
|
||||
|
||||
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
|
||||
if span_streaming:
|
||||
if sentry_sdk.traces.get_current_span() is None:
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
return await continuation(client_call_details, request)
|
||||
with sentry_sdk.traces.start_span(
|
||||
name="unary stream call to %s" % method.decode(),
|
||||
attributes={
|
||||
"sentry.op": OP.GRPC_CLIENT,
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
SPANDATA.RPC_METHOD: method.decode(),
|
||||
},
|
||||
) as span:
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
|
||||
response = await continuation(client_call_details, request)
|
||||
|
||||
return response
|
||||
else:
|
||||
with sentry_sdk.start_span(
|
||||
op=OP.GRPC_CLIENT,
|
||||
name="unary stream call to %s" % method.decode(),
|
||||
origin=SPAN_ORIGIN,
|
||||
) as span:
|
||||
span.set_data("type", "unary stream")
|
||||
span.set_data("method", method)
|
||||
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
|
||||
response = await continuation(client_call_details, request)
|
||||
# status_code = await response.code()
|
||||
# span.set_data("code", status_code)
|
||||
|
||||
return response
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.consts import OP
|
||||
from sentry_sdk.integrations import DidNotEnable
|
||||
from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN
|
||||
from sentry_sdk.traces import SegmentSource
|
||||
from sentry_sdk.tracing import TransactionSource
|
||||
from sentry_sdk.tracing_utils import has_span_streaming_enabled
|
||||
from sentry_sdk.utils import event_from_exception
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Awaitable, Callable
|
||||
from typing import Any, Optional
|
||||
|
||||
|
||||
try:
|
||||
import grpc
|
||||
from grpc import HandlerCallDetails, RpcMethodHandler
|
||||
from grpc.aio import AbortError, ServicerContext
|
||||
except ImportError:
|
||||
raise DidNotEnable("grpcio is not installed")
|
||||
|
||||
|
||||
class ServerInterceptor(grpc.aio.ServerInterceptor): # type: ignore
|
||||
def __init__(
|
||||
self: "ServerInterceptor",
|
||||
find_name: "Callable[[ServicerContext], str] | None" = None,
|
||||
) -> None:
|
||||
self._custom_find_name = find_name
|
||||
|
||||
super().__init__()
|
||||
|
||||
async def intercept_service(
|
||||
self: "ServerInterceptor",
|
||||
continuation: "Callable[[HandlerCallDetails], Awaitable[RpcMethodHandler]]",
|
||||
handler_call_details: "HandlerCallDetails",
|
||||
) -> "Optional[Awaitable[RpcMethodHandler]]":
|
||||
handler = await continuation(handler_call_details)
|
||||
if handler is None:
|
||||
return None
|
||||
|
||||
method_name = handler_call_details.method
|
||||
custom_find_name = self._custom_find_name
|
||||
|
||||
if not handler.request_streaming and not handler.response_streaming:
|
||||
handler_factory = grpc.unary_unary_rpc_method_handler
|
||||
|
||||
async def wrapped(request: "Any", context: "ServicerContext") -> "Any":
|
||||
with sentry_sdk.isolation_scope():
|
||||
name = (
|
||||
custom_find_name(context) if custom_find_name else method_name
|
||||
)
|
||||
if not name:
|
||||
return await handler(request, context)
|
||||
|
||||
span_streaming = has_span_streaming_enabled(
|
||||
sentry_sdk.get_client().options
|
||||
)
|
||||
if span_streaming:
|
||||
# What if the headers are empty?
|
||||
sentry_sdk.traces.continue_trace(
|
||||
dict(context.invocation_metadata())
|
||||
)
|
||||
|
||||
with sentry_sdk.traces.start_span(
|
||||
name=name,
|
||||
attributes={
|
||||
"sentry.op": OP.GRPC_SERVER,
|
||||
"sentry.span.source": SegmentSource.CUSTOM.value,
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
},
|
||||
parent_span=None,
|
||||
):
|
||||
try:
|
||||
return await handler.unary_unary(request, context)
|
||||
except AbortError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
event, hint = event_from_exception(
|
||||
exc,
|
||||
mechanism={"type": "grpc", "handled": False},
|
||||
)
|
||||
sentry_sdk.capture_event(event, hint=hint)
|
||||
raise
|
||||
else:
|
||||
# What if the headers are empty?
|
||||
transaction = sentry_sdk.continue_trace(
|
||||
dict(context.invocation_metadata()),
|
||||
op=OP.GRPC_SERVER,
|
||||
name=name,
|
||||
source=TransactionSource.CUSTOM,
|
||||
origin=SPAN_ORIGIN,
|
||||
)
|
||||
|
||||
with sentry_sdk.start_transaction(transaction=transaction):
|
||||
try:
|
||||
return await handler.unary_unary(request, context)
|
||||
except AbortError:
|
||||
raise
|
||||
except Exception as exc:
|
||||
event, hint = event_from_exception(
|
||||
exc,
|
||||
mechanism={"type": "grpc", "handled": False},
|
||||
)
|
||||
sentry_sdk.capture_event(event, hint=hint)
|
||||
raise
|
||||
|
||||
elif not handler.request_streaming and handler.response_streaming:
|
||||
handler_factory = grpc.unary_stream_rpc_method_handler
|
||||
|
||||
async def wrapped(request: "Any", context: "ServicerContext") -> "Any": # type: ignore
|
||||
async for r in handler.unary_stream(request, context):
|
||||
yield r
|
||||
|
||||
elif handler.request_streaming and not handler.response_streaming:
|
||||
handler_factory = grpc.stream_unary_rpc_method_handler
|
||||
|
||||
async def wrapped(request: "Any", context: "ServicerContext") -> "Any":
|
||||
response = handler.stream_unary(request, context)
|
||||
return await response
|
||||
|
||||
elif handler.request_streaming and handler.response_streaming:
|
||||
handler_factory = grpc.stream_stream_rpc_method_handler
|
||||
|
||||
async def wrapped(request: "Any", context: "ServicerContext") -> "Any": # type: ignore
|
||||
async for r in handler.stream_stream(request, context):
|
||||
yield r
|
||||
|
||||
return handler_factory(
|
||||
wrapped,
|
||||
request_deserializer=handler.request_deserializer,
|
||||
response_serializer=handler.response_serializer,
|
||||
)
|
||||
@@ -0,0 +1,163 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.consts import OP, SPANDATA
|
||||
from sentry_sdk.integrations import DidNotEnable
|
||||
from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN
|
||||
from sentry_sdk.tracing_utils import has_span_streaming_enabled
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable, Iterable, Iterator, Union
|
||||
|
||||
try:
|
||||
import grpc
|
||||
from google.protobuf.message import Message
|
||||
from grpc import Call, ClientCallDetails
|
||||
from grpc._interceptor import _UnaryOutcome
|
||||
from grpc.aio._interceptor import UnaryStreamCall
|
||||
except ImportError:
|
||||
raise DidNotEnable("grpcio is not installed")
|
||||
|
||||
|
||||
class ClientInterceptor(
|
||||
grpc.UnaryUnaryClientInterceptor, # type: ignore
|
||||
grpc.UnaryStreamClientInterceptor, # type: ignore
|
||||
):
|
||||
def intercept_unary_unary(
|
||||
self: "ClientInterceptor",
|
||||
continuation: "Callable[[ClientCallDetails, Message], _UnaryOutcome]",
|
||||
client_call_details: "ClientCallDetails",
|
||||
request: "Message",
|
||||
) -> "_UnaryOutcome":
|
||||
method = client_call_details.method
|
||||
|
||||
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
|
||||
if span_streaming:
|
||||
if sentry_sdk.traces.get_current_span() is None:
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
return continuation(client_call_details, request)
|
||||
with sentry_sdk.traces.start_span(
|
||||
name="unary unary call to %s" % method,
|
||||
attributes={
|
||||
"sentry.op": OP.GRPC_CLIENT,
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
SPANDATA.RPC_METHOD: method,
|
||||
},
|
||||
) as span:
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
|
||||
response = continuation(client_call_details, request)
|
||||
span.set_attribute(
|
||||
SPANDATA.RPC_RESPONSE_STATUS_CODE, response.code().name
|
||||
)
|
||||
|
||||
return response
|
||||
else:
|
||||
with sentry_sdk.start_span(
|
||||
op=OP.GRPC_CLIENT,
|
||||
name="unary unary call to %s" % method,
|
||||
origin=SPAN_ORIGIN,
|
||||
) as span:
|
||||
span.set_data("type", "unary unary")
|
||||
span.set_data("method", method)
|
||||
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
|
||||
response = continuation(client_call_details, request)
|
||||
span.set_data("code", response.code().name)
|
||||
|
||||
return response
|
||||
|
||||
def intercept_unary_stream(
|
||||
self: "ClientInterceptor",
|
||||
continuation: "Callable[[ClientCallDetails, Message], Union[Iterable[Any], UnaryStreamCall]]",
|
||||
client_call_details: "ClientCallDetails",
|
||||
request: "Message",
|
||||
) -> "Union[Iterator[Message], Call]":
|
||||
method = client_call_details.method
|
||||
|
||||
span_streaming = has_span_streaming_enabled(sentry_sdk.get_client().options)
|
||||
response: "UnaryStreamCall"
|
||||
if span_streaming:
|
||||
if sentry_sdk.traces.get_current_span() is None:
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
return continuation(client_call_details, request)
|
||||
with sentry_sdk.traces.start_span(
|
||||
name="unary stream call to %s" % method,
|
||||
attributes={
|
||||
"sentry.op": OP.GRPC_CLIENT,
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
SPANDATA.RPC_METHOD: method,
|
||||
},
|
||||
) as span:
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
|
||||
response = continuation(client_call_details, request)
|
||||
# Setting code on unary-stream leads to execution getting stuck
|
||||
# span.set_data("code", response.code().name)
|
||||
|
||||
return response
|
||||
else:
|
||||
with sentry_sdk.start_span(
|
||||
op=OP.GRPC_CLIENT,
|
||||
name="unary stream call to %s" % method,
|
||||
origin=SPAN_ORIGIN,
|
||||
) as span:
|
||||
span.set_data("type", "unary stream")
|
||||
span.set_data("method", method)
|
||||
|
||||
client_call_details = (
|
||||
self._update_client_call_details_metadata_from_scope(
|
||||
client_call_details
|
||||
)
|
||||
)
|
||||
|
||||
response = continuation(client_call_details, request)
|
||||
# Setting code on unary-stream leads to execution getting stuck
|
||||
# span.set_data("code", response.code().name)
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _update_client_call_details_metadata_from_scope(
|
||||
client_call_details: "ClientCallDetails",
|
||||
) -> "ClientCallDetails":
|
||||
metadata = (
|
||||
list(client_call_details.metadata) if client_call_details.metadata else []
|
||||
)
|
||||
for (
|
||||
key,
|
||||
value,
|
||||
) in sentry_sdk.get_current_scope().iter_trace_propagation_headers():
|
||||
metadata.append((key, value))
|
||||
|
||||
client_call_details = grpc._interceptor._ClientCallDetails(
|
||||
method=client_call_details.method,
|
||||
timeout=client_call_details.timeout,
|
||||
metadata=metadata,
|
||||
credentials=client_call_details.credentials,
|
||||
wait_for_ready=client_call_details.wait_for_ready,
|
||||
compression=client_call_details.compression,
|
||||
)
|
||||
|
||||
return client_call_details
|
||||
@@ -0,0 +1 @@
|
||||
SPAN_ORIGIN = "auto.grpc.grpc"
|
||||
@@ -0,0 +1,91 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.consts import OP
|
||||
from sentry_sdk.integrations import DidNotEnable
|
||||
from sentry_sdk.integrations.grpc.consts import SPAN_ORIGIN
|
||||
from sentry_sdk.traces import SegmentSource
|
||||
from sentry_sdk.tracing import TransactionSource
|
||||
from sentry_sdk.tracing_utils import has_span_streaming_enabled
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Callable, Optional
|
||||
|
||||
from google.protobuf.message import Message
|
||||
|
||||
try:
|
||||
import grpc
|
||||
from grpc import HandlerCallDetails, RpcMethodHandler, ServicerContext
|
||||
except ImportError:
|
||||
raise DidNotEnable("grpcio is not installed")
|
||||
|
||||
|
||||
class ServerInterceptor(grpc.ServerInterceptor): # type: ignore
|
||||
def __init__(
|
||||
self: "ServerInterceptor",
|
||||
find_name: "Optional[Callable[[ServicerContext], str]]" = None,
|
||||
) -> None:
|
||||
self._custom_find_name = find_name
|
||||
|
||||
super().__init__()
|
||||
|
||||
def intercept_service(
|
||||
self: "ServerInterceptor",
|
||||
continuation: "Callable[[HandlerCallDetails], RpcMethodHandler]",
|
||||
handler_call_details: "HandlerCallDetails",
|
||||
) -> "RpcMethodHandler":
|
||||
handler = continuation(handler_call_details)
|
||||
if not handler or not handler.unary_unary:
|
||||
return handler
|
||||
|
||||
method_name = handler_call_details.method
|
||||
custom_find_name = self._custom_find_name
|
||||
|
||||
def behavior(request: "Message", context: "ServicerContext") -> "Message":
|
||||
with sentry_sdk.isolation_scope():
|
||||
name = custom_find_name(context) if custom_find_name else method_name
|
||||
|
||||
if name:
|
||||
metadata = dict(context.invocation_metadata())
|
||||
|
||||
span_streaming = has_span_streaming_enabled(
|
||||
sentry_sdk.get_client().options
|
||||
)
|
||||
if span_streaming:
|
||||
sentry_sdk.traces.continue_trace(metadata)
|
||||
|
||||
with sentry_sdk.traces.start_span(
|
||||
name=name,
|
||||
attributes={
|
||||
"sentry.op": OP.GRPC_SERVER,
|
||||
"sentry.span.source": SegmentSource.CUSTOM.value,
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
},
|
||||
parent_span=None,
|
||||
):
|
||||
try:
|
||||
return handler.unary_unary(request, context)
|
||||
except BaseException as e:
|
||||
raise e
|
||||
else:
|
||||
transaction = sentry_sdk.continue_trace(
|
||||
metadata,
|
||||
op=OP.GRPC_SERVER,
|
||||
name=name,
|
||||
source=TransactionSource.CUSTOM,
|
||||
origin=SPAN_ORIGIN,
|
||||
)
|
||||
|
||||
with sentry_sdk.start_transaction(transaction=transaction):
|
||||
try:
|
||||
return handler.unary_unary(request, context)
|
||||
except BaseException as e:
|
||||
raise e
|
||||
else:
|
||||
return handler.unary_unary(request, context)
|
||||
|
||||
return grpc.unary_unary_rpc_method_handler(
|
||||
behavior,
|
||||
request_deserializer=handler.request_deserializer,
|
||||
response_serializer=handler.response_serializer,
|
||||
)
|
||||
Reference in New Issue
Block a user