Skip to content

Commit 26f22bc

Browse files
authored
chore(tracer): report changes to config values (#7909)
A feature is being built to report configuration in the Datadog UI based on the telemetry data. In order to provide the most up to date configuration data in the UI we need to report configuration change events when settings are updated. The feature also supports reporting the origin of a setting at the granularity of ("code", "env_var", "remote_config" or "default") which will help customers debug where a setting for their service might be coming from. I noticed that the telemetry config changed code doesn't do any batching of configuration updates so this would be a potential performance improvement in the future. There is the risk that if a customer is frequently configuring a setting that many telemetry events will be generated which could lead to performance regressions. I imagine this case is small since up until #7489 global config updates to sample rate would not have any effect. Tests are added to cover the different origins. ![image](https://github.com/DataDog/dd-trace-py/assets/6321485/8cbfbb1e-c2ad-4c1e-984b-2170822d2264) The implementation should be consistent with DataDog/system-tests#1734.
1 parent 8d84740 commit 26f22bc

File tree

6 files changed

+237
-130
lines changed

6 files changed

+237
-130
lines changed

ddtrace/internal/telemetry/constants.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
TELEMETRY_ANALYTICS_ENABLED = "DD_TRACE_ANALYTICS_ENABLED"
2323
TELEMETRY_STARTUP_LOGS_ENABLED = "DD_TRACE_STARTUP_LOGS"
2424
TELEMETRY_CLIENT_IP_ENABLED = "DD_TRACE_CLIENT_IP_ENABLED"
25-
TELEMETRY_LOGS_INJECTION_ENABLED = "DD_LOGS_INJECTION"
2625
TELEMETRY_128_BIT_TRACEID_GENERATION_ENABLED = "DD_TRACE_128_BIT_TRACEID_GENERATION_ENABLED"
2726
TELEMETRY_128_BIT_TRACEID_LOGGING_ENABLED = "DD_TRACE_128_BIT_TRACEID_LOGGING_ENABLED"
2827
TELEMETRY_TRACE_COMPUTE_STATS = "DD_TRACE_COMPUTE_STATS"
@@ -38,7 +37,6 @@
3837
TELEMETRY_SPAN_SAMPLING_RULES_FILE = "DD_SPAN_SAMPLING_RULES_FILE"
3938
TELEMETRY_PROPAGATION_STYLE_INJECT = "DD_TRACE_PROPAGATION_STYLE_INJECT"
4039
TELEMETRY_PROPAGATION_STYLE_EXTRACT = "DD_TRACE_PROPAGATION_STYLE_EXTRACT"
41-
TELEMETRY_TRACE_SAMPLING_RATE = "DD_TRACE_SAMPLE_RATE"
4240
TELEMETRY_TRACE_SAMPLING_RULES = "DD_TRACE_SAMPLING_RULES"
4341
TELEMETRY_TRACE_SAMPLING_LIMIT = "DD_TRACE_RATE_LIMIT"
4442
TELEMETRY_PRIORITY_SAMPLING = "DD_PRIORITY_SAMPLING"

ddtrace/internal/telemetry/writer.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from ...internal.schema import _remove_client_service_names
2020
from ...settings import _config as config
2121
from ...settings.asm import config as asm_config
22+
from ...settings.config import _ConfigSource
2223
from ...settings.dynamic_instrumentation import config as di_config
2324
from ...settings.exception_debugging import config as ed_config
2425
from ...settings.peer_service import _ps_config
@@ -50,7 +51,6 @@
5051
from .constants import TELEMETRY_DYNAMIC_INSTRUMENTATION_ENABLED
5152
from .constants import TELEMETRY_ENABLED
5253
from .constants import TELEMETRY_EXCEPTION_DEBUGGING_ENABLED
53-
from .constants import TELEMETRY_LOGS_INJECTION_ENABLED
5454
from .constants import TELEMETRY_OBFUSCATION_QUERY_STRING_PATTERN
5555
from .constants import TELEMETRY_OTEL_ENABLED
5656
from .constants import TELEMETRY_PARTIAL_FLUSH_ENABLED
@@ -75,7 +75,6 @@
7575
from .constants import TELEMETRY_TRACE_PEER_SERVICE_MAPPING
7676
from .constants import TELEMETRY_TRACE_REMOVE_INTEGRATION_SERVICE_NAMES_ENABLED
7777
from .constants import TELEMETRY_TRACE_SAMPLING_LIMIT
78-
from .constants import TELEMETRY_TRACE_SAMPLING_RATE
7978
from .constants import TELEMETRY_TRACE_SAMPLING_RULES
8079
from .constants import TELEMETRY_TRACE_SPAN_ATTRIBUTE_SCHEMA
8180
from .constants import TELEMETRY_TRACE_WRITER_BUFFER_SIZE_BYTES
@@ -294,6 +293,25 @@ def add_error(self, code, msg, filename, line_number):
294293
msg = "%s:%s: %s" % (filename, line_number, msg)
295294
self._error = (code, msg)
296295

296+
def add_configs_changed(self, cfg_names):
297+
cs = [{"name": n, "value": v, "origin": o} for n, v, o in [self._telemetry_entry(n) for n in cfg_names]]
298+
self._app_client_configuration_changed_event(cs)
299+
300+
def _telemetry_entry(self, cfg_name: str) -> Tuple[str, str, _ConfigSource]:
301+
item = config._config[cfg_name]
302+
if cfg_name == "_trace_sample_rate":
303+
name = "trace_sample_rate"
304+
value = str(item.value())
305+
elif cfg_name == "logs_injection":
306+
name = "logs_injection_enabled"
307+
value = "true" if item.value() else "false"
308+
elif cfg_name == "trace_http_header_tags":
309+
name = "trace_header_tags"
310+
value = ",".join(":".join(x) for x in item.value().items())
311+
else:
312+
raise ValueError("Unknown configuration item: %s" % cfg_name)
313+
return name, value, item.source()
314+
297315
def _app_started_event(self, register_app_shutdown=True):
298316
# type: (bool) -> None
299317
"""Sent when TelemetryWriter is enabled or forks"""
@@ -308,6 +326,9 @@ def _app_started_event(self, register_app_shutdown=True):
308326

309327
self.add_configurations(
310328
[
329+
self._telemetry_entry("_trace_sample_rate"),
330+
self._telemetry_entry("logs_injection"),
331+
self._telemetry_entry("trace_http_header_tags"),
311332
(TELEMETRY_TRACING_ENABLED, config._tracing_enabled, "unknown"),
312333
(TELEMETRY_STARTUP_LOGS_ENABLED, config._startup_logs_enabled, "unknown"),
313334
(TELEMETRY_DSM_ENABLED, config._data_streams_enabled, "unknown"),
@@ -324,7 +345,6 @@ def _app_started_event(self, register_app_shutdown=True):
324345
(TELEMETRY_ENABLED, config._telemetry_enabled, "unknown"),
325346
(TELEMETRY_ANALYTICS_ENABLED, config.analytics_enabled, "unknown"),
326347
(TELEMETRY_CLIENT_IP_ENABLED, config.client_ip_header, "unknown"),
327-
(TELEMETRY_LOGS_INJECTION_ENABLED, config.logs_injection, "unknown"),
328348
(TELEMETRY_128_BIT_TRACEID_GENERATION_ENABLED, config._128_bit_trace_id_enabled, "unknown"),
329349
(TELEMETRY_128_BIT_TRACEID_LOGGING_ENABLED, config._128_bit_trace_id_logging_enabled, "unknown"),
330350
(TELEMETRY_TRACE_COMPUTE_STATS, config._trace_compute_stats, "unknown"),
@@ -340,7 +360,6 @@ def _app_started_event(self, register_app_shutdown=True):
340360
(TELEMETRY_RUNTIMEMETRICS_ENABLED, config._runtime_metrics_enabled, "unknown"),
341361
(TELEMETRY_REMOTE_CONFIGURATION_ENABLED, config._remote_config_enabled, "unknown"),
342362
(TELEMETRY_REMOTE_CONFIGURATION_INTERVAL, config._remote_config_poll_interval, "unknown"),
343-
(TELEMETRY_TRACE_SAMPLING_RATE, config._trace_sample_rate, "unknown"),
344363
(TELEMETRY_TRACE_SAMPLING_LIMIT, config._trace_rate_limit, "unknown"),
345364
(TELEMETRY_SPAN_SAMPLING_RULES, config._sampling_rules, "unknown"),
346365
(TELEMETRY_SPAN_SAMPLING_RULES_FILE, config._sampling_rules_file, "unknown"),

ddtrace/settings/config.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ def get_error_ranges(error_range_str):
193193
return error_ranges # type: ignore[return-value]
194194

195195

196-
_ConfigSource = Literal["default", "env", "code", "remote_config"]
196+
_ConfigSource = Literal["default", "env_var", "code", "remote_config"]
197197
_JSONType = Union[None, int, float, str, bool, List["_JSONType"], Dict[str, "_JSONType"]]
198198

199199

@@ -247,7 +247,7 @@ def source(self):
247247
if self._code_value is not None:
248248
return "code"
249249
if self._env_value is not None:
250-
return "env"
250+
return "env_var"
251251
return "default"
252252

253253
def __repr__(self):
@@ -653,14 +653,15 @@ def __setattr__(self, key, value):
653653

654654
def _set_config_items(self, items):
655655
# type: (List[Tuple[str, Any, _ConfigSource]]) -> None
656+
item_names = []
656657
for key, value, origin in items:
658+
item_names.append(key)
657659
self._config[key].set_value_source(value, origin)
658660

659661
from ..internal.telemetry import telemetry_writer
660662

661-
cs = [{"name": k, "value": self._config[k].value(), "origin": self._get_source(k)} for k, _, _ in items]
662-
telemetry_writer.add_event({"configuration": cs}, "app-client-configuration-change")
663-
self._notify_subscribers([i[0] for i in items])
663+
telemetry_writer.add_configs_changed(item_names)
664+
self._notify_subscribers(item_names)
664665

665666
def _reset(self):
666667
# type: () -> None

tests/integration/test_settings.py

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
13
import pytest
24

35
from .test_integration import AGENT_VERSION
@@ -11,6 +13,85 @@ def _get_latest_telemetry_config_item(events, item_name):
1113
return None
1214

1315

16+
@pytest.mark.skipif(AGENT_VERSION != "testagent", reason="Tests only compatible with a testagent")
17+
def test_setting_origin_environment(test_agent_session, run_python_code_in_subprocess):
18+
env = os.environ.copy()
19+
env.update(
20+
{
21+
"DD_TRACE_SAMPLE_RATE": "0.1",
22+
"DD_LOGS_INJECTION": "true",
23+
"DD_TRACE_HEADER_TAGS": "X-Header-Tag-1:header_tag_1,X-Header-Tag-2:header_tag_2",
24+
}
25+
)
26+
out, err, status, _ = run_python_code_in_subprocess(
27+
"""
28+
from ddtrace import config, tracer
29+
with tracer.trace("test") as span:
30+
pass
31+
""",
32+
env=env,
33+
)
34+
assert status == 0, err
35+
36+
events = test_agent_session.get_events()
37+
assert _get_latest_telemetry_config_item(events, "trace_sample_rate") == {
38+
"name": "trace_sample_rate",
39+
"value": "0.1",
40+
"origin": "env_var",
41+
}
42+
assert _get_latest_telemetry_config_item(events, "logs_injection_enabled") == {
43+
"name": "logs_injection_enabled",
44+
"value": "true",
45+
"origin": "env_var",
46+
}
47+
assert _get_latest_telemetry_config_item(events, "trace_header_tags") == {
48+
"name": "trace_header_tags",
49+
"value": "X-Header-Tag-1:header_tag_1,X-Header-Tag-2:header_tag_2",
50+
"origin": "env_var",
51+
}
52+
53+
54+
@pytest.mark.skipif(AGENT_VERSION != "testagent", reason="Tests only compatible with a testagent")
55+
def test_setting_origin_code(test_agent_session, run_python_code_in_subprocess):
56+
env = os.environ.copy()
57+
env.update(
58+
{
59+
"DD_TRACE_SAMPLE_RATE": "0.1",
60+
"DD_LOGS_INJECTION": "true",
61+
"DD_TRACE_HEADER_TAGS": "X-Header-Tag-1:header_tag_1,X-Header-Tag-2:header_tag_2",
62+
}
63+
)
64+
out, err, status, _ = run_python_code_in_subprocess(
65+
"""
66+
from ddtrace import config, tracer
67+
config._trace_sample_rate = 0.2
68+
config.logs_injection = False
69+
config.trace_http_header_tags = {"header": "value"}
70+
with tracer.trace("test") as span:
71+
pass
72+
""",
73+
env=env,
74+
)
75+
assert status == 0, err
76+
77+
events = test_agent_session.get_events()
78+
assert _get_latest_telemetry_config_item(events, "trace_sample_rate") == {
79+
"name": "trace_sample_rate",
80+
"value": "0.2",
81+
"origin": "code",
82+
}
83+
assert _get_latest_telemetry_config_item(events, "logs_injection_enabled") == {
84+
"name": "logs_injection_enabled",
85+
"value": "false",
86+
"origin": "code",
87+
}
88+
assert _get_latest_telemetry_config_item(events, "trace_header_tags") == {
89+
"name": "trace_header_tags",
90+
"value": "header:value",
91+
"origin": "code",
92+
}
93+
94+
1495
@pytest.mark.skipif(AGENT_VERSION != "testagent", reason="Tests only compatible with a testagent")
1596
def test_remoteconfig_sampling_rate_default(test_agent_session, run_python_code_in_subprocess):
1697
out, err, status, _ = run_python_code_in_subprocess(
@@ -46,9 +127,9 @@ def test_remoteconfig_sampling_rate_default(test_agent_session, run_python_code_
46127
assert status == 0, err
47128

48129
events = test_agent_session.get_events()
49-
assert _get_latest_telemetry_config_item(events, "_trace_sample_rate") == {
50-
"name": "_trace_sample_rate",
51-
"value": 1.0,
130+
assert _get_latest_telemetry_config_item(events, "trace_sample_rate") == {
131+
"name": "trace_sample_rate",
132+
"value": "1.0",
52133
"origin": "default",
53134
}
54135

@@ -69,8 +150,8 @@ def test_remoteconfig_sampling_rate_telemetry(test_agent_session, run_python_cod
69150
assert status == 0, err
70151

71152
events = test_agent_session.get_events()
72-
assert _get_latest_telemetry_config_item(events, "_trace_sample_rate") == {
73-
"name": "_trace_sample_rate",
74-
"value": 0.5,
153+
assert _get_latest_telemetry_config_item(events, "trace_sample_rate") == {
154+
"name": "trace_sample_rate",
155+
"value": "0.5",
75156
"origin": "remote_config",
76157
}

tests/internal/test_settings.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def _base_rc_config(cfg):
5050
{
5151
"env": {"DD_TRACE_SAMPLE_RATE": "0.9"},
5252
"expected": {"_trace_sample_rate": 0.9},
53-
"expected_source": {"_trace_sample_rate": "env"},
53+
"expected_source": {"_trace_sample_rate": "env_var"},
5454
},
5555
{
5656
"env": {"DD_TRACE_SAMPLE_RATE": "0.9"},
@@ -68,7 +68,7 @@ def _base_rc_config(cfg):
6868
{
6969
"env": {"DD_LOGS_INJECTION": "true"},
7070
"expected": {"logs_injection": True},
71-
"expected_source": {"logs_injection": "env"},
71+
"expected_source": {"logs_injection": "env_var"},
7272
},
7373
{
7474
"env": {"DD_LOGS_INJECTION": "true"},
@@ -81,7 +81,7 @@ def _base_rc_config(cfg):
8181
"expected": {
8282
"trace_http_header_tags": {"X-Header-Tag-1": "header_tag_1", "X-Header-Tag-2": "header_tag_2"}
8383
},
84-
"expected_source": {"trace_http_header_tags": "env"},
84+
"expected_source": {"trace_http_header_tags": "env_var"},
8585
},
8686
{
8787
"env": {"DD_TRACE_HEADER_TAGS": "X-Header-Tag-1:header_tag_1,X-Header-Tag-2:header_tag_2"},

0 commit comments

Comments
 (0)