mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-07-29 21:23:55 +08:00
185 lines
6.6 KiB
Python
185 lines
6.6 KiB
Python
import ddtrace
|
|
|
|
from ddtrace.vendor import debtcollector
|
|
|
|
from .internal.logger import get_logger
|
|
from .vendor import wrapt
|
|
|
|
|
|
log = get_logger(__name__)
|
|
|
|
|
|
# To set attributes on wrapt proxy objects use this prefix:
|
|
# http://wrapt.readthedocs.io/en/latest/wrappers.html
|
|
_DD_PIN_NAME = '_datadog_pin'
|
|
_DD_PIN_PROXY_NAME = '_self_' + _DD_PIN_NAME
|
|
|
|
|
|
class Pin(object):
|
|
"""Pin (a.k.a Patch INfo) is a small class which is used to
|
|
set tracing metadata on a particular traced connection.
|
|
This is useful if you wanted to, say, trace two different
|
|
database clusters.
|
|
|
|
>>> conn = sqlite.connect('/tmp/user.db')
|
|
>>> # Override a pin for a specific connection
|
|
>>> pin = Pin.override(conn, service='user-db')
|
|
>>> conn = sqlite.connect('/tmp/image.db')
|
|
"""
|
|
__slots__ = ['app', 'tags', 'tracer', '_target', '_config', '_initialized']
|
|
|
|
@debtcollector.removals.removed_kwarg("app_type")
|
|
def __init__(self, service, app=None, app_type=None, tags=None, tracer=None, _config=None):
|
|
tracer = tracer or ddtrace.tracer
|
|
self.app = app
|
|
self.tags = tags
|
|
self.tracer = tracer
|
|
self._target = None
|
|
# keep the configuration attribute internal because the
|
|
# public API to access it is not the Pin class
|
|
self._config = _config or {}
|
|
# [Backward compatibility]: service argument updates the `Pin` config
|
|
self._config['service_name'] = service
|
|
self._initialized = True
|
|
|
|
@property
|
|
def service(self):
|
|
"""Backward compatibility: accessing to `pin.service` returns the underlying
|
|
configuration value.
|
|
"""
|
|
return self._config['service_name']
|
|
|
|
def __setattr__(self, name, value):
|
|
if getattr(self, '_initialized', False) and name != '_target':
|
|
raise AttributeError("can't mutate a pin, use override() or clone() instead")
|
|
super(Pin, self).__setattr__(name, value)
|
|
|
|
def __repr__(self):
|
|
return 'Pin(service=%s, app=%s, tags=%s, tracer=%s)' % (
|
|
self.service, self.app, self.tags, self.tracer)
|
|
|
|
@staticmethod
|
|
def _find(*objs):
|
|
"""
|
|
Return the first :class:`ddtrace.pin.Pin` found on any of the provided objects or `None` if none were found
|
|
|
|
|
|
>>> pin = Pin._find(wrapper, instance, conn, app)
|
|
|
|
:param objs: The objects to search for a :class:`ddtrace.pin.Pin` on
|
|
:type objs: List of objects
|
|
:rtype: :class:`ddtrace.pin.Pin`, None
|
|
:returns: The first found :class:`ddtrace.pin.Pin` or `None` is none was found
|
|
"""
|
|
for obj in objs:
|
|
pin = Pin.get_from(obj)
|
|
if pin:
|
|
return pin
|
|
return None
|
|
|
|
@staticmethod
|
|
def get_from(obj):
|
|
"""Return the pin associated with the given object. If a pin is attached to
|
|
`obj` but the instance is not the owner of the pin, a new pin is cloned and
|
|
attached. This ensures that a pin inherited from a class is a copy for the new
|
|
instance, avoiding that a specific instance overrides other pins values.
|
|
|
|
>>> pin = Pin.get_from(conn)
|
|
|
|
:param obj: The object to look for a :class:`ddtrace.pin.Pin` on
|
|
:type obj: object
|
|
:rtype: :class:`ddtrace.pin.Pin`, None
|
|
:returns: :class:`ddtrace.pin.Pin` associated with the object, or None if none was found
|
|
"""
|
|
if hasattr(obj, '__getddpin__'):
|
|
return obj.__getddpin__()
|
|
|
|
pin_name = _DD_PIN_PROXY_NAME if isinstance(obj, wrapt.ObjectProxy) else _DD_PIN_NAME
|
|
pin = getattr(obj, pin_name, None)
|
|
# detect if the PIN has been inherited from a class
|
|
if pin is not None and pin._target != id(obj):
|
|
pin = pin.clone()
|
|
pin.onto(obj)
|
|
return pin
|
|
|
|
@classmethod
|
|
@debtcollector.removals.removed_kwarg("app_type")
|
|
def override(cls, obj, service=None, app=None, app_type=None, tags=None, tracer=None):
|
|
"""Override an object with the given attributes.
|
|
|
|
That's the recommended way to customize an already instrumented client, without
|
|
losing existing attributes.
|
|
|
|
>>> conn = sqlite.connect('/tmp/user.db')
|
|
>>> # Override a pin for a specific connection
|
|
>>> Pin.override(conn, service='user-db')
|
|
"""
|
|
if not obj:
|
|
return
|
|
|
|
pin = cls.get_from(obj)
|
|
if not pin:
|
|
pin = Pin(service)
|
|
|
|
pin.clone(
|
|
service=service,
|
|
app=app,
|
|
tags=tags,
|
|
tracer=tracer,
|
|
).onto(obj)
|
|
|
|
def enabled(self):
|
|
"""Return true if this pin's tracer is enabled. """
|
|
return bool(self.tracer) and self.tracer.enabled
|
|
|
|
def onto(self, obj, send=True):
|
|
"""Patch this pin onto the given object. If send is true, it will also
|
|
queue the metadata to be sent to the server.
|
|
"""
|
|
# Actually patch it on the object.
|
|
try:
|
|
if hasattr(obj, '__setddpin__'):
|
|
return obj.__setddpin__(self)
|
|
|
|
pin_name = _DD_PIN_PROXY_NAME if isinstance(obj, wrapt.ObjectProxy) else _DD_PIN_NAME
|
|
|
|
# set the target reference; any get_from, clones and retarget the new PIN
|
|
self._target = id(obj)
|
|
return setattr(obj, pin_name, self)
|
|
except AttributeError:
|
|
log.debug("can't pin onto object. skipping", exc_info=True)
|
|
|
|
def remove_from(self, obj):
|
|
# Remove pin from the object.
|
|
try:
|
|
pin_name = _DD_PIN_PROXY_NAME if isinstance(obj, wrapt.ObjectProxy) else _DD_PIN_NAME
|
|
|
|
pin = Pin.get_from(obj)
|
|
if pin is not None:
|
|
delattr(obj, pin_name)
|
|
except AttributeError:
|
|
log.debug("can't remove pin from object. skipping", exc_info=True)
|
|
|
|
@debtcollector.removals.removed_kwarg("app_type")
|
|
def clone(self, service=None, app=None, app_type=None, tags=None, tracer=None):
|
|
"""Return a clone of the pin with the given attributes replaced."""
|
|
# do a shallow copy of Pin dicts
|
|
if not tags and self.tags:
|
|
tags = self.tags.copy()
|
|
|
|
# we use a copy instead of a deepcopy because we expect configurations
|
|
# to have only a root level dictionary without nested objects. Using
|
|
# deepcopy introduces a big overhead:
|
|
#
|
|
# copy: 0.00654911994934082
|
|
# deepcopy: 0.2787208557128906
|
|
config = self._config.copy()
|
|
|
|
return Pin(
|
|
service=service or self.service,
|
|
app=app or self.app,
|
|
tags=tags,
|
|
tracer=tracer or self.tracer, # do not clone the Tracer
|
|
_config=config,
|
|
)
|