-
Notifications
You must be signed in to change notification settings - Fork 423
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into brettlangdon/oci.dedupe
- Loading branch information
Showing
234 changed files
with
605 additions
and
546 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import abc | ||
import re | ||
from typing import TYPE_CHECKING # noqa:F401 | ||
from typing import List # noqa:F401 | ||
from typing import Optional # noqa:F401 | ||
from typing import Union # noqa:F401 | ||
|
||
from ddtrace._trace.processor import TraceProcessor | ||
from ddtrace.ext import http | ||
|
||
|
||
if TYPE_CHECKING: # pragma: no cover | ||
from ddtrace._trace.span import Span # noqa:F401 | ||
|
||
|
||
class TraceFilter(TraceProcessor): | ||
@abc.abstractmethod | ||
def process_trace(self, trace): | ||
# type: (List[Span]) -> Optional[List[Span]] | ||
"""Processes a trace. | ||
None can be returned to prevent the trace from being exported. | ||
""" | ||
pass | ||
|
||
|
||
class FilterRequestsOnUrl(TraceFilter): | ||
r"""Filter out traces from incoming http requests based on the request's url. | ||
This class takes as argument a list of regular expression patterns | ||
representing the urls to be excluded from tracing. A trace will be excluded | ||
if its root span contains a ``http.url`` tag and if this tag matches any of | ||
the provided regular expression using the standard python regexp match | ||
semantic (https://docs.python.org/3/library/re.html#re.match). | ||
:param list regexps: a list of regular expressions (or a single string) defining | ||
the urls that should be filtered out. | ||
Examples: | ||
To filter out http calls to domain api.example.com:: | ||
FilterRequestsOnUrl(r'http://api\\.example\\.com') | ||
To filter out http calls to all first level subdomains from example.com:: | ||
FilterRequestOnUrl(r'http://.*+\\.example\\.com') | ||
To filter out calls to both http://test.example.com and http://example.com/healthcheck:: | ||
FilterRequestOnUrl([r'http://test\\.example\\.com', r'http://example\\.com/healthcheck']) | ||
""" | ||
|
||
def __init__(self, regexps: Union[str, List[str]]): | ||
if isinstance(regexps, str): | ||
regexps = [regexps] | ||
self._regexps = [re.compile(regexp) for regexp in regexps] | ||
|
||
def process_trace(self, trace): | ||
# type: (List[Span]) -> Optional[List[Span]] | ||
""" | ||
When the filter is registered in the tracer, process_trace is called by | ||
on each trace before it is sent to the agent, the returned value will | ||
be fed to the next filter in the list. If process_trace returns None, | ||
the whole trace is discarded. | ||
""" | ||
for span in trace: | ||
url = span.get_tag(http.URL) | ||
if span.parent_id is None and url is not None: | ||
for regexp in self._regexps: | ||
if regexp.match(url): | ||
return None | ||
return trace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
from typing import TYPE_CHECKING # noqa:F401 | ||
from typing import Any # noqa:F401 | ||
from typing import Dict # noqa:F401 | ||
from typing import Optional # noqa:F401 | ||
|
||
import wrapt | ||
|
||
import ddtrace | ||
|
||
from ..internal.logger import get_logger | ||
|
||
|
||
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__ = ["tags", "tracer", "_target", "_config", "_initialized"] | ||
|
||
def __init__( | ||
self, | ||
service=None, # type: Optional[str] | ||
tags=None, # type: Optional[Dict[str, str]] | ||
tracer=None, | ||
_config=None, # type: Optional[Dict[str, Any]] | ||
): | ||
# type: (...) -> None | ||
tracer = tracer or ddtrace.tracer | ||
self.tags = tags | ||
self.tracer = tracer | ||
self._target = None # type: Optional[int] | ||
# keep the configuration attribute internal because the | ||
# public API to access it is not the Pin class | ||
self._config = _config or {} # type: Dict[str, Any] | ||
# [Backward compatibility]: service argument updates the `Pin` config | ||
self._config["service_name"] = service | ||
self._initialized = True | ||
|
||
@property | ||
def service(self): | ||
# type: () -> str | ||
"""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, tags=%s, tracer=%s)" % (self.service, self.tags, self.tracer) | ||
|
||
@staticmethod | ||
def _find(*objs): | ||
# type: (Any) -> Optional[Pin] | ||
""" | ||
Return the first :class:`ddtrace.trace.Pin` found on any of the provided objects or `None` if none were found | ||
>>> pin = Pin._find(wrapper, instance, conn) | ||
:param objs: The objects to search for a :class:`ddtrace.trace.Pin` on | ||
:type objs: List of objects | ||
:rtype: :class:`ddtrace.trace.Pin`, None | ||
:returns: The first found :class:`ddtrace.trace.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): | ||
# type: (Any) -> Optional[Pin] | ||
"""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.trace.Pin` on | ||
:type obj: object | ||
:rtype: :class:`ddtrace.trace.Pin`, None | ||
:returns: :class:`ddtrace.trace.Pin` associated with the object or None | ||
""" | ||
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 | ||
def override( | ||
cls, | ||
obj, # type: Any | ||
service=None, # type: Optional[str] | ||
tags=None, # type: Optional[Dict[str, str]] | ||
tracer=None, | ||
): | ||
# type: (...) -> 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 pin is None: | ||
Pin(service=service, tags=tags, tracer=tracer).onto(obj) | ||
else: | ||
pin.clone(service=service, tags=tags, tracer=tracer).onto(obj) | ||
|
||
def enabled(self): | ||
# type: () -> bool | ||
"""Return true if this pin's tracer is enabled.""" | ||
# inline to avoid circular imports | ||
from ddtrace.settings.asm import config as asm_config | ||
|
||
return bool(self.tracer) and (self.tracer.enabled or asm_config._apm_opt_out) | ||
|
||
def onto(self, obj, send=True): | ||
# type: (Any, bool) -> None | ||
"""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) | ||
if self.service: | ||
ddtrace.config._add_extra_service(self.service) | ||
return setattr(obj, pin_name, self) | ||
except AttributeError: | ||
log.debug("can't pin onto object. skipping", exc_info=True) | ||
|
||
def remove_from(self, obj): | ||
# type: (Any) -> None | ||
# 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) | ||
|
||
def clone( | ||
self, | ||
service=None, # type: Optional[str] | ||
tags=None, # type: Optional[Dict[str, str]] | ||
tracer=None, | ||
): | ||
# type: (...) -> Pin | ||
"""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, | ||
tags=tags, | ||
tracer=tracer or self.tracer, # do not clone the Tracer | ||
_config=config, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.