diff --git a/ddtrace/contrib/internal/django/__init__.py b/ddtrace/contrib/internal/django/__init__.py index 195fa6242a5..3fba8fc1acc 100644 --- a/ddtrace/contrib/internal/django/__init__.py +++ b/ddtrace/contrib/internal/django/__init__.py @@ -104,7 +104,7 @@ Consider using this option if your application is performance-sensitive and the additional Django-layer spans are not required for your observability needs. - Default: ``False`` + Default: ``True`` *New in version v3.15.0.* @@ -120,40 +120,25 @@ Whether or not to instrument template rendering. - Can also be enabled with the ``DD_DJANGO_INSTRUMENT_TEMPLATES`` environment variable. + Can be enabled with the ``DD_DJANGO_INSTRUMENT_TEMPLATES=true`` or ``DD_DJANGO_TRACING_MINIMAL=false`` environment variables. - Default: ``True`` + Default: ``False`` .. py:data:: ddtrace.config.django['instrument_databases'] Whether or not to instrument databases. - Can also be enabled with the ``DD_DJANGO_INSTRUMENT_DATABASES`` environment variable. - - Default: ``True`` - -.. py:data:: ddtrace.config.django['always_create_database_spans'] - - Whether or not to enforce that a Django database span is created regardless of other - database instrumentation. + Can be enabled with the ``DD_DJANGO_INSTRUMENT_DATABASES=true`` or ``DD_DJANGO_TRACING_MINIMAL=false`` environment variables. - Enabling this will provide database spans when the database engine is not yet supported - by ``ddtrace``, however it may result in duplicate database spans when the database - engine is supported and enabled. - - Can also be enabled with the ``DD_DJANGO_ALWAYS_CREATE_DATABASE_SPANS`` environment variable. - - Default: ``True`` - - *New in version v3.13.0.* + Default: ``False`` .. py:data:: ddtrace.config.django['instrument_caches'] Whether or not to instrument caches. - Can also be enabled with the ``DD_DJANGO_INSTRUMENT_CACHES`` environment variable. + Can be enabled with the ``DD_DJANGO_INSTRUMENT_CACHES=true`` or ``DD_DJANGO_TRACING_MINIMAL=false`` environment variables. - Default: ``True`` + Default: ``False`` .. py:data:: ddtrace.config.django.http['trace_query_string'] diff --git a/ddtrace/contrib/internal/django/database.py b/ddtrace/contrib/internal/django/database.py index a56dc5e670d..659d1f324c4 100644 --- a/ddtrace/contrib/internal/django/database.py +++ b/ddtrace/contrib/internal/django/database.py @@ -67,7 +67,7 @@ def cursor(func: FunctionType, args: Tuple[Any], kwargs: Dict[str, Any]) -> Any: # Don't double wrap Django database cursors: # If the underlying cursor is already wrapped (e.g. by another library), # we just add the Django tags to the existing Pin (if any) and return - if is_wrapted(cursor.cursor) and not config_django.always_create_database_spans: + if is_wrapted(cursor.cursor): instance = args[0] tags = { "django.db.vendor": getattr(instance, "vendor", "db"), @@ -86,8 +86,8 @@ def cursor(func: FunctionType, args: Tuple[Any], kwargs: Dict[str, Any]) -> Any: return cursor # Always wrap Django database cursors: - # If the underlying cursor is not already wrapped, or if `always_create_database_spans` - # is set to True, we wrap the underlying cursor with our TracedCursor class + # If the underlying cursor is not already wrapped, + # we wrap the underlying cursor with our TracedCursor class # # This allows us to get Database spans for any query executed where we don't # have an integration for the database library in use, or in the case that diff --git a/ddtrace/contrib/internal/django/patch.py b/ddtrace/contrib/internal/django/patch.py index 8e0b56ac040..8b6d613f1ba 100644 --- a/ddtrace/contrib/internal/django/patch.py +++ b/ddtrace/contrib/internal/django/patch.py @@ -40,8 +40,7 @@ log = get_logger(__name__) -# TODO[4.0]: Change this to True by default -DJANGO_TRACING_MINIMAL = asbool(_get_config("DD_DJANGO_TRACING_MINIMAL", default=False)) +DJANGO_TRACING_MINIMAL = asbool(_get_config("DD_DJANGO_TRACING_MINIMAL", default=True)) config._add( "django", @@ -55,8 +54,6 @@ instrument_middleware=asbool(os.getenv("DD_DJANGO_INSTRUMENT_MIDDLEWARE", default=True)), instrument_templates=asbool(os.getenv("DD_DJANGO_INSTRUMENT_TEMPLATES", default=not DJANGO_TRACING_MINIMAL)), instrument_databases=asbool(os.getenv("DD_DJANGO_INSTRUMENT_DATABASES", default=not DJANGO_TRACING_MINIMAL)), - # TODO[4.0]: remove this option and make it the default behavior when databases are instrumented - always_create_database_spans=asbool(os.getenv("DD_DJANGO_ALWAYS_CREATE_DATABASE_SPANS", default=True)), instrument_caches=asbool(os.getenv("DD_DJANGO_INSTRUMENT_CACHES", default=not DJANGO_TRACING_MINIMAL)), trace_query_string=None, # Default to global config include_user_name=asm_config._django_include_user_name, diff --git a/releasenotes/notes/django-tracing-minimal-default-81e1531ed01a1980.yaml b/releasenotes/notes/django-tracing-minimal-default-81e1531ed01a1980.yaml new file mode 100644 index 00000000000..ab2d373062d --- /dev/null +++ b/releasenotes/notes/django-tracing-minimal-default-81e1531ed01a1980.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + Django: This upgrades the default tracing behavior to enable minimal tracing mode by default (``DD_DJANGO_TRACING_MINIMAL`` now defaults to ``true``). Django ORM, cache, and template instrumentation are disabled by default to eliminate duplicate span creation since library integrations for database drivers (psycopg, MySQLdb, sqlite3), cache clients (redis, memcached), template renderers (Jinja2), and other supported libraries continue to be traced. This reduces performance overhead by removing redundant Django-layer instrumentation. To restore all Django instrumentation, set ``DD_DJANGO_TRACING_MINIMAL=false``, or enable individual features using ``DD_DJANGO_INSTRUMENT_DATABASES=true``, ``DD_DJANGO_INSTRUMENT_CACHES=true``, and ``DD_DJANGO_INSTRUMENT_TEMPLATES=true``. + - | + Django: When ``DD_DJANGO_INSTRUMENT_DATABASES=true`` (default ``false``), database instrumentation now merges Django-specific tags into database driver spans created by supported integrations (psycopg, sqlite3, MySQLdb, etc.) instead of creating duplicate Django database spans. If the database cursor is not already wrapped by a supported integration, Django wraps it and creates a span. This change reduces overhead and duplicate spans while preserving visibility into database operations. diff --git a/riotfile.py b/riotfile.py index ce6cfd97c3d..1ba8eb7492e 100644 --- a/riotfile.py +++ b/riotfile.py @@ -929,6 +929,8 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT env={ "DD_CIVISIBILITY_ITR_ENABLED": "0", "DD_IAST_REQUEST_SAMPLING": "100", # Override default 30% to analyze all IAST requests + # TODO: Remove once pkg_resources warnings are no longer emitted from this internal module + "PYTHONWARNINGS": "ignore::UserWarning:ddtrace.internal.module", }, venvs=[ Venv( diff --git a/tests/appsec/integrations/django_tests/conftest.py b/tests/appsec/integrations/django_tests/conftest.py index 527fe54eb7e..68843239604 100644 --- a/tests/appsec/integrations/django_tests/conftest.py +++ b/tests/appsec/integrations/django_tests/conftest.py @@ -11,7 +11,9 @@ from ddtrace.appsec._iast._taint_tracking._context import debug_context_array_free_slots_number from ddtrace.appsec._iast.main import patch_iast from ddtrace.contrib.internal.django.patch import patch as django_patch +from ddtrace.contrib.internal.psycopg.patch import patch as psycopg_patch from ddtrace.contrib.internal.requests.patch import patch as requests_patch +from ddtrace.contrib.internal.sqlite3.patch import patch as sqlite3_patch from ddtrace.internal import core from tests.utils import DummyTracer from tests.utils import TracerSpanContainer @@ -35,7 +37,9 @@ def pytest_configure(): settings.DEBUG = False patch_iast() load_iast() + psycopg_patch() requests_patch() + sqlite3_patch() django_patch() enable_iast_propagation() django.setup() diff --git a/tests/contrib/django/test_django.py b/tests/contrib/django/test_django.py index a10478fb143..753eae11ac7 100644 --- a/tests/contrib/django/test_django.py +++ b/tests/contrib/django/test_django.py @@ -30,14 +30,12 @@ from ddtrace.ext import user from ddtrace.internal import wrapping from ddtrace.internal.compat import ensure_text -from ddtrace.internal.schema import schematize_service_name from ddtrace.propagation._utils import get_wsgi_header from ddtrace.propagation.http import HTTP_HEADER_PARENT_ID from ddtrace.propagation.http import HTTP_HEADER_SAMPLING_PRIORITY from ddtrace.propagation.http import HTTP_HEADER_TRACE_ID from tests.conftest import DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME from tests.tracer.utils_inferred_spans.test_helpers import assert_web_and_inferred_aws_api_gateway_span_data -from tests.utils import assert_dict_issuperset from tests.utils import override_config from tests.utils import override_env from tests.utils import override_global_config @@ -333,11 +331,11 @@ def test_django_request_not_found(client, test_spans): # Assert the correct number of traces and spans if django.VERSION >= (2, 0, 0): - span_count = 27 + span_count = 26 elif django.VERSION >= (1, 11, 0): - span_count = 18 + span_count = 17 else: - span_count = 16 + span_count = 15 test_spans.assert_span_count(span_count) # Assert the structure of the root `django.request` span @@ -358,19 +356,6 @@ def test_django_request_not_found(client, test_spans): }, ) - # Assert template render - render_spans = list(test_spans.filter_spans(name="django.template.render")) - assert len(render_spans) == 1 - - render_span = render_spans[0] - render_span.assert_matches( - name="django.template.render", - resource="django.template.base.Template.render", - meta={ - "django.template.engine.class": "django.template.engine.Engine", - }, - ) - def test_middleware_trace_error_500(client, test_spans): # ensures exceptions generated by views are traced @@ -718,26 +703,33 @@ def test_simple_view_head(client, test_spans): """ -@pytest.mark.django_db -def test_connection(client, test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_DATABASES": "true"}) +def test_connection(): + from tests.contrib.django.utils import setup_django_test_spans + from tests.contrib.django.utils import with_django_db + + test_spans = setup_django_test_spans() + """ When database queries are made from Django The queries are traced """ - from django.contrib.auth.models import User + with with_django_db(test_spans): + from django.contrib.auth.models import User - users = User.objects.count() - assert users == 0 + users = User.objects.count() - test_spans.assert_span_count(1) - spans = test_spans.get_spans() + assert users == 0 - span = spans[0] - assert span.name == "sqlite.query" - assert span.service == "defaultdb" - assert span.span_type == "sql" - assert span.get_tag("django.db.vendor") == "sqlite" - assert span.get_tag("django.db.alias") == "default" + test_spans.assert_span_count(1) + spans = test_spans.get_spans() + + span = spans[0] + assert span.name == "sqlite.query" + assert span.service == "defaultdb" + assert span.span_type == "sql" + assert span.get_tag("django.db.vendor") == "sqlite" + assert span.get_tag("django.db.alias") == "default" """ @@ -745,7 +737,15 @@ def test_connection(client, test_spans): """ -def test_cache_get(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -770,19 +770,37 @@ def test_cache_get(test_spans): assert_dict_issuperset(span.get_tags(), expected_meta) -def test_cache_service_schematization(test_spans): +@pytest.mark.subprocess( + env={"DD_DJANGO_INSTRUMENT_CACHES": "true", "DD_DJANGO_CACHE_SERVICE_NAME": "test-cache-service"} +) +def test_cache_service_schematization(): + import django + + from ddtrace.internal.schema import schematize_service_name + from ddtrace.internal.settings._config import config + from tests.contrib.django.utils import setup_django_test_spans + + test_spans = setup_django_test_spans() + cache = django.core.cache.caches["default"] - with override_config("django", dict(cache_service_name="test-cache-service")): - cache.get("missing_key") - spans = test_spans.get_spans() - assert spans - span = spans[0] - expected_service_name = schematize_service_name(config.django.cache_service_name) - assert span.service == expected_service_name + cache.get("missing_key") + spans = test_spans.get_spans() + assert spans + span = spans[0] + expected_service_name = schematize_service_name(config.django.cache_service_name) + assert span.service == expected_service_name + + +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get_rowcount_existing_key(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + test_spans = setup_django_test_spans() -def test_cache_get_rowcount_existing_key(test_spans): # get the default cache cache = django.core.cache.caches["default"] @@ -800,7 +818,15 @@ def test_cache_get_rowcount_existing_key(test_spans): assert_dict_issuperset(span.get_metrics(), {"db.row_count": 1}) -def test_cache_get_rowcount_missing_key(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get_rowcount_missing_key(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -816,12 +842,19 @@ def test_cache_get_rowcount_missing_key(test_spans): assert_dict_issuperset(span.get_metrics(), {"db.row_count": 0}) -class NoBool: - def __bool__(self): - raise NotImplementedError +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get_rowcount_empty_key(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + class NoBool: + def __bool__(self): + raise NotImplementedError -def test_cache_get_rowcount_empty_key(test_spans): # get the default cache cache = django.core.cache.caches["default"] cache.set(1, NoBool()) @@ -840,7 +873,15 @@ def test_cache_get_rowcount_empty_key(test_spans): assert_dict_issuperset(get_span.get_metrics(), {"db.row_count": 1}) +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) def test_cache_get_rowcount_missing_key_with_default(test_spans): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -856,22 +897,27 @@ def test_cache_get_rowcount_missing_key_with_default(test_spans): assert_dict_issuperset(span.get_metrics(), {"db.row_count": 1}) -class RaiseNotImplementedError: - def __eq__(self, _): - raise NotImplementedError +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get_rowcount_throws_attribute_and_value_error(): + import django + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset -class RaiseValueError: - def __eq__(self, _): - raise ValueError + test_spans = setup_django_test_spans() + class RaiseNotImplementedError: + def __eq__(self, _): + raise NotImplementedError -class RaiseAttributeError: - def __eq__(self, _): - raise AttributeError + class RaiseValueError: + def __eq__(self, _): + raise ValueError + class RaiseAttributeError: + def __eq__(self, _): + raise AttributeError -def test_cache_get_rowcount_throws_attribute_and_value_error(test_spans): # get the default cache cache = django.core.cache.caches["default"] @@ -916,25 +962,33 @@ def test_cache_get_rowcount_throws_attribute_and_value_error(test_spans): assert_dict_issuperset(get_3.get_metrics(), {"db.row_count": 0}) -class MockDataFrame: - def __init__(self, data): - self.data = data +@pytest.mark.skipif(django.VERSION < (2, 0, 0), reason="") +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get_rowcount_iterable_ambiguous_truthiness(): + import django + import pytest - def __eq__(self, other): - if isinstance(other, str): - return MockDataFrame([item == other for item in self.data]) - else: - return MockDataFrame([row == other for row in self.data]) + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset - def __bool__(self): - raise ValueError("Cannot determine truthiness of comparison result for DataFrame.") + test_spans = setup_django_test_spans() - def __iter__(self): - return iter(self.data) + class MockDataFrame: + def __init__(self, data): + self.data = data + def __eq__(self, other): + if isinstance(other, str): + return MockDataFrame([item == other for item in self.data]) + else: + return MockDataFrame([row == other for row in self.data]) + + def __bool__(self): + raise ValueError("Cannot determine truthiness of comparison result for DataFrame.") + + def __iter__(self): + return iter(self.data) -@pytest.mark.skipif(django.VERSION < (2, 0, 0), reason="") -def test_cache_get_rowcount_iterable_ambiguous_truthiness(test_spans): # get the default cache data = {"col1": 1, "col2": 2, "col3": 3} @@ -979,7 +1033,15 @@ def test_cache_get_rowcount_iterable_ambiguous_truthiness(test_spans): assert_dict_issuperset(get_2.get_metrics(), {"db.row_count": 0}) +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) def test_cache_get_unicode(test_spans): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -1004,7 +1066,15 @@ def test_cache_get_unicode(test_spans): assert_dict_issuperset(span.get_tags(), expected_meta) -def test_cache_set(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_set(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -1030,7 +1100,15 @@ def test_cache_set(test_spans): assert_dict_issuperset(span.get_tags(), expected_meta) -def test_cache_delete(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_delete(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -1056,7 +1134,15 @@ def test_cache_delete(test_spans): @pytest.mark.skipif(django.VERSION >= (2, 1, 0), reason="") -def test_cache_incr_1XX(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_incr_1XX(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache, set the value and reset the spans cache = django.core.cache.caches["default"] cache.set("value", 0) @@ -1093,7 +1179,15 @@ def test_cache_incr_1XX(test_spans): @pytest.mark.skipif(django.VERSION < (2, 1, 0), reason="") -def test_cache_incr_2XX(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_incr_2XX(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache, set the value and reset the spans cache = django.core.cache.caches["default"] cache.set("value", 0) @@ -1123,7 +1217,15 @@ def test_cache_incr_2XX(test_spans): @pytest.mark.skipif(django.VERSION >= (2, 1, 0), reason="") -def test_cache_decr_1XX(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_decr_1XX(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache, set the value and reset the spans cache = django.core.cache.caches["default"] cache.set("value", 0) @@ -1167,7 +1269,15 @@ def test_cache_decr_1XX(test_spans): @pytest.mark.skipif(django.VERSION < (2, 1, 0), reason="") -def test_cache_decr_2XX(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_decr_2XX(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache, set the value and reset the spans cache = django.core.cache.caches["default"] cache.set("value", 0) @@ -1203,7 +1313,15 @@ def test_cache_decr_2XX(test_spans): assert_dict_issuperset(span_decr.get_tags(), expected_meta) -def test_cache_get_many(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get_many(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -1242,7 +1360,15 @@ def test_cache_get_many(test_spans): assert_dict_issuperset(span_get_many.get_tags(), expected_meta) -def test_cache_get_many_rowcount_all_existing(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get_many_rowcount_all_existing(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -1270,7 +1396,15 @@ def test_cache_get_many_rowcount_all_existing(test_spans): assert_dict_issuperset(span_get_second.get_metrics(), {"db.row_count": 1}) -def test_cache_get_many_rowcount_none_existing(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get_many_rowcount_none_existing(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -1297,7 +1431,15 @@ def test_cache_get_many_rowcount_none_existing(test_spans): assert_dict_issuperset(span_get_second.get_metrics(), {"db.row_count": 0}) -def test_cache_get_many_rowcount_some_existing(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_get_many_rowcount_some_existing(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + from tests.utils import assert_dict_issuperset + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -1305,7 +1447,6 @@ def test_cache_get_many_rowcount_some_existing(test_spans): result = cache.get_many(["first_key", "missing_key"]) - print(result) assert result == {"first_key": 1} spans = test_spans.get_spans() @@ -1327,7 +1468,14 @@ def test_cache_get_many_rowcount_some_existing(test_spans): assert_dict_issuperset(span_get_second.get_metrics(), {"db.row_count": 0}) -def test_cache_set_many(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_set_many(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -1362,7 +1510,14 @@ def test_cache_set_many(test_spans): assert "second_key" in span_set_many.get_tag("django.cache.key") -def test_cache_delete_many(test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cache_delete_many(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + + test_spans = setup_django_test_spans() + # get the default cache cache = django.core.cache.caches["default"] @@ -1397,59 +1552,68 @@ def test_cache_delete_many(test_spans): assert "another_key" in span_delete_many.get_tag("django.cache.key") -@pytest.mark.django_db -def test_cached_view(client, test_spans): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_CACHES": "true"}) +def test_cached_view(): + from django.test import Client + + from tests.contrib.django.utils import setup_django_test_spans + from tests.contrib.django.utils import with_django_db + + test_spans = setup_django_test_spans() + # make the first request so that the view is cached - response = client.get("/cached-users/") - assert response.status_code == 200 + with with_django_db(test_spans): + client = Client() + response = client.get("/cached-users/") + assert response.status_code == 200 - # check the first call for a non-cached view - spans = list(test_spans.filter_spans(name="django.cache")) - assert len(spans) == 3 - # the cache miss - assert spans[0].resource == "django.core.cache.backends.locmem.get" - # store the result in the cache - assert spans[1].resource == "django.core.cache.backends.locmem.set" - assert spans[2].resource == "django.core.cache.backends.locmem.set" - - # check if the cache hit is traced - response = client.get("/cached-users/") - assert response.status_code == 200 - spans = list(test_spans.filter_spans(name="django.cache")) - # There should be two more spans now - assert len(spans) == 5 - - span_header = spans[3] - span_view = spans[4] - assert span_view.service == "django" - assert span_view.resource == "django.core.cache.backends.locmem.get" - assert span_view.name == "django.cache" - assert span_view.span_type == "cache" - assert span_view.error == 0 - assert span_header.service == "django" - assert span_header.resource == "django.core.cache.backends.locmem.get" - assert span_header.name == "django.cache" - assert span_header.span_type == "cache" - assert span_header.error == 0 - - expected_meta_view = { - "component": "django", - "django.cache.backend": "django.core.cache.backends.locmem.LocMemCache", - "django.cache.key": ( - "views.decorators.cache.cache_page..GET.03cdc1cc4aab71b038a6764e5fcabb82.d41d8cd98f00b204e9800998ecf8..." - ), - "_dd.base_service": "tests.contrib.django", - } + # check the first call for a non-cached view + spans = list(test_spans.filter_spans(name="django.cache")) + assert len(spans) == 3 + # the cache miss + assert spans[0].resource == "django.core.cache.backends.locmem.get" + # store the result in the cache + assert spans[1].resource == "django.core.cache.backends.locmem.set" + assert spans[2].resource == "django.core.cache.backends.locmem.set" + + # check if the cache hit is traced + response = client.get("/cached-users/") + assert response.status_code == 200 + spans = list(test_spans.filter_spans(name="django.cache")) + # There should be two more spans now + assert len(spans) == 5 + + span_header = spans[3] + span_view = spans[4] + assert span_view.service == "django" + assert span_view.resource == "django.core.cache.backends.locmem.get" + assert span_view.name == "django.cache" + assert span_view.span_type == "cache" + assert span_view.error == 0 + assert span_header.service == "django" + assert span_header.resource == "django.core.cache.backends.locmem.get" + assert span_header.name == "django.cache" + assert span_header.span_type == "cache" + assert span_header.error == 0 + + expected_meta_view = { + "component": "django", + "django.cache.backend": "django.core.cache.backends.locmem.LocMemCache", + "django.cache.key": ( + "views.decorators.cache.cache_page..GET.03cdc1cc4aab71b038a6764e5fcabb82.d41d8cd98f00b204e9800998ecf8..." + ), + "_dd.base_service": "ddtrace_subprocess_dir", + } - expected_meta_header = { - "component": "django", - "django.cache.backend": "django.core.cache.backends.locmem.LocMemCache", - "django.cache.key": "views.decorators.cache.cache_header..03cdc1cc4aab71b038a6764e5fcabb82.en-us", - "_dd.base_service": "tests.contrib.django", - } + expected_meta_header = { + "component": "django", + "django.cache.backend": "django.core.cache.backends.locmem.LocMemCache", + "django.cache.key": "views.decorators.cache.cache_header..03cdc1cc4aab71b038a6764e5fcabb82.en-us", + "_dd.base_service": "ddtrace_subprocess_dir", + } - assert span_view.get_tags() == expected_meta_view - assert span_header.get_tags() == expected_meta_header + assert span_view.get_tags() == expected_meta_view, span_view.get_tags() + assert span_header.get_tags() == expected_meta_header """ @@ -1529,16 +1693,13 @@ def test_schematized_default_db_service_name( "v1": global_service_name or DEFAULT_DDTRACE_SUBPROCESS_TEST_SERVICE_NAME, }[schema_version] code = """ -import pytest -import sys - import django -from tests.contrib.django.conftest import * -from tests.utils import override_config +from tests.contrib.django.utils import setup_django_test_spans +from tests.contrib.django.utils import with_django_db -@pytest.mark.django_db -def test_connection(client, test_spans): +test_spans = setup_django_test_spans() +with with_django_db(test_spans): from django.contrib.auth.models import User users = User.objects.count() @@ -1553,15 +1714,14 @@ def test_connection(client, test_spans): assert span.span_type == "sql" assert span.get_tag("django.db.vendor") == "sqlite" assert span.get_tag("django.db.alias") == "default" - -if __name__ == "__main__": - # --reuse-db needed so the subprocess will not delete the main process database. - sys.exit(pytest.main(["-x", "--reuse-db", __file__])) """.format( expected_service_name ) env = os.environ.copy() + env["DD_DJANGO_INSTRUMENT_DATABASES"] = "true" + env["DD_TRACE_PSYCOPG_ENABLED"] = "false" + env["DD_TRACE_SQLITE3_ENABLED"] = "false" if schema_version is not None: env["DD_TRACE_SPAN_ATTRIBUTE_SCHEMA"] = schema_version if global_service_name is not None: @@ -1613,59 +1773,93 @@ def test(client, test_spans): assert status == 0, (out, err) -@pytest.mark.django_db +@pytest.mark.subprocess( + env={ + "DD_DJANGO_INSTRUMENT_DATABASES": "true", + "DD_DJANGO_DATABASE_SERVICE_NAME_PREFIX": "my-", + } +) def test_database_service_prefix_can_be_overridden(test_spans): - with override_config("django", dict(database_service_name_prefix="my-")): + from tests.contrib.django.utils import setup_django_test_spans + from tests.contrib.django.utils import with_django_db + + test_spans = setup_django_test_spans() + + with with_django_db(test_spans): from django.contrib.auth.models import User User.objects.count() - spans = test_spans.get_spans() - assert len(spans) > 0 + spans = test_spans.get_spans() + assert len(spans) > 0 - span = spans[0] - assert span.service == "my-defaultdb" + span = spans[0] + assert span.service == "my-defaultdb" -@pytest.mark.django_db -def test_database_service_can_be_overridden(test_spans): - with override_config("django", dict(database_service_name="django-db")): +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_DATABASES": "true", "DD_DJANGO_DATABASE_SERVICE_NAME": "django-db"}) +def test_database_service_can_be_overridden(): + from tests.contrib.django.utils import setup_django_test_spans + from tests.contrib.django.utils import with_django_db + + test_spans = setup_django_test_spans() + + with with_django_db(test_spans): from django.contrib.auth.models import User User.objects.count() - spans = test_spans.get_spans() - assert len(spans) > 0 + spans = test_spans.get_spans() + assert len(spans) > 0 - span = spans[0] - assert span.service == "django-db" + span = spans[0] + assert span.service == "django-db" -@pytest.mark.django_db -def test_database_service_prefix_precedence(test_spans): - with override_config("django", dict(database_service_name="django-db", database_service_name_prefix="my-")): +@pytest.mark.subprocess( + env={ + "DD_DJANGO_INSTRUMENT_DATABASES": "true", + "DD_DJANGO_DATABASE_SERVICE_NAME": "django-db", + "DD_DJANGO_DATABASE_SERVICE_NAME_PREFIX": "my-", + } +) +def test_database_service_prefix_precedence(): + from tests.contrib.django.utils import setup_django_test_spans + from tests.contrib.django.utils import with_django_db + + test_spans = setup_django_test_spans() + + with with_django_db(test_spans): from django.contrib.auth.models import User User.objects.count() - spans = test_spans.get_spans() - assert len(spans) > 0 + spans = test_spans.get_spans() + assert len(spans) > 0 - span = spans[0] - assert span.service == "django-db" + span = spans[0] + assert span.service == "django-db" -def test_cache_service_can_be_overridden(test_spans): +@pytest.mark.subprocess( + env={"DD_DJANGO_INSTRUMENT_CACHES": "true", "DD_DJANGO_CACHE_SERVICE_NAME": "test-cache-service"} +) +def test_cache_service_can_be_overridden(): + import django + + from tests.contrib.django.utils import setup_django_test_spans + + test_spans = setup_django_test_spans() + cache = django.core.cache.caches["default"] - with override_config("django", dict(cache_service_name="test-cache-service")): - cache.get("missing_key") + cache.get("missing_key") spans = test_spans.get_spans() assert len(spans) == 1 span = spans[0] - assert span.service == "test-cache-service" + assert span.service == "test-cache-service", span.service def test_django_request_distributed(client, test_spans): @@ -1887,7 +2081,14 @@ def test_disabled_caches(client, test_spans): """ -def test_template(test_spans): +@pytest.mark.subprocess(env=dict(DD_DJANGO_INSTRUMENT_TEMPLATES="true")) +def test_template(): + import django.template + + from tests.contrib.django.utils import setup_django_test_spans + + test_spans = setup_django_test_spans() + # prepare a base template using the default engine template = django.template.Template("Hello {{name}}!") ctx = django.template.Context({"name": "Django"}) @@ -1918,23 +2119,29 @@ def test_template_no_instrumented(test_spans): properly disables template spans. """ # prepare a base template using the default engine - with override_config("django", dict(instrument_templates=False)): - template = django.template.Template("Hello {{name}}!") - ctx = django.template.Context({"name": "Django"}) + template = django.template.Template("Hello {{name}}!") + ctx = django.template.Context({"name": "Django"}) - assert template.render(ctx) == "Hello Django!" - spans = test_spans.get_spans() - assert len(spans) == 0 + assert template.render(ctx) == "Hello Django!" + spans = test_spans.get_spans() + assert len(spans) == 0 - template.name = "my-template" - assert template.render(ctx) == "Hello Django!" - spans = test_spans.get_spans() - assert len(spans) == 0 + template.name = "my-template" + assert template.render(ctx) == "Hello Django!" + spans = test_spans.get_spans() + assert len(spans) == 0 -def test_template_name(test_spans): +@pytest.mark.subprocess(env=dict(DD_DJANGO_INSTRUMENT_TEMPLATES="true")) +def test_template_name(): from pathlib import PosixPath + import django.template + + from tests.contrib.django.utils import setup_django_test_spans + + test_spans = setup_django_test_spans() + # prepare a base template using the default engine template = django.template.Template("Hello {{name}}!") @@ -2403,8 +2610,13 @@ def start_response(status, headers): assert root.resource == "GET tests.contrib.django.views.error_500" -@pytest.mark.django_db +@pytest.mark.subprocess(env={"DD_DJANGO_INSTRUMENT_DATABASES": "true"}) def test_connections_patched(): + from ddtrace.internal import wrapping + from tests.contrib.django.utils import setup_django_test_spans + + setup_django_test_spans() + from django.db import connection from django.db import connections diff --git a/tests/contrib/django/test_django_dbm.py b/tests/contrib/django/test_django_dbm.py index 4f7d2debb86..a1899ad85a8 100644 --- a/tests/contrib/django/test_django_dbm.py +++ b/tests/contrib/django/test_django_dbm.py @@ -1,90 +1,198 @@ from django.db import connections -import mock +import pytest -from ddtrace._trace.pin import Pin -from tests.contrib import shared_tests -from tests.utils import DummyTracer -from tests.utils import override_config -from tests.utils import override_dbm_config -from tests.utils import override_env -from tests.utils import override_global_config +from tests.contrib.config import POSTGRES_CONFIG -from ...contrib.config import POSTGRES_CONFIG - -def get_cursor(tracer, service=None, propagation_mode="service", tags={}): +def get_cursor(): conn = connections["postgres"] POSTGRES_CONFIG["db"] = conn.settings_dict["NAME"] - cursor = conn.cursor() + return conn.cursor() + - pin = Pin.get_from(cursor) - assert pin is not None +@pytest.mark.subprocess( + ddtrace_run=True, + env={ + "DD_DJANGO_INSTRUMENT_DATABASES": "true", + "DD_DBM_PROPAGATION_MODE": "full", + "DD_TRACE_PSYCOPG_ENABLED": "false", + }, +) +def test_django_postgres_dbm_propagation_enabled(): + import django - pin._clone(tracer=tracer, tags={**pin.tags, **tags}).onto(cursor) + from ddtrace.contrib.internal.django.database import instrument_dbs + from ddtrace.internal.settings._config import config + from tests.contrib import shared_tests + from tests.contrib.django.test_django_dbm import get_cursor + from tests.utils import DummyTracer - return cursor + instrument_dbs(django) + tracer = DummyTracer() + config.django._tracer = tracer -def test_django_postgres_dbm_propagation_enabled(tracer, transactional_db): - with override_dbm_config({"propagation_mode": "full"}): - tracer = DummyTracer() + cursor = get_cursor() - cursor = get_cursor(tracer) - shared_tests._test_dbm_propagation_enabled(tracer, cursor, "postgres") + shared_tests._test_dbm_propagation_enabled(tracer, cursor, "postgres") -def test_django_postgres_dbm_propagation_comment_with_global_service_name_configured(tracer, transactional_db): +@pytest.mark.subprocess( + ddtrace_run=True, + env={ + "DD_DJANGO_INSTRUMENT_DATABASES": "true", + "DD_DBM_PROPAGATION_MODE": "service", + "DD_TRACE_PSYCOPG_ENABLED": "false", + "DD_SERVICE": "orders-app", + "DD_ENV": "staging", + "DD_VERSION": "v7343437-d7ac743", + }, +) +def test_django_postgres_dbm_propagation_comment_with_global_service_name_configured(): """tests if dbm comment is set in postgres""" - with override_global_config({"service": "orders-app", "env": "staging", "version": "v7343437-d7ac743"}): - with override_dbm_config({"propagation_mode": "service"}): - cursor = get_cursor(tracer) - cursor.__wrapped__ = mock.Mock() - shared_tests._test_dbm_propagation_comment_with_global_service_name_configured( - config=POSTGRES_CONFIG, db_system="postgresdb", cursor=cursor, wrapped_instance=cursor.__wrapped__ - ) + import django + import mock + + from ddtrace.contrib.internal.django.database import instrument_dbs + from ddtrace.internal.settings._config import config + from tests.contrib import shared_tests + from tests.contrib.config import POSTGRES_CONFIG + from tests.contrib.django.test_django_dbm import get_cursor + from tests.utils import DummyTracer + + instrument_dbs(django) + + tracer = DummyTracer() + config.django._tracer = tracer + + cursor = get_cursor() + cursor.__wrapped__ = mock.Mock() + + shared_tests._test_dbm_propagation_comment_with_global_service_name_configured( + config=POSTGRES_CONFIG, + db_system="postgresdb", + cursor=cursor, + wrapped_instance=cursor.__wrapped__, + ) + + +@pytest.mark.subprocess( + ddtrace_run=True, + env={ + "DD_DJANGO_INSTRUMENT_DATABASES": "true", + "DD_DJANGO_DATABASE_SERVICE_NAME": "service-name-override", + "DD_DBM_PROPAGATION_MODE": "service", + "DD_TRACE_PSYCOPG_ENABLED": "false", + "DD_SERVICE": "orders-app", + "DD_ENV": "staging", + "DD_VERSION": "v7343437-d7ac743", + }, +) +def test_django_postgres_dbm_propagation_comment_integration_service_name_override(): + """tests if dbm comment is set in postgres""" + import django + import mock + + from ddtrace.contrib.internal.django.database import instrument_dbs + from ddtrace.internal.settings._config import config + from tests.contrib import shared_tests + from tests.contrib.config import POSTGRES_CONFIG + from tests.contrib.django.test_django_dbm import get_cursor + from tests.utils import DummyTracer + + instrument_dbs(django) + + tracer = DummyTracer() + config.django._tracer = tracer + + cursor = get_cursor() + cursor.__wrapped__ = mock.Mock() + + shared_tests._test_dbm_propagation_comment_integration_service_name_override( + config=POSTGRES_CONFIG, cursor=cursor, wrapped_instance=cursor.__wrapped__ + ) + + +@pytest.mark.subprocess( + ddtrace_run=True, + env={ + "DD_DJANGO_INSTRUMENT_DATABASES": "true", + "DD_DJANGO_DATABASE_SERVICE_NAME": "service-name-override", + "DD_DBM_PROPAGATION_MODE": "service", + "DD_TRACE_PSYCOPG_ENABLED": "false", + "DD_SERVICE": "orders-app", + "DD_ENV": "staging", + "DD_VERSION": "v7343437-d7ac743", + }, +) +def test_django_postgres_dbm_propagation_comment_pin_service_name_override(): + """tests if dbm comment is set in postgres""" -def test_django_postgres_dbm_propagation_comment_integration_service_name_override(tracer, transactional_db): + import django + from django.db import connections + import mock + + from ddtrace.contrib.internal.django.database import instrument_dbs + from ddtrace.internal.settings._config import config + from tests.contrib import shared_tests + from tests.contrib.config import POSTGRES_CONFIG + from tests.contrib.django.test_django_dbm import get_cursor + from tests.utils import DummyTracer + + instrument_dbs(django) + + tracer = DummyTracer() + config.django._tracer = tracer + + cursor = get_cursor() + cursor.__wrapped__ = mock.Mock() + + shared_tests._test_dbm_propagation_comment_pin_service_name_override( + config=POSTGRES_CONFIG, + cursor=cursor, + tracer=tracer, + wrapped_instance=cursor.__wrapped__, + conn=connections["postgres"], + ) + + +@pytest.mark.subprocess( + ddtrace_run=True, + env={ + "DD_DJANGO_INSTRUMENT_DATABASES": "true", + "DD_DJANGO_DATABASE_SERVICE_NAME": "service-name-override", + "DD_DBM_PROPAGATION_MODE": "service", + "DD_TRACE_PSYCOPG_ENABLED": "false", + "DD_SERVICE": "orders-app", + "DD_ENV": "staging", + "DD_VERSION": "v7343437-d7ac743", + "DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED": "true", + }, +) +def test_django_postgres_dbm_propagation_comment_peer_service_enabled(): """tests if dbm comment is set in postgres""" - with override_global_config({"service": "orders-app", "env": "staging", "version": "v7343437-d7ac743"}): - with override_config("django", {"database_service_name": "service-name-override"}): - with override_dbm_config({"propagation_mode": "service"}): - cursor = get_cursor(tracer) - cursor.__wrapped__ = mock.Mock() - shared_tests._test_dbm_propagation_comment_integration_service_name_override( - config=POSTGRES_CONFIG, cursor=cursor, wrapped_instance=cursor.__wrapped__ - ) + import django + import mock + from ddtrace.contrib.internal.django.database import instrument_dbs + from ddtrace.internal.settings._config import config + from tests.contrib import shared_tests + from tests.contrib.config import POSTGRES_CONFIG + from tests.contrib.django.test_django_dbm import get_cursor + from tests.utils import DummyTracer -def test_django_postgres_dbm_propagation_comment_pin_service_name_override(tracer, transactional_db): - """tests if dbm comment is set in postgres""" - with override_global_config({"service": "orders-app", "env": "staging", "version": "v7343437-d7ac743"}): - with override_config("django", {"database_service_name": "service-name-override"}): - with override_dbm_config({"propagation_mode": "service"}): - cursor = get_cursor(tracer) - cursor.__wrapped__ = mock.Mock() - - shared_tests._test_dbm_propagation_comment_pin_service_name_override( - config=POSTGRES_CONFIG, - cursor=cursor, - tracer=tracer, - wrapped_instance=cursor.__wrapped__, - conn=connections["postgres"], - ) - - -def test_django_postgres_dbm_propagation_comment_peer_service_enabled(tracer, transactional_db): - """tests if dbm comment is set in postgres""" - with override_global_config({"service": "orders-app", "env": "staging", "version": "v7343437-d7ac743"}): - with override_env({"DD_TRACE_PEER_SERVICE_DEFAULTS_ENABLED": "True"}): - with override_config("django", {"database_service_name": "service-name-override"}): - with override_dbm_config({"propagation_mode": "service"}): - cursor = get_cursor(tracer) - cursor.__wrapped__ = mock.Mock() - - shared_tests._test_dbm_propagation_comment_peer_service_enabled( - config=POSTGRES_CONFIG, cursor=cursor, wrapped_instance=cursor.__wrapped__ - ) + instrument_dbs(django) + + tracer = DummyTracer() + config.django._tracer = tracer + + cursor = get_cursor() + cursor.__wrapped__ = mock.Mock() + + shared_tests._test_dbm_propagation_comment_peer_service_enabled( + config=POSTGRES_CONFIG, cursor=cursor, wrapped_instance=cursor.__wrapped__ + ) diff --git a/tests/contrib/django/test_django_patch.py b/tests/contrib/django/test_django_patch.py index 8c527d6cee9..1312a3016ab 100644 --- a/tests/contrib/django/test_django_patch.py +++ b/tests/contrib/django/test_django_patch.py @@ -1,3 +1,5 @@ +import pytest + from ddtrace.contrib.internal.django.patch import get_version from ddtrace.contrib.internal.django.patch import patch from tests.contrib.patch import PatchTestCase @@ -22,7 +24,7 @@ def assert_module_patched(self, django): import django.template.base - self.assert_wrapped(django.template.base.Template.render) + self.assert_not_wrapped(django.template.base.Template.render) if django.VERSION >= (2, 0, 0): self.assert_wrapped(django.urls.path) self.assert_wrapped(django.urls.re_path) @@ -46,9 +48,27 @@ def assert_not_module_double_patched(self, django): self.assert_not_double_wrapped(django.apps.registry.Apps.populate) self.assert_not_double_wrapped(django.core.handlers.base.BaseHandler.load_middleware) self.assert_not_double_wrapped(django.core.handlers.base.BaseHandler.get_response) - self.assert_not_double_wrapped(django.template.base.Template.render) + self.assert_not_wrapped(django.template.base.Template.render) if django.VERSION >= (2, 0, 0): self.assert_not_double_wrapped(django.urls.path) self.assert_not_double_wrapped(django.urls.re_path) self.assert_not_double_wrapped(django.views.generic.base.View.as_view) + + +@pytest.mark.subprocess(ddtrace_run=True, env={"DD_DJANGO_INSTRUMENT_TEMPLATES": "true"}) +def test_instrument_templates_patching(): + import django.template.base + + from ddtrace.internal.wrapping import is_wrapped + + assert is_wrapped(django.template.base.Template.render) + + +@pytest.mark.subprocess(ddtrace_run=True, env={"DD_DJANGO_TRACING_MINIMAL": "false"}) +def test_tracing_minimal_patching(): + import django.template.base + + from ddtrace.internal.wrapping import is_wrapped + + assert is_wrapped(django.template.base.Template.render) diff --git a/tests/contrib/django/test_django_snapshots.py b/tests/contrib/django/test_django_snapshots.py index 4a75d62be76..7fe2f0e04e9 100644 --- a/tests/contrib/django/test_django_snapshots.py +++ b/tests/contrib/django/test_django_snapshots.py @@ -8,7 +8,6 @@ import pytest from tests.utils import _build_env -from tests.utils import override_config from tests.utils import package_installed from tests.utils import snapshot from tests.webclient import Client @@ -108,7 +107,7 @@ def test_middleware_trace_callable_view(client): @pytest.mark.skipif( sys.version_info >= (3, 10, 0), - reason=("func_name changed with Python 3.10 which changes the resource name." "TODO: new snapshot required."), + reason=("func_name changed with Python 3.10 which changes the resource name.TODO: new snapshot required."), ) @snapshot( variants={ @@ -151,105 +150,50 @@ def test_404_exceptions(client): assert client.get("/404-view/").status_code == 404 -@pytest.fixture() -def always_create_database_spans(): - # Default value - yield True +@pytest.mark.skipif( + django.VERSION >= (4, 2, 0) and package_installed("psycopg"), + reason="Django 4.2.0 prefers psycopg3 if both are installed", +) +@pytest.mark.snapshot(ignores=SNAPSHOT_IGNORES + ["meta.out.host", "metrics._dd.tracer_kr"]) +@pytest.mark.subprocess(ddtrace_run=True, env={"DD_DJANGO_INSTRUMENT_DATABASES": "true"}) +def test_psycopg2_query_default(): + """Execute a psycopg2 query on a Django database wrapper.""" + from tests.contrib.django.utils import setup_django - -@pytest.fixture() -def psycopg2_patched(always_create_database_spans: bool, transactional_db): - from django.db import connections - - from ddtrace.contrib.internal.psycopg.patch import patch - from ddtrace.contrib.internal.psycopg.patch import unpatch - - with override_config("django", {"always_create_database_spans": always_create_database_spans}): - patch() - - # # force recreate connection to ensure psycopg2 patching has occurred - del connections["postgres"] - connections["postgres"].close() - connections["postgres"].connect() - - yield - - unpatch() - - -@pytest.mark.django_db -@pytest.mark.parametrize("always_create_database_spans", (True, False)) -def test_psycopg2_query_default(always_create_database_spans: bool, client, snapshot_context, psycopg2_patched): - """Execute a psycopg2 query on a Django database wrapper. - - If we use @snapshot decorator in a Django snapshot test, the first test adds DB creation traces - """ - if django.VERSION >= (4, 2, 0) and package_installed("psycopg"): - # skip test if both versions are available as psycopg2.sql.SQL statement will cause an error from psycopg3 - pytest.skip(reason="Django versions over 4.2.0 use psycopg3 if both psycopg3 and psycopg2 are installed.") + setup_django() from django.db import connections from psycopg2.sql import SQL as SQL2 - with snapshot_context(ignores=SNAPSHOT_IGNORES + ["meta.out.host", "metrics._dd.tracer_kr"]): - query = SQL2("""select 'one' as x""") - conn = connections["postgres"] - with conn.cursor() as cur: - cur.execute(query) - rows = cur.fetchall() - assert len(rows) == 1, rows - assert rows[0][0] == "one" - - -@pytest.fixture() -def psycopg3_patched(always_create_database_spans: bool, transactional_db): - # If Django version >= 4.2.0, check if psycopg3 is installed, - # as we test Django>=4.2 with psycopg2 solely installed and not psycopg3 to ensure both work. - if django.VERSION < (4, 2, 0): - pytest.skip(reason="Psycopg3 not supported in django<4.2") - else: - from django.db import connections - - from ddtrace.contrib.internal.psycopg.patch import patch - from ddtrace.contrib.internal.psycopg.patch import unpatch - - with override_config("django", {"always_create_database_spans": always_create_database_spans}): - patch() - - # # force recreate connection to ensure psycopg3 patching has occurred - del connections["postgres"] - connections["postgres"].close() - connections["postgres"].connect() + query = SQL2("""select 'one' as x""") + conn = connections["postgres"] + with conn.cursor() as cur: + cur.execute(query) + rows = cur.fetchall() + assert len(rows) == 1, rows + assert rows[0][0] == "one" - yield - unpatch() - - -@pytest.mark.django_db @pytest.mark.skipif(django.VERSION < (4, 2, 0), reason="Psycopg3 not supported in django<4.2") -@pytest.mark.parametrize("always_create_database_spans", (True, False)) -def test_psycopg3_query_default(always_create_database_spans: bool, client, snapshot_context, psycopg3_patched): - """Execute a psycopg3 query on a Django database wrapper. +@pytest.mark.skipif(not package_installed("psycopg"), reason="Psycopg3 not installed") +@pytest.mark.snapshot(ignores=SNAPSHOT_IGNORES + ["meta.out.host", "metrics._dd.tracer_kr"]) +@pytest.mark.subprocess(ddtrace_run=True, env={"DD_DJANGO_INSTRUMENT_DATABASES": "true"}) +def test_psycopg3_query_default(): + """Execute a psycopg3 query on a Django database wrapper.""" + from tests.contrib.django.utils import setup_django - If we use @snapshot decorator in a Django snapshot test, the first test adds DB creation traces - """ - - if not package_installed("psycopg"): - # skip test if psycopg3 is not installed as we need to test psycopg2 standalone with Django>=4.2.0 - pytest.skip(reason="Psycopg3 not installed. Focusing on testing psycopg2 with Django>=4.2.0") + setup_django() from django.db import connections from psycopg.sql import SQL - with snapshot_context(ignores=SNAPSHOT_IGNORES + ["meta.out.host", "metrics._dd.tracer_kr"]): - query = SQL("""select 'one' as x""") - conn = connections["postgres"] - with conn.cursor() as cur: - cur.execute(query) - rows = cur.fetchall() - assert len(rows) == 1, rows - assert rows[0][0] == "one" + query = SQL("""select 'one' as x""") + conn = connections["postgres"] + with conn.cursor() as cur: + cur.execute(query) + rows = cur.fetchall() + assert len(rows) == 1, rows + assert rows[0][0] == "one" @pytest.mark.skipif(django.VERSION < (3, 0, 0), reason="ASGI not supported in django<3") @@ -311,7 +255,7 @@ def test_asgi_500(): ) def test_templates_enabled(): """Default behavior to compare with disabled variant""" - with daphne_client("application") as (client, _): + with daphne_client("application", additional_env={"DD_DJANGO_INSTRUMENT_TEMPLATES": "true"}) as (client, _): resp = client.get("/template-view/", timeout=10) assert resp.status_code == 200 assert resp.content == b"some content\n" diff --git a/tests/contrib/django/utils.py b/tests/contrib/django/utils.py index f69140a0456..9e1b69ad86c 100644 --- a/tests/contrib/django/utils.py +++ b/tests/contrib/django/utils.py @@ -1,3 +1,5 @@ +import contextlib + from zeep import Client from zeep.transports import Transport @@ -12,3 +14,42 @@ def make_soap_request(url): print(f"ErrorText: {response.errorText}") return response + + +def setup_django(): + import django + + from ddtrace.contrib.internal.django.patch import patch + + patch() + django.setup() + + +def setup_django_test_spans(): + setup_django() + + from ddtrace.internal.settings._config import config + from tests.utils import DummyTracer + from tests.utils import TracerSpanContainer + + config.django._tracer = DummyTracer() + return TracerSpanContainer(config.django._tracer) + + +@contextlib.contextmanager +def with_django_db(test_spans=None): + from django.test.utils import setup_databases + from django.test.utils import teardown_databases + + old_config = setup_databases( + verbosity=0, + interactive=False, + keepdb=False, + ) + if test_spans is not None: + # Clear the migration spans + test_spans.reset() + try: + yield + finally: + teardown_databases(old_config, verbosity=0) diff --git a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json index 8f2fc75b91a..622caeb0da7 100644 --- a/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json +++ b/tests/snapshots/tests.contrib.django.test_django_appsec_snapshots.test_appsec_enabled_attack.json @@ -367,28 +367,12 @@ "duration": 13208, "start": 1754925803975252719 }, - { - "name": "django.template.render", - "service": "django", - "resource": "django.template.base.Template.render", - "trace_id": 0, - "span_id": 26, - "parent_id": 23, - "type": "template", - "meta": { - "_dd.base_service": "", - "component": "django", - "django.template.engine.class": "django.template.engine.Engine" - }, - "duration": 38708, - "start": 1754925803984285594 - }, { "name": "django.middleware", "service": "django", "resource": "django.views.decorators.csrf._EnsureCsrfToken.process_response", "trace_id": 0, - "span_id": 27, + "span_id": 26, "parent_id": 23, "meta": { "_dd.base_service": "", diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions.json index 73c06db4c88..01235fbbea8 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions.json @@ -387,29 +387,12 @@ "duration": 40458, "start": 1754919068291315545 }, - { - "name": "django.template.render", - "service": "django", - "resource": "django.template.base.Template.render", - "trace_id": 0, - "span_id": 30, - "parent_id": 23, - "type": "template", - "error": 0, - "meta": { - "_dd.base_service": "tests.contrib.django", - "component": "django", - "django.template.engine.class": "django.template.engine.Engine" - }, - "duration": 113167, - "start": 1754919068301511295 - }, { "name": "django.middleware", "service": "django", "resource": "django.views.decorators.csrf._EnsureCsrfToken.process_response", "trace_id": 0, - "span_id": 31, + "span_id": 30, "parent_id": 23, "type": "", "error": 0, diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_111x.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_111x.json index 7e4f138ed68..b7e2592aee8 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_111x.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_111x.json @@ -186,30 +186,12 @@ "duration": 38000, "start": 1692907166412379000 }, - { - "name": "django.template.render", - "service": "django", - "resource": "django.template.base.Template.render", - "trace_id": 0, - "span_id": 11, - "parent_id": 1, - "type": "template", - "error": 0, - "meta": { - "_dd.base_service": "", - "_dd.p.tid": "654a694400000000", - "component": "django", - "django.template.engine.class": "django.template.engine.Engine" - }, - "duration": 128000, - "start": 1692907166414461000 - }, { "name": "django.middleware", "service": "django", "resource": "django.middleware.csrf.CsrfViewMiddleware.process_response", "trace_id": 0, - "span_id": 12, + "span_id": 11, "parent_id": 1, "type": "", "error": 0, @@ -226,7 +208,7 @@ "service": "django", "resource": "django.middleware.security.SecurityMiddleware.process_response", "trace_id": 0, - "span_id": 13, + "span_id": 12, "parent_id": 1, "type": "", "error": 0, @@ -243,7 +225,7 @@ "service": "django", "resource": "django.middleware.clickjacking.XFrameOptionsMiddleware.process_response", "trace_id": 0, - "span_id": 14, + "span_id": 13, "parent_id": 1, "type": "", "error": 0, @@ -260,7 +242,7 @@ "service": "django", "resource": "django.contrib.messages.middleware.MessageMiddleware.process_response", "trace_id": 0, - "span_id": 15, + "span_id": 14, "parent_id": 1, "type": "", "error": 0, @@ -277,7 +259,7 @@ "service": "django", "resource": "django.middleware.csrf.CsrfViewMiddleware.process_response", "trace_id": 0, - "span_id": 16, + "span_id": 15, "parent_id": 1, "type": "", "error": 0, @@ -294,7 +276,7 @@ "service": "django", "resource": "django.middleware.common.CommonMiddleware.process_response", "trace_id": 0, - "span_id": 17, + "span_id": 16, "parent_id": 1, "type": "", "error": 0, @@ -311,7 +293,7 @@ "service": "django", "resource": "django.contrib.sessions.middleware.SessionMiddleware.process_response", "trace_id": 0, - "span_id": 18, + "span_id": 17, "parent_id": 1, "type": "", "error": 0, diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_18x.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_18x.json index 9affb87eb10..a6d758cdc41 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_18x.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_18x.json @@ -144,29 +144,12 @@ "duration": 325000, "start": 1633584190407138000 }, - { - "name": "django.template.render", - "service": "django", - "resource": "django.template.base.Template.render", - "trace_id": 0, - "span_id": 9, - "parent_id": 1, - "type": "template", - "error": 0, - "meta": { - "_dd.p.tid": "654a694400000000", - "component": "django", - "django.template.engine.class": "django.template.engine.Engine" - }, - "duration": 313000, - "start": 1633584190410760000 - }, { "name": "django.middleware", "service": "django", "resource": "django.middleware.csrf.CsrfViewMiddleware.process_response", "trace_id": 0, - "span_id": 10, + "span_id": 9, "parent_id": 1, "type": "", "error": 0, @@ -182,7 +165,7 @@ "service": "django", "resource": "django.middleware.security.SecurityMiddleware.process_response", "trace_id": 0, - "span_id": 11, + "span_id": 10, "parent_id": 1, "type": "", "error": 0, @@ -198,7 +181,7 @@ "service": "django", "resource": "django.middleware.clickjacking.XFrameOptionsMiddleware.process_response", "trace_id": 0, - "span_id": 12, + "span_id": 11, "parent_id": 1, "type": "", "error": 0, @@ -214,7 +197,7 @@ "service": "django", "resource": "django.contrib.messages.middleware.MessageMiddleware.process_response", "trace_id": 0, - "span_id": 13, + "span_id": 12, "parent_id": 1, "type": "", "error": 0, @@ -230,7 +213,7 @@ "service": "django", "resource": "django.middleware.csrf.CsrfViewMiddleware.process_response", "trace_id": 0, - "span_id": 14, + "span_id": 13, "parent_id": 1, "type": "", "error": 0, @@ -246,7 +229,7 @@ "service": "django", "resource": "django.middleware.common.CommonMiddleware.process_response", "trace_id": 0, - "span_id": 15, + "span_id": 14, "parent_id": 1, "type": "", "error": 0, @@ -262,7 +245,7 @@ "service": "django", "resource": "django.contrib.sessions.middleware.SessionMiddleware.process_response", "trace_id": 0, - "span_id": 16, + "span_id": 15, "parent_id": 1, "type": "", "error": 0, diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_21x.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_21x.json index a62be36b9db..82fa6e505cd 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_21x.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_404_exceptions_21x.json @@ -385,29 +385,12 @@ "duration": 9000, "start": 1669648104159881000 }, - { - "name": "django.template.render", - "service": "django", - "resource": "django.template.base.Template.render", - "trace_id": 0, - "span_id": 30, - "parent_id": 23, - "type": "template", - "error": 0, - "meta": { - "_dd.p.tid": "654a694400000000", - "component": "django", - "django.template.engine.class": "django.template.engine.Engine" - }, - "duration": 214000, - "start": 1669648104160117000 - }, { "name": "django.middleware", "service": "django", "resource": "django.middleware.csrf.CsrfViewMiddleware.process_response", "trace_id": 0, - "span_id": 31, + "span_id": 30, "parent_id": 23, "type": "", "error": 0, diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_500_3x.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_500_3x.json index be177a1f724..b9d84377e7d 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_500_3x.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_asgi_500_3x.json @@ -409,23 +409,6 @@ "duration": 30667, "start": 1754921175203619881 }, - { - "name": "django.template.render", - "service": "django", - "resource": "django.template.base.Template.render", - "trace_id": 0, - "span_id": 31, - "parent_id": 23, - "type": "template", - "error": 0, - "meta": { - "_dd.base_service": "", - "component": "django", - "django.template.engine.class": "django.template.engine.Engine" - }, - "duration": 4145500, - "start": 1754921175211694215 - }, { "name": "django.middleware", "service": "django", diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default.json new file mode 100644 index 00000000000..ba0e74330d6 --- /dev/null +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default.json @@ -0,0 +1,78 @@ +[[ + { + "name": "postgres.query", + "service": "postgres", + "resource": "SELECT set_config('TimeZone', %s, false)", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "sql", + "error": 0, + "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.p.dm": "-0", + "_dd.p.tid": "6913a9ec00000000", + "component": "psycopg", + "db.application": "None", + "db.name": "postgres", + "db.system": "postgresql", + "db.user": "postgres", + "django.db.alias": "postgres", + "django.db.vendor": "postgresql", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "425ea5115eb047fa9fcaeafd3e15afdc", + "server.address": "127.0.0.1", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 1, + "network.destination.port": 5432, + "process_id": 1416 + }, + "duration": 531542, + "start": 1762896364850910298 + }], +[ + { + "name": "postgres.query", + "service": "postgres", + "resource": "select 'one' as x", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "sql", + "error": 0, + "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.p.dm": "-0", + "_dd.p.tid": "6913a9ec00000000", + "component": "psycopg", + "db.application": "None", + "db.name": "postgres", + "db.system": "postgresql", + "db.user": "postgres", + "django.db.alias": "postgres", + "django.db.vendor": "postgresql", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "425ea5115eb047fa9fcaeafd3e15afdc", + "server.address": "127.0.0.1", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 1, + "network.destination.port": 5432, + "process_id": 1416 + }, + "duration": 126333, + "start": 1762896364851734590 + }]] diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default[False].json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default[False].json index b3ce6961719..b516f62e5ff 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default[False].json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default[False].json @@ -17,8 +17,6 @@ "db.name": "test_postgres", "db.system": "postgresql", "db.user": "postgres", - "django.db.alias": "postgres", - "django.db.vendor": "postgresql", "language": "python", "out.host": "127.0.0.1", "runtime-id": "b29b553f30da448fa6e487f751a456fc", diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default[True].json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default[True].json index cf79230956a..6ff5a0918d2 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default[True].json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg2_query_default[True].json @@ -1,7 +1,7 @@ [[ { "name": "postgres.query", - "service": "postgresdb", + "service": "postgres", "resource": "select 'one' as x", "trace_id": 0, "span_id": 1, @@ -12,11 +12,11 @@ "_dd.base_service": "tests.contrib.django", "_dd.p.dm": "-0", "_dd.p.tid": "68a371a500000000", - "component": "django-database", + "component": "psycopg", + "db.application": "None", "db.name": "test_postgres", + "db.system": "postgresql", "db.user": "postgres", - "django.db.alias": "postgres", - "django.db.vendor": "postgresql", "language": "python", "out.host": "127.0.0.1", "runtime-id": "989125d097ec4fc3a5c7572974347510", @@ -34,33 +34,4 @@ }, "duration": 1153250, "start": 1755541925158261419 - }, - { - "name": "postgres.query", - "service": "postgres", - "resource": "select 'one' as x", - "trace_id": 0, - "span_id": 2, - "parent_id": 1, - "type": "sql", - "error": 0, - "meta": { - "_dd.base_service": "tests.contrib.django", - "component": "psycopg", - "db.application": "None", - "db.name": "test_postgres", - "db.system": "postgresql", - "db.user": "postgres", - "out.host": "127.0.0.1", - "server.address": "127.0.0.1", - "span.kind": "client" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "db.row_count": 1, - "network.destination.port": 5432 - }, - "duration": 790584, - "start": 1755541925158563294 - }]] + }]] diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default.json new file mode 100644 index 00000000000..2447d089992 --- /dev/null +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default.json @@ -0,0 +1,78 @@ +[[ + { + "name": "postgres.query", + "service": "postgres", + "resource": "SELECT set_config('TimeZone', %s, false)", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "sql", + "error": 0, + "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.p.dm": "-0", + "_dd.p.tid": "6913a07500000000", + "component": "psycopg", + "db.application": "None", + "db.name": "postgres", + "db.system": "postgresql", + "db.user": "postgres", + "django.db.alias": "postgres", + "django.db.vendor": "postgresql", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "29c5e48f968f4341ab5038f9a3a40e5a", + "server.address": "127.0.0.1", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 1, + "network.destination.port": 5432, + "process_id": 690 + }, + "duration": 512000, + "start": 1762893941787372760 + }], +[ + { + "name": "postgres.query", + "service": "postgres", + "resource": "select 'one' as x", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "sql", + "error": 0, + "meta": { + "_dd.base_service": "ddtrace_subprocess_dir", + "_dd.p.dm": "-0", + "_dd.p.tid": "6913a07500000000", + "component": "psycopg", + "db.application": "None", + "db.name": "postgres", + "db.system": "postgresql", + "db.user": "postgres", + "django.db.alias": "postgres", + "django.db.vendor": "postgresql", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "29c5e48f968f4341ab5038f9a3a40e5a", + "server.address": "127.0.0.1", + "span.kind": "client" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 1, + "network.destination.port": 5432, + "process_id": 690 + }, + "duration": 162000, + "start": 1762893941788237219 + }]] diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default[False].json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default[False].json deleted file mode 100644 index 6652a002e8a..00000000000 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default[False].json +++ /dev/null @@ -1,39 +0,0 @@ -[[ - { - "name": "postgres.query", - "service": "postgres", - "resource": "select 'one' as x", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "sql", - "error": 0, - "meta": { - "_dd.base_service": "tests.contrib.django", - "_dd.p.dm": "-0", - "_dd.p.tid": "689f9ed400000000", - "component": "psycopg", - "db.application": "None", - "db.name": "test_postgres", - "db.system": "postgresql", - "db.user": "postgres", - "django.db.alias": "postgres", - "django.db.vendor": "postgresql", - "language": "python", - "out.host": "127.0.0.1", - "runtime-id": "f0d4b94a202a415496244aa08360304d", - "server.address": "127.0.0.1", - "span.kind": "client" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "db.row_count": 1, - "network.destination.port": 5432, - "process_id": 426 - }, - "duration": 256292, - "start": 1755291348453980512 - }]] diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default[True].json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default[True].json deleted file mode 100644 index c8f71ab5645..00000000000 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_psycopg3_query_default[True].json +++ /dev/null @@ -1,66 +0,0 @@ -[[ - { - "name": "postgres.query", - "service": "postgresdb", - "resource": "select 'one' as x", - "trace_id": 0, - "span_id": 1, - "parent_id": 0, - "type": "sql", - "error": 0, - "meta": { - "_dd.base_service": "tests.contrib.django", - "_dd.p.dm": "-0", - "_dd.p.tid": "689f9ed400000000", - "component": "django-database", - "db.name": "test_postgres", - "db.user": "postgres", - "django.db.alias": "postgres", - "django.db.vendor": "postgresql", - "language": "python", - "out.host": "127.0.0.1", - "runtime-id": "f0d4b94a202a415496244aa08360304d", - "server.address": "127.0.0.1", - "span.kind": "client" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "_dd.tracer_kr": 1.0, - "_sampling_priority_v1": 1, - "db.row_count": 1, - "network.destination.port": 5432, - "process_id": 426 - }, - "duration": 322125, - "start": 1755291348387534095 - }, - { - "name": "postgres.query", - "service": "postgres", - "resource": "select 'one' as x", - "trace_id": 0, - "span_id": 2, - "parent_id": 1, - "type": "sql", - "error": 0, - "meta": { - "_dd.base_service": "tests.contrib.django", - "component": "psycopg", - "db.application": "None", - "db.name": "test_postgres", - "db.system": "postgresql", - "db.user": "postgres", - "out.host": "127.0.0.1", - "server.address": "127.0.0.1", - "span.kind": "client" - }, - "metrics": { - "_dd.measured": 1, - "_dd.top_level": 1, - "db.row_count": 1, - "network.destination.port": 5432 - }, - "duration": 219583, - "start": 1755291348387604554 - }]] diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding.json index 9403c178210..61887788602 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding.json @@ -399,7 +399,7 @@ "service": "django", "resource": "django.views.generic.list.BaseListView.get", "trace_id": 0, - "span_id": 32, + "span_id": 31, "parent_id": 30, "type": "", "error": 0, @@ -445,47 +445,6 @@ "duration": 275958, "start": 1692647371693455802 }, - { - "name": "django.template.render", - "service": "django", - "resource": "cached_list.html", - "trace_id": 0, - "span_id": 31, - "parent_id": 28, - "type": "template", - "error": 0, - "meta": { - "_dd.base_service": "tests.contrib.django", - "_dd.p.tid": "654a694400000000", - "component": "django", - "django.template.engine.class": "django.template.engine.Engine", - "django.template.name": "cached_list.html" - }, - "duration": 196084, - "start": 1692647371693511968 - }, - { - "name": "django.cache", - "service": "django", - "resource": "django.core.cache.backends.locmem.get", - "trace_id": 0, - "span_id": 33, - "parent_id": 31, - "type": "cache", - "error": 0, - "meta": { - "_dd.base_service": "tests.contrib.django", - "_dd.p.tid": "654a694400000000", - "component": "django", - "django.cache.backend": "django.core.cache.backends.locmem.LocMemCache", - "django.cache.key": "template.cache.users_list.d41d8cd98f00b204e9800998ecf8427e" - }, - "metrics": { - "db.row_count": 1 - }, - "duration": 55959, - "start": 1692647371693633093 - }, { "name": "django.middleware", "service": "django", diff --git a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding_111x.json b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding_111x.json index 88ad21bea48..8c7f8015692 100644 --- a/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding_111x.json +++ b/tests/snapshots/tests.contrib.django.test_django_snapshots.test_safe_string_encoding_111x.json @@ -211,7 +211,7 @@ "service": "django", "resource": "django.views.generic.list.BaseListView.get", "trace_id": 0, - "span_id": 20, + "span_id": 19, "parent_id": 18, "type": "", "error": 0, @@ -240,47 +240,6 @@ "duration": 459000, "start": 1692907039015655000 }, - { - "name": "django.template.render", - "service": "django", - "resource": "cached_list.html", - "trace_id": 0, - "span_id": 19, - "parent_id": 11, - "type": "template", - "error": 0, - "meta": { - "_dd.base_service": "", - "_dd.p.tid": "654a694400000000", - "component": "django", - "django.template.engine.class": "django.template.engine.Engine", - "django.template.name": "cached_list.html" - }, - "duration": 343000, - "start": 1692907039015730000 - }, - { - "name": "django.cache", - "service": "django", - "resource": "django.core.cache.backends.locmem.get", - "trace_id": 0, - "span_id": 21, - "parent_id": 19, - "type": "cache", - "error": 0, - "meta": { - "_dd.base_service": "", - "_dd.p.tid": "654a694400000000", - "component": "django", - "django.cache.backend": "django.core.cache.backends.locmem.LocMemCache", - "django.cache.key": "template.cache.users_list.d41d8cd98f00b204e9800998ecf8427e" - }, - "metrics": { - "db.row_count": 1 - }, - "duration": 140000, - "start": 1692907039015885000 - }, { "name": "django.middleware", "service": "django",