diff --git a/.github/workflows/system-tests.yml b/.github/workflows/system-tests.yml index 4f9b83668e4..1535fd3ef1b 100644 --- a/.github/workflows/system-tests.yml +++ b/.github/workflows/system-tests.yml @@ -42,7 +42,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '13b08f0faf7673656bd68dad5c142ad04eddee69' + ref: 'e79a35571dedbe2e9bc5f4731a3cf172f589b032' - name: Checkout dd-trace-py uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 @@ -96,7 +96,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '13b08f0faf7673656bd68dad5c142ad04eddee69' + ref: 'e79a35571dedbe2e9bc5f4731a3cf172f589b032' - name: Build runner uses: ./.github/actions/install_runner @@ -277,7 +277,7 @@ jobs: persist-credentials: false repository: 'DataDog/system-tests' # Automatically managed, use scripts/update-system-tests-version to update - ref: '13b08f0faf7673656bd68dad5c142ad04eddee69' + ref: 'e79a35571dedbe2e9bc5f4731a3cf172f589b032' - name: Checkout dd-trace-py uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: diff --git a/ddtrace/_logger.py b/ddtrace/_logger.py index 77123abd582..f87b837078f 100644 --- a/ddtrace/_logger.py +++ b/ddtrace/_logger.py @@ -20,6 +20,7 @@ class LogInjectionState(object): # Log injection is enabled, but not yet configured ENABLED = "true" # Log injection is enabled and configured for structured logging + # This value is deprecated, but kept for backwards compatibility STRUCTURED = "structured" @@ -108,17 +109,15 @@ def set_log_formatting(): handler.setFormatter(logging.Formatter(DD_LOG_FORMAT)) -def get_log_injection_state(raw_config: Optional[str]) -> str: +def get_log_injection_state(raw_config: Optional[str]) -> bool: """Returns the current log injection state.""" if raw_config: normalized = raw_config.lower().strip() - if normalized == LogInjectionState.STRUCTURED: - return LogInjectionState.STRUCTURED - elif normalized in ("true", "1"): - return LogInjectionState.ENABLED + if normalized == LogInjectionState.STRUCTURED or normalized in ("true", "1"): + return True elif normalized not in ("false", "0"): logging.warning( "Invalid log injection state '%s'. Expected 'true', 'false', or 'structured'. Defaulting to 'false'.", normalized, ) - return LogInjectionState.DISABLED + return False diff --git a/ddtrace/_trace/product.py b/ddtrace/_trace/product.py index e7b5d1b0e4c..c59c692510e 100644 --- a/ddtrace/_trace/product.py +++ b/ddtrace/_trace/product.py @@ -215,7 +215,7 @@ def apm_tracing_rc(lib_config, dd_config): new_rc_configs["_trace_sampling_rules"] = trace_sampling_rules if "log_injection_enabled" in lib_config: - new_rc_configs["_logs_injection"] = str(lib_config["log_injection_enabled"]).lower() + new_rc_configs["_logs_injection"] = lib_config["log_injection_enabled"] if "tracing_tags" in lib_config: tags = lib_config["tracing_tags"] diff --git a/ddtrace/contrib/internal/logbook/patch.py b/ddtrace/contrib/internal/logbook/patch.py index a5c1f7141a7..c84675a3258 100644 --- a/ddtrace/contrib/internal/logbook/patch.py +++ b/ddtrace/contrib/internal/logbook/patch.py @@ -5,7 +5,6 @@ import ddtrace from ddtrace import config -from ddtrace._logger import LogInjectionState from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.internal.utils import get_argument_value @@ -27,7 +26,7 @@ def _supported_versions() -> Dict[str, str]: def _w_process_record(func, instance, args, kwargs): # patch logger to include datadog info before logging - if config._logs_injection != LogInjectionState.DISABLED: + if config._logs_injection: record = get_argument_value(args, kwargs, 0, "record") record.extra.update(ddtrace.tracer.get_log_correlation_context()) return func(*args, **kwargs) diff --git a/ddtrace/contrib/internal/logging/patch.py b/ddtrace/contrib/internal/logging/patch.py index a491216a60c..a68f0f6e4e8 100644 --- a/ddtrace/contrib/internal/logging/patch.py +++ b/ddtrace/contrib/internal/logging/patch.py @@ -5,7 +5,6 @@ import ddtrace from ddtrace import config -from ddtrace._logger import LogInjectionState from ddtrace._logger import set_log_formatting from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.internal.constants import LOG_ATTR_ENV @@ -54,15 +53,13 @@ def __init__(self, trace_id: int, span_id: int, service: str, version: str, env: def _w_makeRecord(func, instance, args, kwargs): # Get the LogRecord instance for this log record = func(*args, **kwargs) - if config._logs_injection == LogInjectionState.DISABLED: - # log injection is opt-in for non-structured logging - return record - record.__dict__.update(ddtrace.tracer.get_log_correlation_context()) + if config._logs_injection: + record.__dict__.update(ddtrace.tracer.get_log_correlation_context()) return record def _w_StrFormatStyle_format(func, instance, args, kwargs): - if config._logs_injection != LogInjectionState.ENABLED: + if not config._logs_injection: return func(*args, **kwargs) # The format string "dd.service={dd.service}" expects # the record to have a "dd" property which is an object that @@ -103,7 +100,7 @@ def patch(): _w(logging.Logger, "makeRecord", _w_makeRecord) _w(logging.StrFormatStyle, "_format", _w_StrFormatStyle_format) - if config._logs_injection == LogInjectionState.ENABLED: + if config._logs_injection: # Only set the formatter is DD_LOGS_INJECTION is set to True. We do not want to modify # unstructured logs if a user has not enabled logs injection. # Also, the Datadog log format must be set after the logging module has been patched, diff --git a/ddtrace/contrib/internal/loguru/patch.py b/ddtrace/contrib/internal/loguru/patch.py index 3ed411bd685..32e316dd18e 100644 --- a/ddtrace/contrib/internal/loguru/patch.py +++ b/ddtrace/contrib/internal/loguru/patch.py @@ -5,7 +5,6 @@ import ddtrace from ddtrace import config -from ddtrace._logger import LogInjectionState from ddtrace.contrib.internal.trace_utils import unwrap as _u @@ -25,12 +24,11 @@ def _supported_versions() -> Dict[str, str]: def _tracer_injection(event_dict): - if config._logs_injection == LogInjectionState.DISABLED: + if not config._logs_injection: # log injection is opt-out for structured logging return event_dict event_dd_attributes = ddtrace.tracer.get_log_correlation_context() event_dict.update(event_dd_attributes) - return event_dd_attributes diff --git a/ddtrace/contrib/internal/structlog/patch.py b/ddtrace/contrib/internal/structlog/patch.py index 9732e69cc5b..9ae46f639f0 100644 --- a/ddtrace/contrib/internal/structlog/patch.py +++ b/ddtrace/contrib/internal/structlog/patch.py @@ -4,7 +4,6 @@ import ddtrace from ddtrace import config -from ddtrace._logger import LogInjectionState from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.contrib.internal.trace_utils import wrap as _w from ddtrace.internal.utils import get_argument_value @@ -27,9 +26,8 @@ def _supported_versions() -> Dict[str, str]: def _tracer_injection(_, __, event_dict): - if config._logs_injection == LogInjectionState.DISABLED: - return event_dict - event_dict.update(ddtrace.tracer.get_log_correlation_context()) + if config._logs_injection: + event_dict.update(ddtrace.tracer.get_log_correlation_context()) return event_dict diff --git a/ddtrace/settings/_config.py b/ddtrace/settings/_config.py index 53c2822fc56..64417322ec3 100644 --- a/ddtrace/settings/_config.py +++ b/ddtrace/settings/_config.py @@ -17,7 +17,6 @@ from ddtrace.internal.telemetry import validate_otel_envs from ddtrace.internal.utils.cache import cachedmethod -from .._logger import LogInjectionState from .._logger import get_log_injection_state from ..internal import gitmetadata from ..internal.constants import _PROPAGATION_BEHAVIOR_DEFAULT @@ -378,7 +377,7 @@ def _default_config() -> Dict[str, _ConfigItem]: modifier=str, ), "_logs_injection": _ConfigItem( - default=LogInjectionState.STRUCTURED, + default=True, envs=["DD_LOGS_INJECTION"], modifier=get_log_injection_state, ), diff --git a/tests/commands/test_runner.py b/tests/commands/test_runner.py index 815c377e1c0..25f29d0a871 100644 --- a/tests/commands/test_runner.py +++ b/tests/commands/test_runner.py @@ -330,7 +330,7 @@ def test_info_no_configs(): b"Application Security enabled: False", b"Remote Configuration enabled: False", b"Debug logging: False", - b"Log injection enabled: structured", + b"Log injection enabled: True", b"Health metrics enabled: False", b"Partial flushing enabled: True", b"Partial flush minimum number of spans: 300", @@ -386,7 +386,7 @@ def test_info_w_configs(): b"Remote Configuration enabled: True", b"IAST enabled (experimental)", b"Debug logging: True", - b"Log injection enabled: true", + b"Log injection enabled: True", b"Health metrics enabled: False", b"Partial flushing enabled: True", b"Partial flush minimum number of spans: 1000", diff --git a/tests/contrib/logging/test_logging.py b/tests/contrib/logging/test_logging.py index db648738af8..afb5b27e4a1 100644 --- a/tests/contrib/logging/test_logging.py +++ b/tests/contrib/logging/test_logging.py @@ -303,7 +303,7 @@ def test_log_strformat_style_format(self): assert getattr(record, LOG_ATTR_SPAN_ID) == str(span.span_id) -@pytest.mark.parametrize("dd_logs_enabled", ["true", "false", "structured"]) +@pytest.mark.parametrize("dd_logs_enabled", ["true", "structured"]) def test_manual_log_formatter_injection(dd_logs_enabled: str, run_python_code_in_subprocess): code = """ import ddtrace.auto @@ -321,7 +321,7 @@ def test_manual_log_formatter_injection(dd_logs_enabled: str, run_python_code_in """ env = os.environ.copy() - env["DD_LOGS_ENABLED"] = dd_logs_enabled + env["DD_LOGS_INJECTION"] = dd_logs_enabled stdout, stderr, status, _ = run_python_code_in_subprocess(code, env=env) assert status == 0, stderr diff --git a/tests/contrib/logging/test_tracer_logging.py b/tests/contrib/logging/test_tracer_logging.py index 47b49855340..039429af022 100644 --- a/tests/contrib/logging/test_tracer_logging.py +++ b/tests/contrib/logging/test_tracer_logging.py @@ -5,7 +5,11 @@ # example: '2022-06-10 21:49:26,010 CRITICAL [ddtrace] [test.py:15] - ddtrace critical log\n' -LOG_PATTERN = r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \w{1,} \[\S{1,}\] \[\w{1,}.\w{2}:\d{1,}\] - .{1,}$" +# example: '2025-07-16 16:27:02,708 CRITICAL [ddtrace] [test.py:8] [dd.service=ddtrace_subprocess_dir +# dd.env= dd.version= dd.trace_id=0 dd.span_id=0] - ddtrace critical log\n' +LOG_PATTERN = ( + r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3} \w{1,} \[\S{1,}\] \[\w{1,}\.\w{2}:\d{1,}\]( \[.*\])? - .{1,}$" +) def assert_log_files(test_directory, test_log_file, total_file_count): @@ -246,7 +250,7 @@ def test_warn_logs_can_go_to_file(run_python_code_in_subprocess, ddtrace_run_pyt ]: out, err, status, pid = run_in_subprocess(code, env=env) assert status == 0, err - assert err == b"warning log\n", err.decode() + assert b"warning log\n" in err, err.decode() assert out == b"", out.decode() with open(log_file) as file: first_line = file.readline() diff --git a/tests/contrib/starlette/test_starlette.py b/tests/contrib/starlette/test_starlette.py index 716712e2a29..5759a5d57fa 100644 --- a/tests/contrib/starlette/test_starlette.py +++ b/tests/contrib/starlette/test_starlette.py @@ -525,7 +525,7 @@ def test_incorrect_patching(run_python_code_in_subprocess): out, err, status, _ = run_python_code_in_subprocess(code) assert status == 0, err assert out == b"", err - assert err == b"datadog context not present in ASGI request scope, trace middleware may be missing\n" + assert b"datadog context not present in ASGI request scope, trace middleware may be missing\n" in err, err # Ignoring span link attributes until values are diff --git a/tests/integration/test_debug.py b/tests/integration/test_debug.py index e2cf44606d8..0cd29b112d1 100644 --- a/tests/integration/test_debug.py +++ b/tests/integration/test_debug.py @@ -82,7 +82,7 @@ def test_standard_tags(): assert f.get("dd_version") == "" assert f.get("debug") is False assert f.get("enabled_cli") is False - assert f.get("log_injection_enabled") == "structured" + assert f.get("log_injection_enabled") is True assert f.get("health_metrics_enabled") is False assert f.get("runtime_metrics_enabled") is False assert f.get("sampling_rules") == [] @@ -127,7 +127,7 @@ def test_env_config(self): f = debug.collect(ddtrace.tracer) assert f.get("agent_url") == "http://0.0.0.0:4321" assert f.get("health_metrics_enabled") is True - assert f.get("log_injection_enabled") == "true" + assert f.get("log_injection_enabled") is True assert f.get("env") == "prod" assert f.get("dd_version") == "123456" assert f.get("service") == "service" diff --git a/tests/integration/test_settings.py b/tests/integration/test_settings.py index 9a9ad3f8338..3a0f876df28 100644 --- a/tests/integration/test_settings.py +++ b/tests/integration/test_settings.py @@ -48,7 +48,7 @@ def test_setting_origin_environment(test_agent_session, run_python_code_in_subpr } in events_trace_sample_rate events_logs_injection_enabled = _get_telemetry_config_items(events, "DD_LOGS_INJECTION") - assert {"name": "DD_LOGS_INJECTION", "value": "true", "origin": "env_var"} in events_logs_injection_enabled + assert {"name": "DD_LOGS_INJECTION", "value": True, "origin": "env_var"} in events_logs_injection_enabled events_trace_header_tags = _get_telemetry_config_items(events, "DD_TRACE_HEADER_TAGS") assert { @@ -80,7 +80,7 @@ def test_setting_origin_code(test_agent_session, run_python_code_in_subprocess): """ from ddtrace import config, tracer -config._logs_injection = "false" +config._logs_injection = False config._trace_http_header_tags = {"header": "value"} config.tags = {"header": "value"} config._tracing_enabled = False @@ -98,7 +98,7 @@ def test_setting_origin_code(test_agent_session, run_python_code_in_subprocess): events_logs_injection_enabled = _get_telemetry_config_items(events, "DD_LOGS_INJECTION") assert { "name": "DD_LOGS_INJECTION", - "value": "false", + "value": False, "origin": "code", } in events_logs_injection_enabled diff --git a/tests/internal/test_settings.py b/tests/internal/test_settings.py index 0377c17f95d..ac4213c251b 100644 --- a/tests/internal/test_settings.py +++ b/tests/internal/test_settings.py @@ -74,7 +74,7 @@ def call_apm_tracing_rc(payloads: Sequence[Payload], g_config): { "expected": { "_trace_sampling_rules": "", - "_logs_injection": "structured", + "_logs_injection": True, "_trace_http_header_tags": {}, }, "expected_source": { @@ -116,8 +116,8 @@ def call_apm_tracing_rc(payloads: Sequence[Payload], g_config): "expected_source": {"_trace_sampling_rules": "remote_config"}, }, { - "env": {"DD_LOGS_INJECTION": "true"}, - "expected": {"_logs_injection": "true"}, + "env": {"DD_LOGS_INJECTION": "false"}, + "expected": {"_logs_injection": False}, "expected_source": {"_logs_injection": "env_var"}, }, { @@ -706,7 +706,7 @@ def test_remoteconfig_debug_logging(): "dd tags, tracing enablement, and HTTP header tags.\nConfigs on startup: " "sampling_rules: %s, logs_injection: %s, tags: %s, tracing_enabled: %s, trace_http_header_tags: %s", "", - "structured", + True, {}, True, {}, @@ -721,7 +721,7 @@ def test_remoteconfig_debug_logging(): mock.call( "Updated HTTP header tags configuration via remote_config: %s", {"x-header-tag-420": "header_tag_420"} ), - mock.call("Updated logs injection configuration via remote_config: %s", "false"), + mock.call("Updated logs injection configuration via remote_config: %s", False), # 3 payloads should be received generating 3 debug logs. mock.call( "APM Tracing Received: %s from the Agent", diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index c49218c29e6..4eae073e993 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -190,7 +190,7 @@ def test_app_started_event(telemetry_writer, test_agent_session, mock_time): {"name": "trace_sample_rate", "origin": "default", "value": "1.0"}, {"name": "trace_sampling_rules", "origin": "default", "value": ""}, {"name": "trace_header_tags", "origin": "default", "value": ""}, - {"name": "logs_injection_enabled", "origin": "default", "value": "structured"}, + {"name": "logs_injection_enabled", "origin": "default", "value": True}, {"name": "trace_tags", "origin": "default", "value": ""}, {"name": "trace_enabled", "origin": "default", "value": "true"}, {"name": "instrumentation_config_id", "origin": "default", "value": ""}, @@ -419,7 +419,7 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_LLMOBS_INSTRUMENTED_PROXY_URLS", "origin": "default", "value": None}, {"name": "DD_LLMOBS_ML_APP", "origin": "default", "value": None}, {"name": "DD_LLMOBS_SAMPLE_RATE", "origin": "default", "value": 1.0}, - {"name": "DD_LOGS_INJECTION", "origin": "env_var", "value": "true"}, + {"name": "DD_LOGS_INJECTION", "origin": "env_var", "value": True}, {"name": "DD_METRICS_OTEL_ENABLED", "origin": "default", "value": False}, {"name": "DD_PROFILING_AGENTLESS", "origin": "default", "value": False}, {"name": "DD_PROFILING_API_TIMEOUT", "origin": "default", "value": 10.0}, diff --git a/tests/tracer/test_settings.py b/tests/tracer/test_settings.py index db16cfb19f2..f6da2c95c2f 100644 --- a/tests/tracer/test_settings.py +++ b/tests/tracer/test_settings.py @@ -2,7 +2,6 @@ import pytest -from ddtrace._logger import LogInjectionState from ddtrace.settings import HttpConfig from ddtrace.settings import IntegrationConfig from ddtrace.settings._config import Config @@ -14,20 +13,24 @@ class TestConfig(BaseTestCase): def test_logs_injection(self): with self.override_env(dict(DD_LOGS_INJECTION="True")): config = Config() - self.assertEqual(config._logs_injection, LogInjectionState.ENABLED) + self.assertEqual(config._logs_injection, True) with self.override_env(dict(DD_LOGS_INJECTION="FALSE")): config = Config() - self.assertEqual(config._logs_injection, LogInjectionState.DISABLED) + self.assertEqual(config._logs_injection, False) + + with self.override_env(dict(DD_LOGS_INJECTION="structured")): + config = Config() + self.assertEqual(config._logs_injection, True) with self.override_env(dict(), replace_os_env=True): config = Config() - self.assertEqual(config._logs_injection, LogInjectionState.STRUCTURED) + self.assertEqual(config._logs_injection, True) with self.override_env(dict(DD_LOGS_INJECTION="nonsense")): # If the value is not recognized, it should default to DISABLED config = Config() - self.assertEqual(config._logs_injection, LogInjectionState.DISABLED) + self.assertEqual(config._logs_injection, False) def test_service(self): # If none is provided the default should be ``None`` diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index 60061984f2f..740e2f4818e 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -1091,8 +1091,6 @@ def test_enable(): ) def test_unfinished_span_warning_log(): """Test that a warning log is emitted when the tracer is shut down with unfinished spans.""" - import ddtrace.auto # noqa - from ddtrace.constants import MANUAL_KEEP_KEY from ddtrace.trace import tracer