Skip to content

Commit e792a92

Browse files
committed
refactor: introduce enabled product interface method
We extend the product plugin interface with a new mandatory `enabled` function. This is used to determine whether a product is enabled and needs its lifecycle managed by the product plugin protocol. The main reason behind this change is to fix an issue with configuration telemetry reporting whereby products are reported as activated simply when they are started, but not necessarily enabled. This change now moves the responsibility for checking enablement to the plugin protocol, which can then make a more accurate configuration telemetry reporting when it comes to product enablement. This also removes some boilerplate code in product plugin implementations by not requiring them to include an explicit enablement check.
1 parent e848afc commit e792a92

File tree

14 files changed

+128
-110
lines changed

14 files changed

+128
-110
lines changed

ddtrace/_trace/product.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,17 @@ def post_preload():
3636
_patch_all(**modules_to_bool)
3737

3838

39+
def enabled():
40+
return _config.enabled
41+
42+
3943
def start():
40-
if _config.enabled:
41-
from ddtrace.internal.settings._config import config
44+
from ddtrace.internal.settings._config import config
4245

43-
if config._trace_methods:
44-
from ddtrace.internal.tracemethods import _install_trace_methods
46+
if config._trace_methods:
47+
from ddtrace.internal.tracemethods import _install_trace_methods
4548

46-
_install_trace_methods(config._trace_methods)
49+
_install_trace_methods(config._trace_methods)
4750

4851

4952
def restart(join=False):

ddtrace/debugging/_products/code_origin/span.py

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,50 +20,42 @@
2020
# requires = ["tracer"]
2121

2222

23-
def post_preload() -> None:
24-
pass
23+
# We need to instrument the entrypoints on boot because this is the only
24+
# time the tracer will notify us of entrypoints being registered.
25+
@partial(core.on, "service_entrypoint.patch")
26+
def _(f: t.Union[FunctionType, MethodType]) -> None:
27+
from ddtrace.debugging._origin.span import SpanCodeOriginProcessorEntry
2528

29+
SpanCodeOriginProcessorEntry.instrument_view(f)
2630

27-
def _start() -> None:
28-
from ddtrace.debugging._origin.span import SpanCodeOriginProcessorEntry
2931

30-
SpanCodeOriginProcessorEntry.enable()
32+
def post_preload() -> None:
33+
pass
3134

3235

3336
def start() -> None:
34-
# We need to instrument the entrypoints on boot because this is the only
35-
# time the tracer will notify us of entrypoints being registered.
36-
@partial(core.on, "service_entrypoint.patch")
37-
def _(f: t.Union[FunctionType, MethodType]) -> None:
38-
from ddtrace.debugging._origin.span import SpanCodeOriginProcessorEntry
37+
from ddtrace.debugging._origin.span import SpanCodeOriginProcessorEntry
3938

40-
SpanCodeOriginProcessorEntry.instrument_view(f)
39+
SpanCodeOriginProcessorEntry.enable()
4140

42-
log.debug("Registered entrypoint patching hook for code origin for spans")
4341

42+
def enabled() -> bool:
4443
# If dynamic instrumentation is enabled, and code origin for spans is not explicitly disabled,
4544
# we'll enable code origin for spans.
4645
di_enabled = product_manager.is_enabled(DI_PRODUCT_KEY) and config.value_source(CO_ENABLED) == ValueSource.DEFAULT
47-
if config.span.enabled or di_enabled:
48-
_start()
46+
return config.span.enabled or di_enabled
4947

5048

5149
def restart(join: bool = False) -> None:
5250
pass
5351

5452

55-
def _stop() -> None:
53+
def stop(join: bool = False) -> None:
5654
from ddtrace.debugging._origin.span import SpanCodeOriginProcessorEntry
5755

5856
SpanCodeOriginProcessorEntry.disable()
5957

6058

61-
def stop(join: bool = False) -> None:
62-
di_enabled = product_manager.is_enabled(DI_PRODUCT_KEY) and config.value_source(CO_ENABLED) == ValueSource.DEFAULT
63-
if config.span.enabled or di_enabled:
64-
_stop()
65-
66-
6759
def at_exit(join: bool = False) -> None:
6860
stop(join=join)
6961

