Skip to content
Open
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
10 changes: 6 additions & 4 deletions tests/parametric/capabilities.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ capabilities:
- ASM_AUTO_USER_INSTRUM_MODE

# SDK_CONFIGURATION supersedes the per-setting APM_TRACING capabilities in the 5.x
# line from 5.128.0 and in the 6.x line from 6.17.0.
'<5.128.0 || >=6.0.0-0 <6.17.0':
# line from 5.128.0 and in the 6.x line from 6.17.0. Node.js 6.19.0 restores the
# legacy bits as compatibility metadata while continuing to consume sdk_config. Keep the
# restored-bit range below 7.0.0 so this can land before the change reaches Node.js main.
'<5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0':
- APM_TRACING_CUSTOM_TAGS
- APM_TRACING_ENABLED
- APM_TRACING_HTTP_HEADER_TAGS
Expand All @@ -88,11 +90,11 @@ capabilities:
'>=5.83.0':
- APM_TRACING_MULTICONFIG

'>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0':
'>=5.83.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0':
- APM_TRACING_ENABLE_CODE_ORIGIN
- APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION

'>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0':
'>=5.84.0 <5.128.0 || >=6.0.0-0 <6.17.0 || >=6.19.0 <7.0.0-0':
- APM_TRACING_ENABLE_LIVE_DEBUGGING

python:
Expand Down
16 changes: 8 additions & 8 deletions tests/parametric/test_dynamic_configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ def uses_sdk_configuration(test_agent: TestAgentAPI) -> bool:
def assert_rc_capability(test_agent: TestAgentAPI, capability: Capabilities, wait_loops: int = 100) -> None:
"""Assert that the tracer advertises the capability to remotely configure one setting.

A tracer that has moved to the unified SDK_CONFIGURATION contract advertises that single bit
for every remotely configurable setting instead of the per-setting ones, so it stands in for
any of them. The SDK_CONFIGURATION bit on its own does not, since libdatadog gives that bit a
different meaning; only a tracer that has really dropped the per-setting bits qualifies.
A tracer that has moved to the unified SDK_CONFIGURATION contract can use that bit for every
remotely configurable setting, so it stands in for any missing per-setting one. The
SDK_CONFIGURATION bit on its own does not, since libdatadog gives that bit a different meaning;
only a tracer whose capability combination identifies the unified contract qualifies.
"""
seen_capabilities = test_agent.wait_for_rc_capabilities(wait_loops)
if capability in seen_capabilities:
Expand Down Expand Up @@ -581,10 +581,10 @@ def test_tracing_client_tracing_disable_one_way(
class Test_DynamicConfigSdkConfiguration:
"""Coverage for the generic sdk_config RC delivery path.

sdk_config carries settings as generic env-var-keyed entries, applied via the single
SDK_CONFIGURATION capability instead of custom per-setting parsing. These tests confirm a
tracer that declares SDK_CONFIGURATION consumes sdk_config with no regression in behavior
compared to the equivalent lib_config delivery, starting with DD_TRACE_ENABLED.
sdk_config carries settings as generic env-var-keyed entries, applied via the unified
SDK_CONFIGURATION contract instead of custom per-setting parsing. These tests confirm a tracer
on that contract consumes sdk_config with no regression in behavior compared to the equivalent
lib_config delivery, starting with DD_TRACE_ENABLED.

test_sdk_config_tracing_enabled_matches_lib_config asserts real behavior (tracing actually
stops) for DD_TRACE_ENABLED. test_sdk_config_field_is_applied then layers a shallower
Expand Down
48 changes: 31 additions & 17 deletions tests/test_the_test/test_remote_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,10 @@ def test_resolve_sdk_configuration_contract():
"""The SDK_CONFIGURATION bit alone does not mean the library reads sdk_config.

Bit 49 is SDK_CONFIGURATION in the remote config source of truth, but libdatadog gives the
same bit to ASM_RAW_RESPONSE_BODY, so the per-setting capabilities have to be gone too.
same bit to ASM_RAW_RESPONSE_BODY. Capability interpretation therefore also needs to account
for the tracer implementation when legacy bits and SDK_CONFIGURATION appear together.
"""
# dd-trace-js with the SDK_CONFIGURATION support: the per-setting capabilities are dropped
# dd-trace-js 6.17.0 and 6.18.0: the per-setting capabilities are dropped
assert rc.resolve_sdk_configuration_contract(
{
Capabilities.ASM_ACTIVATION,
Expand All @@ -207,21 +208,34 @@ def test_resolve_sdk_configuration_contract():
}
)

# dd-trace-php: bit 49 is ASM_RAW_RESPONSE_BODY there, and lib_config is still what it reads
core_lib_config_capabilities = {
Capabilities.APM_TRACING_CUSTOM_TAGS,
Capabilities.APM_TRACING_ENABLED,
Capabilities.APM_TRACING_HTTP_HEADER_TAGS,
Capabilities.APM_TRACING_LOGS_INJECTION,
Capabilities.APM_TRACING_SAMPLE_RATE,
Capabilities.APM_TRACING_SAMPLE_RULES,
}

# dd-trace-php: bit 49 is ASM_RAW_RESPONSE_BODY there, and the per-setting fingerprint confirms
# that lib_config is still what it reads.
php_lib_config_capabilities = core_lib_config_capabilities | {
Capabilities.APM_TRACING_MULTICONFIG,
Capabilities.SDK_CONFIGURATION,
}
assert rc.resolve_sdk_configuration_contract(php_lib_config_capabilities, library_name="php") is False

# dd-trace-js 6.19.0+: all nine restored per-setting bits are compatibility metadata, but
# sdk_config remains the only application path.
nodejs_sdk_config_with_legacy_capabilities = core_lib_config_capabilities | {
Capabilities.APM_TRACING_MULTICONFIG,
Capabilities.APM_TRACING_ENABLE_CODE_ORIGIN,
Capabilities.APM_TRACING_ENABLE_DYNAMIC_INSTRUMENTATION,
Capabilities.APM_TRACING_ENABLE_LIVE_DEBUGGING,
Capabilities.SDK_CONFIGURATION,
}
assert (
rc.resolve_sdk_configuration_contract(
{
Capabilities.APM_TRACING_CUSTOM_TAGS,
Capabilities.APM_TRACING_ENABLED,
Capabilities.APM_TRACING_HTTP_HEADER_TAGS,
Capabilities.APM_TRACING_LOGS_INJECTION,
Capabilities.APM_TRACING_SAMPLE_RATE,
Capabilities.APM_TRACING_SAMPLE_RULES,
Capabilities.APM_TRACING_MULTICONFIG,
Capabilities.SDK_CONFIGURATION,
}
)
is False
rc.resolve_sdk_configuration_contract(nodejs_sdk_config_with_legacy_capabilities, library_name="nodejs") is True
)

# dd-trace-java: no SDK_CONFIGURATION at all
Expand Down Expand Up @@ -265,5 +279,5 @@ def test_apm_tracing_capabilities_exclude_sdk_configuration():
`resolve_sdk_configuration_contract`.
"""
assert Capabilities.SDK_CONFIGURATION not in rc.APM_TRACING_CAPABILITIES
assert rc.LEGACY_APM_TRACING_CAPABILITIES <= rc.APM_TRACING_CAPABILITIES
assert rc.LIB_CONFIG_CAPABILITY_FINGERPRINT <= rc.APM_TRACING_CAPABILITIES
assert Capabilities.APM_TRACING_MULTICONFIG in rc.APM_TRACING_CAPABILITIES
33 changes: 22 additions & 11 deletions utils/_remote_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -645,9 +645,11 @@ def to_sdk_config_payload(config: dict[str, Any]) -> dict[str, Any]:
capability for capability in Capabilities if capability.name.startswith("APM_TRACING_")
)

