Skip to content

Commit 39d5279

Browse files
mabdinurZStriker19
authored andcommitted
chore(tracing): make ddtrace.trace.Tracer a singleton (#12209)
This PR ensures an error is logged when more than one instance of the Tracer is initialized in a process. In all scenarios the global tracer must be used to trace an application. ## Checklist - [x] PR author has checked that all the criteria below are met - The PR description includes an overview of the change - The PR description articulates the motivation for the change - The change includes tests OR the PR description describes a testing strategy - The PR description notes risks associated with the change, if any - Newly-added code is easy to change - The change follows the [library release note guidelines](https://ddtrace.readthedocs.io/en/stable/releasenotes.html) - The change includes or references documentation updates if necessary - Backport labels are set (if [applicable](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)) ## Reviewer Checklist - [x] Reviewer has checked that all the criteria below are met - Title is accurate - All changes are related to the pull request's stated goal - Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes - Testing strategy adequately addresses listed risks - Newly-added code is easy to change - Release note makes sense to a user of the library - If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment - Backport labels are set in a manner that is consistent with the [release branch maintenance policy](https://ddtrace.readthedocs.io/en/latest/contributing.html#backporting)
1 parent 647757d commit 39d5279

24 files changed

+267
-391
lines changed

ddtrace/_trace/tracer.py

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -217,16 +217,8 @@ def __init__(
217217
if Tracer._instance is None:
218218
Tracer._instance = self
219219
else:
220-
# ddtrace library does not support context propagation for multiple tracers.
221-
# All instances of ddtrace ContextProviders share the same ContextVars. This means that
222-
# if you create multiple instances of Tracer, spans will be shared between them creating a
223-
# broken experience.
224-
# TODO(mabdinur): Convert this warning to an ValueError in 3.0.0
225-
deprecate(
226-
"Support for multiple Tracer instances is deprecated",
227-
". Use ddtrace.tracer instead.",
228-
category=DDTraceDeprecationWarning,
229-
removal_version="3.0.0",
220+
log.error(
221+
"Multiple Tracer instances can not be initialized. Use ``ddtrace.trace.tracer`` instead.",
230222
)
231223

232224
self._user_trace_processors: List[TraceProcessor] = []
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
upgrade:
3+
- |
4+
tracing: Drops support for multiple Tracer instances in the same process. Use ``ddtrace.trace.tracer`` to access the global tracer instance.

tests/ci_visibility/test_ci_visibility.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from ddtrace.internal.ci_visibility.git_client import METADATA_UPLOAD_STATUS
2929
from ddtrace.internal.ci_visibility.git_client import CIVisibilityGitClient
3030
from ddtrace.internal.ci_visibility.git_client import CIVisibilityGitClientSerializerV1
31+
from ddtrace.internal.ci_visibility.recorder import CIVisibilityTracer
3132
from ddtrace.internal.ci_visibility.recorder import _extract_repository_name_from_url
3233
import ddtrace.internal.test_visibility._internal_item_ids
3334
from ddtrace.internal.utils.http import Response
@@ -685,7 +686,7 @@ def test_civisibilitywriter_evp_proxy_url(self):
685686
), mock.patch(
686687
"ddtrace.internal.agent.get_trace_url", return_value="http://evpproxy.bar:1234"
687688
), mock.patch("ddtrace.settings._config.Config", _get_default_civisibility_ddconfig()), mock.patch(
688-
"ddtrace.tracer", ddtrace.trace.Tracer()
689+
"ddtrace.tracer", CIVisibilityTracer()
689690
), mock.patch(
690691
"ddtrace.internal.ci_visibility.recorder.CIVisibility._agent_evp_proxy_is_available", return_value=True
691692
), _dummy_noop_git_client(), mock.patch(
@@ -705,7 +706,7 @@ def test_civisibilitywriter_only_traces(self):
705706
)
706707
), mock.patch(
707708
"ddtrace.internal.agent.get_trace_url", return_value="http://onlytraces:1234"
708-
), mock.patch("ddtrace.tracer", ddtrace.trace.Tracer()), mock.patch(
709+
), mock.patch("ddtrace.tracer", CIVisibilityTracer()), mock.patch(
709710
"ddtrace.internal.ci_visibility.recorder.CIVisibility._agent_evp_proxy_is_available", return_value=False
710711
), mock.patch(
711712
"ddtrace.internal.ci_visibility.writer.config", ddtrace.settings.Config()
@@ -1119,7 +1120,7 @@ def test_civisibility_enable_respects_passed_in_tracer():
11191120
), _dummy_noop_git_client(), mock.patch(
11201121
"ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig()
11211122
), mock.patch("ddtrace.internal.ci_visibility.writer.config", ddtrace.settings.Config()):
1122-
tracer = ddtrace.trace.Tracer()
1123+
tracer = CIVisibilityTracer()
11231124
tracer._configure(partial_flush_enabled=False, partial_flush_min_spans=100)
11241125
CIVisibility.enable(tracer=tracer)
11251126
assert CIVisibility._instance.tracer._partial_flush_enabled is False

tests/ci_visibility/util.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from ddtrace.internal.ci_visibility.git_client import METADATA_UPLOAD_STATUS
1313
from ddtrace.internal.ci_visibility.git_client import CIVisibilityGitClient
1414
from ddtrace.internal.ci_visibility.recorder import CIVisibility
15+
from ddtrace.internal.ci_visibility.recorder import CIVisibilityTracer
1516
from ddtrace.internal.test_visibility._internal_item_ids import InternalTestId
1617
from tests.utils import DummyCIVisibilityWriter
1718
from tests.utils import override_env
@@ -209,5 +210,5 @@ def _ci_override_env(
209210
new_vars: t.Optional[t.Dict[str, str]] = None, mock_ci_env=False, replace_os_env=True, full_clear=False
210211
):
211212
env_vars = _get_default_ci_env_vars(new_vars, mock_ci_env, full_clear)
212-
with override_env(env_vars, replace_os_env=replace_os_env), mock.patch("ddtrace.tracer", ddtrace.trace.Tracer()):
213+
with override_env(env_vars, replace_os_env=replace_os_env), mock.patch("ddtrace.tracer", CIVisibilityTracer()):
213214
yield

tests/contrib/aiomysql/test_aiomysql.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from ddtrace.contrib.internal.aiomysql.patch import unpatch
1010
from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME
1111
from ddtrace.trace import Pin
12-
from ddtrace.trace import Tracer
1312
from tests.contrib import shared_tests_async as shared_tests
1413
from tests.contrib.asyncio.utils import AsyncioTestCase
1514
from tests.contrib.asyncio.utils import mark_asyncio
@@ -31,19 +30,16 @@ def patch_aiomysql():
3130
@pytest.fixture
3231
async def patched_conn(tracer):
3332
conn = await aiomysql.connect(**AIOMYSQL_CONFIG)
34-
Pin.get_from(conn).clone(tracer=tracer).onto(conn)
3533
yield conn
3634
conn.close()
3735

3836

3937
@pytest.fixture()
40-
async def snapshot_conn():
41-
tracer = Tracer()
38+
async def snapshot_conn(tracer):
4239
conn = await aiomysql.connect(**AIOMYSQL_CONFIG)
43-
Pin.get_from(conn).clone(tracer=tracer).onto(conn)
4440
yield conn
4541
conn.close()
46-
tracer.shutdown()
42+
tracer.flush()
4743

4844

4945
@pytest.mark.asyncio

tests/contrib/flask_cache/test_utils.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from ddtrace.contrib.internal.flask_cache.utils import _extract_client
77
from ddtrace.contrib.internal.flask_cache.utils import _extract_conn_tags
88
from ddtrace.contrib.internal.flask_cache.utils import _resource_from_cache_prefix
9-
from ddtrace.trace import Tracer
9+
from ddtrace.trace import tracer
1010

1111
from ..config import MEMCACHED_CONFIG
1212
from ..config import REDIS_CONFIG
@@ -17,7 +17,6 @@ class FlaskCacheUtilsTest(unittest.TestCase):
1717

1818
def test_extract_redis_connection_metadata(self):
1919
# create the TracedCache instance for a Flask app
20-
tracer = Tracer()
2120
Cache = get_traced_cache(tracer, service=self.SERVICE)
2221
app = Flask(__name__)
2322
config = {
@@ -37,7 +36,6 @@ def test_extract_redis_connection_metadata(self):
3736

3837
def test_extract_memcached_connection_metadata(self):
3938
# create the TracedCache instance for a Flask app
40-
tracer = Tracer()
4139
Cache = get_traced_cache(tracer, service=self.SERVICE)
4240
app = Flask(__name__)
4341
config = {
@@ -56,7 +54,6 @@ def test_extract_memcached_connection_metadata(self):
5654

5755
def test_extract_memcached_multiple_connection_metadata(self):
5856
# create the TracedCache instance for a Flask app
59-
tracer = Tracer()
6057
Cache = get_traced_cache(tracer, service=self.SERVICE)
6158
app = Flask(__name__)
6259
config = {
@@ -78,7 +75,6 @@ def test_extract_memcached_multiple_connection_metadata(self):
7875

7976
def test_resource_from_cache_with_prefix(self):
8077
# create the TracedCache instance for a Flask app
81-
tracer = Tracer()
8278
Cache = get_traced_cache(tracer, service=self.SERVICE)
8379
app = Flask(__name__)
8480
config = {
@@ -94,7 +90,6 @@ def test_resource_from_cache_with_prefix(self):
9490

9591
def test_resource_from_cache_with_empty_prefix(self):
9692
# create the TracedCache instance for a Flask app
97-
tracer = Tracer()
9893
Cache = get_traced_cache(tracer, service=self.SERVICE)
9994
app = Flask(__name__)
10095
config = {
@@ -110,7 +105,6 @@ def test_resource_from_cache_with_empty_prefix(self):
110105

111106
def test_resource_from_cache_without_prefix(self):
112107
# create the TracedCache instance for a Flask app
113-
tracer = Tracer()
114108
Cache = get_traced_cache(tracer, service=self.SERVICE)
115109
app = Flask(__name__)
116110
traced_cache = Cache(app, config={"CACHE_TYPE": "redis"})

tests/contrib/tornado/test_config.py

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from ddtrace.trace import TraceFilter
2-
from ddtrace.trace import Tracer
3-
from tests.utils import DummyWriter
2+
from tests.utils import DummyTracer
43

54
from .utils import TornadoTestCase
65

@@ -19,8 +18,7 @@ class TestTornadoSettings(TornadoTestCase):
1918
"""
2019

2120
def get_app(self):
22-
# Override with a real tracer
23-
self.tracer = Tracer()
21+
self.tracer = DummyTracer()
2422
super(TestTornadoSettings, self).get_app()
2523

2624
def get_settings(self):
@@ -40,25 +38,6 @@ def get_settings(self):
4038
},
4139
}
4240

43-
def test_tracer_is_properly_configured(self):
44-
# the tracer must be properly configured
45-
assert self.tracer._tags.get("env") == "production"
46-
assert self.tracer._tags.get("debug") == "false"
47-
assert self.tracer.enabled is False
48-
assert self.tracer.agent_trace_url == "http://dd-agent.service.consul:8126"
49-
50-
writer = DummyWriter()
51-
self.tracer._configure(enabled=True, writer=writer)
52-
with self.tracer.trace("keep"):
53-
pass
54-
spans = writer.pop()
55-
assert len(spans) == 1
56-
57-
with self.tracer.trace("drop"):
58-
pass
59-
spans = writer.pop()
60-
assert len(spans) == 0
61-
6241

6342
class TestTornadoSettingsEnabled(TornadoTestCase):
6443
def get_settings(self):

tests/integration/test_debug.py

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import os
44
import re
55
import subprocess
6-
from typing import List
7-
from typing import Optional
86

97
import mock
108
import pytest
@@ -13,11 +11,10 @@
1311
import ddtrace._trace.sampler
1412
from ddtrace.internal import debug
1513
from ddtrace.internal.writer import AgentWriter
16-
from ddtrace.internal.writer import TraceWriter
17-
from ddtrace.trace import Span
1814
from tests.integration.utils import AGENT_VERSION
1915
from tests.subprocesstest import SubprocessTestCase
2016
from tests.subprocesstest import run_in_subprocess
17+
from tests.utils import DummyTracer
2118

2219

2320
pytestmark = pytest.mark.skipif(AGENT_VERSION == "testagent", reason="The test agent doesn't support startup logs.")
@@ -189,13 +186,12 @@ def test_trace_agent_url(self):
189186
)
190187
)
191188
def test_tracer_loglevel_info_connection(self):
192-
tracer = ddtrace.trace.Tracer()
193189
logging.basicConfig(level=logging.INFO)
194190
with mock.patch.object(logging.Logger, "log") as mock_logger:
195191
# shove an unserializable object into the config log output
196192
# regression: this used to cause an exception to be raised
197193
ddtrace.config.version = AgentWriter(agent_url="foobar")
198-
tracer._configure()
194+
ddtrace.trace.tracer.configure()
199195
assert mock.call(logging.INFO, re_matcher("- DATADOG TRACER CONFIGURATION - ")) in mock_logger.mock_calls
200196

201197
@run_in_subprocess(
@@ -205,10 +201,9 @@ def test_tracer_loglevel_info_connection(self):
205201
)
206202
)
207203
def test_tracer_loglevel_info_no_connection(self):
208-
tracer = ddtrace.trace.Tracer()
209204
logging.basicConfig(level=logging.INFO)
210205
with mock.patch.object(logging.Logger, "log") as mock_logger:
211-
tracer._configure()
206+
ddtrace.trace.tracer.configure()
212207
assert mock.call(logging.INFO, re_matcher("- DATADOG TRACER CONFIGURATION - ")) in mock_logger.mock_calls
213208
assert mock.call(logging.WARNING, re_matcher("- DATADOG TRACER DIAGNOSTIC - ")) in mock_logger.mock_calls
214209

@@ -219,9 +214,8 @@ def test_tracer_loglevel_info_no_connection(self):
219214
)
220215
)
221216
def test_tracer_log_disabled_error(self):
222-
tracer = ddtrace.trace.Tracer()
223217
with mock.patch.object(logging.Logger, "log") as mock_logger:
224-
tracer._configure()
218+
ddtrace.trace.tracer._configure()
225219
assert mock_logger.mock_calls == []
226220

227221
@run_in_subprocess(
@@ -231,9 +225,8 @@ def test_tracer_log_disabled_error(self):
231225
)
232226
)
233227
def test_tracer_log_disabled(self):
234-
tracer = ddtrace.trace.Tracer()
235228
with mock.patch.object(logging.Logger, "log") as mock_logger:
236-
tracer._configure()
229+
ddtrace.trace.tracer._configure()
237230
assert mock_logger.mock_calls == []
238231

239232
@run_in_subprocess(
@@ -243,9 +236,8 @@ def test_tracer_log_disabled(self):
243236
)
244237
def test_tracer_info_level_log(self):
245238
logging.basicConfig(level=logging.INFO)
246-
tracer = ddtrace.trace.Tracer()
247239
with mock.patch.object(logging.Logger, "log") as mock_logger:
248-
tracer._configure()
240+
ddtrace.trace.tracer._configure()
249241
assert mock_logger.mock_calls == []
250242

251243

@@ -287,16 +279,24 @@ def test_to_json():
287279
json.dumps(info)
288280

289281

282+
@pytest.mark.subprocess(env={"AWS_LAMBDA_FUNCTION_NAME": "something"})
290283
def test_agentless(monkeypatch):
291-
monkeypatch.setenv("AWS_LAMBDA_FUNCTION_NAME", "something")
292-
tracer = ddtrace.trace.Tracer()
293-
info = debug.collect(tracer)
284+
from ddtrace.internal import debug
285+
from ddtrace.trace import tracer
294286

287+
info = debug.collect(tracer)
295288
assert info.get("agent_url") == "AGENTLESS"
296289

297290

291+
@pytest.mark.subprocess()
298292
def test_custom_writer():
299-
tracer = ddtrace.trace.Tracer()
293+
from typing import List
294+
from typing import Optional
295+
296+
from ddtrace.internal import debug
297+
from ddtrace.internal.writer import TraceWriter
298+
from ddtrace.trace import Span
299+
from ddtrace.trace import tracer
300300

301301
class CustomWriter(TraceWriter):
302302
def recreate(self) -> TraceWriter:
@@ -317,16 +317,24 @@ def flush_queue(self) -> None:
317317
assert info.get("agent_url") == "CUSTOM"
318318

319319

320+
@pytest.mark.subprocess()
320321
def test_different_samplers():
321-
tracer = ddtrace.trace.Tracer()
322+
import ddtrace
323+
from ddtrace.internal import debug
324+
from ddtrace.trace import tracer
325+
322326
tracer._configure(sampler=ddtrace._trace.sampler.RateSampler())
323327
info = debug.collect(tracer)
324328

325329
assert info.get("sampler_type") == "RateSampler"
326330

327331

332+
@pytest.mark.subprocess()
328333
def test_startup_logs_sampling_rules():
329-
tracer = ddtrace.trace.Tracer()
334+
import ddtrace
335+
from ddtrace.internal import debug
336+
from ddtrace.trace import tracer
337+
330338
sampler = ddtrace._trace.sampler.DatadogSampler(rules=[ddtrace._trace.sampler.SamplingRule(sample_rate=1.0)])
331339
tracer._configure(sampler=sampler)
332340
f = debug.collect(tracer)
@@ -415,7 +423,7 @@ def test_debug_span_log():
415423

416424

417425
def test_partial_flush_log():
418-
tracer = ddtrace.trace.Tracer()
426+
tracer = DummyTracer()
419427

420428
tracer._configure(
421429
partial_flush_enabled=True,

tests/integration/test_encoding.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,14 @@
44
import mock
55
import pytest
66

7-
from ddtrace.trace import Tracer
7+
from ddtrace.trace import tracer
88

99

1010
AGENT_VERSION = os.environ.get("AGENT_VERSION")
1111

1212

1313
class TestTraceAcceptedByAgent:
1414
def test_simple_trace_accepted_by_agent(self):
15-
tracer = Tracer()
1615
with mock.patch("ddtrace.internal.writer.writer.log") as log:
1716
with tracer.trace("root"):
1817
for _ in range(999):
@@ -32,7 +31,6 @@ def test_simple_trace_accepted_by_agent(self):
3231
)
3332
def test_trace_with_meta_accepted_by_agent(self, tags):
3433
"""Meta tags should be text types."""
35-
tracer = Tracer()
3634
with mock.patch("ddtrace.internal.writer.writer.log") as log:
3735
with tracer.trace("root", service="test_encoding", resource="test_resource") as root:
3836
root.set_tags(tags)
@@ -53,7 +51,6 @@ def test_trace_with_meta_accepted_by_agent(self, tags):
5351
)
5452
def test_trace_with_metrics_accepted_by_agent(self, metrics):
5553
"""Metric tags should be numeric types - i.e. int, float, long (py3), and str numbers."""
56-
tracer = Tracer()
5754
with mock.patch("ddtrace.internal.writer.writer.log") as log:
5855
with tracer.trace("root") as root:
5956
root.set_metrics(metrics)
@@ -72,7 +69,6 @@ def test_trace_with_metrics_accepted_by_agent(self, metrics):
7269
)
7370
def test_trace_with_links_accepted_by_agent(self, span_links_kwargs):
7471
"""Links should not break things."""
75-
tracer = Tracer()
7672
with mock.patch("ddtrace.internal.writer.writer.log") as log:
7773
with tracer.trace("root", service="test_encoding", resource="test_resource") as root:
7874
root.set_link(**span_links_kwargs)

0 commit comments

Comments
 (0)