diff --git a/manifests/dotnet.yml b/manifests/dotnet.yml index 6133abc9d72..70a0e36509a 100644 --- a/manifests/dotnet.yml +++ b/manifests/dotnet.yml @@ -749,6 +749,9 @@ manifest: tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Exception_Replay: v3.29.0 ? tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Exception_Replay::test_inproduct_enablement_exception_replay_apm_multiconfig : bug (DEBUG-4637) + tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Multiconfig_Partial_Update: # DEBUG-4637: fix remote enablement (#9344) + - declaration: bug (DEBUG-4637) + component_version: '<3.57.0' tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction: v2.50.0 tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction::test_pii_redaction_method_full: - declaration: missing_feature (keywords are not fully redacted) diff --git a/manifests/golang.yml b/manifests/golang.yml index 879c6050c7e..cbc991322f3 100644 --- a/manifests/golang.yml +++ b/manifests/golang.yml @@ -1665,6 +1665,7 @@ manifest: tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Code_Origin_Default_On: irrelevant (Scoped to dotnet rollout in this PR) tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Dynamic_Instrumentation: missing_feature tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Exception_Replay: missing_feature + tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Multiconfig_Partial_Update: missing_feature tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction: missing_feature (feature not implemented) tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction_Excluded_Identifiers: missing_feature (feature not implemented) tests/debugger/test_debugger_probe_budgets.py::Test_Debugger_Probe_Budgets: v2.2.3 diff --git a/manifests/java.yml b/manifests/java.yml index f64312410e4..e526e65d1f8 100644 --- a/manifests/java.yml +++ b/manifests/java.yml @@ -3272,6 +3272,16 @@ manifest: uds-spring-boot: v1.60.1 # TODO: a lower version might be supported ? tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Exception_Replay::test_inproduct_enablement_exception_replay_apm_multiconfig : bug (DEBUG-4736) + tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Multiconfig_Partial_Update: + - weblog_declaration: + "*": missing_feature + spring-boot: v1.54.0 + spring-boot-jetty: v1.54.0 + spring-boot-openliberty: v1.54.0 + spring-boot-payara: v1.54.0 + spring-boot-undertow: v1.54.0 + spring-boot-wildfly: v1.54.0 + uds-spring-boot: v1.54.0 tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction: - weblog_declaration: "*": missing_feature diff --git a/manifests/nodejs.yml b/manifests/nodejs.yml index 7c8777e44f5..be93edcc495 100644 --- a/manifests/nodejs.yml +++ b/manifests/nodejs.yml @@ -1765,6 +1765,10 @@ manifest: "*": *ref_5_83_0 nextjs: irrelevant tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Exception_Replay: missing_feature + tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Multiconfig_Partial_Update: + - weblog_declaration: + "*": *ref_5_83_0 + nextjs: irrelevant tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction: - weblog_declaration: "*": *ref_5_39_0 diff --git a/manifests/php.yml b/manifests/php.yml index d76568c7418..ec01c6cbe16 100644 --- a/manifests/php.yml +++ b/manifests/php.yml @@ -1178,6 +1178,11 @@ manifest: laravel11x: incomplete_test_app symfony7x: incomplete_test_app tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Exception_Replay: missing_feature (blocked by exception replay throwable gap) + tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Multiconfig_Partial_Update: + - weblog_declaration: + "*": v1.20.0 + laravel11x: incomplete_test_app + symfony7x: incomplete_test_app tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction: - weblog_declaration: "*": v1.19.0 diff --git a/manifests/python.yml b/manifests/python.yml index 2cb386b31f8..56effa71c1f 100644 --- a/manifests/python.yml +++ b/manifests/python.yml @@ -2079,6 +2079,10 @@ manifest: component_version: <4.9.0 ? tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Exception_Replay::test_inproduct_enablement_exception_replay_apm_multiconfig : missing_feature + tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Multiconfig_Partial_Update: + - weblog_declaration: + "*": missing_feature + *flask : v3.15.0 tests/debugger/test_debugger_pii.py: - weblog_declaration: aiohttp: missing_feature (new weblog, tests are enabled with easy wins) diff --git a/manifests/ruby.yml b/manifests/ruby.yml index 502a9a722ee..163024232c3 100644 --- a/manifests/ruby.yml +++ b/manifests/ruby.yml @@ -1968,6 +1968,12 @@ manifest: rails80: v2.39.0-dev uds-rails: v2.39.0-dev tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Exception_Replay: missing_feature + tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Multiconfig_Partial_Update: + - weblog_declaration: + "*": missing_feature + rails72: v2.42.0 + rails80: v2.42.0 + uds-rails: v2.42.0 tests/debugger/test_debugger_pii.py::Test_Debugger_PII_Redaction: - weblog_declaration: "*": irrelevant diff --git a/tests/debugger/test_debugger_inproduct_enablement.py b/tests/debugger/test_debugger_inproduct_enablement.py index f6fdddc0ed5..21e574e3d3c 100644 --- a/tests/debugger/test_debugger_inproduct_enablement.py +++ b/tests/debugger/test_debugger_inproduct_enablement.py @@ -3,7 +3,7 @@ # Copyright 2021 Datadog, Inc. import tests.debugger.utils as debugger -from utils import context, features, logger, scenarios, slow +from utils import context, features, logger, remote_config, scenarios, slow import json import time @@ -11,26 +11,25 @@ DEFAULT_ENVVARS = { "DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS": "0.2", } +LOG_PROBE_TEMPLATE = """ +{ + "version": 0, + "where": { + "typeName": null, + "sourceFile": "ACTUAL_SOURCE_FILE", + "lines": ["20"] + } +} +""" @features.debugger_inproduct_enablement @scenarios.debugger_inproduct_enablement class Test_Debugger_InProduct_Enablement_Dynamic_Instrumentation(debugger.BaseDebuggerTest): ############ dynamic instrumentation ############ - _probe_template = """ - { - "version": 0, - "where": { - "typeName": null, - "sourceFile": "ACTUAL_SOURCE_FILE", - "lines": ["20"] - } - } - """ - def setup_inproduct_enablement_di(self): def _send_config(*, enabled: bool | None = None, reset: bool = True): - probe = json.loads(self._probe_template) + probe = json.loads(LOG_PROBE_TEMPLATE) probe["id"] = debugger.generate_probe_id("log") self.set_probes([probe]) @@ -263,3 +262,81 @@ def test_code_origin_enabled_by_default(self): self.assert_all_weblog_responses_ok() assert self.code_origin_enabled_by_default, "Expected code origin enabled by default" + + +@features.debugger_inproduct_enablement +@scenarios.debugger_inproduct_enablement +class Test_Debugger_InProduct_Enablement_Multiconfig_Partial_Update(debugger.BaseDebuggerTest): + """Updating one APM_TRACING config must not drop the other active configs. + + An unchanged file keeps its hash, so libraries skip it, but it is still listed + in client_configs and must stay applied. + """ + + _WILDCARD_CONFIG_PATH = "datadog/2/APM_TRACING/inproduct-multiconfig-wildcard/config" + _SERVICE_CONFIG_PATH = "datadog/2/APM_TRACING/inproduct-multiconfig-service/config" + + def _set_apm_tracing_config(self, path: str, settings: dict, *, service_name: str, env: str) -> None: + lib_config = {"library_language": "all", "library_version": "latest", "tracing_enabled": True} | settings + config = { + "schema_version": "v1.0.0", + "action": "enable", + "lib_config": lib_config, + "service_target": {"service": service_name, "env": env}, + } + if remote_config.library_supports_sdk_configuration(): + config = remote_config.to_sdk_config_payload(config) + remote_config.tracer_rc_state.set_config(path, config) + + def _apply_with_new_probe_and_check_emitting(self) -> bool: + probe = json.loads(LOG_PROBE_TEMPLATE) + probe["id"] = debugger.generate_probe_id("log") + self.set_probes([probe]) + self.step_probe_ids.append(probe["id"]) + remote_config.tracer_rc_state.set_config( + f"datadog/2/LIVE_DEBUGGING/logProbe_{probe['id']}/config", self.probe_definitions[0] + ) + + self.rc_states.append(remote_config.tracer_rc_state.apply()) + self.resolve_php_probes() + + self.send_weblog_request("/debugger/log", reset=False) + # An ERROR status also ends this wait, so the test checks that each probe reached EMITTING + return self.wait_for_all_probes(statuses=["EMITTING"], timeout=TIMEOUT) + + def setup_inproduct_enablement_multiconfig_partial_update(self): + self.initialize_weblog_remote_config() + self.weblog_responses = [] + self.rc_states = [] + self.step_probe_ids: list[str] = [] + remote_config.tracer_rc_state.reset().apply() + + # The wildcard config enables dynamic instrumentation, the service config only sets code origin. + self._set_apm_tracing_config( + self._WILDCARD_CONFIG_PATH, {"dynamic_instrumentation_enabled": True}, service_name="*", env="*" + ) + self._set_apm_tracing_config( + self._SERVICE_CONFIG_PATH, {"code_origin_enabled": True}, service_name="weblog", env="system-tests" + ) + self.di_enabled = self._apply_with_new_probe_and_check_emitting() + + # Change a single setting of the service config. The wildcard config is resent unchanged. + self._set_apm_tracing_config( + self._SERVICE_CONFIG_PATH, {"code_origin_enabled": False}, service_name="weblog", env="system-tests" + ) + self.di_kept_enabled = self._apply_with_new_probe_and_check_emitting() + + def test_inproduct_enablement_multiconfig_partial_update(self): + self.assert_rc_state_not_error() + self.assert_all_weblog_responses_ok() + + self.collect() + statuses = {probe_id: diagnostics["status"] for probe_id, diagnostics in self.probe_diagnostics.items()} + first_status, second_status = (statuses.get(probe_id) for probe_id in self.step_probe_ids) + + assert self.di_enabled, ( + "Expected probes to be emitting after the wildcard config enabled dynamic instrumentation" + ) + assert first_status == "EMITTING", f"Expected the first probe to be EMITTING, got {first_status}" + assert self.di_kept_enabled, "Expected probes to keep emitting after updating only the service config" + assert second_status == "EMITTING", f"Expected the second probe to be EMITTING, got {second_status}" diff --git a/tests/debugger/utils.py b/tests/debugger/utils.py index 50db8eaf538..b73582505b0 100644 --- a/tests/debugger/utils.py +++ b/tests/debugger/utils.py @@ -391,6 +391,9 @@ def send_rc_probes(self, *, reset: bool = True) -> None: remote_config.send_debugger_command(probes=self.probe_definitions, version=BaseDebuggerTest._rc_version) ) + self.resolve_php_probes() + + def resolve_php_probes(self) -> None: # PHP tracer requires requests to /debugger/* to process RC and resolve probe hooks. # Pass probe IDs so the PHP endpoint polls until they appear in the loaded RC state. if context.library == "php": @@ -472,11 +475,7 @@ def send_rc_apm_tracing_and_probes( ) ) - # PHP tracer requires requests to /debugger/* to process RC before the test request. - # Pass probe IDs so the PHP endpoint polls until they appear in the loaded RC state. - if context.library == "php": - probe_ids = ",".join(p["id"] for p in self.probe_definitions if "id" in p) - weblog.get(f"/debugger/init?probes={probe_ids}") + self.resolve_php_probes() def send_rc_symdb(self, *, reset: bool = True) -> None: BaseDebuggerTest._rc_version += 1 diff --git a/utils/build/docker/dotnet/weblog/Controllers/DebuggerController.cs b/utils/build/docker/dotnet/weblog/Controllers/DebuggerController.cs index d159e702521..1f936d3b644 100644 --- a/utils/build/docker/dotnet/weblog/Controllers/DebuggerController.cs +++ b/utils/build/docker/dotnet/weblog/Controllers/DebuggerController.cs @@ -14,7 +14,7 @@ namespace weblog public class DebuggerController : Controller { [HttpGet("log")] - [Consumes("application/json", "application/xml")] + [Consumes("application/json", "application/xml"), System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)] public IActionResult LogProbe() { return Content($"Log probe");