# The per-setting APM_TRACING capabilities that SDK_CONFIGURATION replaces. A library on the new
# contract advertises the single SDK_CONFIGURATION bit instead of all of these.
LEGACY_APM_TRACING_CAPABILITIES = frozenset(
# The per-setting capability fingerprint observed on libraries that consume `lib_config`. This is
# deliberately not an exhaustive set of legacy APM_TRACING capabilities: optional feature bits do
# not reliably identify the payload contract. Node.js 6.19.0+ re-advertises this fingerprint for
# backend/frontend compatibility while continuing to consume `sdk_config`.
LIB_CONFIG_CAPABILITY_FINGERPRINT = frozenset(
{
Capabilities.APM_TRACING_CUSTOM_TAGS,
Capabilities.APM_TRACING_ENABLED,
Expand All @@ -659,7 +661,9 @@ def to_sdk_config_payload(config: dict[str, Any]) -> dict[str, Any]:
)


def resolve_sdk_configuration_contract(capabilities: set[Capabilities]) -> bool | None:
def resolve_sdk_configuration_contract(
capabilities: set[Capabilities], *, library_name: str | None = None
) -> bool | None:
"""Decide which APM_TRACING payload shape a set of advertised capabilities asks for.

Returns True for `sdk_config`, False for `lib_config`, and None when the capabilities seen so
Expand All @@ -670,16 +674,23 @@ def resolve_sdk_configuration_contract(capabilities: set[Capabilities]) -> bool
libdatadog hands the same bit to `ASM_RAW_RESPONSE_BODY`, so a libdatadog-based library such as
dd-trace-php advertises it while still reading `lib_config`.

Dropping the per-setting capabilities is the whole point of the unified bit, so their absence
is what distinguishes the two. Absence only counts once the library has actually registered its
APM_TRACING remote config, though: capabilities are added as products start, and AppSec ones
come first, so an early poll from a libdatadog library shows bit 49 with no APM_TRACING bit yet
and would otherwise be mistaken for the unified contract.
For Node.js, SDK_CONFIGURATION is authoritative even when the legacy per-setting bits are also
present. Node.js 6.19.0 restored those bits for backend/frontend compatibility without restoring
the legacy `lib_config` application path.

For other tracers, the per-setting fingerprint distinguishes the two contracts. Its absence
only counts once the library has actually registered its APM_TRACING remote config, though:
capabilities are added as products start, and AppSec ones come first, so an early poll from a
libdatadog library shows bit 49 with no APM_TRACING bit yet and would otherwise be mistaken for
the unified contract.
"""
if not capabilities & APM_TRACING_CAPABILITIES:
return None

if capabilities & LEGACY_APM_TRACING_CAPABILITIES:
if library_name == "nodejs" and Capabilities.SDK_CONFIGURATION in capabilities:
return True

if capabilities & LIB_CONFIG_CAPABILITY_FINGERPRINT:
return False

return Capabilities.SDK_CONFIGURATION in capabilities
Expand Down Expand Up @@ -711,7 +722,7 @@ def resolve_sdk_configuration_support(get_capabilities: Callable[[], set[Capabil
logger.error(f"Could not read the RC capabilities ({e}), assuming no SDK_CONFIGURATION support")
return False

supported = resolve_sdk_configuration_contract(capabilities)
supported = resolve_sdk_configuration_contract(capabilities, library_name=context.library.name)
if supported is None:
logger.info("No APM_TRACING capability advertised yet, sending lib_config for now")
return False
Expand Down
Loading