@@ -75,4 +67,4 @@ class APMCapabilities(enum.IntFlag):
7567
def apm_tracing_rc(lib_config: t.Any, _config: t.Any) -> None:
7668
if (enabled := lib_config.get("code_origin_enabled")) is not None:
7769
should_start = (config.span.spec.enabled.full_name not in config.source or config.span.enabled) and enabled
78-
_start() if should_start else _stop()
70+
start() if should_start else stop()
Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,30 @@
11
import enum
22
from typing import Any
33

4+
from ddtrace.debugging._import import DebuggerModuleWatchdog
45
from ddtrace.internal.settings.dynamic_instrumentation import config
56

67

8+
# We need to install this on start-up because if DI gets enabled remotely
9+
# we won't be able to capture many of the code objects from the modules
10+
# that are already loaded.
11+
DebuggerModuleWatchdog.install()
12+
713
requires = ["remote-configuration"]
814

915

1016
def post_preload() -> None:
1117
pass
1218

1319

14-
def _start() -> None:
20+
def start() -> None:
1521
from ddtrace.debugging import DynamicInstrumentation
1622

1723
DynamicInstrumentation.enable()
1824

1925

20-
def start() -> None:
21-
from ddtrace.debugging._import import DebuggerModuleWatchdog
22-
23-
# We need to install this on start-up because if DI gets enabled remotely
24-
# we won't be able to capture many of the code objects from the modules
25-
# that are already loaded.
26-
DebuggerModuleWatchdog.install()
27-
28-
if config.enabled:
29-
_start()
26+
def enabled() -> bool:
27+
return config.enabled
3028

3129

3230
def before_fork() -> None:
@@ -40,10 +38,9 @@ def restart(join: bool = False) -> None:
4038

4139

4240
def stop(join: bool = False) -> None:
43-
if config.enabled:
44-
from ddtrace.debugging import DynamicInstrumentation
41+
from ddtrace.debugging import DynamicInstrumentation
4542

46-
DynamicInstrumentation.disable(join=join)
43+
DynamicInstrumentation.disable(join=join)
4744

4845

4946
def at_exit(join: bool = False) -> None:
@@ -57,4 +54,4 @@ class APMCapabilities(enum.IntFlag):
5754
def apm_tracing_rc(lib_config: Any, _config: Any) -> None:
5855
if (enabled := lib_config.get("dynamic_instrumentation_enabled")) is not None:
5956
should_start = (config.spec.enabled.full_name not in config.source or config.enabled) and enabled
60-
_start() if should_start else stop()
57+
start() if should_start else stop()

ddtrace/debugging/_products/exception_replay.py

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,26 @@ def post_preload() -> None:
1212
pass
1313

1414

15-
def _start() -> None:
15+
def start() -> None:
1616
from ddtrace.debugging._exception.replay import SpanExceptionHandler
1717

1818
SpanExceptionHandler.enable()
1919

2020

21-
def start() -> None:
22-
if config.enabled:
23-
_start()
21+
def enabled() -> bool:
22+
return config.enabled
2423

2524

2625
def restart(join: bool = False) -> None:
2726
pass
2827

2928

30-
def _stop() -> None:
29+
def stop(join: bool = False) -> None:
3130
from ddtrace.debugging._exception.replay import SpanExceptionHandler
3231

3332
SpanExceptionHandler.disable()
3433

3534

36-
def stop(join: bool = False) -> None:
37-
if config.enabled:
38-
_stop()
39-
40-
4135
def at_exit(join: bool = False) -> None:
4236
stop(join=join)
4337

@@ -49,4 +43,4 @@ class APMCapabilities(enum.IntFlag):
4943
def apm_tracing_rc(lib_config: Any, _config: Any) -> None:
5044
if (enabled := lib_config.get("exception_replay_enabled")) is not None:
5145
should_start = (config.spec.enabled.full_name not in config.source or config.enabled) and enabled
52-
_start() if should_start else _stop()
46+
start() if should_start else stop()

ddtrace/debugging/_products/live_debugger.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,24 @@ def post_preload() -> None:
99
pass
1010

1111

12+
def enabled() -> bool:
13+
return config.enabled
14+
15+
1216
def start() -> None:
13-
if config.enabled:
14-
from ddtrace.debugging._live import enable
17+
from ddtrace.debugging._live import enable
1518

16-
enable()
19+
enable()
1720

1821

1922
def restart(join: bool = False) -> None:
2023
pass
2124

2225

2326
def stop(join: bool = False) -> None:
24-
if config.enabled:
25-
from ddtrace.debugging._live import disable
27+
from ddtrace.debugging._live import disable
2628

