diff --git a/ddtrace/debugging/_debugger.py b/ddtrace/debugging/_debugger.py index c36bb94fdfb..c22d45c6fed 100644 --- a/ddtrace/debugging/_debugger.py +++ b/ddtrace/debugging/_debugger.py @@ -274,8 +274,6 @@ def enable(cls) -> None: di_config.enabled = True - cls.__watchdog__.install() - if di_config.metrics: metrics.enable() @@ -307,7 +305,6 @@ def disable(cls, join: bool = True) -> None: cls._instance.stop(join=join) cls._instance = None - cls.__watchdog__.uninstall() if di_config.metrics: metrics.disable() diff --git a/ddtrace/debugging/_products/dynamic_instrumentation.py b/ddtrace/debugging/_products/dynamic_instrumentation.py index 136d5692ec8..c461940f6c0 100644 --- a/ddtrace/debugging/_products/dynamic_instrumentation.py +++ b/ddtrace/debugging/_products/dynamic_instrumentation.py @@ -1,3 +1,4 @@ +from ddtrace.internal.core.event_hub import on from ddtrace.settings.dynamic_instrumentation import config @@ -5,14 +6,23 @@ def post_preload(): - pass + from ddtrace.debugging._debugger import Debugger + + # We need to install this on start-up because if DI gets enabled remotely + # we won't be able to capture many of the code objects from the modules + # that are already loaded. + Debugger.__watchdog__.install() + + +def _start(): + from ddtrace.debugging import DynamicInstrumentation + + DynamicInstrumentation.enable() def start(): if config.enabled: - from ddtrace.debugging import DynamicInstrumentation - - DynamicInstrumentation.enable() + _start() def restart(join=False): @@ -29,3 +39,21 @@ def stop(join=False): def at_exit(join=False): stop(join=join) + + +def apm_tracing_rc(lib_config): + enabled = lib_config.get("dynamic_instrumentation_enabled") + try: + if enabled is not None: # and config.spec.enabled.full_name not in config.source: + if (config.spec.enabled.full_name not in config.source or config.enabled) and enabled: + _start() + else: + stop() + except Exception: + from traceback import print_exc + + print_exc() + raise + + +on("apm-tracing.rc", apm_tracing_rc, "dynamic-instrumentation") diff --git a/ddtrace/internal/remoteconfig/products/__init__.py b/ddtrace/internal/remoteconfig/products/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ddtrace/internal/remoteconfig/products/apm_tracing.py b/ddtrace/internal/remoteconfig/products/apm_tracing.py new file mode 100644 index 00000000000..b60981c698a --- /dev/null +++ b/ddtrace/internal/remoteconfig/products/apm_tracing.py @@ -0,0 +1,31 @@ +from ddtrace import config + + +requires = ["remote-configuration"] + + +def post_preload(): + pass + + +def start(): + if config._remote_config_enabled: + from ddtrace.internal.remoteconfig.worker import remoteconfig_poller + from ddtrace.settings.remoteconfig import APMTracingAdapter + + remoteconfig_poller.register("APM_TRACING", APMTracingAdapter(), restart_on_fork=True) + + +def restart(join=False): + pass + + +def stop(join=False): + if config._remote_config_enabled: + from ddtrace.internal.remoteconfig.worker import remoteconfig_poller + + remoteconfig_poller.unregister("APM_TRACING") + + +def at_exit(join=False): + stop(join=join) diff --git a/ddtrace/internal/remoteconfig/product.py b/ddtrace/internal/remoteconfig/products/client.py similarity index 100% rename from ddtrace/internal/remoteconfig/product.py rename to ddtrace/internal/remoteconfig/products/client.py diff --git a/ddtrace/settings/remoteconfig.py b/ddtrace/settings/remoteconfig.py new file mode 100644 index 00000000000..0f13bd5de62 --- /dev/null +++ b/ddtrace/settings/remoteconfig.py @@ -0,0 +1,40 @@ +from ddtrace import config +from ddtrace.internal.core.event_hub import dispatch +from ddtrace.internal.logger import get_logger +from ddtrace.internal.remoteconfig._connectors import PublisherSubscriberConnector +from ddtrace.internal.remoteconfig._publishers import RemoteConfigPublisher +from ddtrace.internal.remoteconfig._pubsub import PubSub +from ddtrace.internal.remoteconfig._subscribers import RemoteConfigSubscriber + + +log = get_logger(__name__) + + +def _rc_callback(data, test_tracer=None): + for metadata, data in zip(data["metadata"], data["config"]): + if metadata is None or not isinstance(data, dict): + continue + + service_target = data.get("service_target") + if service_target is not None: + service = service_target.get("service") + if service is not None and service != config.service: + continue + + env = service_target.get("env") + if env is not None and env != config.env: + continue + + lib_config = data.get("lib_config") + if lib_config is not None: + dispatch("apm-tracing.rc", (lib_config,)) + + +class APMTracingAdapter(PubSub): + __publisher_class__ = RemoteConfigPublisher + __subscriber_class__ = RemoteConfigSubscriber + __shared_data__ = PublisherSubscriberConnector() + + def __init__(self): + self._publisher = self.__publisher_class__(self.__shared_data__) + self._subscriber = self.__subscriber_class__(self.__shared_data__, _rc_callback, "APM_TRACING") diff --git a/pyproject.toml b/pyproject.toml index df5fbdcdbb2..e46d2597b07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,10 +60,11 @@ ddtrace = "ddtrace.contrib.pytest.plugin" "ddtrace.pytest_benchmark" = "ddtrace.contrib.pytest_benchmark.plugin" [project.entry-points.'ddtrace.products'] +"apm-tracing-rc" = "ddtrace.internal.remoteconfig.products.apm_tracing" "code-origin-for-spans" = "ddtrace.debugging._products.code_origin.span" "dynamic-instrumentation" = "ddtrace.debugging._products.dynamic_instrumentation" "exception-replay" = "ddtrace.debugging._products.exception_replay" -"remote-configuration" = "ddtrace.internal.remoteconfig.product" +"remote-configuration" = "ddtrace.internal.remoteconfig.products.client" "symbol-database" = "ddtrace.internal.symbol_db.product" [project.urls]