Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions manifests/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -532,12 +532,8 @@ manifest:
tests/debugger/test_debugger_expression_language.py::Test_Debugger_Expression_Language::test_expression_language_nulls_true: bug (DEBUG-2618)
? tests/debugger/test_debugger_expression_language.py::Test_Debugger_Expression_Language::test_expression_language_string_operations
: bug (DEBUG-2560)
tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Code_Origin: v3.29.0
? tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Code_Origin::test_inproduct_enablement_code_origin
: bug (DEBUG-4637)
tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Dynamic_Instrumentation: v3.29.0
? tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Dynamic_Instrumentation::test_inproduct_enablement_di
: bug (DEBUG-4637)
tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Code_Origin: bug (DEBUG-4637)
tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Dynamic_Instrumentation: bug (DEBUG-4637)
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)
Expand Down
10 changes: 8 additions & 2 deletions manifests/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1366,8 +1366,14 @@ manifest:
tests/debugger/test_debugger_expression_language.py::Test_Debugger_Expression_Language::test_expression_language_instance_of:
- weblog_declaration:
express4-typescript: bug (DEBUG-3715)
tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Code_Origin: missing_feature
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_Code_Origin:
- weblog_declaration:
"*": *ref_5_83_0
nextjs: irrelevant
tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Dynamic_Instrumentation:
- weblog_declaration:
"*": *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_pii.py::Test_Debugger_PII_Redaction:
- weblog_declaration:
Expand Down
2 changes: 2 additions & 0 deletions manifests/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -914,6 +914,8 @@ manifest:
- weblog_declaration:
"*": missing_feature
*flask: v3.5.0
? tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Dynamic_Instrumentation::test_inproduct_enablement_di
: bug (DEBUG-5000)
tests/debugger/test_debugger_inproduct_enablement.py::Test_Debugger_InProduct_Enablement_Exception_Replay:
- weblog_declaration:
"*": missing_feature
Expand Down
56 changes: 33 additions & 23 deletions tests/debugger/test_debugger_inproduct_enablement.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Copyright 2021 Datadog, Inc.

import tests.debugger.utils as debugger
from utils import features, scenarios, missing_feature, context, logger, bug
from utils import features, scenarios, context, logger, bug, missing_feature
import json
import time

