관리-도구
편집 파일: caching.py
import functools from typing import TYPE_CHECKING from sentry_sdk.integrations.redis.utils import _get_safe_key, _key_as_string from urllib3.util import parse_url as urlparse from django import VERSION as DJANGO_VERSION from django.core.cache import CacheHandler import sentry_sdk from sentry_sdk.consts import OP, SPANDATA from sentry_sdk.utils import ( capture_internal_exceptions, ensure_integration_enabled, ) if TYPE_CHECKING: from typing import Any from typing import Callable from typing import Optional METHODS_TO_INSTRUMENT = [ "set", "set_many", "get", "get_many", ] def _get_span_description(method_name, args, kwargs): # type: (str, tuple[Any], dict[str, Any]) -> str return _key_as_string(_get_safe_key(method_name, args, kwargs)) def _patch_cache_method(cache, method_name, address, port): # type: (CacheHandler, str, Optional[str], Optional[int]) -> None from sentry_sdk.integrations.django import DjangoIntegration original_method = getattr(cache, method_name) @ensure_integration_enabled(DjangoIntegration, original_method) def _instrument_call( cache, method_name, original_method, args, kwargs, address, port ): # type: (CacheHandler, str, Callable[..., Any], tuple[Any, ...], dict[str, Any], Optional[str], Optional[int]) -> Any is_set_operation = method_name.startswith("set") is_get_operation = not is_set_operation op = OP.CACHE_PUT if is_set_operation else OP.CACHE_GET description = _get_span_description(method_name, args, kwargs) with sentry_sdk.start_span( op=op, name=description, origin=DjangoIntegration.origin, ) as span: value = original_method(*args, **kwargs) with capture_internal_exceptions(): if address is not None: span.set_data(SPANDATA.NETWORK_PEER_ADDRESS, address) if port is not None: span.set_data(SPANDATA.NETWORK_PEER_PORT, port) key = _get_safe_key(method_name, args, kwargs) if key is not None: span.set_data(SPANDATA.CACHE_KEY, key) item_size = None if is_get_operation: if value: item_size = len(str(value)) span.set_data(SPANDATA.CACHE_HIT, True) else: span.set_data(SPANDATA.CACHE_HIT, False) else: try: # 'set' command item_size = len(str(args[1])) except IndexError: # 'set_many' command item_size = len(str(args[0])) if item_size is not None: span.set_data(SPANDATA.CACHE_ITEM_SIZE, item_size) return value @functools.wraps(original_method) def sentry_method(*args, **kwargs): # type: (*Any, **Any) -> Any return _instrument_call( cache, method_name, original_method, args, kwargs, address, port ) setattr(cache, method_name, sentry_method) def _patch_cache(cache, address=None, port=None): # type: (CacheHandler, Optional[str], Optional[int]) -> None if not hasattr(cache, "_sentry_patched"): for method_name in METHODS_TO_INSTRUMENT: _patch_cache_method(cache, method_name, address, port) cache._sentry_patched = True def _get_address_port(settings): # type: (dict[str, Any]) -> tuple[Optional[str], Optional[int]] location = settings.get("LOCATION") # TODO: location can also be an array of locations # see: https://docs.djangoproject.com/en/5.0/topics/cache/#redis # GitHub issue: https://github.com/getsentry/sentry-python/issues/3062 if not isinstance(location, str): return None, None if "://" in location: parsed_url = urlparse(location) # remove the username and password from URL to not leak sensitive data. address = "{}://{}{}".format( parsed_url.scheme or "", parsed_url.hostname or "", parsed_url.path or "", ) port = parsed_url.port else: address = location port = None return address, int(port) if port is not None else None def patch_caching(): # type: () -> None from sentry_sdk.integrations.django import DjangoIntegration if not hasattr(CacheHandler, "_sentry_patched"): if DJANGO_VERSION < (3, 2): original_get_item = CacheHandler.__getitem__ @functools.wraps(original_get_item) def sentry_get_item(self, alias): # type: (CacheHandler, str) -> Any cache = original_get_item(self, alias) integration = sentry_sdk.get_client().get_integration(DjangoIntegration) if integration is not None and integration.cache_spans: from django.conf import settings address, port = _get_address_port( settings.CACHES[alias or "default"] ) _patch_cache(cache, address, port) return cache CacheHandler.__getitem__ = sentry_get_item CacheHandler._sentry_patched = True else: original_create_connection = CacheHandler.create_connection @functools.wraps(original_create_connection) def sentry_create_connection(self, alias): # type: (CacheHandler, str) -> Any cache = original_create_connection(self, alias) integration = sentry_sdk.get_client().get_integration(DjangoIntegration) if integration is not None and integration.cache_spans: address, port = _get_address_port(self.settings[alias or "default"]) _patch_cache(cache, address, port) return cache CacheHandler.create_connection = sentry_create_connection CacheHandler._sentry_patched = True