From dc3078467a7348e23e32c534c5cb61f375e1a8d6 Mon Sep 17 00:00:00 2001 From: Munir Abdinur Date: Mon, 3 Feb 2025 16:46:12 -0500 Subject: [PATCH] chore(tracing): ensure ddtrace.trace.Tracer() is not reinitialized allow configuring dummy writers for civis fix broken tests update tracer fix stuff try to fix pytest address failues --- ddtrace/_trace/tracer.py | 12 +- ...-multitracer-support-ec109486f71c9c62.yaml | 6 + tests/ci_visibility/test_ci_visibility.py | 7 +- tests/ci_visibility/util.py | 3 +- tests/contrib/aiomysql/test_aiomysql.py | 8 +- tests/contrib/flask_cache/test_utils.py | 8 +- tests/contrib/kafka/test_kafka.py | 17 +- tests/contrib/tornado/test_config.py | 25 +- tests/integration/test_debug.py | 50 +-- tests/integration/test_encoding.py | 6 +- tests/integration/test_integration.py | 4 +- .../test_integration_civisibility.py | 6 +- .../integration/test_integration_snapshots.py | 2 - tests/integration/test_priority_sampling.py | 24 +- tests/integration/utils.py | 5 +- .../opentracer/core/test_dd_compatibility.py | 8 - tests/opentracer/core/test_tracer.py | 8 - tests/profiling/collector/conftest.py | 7 +- tests/profiling_v2/collector/conftest.py | 2 +- tests/tracer/test_correlation_log_context.py | 21 +- tests/tracer/test_gitmetadata.py | 39 ++- tests/tracer/test_memory_leak.py | 52 ++- tests/tracer/test_processors.py | 17 +- .../tracer/test_single_span_sampling_rules.py | 7 +- tests/tracer/test_trace_utils.py | 25 +- tests/tracer/test_tracer.py | 301 ++++++------------ tests/utils.py | 5 +- 27 files changed, 275 insertions(+), 400 deletions(-) create mode 100644 releasenotes/notes/drop-multitracer-support-ec109486f71c9c62.yaml diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index 87f312bb18c..eaab1475fd0 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -216,16 +216,8 @@ def __init__( if Tracer._instance is None: Tracer._instance = self else: - # ddtrace library does not support context propagation for multiple tracers. - # All instances of ddtrace ContextProviders share the same ContextVars. This means that - # if you create multiple instances of Tracer, spans will be shared between them creating a - # broken experience. - # TODO(mabdinur): Convert this warning to an ValueError in 3.0.0 - deprecate( - "Support for multiple Tracer instances is deprecated", - ". Use ddtrace.tracer instead.", - category=DDTraceDeprecationWarning, - removal_version="3.0.0", + raise ValueError( + "Multiple Tracer instances can not be initialized. Use ``ddtrace.trace.tracer`` instead.", ) self._user_trace_processors: List[TraceProcessor] = [] diff --git a/releasenotes/notes/drop-multitracer-support-ec109486f71c9c62.yaml b/releasenotes/notes/drop-multitracer-support-ec109486f71c9c62.yaml new file mode 100644 index 00000000000..81dcce7632f --- /dev/null +++ b/releasenotes/notes/drop-multitracer-support-ec109486f71c9c62.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + tracing: Drops support for multiple Tracer instances in the same process. Use ``ddtrace.trace.tracer`` to access the global tracer instance. + - | + pin: Removes the ``tracer`` parameter from ``ddtrace.trace.Pin`` methods. All Pin objects now use the global tracer instance. \ No newline at end of file diff --git a/tests/ci_visibility/test_ci_visibility.py b/tests/ci_visibility/test_ci_visibility.py index 1db4f068c7a..778568f544e 100644 --- a/tests/ci_visibility/test_ci_visibility.py +++ b/tests/ci_visibility/test_ci_visibility.py @@ -28,6 +28,7 @@ from ddtrace.internal.ci_visibility.git_client import METADATA_UPLOAD_STATUS from ddtrace.internal.ci_visibility.git_client import CIVisibilityGitClient from ddtrace.internal.ci_visibility.git_client import CIVisibilityGitClientSerializerV1 +from ddtrace.internal.ci_visibility.recorder import CIVisibilityTracer from ddtrace.internal.ci_visibility.recorder import _extract_repository_name_from_url import ddtrace.internal.test_visibility._internal_item_ids from ddtrace.internal.utils.http import Response @@ -685,7 +686,7 @@ def test_civisibilitywriter_evp_proxy_url(self): ), mock.patch( "ddtrace.internal.agent.get_trace_url", return_value="http://evpproxy.bar:1234" ), mock.patch("ddtrace.settings._config.Config", _get_default_civisibility_ddconfig()), mock.patch( - "ddtrace.tracer", ddtrace.trace.Tracer() + "ddtrace.tracer", CIVisibilityTracer() ), mock.patch( "ddtrace.internal.ci_visibility.recorder.CIVisibility._agent_evp_proxy_is_available", return_value=True ), _dummy_noop_git_client(), mock.patch( @@ -705,7 +706,7 @@ def test_civisibilitywriter_only_traces(self): ) ), mock.patch( "ddtrace.internal.agent.get_trace_url", return_value="http://onlytraces:1234" - ), mock.patch("ddtrace.tracer", ddtrace.trace.Tracer()), mock.patch( + ), mock.patch("ddtrace.tracer", CIVisibilityTracer()), mock.patch( "ddtrace.internal.ci_visibility.recorder.CIVisibility._agent_evp_proxy_is_available", return_value=False ), mock.patch( "ddtrace.internal.ci_visibility.writer.config", ddtrace.settings.Config() @@ -1119,7 +1120,7 @@ def test_civisibility_enable_respects_passed_in_tracer(): ), _dummy_noop_git_client(), mock.patch( "ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig() ), mock.patch("ddtrace.internal.ci_visibility.writer.config", ddtrace.settings.Config()): - tracer = ddtrace.trace.Tracer() + tracer = CIVisibilityTracer() tracer._configure(partial_flush_enabled=False, partial_flush_min_spans=100) CIVisibility.enable(tracer=tracer) assert CIVisibility._instance.tracer._partial_flush_enabled is False diff --git a/tests/ci_visibility/util.py b/tests/ci_visibility/util.py index dc0b886ca64..f1911e20e93 100644 --- a/tests/ci_visibility/util.py +++ b/tests/ci_visibility/util.py @@ -12,6 +12,7 @@ from ddtrace.internal.ci_visibility.git_client import METADATA_UPLOAD_STATUS from ddtrace.internal.ci_visibility.git_client import CIVisibilityGitClient from ddtrace.internal.ci_visibility.recorder import CIVisibility +from ddtrace.internal.ci_visibility.recorder import CIVisibilityTracer from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId from tests.utils import DummyCIVisibilityWriter from tests.utils import override_env @@ -209,5 +210,5 @@ def _ci_override_env( new_vars: t.Optional[t.Dict[str, str]] = None, mock_ci_env=False, replace_os_env=True, full_clear=False ): env_vars = _get_default_ci_env_vars(new_vars, mock_ci_env, full_clear) - with override_env(env_vars, replace_os_env=replace_os_env), mock.patch("ddtrace.tracer", ddtrace.trace.Tracer()): + with override_env(env_vars, replace_os_env=replace_os_env), mock.patch("ddtrace.tracer", CIVisibilityTracer()): yield diff --git a/tests/contrib/aiomysql/test_aiomysql.py b/tests/contrib/aiomysql/test_aiomysql.py index 8199b5c16a1..8c923b9e292 100644 --- a/tests/contrib/aiomysql/test_aiomysql.py +++ b/tests/contrib/aiomysql/test_aiomysql.py @@ -9,7 +9,6 @@ from ddtrace.contrib.internal.aiomysql.patch import unpatch from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from ddtrace.trace import Pin -from ddtrace.trace import Tracer from tests.contrib import shared_tests_async as shared_tests from tests.contrib.asyncio.utils import AsyncioTestCase from tests.contrib.asyncio.utils import mark_asyncio @@ -31,19 +30,16 @@ def patch_aiomysql(): @pytest.fixture async def patched_conn(tracer): conn = await aiomysql.connect(**AIOMYSQL_CONFIG) - Pin.get_from(conn).clone(tracer=tracer).onto(conn) yield conn conn.close() @pytest.fixture() -async def snapshot_conn(): - tracer = Tracer() +async def snapshot_conn(tracer): conn = await aiomysql.connect(**AIOMYSQL_CONFIG) - Pin.get_from(conn).clone(tracer=tracer).onto(conn) yield conn conn.close() - tracer.shutdown() + tracer.flush() @pytest.mark.asyncio diff --git a/tests/contrib/flask_cache/test_utils.py b/tests/contrib/flask_cache/test_utils.py index fc5d640b5cf..6e779d21109 100644 --- a/tests/contrib/flask_cache/test_utils.py +++ b/tests/contrib/flask_cache/test_utils.py @@ -6,7 +6,7 @@ from ddtrace.contrib.internal.flask_cache.utils import _extract_client from ddtrace.contrib.internal.flask_cache.utils import _extract_conn_tags from ddtrace.contrib.internal.flask_cache.utils import _resource_from_cache_prefix -from ddtrace.trace import Tracer +from ddtrace.trace import tracer from ..config import MEMCACHED_CONFIG from ..config import REDIS_CONFIG @@ -17,7 +17,6 @@ class FlaskCacheUtilsTest(unittest.TestCase): def test_extract_redis_connection_metadata(self): # create the TracedCache instance for a Flask app - tracer = Tracer() Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) config = { @@ -37,7 +36,6 @@ def test_extract_redis_connection_metadata(self): def test_extract_memcached_connection_metadata(self): # create the TracedCache instance for a Flask app - tracer = Tracer() Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) config = { @@ -56,7 +54,6 @@ def test_extract_memcached_connection_metadata(self): def test_extract_memcached_multiple_connection_metadata(self): # create the TracedCache instance for a Flask app - tracer = Tracer() Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) config = { @@ -78,7 +75,6 @@ def test_extract_memcached_multiple_connection_metadata(self): def test_resource_from_cache_with_prefix(self): # create the TracedCache instance for a Flask app - tracer = Tracer() Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) config = { @@ -94,7 +90,6 @@ def test_resource_from_cache_with_prefix(self): def test_resource_from_cache_with_empty_prefix(self): # create the TracedCache instance for a Flask app - tracer = Tracer() Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) config = { @@ -110,7 +105,6 @@ def test_resource_from_cache_with_empty_prefix(self): def test_resource_from_cache_without_prefix(self): # create the TracedCache instance for a Flask app - tracer = Tracer() Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) traced_cache = Cache(app, config={"CACHE_TYPE": "redis"}) diff --git a/tests/contrib/kafka/test_kafka.py b/tests/contrib/kafka/test_kafka.py index c67bdd08b01..721111a7099 100644 --- a/tests/contrib/kafka/test_kafka.py +++ b/tests/contrib/kafka/test_kafka.py @@ -22,7 +22,7 @@ from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter from ddtrace.trace import Pin from ddtrace.trace import TraceFilter -from ddtrace.trace import Tracer +from ddtrace.trace import tracer as ddtracer from tests.contrib.config import KAFKA_CONFIG from tests.datastreams.test_public_api import MockedTracer from tests.utils import DummyTracer @@ -106,16 +106,16 @@ def should_filter_empty_polls(): @pytest.fixture def tracer(should_filter_empty_polls): patch() - t = Tracer() if should_filter_empty_polls: - t._configure(trace_processors=[KafkaConsumerPollFilter()]) + ddtracer.configure(trace_processors=[KafkaConsumerPollFilter()]) # disable backoff because it makes these tests less reliable - t._writer._send_payload_with_backoff = t._writer._send_payload + previous_backoff = ddtracer._writer._send_payload_with_backoff + ddtracer._writer._send_payload_with_backoff = ddtracer._writer._send_payload try: - yield t + yield ddtracer finally: - t.flush() - t.shutdown() + ddtracer.flush() + ddtracer._writer._send_payload_with_backoff = previous_backoff unpatch() @@ -124,6 +124,8 @@ def dsm_processor(tracer): processor = tracer.data_streams_processor with mock.patch("ddtrace.internal.datastreams.data_streams_processor", return_value=processor): yield processor + # flush buckets for the next test run + processor.periodic() @pytest.fixture @@ -325,6 +327,7 @@ def test_commit_with_consume_with_multiple_messages(producer, consumer, kafka_to @pytest.mark.snapshot(ignores=SNAPSHOT_IGNORES) @pytest.mark.parametrize("should_filter_empty_polls", [False]) +@pytest.mark.skip(reason="FIXME: This test requires the initialization of a new tracer. This is not supported") def test_commit_with_consume_with_error(producer, consumer, kafka_topic): producer.produce(kafka_topic, PAYLOAD, key=KEY) producer.flush() diff --git a/tests/contrib/tornado/test_config.py b/tests/contrib/tornado/test_config.py index aaa87fcb2ec..0130db034eb 100644 --- a/tests/contrib/tornado/test_config.py +++ b/tests/contrib/tornado/test_config.py @@ -1,6 +1,5 @@ from ddtrace.trace import TraceFilter -from ddtrace.trace import Tracer -from tests.utils import DummyWriter +from tests.utils import DummyTracer from .utils import TornadoTestCase @@ -19,8 +18,7 @@ class TestTornadoSettings(TornadoTestCase): """ def get_app(self): - # Override with a real tracer - self.tracer = Tracer() + self.tracer = DummyTracer() super(TestTornadoSettings, self).get_app() def get_settings(self): @@ -40,25 +38,6 @@ def get_settings(self): }, } - def test_tracer_is_properly_configured(self): - # the tracer must be properly configured - assert self.tracer._tags.get("env") == "production" - assert self.tracer._tags.get("debug") == "false" - assert self.tracer.enabled is False - assert self.tracer.agent_trace_url == "http://dd-agent.service.consul:8126" - - writer = DummyWriter() - self.tracer._configure(enabled=True, writer=writer) - with self.tracer.trace("keep"): - pass - spans = writer.pop() - assert len(spans) == 1 - - with self.tracer.trace("drop"): - pass - spans = writer.pop() - assert len(spans) == 0 - class TestTornadoSettingsEnabled(TornadoTestCase): def get_settings(self): diff --git a/tests/integration/test_debug.py b/tests/integration/test_debug.py index f5453f353fe..2cb3d3fb181 100644 --- a/tests/integration/test_debug.py +++ b/tests/integration/test_debug.py @@ -3,8 +3,6 @@ import os import re import subprocess -from typing import List -from typing import Optional import mock import pytest @@ -13,11 +11,10 @@ import ddtrace._trace.sampler from ddtrace.internal import debug from ddtrace.internal.writer import AgentWriter -from ddtrace.internal.writer import TraceWriter -from ddtrace.trace import Span from tests.integration.utils import AGENT_VERSION from tests.subprocesstest import SubprocessTestCase from tests.subprocesstest import run_in_subprocess +from tests.utils import DummyTracer pytestmark = pytest.mark.skipif(AGENT_VERSION == "testagent", reason="The test agent doesn't support startup logs.") @@ -198,13 +195,12 @@ def test_trace_agent_url(self): ) ) def test_tracer_loglevel_info_connection(self): - tracer = ddtrace.trace.Tracer() logging.basicConfig(level=logging.INFO) with mock.patch.object(logging.Logger, "log") as mock_logger: # shove an unserializable object into the config log output # regression: this used to cause an exception to be raised ddtrace.config.version = AgentWriter(agent_url="foobar") - tracer._configure() + ddtrace.trace.tracer.configure() assert mock.call(logging.INFO, re_matcher("- DATADOG TRACER CONFIGURATION - ")) in mock_logger.mock_calls @run_in_subprocess( @@ -214,10 +210,9 @@ def test_tracer_loglevel_info_connection(self): ) ) def test_tracer_loglevel_info_no_connection(self): - tracer = ddtrace.trace.Tracer() logging.basicConfig(level=logging.INFO) with mock.patch.object(logging.Logger, "log") as mock_logger: - tracer._configure() + ddtrace.trace.tracer.configure() assert mock.call(logging.INFO, re_matcher("- DATADOG TRACER CONFIGURATION - ")) in mock_logger.mock_calls assert mock.call(logging.WARNING, re_matcher("- DATADOG TRACER DIAGNOSTIC - ")) in mock_logger.mock_calls @@ -228,9 +223,8 @@ def test_tracer_loglevel_info_no_connection(self): ) ) def test_tracer_log_disabled_error(self): - tracer = ddtrace.trace.Tracer() with mock.patch.object(logging.Logger, "log") as mock_logger: - tracer._configure() + ddtrace.trace.tracer._configure() assert mock_logger.mock_calls == [] @run_in_subprocess( @@ -240,9 +234,8 @@ def test_tracer_log_disabled_error(self): ) ) def test_tracer_log_disabled(self): - tracer = ddtrace.trace.Tracer() with mock.patch.object(logging.Logger, "log") as mock_logger: - tracer._configure() + ddtrace.trace.tracer._configure() assert mock_logger.mock_calls == [] @run_in_subprocess( @@ -252,9 +245,8 @@ def test_tracer_log_disabled(self): ) def test_tracer_info_level_log(self): logging.basicConfig(level=logging.INFO) - tracer = ddtrace.trace.Tracer() with mock.patch.object(logging.Logger, "log") as mock_logger: - tracer._configure() + ddtrace.trace.tracer._configure() assert mock_logger.mock_calls == [] @@ -296,16 +288,24 @@ def test_to_json(): json.dumps(info) +@pytest.mark.subprocess(env={"AWS_LAMBDA_FUNCTION_NAME": "something"}) def test_agentless(monkeypatch): - monkeypatch.setenv("AWS_LAMBDA_FUNCTION_NAME", "something") - tracer = ddtrace.trace.Tracer() - info = debug.collect(tracer) + from ddtrace.internal import debug + from ddtrace.trace import tracer + info = debug.collect(tracer) assert info.get("agent_url") == "AGENTLESS" +@pytest.mark.subprocess() def test_custom_writer(): - tracer = ddtrace.trace.Tracer() + from typing import List + from typing import Optional + + from ddtrace.internal import debug + from ddtrace.internal.writer import TraceWriter + from ddtrace.trace import Span + from ddtrace.trace import tracer class CustomWriter(TraceWriter): def recreate(self) -> TraceWriter: @@ -326,16 +326,24 @@ def flush_queue(self) -> None: assert info.get("agent_url") == "CUSTOM" +@pytest.mark.subprocess() def test_different_samplers(): - tracer = ddtrace.trace.Tracer() + import ddtrace + from ddtrace.internal import debug + from ddtrace.trace import tracer + tracer._configure(sampler=ddtrace._trace.sampler.RateSampler()) info = debug.collect(tracer) assert info.get("sampler_type") == "RateSampler" +@pytest.mark.subprocess() def test_startup_logs_sampling_rules(): - tracer = ddtrace.trace.Tracer() + import ddtrace + from ddtrace.internal import debug + from ddtrace.trace import tracer + sampler = ddtrace._trace.sampler.DatadogSampler(rules=[ddtrace._trace.sampler.SamplingRule(sample_rate=1.0)]) tracer._configure(sampler=sampler) f = debug.collect(tracer) @@ -424,7 +432,7 @@ def test_debug_span_log(): def test_partial_flush_log(): - tracer = ddtrace.trace.Tracer() + tracer = DummyTracer() tracer._configure( partial_flush_enabled=True, diff --git a/tests/integration/test_encoding.py b/tests/integration/test_encoding.py index e3f5037e7b3..ff47679af47 100644 --- a/tests/integration/test_encoding.py +++ b/tests/integration/test_encoding.py @@ -4,7 +4,7 @@ import mock import pytest -from ddtrace.trace import Tracer +from ddtrace.trace import tracer AGENT_VERSION = os.environ.get("AGENT_VERSION") @@ -12,7 +12,6 @@ class TestTraceAcceptedByAgent: def test_simple_trace_accepted_by_agent(self): - tracer = Tracer() with mock.patch("ddtrace.internal.writer.writer.log") as log: with tracer.trace("root"): for _ in range(999): @@ -32,7 +31,6 @@ def test_simple_trace_accepted_by_agent(self): ) def test_trace_with_meta_accepted_by_agent(self, tags): """Meta tags should be text types.""" - tracer = Tracer() with mock.patch("ddtrace.internal.writer.writer.log") as log: with tracer.trace("root", service="test_encoding", resource="test_resource") as root: root.set_tags(tags) @@ -53,7 +51,6 @@ def test_trace_with_meta_accepted_by_agent(self, tags): ) def test_trace_with_metrics_accepted_by_agent(self, metrics): """Metric tags should be numeric types - i.e. int, float, long (py3), and str numbers.""" - tracer = Tracer() with mock.patch("ddtrace.internal.writer.writer.log") as log: with tracer.trace("root") as root: root.set_metrics(metrics) @@ -72,7 +69,6 @@ def test_trace_with_metrics_accepted_by_agent(self, metrics): ) def test_trace_with_links_accepted_by_agent(self, span_links_kwargs): """Links should not break things.""" - tracer = Tracer() with mock.patch("ddtrace.internal.writer.writer.log") as log: with tracer.trace("root", service="test_encoding", resource="test_resource") as root: root.set_link(**span_links_kwargs) diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 70cc84cdbfa..fd29cc18231 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -9,10 +9,10 @@ from ddtrace.internal.atexit import register_on_exit_signal from ddtrace.internal.runtime import container -from ddtrace.trace import Tracer from tests.integration.utils import import_ddtrace_in_subprocess from tests.integration.utils import parametrize_with_all_encodings from tests.integration.utils import skip_if_testagent +from tests.utils import DummyTracer from tests.utils import call_program @@ -37,7 +37,7 @@ def test_configure_keeps_api_hostname_and_port(): @mock.patch("signal.getsignal") def test_shutdown_on_exit_signal(mock_get_signal, mock_signal): mock_get_signal.return_value = None - tracer = Tracer() + tracer = DummyTracer() register_on_exit_signal(tracer._atexit) assert mock_signal.call_count == 2 assert mock_signal.call_args_list[0][0][0] == signal.SIGTERM diff --git a/tests/integration/test_integration_civisibility.py b/tests/integration/test_integration_civisibility.py index cc633d12018..9e01d47b756 100644 --- a/tests/integration/test_integration_civisibility.py +++ b/tests/integration/test_integration_civisibility.py @@ -10,7 +10,7 @@ from ddtrace.internal.ci_visibility.constants import EVP_PROXY_AGENT_ENDPOINT from ddtrace.internal.ci_visibility.constants import EVP_SUBDOMAIN_HEADER_EVENT_VALUE from ddtrace.internal.ci_visibility.constants import EVP_SUBDOMAIN_HEADER_NAME -from ddtrace.internal.ci_visibility.recorder import CIVisibilityTracer as Tracer +from ddtrace.internal.ci_visibility.recorder import CIVisibilityTracer from tests.ci_visibility.util import _get_default_civisibility_ddconfig from tests.utils import override_env @@ -36,7 +36,7 @@ def test_civisibility_intake_with_evp_available(): with override_env( dict(DD_API_KEY="foobar.baz", DD_SITE="foo.bar", DD_CIVISIBILITY_AGENTLESS_ENABLED="0") ), mock.patch("ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig()): - t = Tracer() + t = CIVisibilityTracer() CIVisibility.enable(tracer=t) assert CIVisibility._instance.tracer._writer._endpoint == EVP_PROXY_AGENT_ENDPOINT assert CIVisibility._instance.tracer._writer.intake_url == agent.get_trace_url() @@ -64,7 +64,7 @@ def test_civisibility_intake_with_apikey(): with override_env( dict(DD_API_KEY="foobar.baz", DD_SITE="foo.bar", DD_CIVISIBILITY_AGENTLESS_ENABLED="1") ), mock.patch("ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig()): - t = Tracer() + t = CIVisibilityTracer() CIVisibility.enable(tracer=t) assert CIVisibility._instance.tracer._writer._endpoint == AGENTLESS_ENDPOINT assert CIVisibility._instance.tracer._writer.intake_url == "https://citestcycle-intake.foo.bar" diff --git a/tests/integration/test_integration_snapshots.py b/tests/integration/test_integration_snapshots.py index 0ba978fa260..eab323bf319 100644 --- a/tests/integration/test_integration_snapshots.py +++ b/tests/integration/test_integration_snapshots.py @@ -5,7 +5,6 @@ import mock import pytest -from ddtrace.trace import Tracer from ddtrace.trace import tracer from tests.integration.utils import AGENT_VERSION from tests.integration.utils import mark_snapshot @@ -214,7 +213,6 @@ def test_trace_with_wrong_meta_types_not_sent(encoding, meta, monkeypatch): def test_trace_with_wrong_metrics_types_not_sent(encoding, metrics, monkeypatch): """Wrong metric types should raise TypeErrors during encoding and fail to send to the agent.""" with override_global_config(dict(_trace_api=encoding)): - tracer = Tracer() with mock.patch("ddtrace._trace.span.log") as log: with tracer.trace("root") as root: root._metrics = metrics diff --git a/tests/integration/test_priority_sampling.py b/tests/integration/test_priority_sampling.py index 57b64a2fe5c..32fc4e0dcee 100644 --- a/tests/integration/test_priority_sampling.py +++ b/tests/integration/test_priority_sampling.py @@ -8,7 +8,7 @@ from ddtrace.internal.encoding import JSONEncoder from ddtrace.internal.encoding import MsgpackEncoderV04 as Encoder from ddtrace.internal.writer import AgentWriter -from ddtrace.trace import Tracer +from ddtrace.trace import tracer as ddtracer from tests.integration.utils import AGENT_VERSION from tests.integration.utils import parametrize_with_all_encodings from tests.integration.utils import skip_if_testagent @@ -115,18 +115,16 @@ def test_priority_sampling_response(): @pytest.mark.snapshot(agent_sample_rate_by_service={"service:test,env:": 0.9999}) def test_agent_sample_rate_keep(): """Ensure that the agent sample rate is respected when a trace is auto sampled.""" - tracer = Tracer() - # First trace won't actually have the sample rate applied since the response has not yet been received. - with tracer.trace(""): + with ddtracer.trace(""): pass # Force a flush to get the response back. - tracer.flush() + ddtracer.flush() # Subsequent traces should have the rate applied. - with tracer.trace("test", service="test") as span: + with ddtracer.trace("test", service="test") as span: pass - tracer.flush() + ddtracer.flush() assert span.get_metric("_dd.agent_psr") == pytest.approx(0.9999) assert span.get_metric("_sampling_priority_v1") == AUTO_KEEP assert span.get_tag("_dd.p.dm") == "-1" @@ -136,21 +134,17 @@ def test_agent_sample_rate_keep(): @pytest.mark.snapshot(agent_sample_rate_by_service={"service:test,env:": 0.0001}) def test_agent_sample_rate_reject(): """Ensure that the agent sample rate is respected when a trace is auto rejected.""" - from ddtrace.trace import Tracer - - tracer = Tracer() - # First trace won't actually have the sample rate applied since the response has not yet been received. - with tracer.trace(""): + with ddtracer.trace(""): pass # Force a flush to get the response back. - tracer.flush() + ddtracer.flush() # Subsequent traces should have the rate applied. - with tracer.trace("test", service="test") as span: + with ddtracer.trace("test", service="test") as span: pass - tracer.flush() + ddtracer.flush() assert span.get_metric("_dd.agent_psr") == pytest.approx(0.0001) assert span.get_metric("_sampling_priority_v1") == AUTO_REJECT assert span.get_tag("_dd.p.dm") == "-1" diff --git a/tests/integration/utils.py b/tests/integration/utils.py index 5b87161e2d0..53640d82b57 100644 --- a/tests/integration/utils.py +++ b/tests/integration/utils.py @@ -5,8 +5,6 @@ import mock import pytest -from ddtrace.trace import Tracer - AGENT_VERSION = os.environ.get("AGENT_VERSION") @@ -28,7 +26,8 @@ def encode_traces(self, traces): def send_invalid_payload_and_get_logs(encoder_cls=BadEncoder): - t = Tracer() + from ddtrace.trace import tracer as t + for client in t._writer._clients: client.encoder = encoder_cls() with mock.patch("ddtrace.internal.writer.writer.log") as log: diff --git a/tests/opentracer/core/test_dd_compatibility.py b/tests/opentracer/core/test_dd_compatibility.py index 4ba14b0618f..c68b5ca6d6c 100644 --- a/tests/opentracer/core/test_dd_compatibility.py +++ b/tests/opentracer/core/test_dd_compatibility.py @@ -15,14 +15,6 @@ def test_ottracer_uses_global_ddtracer(self): tracer = ddtrace.opentracer.Tracer() assert tracer._dd_tracer is ddtrace.tracer - def test_custom_ddtracer(self): - """A user should be able to specify their own Datadog tracer instance if - they wish. - """ - custom_dd_tracer = ddtrace.trace.Tracer() - tracer = ddtrace.opentracer.Tracer(dd_tracer=custom_dd_tracer) - assert tracer._dd_tracer is custom_dd_tracer - def test_ot_dd_global_tracers(self, global_tracer): """Ensure our test function opentracer_init() prep""" ot_tracer = global_tracer diff --git a/tests/opentracer/core/test_tracer.py b/tests/opentracer/core/test_tracer.py index a0a18ff0dd8..f5534c8f1b0 100644 --- a/tests/opentracer/core/test_tracer.py +++ b/tests/opentracer/core/test_tracer.py @@ -15,8 +15,6 @@ from ddtrace.opentracer.span_context import SpanContext from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID from ddtrace.settings import ConfigException -from ddtrace.trace import Tracer as DDTracer -from tests.utils import override_global_config class TestTracerConfig(object): @@ -69,12 +67,6 @@ def test_invalid_config_key(self): assert ["enabeld", "setttings"] in str(ce_info) # codespell:ignore assert tracer is not None - def test_ddtrace_fallback_config(self): - """Ensure datadog configuration is used by default.""" - with override_global_config(dict(_tracing_enabled=False)): - tracer = Tracer(dd_tracer=DDTracer()) - assert tracer._dd_tracer.enabled is False - def test_global_tags(self): """Global tags should be passed from the opentracer to the tracer.""" config = { diff --git a/tests/profiling/collector/conftest.py b/tests/profiling/collector/conftest.py index a774b20f7da..a53ac79bcad 100644 --- a/tests/profiling/collector/conftest.py +++ b/tests/profiling/collector/conftest.py @@ -2,12 +2,13 @@ import ddtrace from ddtrace.profiling import Profiler +from tests.utils import override_global_config @pytest.fixture -def tracer(monkeypatch): - monkeypatch.setenv("DD_TRACE_STARTUP_LOGS", "0") - return ddtrace.trace.Tracer() +def tracer(): + with override_global_config(dict(_startup_logs_enabled=False)): + yield ddtrace.trace.tracer @pytest.fixture diff --git a/tests/profiling_v2/collector/conftest.py b/tests/profiling_v2/collector/conftest.py index 311c286c11e..7dc1d816091 100644 --- a/tests/profiling_v2/collector/conftest.py +++ b/tests/profiling_v2/collector/conftest.py @@ -5,4 +5,4 @@ @pytest.fixture def tracer(): - return ddtrace.trace.Tracer() + return ddtrace.trace.tracer diff --git a/tests/tracer/test_correlation_log_context.py b/tests/tracer/test_correlation_log_context.py index b7200b8b38f..e2e5dda6b37 100644 --- a/tests/tracer/test_correlation_log_context.py +++ b/tests/tracer/test_correlation_log_context.py @@ -1,8 +1,8 @@ import pytest from ddtrace import config -from ddtrace.trace import Tracer from ddtrace.trace import tracer +from tests.utils import DummyTracer def global_config(config): @@ -10,7 +10,7 @@ def global_config(config): config.env = "test-env" config.version = "test-version" global tracer - tracer = Tracer() + tracer = DummyTracer() yield config.service = config.env = config.version = None @@ -33,9 +33,9 @@ def format_trace_id(span): @pytest.mark.subprocess() def test_get_log_correlation_service(): """Ensure expected DDLogRecord service is generated via get_correlation_log_record.""" - from ddtrace.trace import Tracer from ddtrace.trace import tracer from tests.tracer.test_correlation_log_context import format_trace_id + from tests.utils import DummyTracer from tests.utils import override_global_config with override_global_config(dict(service="test-service", env="test-env", version="test-version")): @@ -49,7 +49,7 @@ def test_get_log_correlation_service(): "version": "test-version", } - test_tracer = Tracer() + test_tracer = DummyTracer() with test_tracer.trace("test-span-2", service="span-service") as span2: dd_log_record = test_tracer.get_log_correlation_context() assert dd_log_record == { @@ -65,12 +65,12 @@ def test_get_log_correlation_service(): def test_get_log_correlation_context_basic(): """Ensure expected DDLogRecord is generated via get_correlation_log_record.""" from ddtrace.trace import Context - from ddtrace.trace import Tracer from tests.tracer.test_correlation_log_context import format_trace_id + from tests.utils import DummyTracer from tests.utils import override_global_config with override_global_config(dict(service="test-service", env="test-env", version="test-version")): - tracer = Tracer() + tracer = DummyTracer() with tracer.trace("test-span-1") as span1: dd_log_record = tracer.get_log_correlation_context() assert dd_log_record == { @@ -80,7 +80,7 @@ def test_get_log_correlation_context_basic(): "env": "test-env", "version": "test-version", }, dd_log_record - test_tracer = Tracer() + test_tracer = DummyTracer() with test_tracer.trace("test-span-2") as span2: dd_log_record = test_tracer.get_log_correlation_context() assert dd_log_record == { @@ -130,9 +130,9 @@ def test_get_log_correlation_context_opentracer(): @pytest.mark.subprocess() def test_get_log_correlation_context_no_active_span(): """Ensure empty DDLogRecord generated if no active span.""" - from ddtrace.trace import Tracer + from tests.utils import DummyTracer - tracer = Tracer() + tracer = DummyTracer() dd_log_record = tracer.get_log_correlation_context() assert dd_log_record == { "span_id": "0", @@ -146,9 +146,8 @@ def test_get_log_correlation_context_no_active_span(): @pytest.mark.subprocess() def test_get_log_correlation_context_disabled_tracer(): """Ensure get_correlation_log_record returns None if tracer is disabled.""" - from ddtrace.trace import Tracer + from ddtrace.trace import tracer - tracer = Tracer() tracer.enabled = False with tracer.trace("test-span"): dd_log_record = tracer.get_log_correlation_context() diff --git a/tests/tracer/test_gitmetadata.py b/tests/tracer/test_gitmetadata.py index cb03d59f7e2..d6c35a2de0c 100644 --- a/tests/tracer/test_gitmetadata.py +++ b/tests/tracer/test_gitmetadata.py @@ -8,10 +8,9 @@ import pytest -import ddtrace from ddtrace.internal import gitmetadata from tests.subprocesstest import run_in_subprocess -from tests.utils import DummyWriter +from tests.utils import DummyTracer from tests.utils import TracerTestCase @@ -44,8 +43,8 @@ class GitMetadataTestCase(TracerTestCase): ) ) def test_gitmetadata_from_package(self): - tracer = ddtrace.trace.Tracer() - tracer._configure(writer=DummyWriter()) + tracer = DummyTracer() + with tracer.trace("span") as s: pass @@ -59,8 +58,8 @@ def test_gitmetadata_from_package(self): ) ) def test_gitmetadata_from_DD_TAGS(self): - tracer = ddtrace.trace.Tracer() - tracer._configure(writer=DummyWriter()) + tracer = DummyTracer() + with tracer.trace("span") as s: pass @@ -80,8 +79,8 @@ def test_gitmetadata_from_DD_TAGS(self): ) ) def test_gitmetadata_from_ENV(self): - tracer = ddtrace.trace.Tracer() - tracer._configure(writer=DummyWriter()) + tracer = DummyTracer() + with tracer.trace("span") as s: pass @@ -104,8 +103,8 @@ def test_gitmetadata_from_ENV(self): ) ) def test_gitmetadata_disabled(self): - tracer = ddtrace.trace.Tracer() - tracer._configure(writer=DummyWriter()) + tracer = DummyTracer() + with tracer.trace("span") as s: pass @@ -123,8 +122,8 @@ def test_gitmetadata_disabled(self): ) ) def test_gitmetadata_package_without_metadata(self): - tracer = ddtrace.trace.Tracer() - tracer._configure(writer=DummyWriter()) + tracer = DummyTracer() + with tracer.trace("span") as s: pass @@ -143,8 +142,8 @@ def test_gitmetadata_package_without_metadata(self): ) ) def test_gitmetadata_from_env_filtering_https(self): - tracer = ddtrace.trace.Tracer() - tracer._configure(writer=DummyWriter()) + tracer = DummyTracer() + with tracer.trace("span") as s: pass @@ -163,8 +162,8 @@ def test_gitmetadata_from_env_filtering_https(self): ) ) def test_gitmetadata_from_ddtags_filtering_https(self): - tracer = ddtrace.trace.Tracer() - tracer._configure(writer=DummyWriter()) + tracer = DummyTracer() + with tracer.trace("span") as s: pass @@ -184,8 +183,8 @@ def test_gitmetadata_from_ddtags_filtering_https(self): ) ) def test_gitmetadata_from_env_filtering_ssh(self): - tracer = ddtrace.trace.Tracer() - tracer._configure(writer=DummyWriter()) + tracer = DummyTracer() + with tracer.trace("span") as s: pass @@ -204,8 +203,8 @@ def test_gitmetadata_from_env_filtering_ssh(self): ) ) def test_gitmetadata_from_ddtags_filtering_ssh(self): - tracer = ddtrace.trace.Tracer() - tracer._configure(writer=DummyWriter()) + tracer = DummyTracer() + with tracer.trace("span") as s: pass diff --git a/tests/tracer/test_memory_leak.py b/tests/tracer/test_memory_leak.py index 7fdcd7589f6..db177bfa7ae 100644 --- a/tests/tracer/test_memory_leak.py +++ b/tests/tracer/test_memory_leak.py @@ -1,15 +1,12 @@ """ Variety of test cases ensuring that ddtrace does not leak memory. """ - -import gc -from threading import Thread from typing import TYPE_CHECKING from weakref import WeakValueDictionary import pytest -from ddtrace.trace import Tracer +from tests.utils import DummyTracer if TYPE_CHECKING: # pragma: no cover @@ -17,11 +14,11 @@ @pytest.fixture -def tracer() -> Tracer: - return Tracer() +def tracer() -> DummyTracer: + return DummyTracer() -def trace(weakdict: WeakValueDictionary, tracer: Tracer, *args, **kwargs): +def trace(weakdict: WeakValueDictionary, tracer, *args, **kwargs): # type: (...) -> Span """Return a span created from ``tracer`` and add it to the given weak dictionary. @@ -34,7 +31,14 @@ def trace(weakdict: WeakValueDictionary, tracer: Tracer, *args, **kwargs): return s -def test_leak(tracer): +@pytest.mark.subprocess +def test_leak(): + import gc + from weakref import WeakValueDictionary + + from ddtrace.trace import tracer + from tests.tracer.test_memory_leak import trace + wd = WeakValueDictionary() with trace(wd, tracer, "span1") as span: with trace(wd, tracer, "span2") as span2: @@ -44,15 +48,23 @@ def test_leak(tracer): # The spans are still open and referenced so they should not be gc'd gc.collect() assert len(wd) == 2 + tracer.flush() del span, span2 gc.collect() assert len(wd) == 0 -def test_single_thread_single_trace(tracer): +@pytest.mark.subprocess +def test_single_thread_single_trace(): """ Ensure a simple trace doesn't leak span objects. """ + import gc + from weakref import WeakValueDictionary + + from ddtrace.trace import tracer + from tests.tracer.test_memory_leak import trace + wd = WeakValueDictionary() with trace(wd, tracer, "span1"): with trace(wd, tracer, "span2"): @@ -64,10 +76,17 @@ def test_single_thread_single_trace(tracer): assert len(wd) == 0 -def test_single_thread_multi_trace(tracer): +@pytest.mark.subprocess +def test_single_thread_multi_trace(): """ Ensure a trace in a thread is properly garbage collected. """ + import gc + from weakref import WeakValueDictionary + + from ddtrace.trace import tracer + from tests.tracer.test_memory_leak import trace + wd = WeakValueDictionary() for _ in range(1000): with trace(wd, tracer, "span1"): @@ -75,17 +94,25 @@ def test_single_thread_multi_trace(tracer): pass with trace(wd, tracer, "span3"): pass - + tracer.flush() # Once these references are deleted then the spans should no longer be # referenced by anything and should be gc'd. gc.collect() assert len(wd) == 0 -def test_multithread_trace(tracer): +@pytest.mark.subprocess +def test_multithread_trace(): """ Ensure a trace that crosses thread boundaries is properly garbage collected. """ + import gc + from threading import Thread + from weakref import WeakValueDictionary + + from ddtrace.trace import tracer + from tests.tracer.test_memory_leak import trace + wd = WeakValueDictionary() state = [] @@ -102,6 +129,7 @@ def _target(ctx): # Ensure thread finished successfully assert state == [1] + tracer.flush() del span gc.collect() assert len(wd) == 0 diff --git a/tests/tracer/test_processors.py b/tests/tracer/test_processors.py index ff19453555b..a5f75f88b3b 100644 --- a/tests/tracer/test_processors.py +++ b/tests/tracer/test_processors.py @@ -26,7 +26,6 @@ from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE from ddtrace.trace import Context from ddtrace.trace import Span -from ddtrace.trace import Tracer from tests.utils import DummyTracer from tests.utils import DummyWriter from tests.utils import override_global_config @@ -244,7 +243,7 @@ def test_aggregator_partial_flush_2_spans(): def test_trace_top_level_span_processor_partial_flushing(): """Parent span and child span have the same service name""" - tracer = Tracer() + tracer = DummyTracer() tracer._configure( partial_flush_enabled=True, partial_flush_min_spans=2, @@ -271,7 +270,7 @@ def test_trace_top_level_span_processor_partial_flushing(): def test_trace_top_level_span_processor_same_service_name(): """Parent span and child span have the same service name""" - tracer = Tracer() + tracer = DummyTracer() tracer._configure(writer=DummyWriter()) with tracer.trace("parent", service="top_level_test") as parent: @@ -285,7 +284,7 @@ def test_trace_top_level_span_processor_same_service_name(): def test_trace_top_level_span_processor_different_service_name(): """Parent span and child span have the different service names""" - tracer = Tracer() + tracer = DummyTracer() tracer._configure(writer=DummyWriter()) with tracer.trace("parent", service="top_level_test_service") as parent: @@ -299,7 +298,7 @@ def test_trace_top_level_span_processor_different_service_name(): def test_trace_top_level_span_processor_orphan_span(): """Trace chuck does not contain parent span""" - tracer = Tracer() + tracer = DummyTracer() tracer._configure(writer=DummyWriter()) with tracer.trace("parent") as parent: @@ -388,7 +387,7 @@ def test_span_creation_metrics(): def test_changing_tracer_sampler_changes_tracesamplingprocessor_sampler(): """Changing the tracer sampler should change the sampling processor's sampler""" - tracer = Tracer() + tracer = DummyTracer() # get processor for aggregator in tracer._deferred_processors: if type(aggregator) is SpanAggregator: @@ -632,7 +631,7 @@ def test_endpoint_call_counter_processor_disabled(): def test_endpoint_call_counter_processor_real_tracer(): - tracer = Tracer() + tracer = DummyTracer() tracer._endpoint_call_counter_span_processor.enable() tracer._configure(writer=DummyWriter()) @@ -656,7 +655,7 @@ def test_endpoint_call_counter_processor_real_tracer(): def test_trace_tag_processor_adds_chunk_root_tags(): - tracer = Tracer() + tracer = DummyTracer() tracer._configure(writer=DummyWriter()) with tracer.trace("parent") as parent: @@ -679,7 +678,7 @@ def on_span_finish(self, span): tp = TestProcessor() tp.register() - tracer = Tracer() + tracer = DummyTracer() with tracer.trace("test") as span: assert span.get_tag("on_start") == "ok" diff --git a/tests/tracer/test_single_span_sampling_rules.py b/tests/tracer/test_single_span_sampling_rules.py index ef33ecfd619..adc8f9f111d 100644 --- a/tests/tracer/test_single_span_sampling_rules.py +++ b/tests/tracer/test_single_span_sampling_rules.py @@ -10,7 +10,6 @@ from ddtrace.internal.sampling import SpanSamplingRule from ddtrace.internal.sampling import _get_file_json from ddtrace.internal.sampling import get_span_sampling_rules -from ddtrace.trace import Tracer from tests.utils import DummyTracer from tests.utils import DummyWriter @@ -129,7 +128,7 @@ def test_env_rules_cause_matching_span_to_be_sampled(): sampling_rules = get_span_sampling_rules() assert sampling_rules[0]._service_matcher.pattern == "test_service" assert sampling_rules[0]._name_matcher.pattern == "test_name" - tracer = Tracer() + tracer = DummyTracer() tracer._configure(writer=DummyWriter()) span = traced_function(sampling_rules[0], tracer=tracer) assert_sampling_decision_tags(span) @@ -141,7 +140,7 @@ def test_env_rules_dont_cause_non_matching_span_to_be_sampled(): sampling_rules = get_span_sampling_rules() assert sampling_rules[0]._service_matcher.pattern == "test_ser" assert sampling_rules[0]._name_matcher.pattern == "test_na" - tracer = Tracer() + tracer = DummyTracer() tracer._configure(writer=DummyWriter()) span = traced_function(sampling_rules[0], tracer=tracer) assert_sampling_decision_tags(span, sample_rate=None, mechanism=None, limit=None) @@ -153,7 +152,7 @@ def test_single_span_rules_not_applied_when_span_sampled_by_trace_sampling(): sampling_rules = get_span_sampling_rules() assert sampling_rules[0]._service_matcher.pattern == "test_service" assert sampling_rules[0]._name_matcher.pattern == "test_name" - tracer = Tracer() + tracer = DummyTracer() tracer._configure(writer=DummyWriter()) span = traced_function(sampling_rules[0], tracer=tracer, trace_sampling=True) assert sampling_rules[0].match(span) is True diff --git a/tests/tracer/test_trace_utils.py b/tests/tracer/test_trace_utils.py index ca564cac394..6820b0c4d76 100644 --- a/tests/tracer/test_trace_utils.py +++ b/tests/tracer/test_trace_utils.py @@ -28,7 +28,6 @@ from ddtrace.trace import Context from ddtrace.trace import Pin from ddtrace.trace import Span -from ddtrace.trace import Tracer from tests.appsec.utils import asm_context from tests.utils import override_global_config @@ -277,9 +276,8 @@ def test_int_service(int_config, pin, config_val, default, global_service, expec assert trace_utils.int_service(pin, int_config.myint, default) == expected -def test_int_service_integration(int_config): +def test_int_service_integration(int_config, tracer): pin = Pin() - tracer = Tracer() assert trace_utils.int_service(pin, int_config.myint) == "tests.tracer" with override_global_config(dict(service="global-svc")): @@ -905,8 +903,7 @@ def test_distributed_tracing_enabled(int_config, props, default, expected): assert trace_utils.distributed_tracing_enabled(int_config.myint, **kwargs) == expected, (props, default, expected) -def test_activate_distributed_headers_enabled(int_config): - tracer = Tracer() +def test_activate_distributed_headers_enabled(int_config, tracer): int_config.myint["distributed_tracing_enabled"] = True headers = { HTTP_HEADER_PARENT_ID: "12345", @@ -925,8 +922,7 @@ def test_activate_distributed_headers_enabled(int_config): assert context.span_id == 12345 -def test_activate_distributed_headers_disabled(int_config): - tracer = Tracer() +def test_activate_distributed_headers_disabled(int_config, tracer): int_config.myint["distributed_tracing_enabled"] = False headers = { HTTP_HEADER_PARENT_ID: "12345", @@ -941,16 +937,14 @@ def test_activate_distributed_headers_disabled(int_config): assert tracer.context_provider.active() is None -def test_activate_distributed_headers_no_headers(int_config): - tracer = Tracer() +def test_activate_distributed_headers_no_headers(int_config, tracer): int_config.myint["distributed_tracing_enabled"] = True trace_utils.activate_distributed_headers(tracer, int_config=int_config.myint, request_headers=None) assert tracer.context_provider.active() is None -def test_activate_distributed_headers_override_true(int_config): - tracer = Tracer() +def test_activate_distributed_headers_override_true(int_config, tracer): int_config.myint["distributed_tracing_enabled"] = False headers = { HTTP_HEADER_PARENT_ID: "12345", @@ -964,8 +958,7 @@ def test_activate_distributed_headers_override_true(int_config): assert context.span_id == 12345 -def test_activate_distributed_headers_override_false(int_config): - tracer = Tracer() +def test_activate_distributed_headers_override_false(int_config, tracer): int_config.myint["distributed_tracing_enabled"] = True headers = { HTTP_HEADER_PARENT_ID: "12345", @@ -977,8 +970,7 @@ def test_activate_distributed_headers_override_false(int_config): assert tracer.context_provider.active() is None -def test_activate_distributed_headers_existing_context(int_config): - tracer = Tracer() +def test_activate_distributed_headers_existing_context(int_config, tracer): int_config.myint["distributed_tracing_enabled"] = True headers = { @@ -993,8 +985,7 @@ def test_activate_distributed_headers_existing_context(int_config): assert tracer.context_provider.active() == ctx -def test_activate_distributed_headers_existing_context_different_trace_id(int_config): - tracer = Tracer() +def test_activate_distributed_headers_existing_context_different_trace_id(int_config, tracer): int_config.myint["distributed_tracing_enabled"] = True headers = { diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index 0a75e5fc037..7438cdef5a9 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -9,7 +9,6 @@ from os import getpid import threading from unittest.case import SkipTest -import weakref import mock import pytest @@ -30,8 +29,7 @@ from ddtrace.constants import VERSION_KEY from ddtrace.contrib.internal.trace_utils import set_user from ddtrace.ext import user -from ddtrace.internal._encoding import MsgpackEncoderV04 -from ddtrace.internal._encoding import MsgpackEncoderV05 +import ddtrace.internal from ddtrace.internal.compat import PYTHON_VERSION_INFO from ddtrace.internal.rate_limiter import RateLimiter from ddtrace.internal.serverless import has_aws_lambda_agent_extension @@ -40,8 +38,9 @@ from ddtrace.internal.writer import LogWriter from ddtrace.settings import Config from ddtrace.trace import Context -from ddtrace.trace import Tracer +from ddtrace.trace import tracer as global_tracer from tests.subprocesstest import run_in_subprocess +from tests.utils import DummyTracer from tests.utils import TracerTestCase from tests.utils import override_global_config @@ -485,32 +484,6 @@ def test_adding_mapped_services(self): pass assert self.tracer._services == set(["one", "three"]) - def test_configure_dogstatsd_url_host_port(self): - tracer = Tracer() - tracer._configure(dogstatsd_url="foo:1234") - assert tracer._writer.dogstatsd.host == "foo" - assert tracer._writer.dogstatsd.port == 1234 - - tracer = Tracer() - writer = AgentWriter("http://localhost:8126") - tracer._configure(writer=writer, dogstatsd_url="foo:1234") - assert tracer._writer.dogstatsd.host == "foo" - assert tracer._writer.dogstatsd.port == 1234 - - def test_configure_dogstatsd_url_socket(self): - tracer = Tracer() - tracer._configure(dogstatsd_url="unix:///foo.sock") - assert tracer._writer.dogstatsd.host is None - assert tracer._writer.dogstatsd.port is None - assert tracer._writer.dogstatsd.socket_path == "/foo.sock" - - tracer = Tracer() - writer = AgentWriter("http://localhost:8126") - tracer._configure(writer=writer, dogstatsd_url="unix:///foo.sock") - assert tracer._writer.dogstatsd.host is None - assert tracer._writer.dogstatsd.port is None - assert tracer._writer.dogstatsd.socket_path == "/foo.sock" - def test_tracer_set_user(self): with self.trace("fake_span") as span: set_user( @@ -637,34 +610,17 @@ def test_tracer_set_user_propagation_string_error(self): @pytest.mark.subprocess(env=dict(DD_AGENT_PORT="", DD_AGENT_HOST="", DD_TRACE_AGENT_URL="")) def test_tracer_url(): - import pytest - import ddtrace - t = ddtrace.trace.Tracer() - assert t._writer.agent_url == "http://localhost:8126" - - t = ddtrace.trace.Tracer(url="http://foobar:12") - assert t._writer.agent_url == "http://foobar:12" - - t = ddtrace.trace.Tracer(url="unix:///foobar") - assert t._writer.agent_url == "unix:///foobar" - - t = ddtrace.trace.Tracer(url="http://localhost") - assert t._writer.agent_url == "http://localhost" - - t = ddtrace.trace.Tracer(url="https://localhost") - assert t._writer.agent_url == "https://localhost" - - with pytest.raises(ValueError) as e: - ddtrace.trace.Tracer(url="foo://foobar:12") - assert ( - str(e.value) == "Unsupported protocol 'foo' in intake URL 'foo://foobar:12'. Must be one of: http, https, unix" - ) + assert ddtrace.trace.tracer._writer.agent_url == "http://localhost:8126" +@pytest.mark.subprocess() def test_tracer_shutdown_no_timeout(): - t = ddtrace.trace.Tracer() + import mock + + from ddtrace.internal.writer import AgentWriter + from ddtrace.trace import tracer as t with mock.patch.object(AgentWriter, "stop") as mock_stop: with mock.patch.object(AgentWriter, "join") as mock_join: @@ -674,8 +630,12 @@ def test_tracer_shutdown_no_timeout(): mock_join.assert_not_called() +@pytest.mark.subprocess() def test_tracer_configure_writer_stop_unstarted(): - t = ddtrace.trace.Tracer() + import mock + + from ddtrace.trace import tracer as t + t._writer = mock.Mock(wraps=t._writer) orig_writer = t._writer @@ -684,8 +644,12 @@ def test_tracer_configure_writer_stop_unstarted(): assert orig_writer.stop.called +@pytest.mark.subprocess() def test_tracer_configure_writer_stop_started(): - t = ddtrace.trace.Tracer() + import mock + + from ddtrace.trace import tracer as t + t._writer = mock.Mock(wraps=t._writer) orig_writer = t._writer @@ -697,8 +661,12 @@ def test_tracer_configure_writer_stop_started(): orig_writer.stop.assert_called_once_with() +@pytest.mark.subprocess() def test_tracer_shutdown_timeout(): - t = ddtrace.trace.Tracer() + import mock + + from ddtrace.internal.writer import AgentWriter + from ddtrace.trace import tracer as t with mock.patch.object(AgentWriter, "stop") as mock_stop: with t.trace("something"): @@ -709,7 +677,11 @@ def test_tracer_shutdown_timeout(): def test_tracer_shutdown(): - t = ddtrace.trace.Tracer() + import mock + + from ddtrace.internal.writer import AgentWriter + from ddtrace.trace import tracer as t + t.shutdown() with mock.patch.object(AgentWriter, "write") as mock_write: @@ -720,7 +692,12 @@ def test_tracer_shutdown(): def test_tracer_shutdown_warning(): - t = ddtrace.trace.Tracer() + import logging + + import mock + + from ddtrace.trace import tracer as t + t.shutdown() with mock.patch.object(logging.Logger, "warning") as mock_logger: @@ -734,30 +711,6 @@ def test_tracer_shutdown_warning(): ) -def test_tracer_dogstatsd_url(): - t = ddtrace.trace.Tracer() - assert t._writer.dogstatsd.host == "localhost" - assert t._writer.dogstatsd.port == 8125 - - t = ddtrace.trace.Tracer(dogstatsd_url="foobar:12") - assert t._writer.dogstatsd.host == "foobar" - assert t._writer.dogstatsd.port == 12 - - t = ddtrace.trace.Tracer(dogstatsd_url="udp://foobar:12") - assert t._writer.dogstatsd.host == "foobar" - assert t._writer.dogstatsd.port == 12 - - t = ddtrace.trace.Tracer(dogstatsd_url="/var/run/statsd.sock") - assert t._writer.dogstatsd.socket_path == "/var/run/statsd.sock" - - t = ddtrace.trace.Tracer(dogstatsd_url="unix:///var/run/statsd.sock") - assert t._writer.dogstatsd.socket_path == "/var/run/statsd.sock" - - with pytest.raises(ValueError) as e: - t = ddtrace.trace.Tracer(dogstatsd_url="foo://foobar:12") - assert str(e) == "Unknown url format for `foo://foobar:12`" - - @pytest.mark.skip(reason="Fails to Pickle RateLimiter in the Tracer") @pytest.mark.subprocess def test_tracer_fork(): @@ -811,7 +764,7 @@ def task(t, errors): def test_tracer_with_version(): - t = ddtrace.trace.Tracer() + t = DummyTracer() # With global `config.version` defined with override_global_config(dict(version="1.2.3")): @@ -838,7 +791,7 @@ def test_tracer_with_version(): def test_tracer_with_env(): - t = ddtrace.trace.Tracer() + t = DummyTracer() # With global `config.env` defined with override_global_config(dict(env="prod")): @@ -960,33 +913,13 @@ def test_version_service_mapping(self): def test_detect_agentless_env_with_lambda(self): assert in_aws_lambda() assert not has_aws_lambda_agent_extension() - tracer = Tracer() - assert isinstance(tracer._writer, LogWriter) - tracer._configure(enabled=True) - assert isinstance(tracer._writer, LogWriter) - - @run_in_subprocess(env_overrides=dict(AWS_LAMBDA_FUNCTION_NAME="my-func")) - def test_detect_agent_config_with_lambda_extension(self): - def mock_os_path_exists(path): - return path == "/opt/extensions/datadog-agent" - - assert in_aws_lambda() - - with mock.patch("os.path.exists", side_effect=mock_os_path_exists): - assert has_aws_lambda_agent_extension() - - tracer = Tracer() - assert isinstance(tracer._writer, AgentWriter) - assert tracer._writer._sync_mode - - tracer._configure(enabled=False) - assert isinstance(tracer._writer, AgentWriter) - assert tracer._writer._sync_mode + assert isinstance(ddtrace.tracer._writer, LogWriter) + ddtrace.tracer._configure(enabled=True) + assert isinstance(ddtrace.tracer._writer, LogWriter) @run_in_subprocess(env_overrides=dict(AWS_LAMBDA_FUNCTION_NAME="my-func", DD_AGENT_HOST="localhost")) def test_detect_agent_config(self): - tracer = Tracer() - assert isinstance(tracer._writer, AgentWriter) + assert isinstance(global_tracer._writer, AgentWriter) @run_in_subprocess(env_overrides=dict(DD_TAGS="key1:value1,key2:value2")) def test_dd_tags(self): @@ -1001,7 +934,7 @@ def test_dd_tags_invalid(self): @run_in_subprocess(env_overrides=dict(DD_TAGS="service:mysvc,env:myenv,version:myvers")) def test_tags_from_DD_TAGS(self): - t = ddtrace.trace.Tracer() + t = DummyTracer() with t.trace("test") as s: assert s.service == "mysvc" assert s.get_tag("env") == "myenv" @@ -1016,33 +949,29 @@ def test_tags_from_DD_TAGS(self): ) ) def test_tags_from_DD_TAGS_precedence(self): - t = ddtrace.trace.Tracer() - with t.trace("test") as s: + with global_tracer.trace("test") as s: assert s.service == "svc" assert s.get_tag("env") == "env" assert s.get_tag("version") == "0.123" @run_in_subprocess(env_overrides=dict(DD_TAGS="service:mysvc,env:myenv,version:myvers")) def test_tags_from_DD_TAGS_override(self): - t = ddtrace.trace.Tracer() ddtrace.config.env = "env" ddtrace.config.service = "service" ddtrace.config.version = "0.123" - with t.trace("test") as s: + with global_tracer.trace("test") as s: assert s.service == "service" assert s.get_tag("env") == "env" assert s.get_tag("version") == "0.123" def test_tracer_set_runtime_tags(): - t = ddtrace.trace.Tracer() - with t.start_span("foobar") as span: + with global_tracer.start_span("foobar") as span: pass assert len(span.get_tag("runtime-id")) - t2 = ddtrace.trace.Tracer() - with t2.start_span("foobaz") as span2: + with global_tracer.start_span("foobaz") as span2: pass assert span.get_tag("runtime-id") == span2.get_tag("runtime-id") @@ -1084,7 +1013,7 @@ def test_tracer_runtime_tags_cross_execution(tracer): def test_start_span_hooks(): - t = ddtrace.trace.Tracer() + t = DummyTracer() result = {} @@ -1099,7 +1028,7 @@ def store_span(span): def test_deregister_start_span_hooks(): - t = ddtrace.trace.Tracer() + t = DummyTracer() result = {} @@ -1119,9 +1048,8 @@ def store_span(span): def test_enable(): import os - import ddtrace + from ddtrace.trace import tracer as t2 - t2 = ddtrace.trace.Tracer() if os.environ["DD_TRACE_ENABLED"] == "true": assert t2.enabled else: @@ -1170,7 +1098,7 @@ def thread_target(): def test_runtime_id_parent_only(): - tracer = ddtrace.trace.Tracer() + tracer = DummyTracer() # Parent spans should have runtime-id with tracer.trace("test") as s: @@ -1221,18 +1149,6 @@ def test_runtime_id_fork(): assert exit_code == 12 -def test_multiple_tracer_ctx(): - t1 = ddtrace.trace.Tracer() - t2 = ddtrace.trace.Tracer() - - with t1.trace("") as s1: - with t2.trace("") as s2: - pass - - assert s2.parent_id == s1.span_id - assert s2.trace_id == s1.trace_id - - def test_filters(tracer, test_spans): class FilterAll(object): def process_trace(self, trace): @@ -1413,12 +1329,10 @@ def _test_partial_flush(self): def test_unicode_config_vals(): - t = ddtrace.trace.Tracer() - with override_global_config(dict(version="😇", env="😇")): - with t.trace("1"): + with global_tracer.trace("1"): pass - t.shutdown() + global_tracer.flush() def test_ctx(tracer, test_spans): @@ -1664,45 +1578,25 @@ def override_service_mapping(service_mapping): ddtrace.config.service_mapping = {} # Test single mapping - with override_service_mapping("foo:bar"), ddtrace.trace.Tracer().trace("renaming", service="foo") as span: + with override_service_mapping("foo:bar"), global_tracer.trace("renaming", service="foo") as span: assert span.service == "bar" # Test multiple mappings - with override_service_mapping("foo:bar,sna:fu"), ddtrace.trace.Tracer().trace("renaming", service="sna") as span: + with override_service_mapping("foo:bar,sna:fu"), global_tracer.trace("renaming", service="sna") as span: assert span.service == "fu" # Test colliding mappings - with override_service_mapping("foo:bar,foo:foobar"), ddtrace.trace.Tracer().trace( - "renaming", service="foo" - ) as span: + with override_service_mapping("foo:bar,foo:foobar"), global_tracer.trace("renaming", service="foo") as span: assert span.service == "foobar" # Test invalid service mapping with override_service_mapping("foo;bar,sna:fu"): - with ddtrace.trace.Tracer().trace("passthru", service="foo") as _: + with global_tracer.trace("passthru", service="foo") as _: assert _.service == "foo" - with ddtrace.trace.Tracer().trace("renaming", "sna") as _: + with global_tracer.trace("renaming", "sna") as _: assert _.service == "fu" -@pytest.mark.subprocess(env=dict(DD_AGENT_PORT="", DD_AGENT_HOST="", DD_TRACE_AGENT_URL="")) -def test_configure_url_partial(): - import ddtrace - - tracer = ddtrace.trace.Tracer() - tracer._configure(hostname="abc") - assert tracer._writer.agent_url == "http://abc:8126" - tracer._configure(port=123) - assert tracer._writer.agent_url == "http://abc:123" - - tracer = ddtrace.trace.Tracer(url="http://abc") - assert tracer._writer.agent_url == "http://abc" - tracer._configure(port=123) - assert tracer._writer.agent_url == "http://abc:123" - tracer._configure(port=431) - assert tracer._writer.agent_url == "http://abc:431" - - @pytest.mark.subprocess(env={"DD_TRACE_AGENT_URL": "bad://localhost:1234"}) def test_bad_agent_url(): import pytest @@ -1910,16 +1804,16 @@ def test_fork_pid(): assert exit_code == 12 +@pytest.mark.subprocess def test_tracer_api_version(): - t = Tracer() - assert isinstance(t._writer._encoder, MsgpackEncoderV05) + from ddtrace.internal.encoding import MsgpackEncoderV05 + from ddtrace.trace import tracer as t - t._configure(api_version="v0.4") - assert isinstance(t._writer._encoder, MsgpackEncoderV04) + assert isinstance(t._writer._encoder, MsgpackEncoderV05) -@pytest.mark.parametrize("enabled", [True, False]) -def test_tracer_memory_leak_span_processors(enabled): +@pytest.mark.subprocess(parametrize={"DD_TRACE_ENABLED": ["true", "false"]}) +def test_tracer_memory_leak_span_processors(): """ Test whether the tracer or span processors will hold onto span references after the trace is complete. @@ -1927,16 +1821,20 @@ def test_tracer_memory_leak_span_processors(enabled): This is a regression test for the tracer not calling on_span_finish of SpanAggregator when the tracer was disabled and traces leaking. """ + import gc + import weakref + + from ddtrace.trace import TraceFilter + from ddtrace.trace import tracer as t + spans = weakref.WeakSet() # Filter to ensure we don't send the traces to the writer - class DropAllFilter: + class DropAllFilter(TraceFilter): def process_trace(self, trace): return None - t = Tracer() - t.enabled = enabled - t._configure(trace_processors=[DropAllFilter()]) + t.configure(trace_processors=[DropAllFilter()]) for _ in range(5): with t.trace("test") as span: @@ -1944,6 +1842,7 @@ def process_trace(self, trace): # Be sure to dereference the last Span held by the local variable `span` span = None + t.flush() # Force gc gc.collect() @@ -1984,11 +1883,9 @@ def test_finish_span_with_ancestors(tracer): assert span3.finished -def test_ctx_api(): +def test_ctx_api(tracer): from ddtrace.internal import core - tracer = Tracer() - assert core.get_item("key") is None with tracer.trace("root") as span: @@ -2034,7 +1931,7 @@ def test_asm_standalone_configuration(sca_enabled, appsec_enabled, iast_enabled) with override_env({"DD_APPSEC_SCA_ENABLED": sca_enabled}): ddtrace.config._reset() - tracer = ddtrace.trace.Tracer() + tracer = DummyTracer() tracer._configure(appsec_enabled=appsec_enabled, iast_enabled=iast_enabled, appsec_standalone_enabled=True) if sca_enabled == "true": assert bool(ddtrace.config._sca_enabled) is True @@ -2053,10 +1950,9 @@ def test_asm_standalone_configuration(sca_enabled, appsec_enabled, iast_enabled) def test_gc_not_used_on_root_spans(): - tracer = ddtrace.trace.Tracer() gc.freeze() - with tracer.trace("test-event"): + with ddtrace.tracer.trace("test-event"): pass # There should be no more span objects lingering around. @@ -2072,25 +1968,36 @@ def test_gc_not_used_on_root_spans(): # print("--------------------") +@pytest.mark.subprocess(env=dict(AWS_LAMBDA_FUNCTION_NAME="my-func")) +def test_detect_agent_config_with_lambda_extension(): + import mock + + def mock_os_path_exists(path): + return path == "/opt/extensions/datadog-agent" + + with mock.patch("os.path.exists", side_effect=mock_os_path_exists): + import ddtrace + from ddtrace.internal.writer import AgentWriter + from ddtrace.trace import tracer + + assert ddtrace.internal.serverless.in_aws_lambda() + + assert ddtrace.internal.serverless.has_aws_lambda_agent_extension() + + assert isinstance(tracer._writer, AgentWriter) + assert tracer._writer._sync_mode + + tracer._configure(enabled=False) + assert isinstance(tracer._writer, AgentWriter) + assert tracer._writer._sync_mode + + @pytest.mark.subprocess() def test_multiple_tracer_instances(): - import warnings + import pytest - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter("always") - import ddtrace + import ddtrace - assert ddtrace.tracer is not None - for w in warns: - # Ensure the warning is not about multiple tracer instances is not logged when importing ddtrace - assert "Support for multiple Tracer instances is deprecated" not in str(w.message) - - warns.clear() - t = ddtrace.trace.Tracer() - # TODO: Update this assertion when the deprecation is removed and the tracer becomes a singleton - assert t is not ddtrace.tracer - assert len(warns) == 1 - assert ( - str(warns[0].message) == "Support for multiple Tracer instances is deprecated and will be " - "removed in version '3.0.0'. Use ddtrace.tracer instead." - ) + assert ddtrace.trace.tracer is not None + with pytest.raises(ValueError): + ddtrace.trace.Tracer() diff --git a/tests/utils.py b/tests/utils.py index bc7acd68b84..5d94598ec4b 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -127,6 +127,7 @@ def override_global_config(values): "_x_datadog_tags_max_length", "_128_bit_trace_id_enabled", "_x_datadog_tags_enabled", + "_startup_logs_enabled", "_propagate_service", "env", "version", @@ -649,8 +650,8 @@ def configure(self, *args, **kwargs): self._configure(*args, **kwargs) def _configure(self, *args, **kwargs): - assert "writer" not in kwargs or isinstance( - kwargs["writer"], DummyWriterMixin + assert isinstance( + kwargs.get("writer"), (DummyWriterMixin, type(None)) ), "cannot configure writer of DummyTracer" if not kwargs.get("writer"):