fix: flatten React Router structure — no nested Routes, each page is a top-level route
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sentry_sdk.integrations import DidNotEnable, Integration
|
||||
from sentry_sdk.integrations.redis.consts import _DEFAULT_MAX_DATA_SIZE
|
||||
from sentry_sdk.integrations.redis.rb import _patch_rb
|
||||
from sentry_sdk.integrations.redis.redis import _patch_redis
|
||||
from sentry_sdk.integrations.redis.redis_cluster import _patch_redis_cluster
|
||||
from sentry_sdk.integrations.redis.redis_py_cluster_legacy import _patch_rediscluster
|
||||
from sentry_sdk.utils import logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class RedisIntegration(Integration):
|
||||
identifier = "redis"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
max_data_size: "Optional[int]" = _DEFAULT_MAX_DATA_SIZE,
|
||||
cache_prefixes: "Optional[list[str]]" = None,
|
||||
) -> None:
|
||||
self.max_data_size = max_data_size
|
||||
self.cache_prefixes = cache_prefixes if cache_prefixes is not None else []
|
||||
|
||||
if max_data_size is not None:
|
||||
warnings.warn(
|
||||
"The `max_data_size` parameter of `RedisIntegration` is "
|
||||
"deprecated and will be removed in version 3.0 of sentry-sdk.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def setup_once() -> None:
|
||||
try:
|
||||
from redis import StrictRedis, client
|
||||
except ImportError:
|
||||
raise DidNotEnable("Redis client not installed")
|
||||
|
||||
_patch_redis(StrictRedis, client)
|
||||
_patch_redis_cluster()
|
||||
_patch_rb()
|
||||
|
||||
try:
|
||||
_patch_rediscluster()
|
||||
except Exception:
|
||||
logger.exception("Error occurred while patching `rediscluster` library")
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.consts import OP, SPANDATA
|
||||
from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
|
||||
from sentry_sdk.integrations.redis.modules.caches import (
|
||||
_compile_cache_span_properties,
|
||||
_set_cache_data,
|
||||
)
|
||||
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
|
||||
from sentry_sdk.integrations.redis.utils import (
|
||||
_get_safe_command,
|
||||
_set_client_data,
|
||||
_set_pipeline_data,
|
||||
)
|
||||
from sentry_sdk.tracing import Span
|
||||
from sentry_sdk.tracing_utils import has_span_streaming_enabled
|
||||
from sentry_sdk.utils import capture_internal_exceptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from redis.asyncio.client import Pipeline, StrictRedis
|
||||
from redis.asyncio.cluster import ClusterPipeline, RedisCluster
|
||||
|
||||
from sentry_sdk.traces import StreamedSpan
|
||||
|
||||
|
||||
def patch_redis_async_pipeline(
|
||||
pipeline_cls: "Union[type[Pipeline[Any]], type[ClusterPipeline[Any]]]",
|
||||
is_cluster: bool,
|
||||
get_command_args_fn: "Any",
|
||||
set_db_data_fn: "Callable[[Union[Span, StreamedSpan], Any], None]",
|
||||
) -> None:
|
||||
old_execute = pipeline_cls.execute
|
||||
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
|
||||
async def _sentry_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
|
||||
client = sentry_sdk.get_client()
|
||||
if client.get_integration(RedisIntegration) is None:
|
||||
return await old_execute(self, *args, **kwargs)
|
||||
|
||||
span_streaming = has_span_streaming_enabled(client.options)
|
||||
|
||||
span: "Union[Span, StreamedSpan]"
|
||||
if span_streaming:
|
||||
if sentry_sdk.traces.get_current_span() is None:
|
||||
return await old_execute(self, *args, **kwargs)
|
||||
span = sentry_sdk.traces.start_span(
|
||||
name="redis.pipeline.execute",
|
||||
attributes={
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
"sentry.op": OP.DB_REDIS,
|
||||
},
|
||||
)
|
||||
else:
|
||||
span = sentry_sdk.start_span(
|
||||
op=OP.DB_REDIS,
|
||||
name="redis.pipeline.execute",
|
||||
origin=SPAN_ORIGIN,
|
||||
)
|
||||
|
||||
with span:
|
||||
with capture_internal_exceptions():
|
||||
try:
|
||||
command_seq = self._execution_strategy._command_queue
|
||||
except AttributeError:
|
||||
if is_cluster:
|
||||
command_seq = self._command_stack
|
||||
else:
|
||||
command_seq = self.command_stack
|
||||
|
||||
set_db_data_fn(span, self)
|
||||
_set_pipeline_data(
|
||||
span,
|
||||
is_cluster,
|
||||
get_command_args_fn,
|
||||
False if is_cluster else self.is_transaction,
|
||||
command_seq,
|
||||
)
|
||||
|
||||
return await old_execute(self, *args, **kwargs)
|
||||
|
||||
pipeline_cls.execute = _sentry_execute # type: ignore
|
||||
|
||||
|
||||
def patch_redis_async_client(
|
||||
cls: "Union[type[StrictRedis[Any]], type[RedisCluster[Any]]]",
|
||||
is_cluster: bool,
|
||||
set_db_data_fn: "Callable[[Union[Span, StreamedSpan], Any], None]",
|
||||
) -> None:
|
||||
old_execute_command = cls.execute_command
|
||||
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
|
||||
async def _sentry_execute_command(
|
||||
self: "Any", name: str, *args: "Any", **kwargs: "Any"
|
||||
) -> "Any":
|
||||
client = sentry_sdk.get_client()
|
||||
integration = client.get_integration(RedisIntegration)
|
||||
if integration is None:
|
||||
return await old_execute_command(self, name, *args, **kwargs)
|
||||
|
||||
span_streaming = has_span_streaming_enabled(client.options)
|
||||
|
||||
if span_streaming and sentry_sdk.traces.get_current_span() is None:
|
||||
return await old_execute_command(self, name, *args, **kwargs)
|
||||
|
||||
cache_properties = _compile_cache_span_properties(
|
||||
name,
|
||||
args,
|
||||
kwargs,
|
||||
integration,
|
||||
)
|
||||
|
||||
additional_cache_span_attributes = {}
|
||||
with capture_internal_exceptions():
|
||||
additional_cache_span_attributes[SPANDATA.DB_QUERY_TEXT] = (
|
||||
_get_safe_command(name, args)
|
||||
)
|
||||
|
||||
cache_span: "Optional[Union[Span, StreamedSpan]]" = None
|
||||
if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
|
||||
if span_streaming:
|
||||
cache_span = sentry_sdk.traces.start_span(
|
||||
name=cache_properties["description"],
|
||||
attributes={
|
||||
"sentry.op": cache_properties["op"],
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
**additional_cache_span_attributes,
|
||||
},
|
||||
)
|
||||
else:
|
||||
cache_span = sentry_sdk.start_span(
|
||||
op=cache_properties["op"],
|
||||
name=cache_properties["description"],
|
||||
origin=SPAN_ORIGIN,
|
||||
)
|
||||
cache_span.__enter__()
|
||||
|
||||
db_properties = _compile_db_span_properties(integration, name, args)
|
||||
|
||||
additional_db_span_attributes = {}
|
||||
with capture_internal_exceptions():
|
||||
additional_db_span_attributes[SPANDATA.DB_QUERY_TEXT] = _get_safe_command(
|
||||
name, args
|
||||
)
|
||||
|
||||
db_span: "Union[Span, StreamedSpan]"
|
||||
if span_streaming:
|
||||
db_span = sentry_sdk.traces.start_span(
|
||||
name=db_properties["description"],
|
||||
attributes={
|
||||
"sentry.op": db_properties["op"],
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
**additional_db_span_attributes,
|
||||
},
|
||||
)
|
||||
else:
|
||||
db_span = sentry_sdk.start_span(
|
||||
op=db_properties["op"],
|
||||
name=db_properties["description"],
|
||||
origin=SPAN_ORIGIN,
|
||||
)
|
||||
db_span.__enter__()
|
||||
|
||||
set_db_data_fn(db_span, self)
|
||||
_set_client_data(db_span, is_cluster, name, *args)
|
||||
|
||||
value = await old_execute_command(self, name, *args, **kwargs)
|
||||
|
||||
db_span.__exit__(None, None, None)
|
||||
|
||||
if cache_span:
|
||||
_set_cache_data(cache_span, self, cache_properties, value)
|
||||
cache_span.__exit__(None, None, None)
|
||||
|
||||
return value
|
||||
|
||||
cls.execute_command = _sentry_execute_command # type: ignore
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import sentry_sdk
|
||||
from sentry_sdk.consts import OP, SPANDATA
|
||||
from sentry_sdk.integrations.redis.consts import SPAN_ORIGIN
|
||||
from sentry_sdk.integrations.redis.modules.caches import (
|
||||
_compile_cache_span_properties,
|
||||
_set_cache_data,
|
||||
)
|
||||
from sentry_sdk.integrations.redis.modules.queries import _compile_db_span_properties
|
||||
from sentry_sdk.integrations.redis.utils import (
|
||||
_get_safe_command,
|
||||
_set_client_data,
|
||||
_set_pipeline_data,
|
||||
)
|
||||
from sentry_sdk.tracing import Span
|
||||
from sentry_sdk.tracing_utils import has_span_streaming_enabled
|
||||
from sentry_sdk.utils import capture_internal_exceptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from sentry_sdk.traces import StreamedSpan
|
||||
|
||||
|
||||
def patch_redis_pipeline(
|
||||
pipeline_cls: "Any",
|
||||
is_cluster: bool,
|
||||
get_command_args_fn: "Any",
|
||||
set_db_data_fn: "Callable[[Union[Span, StreamedSpan], Any], None]",
|
||||
) -> None:
|
||||
old_execute = pipeline_cls.execute
|
||||
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
|
||||
def sentry_patched_execute(self: "Any", *args: "Any", **kwargs: "Any") -> "Any":
|
||||
client = sentry_sdk.get_client()
|
||||
if client.get_integration(RedisIntegration) is None:
|
||||
return old_execute(self, *args, **kwargs)
|
||||
|
||||
span_streaming = has_span_streaming_enabled(client.options)
|
||||
|
||||
span: "Union[Span, StreamedSpan]"
|
||||
if span_streaming:
|
||||
if sentry_sdk.traces.get_current_span() is None:
|
||||
return old_execute(self, *args, **kwargs)
|
||||
span = sentry_sdk.traces.start_span(
|
||||
name="redis.pipeline.execute",
|
||||
attributes={
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
"sentry.op": OP.DB_REDIS,
|
||||
},
|
||||
)
|
||||
else:
|
||||
span = sentry_sdk.start_span(
|
||||
op=OP.DB_REDIS,
|
||||
name="redis.pipeline.execute",
|
||||
origin=SPAN_ORIGIN,
|
||||
)
|
||||
|
||||
with span:
|
||||
with capture_internal_exceptions():
|
||||
command_seq = None
|
||||
try:
|
||||
command_seq = self._execution_strategy.command_queue
|
||||
except AttributeError:
|
||||
command_seq = self.command_stack
|
||||
|
||||
set_db_data_fn(span, self)
|
||||
_set_pipeline_data(
|
||||
span,
|
||||
is_cluster,
|
||||
get_command_args_fn,
|
||||
False if is_cluster else self.transaction,
|
||||
command_seq,
|
||||
)
|
||||
|
||||
return old_execute(self, *args, **kwargs)
|
||||
|
||||
pipeline_cls.execute = sentry_patched_execute
|
||||
|
||||
|
||||
def patch_redis_client(
|
||||
cls: "Any",
|
||||
is_cluster: bool,
|
||||
set_db_data_fn: "Callable[[Union[Span, StreamedSpan], Any], None]",
|
||||
) -> None:
|
||||
"""
|
||||
This function can be used to instrument custom redis client classes or
|
||||
subclasses.
|
||||
"""
|
||||
old_execute_command = cls.execute_command
|
||||
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
|
||||
def sentry_patched_execute_command(
|
||||
self: "Any", name: str, *args: "Any", **kwargs: "Any"
|
||||
) -> "Any":
|
||||
client = sentry_sdk.get_client()
|
||||
integration = client.get_integration(RedisIntegration)
|
||||
if integration is None:
|
||||
return old_execute_command(self, name, *args, **kwargs)
|
||||
|
||||
span_streaming = has_span_streaming_enabled(client.options)
|
||||
|
||||
if span_streaming and sentry_sdk.traces.get_current_span() is None:
|
||||
return old_execute_command(self, name, *args, **kwargs)
|
||||
|
||||
cache_properties = _compile_cache_span_properties(
|
||||
name,
|
||||
args,
|
||||
kwargs,
|
||||
integration,
|
||||
)
|
||||
|
||||
additional_cache_span_attributes = {}
|
||||
with capture_internal_exceptions():
|
||||
additional_cache_span_attributes[SPANDATA.DB_QUERY_TEXT] = (
|
||||
_get_safe_command(name, args)
|
||||
)
|
||||
|
||||
cache_span: "Optional[Union[Span, StreamedSpan]]" = None
|
||||
if cache_properties["is_cache_key"] and cache_properties["op"] is not None:
|
||||
if span_streaming:
|
||||
cache_span = sentry_sdk.traces.start_span(
|
||||
name=cache_properties["description"],
|
||||
attributes={
|
||||
"sentry.op": cache_properties["op"],
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
**additional_cache_span_attributes,
|
||||
},
|
||||
)
|
||||
else:
|
||||
cache_span = sentry_sdk.start_span(
|
||||
op=cache_properties["op"],
|
||||
name=cache_properties["description"],
|
||||
origin=SPAN_ORIGIN,
|
||||
)
|
||||
cache_span.__enter__()
|
||||
|
||||
db_properties = _compile_db_span_properties(integration, name, args)
|
||||
|
||||
additional_db_span_attributes = {}
|
||||
with capture_internal_exceptions():
|
||||
additional_db_span_attributes[SPANDATA.DB_QUERY_TEXT] = _get_safe_command(
|
||||
name, args
|
||||
)
|
||||
|
||||
db_span: "Union[Span, StreamedSpan]"
|
||||
if span_streaming:
|
||||
db_span = sentry_sdk.traces.start_span(
|
||||
name=db_properties["description"],
|
||||
attributes={
|
||||
"sentry.op": db_properties["op"],
|
||||
"sentry.origin": SPAN_ORIGIN,
|
||||
**additional_db_span_attributes,
|
||||
},
|
||||
)
|
||||
else:
|
||||
db_span = sentry_sdk.start_span(
|
||||
op=db_properties["op"],
|
||||
name=db_properties["description"],
|
||||
origin=SPAN_ORIGIN,
|
||||
)
|
||||
db_span.__enter__()
|
||||
|
||||
set_db_data_fn(db_span, self)
|
||||
_set_client_data(db_span, is_cluster, name, *args)
|
||||
|
||||
value = old_execute_command(self, name, *args, **kwargs)
|
||||
|
||||
db_span.__exit__(None, None, None)
|
||||
|
||||
if cache_span:
|
||||
_set_cache_data(cache_span, self, cache_properties, value)
|
||||
cache_span.__exit__(None, None, None)
|
||||
|
||||
return value
|
||||
|
||||
cls.execute_command = sentry_patched_execute_command
|
||||
@@ -0,0 +1,19 @@
|
||||
SPAN_ORIGIN = "auto.db.redis"
|
||||
|
||||
_SINGLE_KEY_COMMANDS = frozenset(
|
||||
["decr", "decrby", "get", "incr", "incrby", "pttl", "set", "setex", "setnx", "ttl"],
|
||||
)
|
||||
_MULTI_KEY_COMMANDS = frozenset(
|
||||
[
|
||||
"del",
|
||||
"touch",
|
||||
"unlink",
|
||||
"mget",
|
||||
],
|
||||
)
|
||||
_COMMANDS_INCLUDING_SENSITIVE_DATA = [
|
||||
"auth",
|
||||
]
|
||||
_MAX_NUM_ARGS = 10 # Trim argument lists to this many values
|
||||
_MAX_NUM_COMMANDS = 10 # Trim command lists to this many values
|
||||
_DEFAULT_MAX_DATA_SIZE = None
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
"""
|
||||
Code used for the Caches module in Sentry
|
||||
"""
|
||||
|
||||
from sentry_sdk.consts import OP, SPANDATA
|
||||
from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string
|
||||
from sentry_sdk.traces import StreamedSpan
|
||||
from sentry_sdk.utils import capture_internal_exceptions
|
||||
|
||||
GET_COMMANDS = ("get", "mget")
|
||||
SET_COMMANDS = ("set", "setex")
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
from sentry_sdk.tracing import Span
|
||||
|
||||
|
||||
def _get_op(name: str) -> "Optional[str]":
|
||||
op = None
|
||||
if name.lower() in GET_COMMANDS:
|
||||
op = OP.CACHE_GET
|
||||
elif name.lower() in SET_COMMANDS:
|
||||
op = OP.CACHE_PUT
|
||||
|
||||
return op
|
||||
|
||||
|
||||
def _compile_cache_span_properties(
|
||||
redis_command: str,
|
||||
args: "tuple[Any, ...]",
|
||||
kwargs: "dict[str, Any]",
|
||||
integration: "RedisIntegration",
|
||||
) -> "dict[str, Any]":
|
||||
key = _get_safe_key(redis_command, args, kwargs)
|
||||
key_as_string = _key_as_string(key)
|
||||
keys_as_string = key_as_string.split(", ")
|
||||
|
||||
is_cache_key = False
|
||||
for prefix in integration.cache_prefixes:
|
||||
for kee in keys_as_string:
|
||||
if kee.startswith(prefix):
|
||||
is_cache_key = True
|
||||
break
|
||||
if is_cache_key:
|
||||
break
|
||||
|
||||
value = None
|
||||
if redis_command.lower() in SET_COMMANDS:
|
||||
value = args[-1]
|
||||
|
||||
properties = {
|
||||
"op": _get_op(redis_command),
|
||||
"description": _get_cache_span_description(
|
||||
redis_command, args, kwargs, integration
|
||||
),
|
||||
"key": key,
|
||||
"key_as_string": key_as_string,
|
||||
"redis_command": redis_command.lower(),
|
||||
"is_cache_key": is_cache_key,
|
||||
"value": value,
|
||||
}
|
||||
|
||||
return properties
|
||||
|
||||
|
||||
def _get_cache_span_description(
|
||||
redis_command: str,
|
||||
args: "tuple[Any, ...]",
|
||||
kwargs: "dict[str, Any]",
|
||||
integration: "RedisIntegration",
|
||||
) -> str:
|
||||
description = _key_as_string(_get_safe_key(redis_command, args, kwargs))
|
||||
|
||||
if integration.max_data_size and len(description) > integration.max_data_size:
|
||||
description = description[: integration.max_data_size - len("...")] + "..."
|
||||
|
||||
return description
|
||||
|
||||
|
||||
def _set_cache_data(
|
||||
span: "Union[Span, StreamedSpan]",
|
||||
redis_client: "Any",
|
||||
properties: "dict[str, Any]",
|
||||
return_value: "Optional[Any]",
|
||||
) -> None:
|
||||
if isinstance(span, StreamedSpan):
|
||||
set_on_span = span.set_attribute
|
||||
else:
|
||||
set_on_span = span.set_data
|
||||
|
||||
with capture_internal_exceptions():
|
||||
set_on_span(SPANDATA.CACHE_KEY, properties["key"])
|
||||
|
||||
if properties["redis_command"] in GET_COMMANDS:
|
||||
if return_value is not None:
|
||||
set_on_span(SPANDATA.CACHE_HIT, True)
|
||||
size = (
|
||||
len(str(return_value).encode("utf-8"))
|
||||
if not isinstance(return_value, bytes)
|
||||
else len(return_value)
|
||||
)
|
||||
set_on_span(SPANDATA.CACHE_ITEM_SIZE, size)
|
||||
else:
|
||||
set_on_span(SPANDATA.CACHE_HIT, False)
|
||||
|
||||
elif properties["redis_command"] in SET_COMMANDS:
|
||||
if properties["value"] is not None:
|
||||
size = (
|
||||
len(properties["value"].encode("utf-8"))
|
||||
if not isinstance(properties["value"], bytes)
|
||||
else len(properties["value"])
|
||||
)
|
||||
set_on_span(SPANDATA.CACHE_ITEM_SIZE, size)
|
||||
|
||||
try:
|
||||
connection_params = redis_client.connection_pool.connection_kwargs
|
||||
except AttributeError:
|
||||
# If it is a cluster, there is no connection_pool attribute so we
|
||||
# need to get the default node from the cluster instance
|
||||
default_node = redis_client.get_default_node()
|
||||
connection_params = {
|
||||
"host": default_node.host,
|
||||
"port": default_node.port,
|
||||
}
|
||||
|
||||
host = connection_params.get("host")
|
||||
if host is not None:
|
||||
set_on_span(SPANDATA.NETWORK_PEER_ADDRESS, host)
|
||||
|
||||
port = connection_params.get("port")
|
||||
if port is not None:
|
||||
set_on_span(SPANDATA.NETWORK_PEER_PORT, port)
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
"""
|
||||
Code used for the Queries module in Sentry
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sentry_sdk.consts import OP, SPANDATA
|
||||
from sentry_sdk.integrations.redis.utils import _get_safe_command
|
||||
from sentry_sdk.traces import StreamedSpan
|
||||
from sentry_sdk.utils import capture_internal_exceptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Union
|
||||
|
||||
from redis import Redis
|
||||
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
from sentry_sdk.tracing import Span
|
||||
|
||||
|
||||
def _compile_db_span_properties(
|
||||
integration: "RedisIntegration", redis_command: str, args: "tuple[Any, ...]"
|
||||
) -> "dict[str, Any]":
|
||||
description = _get_db_span_description(integration, redis_command, args)
|
||||
|
||||
properties = {
|
||||
"op": OP.DB_REDIS,
|
||||
"description": description,
|
||||
}
|
||||
|
||||
return properties
|
||||
|
||||
|
||||
def _get_db_span_description(
|
||||
integration: "RedisIntegration", command_name: str, args: "tuple[Any, ...]"
|
||||
) -> str:
|
||||
description = command_name
|
||||
|
||||
with capture_internal_exceptions():
|
||||
description = _get_safe_command(command_name, args)
|
||||
|
||||
if integration.max_data_size and len(description) > integration.max_data_size:
|
||||
description = description[: integration.max_data_size - len("...")] + "..."
|
||||
|
||||
return description
|
||||
|
||||
|
||||
def _set_db_data_on_span(
|
||||
span: "Union[Span, StreamedSpan]", connection_params: "dict[str, Any]"
|
||||
) -> None:
|
||||
db = connection_params.get("db")
|
||||
host = connection_params.get("host")
|
||||
port = connection_params.get("port")
|
||||
|
||||
if isinstance(span, StreamedSpan):
|
||||
span.set_attribute(SPANDATA.DB_SYSTEM_NAME, "redis")
|
||||
span.set_attribute(SPANDATA.DB_DRIVER_NAME, "redis-py")
|
||||
|
||||
if db is not None:
|
||||
span.set_attribute(SPANDATA.DB_NAMESPACE, str(db))
|
||||
|
||||
if host is not None:
|
||||
span.set_attribute(SPANDATA.SERVER_ADDRESS, host)
|
||||
|
||||
if port is not None:
|
||||
span.set_attribute(SPANDATA.SERVER_PORT, port)
|
||||
|
||||
else:
|
||||
span.set_data(SPANDATA.DB_SYSTEM, "redis")
|
||||
span.set_data(SPANDATA.DB_DRIVER_NAME, "redis-py")
|
||||
|
||||
if db is not None:
|
||||
span.set_data(SPANDATA.DB_NAME, str(db))
|
||||
|
||||
if host is not None:
|
||||
span.set_data(SPANDATA.SERVER_ADDRESS, host)
|
||||
|
||||
if port is not None:
|
||||
span.set_data(SPANDATA.SERVER_PORT, port)
|
||||
|
||||
|
||||
def _set_db_data(
|
||||
span: "Union[Span, StreamedSpan]", redis_instance: "Redis[Any]"
|
||||
) -> None:
|
||||
try:
|
||||
_set_db_data_on_span(span, redis_instance.connection_pool.connection_kwargs)
|
||||
except AttributeError:
|
||||
pass # connections_kwargs may be missing in some cases
|
||||
@@ -0,0 +1,31 @@
|
||||
"""
|
||||
Instrumentation for Redis Blaster (rb)
|
||||
|
||||
https://github.com/getsentry/rb
|
||||
"""
|
||||
|
||||
from sentry_sdk.integrations.redis._sync_common import patch_redis_client
|
||||
from sentry_sdk.integrations.redis.modules.queries import _set_db_data
|
||||
|
||||
|
||||
def _patch_rb() -> None:
|
||||
try:
|
||||
import rb.clients # type: ignore
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
patch_redis_client(
|
||||
rb.clients.FanoutClient,
|
||||
is_cluster=False,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
patch_redis_client(
|
||||
rb.clients.MappingClient,
|
||||
is_cluster=False,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
patch_redis_client(
|
||||
rb.clients.RoutingClient,
|
||||
is_cluster=False,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
Instrumentation for Redis
|
||||
|
||||
https://github.com/redis/redis-py
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sentry_sdk.integrations.redis._sync_common import (
|
||||
patch_redis_client,
|
||||
patch_redis_pipeline,
|
||||
)
|
||||
from sentry_sdk.integrations.redis.modules.queries import _set_db_data
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
def _get_redis_command_args(command: "Any") -> "Sequence[Any]":
|
||||
return command[0]
|
||||
|
||||
|
||||
def _patch_redis(StrictRedis: "Any", client: "Any") -> None: # noqa: N803
|
||||
patch_redis_client(
|
||||
StrictRedis,
|
||||
is_cluster=False,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
patch_redis_pipeline(
|
||||
client.Pipeline,
|
||||
is_cluster=False,
|
||||
get_command_args_fn=_get_redis_command_args,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
try:
|
||||
strict_pipeline = client.StrictPipeline
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
patch_redis_pipeline(
|
||||
strict_pipeline,
|
||||
is_cluster=False,
|
||||
get_command_args_fn=_get_redis_command_args,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
|
||||
try:
|
||||
import redis.asyncio
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
from sentry_sdk.integrations.redis._async_common import (
|
||||
patch_redis_async_client,
|
||||
patch_redis_async_pipeline,
|
||||
)
|
||||
|
||||
patch_redis_async_client(
|
||||
redis.asyncio.client.StrictRedis,
|
||||
is_cluster=False,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
patch_redis_async_pipeline(
|
||||
redis.asyncio.client.Pipeline,
|
||||
False,
|
||||
_get_redis_command_args,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
Instrumentation for RedisCluster
|
||||
This is part of the main redis-py client.
|
||||
|
||||
https://github.com/redis/redis-py/blob/master/redis/cluster.py
|
||||
"""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sentry_sdk.integrations.redis._sync_common import (
|
||||
patch_redis_client,
|
||||
patch_redis_pipeline,
|
||||
)
|
||||
from sentry_sdk.integrations.redis.modules.queries import _set_db_data_on_span
|
||||
from sentry_sdk.integrations.redis.utils import _parse_rediscluster_command
|
||||
from sentry_sdk.utils import capture_internal_exceptions
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Union
|
||||
|
||||
from redis import RedisCluster
|
||||
from redis.asyncio.cluster import (
|
||||
ClusterPipeline as AsyncClusterPipeline,
|
||||
)
|
||||
from redis.asyncio.cluster import (
|
||||
RedisCluster as AsyncRedisCluster,
|
||||
)
|
||||
|
||||
from sentry_sdk.traces import StreamedSpan
|
||||
from sentry_sdk.tracing import Span
|
||||
|
||||
|
||||
def _set_async_cluster_db_data(
|
||||
span: "Union[Span, StreamedSpan]",
|
||||
async_redis_cluster_instance: "AsyncRedisCluster[Any]",
|
||||
) -> None:
|
||||
default_node = async_redis_cluster_instance.get_default_node()
|
||||
if default_node is not None and default_node.connection_kwargs is not None:
|
||||
_set_db_data_on_span(span, default_node.connection_kwargs)
|
||||
|
||||
|
||||
def _set_async_cluster_pipeline_db_data(
|
||||
span: "Union[Span, StreamedSpan]",
|
||||
async_redis_cluster_pipeline_instance: "AsyncClusterPipeline[Any]",
|
||||
) -> None:
|
||||
with capture_internal_exceptions():
|
||||
client = getattr(async_redis_cluster_pipeline_instance, "cluster_client", None)
|
||||
if client is None:
|
||||
# In older redis-py versions, the AsyncClusterPipeline had a `_client`
|
||||
# attr but it is private so potentially problematic and mypy does not
|
||||
# recognize it - see
|
||||
# https://github.com/redis/redis-py/blame/v5.0.0/redis/asyncio/cluster.py#L1386
|
||||
client = (
|
||||
async_redis_cluster_pipeline_instance._client # type: ignore[attr-defined]
|
||||
)
|
||||
|
||||
_set_async_cluster_db_data(
|
||||
span,
|
||||
client,
|
||||
)
|
||||
|
||||
|
||||
def _set_cluster_db_data(
|
||||
span: "Union[Span, StreamedSpan]", redis_cluster_instance: "RedisCluster[Any]"
|
||||
) -> None:
|
||||
default_node = redis_cluster_instance.get_default_node()
|
||||
|
||||
if default_node is not None:
|
||||
connection_params = {
|
||||
"host": default_node.host,
|
||||
"port": default_node.port,
|
||||
}
|
||||
_set_db_data_on_span(span, connection_params)
|
||||
|
||||
|
||||
def _patch_redis_cluster() -> None:
|
||||
"""Patches the cluster module on redis SDK (as opposed to rediscluster library)"""
|
||||
try:
|
||||
from redis import RedisCluster, cluster
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
patch_redis_client(
|
||||
RedisCluster,
|
||||
is_cluster=True,
|
||||
set_db_data_fn=_set_cluster_db_data,
|
||||
)
|
||||
patch_redis_pipeline(
|
||||
cluster.ClusterPipeline,
|
||||
is_cluster=True,
|
||||
get_command_args_fn=_parse_rediscluster_command,
|
||||
set_db_data_fn=_set_cluster_db_data,
|
||||
)
|
||||
|
||||
try:
|
||||
from redis.asyncio import cluster as async_cluster
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
from sentry_sdk.integrations.redis._async_common import (
|
||||
patch_redis_async_client,
|
||||
patch_redis_async_pipeline,
|
||||
)
|
||||
|
||||
patch_redis_async_client(
|
||||
async_cluster.RedisCluster,
|
||||
is_cluster=True,
|
||||
set_db_data_fn=_set_async_cluster_db_data,
|
||||
)
|
||||
patch_redis_async_pipeline(
|
||||
async_cluster.ClusterPipeline,
|
||||
is_cluster=True,
|
||||
get_command_args_fn=_parse_rediscluster_command,
|
||||
set_db_data_fn=_set_async_cluster_pipeline_db_data,
|
||||
)
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
"""
|
||||
Instrumentation for redis-py-cluster
|
||||
The project redis-py-cluster is EOL and was integrated into redis-py starting from version 4.1.0 (Dec 26, 2021).
|
||||
|
||||
https://github.com/grokzen/redis-py-cluster
|
||||
"""
|
||||
|
||||
from sentry_sdk.integrations.redis._sync_common import (
|
||||
patch_redis_client,
|
||||
patch_redis_pipeline,
|
||||
)
|
||||
from sentry_sdk.integrations.redis.modules.queries import _set_db_data
|
||||
from sentry_sdk.integrations.redis.utils import _parse_rediscluster_command
|
||||
|
||||
|
||||
def _patch_rediscluster() -> None:
|
||||
try:
|
||||
import rediscluster # type: ignore
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
patch_redis_client(
|
||||
rediscluster.RedisCluster,
|
||||
is_cluster=True,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
|
||||
# up to v1.3.6, __version__ attribute is a tuple
|
||||
# from v2.0.0, __version__ is a string and VERSION a tuple
|
||||
version = getattr(rediscluster, "VERSION", rediscluster.__version__)
|
||||
|
||||
# StrictRedisCluster was introduced in v0.2.0 and removed in v2.0.0
|
||||
# https://github.com/Grokzen/redis-py-cluster/blob/master/docs/release-notes.rst
|
||||
if (0, 2, 0) < version < (2, 0, 0):
|
||||
pipeline_cls = rediscluster.pipeline.StrictClusterPipeline
|
||||
patch_redis_client(
|
||||
rediscluster.StrictRedisCluster,
|
||||
is_cluster=True,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
else:
|
||||
pipeline_cls = rediscluster.pipeline.ClusterPipeline
|
||||
|
||||
patch_redis_pipeline(
|
||||
pipeline_cls,
|
||||
is_cluster=True,
|
||||
get_command_args_fn=_parse_rediscluster_command,
|
||||
set_db_data_fn=_set_db_data,
|
||||
)
|
||||
@@ -0,0 +1,159 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sentry_sdk.consts import SPANDATA
|
||||
from sentry_sdk.integrations.redis.consts import (
|
||||
_COMMANDS_INCLUDING_SENSITIVE_DATA,
|
||||
_MAX_NUM_ARGS,
|
||||
_MAX_NUM_COMMANDS,
|
||||
_MULTI_KEY_COMMANDS,
|
||||
_SINGLE_KEY_COMMANDS,
|
||||
)
|
||||
from sentry_sdk.scope import should_send_default_pii
|
||||
from sentry_sdk.traces import StreamedSpan
|
||||
from sentry_sdk.tracing import Span
|
||||
from sentry_sdk.utils import SENSITIVE_DATA_SUBSTITUTE
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Optional, Sequence, Union
|
||||
|
||||
|
||||
def _get_safe_command(name: str, args: "Sequence[Any]") -> str:
|
||||
command_parts = [name]
|
||||
|
||||
name_low = name.lower()
|
||||
send_default_pii = should_send_default_pii()
|
||||
|
||||
for i, arg in enumerate(args):
|
||||
if i > _MAX_NUM_ARGS:
|
||||
break
|
||||
|
||||
if name_low in _COMMANDS_INCLUDING_SENSITIVE_DATA:
|
||||
command_parts.append(SENSITIVE_DATA_SUBSTITUTE)
|
||||
continue
|
||||
|
||||
arg_is_the_key = i == 0
|
||||
if arg_is_the_key:
|
||||
command_parts.append(repr(arg))
|
||||
else:
|
||||
if send_default_pii:
|
||||
command_parts.append(repr(arg))
|
||||
else:
|
||||
command_parts.append(SENSITIVE_DATA_SUBSTITUTE)
|
||||
|
||||
command = " ".join(command_parts)
|
||||
return command
|
||||
|
||||
|
||||
def _safe_decode(key: "Any") -> str:
|
||||
if isinstance(key, bytes):
|
||||
try:
|
||||
return key.decode()
|
||||
except UnicodeDecodeError:
|
||||
return ""
|
||||
|
||||
return str(key)
|
||||
|
||||
|
||||
def _key_as_string(key: "Any") -> str:
|
||||
if isinstance(key, (dict, list, tuple)):
|
||||
key = ", ".join(_safe_decode(x) for x in key)
|
||||
elif isinstance(key, bytes):
|
||||
key = _safe_decode(key)
|
||||
elif key is None:
|
||||
key = ""
|
||||
else:
|
||||
key = str(key)
|
||||
|
||||
return key
|
||||
|
||||
|
||||
def _get_safe_key(
|
||||
method_name: str,
|
||||
args: "Optional[tuple[Any, ...]]",
|
||||
kwargs: "Optional[dict[str, Any]]",
|
||||
) -> "Optional[tuple[str, ...]]":
|
||||
"""
|
||||
Gets the key (or keys) from the given method_name.
|
||||
The method_name could be a redis command or a django caching command
|
||||
"""
|
||||
key = None
|
||||
|
||||
if args is not None and method_name.lower() in _MULTI_KEY_COMMANDS:
|
||||
# for example redis "mget"
|
||||
key = tuple(args)
|
||||
|
||||
elif args is not None and len(args) >= 1:
|
||||
# for example django "set_many/get_many" or redis "get"
|
||||
if isinstance(args[0], (dict, list, tuple)):
|
||||
key = tuple(args[0])
|
||||
else:
|
||||
key = (args[0],)
|
||||
|
||||
elif kwargs is not None and "key" in kwargs:
|
||||
# this is a legacy case for older versions of Django
|
||||
if isinstance(kwargs["key"], (list, tuple)):
|
||||
if len(kwargs["key"]) > 0:
|
||||
key = tuple(kwargs["key"])
|
||||
else:
|
||||
if kwargs["key"] is not None:
|
||||
key = (kwargs["key"],)
|
||||
|
||||
return key
|
||||
|
||||
|
||||
def _parse_rediscluster_command(command: "Any") -> "Sequence[Any]":
|
||||
return command.args
|
||||
|
||||
|
||||
def _set_pipeline_data(
|
||||
span: "Union[Span, StreamedSpan]",
|
||||
is_cluster: bool,
|
||||
get_command_args_fn: "Any",
|
||||
is_transaction: bool,
|
||||
commands_seq: "Sequence[Any]",
|
||||
) -> None:
|
||||
# TODO: Remove this whole function when removing transaction based tracing
|
||||
if isinstance(span, StreamedSpan):
|
||||
return
|
||||
|
||||
span.set_tag("redis.is_cluster", is_cluster)
|
||||
span.set_tag("redis.transaction", is_transaction)
|
||||
|
||||
commands = []
|
||||
for i, arg in enumerate(commands_seq):
|
||||
if i >= _MAX_NUM_COMMANDS:
|
||||
break
|
||||
|
||||
command = get_command_args_fn(arg)
|
||||
commands.append(_get_safe_command(command[0], command[1:]))
|
||||
|
||||
span.set_data(
|
||||
"redis.commands",
|
||||
{
|
||||
"count": len(commands_seq),
|
||||
"first_ten": commands,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _set_client_data(
|
||||
span: "Union[Span, StreamedSpan]", is_cluster: bool, name: str, *args: "Any"
|
||||
) -> None:
|
||||
if isinstance(span, StreamedSpan):
|
||||
if name:
|
||||
span.set_attribute(SPANDATA.DB_OPERATION_NAME, name)
|
||||
else:
|
||||
span.set_tag("redis.is_cluster", is_cluster)
|
||||
if name:
|
||||
span.set_tag("redis.command", name)
|
||||
span.set_tag(SPANDATA.DB_OPERATION, name)
|
||||
|
||||
if name and args:
|
||||
name_low = name.lower()
|
||||
if (name_low in _SINGLE_KEY_COMMANDS) or (
|
||||
name_low in _MULTI_KEY_COMMANDS and len(args) == 1
|
||||
):
|
||||
if isinstance(span, StreamedSpan):
|
||||
span.set_attribute("db.redis.key", args[0])
|
||||
else:
|
||||
span.set_tag("redis.key", args[0])
|
||||
Reference in New Issue
Block a user