Expand All @@ -26,13 +26,12 @@ class Test_Debugger_InProduct_Enablement_Dynamic_Instrumentation(debugger.BaseDe
"""

def setup_inproduct_enablement_di(self):
def _send_config(*, enabled: bool | None = None):
def _send_config(*, enabled: bool | None = None, reset: bool = True):
probe = json.loads(self._probe_template)
probe["id"] = debugger.generate_probe_id("log")
self.set_probes([probe])

self.send_rc_apm_tracing(dynamic_instrumentation_enabled=enabled)
self.send_rc_probes()
self.send_rc_apm_tracing_and_probes(dynamic_instrumentation_enabled=enabled, reset=reset)
self.send_weblog_request("/debugger/log")

self.initialize_weblog_remote_config()
Expand All @@ -42,13 +41,13 @@ def _send_config(*, enabled: bool | None = None):
_send_config()
self.di_initial_disabled = not self.wait_for_all_probes(statuses=["EMITTING"], timeout=TIMEOUT)

_send_config(enabled=True)
_send_config(enabled=True, reset=False)
self.di_explicit_enabled = self.wait_for_all_probes(statuses=["EMITTING"], timeout=TIMEOUT)

_send_config()
_send_config(reset=False)
self.di_empty_config = self.wait_for_all_probes(statuses=["EMITTING"], timeout=TIMEOUT)

_send_config(enabled=False)
_send_config(enabled=False, reset=False)
self.di_explicit_disabled = not self.wait_for_all_probes(statuses=["EMITTING"], timeout=TIMEOUT)

def test_inproduct_enablement_di(self):
Expand Down Expand Up @@ -172,31 +171,42 @@ def test_inproduct_enablement_exception_replay_apm_multiconfig(self):
@missing_feature(context.library == "python", force_skip=True)
class Test_Debugger_InProduct_Enablement_Code_Origin(debugger.BaseDebuggerTest):
########### code origin ############
def setup_inproduct_enablement_code_origin(self):
def _send_config(*, enabled: bool | None = None):
self.send_rc_apm_tracing(code_origin_enabled=enabled)
self.send_weblog_request("/healthcheck")
def _check_code_origin(self):
"""Send a request and check if code origin spans are present."""
self.send_weblog_request("/")
return self.wait_for_code_origin_span(TIMEOUT)

def _set_code_origin_and_check(self, *, enabled: bool | None):
"""Set code origin via remote config and check if spans are present."""
self.send_rc_apm_tracing(code_origin_enabled=enabled)
return self._check_code_origin()

def setup_inproduct_enablement_code_origin(self):
self.initialize_weblog_remote_config()
self.weblog_responses = []
self.rc_states = []

self.er_initial_enabled = not self.wait_for_code_origin_span(TIMEOUT)
# Check initial state (default varies by language)
self.co_initial_state = self._check_code_origin()

_send_config(enabled=True)
self.er_explicit_enabled = self.wait_for_code_origin_span(TIMEOUT)
# Explicitly enable via remote config
self.co_explicit_enabled = self._set_code_origin_and_check(enabled=True)

_send_config()
self.er_empty_config = self.wait_for_code_origin_span(TIMEOUT)
# Send empty config (null value), should maintain enabled state
self.co_empty_config = self._set_code_origin_and_check(enabled=None)

_send_config(enabled=False)
self.er_explicit_disabled = not self.wait_for_code_origin_span(TIMEOUT)
# Explicitly disable via remote config
self.co_explicit_disabled = not self._set_code_origin_and_check(enabled=False)

def test_inproduct_enablement_code_origin(self):
self.assert_rc_state_not_error()
self.assert_all_weblog_responses_ok()

assert self.er_initial_enabled, "Expected no spans when code origin was disabled"
assert self.er_explicit_enabled, "Expected spans to emit after enabling code origin"
assert self.er_empty_config, "Expected spans to continue emitting with empty config"
assert self.er_explicit_disabled, "Expected spans to stop emitting after explicit disable"
# Check initial state based on language-specific defaults
if context.library == "nodejs":
assert self.co_initial_state, "Expected code origin enabled by default"
else:
assert not self.co_initial_state, "Expected code origin disabled by default"

assert self.co_explicit_enabled, "Expected spans with code origin after explicit enable"
assert self.co_empty_config, "Expected spans to continue emitting with empty config"
assert self.co_explicit_disabled, "Expected spans to stop emitting after explicit disable"
69 changes: 61 additions & 8 deletions tests/debugger/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,49 @@ def send_rc_apm_tracing(
)
)

def send_rc_apm_tracing_and_probes(
self,
*,
dynamic_instrumentation_enabled: bool | None = None,
exception_replay_enabled: bool | None = None,
live_debugging_enabled: bool | None = None,
code_origin_enabled: bool | None = None,
dynamic_sampling_enabled: bool | None = None,
service_name: str | None = "weblog",
env: str | None = "system-tests",
reset: bool = True,
) -> None:
"""Send a combined RC command with both APM_TRACING and LIVE_DEBUGGING (probes) configs.

This is required because Remote Config's `client_configs` field represents the COMPLETE
list of configs that should be active (not incremental updates). When sending separate
payloads for APM_TRACING and LIVE_DEBUGGING, the tracer sees that configs from the first
product are missing from the second payload's `client_configs` list, and unapplies them.

This happens in RemoteConfig.parseConfig() which checks if previously applied configs
are still in the new client_configs list, before product-specific batch handlers run.
"""
BaseDebuggerTest._rc_version += 1

if reset:
self.rc_states = []
self.prev_payloads = []

self.rc_states.append(
remote_config.send_combined_apm_tracing_and_debugger_command(
prev_payloads=self.prev_payloads,
probes=self.probe_definitions,
dynamic_instrumentation_enabled=dynamic_instrumentation_enabled,
exception_replay_enabled=exception_replay_enabled,
live_debugging_enabled=live_debugging_enabled,
code_origin_enabled=code_origin_enabled,
dynamic_sampling_enabled=dynamic_sampling_enabled,
version=BaseDebuggerTest._rc_version,
service_name=service_name,
env=env,
)
)

def send_rc_symdb(self, *, reset: bool = True) -> None:
BaseDebuggerTest._rc_version += 1
if reset:
Expand Down Expand Up @@ -430,30 +473,40 @@ def _wait_for_no_capture_reason_span(self, data: dict):

def wait_for_code_origin_span(self, timeout: int = 5) -> bool:
self._span_found = False
threshold = self._get_max_trace_file_number()

interfaces.agent.wait_for(self._wait_for_code_origin_span, timeout=timeout)
interfaces.agent.wait_for(
lambda data: self._wait_for_code_origin_span(data, threshold=threshold),
timeout=timeout,
)
return self._span_found

_last_read_span = 0
def _get_max_trace_file_number(self) -> int:
"""Get the maximum trace file number currently in the agent interface."""
max_number = 0
for data in interfaces.agent.get_data(_TRACES_PATH):
log_filename_found = re.search(r"/(\d+)__", data["log_filename"])
if log_filename_found:
file_number = int(log_filename_found.group(1))
max_number = max(max_number, file_number)
return max_number

def _wait_for_code_origin_span(self, data: dict):
def _wait_for_code_origin_span(self, data: dict, *, threshold: int) -> bool:
if data["path"] == _TRACES_PATH:
log_filename_found = re.search(r"/(\d+)__", data["log_filename"])
if not log_filename_found:
return False

log_number = int(log_filename_found.group(1))
if log_number >= BaseDebuggerTest._last_read_span:
BaseDebuggerTest._last_read_span = log_number

if log_number > threshold:
content = data["request"]["content"]
if content:
for payload in content["tracerPayloads"]:
for chunk in payload["chunks"]:
for span in chunk["spans"]:
resource, resource_type = span.get("resource"), span.get("type")
resource, resource_type = span.get("resource", ""), span.get("type")

if resource == "GET /healthcheck" and resource_type == "web":
if resource.startswith("GET") and resource_type == "web":
code_origin_type = span["meta"].get("_dd.code_origin.type", "")

if code_origin_type == "entry":
Expand Down
5 changes: 3 additions & 2 deletions utils/_context/_scenarios/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,9 +746,10 @@ class _Scenarios:
debugger_inproduct_enablement = EndToEndScenario(
"DEBUGGER_INPRODUCT_ENABLEMENT",
rc_api_enabled=True,
rc_backend_enabled=True,
rc_backend_enabled=False,
weblog_env={
"DD_APM_TRACING_ENABLED": "true",
"DD_REMOTE_CONFIG_POLL_INTERVAL_SECONDS": "0.1",
"DD_TRACE_FLUSH_INTERVAL": "100",
},
library_interface_timeout=5,
doc="Test scenario for checking dynamic enablement.",
Expand Down
Loading
Loading