mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-01 09:13:23 +08:00
112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
from functools import wraps
|
|
|
|
from django.conf import settings as django_settings
|
|
|
|
from ...ext import SpanTypes
|
|
from ...internal.logger import get_logger
|
|
from .conf import settings, import_from_string
|
|
from .utils import quantize_key_values, _resource_from_cache_prefix
|
|
|
|
|
|
log = get_logger(__name__)
|
|
|
|
# code instrumentation
|
|
DATADOG_NAMESPACE = '__datadog_original_{method}'
|
|
TRACED_METHODS = [
|
|
'get',
|
|
'set',
|
|
'add',
|
|
'delete',
|
|
'incr',
|
|
'decr',
|
|
'get_many',
|
|
'set_many',
|
|
'delete_many',
|
|
]
|
|
|
|
# standard tags
|
|
CACHE_BACKEND = 'django.cache.backend'
|
|
CACHE_COMMAND_KEY = 'django.cache.key'
|
|
|
|
|
|
def patch_cache(tracer):
|
|
"""
|
|
Function that patches the inner cache system. Because the cache backend
|
|
can have different implementations and connectors, this function must
|
|
handle all possible interactions with the Django cache. What follows
|
|
is currently traced:
|
|
|
|
* in-memory cache
|
|
* the cache client wrapper that could use any of the common
|
|
Django supported cache servers (Redis, Memcached, Database, Custom)
|
|
"""
|
|
# discover used cache backends
|
|
cache_backends = set([cache['BACKEND'] for cache in django_settings.CACHES.values()])
|
|
|
|
def _trace_operation(fn, method_name):
|
|
"""
|
|
Return a wrapped function that traces a cache operation
|
|
"""
|
|
cache_service_name = settings.DEFAULT_CACHE_SERVICE \
|
|
if settings.DEFAULT_CACHE_SERVICE else settings.DEFAULT_SERVICE
|
|
|
|
@wraps(fn)
|
|
def wrapped(self, *args, **kwargs):
|
|
# get the original function method
|
|
method = getattr(self, DATADOG_NAMESPACE.format(method=method_name))
|
|
with tracer.trace('django.cache', span_type=SpanTypes.CACHE, service=cache_service_name) as span:
|
|
# update the resource name and tag the cache backend
|
|
span.resource = _resource_from_cache_prefix(method_name, self)
|
|
cache_backend = '{}.{}'.format(self.__module__, self.__class__.__name__)
|
|
span.set_tag(CACHE_BACKEND, cache_backend)
|
|
|
|
if args:
|
|
keys = quantize_key_values(args[0])
|
|
span.set_tag(CACHE_COMMAND_KEY, keys)
|
|
|
|
return method(*args, **kwargs)
|
|
return wrapped
|
|
|
|
def _wrap_method(cls, method_name):
|
|
"""
|
|
For the given class, wraps the method name with a traced operation
|
|
so that the original method is executed, while the span is properly
|
|
created
|
|
"""
|
|
# check if the backend owns the given bounded method
|
|
if not hasattr(cls, method_name):
|
|
return
|
|
|
|
# prevent patching each backend's method more than once
|
|
if hasattr(cls, DATADOG_NAMESPACE.format(method=method_name)):
|
|
log.debug('%s already traced', method_name)
|
|
else:
|
|
method = getattr(cls, method_name)
|
|
setattr(cls, DATADOG_NAMESPACE.format(method=method_name), method)
|
|
setattr(cls, method_name, _trace_operation(method, method_name))
|
|
|
|
# trace all backends
|
|
for cache_module in cache_backends:
|
|
cache = import_from_string(cache_module, cache_module)
|
|
|
|
for method in TRACED_METHODS:
|
|
_wrap_method(cache, method)
|
|
|
|
|
|
def unpatch_method(cls, method_name):
|
|
method = getattr(cls, DATADOG_NAMESPACE.format(method=method_name), None)
|
|
if method is None:
|
|
log.debug('nothing to do, the class is not patched')
|
|
return
|
|
setattr(cls, method_name, method)
|
|
delattr(cls, DATADOG_NAMESPACE.format(method=method_name))
|
|
|
|
|
|
def unpatch_cache():
|
|
cache_backends = set([cache['BACKEND'] for cache in django_settings.CACHES.values()])
|
|
for cache_module in cache_backends:
|
|
cache = import_from_string(cache_module, cache_module)
|
|
|
|
for method in TRACED_METHODS:
|
|
unpatch_method(cache, method)
|