27-
disable()
29+
disable()
2830

2931

3032
def at_exit(join: bool = False) -> None:

ddtrace/errortracking/product.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,24 @@ def post_preload():
1212
pass
1313

1414

15+
def enabled() -> bool:
16+
return config.enabled
17+
18+
1519
def start() -> None:
16-
if config.enabled:
17-
from ddtrace.errortracking._handled_exceptions.collector import HandledExceptionCollector
20+
from ddtrace.errortracking._handled_exceptions.collector import HandledExceptionCollector
1821

19-
HandledExceptionCollector.enable()
22+
HandledExceptionCollector.enable()
2023

2124

2225
def restart(join: bool = False) -> None:
2326
pass
2427

2528

2629
def stop(join: bool = False):
27-
if config.enabled:
28-
from ddtrace.errortracking._handled_exceptions.collector import HandledExceptionCollector
30+
from ddtrace.errortracking._handled_exceptions.collector import HandledExceptionCollector
2931

30-
HandledExceptionCollector.disable()
32+
HandledExceptionCollector.disable()
3133

3234

3335
def at_exit(join: bool = False):

ddtrace/internal/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ object that implements the product protocol. This consists of a Python object
1515

1616
| Attribute | Description |
1717
|-----------|-------------|
18-
| `start() -> None` | A function with the logic required to start the product |
18+
| `post_preload() -> None` | A function with the logic required to finish initialization after the library preload stage |
19+
| `enabled() -> bool` | A function that returns whether the product should be started; called before `start()` by the product manager |
20+
| `start() -> None` | A function with the logic required to enable the product (called only when `enabled()` returns `True`) |
1921
| `restart(join: bool = False) -> None` | A function with the logic required to restart the product after a fork |
2022
| `stop(join: bool = False) -> None` | A function with the logic required to stop the product |
2123
| `at_exit(join: bool = False) -> None` | A function with the logic required to stop the product at exit |
22-
| `post_preload() -> None` | A function with the logic required to finish initialization after the library preload stage |
2324

2425
The product object needs to be made available to the Python plugin system by
2526
defining an entry point in the `project.entry-points.'ddtrace.products'` section

ddtrace/internal/appsec/product.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ def post_preload():
99
pass
1010

1111

12+
def enabled():
13+
return (
14+
config._asm_enabled or config._asm_can_be_enabled or config._asm_rc_enabled or ai_guard_config._ai_guard_enabled
15+
)
16+
17+
1218
def start():
1319
if config._asm_enabled or config._asm_can_be_enabled:
1420
from ddtrace.appsec._listeners import load_common_appsec_modules

ddtrace/internal/iast/product.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,20 @@ def post_preload():
9191
# Dropping inspect causes: "unexpected object <Signature> in __signature__ attribute"
9292

9393

94+
def enabled():
95+
"""
96+
Return whether the IAST product is enabled.
97+
"""
98+
return asm_config._iast_enabled
99+
100+
94101
def start():
95102
"""
96103
Start the IAST product.
97104
"""
98-
if asm_config._iast_enabled:
99-
from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor
105+
from ddtrace.appsec._iast.processor import AppSecIastSpanProcessor
100106

101-
AppSecIastSpanProcessor.enable()
107+
AppSecIastSpanProcessor.enable()
102108

103109

104110
def restart(join=False):

ddtrace/internal/openfeature/product.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,24 @@ def post_preload():
88
pass
99

1010

11+
def enabled():
12+
return ffe_config.experimental_flagging_provider_enabled
13+
14+
1115
def start():
12-
if ffe_config.experimental_flagging_provider_enabled:
13-
from ddtrace.internal.openfeature._remoteconfiguration import enable_featureflags_rc
16+
from ddtrace.internal.openfeature._remoteconfiguration import enable_featureflags_rc
1417

15-
enable_featureflags_rc()
18+
enable_featureflags_rc()
1619

1720

1821
def restart(join=False):
1922
pass
2023

2124

2225
def stop(join=False):
23-
if ffe_config.experimental_flagging_provider_enabled:
24-
from ddtrace.internal.openfeature._remoteconfiguration import disable_featureflags_rc
26+
from ddtrace.internal.openfeature._remoteconfiguration import disable_featureflags_rc
2527

26-
disable_featureflags_rc()
28+
disable_featureflags_rc()
2729

2830

2931
def at_exit(join=False):

0 commit comments

Comments
 (0)