148 lines
3.9 KiB
Python
148 lines
3.9 KiB
Python
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.utils import SENSITIVE_DATA_SUBSTITUTE
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from typing import Any, Optional, Sequence
|
|
from sentry_sdk.tracing import Span
|
|
|
|
|
|
def _get_safe_command(name, args):
|
|
# type: (str, 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):
|
|
# type: (Any) -> str
|
|
if isinstance(key, bytes):
|
|
try:
|
|
return key.decode()
|
|
except UnicodeDecodeError:
|
|
return ""
|
|
|
|
return str(key)
|
|
|
|
|
|
def _key_as_string(key):
|
|
# type: (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, args, kwargs):
|
|
# type: (str, Optional[tuple[Any, ...]], 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):
|
|
# type: (Any) -> Sequence[Any]
|
|
return command.args
|
|
|
|
|
|
def _set_pipeline_data(
|
|
span,
|
|
is_cluster,
|
|
get_command_args_fn,
|
|
is_transaction,
|
|
commands_seq,
|
|
):
|
|
# type: (Span, bool, Any, bool, Sequence[Any]) -> None
|
|
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, is_cluster, name, *args):
|
|
# type: (Span, bool, str, *Any) -> None
|
|
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
|
|
):
|
|
span.set_tag("redis.key", args[0])
|