From 50c61c8a22723d49594a18fa9cce24cecec4c6f8 Mon Sep 17 00:00:00 2001
From: Munir Abdinur <munir.abdinur@datadoghq.com>
Date: Mon, 20 Jan 2025 16:46:05 -0500
Subject: [PATCH] chore(trace_utils): move implementation details to internal
 [3.0] (#12002)

## Motivation

- Create a unified public interface for all contrib utils. We will
deprecate and redesign this interface in v3.0.
- Ensure the `ddtrace.contrib.internal` package is self contained and
does rely on `ddtrace.contrib` public interfaces.

## Changes

- Deprecates `ddtrace.contrib.redis_utils` module. Expose all public
redis_utils in `ddtrace.contrib.trace_utils` (for now).
- Deprecates `ddtrace.contrib.trace_utils_async` module. Expose all
public async utils to `ddtrace.contrib.trace_utils`.
- Deprecates `trace_handlers`, `func_name`, `module_name`, and
`require_modules` functions exposed in `ddtrace.contrib.__init__`. These
functions are internal to ddtrace.
- Moves the implementation details of `trace_utils`,
`trace_utils_async`, and `redis_utils` to
`ddtrace.contrib.internal.trace_util`.


## Checklist
- [ ] 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
- [ ] 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)
---
 ddtrace/_trace/trace_handlers.py              |   2 +-
 ddtrace/_trace/utils_redis.py                 |   2 +-
 ddtrace/appsec/_handlers.py                   |   4 +-
 ddtrace/appsec/_processor.py                  |   4 +-
 ddtrace/appsec/_trace_utils.py                |   2 +-
 ddtrace/appsec/_utils.py                      |   2 +-
 ddtrace/contrib/__init__.py                   |  15 +
 ddtrace/contrib/dbapi/__init__.py             |   4 +-
 ddtrace/contrib/dbapi_async/__init__.py       |   4 +-
 ddtrace/contrib/internal/aiobotocore/patch.py |   4 +-
 ddtrace/contrib/internal/aiohttp/patch.py     |  14 +-
 .../contrib/internal/aiohttp_jinja2/patch.py  |   6 +-
 ddtrace/contrib/internal/aiomysql/patch.py    |   3 +-
 ddtrace/contrib/internal/aioredis/patch.py    |   2 +-
 ddtrace/contrib/internal/anthropic/patch.py   |   6 +-
 ddtrace/contrib/internal/aredis/patch.py      |   2 +-
 ddtrace/contrib/internal/asyncpg/patch.py     |   8 +-
 .../contrib/internal/azure_functions/patch.py |   4 +-
 ddtrace/contrib/internal/botocore/patch.py    |   6 +-
 .../internal/botocore/services/bedrock.py     |   2 +-
 .../internal/botocore/services/kinesis.py     |   2 +-
 .../contrib/internal/botocore/services/sqs.py |   2 +-
 .../botocore/services/stepfunctions.py        |   2 +-
 ddtrace/contrib/internal/celery/utils.py      |   2 +-
 ddtrace/contrib/internal/django/patch.py      |   5 +-
 .../contrib/internal/django/restframework.py  |   4 +-
 .../contrib/internal/elasticsearch/patch.py   |   4 +-
 ddtrace/contrib/internal/flask/patch.py       |   2 +-
 .../internal/google_generativeai/patch.py     |   6 +-
 ddtrace/contrib/internal/grpc/patch.py        |   2 +-
 ddtrace/contrib/internal/httplib/patch.py     |   2 +-
 ddtrace/contrib/internal/httpx/patch.py       |   6 +-
 ddtrace/contrib/internal/jinja2/patch.py      |   2 +-
 ddtrace/contrib/internal/langchain/patch.py   |   6 +-
 ddtrace/contrib/internal/logbook/patch.py     |   2 +-
 ddtrace/contrib/internal/logging/patch.py     |   2 +-
 ddtrace/contrib/internal/loguru/patch.py      |   2 +-
 ddtrace/contrib/internal/mako/patch.py        |   6 +-
 ddtrace/contrib/internal/molten/patch.py      |   2 +-
 ddtrace/contrib/internal/mysql/patch.py       |   2 +-
 ddtrace/contrib/internal/mysqldb/patch.py     |   4 +-
 .../internal/psycopg/async_connection.py      |   2 +-
 .../contrib/internal/psycopg/connection.py    |   2 +-
 ddtrace/contrib/internal/pymysql/patch.py     |   2 +-
 ddtrace/contrib/internal/pynamodb/patch.py    |   2 +-
 ddtrace/contrib/internal/pyodbc/patch.py      |   4 +-
 .../contrib/internal/redis/asyncio_patch.py   |   2 +-
 ddtrace/contrib/internal/redis/patch.py       |   2 +-
 ddtrace/contrib/internal/redis_utils.py       |  84 ++
 .../contrib/internal/requests/connection.py   |   3 +-
 ddtrace/contrib/internal/requests/patch.py    |   2 +-
 ddtrace/contrib/internal/snowflake/patch.py   |   2 +-
 ddtrace/contrib/internal/sqlalchemy/patch.py  |   2 +-
 ddtrace/contrib/internal/starlette/patch.py   |   2 +-
 ddtrace/contrib/internal/structlog/patch.py   |   4 +-
 ddtrace/contrib/internal/tornado/handlers.py  |   2 +-
 ddtrace/contrib/internal/trace_utils.py       | 719 +++++++++++++++++
 ddtrace/contrib/internal/trace_utils_async.py |  39 +
 ddtrace/contrib/internal/urllib/patch.py      |   2 +-
 ddtrace/contrib/internal/vertexai/patch.py    |   6 +-
 ddtrace/contrib/internal/webbrowser/patch.py  |   2 +-
 ddtrace/contrib/internal/yaaredis/patch.py    |   2 +-
 ddtrace/contrib/redis_utils.py                |  91 +--
 ddtrace/contrib/trace_utils.py                | 755 +-----------------
 ddtrace/contrib/trace_utils_async.py          |  46 +-
 ddtrace/contrib/trace_utils_redis.py          |   9 +-
 ddtrace/llmobs/_integrations/base.py          |   2 +-
 ...refactor-trace-utils-e887e8da8a01430b.yaml |   6 +
 .../appsec/appsec/test_appsec_trace_utils.py  |   2 +-
 tests/appsec/appsec/test_asm_standalone.py    |   2 +-
 tests/appsec/appsec/test_processor.py         |   2 +-
 .../appsec/appsec/test_remoteconfiguration.py |   2 +-
 tests/appsec/appsec/test_telemetry.py         |   2 +-
 tests/contrib/asyncpg/test_asyncpg.py         |   2 +-
 tests/contrib/django/views.py                 |   2 +-
 tests/contrib/flask/app.py                    |   2 +-
 tests/contrib/openai/test_openai_v0.py        |   2 +-
 tests/contrib/openai/test_openai_v1.py        |   2 +-
 tests/internal/test_module.py                 |   5 +-
 tests/tracer/test_trace_utils.py              |  24 +-
 tests/tracer/test_tracer.py                   |   2 +-
 81 files changed, 1049 insertions(+), 961 deletions(-)
 create mode 100644 ddtrace/contrib/internal/redis_utils.py
 create mode 100644 ddtrace/contrib/internal/trace_utils.py
 create mode 100644 ddtrace/contrib/internal/trace_utils_async.py
 create mode 100644 releasenotes/notes/munir-refactor-trace-utils-e887e8da8a01430b.yaml

diff --git a/ddtrace/_trace/trace_handlers.py b/ddtrace/_trace/trace_handlers.py
index 7c2ba02d6b4..96c33da8c6b 100644
--- a/ddtrace/_trace/trace_handlers.py
+++ b/ddtrace/_trace/trace_handlers.py
@@ -22,7 +22,7 @@
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib import trace_utils
 from ddtrace.contrib.internal.botocore.constants import BOTOCORE_STEPFUNCTIONS_INPUT_KEY
-from ddtrace.contrib.trace_utils import _set_url_tag
+from ddtrace.contrib.internal.trace_utils import _set_url_tag
 from ddtrace.ext import SpanKind
 from ddtrace.ext import db
 from ddtrace.ext import http
diff --git a/ddtrace/_trace/utils_redis.py b/ddtrace/_trace/utils_redis.py
index 0ea1388e255..433d8578fbb 100644
--- a/ddtrace/_trace/utils_redis.py
+++ b/ddtrace/_trace/utils_redis.py
@@ -10,7 +10,7 @@
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib import trace_utils
-from ddtrace.contrib.redis_utils import _extract_conn_tags
+from ddtrace.contrib.internal.redis_utils import _extract_conn_tags
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import db
diff --git a/ddtrace/appsec/_handlers.py b/ddtrace/appsec/_handlers.py
index 42464f9fe40..d34b2da3517 100644
--- a/ddtrace/appsec/_handlers.py
+++ b/ddtrace/appsec/_handlers.py
@@ -6,8 +6,8 @@
 from ddtrace.appsec._asm_request_context import get_blocked
 from ddtrace.appsec._constants import SPAN_DATA_NAMES
 from ddtrace.contrib import trace_utils
-from ddtrace.contrib.trace_utils import _get_request_header_user_agent
-from ddtrace.contrib.trace_utils import _set_url_tag
+from ddtrace.contrib.internal.trace_utils import _get_request_header_user_agent
+from ddtrace.contrib.internal.trace_utils import _set_url_tag
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import http
 from ddtrace.internal import core
diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py
index fb18c268fdb..33f3ac8ced1 100644
--- a/ddtrace/appsec/_processor.py
+++ b/ddtrace/appsec/_processor.py
@@ -101,7 +101,7 @@ def get_rules() -> str:
 
 
 def _set_headers(span: Span, headers: Any, kind: str, only_asm_enabled: bool = False) -> None:
-    from ddtrace.contrib.trace_utils import _normalize_tag_name
+    from ddtrace.contrib.internal.trace_utils import _normalize_tag_name
 
     for k in headers:
         if isinstance(k, tuple):
@@ -218,7 +218,7 @@ def rasp_sqli_enabled(self) -> bool:
         return WAF_DATA_NAMES.SQLI_ADDRESS in self._addresses_to_keep
 
     def on_span_start(self, span: Span) -> None:
-        from ddtrace.contrib import trace_utils
+        from ddtrace.contrib.internal import trace_utils
 
         if not hasattr(self, "_ddwaf"):
             self.delayed_init()
diff --git a/ddtrace/appsec/_trace_utils.py b/ddtrace/appsec/_trace_utils.py
index 77cb1aaca3a..56f34d81f99 100644
--- a/ddtrace/appsec/_trace_utils.py
+++ b/ddtrace/appsec/_trace_utils.py
@@ -11,7 +11,7 @@
 from ddtrace.appsec._constants import LOGIN_EVENTS_MODE
 from ddtrace.appsec._constants import WAF_ACTIONS
 from ddtrace.appsec._utils import _hash_user_id
-from ddtrace.contrib.trace_utils import set_user
+from ddtrace.contrib.internal.trace_utils import set_user
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import user
 from ddtrace.internal import core
diff --git a/ddtrace/appsec/_utils.py b/ddtrace/appsec/_utils.py
index bb8739654c5..79f8f8b5311 100644
--- a/ddtrace/appsec/_utils.py
+++ b/ddtrace/appsec/_utils.py
@@ -22,7 +22,7 @@ def parse_response_body(raw_body):
 
     from ddtrace.appsec import _asm_request_context
     from ddtrace.appsec._constants import SPAN_DATA_NAMES
-    from ddtrace.contrib.trace_utils import _get_header_value_case_insensitive
+    from ddtrace.contrib.internal.trace_utils import _get_header_value_case_insensitive
 
     if not raw_body:
         return
diff --git a/ddtrace/contrib/__init__.py b/ddtrace/contrib/__init__.py
index 1c75b9d0e44..5ed52efa04e 100644
--- a/ddtrace/contrib/__init__.py
+++ b/ddtrace/contrib/__init__.py
@@ -1,4 +1,19 @@
 from ddtrace._trace import trace_handlers  # noqa:F401
+from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning
 from ddtrace.internal.utils.importlib import func_name  # noqa:F401
 from ddtrace.internal.utils.importlib import module_name  # noqa:F401
 from ddtrace.internal.utils.importlib import require_modules  # noqa:F401
+from ddtrace.vendor.debtcollector import deprecate
+
+
+def __getattr__(name):
+    if name in ("trace_handlers", "func_name", "module_name", "require_modules"):
+        deprecate(
+            ("%s.%s is deprecated" % (__name__, name)),
+            category=DDTraceDeprecationWarning,
+            removal_version="3.0.0",
+        )
+
+    if name in globals():
+        return globals()[name]
+    raise AttributeError("%s has no attribute %s", __name__, name)
diff --git a/ddtrace/contrib/dbapi/__init__.py b/ddtrace/contrib/dbapi/__init__.py
index 0b772ac04ec..e511fc31657 100644
--- a/ddtrace/contrib/dbapi/__init__.py
+++ b/ddtrace/contrib/dbapi/__init__.py
@@ -21,8 +21,8 @@
 from ...ext import db
 from ...ext import sql
 from ...trace import Pin
-from ..trace_utils import ext_service
-from ..trace_utils import iswrapped
+from ..internal.trace_utils import ext_service
+from ..internal.trace_utils import iswrapped
 
 
 log = get_logger(__name__)
diff --git a/ddtrace/contrib/dbapi_async/__init__.py b/ddtrace/contrib/dbapi_async/__init__.py
index d0c43fc1c2b..9ebf36847b0 100644
--- a/ddtrace/contrib/dbapi_async/__init__.py
+++ b/ddtrace/contrib/dbapi_async/__init__.py
@@ -16,8 +16,8 @@
 from ...trace import Pin
 from ..dbapi import TracedConnection
 from ..dbapi import TracedCursor
-from ..trace_utils import ext_service
-from ..trace_utils import iswrapped
+from ..internal.trace_utils import ext_service
+from ..internal.trace_utils import iswrapped
 
 
 log = get_logger(__name__)
diff --git a/ddtrace/contrib/internal/aiobotocore/patch.py b/ddtrace/contrib/internal/aiobotocore/patch.py
index 7431bd5c592..0df89927ece 100644
--- a/ddtrace/contrib/internal/aiobotocore/patch.py
+++ b/ddtrace/contrib/internal/aiobotocore/patch.py
@@ -7,8 +7,8 @@
 from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
-from ddtrace.contrib.trace_utils import ext_service
-from ddtrace.contrib.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import unwrap
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import aws
diff --git a/ddtrace/contrib/internal/aiohttp/patch.py b/ddtrace/contrib/internal/aiohttp/patch.py
index e0f0bc869e9..900a8d26e41 100644
--- a/ddtrace/contrib/internal/aiohttp/patch.py
+++ b/ddtrace/contrib/internal/aiohttp/patch.py
@@ -6,13 +6,13 @@
 
 from ddtrace import config
 from ddtrace.constants import SPAN_KIND
-from ddtrace.contrib.trace_utils import ext_service
-from ddtrace.contrib.trace_utils import extract_netloc_and_query_info_from_url
-from ddtrace.contrib.trace_utils import set_http_meta
-from ddtrace.contrib.trace_utils import unwrap
-from ddtrace.contrib.trace_utils import with_traced_module as with_traced_module_sync
-from ddtrace.contrib.trace_utils import wrap
-from ddtrace.contrib.trace_utils_async import with_traced_module
+from ddtrace.contrib.internal.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import extract_netloc_and_query_info_from_url
+from ddtrace.contrib.internal.trace_utils import set_http_meta
+from ddtrace.contrib.internal.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import with_traced_module as with_traced_module_sync
+from ddtrace.contrib.internal.trace_utils import wrap
+from ddtrace.contrib.internal.trace_utils_async import with_traced_module
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.constants import COMPONENT
diff --git a/ddtrace/contrib/internal/aiohttp_jinja2/patch.py b/ddtrace/contrib/internal/aiohttp_jinja2/patch.py
index 84553899c39..74987483a20 100644
--- a/ddtrace/contrib/internal/aiohttp_jinja2/patch.py
+++ b/ddtrace/contrib/internal/aiohttp_jinja2/patch.py
@@ -1,9 +1,9 @@
 import aiohttp_jinja2
 
 from ddtrace import config
-from ddtrace.contrib.trace_utils import unwrap
-from ddtrace.contrib.trace_utils import with_traced_module
-from ddtrace.contrib.trace_utils import wrap
+from ddtrace.contrib.internal.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import with_traced_module
+from ddtrace.contrib.internal.trace_utils import wrap
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.constants import COMPONENT
 from ddtrace.internal.utils import get_argument_value
diff --git a/ddtrace/contrib/internal/aiomysql/patch.py b/ddtrace/contrib/internal/aiomysql/patch.py
index 0053e4f8a5b..193d471d124 100644
--- a/ddtrace/contrib/internal/aiomysql/patch.py
+++ b/ddtrace/contrib/internal/aiomysql/patch.py
@@ -7,6 +7,7 @@
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib import dbapi
 from ddtrace.contrib import trace_utils
+from ddtrace.contrib.internal.trace_utils import _convert_to_string
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import db
@@ -48,7 +49,7 @@ async def patched_connect(connect_func, _, args, kwargs):
     tags = {}
     for tag, attr in CONN_ATTR_BY_TAG.items():
         if hasattr(conn, attr):
-            tags[tag] = trace_utils._convert_to_string(getattr(conn, attr, None))
+            tags[tag] = _convert_to_string(getattr(conn, attr, None))
     tags[db.SYSTEM] = "mysql"
 
     c = AIOTracedConnection(conn)
diff --git a/ddtrace/contrib/internal/aioredis/patch.py b/ddtrace/contrib/internal/aioredis/patch.py
index dc6004b9caa..3e1f0c062a3 100644
--- a/ddtrace/contrib/internal/aioredis/patch.py
+++ b/ddtrace/contrib/internal/aioredis/patch.py
@@ -12,8 +12,8 @@
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib import trace_utils
+from ddtrace.contrib.internal.redis_utils import _run_redis_command_async
 from ddtrace.contrib.redis_utils import ROW_RETURNING_COMMANDS
-from ddtrace.contrib.redis_utils import _run_redis_command_async
 from ddtrace.contrib.redis_utils import determine_row_count
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
diff --git a/ddtrace/contrib/internal/anthropic/patch.py b/ddtrace/contrib/internal/anthropic/patch.py
index 24f72f2b511..2bb35836580 100644
--- a/ddtrace/contrib/internal/anthropic/patch.py
+++ b/ddtrace/contrib/internal/anthropic/patch.py
@@ -11,9 +11,9 @@
 from ddtrace.contrib.internal.anthropic.utils import tag_params_on_span
 from ddtrace.contrib.internal.anthropic.utils import tag_tool_result_input_on_span
 from ddtrace.contrib.internal.anthropic.utils import tag_tool_use_input_on_span
-from ddtrace.contrib.trace_utils import unwrap
-from ddtrace.contrib.trace_utils import with_traced_module
-from ddtrace.contrib.trace_utils import wrap
+from ddtrace.contrib.internal.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import with_traced_module
+from ddtrace.contrib.internal.trace_utils import wrap
 from ddtrace.internal.logger import get_logger
 from ddtrace.internal.utils import get_argument_value
 from ddtrace.llmobs._integrations import AnthropicIntegration
diff --git a/ddtrace/contrib/internal/aredis/patch.py b/ddtrace/contrib/internal/aredis/patch.py
index bd8c5b4c750..4374f98c601 100644
--- a/ddtrace/contrib/internal/aredis/patch.py
+++ b/ddtrace/contrib/internal/aredis/patch.py
@@ -6,7 +6,7 @@
 from ddtrace import config
 from ddtrace._trace.utils_redis import _instrument_redis_cmd
 from ddtrace._trace.utils_redis import _instrument_redis_execute_pipeline
-from ddtrace.contrib.redis_utils import _run_redis_command_async
+from ddtrace.contrib.internal.redis_utils import _run_redis_command_async
 from ddtrace.internal.schema import schematize_service_name
 from ddtrace.internal.utils.formats import CMD_MAX_LEN
 from ddtrace.internal.utils.formats import asbool
diff --git a/ddtrace/contrib/internal/asyncpg/patch.py b/ddtrace/contrib/internal/asyncpg/patch.py
index ac1347a7de6..538af434e0b 100644
--- a/ddtrace/contrib/internal/asyncpg/patch.py
+++ b/ddtrace/contrib/internal/asyncpg/patch.py
@@ -19,10 +19,10 @@
 from ddtrace.internal.schema import schematize_service_name
 from ddtrace.internal.utils import get_argument_value
 from ddtrace.propagation._database_monitoring import _DBM_Propagator
-from ddtrace.contrib.trace_utils import ext_service
-from ddtrace.contrib.trace_utils import unwrap
-from ddtrace.contrib.trace_utils import wrap
-from ddtrace.contrib.trace_utils_async import with_traced_module
+from ddtrace.contrib.internal.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import wrap
+from ddtrace.contrib.internal.trace_utils_async import with_traced_module
 
 
 if TYPE_CHECKING:  # pragma: no cover
diff --git a/ddtrace/contrib/internal/azure_functions/patch.py b/ddtrace/contrib/internal/azure_functions/patch.py
index 1c0c658a9eb..7690368a852 100644
--- a/ddtrace/contrib/internal/azure_functions/patch.py
+++ b/ddtrace/contrib/internal/azure_functions/patch.py
@@ -2,8 +2,8 @@
 from wrapt import wrap_function_wrapper as _w
 
 from ddtrace import config
-from ddtrace.contrib.trace_utils import int_service
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import int_service
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.ext import SpanTypes
 from ddtrace.internal import core
 from ddtrace.internal.schema import schematize_cloud_faas_operation
diff --git a/ddtrace/contrib/internal/botocore/patch.py b/ddtrace/contrib/internal/botocore/patch.py
index 07c0bd403e4..734c429d798 100644
--- a/ddtrace/contrib/internal/botocore/patch.py
+++ b/ddtrace/contrib/internal/botocore/patch.py
@@ -17,9 +17,9 @@
 
 from ddtrace import config
 from ddtrace.constants import SPAN_KIND
-from ddtrace.contrib.trace_utils import ext_service
-from ddtrace.contrib.trace_utils import unwrap
-from ddtrace.contrib.trace_utils import with_traced_module
+from ddtrace.contrib.internal.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import with_traced_module
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.internal import core
diff --git a/ddtrace/contrib/internal/botocore/services/bedrock.py b/ddtrace/contrib/internal/botocore/services/bedrock.py
index 00e9aa5756f..4f237b67342 100644
--- a/ddtrace/contrib/internal/botocore/services/bedrock.py
+++ b/ddtrace/contrib/internal/botocore/services/bedrock.py
@@ -7,7 +7,7 @@
 import wrapt
 
 from ddtrace import config
-from ddtrace.contrib.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import ext_service
 from ddtrace.ext import SpanTypes
 from ddtrace.internal import core
 from ddtrace.internal.logger import get_logger
diff --git a/ddtrace/contrib/internal/botocore/services/kinesis.py b/ddtrace/contrib/internal/botocore/services/kinesis.py
index 0287c29d2bc..f1f71d0b819 100644
--- a/ddtrace/contrib/internal/botocore/services/kinesis.py
+++ b/ddtrace/contrib/internal/botocore/services/kinesis.py
@@ -10,7 +10,7 @@
 import botocore.exceptions
 
 from ddtrace import config
-from ddtrace.contrib.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import ext_service
 from ddtrace.ext import SpanTypes
 from ddtrace.internal import core
 from ddtrace.internal.logger import get_logger
diff --git a/ddtrace/contrib/internal/botocore/services/sqs.py b/ddtrace/contrib/internal/botocore/services/sqs.py
index 084a6f77dc7..5bf238c8cfd 100644
--- a/ddtrace/contrib/internal/botocore/services/sqs.py
+++ b/ddtrace/contrib/internal/botocore/services/sqs.py
@@ -7,7 +7,7 @@
 import botocore.exceptions
 
 from ddtrace import config
-from ddtrace.contrib.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import ext_service
 from ddtrace.ext import SpanTypes
 from ddtrace.internal import core
 from ddtrace.internal.logger import get_logger
diff --git a/ddtrace/contrib/internal/botocore/services/stepfunctions.py b/ddtrace/contrib/internal/botocore/services/stepfunctions.py
index 9e3c1d2db13..8bf2c1433ee 100644
--- a/ddtrace/contrib/internal/botocore/services/stepfunctions.py
+++ b/ddtrace/contrib/internal/botocore/services/stepfunctions.py
@@ -6,7 +6,7 @@
 
 from ddtrace import config
 from ddtrace.contrib.internal.botocore.constants import BOTOCORE_STEPFUNCTIONS_INPUT_KEY
-from ddtrace.contrib.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import ext_service
 from ddtrace.ext import SpanTypes
 from ddtrace.internal import core
 from ddtrace.internal.logger import get_logger
diff --git a/ddtrace/contrib/internal/celery/utils.py b/ddtrace/contrib/internal/celery/utils.py
index 8945fb5857b..5c7a4c077e8 100644
--- a/ddtrace/contrib/internal/celery/utils.py
+++ b/ddtrace/contrib/internal/celery/utils.py
@@ -3,7 +3,7 @@
 from weakref import WeakValueDictionary
 
 from ddtrace._trace.span import Span
-from ddtrace.contrib.trace_utils import set_flattened_tags
+from ddtrace.contrib.internal.trace_utils import set_flattened_tags
 from ddtrace.propagation.http import HTTPPropagator
 
 from .constants import CTX_KEY
diff --git a/ddtrace/contrib/internal/django/patch.py b/ddtrace/contrib/internal/django/patch.py
index 8bc523dd1c1..7378d8da31c 100644
--- a/ddtrace/contrib/internal/django/patch.py
+++ b/ddtrace/contrib/internal/django/patch.py
@@ -23,7 +23,8 @@
 from ddtrace.constants import SPAN_KIND
 from ddtrace.contrib import dbapi
 from ddtrace.contrib import trace_utils
-from ddtrace.contrib.trace_utils import _get_request_header_user_agent
+from ddtrace.contrib.internal.trace_utils import _convert_to_string
+from ddtrace.contrib.internal.trace_utils import _get_request_header_user_agent
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import db
@@ -127,7 +128,7 @@ def patch_conn(django, conn):
     for tag, attr in DB_CONN_ATTR_BY_TAG.items():
         if attr in settings_dict:
             try:
-                tags[tag] = trace_utils._convert_to_string(conn.settings_dict.get(attr))
+                tags[tag] = _convert_to_string(conn.settings_dict.get(attr))
             except Exception:
                 tags[tag] = str(conn.settings_dict.get(attr))
     conn._datadog_tags = tags
diff --git a/ddtrace/contrib/internal/django/restframework.py b/ddtrace/contrib/internal/django/restframework.py
index d9a7266fb03..e1bebeaea73 100644
--- a/ddtrace/contrib/internal/django/restframework.py
+++ b/ddtrace/contrib/internal/django/restframework.py
@@ -1,8 +1,8 @@
 import rest_framework.views
 from wrapt import wrap_function_wrapper as wrap
 
-from ddtrace.contrib.trace_utils import iswrapped
-from ddtrace.contrib.trace_utils import with_traced_module
+from ddtrace.contrib.internal.trace_utils import iswrapped
+from ddtrace.contrib.internal.trace_utils import with_traced_module
 
 
 @with_traced_module
diff --git a/ddtrace/contrib/internal/elasticsearch/patch.py b/ddtrace/contrib/internal/elasticsearch/patch.py
index 7c408db55a5..09eff66dbca 100644
--- a/ddtrace/contrib/internal/elasticsearch/patch.py
+++ b/ddtrace/contrib/internal/elasticsearch/patch.py
@@ -9,8 +9,8 @@
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib.internal.elasticsearch.quantize import quantize
-from ddtrace.contrib.trace_utils import ext_service
-from ddtrace.contrib.trace_utils import extract_netloc_and_query_info_from_url
+from ddtrace.contrib.internal.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import extract_netloc_and_query_info_from_url
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import elasticsearch as metadata
diff --git a/ddtrace/contrib/internal/flask/patch.py b/ddtrace/contrib/internal/flask/patch.py
index 010df5218c5..7111310b6ef 100644
--- a/ddtrace/contrib/internal/flask/patch.py
+++ b/ddtrace/contrib/internal/flask/patch.py
@@ -30,8 +30,8 @@
 from wrapt import wrap_function_wrapper as _w
 
 from ddtrace import config
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.contrib.internal.wsgi.wsgi import _DDWSGIMiddlewareBase
-from ddtrace.contrib.trace_utils import unwrap as _u
 from ddtrace.internal.logger import get_logger
 from ddtrace.internal.utils import get_argument_value
 from ddtrace.internal.utils.importlib import func_name
diff --git a/ddtrace/contrib/internal/google_generativeai/patch.py b/ddtrace/contrib/internal/google_generativeai/patch.py
index 3564f9ec1ec..9cd28a531f4 100644
--- a/ddtrace/contrib/internal/google_generativeai/patch.py
+++ b/ddtrace/contrib/internal/google_generativeai/patch.py
@@ -9,9 +9,9 @@
 from ddtrace.contrib.internal.google_generativeai._utils import _extract_api_key
 from ddtrace.contrib.internal.google_generativeai._utils import tag_request
 from ddtrace.contrib.internal.google_generativeai._utils import tag_response
-from ddtrace.contrib.trace_utils import unwrap
-from ddtrace.contrib.trace_utils import with_traced_module
-from ddtrace.contrib.trace_utils import wrap
+from ddtrace.contrib.internal.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import with_traced_module
+from ddtrace.contrib.internal.trace_utils import wrap
 from ddtrace.llmobs._integrations import GeminiIntegration
 from ddtrace.llmobs._integrations.utils import extract_model_name_google
 from ddtrace.trace import Pin
diff --git a/ddtrace/contrib/internal/grpc/patch.py b/ddtrace/contrib/internal/grpc/patch.py
index 122893b030f..5e1053af28a 100644
--- a/ddtrace/contrib/internal/grpc/patch.py
+++ b/ddtrace/contrib/internal/grpc/patch.py
@@ -7,7 +7,7 @@
 from ddtrace.contrib.internal.grpc.client_interceptor import create_client_interceptor
 from ddtrace.contrib.internal.grpc.client_interceptor import intercept_channel
 from ddtrace.contrib.internal.grpc.server_interceptor import create_server_interceptor
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.internal.logger import get_logger
 from ddtrace.internal.schema import schematize_service_name
 from ddtrace.internal.utils import get_argument_value
diff --git a/ddtrace/contrib/internal/httplib/patch.py b/ddtrace/contrib/internal/httplib/patch.py
index 3e354aeedea..a1e367af3a1 100644
--- a/ddtrace/contrib/internal/httplib/patch.py
+++ b/ddtrace/contrib/internal/httplib/patch.py
@@ -9,7 +9,7 @@
 from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY
 from ddtrace.constants import SPAN_KIND
 from ddtrace.contrib import trace_utils
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.compat import httplib
diff --git a/ddtrace/contrib/internal/httpx/patch.py b/ddtrace/contrib/internal/httpx/patch.py
index 8a9e4eebc3a..a3a677c14ac 100644
--- a/ddtrace/contrib/internal/httpx/patch.py
+++ b/ddtrace/contrib/internal/httpx/patch.py
@@ -8,9 +8,9 @@
 from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
-from ddtrace.contrib.trace_utils import distributed_tracing_enabled
-from ddtrace.contrib.trace_utils import ext_service
-from ddtrace.contrib.trace_utils import set_http_meta
+from ddtrace.contrib.internal.trace_utils import distributed_tracing_enabled
+from ddtrace.contrib.internal.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import set_http_meta
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.compat import ensure_binary
diff --git a/ddtrace/contrib/internal/jinja2/patch.py b/ddtrace/contrib/internal/jinja2/patch.py
index cdf1254527d..67c704c415d 100644
--- a/ddtrace/contrib/internal/jinja2/patch.py
+++ b/ddtrace/contrib/internal/jinja2/patch.py
@@ -5,7 +5,7 @@
 
 from ddtrace import config
 from ddtrace.constants import SPAN_MEASURED_KEY
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.constants import COMPONENT
 from ddtrace.internal.utils import ArgumentError
diff --git a/ddtrace/contrib/internal/langchain/patch.py b/ddtrace/contrib/internal/langchain/patch.py
index f9c58249cb2..430700e2782 100644
--- a/ddtrace/contrib/internal/langchain/patch.py
+++ b/ddtrace/contrib/internal/langchain/patch.py
@@ -51,9 +51,9 @@
 from ddtrace.contrib.internal.langchain.constants import vectorstore_classes
 from ddtrace.contrib.internal.langchain.utils import shared_stream
 from ddtrace.contrib.internal.langchain.utils import tag_general_message_input
-from ddtrace.contrib.trace_utils import unwrap
-from ddtrace.contrib.trace_utils import with_traced_module
-from ddtrace.contrib.trace_utils import wrap
+from ddtrace.contrib.internal.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import with_traced_module
+from ddtrace.contrib.internal.trace_utils import wrap
 from ddtrace.internal.logger import get_logger
 from ddtrace.internal.utils import ArgumentError
 from ddtrace.internal.utils import get_argument_value
diff --git a/ddtrace/contrib/internal/logbook/patch.py b/ddtrace/contrib/internal/logbook/patch.py
index 075b00787bc..7a1c1ae1484 100644
--- a/ddtrace/contrib/internal/logbook/patch.py
+++ b/ddtrace/contrib/internal/logbook/patch.py
@@ -9,7 +9,7 @@
 from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_TRACE_ID
 from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_VALUE_EMPTY
 from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_VERSION
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.internal.utils import get_argument_value
 
 
diff --git a/ddtrace/contrib/internal/logging/patch.py b/ddtrace/contrib/internal/logging/patch.py
index 51508bae28f..11edd5d938f 100644
--- a/ddtrace/contrib/internal/logging/patch.py
+++ b/ddtrace/contrib/internal/logging/patch.py
@@ -4,7 +4,7 @@
 
 import ddtrace
 from ddtrace import config
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.internal.utils import get_argument_value
 
 from .constants import RECORD_ATTR_ENV
diff --git a/ddtrace/contrib/internal/loguru/patch.py b/ddtrace/contrib/internal/loguru/patch.py
index e25aba4cff4..1dff8046f80 100644
--- a/ddtrace/contrib/internal/loguru/patch.py
+++ b/ddtrace/contrib/internal/loguru/patch.py
@@ -3,7 +3,7 @@
 
 import ddtrace
 from ddtrace import config
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 
 from ..logging.constants import RECORD_ATTR_ENV
 from ..logging.constants import RECORD_ATTR_SERVICE
diff --git a/ddtrace/contrib/internal/mako/patch.py b/ddtrace/contrib/internal/mako/patch.py
index d39a51238a2..ca569477773 100644
--- a/ddtrace/contrib/internal/mako/patch.py
+++ b/ddtrace/contrib/internal/mako/patch.py
@@ -4,9 +4,9 @@
 
 from ddtrace import config
 from ddtrace.constants import SPAN_MEASURED_KEY
-from ddtrace.contrib.trace_utils import int_service
-from ddtrace.contrib.trace_utils import unwrap as _u
-from ddtrace.contrib.trace_utils import wrap as _w
+from ddtrace.contrib.internal.trace_utils import int_service
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import wrap as _w
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.constants import COMPONENT
 from ddtrace.internal.schema import schematize_service_name
diff --git a/ddtrace/contrib/internal/molten/patch.py b/ddtrace/contrib/internal/molten/patch.py
index 7c60d37d0d6..6fc237a5b58 100644
--- a/ddtrace/contrib/internal/molten/patch.py
+++ b/ddtrace/contrib/internal/molten/patch.py
@@ -9,7 +9,7 @@
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib import trace_utils
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.compat import urlencode
diff --git a/ddtrace/contrib/internal/mysql/patch.py b/ddtrace/contrib/internal/mysql/patch.py
index d18d357d107..6425bd33766 100644
--- a/ddtrace/contrib/internal/mysql/patch.py
+++ b/ddtrace/contrib/internal/mysql/patch.py
@@ -7,7 +7,7 @@
 from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink
 from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION
 from ddtrace.contrib.dbapi import TracedConnection
-from ddtrace.contrib.trace_utils import _convert_to_string
+from ddtrace.contrib.internal.trace_utils import _convert_to_string
 from ddtrace.ext import db
 from ddtrace.ext import net
 from ddtrace.internal.schema import schematize_database_operation
diff --git a/ddtrace/contrib/internal/mysqldb/patch.py b/ddtrace/contrib/internal/mysqldb/patch.py
index 8b6aa7bb7f2..e4d124ee1f6 100644
--- a/ddtrace/contrib/internal/mysqldb/patch.py
+++ b/ddtrace/contrib/internal/mysqldb/patch.py
@@ -9,8 +9,8 @@
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib.dbapi import TracedConnection
-from ddtrace.contrib.trace_utils import _convert_to_string
-from ddtrace.contrib.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import _convert_to_string
+from ddtrace.contrib.internal.trace_utils import ext_service
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import db
diff --git a/ddtrace/contrib/internal/psycopg/async_connection.py b/ddtrace/contrib/internal/psycopg/async_connection.py
index 72c8d70e7ec..8d400330ff5 100644
--- a/ddtrace/contrib/internal/psycopg/async_connection.py
+++ b/ddtrace/contrib/internal/psycopg/async_connection.py
@@ -5,7 +5,7 @@
 from ddtrace.contrib.internal.psycopg.async_cursor import Psycopg3FetchTracedAsyncCursor
 from ddtrace.contrib.internal.psycopg.async_cursor import Psycopg3TracedAsyncCursor
 from ddtrace.contrib.internal.psycopg.connection import patch_conn
-from ddtrace.contrib.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import ext_service
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import db
diff --git a/ddtrace/contrib/internal/psycopg/connection.py b/ddtrace/contrib/internal/psycopg/connection.py
index a5e5353ad13..c3b7caef2c4 100644
--- a/ddtrace/contrib/internal/psycopg/connection.py
+++ b/ddtrace/contrib/internal/psycopg/connection.py
@@ -7,7 +7,7 @@
 from ddtrace.contrib.internal.psycopg.cursor import Psycopg3FetchTracedCursor
 from ddtrace.contrib.internal.psycopg.cursor import Psycopg3TracedCursor
 from ddtrace.contrib.internal.psycopg.extensions import _patch_extensions
-from ddtrace.contrib.trace_utils import ext_service
+from ddtrace.contrib.internal.trace_utils import ext_service
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import db
diff --git a/ddtrace/contrib/internal/pymysql/patch.py b/ddtrace/contrib/internal/pymysql/patch.py
index a9a16d50608..eab651bb784 100644
--- a/ddtrace/contrib/internal/pymysql/patch.py
+++ b/ddtrace/contrib/internal/pymysql/patch.py
@@ -5,7 +5,7 @@
 
 from ddtrace import config
 from ddtrace.contrib.dbapi import TracedConnection
-from ddtrace.contrib.trace_utils import _convert_to_string
+from ddtrace.contrib.internal.trace_utils import _convert_to_string
 from ddtrace.ext import db
 from ddtrace.ext import net
 from ddtrace.internal.schema import schematize_database_operation
diff --git a/ddtrace/contrib/internal/pynamodb/patch.py b/ddtrace/contrib/internal/pynamodb/patch.py
index be4ba00c893..93171d176bd 100644
--- a/ddtrace/contrib/internal/pynamodb/patch.py
+++ b/ddtrace/contrib/internal/pynamodb/patch.py
@@ -10,7 +10,7 @@
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib import trace_utils
-from ddtrace.contrib.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import unwrap
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import db
diff --git a/ddtrace/contrib/internal/pyodbc/patch.py b/ddtrace/contrib/internal/pyodbc/patch.py
index 180895a202e..8b51c5b425a 100644
--- a/ddtrace/contrib/internal/pyodbc/patch.py
+++ b/ddtrace/contrib/internal/pyodbc/patch.py
@@ -5,8 +5,8 @@
 from ddtrace import config
 from ddtrace.contrib.dbapi import TracedConnection
 from ddtrace.contrib.dbapi import TracedCursor
-from ddtrace.contrib.trace_utils import unwrap
-from ddtrace.contrib.trace_utils import wrap
+from ddtrace.contrib.internal.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import wrap
 from ddtrace.ext import db
 from ddtrace.internal.schema import schematize_service_name
 from ddtrace.internal.utils.formats import asbool
diff --git a/ddtrace/contrib/internal/redis/asyncio_patch.py b/ddtrace/contrib/internal/redis/asyncio_patch.py
index 7c5bad354ab..66a0fa8ba4c 100644
--- a/ddtrace/contrib/internal/redis/asyncio_patch.py
+++ b/ddtrace/contrib/internal/redis/asyncio_patch.py
@@ -2,7 +2,7 @@
 from ddtrace._trace.utils_redis import _instrument_redis_cmd
 from ddtrace._trace.utils_redis import _instrument_redis_execute_async_cluster_pipeline
 from ddtrace._trace.utils_redis import _instrument_redis_execute_pipeline
-from ddtrace.contrib.redis_utils import _run_redis_command_async
+from ddtrace.contrib.internal.redis_utils import _run_redis_command_async
 from ddtrace.internal.utils.formats import stringify_cache_args
 from ddtrace.trace import Pin
 
diff --git a/ddtrace/contrib/internal/redis/patch.py b/ddtrace/contrib/internal/redis/patch.py
index 33520e5894d..28beeb4d979 100644
--- a/ddtrace/contrib/internal/redis/patch.py
+++ b/ddtrace/contrib/internal/redis/patch.py
@@ -6,9 +6,9 @@
 from ddtrace import config
 from ddtrace._trace.utils_redis import _instrument_redis_cmd
 from ddtrace._trace.utils_redis import _instrument_redis_execute_pipeline
+from ddtrace.contrib.internal.trace_utils import unwrap
 from ddtrace.contrib.redis_utils import ROW_RETURNING_COMMANDS
 from ddtrace.contrib.redis_utils import determine_row_count
-from ddtrace.contrib.trace_utils import unwrap
 from ddtrace.internal import core
 from ddtrace.internal.schema import schematize_service_name
 from ddtrace.internal.utils.formats import CMD_MAX_LEN
diff --git a/ddtrace/contrib/internal/redis_utils.py b/ddtrace/contrib/internal/redis_utils.py
new file mode 100644
index 00000000000..63d950389c8
--- /dev/null
+++ b/ddtrace/contrib/internal/redis_utils.py
@@ -0,0 +1,84 @@
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Union
+
+from ddtrace.ext import net
+from ddtrace.ext import redis as redisx
+from ddtrace.internal import core
+from ddtrace.internal.utils.formats import stringify_cache_args
+
+
+SINGLE_KEY_COMMANDS = [
+    "GET",
+    "GETDEL",
+    "GETEX",
+    "GETRANGE",
+    "GETSET",
+    "LINDEX",
+    "LRANGE",
+    "RPOP",
+    "LPOP",
+    "HGET",
+    "HGETALL",
+    "HKEYS",
+    "HMGET",
+    "HRANDFIELD",
+    "HVALS",
+]
+MULTI_KEY_COMMANDS = ["MGET"]
+ROW_RETURNING_COMMANDS = SINGLE_KEY_COMMANDS + MULTI_KEY_COMMANDS
+
+
+def _extract_conn_tags(conn_kwargs):
+    """Transform redis conn info into dogtrace metas"""
+    try:
+        conn_tags = {
+            net.TARGET_HOST: conn_kwargs["host"],
+            net.TARGET_PORT: conn_kwargs["port"],
+            net.SERVER_ADDRESS: conn_kwargs["host"],
+            redisx.DB: conn_kwargs.get("db") or 0,
+        }
+        client_name = conn_kwargs.get("client_name")
+        if client_name:
+            conn_tags[redisx.CLIENT_NAME] = client_name
+        return conn_tags
+    except Exception:
+        return {}
+
+
+def determine_row_count(redis_command: str, result: Optional[Union[List, Dict, str]]) -> int:
+    empty_results = [b"", [], {}, None]
+    # result can be an empty list / dict / string
+    if result not in empty_results:
+        if redis_command == "MGET":
+            # only include valid key results within count
+            result = [x for x in result if x not in empty_results]
+            return len(result)
+        elif redis_command == "HMGET":
+            # only include valid key results within count
+            result = [x for x in result if x not in empty_results]
+            return 1 if len(result) > 0 else 0
+        else:
+            return 1
+    else:
+        return 0
+
+
+async def _run_redis_command_async(ctx: core.ExecutionContext, func, args, kwargs):
+    parsed_command = stringify_cache_args(args)
+    redis_command = parsed_command.split(" ")[0]
+    rowcount = None
+    result = None
+    try:
+        result = await func(*args, **kwargs)
+        return result
+    except BaseException:
+        rowcount = 0
+        raise
+    finally:
+        if rowcount is None:
+            rowcount = determine_row_count(redis_command=redis_command, result=result)
+        if redis_command not in ROW_RETURNING_COMMANDS:
+            rowcount = None
+        core.dispatch("redis.async_command.post", [ctx, rowcount])
diff --git a/ddtrace/contrib/internal/requests/connection.py b/ddtrace/contrib/internal/requests/connection.py
index 06d3347f0a1..9ad198faacd 100644
--- a/ddtrace/contrib/internal/requests/connection.py
+++ b/ddtrace/contrib/internal/requests/connection.py
@@ -6,6 +6,7 @@
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib import trace_utils
+from ddtrace.contrib.internal.trace_utils import _sanitized_url
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.compat import parse
@@ -67,7 +68,7 @@ def _wrap_send(func, instance, args, kwargs):
     if not request:
         return func(*args, **kwargs)
 
-    url = trace_utils._sanitized_url(request.url)
+    url = _sanitized_url(request.url)
     method = ""
     if request.method is not None:
         method = request.method.upper()
diff --git a/ddtrace/contrib/internal/requests/patch.py b/ddtrace/contrib/internal/requests/patch.py
index d4ec1f5182d..6485bb7aed8 100644
--- a/ddtrace/contrib/internal/requests/patch.py
+++ b/ddtrace/contrib/internal/requests/patch.py
@@ -7,7 +7,7 @@
 from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_request
 from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink
 from ddtrace.appsec._iast.constants import VULN_SSRF
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.internal.schema import schematize_service_name
 from ddtrace.internal.utils.formats import asbool
 from ddtrace.settings.asm import config as asm_config
diff --git a/ddtrace/contrib/internal/snowflake/patch.py b/ddtrace/contrib/internal/snowflake/patch.py
index d28844ea992..86d11bb9372 100644
--- a/ddtrace/contrib/internal/snowflake/patch.py
+++ b/ddtrace/contrib/internal/snowflake/patch.py
@@ -5,7 +5,7 @@
 from ddtrace import config
 from ddtrace.contrib.dbapi import TracedConnection
 from ddtrace.contrib.dbapi import TracedCursor
-from ddtrace.contrib.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import unwrap
 from ddtrace.ext import db
 from ddtrace.ext import net
 from ddtrace.internal.schema import schematize_service_name
diff --git a/ddtrace/contrib/internal/sqlalchemy/patch.py b/ddtrace/contrib/internal/sqlalchemy/patch.py
index 399a3bfdb32..916cc53daa4 100644
--- a/ddtrace/contrib/internal/sqlalchemy/patch.py
+++ b/ddtrace/contrib/internal/sqlalchemy/patch.py
@@ -3,7 +3,7 @@
 
 from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink
 from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION
-from ddtrace.contrib.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import unwrap
 from ddtrace.settings.asm import config as asm_config
 
 from .engine import _wrap_create_engine
diff --git a/ddtrace/contrib/internal/starlette/patch.py b/ddtrace/contrib/internal/starlette/patch.py
index 064722b67f1..e2ea8f9f4dc 100644
--- a/ddtrace/contrib/internal/starlette/patch.py
+++ b/ddtrace/contrib/internal/starlette/patch.py
@@ -17,7 +17,7 @@
 from ddtrace.appsec._iast import _is_iast_enabled
 from ddtrace.contrib import trace_utils
 from ddtrace.contrib.asgi import TraceMiddleware
-from ddtrace.contrib.trace_utils import with_traced_module
+from ddtrace.contrib.internal.trace_utils import with_traced_module
 from ddtrace.ext import http
 from ddtrace.internal import core
 from ddtrace.internal._exceptions import BlockingException
diff --git a/ddtrace/contrib/internal/structlog/patch.py b/ddtrace/contrib/internal/structlog/patch.py
index b0ccd3c88db..d529b8d33a6 100644
--- a/ddtrace/contrib/internal/structlog/patch.py
+++ b/ddtrace/contrib/internal/structlog/patch.py
@@ -8,8 +8,8 @@
 from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_TRACE_ID
 from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_VALUE_EMPTY
 from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_VERSION
-from ddtrace.contrib.trace_utils import unwrap as _u
-from ddtrace.contrib.trace_utils import wrap as _w
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import wrap as _w
 from ddtrace.internal.utils import get_argument_value
 from ddtrace.internal.utils import set_argument_value
 
diff --git a/ddtrace/contrib/internal/tornado/handlers.py b/ddtrace/contrib/internal/tornado/handlers.py
index f5d6955cdee..ff3a97cf2b8 100644
--- a/ddtrace/contrib/internal/tornado/handlers.py
+++ b/ddtrace/contrib/internal/tornado/handlers.py
@@ -7,7 +7,7 @@
 from ddtrace.constants import SPAN_KIND
 from ddtrace.constants import SPAN_MEASURED_KEY
 from ddtrace.contrib import trace_utils
-from ddtrace.contrib.trace_utils import set_http_meta
+from ddtrace.contrib.internal.trace_utils import set_http_meta
 from ddtrace.ext import SpanKind
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.constants import COMPONENT
diff --git a/ddtrace/contrib/internal/trace_utils.py b/ddtrace/contrib/internal/trace_utils.py
new file mode 100644
index 00000000000..7d2ea0c9986
--- /dev/null
+++ b/ddtrace/contrib/internal/trace_utils.py
@@ -0,0 +1,719 @@
+"""
+This module contains utility functions for writing ddtrace integrations.
+"""
+
+from collections import deque
+import ipaddress
+import re
+from typing import TYPE_CHECKING  # noqa:F401
+from typing import Any  # noqa:F401
+from typing import Callable  # noqa:F401
+from typing import Dict  # noqa:F401
+from typing import Generator  # noqa:F401
+from typing import Iterator  # noqa:F401
+from typing import List  # noqa:F401
+from typing import Mapping  # noqa:F401
+from typing import Optional  # noqa:F401
+from typing import Tuple  # noqa:F401
+from typing import Union  # noqa:F401
+from typing import cast  # noqa:F401
+
+import wrapt
+
+from ddtrace import config
+from ddtrace.ext import http
+from ddtrace.ext import net
+from ddtrace.ext import user
+from ddtrace.internal import core
+from ddtrace.internal.compat import ensure_text
+from ddtrace.internal.compat import ip_is_global
+from ddtrace.internal.compat import parse
+from ddtrace.internal.logger import get_logger
+from ddtrace.internal.utils.cache import cached
+from ddtrace.internal.utils.http import normalize_header_name
+from ddtrace.internal.utils.http import redact_url
+from ddtrace.internal.utils.http import strip_query_string
+import ddtrace.internal.utils.wrappers
+from ddtrace.propagation.http import HTTPPropagator
+from ddtrace.settings.asm import config as asm_config
+from ddtrace.trace import Pin
+
+
+if TYPE_CHECKING:  # pragma: no cover
+    from ddtrace import Span  # noqa:F401
+    from ddtrace import Tracer  # noqa:F401
+    from ddtrace.settings import IntegrationConfig  # noqa:F401
+
+
+log = get_logger(__name__)
+
+wrap = wrapt.wrap_function_wrapper
+unwrap = ddtrace.internal.utils.wrappers.unwrap
+iswrapped = ddtrace.internal.utils.wrappers.iswrapped
+
+REQUEST = "request"
+RESPONSE = "response"
+
+# Tag normalization based on: https://docs.datadoghq.com/tagging/#defining-tags
+# With the exception of '.' in header names which are replaced with '_' to avoid
+# starting a "new object" on the UI.
+NORMALIZE_PATTERN = re.compile(r"([^a-z0-9_\-:/]){1}")
+
+# Possible User Agent header.
+USER_AGENT_PATTERNS = ("http-user-agent", "user-agent")
+
+IP_PATTERNS = (
+    "x-forwarded-for",
+    "x-real-ip",
+    "true-client-ip",
+    "x-client-ip",
+    "forwarded-for",
+    "x-cluster-client-ip",
+    "fastly-client-ip",
+    "cf-connecting-ip",
+    "cf-connecting-ipv6",
+)
+
+
+@cached()
+def _normalized_header_name(header_name):
+    # type: (str) -> str
+    return NORMALIZE_PATTERN.sub("_", normalize_header_name(header_name))
+
+
+def _get_header_value_case_insensitive(headers, keyname):
+    # type: (Mapping[str, str], str) -> Optional[str]
+    """
+    Get a header in a case insensitive way. This function is meant for frameworks
+    like Django < 2.2 that don't store the headers in a case insensitive mapping.
+    """
+    # just in case we are lucky
+    shortcut_value = headers.get(keyname)
+    if shortcut_value is not None:
+        return shortcut_value
+
+    for key, value in headers.items():
+        if key.lower().replace("_", "-") == keyname:
+            return value
+
+    return None
+
+
+def _normalize_tag_name(request_or_response, header_name):
+    # type: (str, str) -> str
+    """
+    Given a tag name, e.g. 'Content-Type', returns a corresponding normalized tag name, i.e
+    'http.request.headers.content_type'. Rules applied actual header name are:
+    - any letter is converted to lowercase
+    - any digit is left unchanged
+    - any block of any length of different ASCII chars is converted to a single underscore '_'
+    :param request_or_response: The context of the headers: request|response
+    :param header_name: The header's name
+    :type header_name: str
+    :rtype: str
+    """
+    # Looking at:
+    #   - http://www.iana.org/assignments/message-headers/message-headers.xhtml
+    #   - https://tools.ietf.org/html/rfc6648
+    # and for consistency with other language integrations seems safe to assume the following algorithm for header
+    # names normalization:
+    #   - any letter is converted to lowercase
+    #   - any digit is left unchanged
+    #   - any block of any length of different ASCII chars is converted to a single underscore '_'
+    normalized_name = _normalized_header_name(header_name)
+    return "http.{}.headers.{}".format(request_or_response, normalized_name)
+
+
+def _store_headers(headers, span, integration_config, request_or_response):
+    # type: (Dict[str, str], Span, IntegrationConfig, str) -> None
+    """
+    :param headers: A dict of http headers to be stored in the span
+    :type headers: dict or list
+    :param span: The Span instance where tags will be stored
+    :type span: ddtrace._trace.span.Span
+    :param integration_config: An integration specific config object.
+    :type integration_config: ddtrace.settings.IntegrationConfig
+    """
+    if not isinstance(headers, dict):
+        try:
+            headers = dict(headers)
+        except Exception:
+            return
+
+    if integration_config is None:
+        log.debug("Skipping headers tracing as no integration config was provided")
+        return
+
+    for header_name, header_value in headers.items():
+        # config._header_tag_name gets an element of the dictionary in config._trace_http_header_tags
+        # which gets the value from DD_TRACE_HEADER_TAGS environment variable."""
+        tag_name = integration_config._header_tag_name(header_name)
+        if tag_name is None:
+            continue
+        # An empty tag defaults to a http.<request or response>.headers.<header name> tag
+        span.set_tag_str(tag_name or _normalize_tag_name(request_or_response, header_name), header_value)
+
+
+def _get_request_header_user_agent(headers, headers_are_case_sensitive=False):
+    # type: (Mapping[str, str], bool) -> str
+    """Get user agent from request headers
+    :param headers: A dict of http headers to be stored in the span
+    :type headers: dict or list
+    """
+    for key_pattern in USER_AGENT_PATTERNS:
+        if not headers_are_case_sensitive:
+            user_agent = headers.get(key_pattern)
+        else:
+            user_agent = _get_header_value_case_insensitive(headers, key_pattern)
+
+        if user_agent:
+            return user_agent
+    return ""
+
+
+def _get_request_header_client_ip(headers, peer_ip=None, headers_are_case_sensitive=False):
+    # type: (Optional[Mapping[str, str]], Optional[str], bool) -> str
+
+    def get_header_value(key):  # type: (str) -> Optional[str]
+        if not headers_are_case_sensitive:
+            return headers.get(key)
+
+        return _get_header_value_case_insensitive(headers, key)
+
+    if not headers:
+        try:
+            _ = ipaddress.ip_address(str(peer_ip))
+        except ValueError:
+            return ""
+        return peer_ip
+
+    ip_header_value = ""
+    user_configured_ip_header = config._client_ip_header
+    if user_configured_ip_header:
+        # Used selected the header to use to get the IP
+        ip_header_value = get_header_value(
+            user_configured_ip_header.lower().replace("_", "-")
+            if headers_are_case_sensitive
+            else user_configured_ip_header
+        )
+        if not ip_header_value:
+            log.debug("DD_TRACE_CLIENT_IP_HEADER configured but '%s' header missing", user_configured_ip_header)
+            return ""
+
+        try:
+            _ = ipaddress.ip_address(str(ip_header_value))
+        except ValueError:
+            log.debug("Invalid IP address from configured %s header: %s", user_configured_ip_header, ip_header_value)
+            return ""
+
+    else:
+        if headers_are_case_sensitive:
+            new_headers = {k.lower().replace("_", "-"): v for k, v in headers.items()}
+            for ip_header in IP_PATTERNS:
+                if ip_header in new_headers:
+                    ip_header_value = new_headers[ip_header]
+                    break
+        else:
+            for ip_header in IP_PATTERNS:
+                if ip_header in headers:
+                    ip_header_value = headers[ip_header]
+                    break
+
+    private_ip_from_headers = ""
+
+    if ip_header_value:
+        # At this point, we have one IP header, check its value and retrieve the first public IP
+        ip_list = ip_header_value.split(",")
+        for ip in ip_list:
+            ip = ip.strip()
+            if not ip:
+                continue
+
+            try:
+                if ip_is_global(ip):
+                    return ip
+                elif not private_ip_from_headers:
+                    # IP is private, store it just in case we don't find a public one later
+                    private_ip_from_headers = ip
+            except ValueError:  # invalid IP
+                continue
+
+    # At this point we have none or maybe one private ip from the headers: check the peer ip in
+    # case it's public and, if not, return either the private_ip from the headers (if we have one)
+    # or the peer private ip
+    try:
+        if ip_is_global(peer_ip) or not private_ip_from_headers:
+            return peer_ip
+    except ValueError:
+        pass
+
+    return private_ip_from_headers
+
+
+def _store_request_headers(headers, span, integration_config):
+    # type: (Dict[str, str], Span, IntegrationConfig) -> None
+    """
+    Store request headers as a span's tags
+    :param headers: All the request's http headers, will be filtered through the whitelist
+    :type headers: dict or list
+    :param span: The Span instance where tags will be stored
+    :type span: ddtrace.Span
+    :param integration_config: An integration specific config object.
+    :type integration_config: ddtrace.settings.IntegrationConfig
+    """
+    _store_headers(headers, span, integration_config, REQUEST)
+
+
+def _store_response_headers(headers, span, integration_config):
+    # type: (Dict[str, str], Span, IntegrationConfig) -> None
+    """
+    Store response headers as a span's tags
+    :param headers: All the response's http headers, will be filtered through the whitelist
+    :type headers: dict or list
+    :param span: The Span instance where tags will be stored
+    :type span: ddtrace.Span
+    :param integration_config: An integration specific config object.
+    :type integration_config: ddtrace.settings.IntegrationConfig
+    """
+    _store_headers(headers, span, integration_config, RESPONSE)
+
+
+def _sanitized_url(url):
+    # type: (str) -> str
+    """
+    Sanitize url by removing parts with potential auth info
+    """
+    if "@" in url:
+        parsed = parse.urlparse(url)
+        netloc = parsed.netloc
+
+        if "@" not in netloc:
+            # Safe url, `@` not in netloc
+            return url
+
+        netloc = netloc[netloc.index("@") + 1 :]
+        return parse.urlunparse(
+            (
+                parsed.scheme,
+                netloc,
+                parsed.path,
+                "",
+                parsed.query,
+                "",
+            )
+        )
+
+    return url
+
+
+def with_traced_module(func):
+    """Helper for providing tracing essentials (module and pin) for tracing
+    wrappers.
+
+    This helper enables tracing wrappers to dynamically be disabled when the
+    corresponding pin is disabled.
+
+    Usage::
+
+        @with_traced_module
+        def my_traced_wrapper(django, pin, func, instance, args, kwargs):
+            # Do tracing stuff
+            pass
+
+        def patch():
+            import django
+            wrap(django.somefunc, my_traced_wrapper(django))
+    """
+
+    def with_mod(mod):
+        def wrapper(wrapped, instance, args, kwargs):
+            pin = Pin._find(instance, mod)
+            if pin and not pin.enabled():
+                return wrapped(*args, **kwargs)
+            elif not pin:
+                log.debug("Pin not found for traced method %r", wrapped)
+                return wrapped(*args, **kwargs)
+            return func(mod, pin, wrapped, instance, args, kwargs)
+
+        return wrapper
+
+    return with_mod
+
+
+def distributed_tracing_enabled(int_config, default=False):
+    # type: (IntegrationConfig, bool) -> bool
+    """Returns whether distributed tracing is enabled for this integration config"""
+    if "distributed_tracing_enabled" in int_config and int_config.distributed_tracing_enabled is not None:
+        return int_config.distributed_tracing_enabled
+    elif "distributed_tracing" in int_config and int_config.distributed_tracing is not None:
+        return int_config.distributed_tracing
+    return default
+
+
+def int_service(pin, int_config, default=None):
+    # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str]
+    """Returns the service name for an integration which is internal
+    to the application. Internal meaning that the work belongs to the
+    user's application. Eg. Web framework, sqlalchemy, web servers.
+
+    For internal integrations we prioritize overrides, then global defaults and
+    lastly the default provided by the integration.
+    """
+    # Pin has top priority since it is user defined in code
+    if pin is not None and pin.service:
+        return pin.service
+
+    # Config is next since it is also configured via code
+    # Note that both service and service_name are used by
+    # integrations.
+    if "service" in int_config and int_config.service is not None:
+        return cast(str, int_config.service)
+    if "service_name" in int_config and int_config.service_name is not None:
+        return cast(str, int_config.service_name)
+
+    global_service = int_config.global_config._get_service()
+    # We check if global_service != _inferred_base_service since global service (config.service)
+    # defaults to _inferred_base_service when no DD_SERVICE is set. In this case, we want to not
+    # use the inferred base service value, and instead use the integration default service. If we
+    # didn't do this, we would have a massive breaking change from adding inferred_base_service.
+    if global_service and global_service != int_config.global_config._inferred_base_service:
+        return cast(str, global_service)
+
+    if "_default_service" in int_config and int_config._default_service is not None:
+        return cast(str, int_config._default_service)
+
+    if default is None and global_service:
+        return cast(str, global_service)
+
+    return default
+
+
+def ext_service(pin, int_config, default=None):
+    # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str]
+    """Returns the service name for an integration which is external
+    to the application. External meaning that the integration generates
+    spans wrapping code that is outside the scope of the user's application. Eg. A database, RPC, cache, etc.
+    """
+    if pin is not None and pin.service:
+        return pin.service
+
+    if "service" in int_config and int_config.service is not None:
+        return cast(str, int_config.service)
+    if "service_name" in int_config and int_config.service_name is not None:
+        return cast(str, int_config.service_name)
+
+    if "_default_service" in int_config and int_config._default_service is not None:
+        return cast(str, int_config._default_service)
+
+    # A default is required since it's an external service.
+    return default
+
+
+def _set_url_tag(integration_config, span, url, query):
+    # type: (IntegrationConfig, Span, str, str) -> None
+    if not integration_config.http_tag_query_string:
+        span.set_tag_str(http.URL, strip_query_string(url))
+    elif config._global_query_string_obfuscation_disabled:
+        # TODO(munir): This case exists for backwards compatibility. To remove query strings from URLs,
+        # users should set ``DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING=False``. This case should be
+        # removed when config.global_query_string_obfuscation_disabled is removed (v3.0).
+        span.set_tag_str(http.URL, url)
+    elif getattr(config._obfuscation_query_string_pattern, "pattern", None) == b"":
+        # obfuscation is disabled when DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP=""
+        span.set_tag_str(http.URL, strip_query_string(url))
+    else:
+        span.set_tag_str(http.URL, redact_url(url, config._obfuscation_query_string_pattern, query))
+
+
+def set_http_meta(
+    span,  # type: Span
+    integration_config,  # type: IntegrationConfig
+    method=None,  # type: Optional[str]
+    url=None,  # type: Optional[str]
+    target_host=None,  # type: Optional[str]
+    server_address=None,  # type: Optional[str]
+    status_code=None,  # type: Optional[Union[int, str]]
+    status_msg=None,  # type: Optional[str]
+    query=None,  # type: Optional[str]
+    parsed_query=None,  # type: Optional[Mapping[str, str]]
+    request_headers=None,  # type: Optional[Mapping[str, str]]
+    response_headers=None,  # type: Optional[Mapping[str, str]]
+    retries_remain=None,  # type: Optional[Union[int, str]]
+    raw_uri=None,  # type: Optional[str]
+    request_cookies=None,  # type: Optional[Dict[str, str]]
+    request_path_params=None,  # type: Optional[Dict[str, str]]
+    request_body=None,  # type: Optional[Union[str, Dict[str, List[str]]]]
+    peer_ip=None,  # type: Optional[str]
+    headers_are_case_sensitive=False,  # type: bool
+    route=None,  # type: Optional[str]
+    response_cookies=None,  # type: Optional[Dict[str, str]]
+):
+    # type: (...) -> None
+    """
+    Set HTTP metas on the span
+
+    :param method: the HTTP method
+    :param url: the HTTP URL
+    :param status_code: the HTTP status code
+    :param status_msg: the HTTP status message
+    :param query: the HTTP query part of the URI as a string
+    :param parsed_query: the HTTP query part of the URI as parsed by the framework and forwarded to the user code
+    :param request_headers: the HTTP request headers
+    :param response_headers: the HTTP response headers
+    :param raw_uri: the full raw HTTP URI (including ports and query)
+    :param request_cookies: the HTTP request cookies as a dict
+    :param request_path_params: the parameters of the HTTP URL as set by the framework: /posts/<id:int> would give us
+         { "id": <int_value> }
+    """
+    if method is not None:
+        span.set_tag_str(http.METHOD, method)
+
+    if url is not None:
+        url = _sanitized_url(url)
+        _set_url_tag(integration_config, span, url, query)
+
+    if target_host is not None:
+        span.set_tag_str(net.TARGET_HOST, target_host)
+
+    if server_address is not None:
+        span.set_tag_str(net.SERVER_ADDRESS, server_address)
+
+    if status_code is not None:
+        try:
+            int_status_code = int(status_code)
+        except (TypeError, ValueError):
+            log.debug("failed to convert http status code %r to int", status_code)
+        else:
+            span.set_tag_str(http.STATUS_CODE, str(status_code))
+            if config.http_server.is_error_code(int_status_code):
+                span.error = 1
+
+    if status_msg is not None:
+        span.set_tag_str(http.STATUS_MSG, status_msg)
+
+    if query is not None and integration_config.trace_query_string:
+        span.set_tag_str(http.QUERY_STRING, query)
+
+    request_ip = peer_ip
+    if request_headers:
+        user_agent = _get_request_header_user_agent(request_headers, headers_are_case_sensitive)
+        if user_agent:
+            span.set_tag_str(http.USER_AGENT, user_agent)
+
+        # We always collect the IP if appsec is enabled to report it on potential vulnerabilities.
+        # https://datadoghq.atlassian.net/wiki/spaces/APS/pages/2118779066/Client+IP+addresses+resolution
+        if asm_config._asm_enabled or config._retrieve_client_ip:
+            # Retrieve the IP if it was calculated on AppSecProcessor.on_span_start
+            request_ip = core.get_item("http.request.remote_ip", span=span)
+
+            if not request_ip:
+                # Not calculated: framework does not support IP blocking or testing env
+                request_ip = (
+                    _get_request_header_client_ip(request_headers, peer_ip, headers_are_case_sensitive) or peer_ip
+                )
+
+            if request_ip:
+                span.set_tag_str(http.CLIENT_IP, request_ip)
+                span.set_tag_str("network.client.ip", request_ip)
+
+        if integration_config.is_header_tracing_configured:
+            """We should store both http.<request_or_response>.headers.<header_name> and
+            http.<key>. The last one
+            is the DD standardized tag for user-agent"""
+            _store_request_headers(dict(request_headers), span, integration_config)
+
+    if response_headers is not None and integration_config.is_header_tracing_configured:
+        _store_response_headers(dict(response_headers), span, integration_config)
+
+    if retries_remain is not None:
+        span.set_tag_str(http.RETRIES_REMAIN, str(retries_remain))
+
+    core.dispatch(
+        "set_http_meta_for_asm",
+        [
+            span,
+            request_ip,
+            raw_uri,
+            route,
+            method,
+            request_headers,
+            request_cookies,
+            parsed_query,
+            request_path_params,
+            request_body,
+            status_code,
+            response_headers,
+            response_cookies,
+        ],
+    )
+
+    if route is not None:
+        span.set_tag_str(http.ROUTE, route)
+
+
+def activate_distributed_headers(tracer, int_config=None, request_headers=None, override=None):
+    # type: (Tracer, Optional[IntegrationConfig], Optional[Dict[str, str]], Optional[bool]) -> None
+    """
+    Helper for activating a distributed trace headers' context if enabled in integration config.
+    int_config will be used to check if distributed trace headers context will be activated, but
+    override will override whatever value is set in int_config if passed any value other than None.
+    """
+    if override is False:
+        return None
+
+    # Only extract and activate if we don't already have an activate span
+    # DEV: Only do this if there is an active Span, an active Context is fine to override
+    # DEV: Use _DD_TRACE_EXTRACT_IGNORE_ACTIVE_SPAN env var to override the default behavior
+    current_span = tracer.current_span()
+    if current_span and not config._extract_ignore_active_span:
+        log.debug(
+            "will not extract distributed headers, a Span(trace_id%d, span_id=%d) is already active",
+            current_span.trace_id,
+            current_span.span_id,
+        )
+        return
+
+    if override or (int_config and distributed_tracing_enabled(int_config)):
+        context = HTTPPropagator.extract(request_headers)
+
+        # Only need to activate the new context if something was propagated
+        # The new context must have one of these values in order for it to be activated
+        if not context.trace_id and not context._baggage and not context._span_links:
+            return None
+        # Do not reactivate a context with the same trace id
+        # DEV: An example could be nested web frameworks, when one layer already
+        #      parsed request headers and activated them.
+        #
+        # Example::
+        #
+        #     app = Flask(__name__)  # Traced via Flask instrumentation
+        #     app = DDWSGIMiddleware(app)  # Extra layer on top for WSGI
+        current_context = tracer.current_trace_context()
+
+        # We accept incoming contexts with only baggage or only span_links, however if we
+        # already have a current_context then an incoming context not
+        # containing a trace_id or containing the same trace_id
+        # should not be activated.
+        if current_context and (
+            not context.trace_id or (context.trace_id and context.trace_id == current_context.trace_id)
+        ):
+            log.debug(
+                "will not activate extracted Context(trace_id=%r, span_id=%r), a context with that trace id is already active",  # noqa: E501
+                context.trace_id,
+                context.span_id,
+            )
+            return None
+
+        # We have parsed a trace id from headers, and we do not already
+        # have a context with the same trace id active
+        tracer.context_provider.activate(context)
+
+
+def _flatten(
+    obj,  # type: Any
+    sep=".",  # type: str
+    prefix="",  # type: str
+    exclude_policy=None,  # type: Optional[Callable[[str], bool]]
+):
+    # type: (...) -> Generator[Tuple[str, Any], None, None]
+    s = deque()  # type: ignore
+    s.append((prefix, obj))
+    while s:
+        p, v = s.pop()
+        if exclude_policy is not None and exclude_policy(p):
+            continue
+        if isinstance(v, dict):
+            s.extend((sep.join((p, k)) if p else k, v) for k, v in v.items())
+        else:
+            yield p, v
+
+
+def set_flattened_tags(
+    span,  # type: Span
+    items,  # type: Iterator[Tuple[str, Any]]
+    sep=".",  # type: str
+    exclude_policy=None,  # type: Optional[Callable[[str], bool]]
+    processor=None,  # type: Optional[Callable[[Any], Any]]
+):
+    # type: (...) -> None
+    for prefix, value in items:
+        for tag, v in _flatten(value, sep, prefix, exclude_policy):
+            span.set_tag(tag, processor(v) if processor is not None else v)
+
+
+def set_user(
+    tracer,  # type: Tracer
+    user_id,  # type: str
+    name=None,  # type: Optional[str]
+    email=None,  # type: Optional[str]
+    scope=None,  # type: Optional[str]
+    role=None,  # type: Optional[str]
+    session_id=None,  # type: Optional[str]
+    propagate=False,  # type bool
+    span=None,  # type: Optional[Span]
+):
+    # type: (...) -> None
+    """Set user tags.
+    https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#user-related-attributes
+    https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/?tab=set_tag&code-lang=python
+    """
+    if span is None:
+        span = tracer.current_root_span()
+    if span:
+        if user_id:
+            str_user_id = str(user_id)
+            span.set_tag_str(user.ID, str_user_id)
+            if propagate:
+                span.context.dd_user_id = str_user_id
+
+        # All other fields are optional
+        if name:
+            span.set_tag_str(user.NAME, name)
+        if email:
+            span.set_tag_str(user.EMAIL, email)
+        if scope:
+            span.set_tag_str(user.SCOPE, scope)
+        if role:
+            span.set_tag_str(user.ROLE, role)
+        if session_id:
+            span.set_tag_str(user.SESSION_ID, session_id)
+
+        if asm_config._asm_enabled:
+            exc = core.dispatch_with_results("set_user_for_asm", [tracer, user_id]).block_user.exception
+            if exc:
+                raise exc
+
+    else:
+        log.warning(
+            "No root span in the current execution. Skipping set_user tags. "
+            "See https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/"
+            "?tab=set_user&code-lang=python for more information.",
+        )
+
+
+def extract_netloc_and_query_info_from_url(url):
+    # type: (str) -> Tuple[str, str]
+    parse_result = parse.urlparse(url)
+    query = parse_result.query
+
+    # Relative URLs don't have a netloc, so we force them
+    if not parse_result.netloc:
+        parse_result = parse.urlparse("//{url}".format(url=url))
+
+    netloc = parse_result.netloc.split("@", 1)[-1]  # Discard auth info
+    netloc = netloc.split(":", 1)[0]  # Discard port information
+    return netloc, query
+
+
+class InterruptException(Exception):
+    pass
+
+
+def _convert_to_string(attr):
+    # ensures attribute is converted to a string
+    if attr:
+        if isinstance(attr, int) or isinstance(attr, float):
+            return str(attr)
+        else:
+            return ensure_text(attr)
+    return attr
diff --git a/ddtrace/contrib/internal/trace_utils_async.py b/ddtrace/contrib/internal/trace_utils_async.py
new file mode 100644
index 00000000000..f58cc4e34bb
--- /dev/null
+++ b/ddtrace/contrib/internal/trace_utils_async.py
@@ -0,0 +1,39 @@
+"""
+async tracing utils
+
+Note that this module should only be imported in Python 3.5+.
+"""
+from ddtrace.internal.logger import get_logger
+from ddtrace.trace import Pin
+
+
+log = get_logger(__name__)
+
+
+def with_traced_module(func):
+    """Async version of trace_utils.with_traced_module.
+    Usage::
+
+        @with_traced_module
+        async def my_traced_wrapper(django, pin, func, instance, args, kwargs):
+            # Do tracing stuff
+            pass
+
+        def patch():
+            import django
+            wrap(django.somefunc, my_traced_wrapper(django))
+    """
+
+    def with_mod(mod):
+        async def wrapper(wrapped, instance, args, kwargs):
+            pin = Pin._find(instance, mod)
+            if pin and not pin.enabled():
+                return await wrapped(*args, **kwargs)
+            elif not pin:
+                log.debug("Pin not found for traced method %r", wrapped)
+                return await wrapped(*args, **kwargs)
+            return await func(mod, pin, wrapped, instance, args, kwargs)
+
+        return wrapper
+
+    return with_mod
diff --git a/ddtrace/contrib/internal/urllib/patch.py b/ddtrace/contrib/internal/urllib/patch.py
index b71b2a85392..ed5e2891f06 100644
--- a/ddtrace/contrib/internal/urllib/patch.py
+++ b/ddtrace/contrib/internal/urllib/patch.py
@@ -5,7 +5,7 @@
 from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_open
 from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink
 from ddtrace.appsec._iast.constants import VULN_SSRF
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.settings.asm import config as asm_config
 
 
diff --git a/ddtrace/contrib/internal/vertexai/patch.py b/ddtrace/contrib/internal/vertexai/patch.py
index bc6e46903c3..82ae00e1a77 100644
--- a/ddtrace/contrib/internal/vertexai/patch.py
+++ b/ddtrace/contrib/internal/vertexai/patch.py
@@ -4,13 +4,13 @@
 import vertexai
 
 from ddtrace import config
+from ddtrace.contrib.internal.trace_utils import unwrap
+from ddtrace.contrib.internal.trace_utils import with_traced_module
+from ddtrace.contrib.internal.trace_utils import wrap
 from ddtrace.contrib.internal.vertexai._utils import TracedAsyncVertexAIStreamResponse
 from ddtrace.contrib.internal.vertexai._utils import TracedVertexAIStreamResponse
 from ddtrace.contrib.internal.vertexai._utils import tag_request
 from ddtrace.contrib.internal.vertexai._utils import tag_response
-from ddtrace.contrib.trace_utils import unwrap
-from ddtrace.contrib.trace_utils import with_traced_module
-from ddtrace.contrib.trace_utils import wrap
 from ddtrace.llmobs._integrations import VertexAIIntegration
 from ddtrace.llmobs._integrations.utils import extract_model_name_google
 from ddtrace.trace import Pin
diff --git a/ddtrace/contrib/internal/webbrowser/patch.py b/ddtrace/contrib/internal/webbrowser/patch.py
index 965425d9ad5..1387df37ac9 100644
--- a/ddtrace/contrib/internal/webbrowser/patch.py
+++ b/ddtrace/contrib/internal/webbrowser/patch.py
@@ -5,7 +5,7 @@
 from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_open
 from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink
 from ddtrace.appsec._iast.constants import VULN_SSRF
-from ddtrace.contrib.trace_utils import unwrap as _u
+from ddtrace.contrib.internal.trace_utils import unwrap as _u
 from ddtrace.settings.asm import config as asm_config
 
 
diff --git a/ddtrace/contrib/internal/yaaredis/patch.py b/ddtrace/contrib/internal/yaaredis/patch.py
index 58c5a47bda4..f9cba77b5bb 100644
--- a/ddtrace/contrib/internal/yaaredis/patch.py
+++ b/ddtrace/contrib/internal/yaaredis/patch.py
@@ -6,7 +6,7 @@
 from ddtrace import config
 from ddtrace._trace.utils_redis import _instrument_redis_cmd
 from ddtrace._trace.utils_redis import _instrument_redis_execute_pipeline
-from ddtrace.contrib.redis_utils import _run_redis_command_async
+from ddtrace.contrib.internal.redis_utils import _run_redis_command_async
 from ddtrace.internal.schema import schematize_service_name
 from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning
 from ddtrace.internal.utils.formats import CMD_MAX_LEN
diff --git a/ddtrace/contrib/redis_utils.py b/ddtrace/contrib/redis_utils.py
index 63d950389c8..f802b198b81 100644
--- a/ddtrace/contrib/redis_utils.py
+++ b/ddtrace/contrib/redis_utils.py
@@ -1,84 +1,11 @@
-from typing import Dict
-from typing import List
-from typing import Optional
-from typing import Union
+from ddtrace.contrib.internal.redis_utils import *  # noqa: F403
+from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning
+from ddtrace.vendor.debtcollector import deprecate
 
-from ddtrace.ext import net
-from ddtrace.ext import redis as redisx
-from ddtrace.internal import core
-from ddtrace.internal.utils.formats import stringify_cache_args
 
-
-SINGLE_KEY_COMMANDS = [
-    "GET",
-    "GETDEL",
-    "GETEX",
-    "GETRANGE",
-    "GETSET",
-    "LINDEX",
-    "LRANGE",
-    "RPOP",
-    "LPOP",
-    "HGET",
-    "HGETALL",
-    "HKEYS",
-    "HMGET",
-    "HRANDFIELD",
-    "HVALS",
-]
-MULTI_KEY_COMMANDS = ["MGET"]
-ROW_RETURNING_COMMANDS = SINGLE_KEY_COMMANDS + MULTI_KEY_COMMANDS
-
-
-def _extract_conn_tags(conn_kwargs):
-    """Transform redis conn info into dogtrace metas"""
-    try:
-        conn_tags = {
-            net.TARGET_HOST: conn_kwargs["host"],
-            net.TARGET_PORT: conn_kwargs["port"],
-            net.SERVER_ADDRESS: conn_kwargs["host"],
-            redisx.DB: conn_kwargs.get("db") or 0,
-        }
-        client_name = conn_kwargs.get("client_name")
-        if client_name:
-            conn_tags[redisx.CLIENT_NAME] = client_name
-        return conn_tags
-    except Exception:
-        return {}
-
-
-def determine_row_count(redis_command: str, result: Optional[Union[List, Dict, str]]) -> int:
-    empty_results = [b"", [], {}, None]
-    # result can be an empty list / dict / string
-    if result not in empty_results:
-        if redis_command == "MGET":
-            # only include valid key results within count
-            result = [x for x in result if x not in empty_results]
-            return len(result)
-        elif redis_command == "HMGET":
-            # only include valid key results within count
-            result = [x for x in result if x not in empty_results]
-            return 1 if len(result) > 0 else 0
-        else:
-            return 1
-    else:
-        return 0
-
-
-async def _run_redis_command_async(ctx: core.ExecutionContext, func, args, kwargs):
-    parsed_command = stringify_cache_args(args)
-    redis_command = parsed_command.split(" ")[0]
-    rowcount = None
-    result = None
-    try:
-        result = await func(*args, **kwargs)
-        return result
-    except BaseException:
-        rowcount = 0
-        raise
-    finally:
-        if rowcount is None:
-            rowcount = determine_row_count(redis_command=redis_command, result=result)
-        if redis_command not in ROW_RETURNING_COMMANDS:
-            rowcount = None
-        core.dispatch("redis.async_command.post", [ctx, rowcount])
+deprecate(
+    "The ddtrace.contrib.redis_utils module is deprecated",
+    message="Import from ``ddtrace.contrib.internal.trace_utils`` instead.",
+    category=DDTraceDeprecationWarning,
+    removal_version="3.0.0",
+)
diff --git a/ddtrace/contrib/trace_utils.py b/ddtrace/contrib/trace_utils.py
index 7d2ea0c9986..c1d2054d23e 100644
--- a/ddtrace/contrib/trace_utils.py
+++ b/ddtrace/contrib/trace_utils.py
@@ -1,719 +1,42 @@
-"""
-This module contains utility functions for writing ddtrace integrations.
-"""
-
-from collections import deque
-import ipaddress
-import re
-from typing import TYPE_CHECKING  # noqa:F401
-from typing import Any  # noqa:F401
-from typing import Callable  # noqa:F401
-from typing import Dict  # noqa:F401
-from typing import Generator  # noqa:F401
-from typing import Iterator  # noqa:F401
-from typing import List  # noqa:F401
-from typing import Mapping  # noqa:F401
-from typing import Optional  # noqa:F401
-from typing import Tuple  # noqa:F401
-from typing import Union  # noqa:F401
-from typing import cast  # noqa:F401
-
-import wrapt
-
-from ddtrace import config
-from ddtrace.ext import http
-from ddtrace.ext import net
-from ddtrace.ext import user
-from ddtrace.internal import core
-from ddtrace.internal.compat import ensure_text
-from ddtrace.internal.compat import ip_is_global
-from ddtrace.internal.compat import parse
-from ddtrace.internal.logger import get_logger
-from ddtrace.internal.utils.cache import cached
-from ddtrace.internal.utils.http import normalize_header_name
-from ddtrace.internal.utils.http import redact_url
-from ddtrace.internal.utils.http import strip_query_string
-import ddtrace.internal.utils.wrappers
-from ddtrace.propagation.http import HTTPPropagator
-from ddtrace.settings.asm import config as asm_config
-from ddtrace.trace import Pin
-
-
-if TYPE_CHECKING:  # pragma: no cover
-    from ddtrace import Span  # noqa:F401
-    from ddtrace import Tracer  # noqa:F401
-    from ddtrace.settings import IntegrationConfig  # noqa:F401
-
-
-log = get_logger(__name__)
-
-wrap = wrapt.wrap_function_wrapper
-unwrap = ddtrace.internal.utils.wrappers.unwrap
-iswrapped = ddtrace.internal.utils.wrappers.iswrapped
-
-REQUEST = "request"
-RESPONSE = "response"
-
-# Tag normalization based on: https://docs.datadoghq.com/tagging/#defining-tags
-# With the exception of '.' in header names which are replaced with '_' to avoid
-# starting a "new object" on the UI.
-NORMALIZE_PATTERN = re.compile(r"([^a-z0-9_\-:/]){1}")
-
-# Possible User Agent header.
-USER_AGENT_PATTERNS = ("http-user-agent", "user-agent")
-
-IP_PATTERNS = (
-    "x-forwarded-for",
-    "x-real-ip",
-    "true-client-ip",
-    "x-client-ip",
-    "forwarded-for",
-    "x-cluster-client-ip",
-    "fastly-client-ip",
-    "cf-connecting-ip",
-    "cf-connecting-ipv6",
-)
-
-
-@cached()
-def _normalized_header_name(header_name):
-    # type: (str) -> str
-    return NORMALIZE_PATTERN.sub("_", normalize_header_name(header_name))
-
-
-def _get_header_value_case_insensitive(headers, keyname):
-    # type: (Mapping[str, str], str) -> Optional[str]
-    """
-    Get a header in a case insensitive way. This function is meant for frameworks
-    like Django < 2.2 that don't store the headers in a case insensitive mapping.
-    """
-    # just in case we are lucky
-    shortcut_value = headers.get(keyname)
-    if shortcut_value is not None:
-        return shortcut_value
-
-    for key, value in headers.items():
-        if key.lower().replace("_", "-") == keyname:
-            return value
-
-    return None
-
-
-def _normalize_tag_name(request_or_response, header_name):
-    # type: (str, str) -> str
-    """
-    Given a tag name, e.g. 'Content-Type', returns a corresponding normalized tag name, i.e
-    'http.request.headers.content_type'. Rules applied actual header name are:
-    - any letter is converted to lowercase
-    - any digit is left unchanged
-    - any block of any length of different ASCII chars is converted to a single underscore '_'
-    :param request_or_response: The context of the headers: request|response
-    :param header_name: The header's name
-    :type header_name: str
-    :rtype: str
-    """
-    # Looking at:
-    #   - http://www.iana.org/assignments/message-headers/message-headers.xhtml
-    #   - https://tools.ietf.org/html/rfc6648
-    # and for consistency with other language integrations seems safe to assume the following algorithm for header
-    # names normalization:
-    #   - any letter is converted to lowercase
-    #   - any digit is left unchanged
-    #   - any block of any length of different ASCII chars is converted to a single underscore '_'
-    normalized_name = _normalized_header_name(header_name)
-    return "http.{}.headers.{}".format(request_or_response, normalized_name)
-
-
-def _store_headers(headers, span, integration_config, request_or_response):
-    # type: (Dict[str, str], Span, IntegrationConfig, str) -> None
-    """
-    :param headers: A dict of http headers to be stored in the span
-    :type headers: dict or list
-    :param span: The Span instance where tags will be stored
-    :type span: ddtrace._trace.span.Span
-    :param integration_config: An integration specific config object.
-    :type integration_config: ddtrace.settings.IntegrationConfig
-    """
-    if not isinstance(headers, dict):
-        try:
-            headers = dict(headers)
-        except Exception:
-            return
-
-    if integration_config is None:
-        log.debug("Skipping headers tracing as no integration config was provided")
-        return
-
-    for header_name, header_value in headers.items():
-        # config._header_tag_name gets an element of the dictionary in config._trace_http_header_tags
-        # which gets the value from DD_TRACE_HEADER_TAGS environment variable."""
-        tag_name = integration_config._header_tag_name(header_name)
-        if tag_name is None:
-            continue
-        # An empty tag defaults to a http.<request or response>.headers.<header name> tag
-        span.set_tag_str(tag_name or _normalize_tag_name(request_or_response, header_name), header_value)
-
-
-def _get_request_header_user_agent(headers, headers_are_case_sensitive=False):
-    # type: (Mapping[str, str], bool) -> str
-    """Get user agent from request headers
-    :param headers: A dict of http headers to be stored in the span
-    :type headers: dict or list
-    """
-    for key_pattern in USER_AGENT_PATTERNS:
-        if not headers_are_case_sensitive:
-            user_agent = headers.get(key_pattern)
-        else:
-            user_agent = _get_header_value_case_insensitive(headers, key_pattern)
-
-        if user_agent:
-            return user_agent
-    return ""
-
-
-def _get_request_header_client_ip(headers, peer_ip=None, headers_are_case_sensitive=False):
-    # type: (Optional[Mapping[str, str]], Optional[str], bool) -> str
-
-    def get_header_value(key):  # type: (str) -> Optional[str]
-        if not headers_are_case_sensitive:
-            return headers.get(key)
-
-        return _get_header_value_case_insensitive(headers, key)
-
-    if not headers:
-        try:
-            _ = ipaddress.ip_address(str(peer_ip))
-        except ValueError:
-            return ""
-        return peer_ip
-
-    ip_header_value = ""
-    user_configured_ip_header = config._client_ip_header
-    if user_configured_ip_header:
-        # Used selected the header to use to get the IP
-        ip_header_value = get_header_value(
-            user_configured_ip_header.lower().replace("_", "-")
-            if headers_are_case_sensitive
-            else user_configured_ip_header
-        )
-        if not ip_header_value:
-            log.debug("DD_TRACE_CLIENT_IP_HEADER configured but '%s' header missing", user_configured_ip_header)
-            return ""
-
-        try:
-            _ = ipaddress.ip_address(str(ip_header_value))
-        except ValueError:
-            log.debug("Invalid IP address from configured %s header: %s", user_configured_ip_header, ip_header_value)
-            return ""
-
-    else:
-        if headers_are_case_sensitive:
-            new_headers = {k.lower().replace("_", "-"): v for k, v in headers.items()}
-            for ip_header in IP_PATTERNS:
-                if ip_header in new_headers:
-                    ip_header_value = new_headers[ip_header]
-                    break
-        else:
-            for ip_header in IP_PATTERNS:
-                if ip_header in headers:
-                    ip_header_value = headers[ip_header]
-                    break
-
-    private_ip_from_headers = ""
-
-    if ip_header_value:
-        # At this point, we have one IP header, check its value and retrieve the first public IP
-        ip_list = ip_header_value.split(",")
-        for ip in ip_list:
-            ip = ip.strip()
-            if not ip:
-                continue
-
-            try:
-                if ip_is_global(ip):
-                    return ip
-                elif not private_ip_from_headers:
-                    # IP is private, store it just in case we don't find a public one later
-                    private_ip_from_headers = ip
-            except ValueError:  # invalid IP
-                continue
-
-    # At this point we have none or maybe one private ip from the headers: check the peer ip in
-    # case it's public and, if not, return either the private_ip from the headers (if we have one)
-    # or the peer private ip
-    try:
-        if ip_is_global(peer_ip) or not private_ip_from_headers:
-            return peer_ip
-    except ValueError:
-        pass
-
-    return private_ip_from_headers
-
-
-def _store_request_headers(headers, span, integration_config):
-    # type: (Dict[str, str], Span, IntegrationConfig) -> None
-    """
-    Store request headers as a span's tags
-    :param headers: All the request's http headers, will be filtered through the whitelist
-    :type headers: dict or list
-    :param span: The Span instance where tags will be stored
-    :type span: ddtrace.Span
-    :param integration_config: An integration specific config object.
-    :type integration_config: ddtrace.settings.IntegrationConfig
-    """
-    _store_headers(headers, span, integration_config, REQUEST)
-
-
-def _store_response_headers(headers, span, integration_config):
-    # type: (Dict[str, str], Span, IntegrationConfig) -> None
-    """
-    Store response headers as a span's tags
-    :param headers: All the response's http headers, will be filtered through the whitelist
-    :type headers: dict or list
-    :param span: The Span instance where tags will be stored
-    :type span: ddtrace.Span
-    :param integration_config: An integration specific config object.
-    :type integration_config: ddtrace.settings.IntegrationConfig
-    """
-    _store_headers(headers, span, integration_config, RESPONSE)
-
-
-def _sanitized_url(url):
-    # type: (str) -> str
-    """
-    Sanitize url by removing parts with potential auth info
-    """
-    if "@" in url:
-        parsed = parse.urlparse(url)
-        netloc = parsed.netloc
-
-        if "@" not in netloc:
-            # Safe url, `@` not in netloc
-            return url
-
-        netloc = netloc[netloc.index("@") + 1 :]
-        return parse.urlunparse(
-            (
-                parsed.scheme,
-                netloc,
-                parsed.path,
-                "",
-                parsed.query,
-                "",
-            )
-        )
-
-    return url
-
-
-def with_traced_module(func):
-    """Helper for providing tracing essentials (module and pin) for tracing
-    wrappers.
-
-    This helper enables tracing wrappers to dynamically be disabled when the
-    corresponding pin is disabled.
-
-    Usage::
-
-        @with_traced_module
-        def my_traced_wrapper(django, pin, func, instance, args, kwargs):
-            # Do tracing stuff
-            pass
-
-        def patch():
-            import django
-            wrap(django.somefunc, my_traced_wrapper(django))
-    """
-
-    def with_mod(mod):
-        def wrapper(wrapped, instance, args, kwargs):
-            pin = Pin._find(instance, mod)
-            if pin and not pin.enabled():
-                return wrapped(*args, **kwargs)
-            elif not pin:
-                log.debug("Pin not found for traced method %r", wrapped)
-                return wrapped(*args, **kwargs)
-            return func(mod, pin, wrapped, instance, args, kwargs)
-
-        return wrapper
-
-    return with_mod
-
-
-def distributed_tracing_enabled(int_config, default=False):
-    # type: (IntegrationConfig, bool) -> bool
-    """Returns whether distributed tracing is enabled for this integration config"""
-    if "distributed_tracing_enabled" in int_config and int_config.distributed_tracing_enabled is not None:
-        return int_config.distributed_tracing_enabled
-    elif "distributed_tracing" in int_config and int_config.distributed_tracing is not None:
-        return int_config.distributed_tracing
-    return default
-
-
-def int_service(pin, int_config, default=None):
-    # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str]
-    """Returns the service name for an integration which is internal
-    to the application. Internal meaning that the work belongs to the
-    user's application. Eg. Web framework, sqlalchemy, web servers.
-
-    For internal integrations we prioritize overrides, then global defaults and
-    lastly the default provided by the integration.
-    """
-    # Pin has top priority since it is user defined in code
-    if pin is not None and pin.service:
-        return pin.service
-
-    # Config is next since it is also configured via code
-    # Note that both service and service_name are used by
-    # integrations.
-    if "service" in int_config and int_config.service is not None:
-        return cast(str, int_config.service)
-    if "service_name" in int_config and int_config.service_name is not None:
-        return cast(str, int_config.service_name)
-
-    global_service = int_config.global_config._get_service()
-    # We check if global_service != _inferred_base_service since global service (config.service)
-    # defaults to _inferred_base_service when no DD_SERVICE is set. In this case, we want to not
-    # use the inferred base service value, and instead use the integration default service. If we
-    # didn't do this, we would have a massive breaking change from adding inferred_base_service.
-    if global_service and global_service != int_config.global_config._inferred_base_service:
-        return cast(str, global_service)
-
-    if "_default_service" in int_config and int_config._default_service is not None:
-        return cast(str, int_config._default_service)
-
-    if default is None and global_service:
-        return cast(str, global_service)
-
-    return default
-
-
-def ext_service(pin, int_config, default=None):
-    # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str]
-    """Returns the service name for an integration which is external
-    to the application. External meaning that the integration generates
-    spans wrapping code that is outside the scope of the user's application. Eg. A database, RPC, cache, etc.
-    """
-    if pin is not None and pin.service:
-        return pin.service
-
-    if "service" in int_config and int_config.service is not None:
-        return cast(str, int_config.service)
-    if "service_name" in int_config and int_config.service_name is not None:
-        return cast(str, int_config.service_name)
-
-    if "_default_service" in int_config and int_config._default_service is not None:
-        return cast(str, int_config._default_service)
-
-    # A default is required since it's an external service.
-    return default
-
-
-def _set_url_tag(integration_config, span, url, query):
-    # type: (IntegrationConfig, Span, str, str) -> None
-    if not integration_config.http_tag_query_string:
-        span.set_tag_str(http.URL, strip_query_string(url))
-    elif config._global_query_string_obfuscation_disabled:
-        # TODO(munir): This case exists for backwards compatibility. To remove query strings from URLs,
-        # users should set ``DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING=False``. This case should be
-        # removed when config.global_query_string_obfuscation_disabled is removed (v3.0).
-        span.set_tag_str(http.URL, url)
-    elif getattr(config._obfuscation_query_string_pattern, "pattern", None) == b"":
-        # obfuscation is disabled when DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP=""
-        span.set_tag_str(http.URL, strip_query_string(url))
-    else:
-        span.set_tag_str(http.URL, redact_url(url, config._obfuscation_query_string_pattern, query))
-
-
-def set_http_meta(
-    span,  # type: Span
-    integration_config,  # type: IntegrationConfig
-    method=None,  # type: Optional[str]
-    url=None,  # type: Optional[str]
-    target_host=None,  # type: Optional[str]
-    server_address=None,  # type: Optional[str]
-    status_code=None,  # type: Optional[Union[int, str]]
-    status_msg=None,  # type: Optional[str]
-    query=None,  # type: Optional[str]
-    parsed_query=None,  # type: Optional[Mapping[str, str]]
-    request_headers=None,  # type: Optional[Mapping[str, str]]
-    response_headers=None,  # type: Optional[Mapping[str, str]]
-    retries_remain=None,  # type: Optional[Union[int, str]]
-    raw_uri=None,  # type: Optional[str]
-    request_cookies=None,  # type: Optional[Dict[str, str]]
-    request_path_params=None,  # type: Optional[Dict[str, str]]
-    request_body=None,  # type: Optional[Union[str, Dict[str, List[str]]]]
-    peer_ip=None,  # type: Optional[str]
-    headers_are_case_sensitive=False,  # type: bool
-    route=None,  # type: Optional[str]
-    response_cookies=None,  # type: Optional[Dict[str, str]]
-):
-    # type: (...) -> None
-    """
-    Set HTTP metas on the span
-
-    :param method: the HTTP method
-    :param url: the HTTP URL
-    :param status_code: the HTTP status code
-    :param status_msg: the HTTP status message
-    :param query: the HTTP query part of the URI as a string
-    :param parsed_query: the HTTP query part of the URI as parsed by the framework and forwarded to the user code
-    :param request_headers: the HTTP request headers
-    :param response_headers: the HTTP response headers
-    :param raw_uri: the full raw HTTP URI (including ports and query)
-    :param request_cookies: the HTTP request cookies as a dict
-    :param request_path_params: the parameters of the HTTP URL as set by the framework: /posts/<id:int> would give us
-         { "id": <int_value> }
-    """
-    if method is not None:
-        span.set_tag_str(http.METHOD, method)
-
-    if url is not None:
-        url = _sanitized_url(url)
-        _set_url_tag(integration_config, span, url, query)
-
-    if target_host is not None:
-        span.set_tag_str(net.TARGET_HOST, target_host)
-
-    if server_address is not None:
-        span.set_tag_str(net.SERVER_ADDRESS, server_address)
-
-    if status_code is not None:
-        try:
-            int_status_code = int(status_code)
-        except (TypeError, ValueError):
-            log.debug("failed to convert http status code %r to int", status_code)
-        else:
-            span.set_tag_str(http.STATUS_CODE, str(status_code))
-            if config.http_server.is_error_code(int_status_code):
-                span.error = 1
-
-    if status_msg is not None:
-        span.set_tag_str(http.STATUS_MSG, status_msg)
-
-    if query is not None and integration_config.trace_query_string:
-        span.set_tag_str(http.QUERY_STRING, query)
-
-    request_ip = peer_ip
-    if request_headers:
-        user_agent = _get_request_header_user_agent(request_headers, headers_are_case_sensitive)
-        if user_agent:
-            span.set_tag_str(http.USER_AGENT, user_agent)
-
-        # We always collect the IP if appsec is enabled to report it on potential vulnerabilities.
-        # https://datadoghq.atlassian.net/wiki/spaces/APS/pages/2118779066/Client+IP+addresses+resolution
-        if asm_config._asm_enabled or config._retrieve_client_ip:
-            # Retrieve the IP if it was calculated on AppSecProcessor.on_span_start
-            request_ip = core.get_item("http.request.remote_ip", span=span)
-
-            if not request_ip:
-                # Not calculated: framework does not support IP blocking or testing env
-                request_ip = (
-                    _get_request_header_client_ip(request_headers, peer_ip, headers_are_case_sensitive) or peer_ip
-                )
-
-            if request_ip:
-                span.set_tag_str(http.CLIENT_IP, request_ip)
-                span.set_tag_str("network.client.ip", request_ip)
-
-        if integration_config.is_header_tracing_configured:
-            """We should store both http.<request_or_response>.headers.<header_name> and
-            http.<key>. The last one
-            is the DD standardized tag for user-agent"""
-            _store_request_headers(dict(request_headers), span, integration_config)
-
-    if response_headers is not None and integration_config.is_header_tracing_configured:
-        _store_response_headers(dict(response_headers), span, integration_config)
-
-    if retries_remain is not None:
-        span.set_tag_str(http.RETRIES_REMAIN, str(retries_remain))
-
-    core.dispatch(
-        "set_http_meta_for_asm",
-        [
-            span,
-            request_ip,
-            raw_uri,
-            route,
-            method,
-            request_headers,
-            request_cookies,
-            parsed_query,
-            request_path_params,
-            request_body,
-            status_code,
-            response_headers,
-            response_cookies,
-        ],
-    )
-
-    if route is not None:
-        span.set_tag_str(http.ROUTE, route)
-
-
-def activate_distributed_headers(tracer, int_config=None, request_headers=None, override=None):
-    # type: (Tracer, Optional[IntegrationConfig], Optional[Dict[str, str]], Optional[bool]) -> None
-    """
-    Helper for activating a distributed trace headers' context if enabled in integration config.
-    int_config will be used to check if distributed trace headers context will be activated, but
-    override will override whatever value is set in int_config if passed any value other than None.
-    """
-    if override is False:
-        return None
-
-    # Only extract and activate if we don't already have an activate span
-    # DEV: Only do this if there is an active Span, an active Context is fine to override
-    # DEV: Use _DD_TRACE_EXTRACT_IGNORE_ACTIVE_SPAN env var to override the default behavior
-    current_span = tracer.current_span()
-    if current_span and not config._extract_ignore_active_span:
-        log.debug(
-            "will not extract distributed headers, a Span(trace_id%d, span_id=%d) is already active",
-            current_span.trace_id,
-            current_span.span_id,
+from ddtrace.contrib.internal.redis_utils import MULTI_KEY_COMMANDS  # noqa: F401
+from ddtrace.contrib.internal.redis_utils import ROW_RETURNING_COMMANDS  # noqa: F401
+from ddtrace.contrib.internal.redis_utils import SINGLE_KEY_COMMANDS  # noqa: F401
+from ddtrace.contrib.internal.redis_utils import determine_row_count  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import IP_PATTERNS  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import NORMALIZE_PATTERN  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import REQUEST  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import RESPONSE  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import USER_AGENT_PATTERNS  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import activate_distributed_headers  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import distributed_tracing_enabled  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import ext_service  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import extract_netloc_and_query_info_from_url  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import int_service  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import iswrapped  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import set_flattened_tags  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import set_http_meta  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import set_user  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import unwrap  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import with_traced_module  # noqa: F401
+from ddtrace.contrib.internal.trace_utils import wrap  # noqa: F401
+from ddtrace.contrib.internal.trace_utils_async import with_traced_module as with_traced_module_async  # noqa: F401
+from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning
+from ddtrace.vendor.debtcollector import deprecate
+
+
+# TODO: Add trace utils that should not be public to the list below
+_DEPRECATED_MODULE_ATTRIBUTES = []
+
+
+def __getattr__(name):
+    if name in _DEPRECATED_MODULE_ATTRIBUTES:
+        deprecate(
+            ("%s.%s is deprecated" % (__name__, name)),
+            category=DDTraceDeprecationWarning,
+            removal_version="3.0.0",
         )
-        return
-
-    if override or (int_config and distributed_tracing_enabled(int_config)):
-        context = HTTPPropagator.extract(request_headers)
-
-        # Only need to activate the new context if something was propagated
-        # The new context must have one of these values in order for it to be activated
-        if not context.trace_id and not context._baggage and not context._span_links:
-            return None
-        # Do not reactivate a context with the same trace id
-        # DEV: An example could be nested web frameworks, when one layer already
-        #      parsed request headers and activated them.
-        #
-        # Example::
-        #
-        #     app = Flask(__name__)  # Traced via Flask instrumentation
-        #     app = DDWSGIMiddleware(app)  # Extra layer on top for WSGI
-        current_context = tracer.current_trace_context()
-
-        # We accept incoming contexts with only baggage or only span_links, however if we
-        # already have a current_context then an incoming context not
-        # containing a trace_id or containing the same trace_id
-        # should not be activated.
-        if current_context and (
-            not context.trace_id or (context.trace_id and context.trace_id == current_context.trace_id)
-        ):
-            log.debug(
-                "will not activate extracted Context(trace_id=%r, span_id=%r), a context with that trace id is already active",  # noqa: E501
-                context.trace_id,
-                context.span_id,
-            )
-            return None
-
-        # We have parsed a trace id from headers, and we do not already
-        # have a context with the same trace id active
-        tracer.context_provider.activate(context)
-
-
-def _flatten(
-    obj,  # type: Any
-    sep=".",  # type: str
-    prefix="",  # type: str
-    exclude_policy=None,  # type: Optional[Callable[[str], bool]]
-):
-    # type: (...) -> Generator[Tuple[str, Any], None, None]
-    s = deque()  # type: ignore
-    s.append((prefix, obj))
-    while s:
-        p, v = s.pop()
-        if exclude_policy is not None and exclude_policy(p):
-            continue
-        if isinstance(v, dict):
-            s.extend((sep.join((p, k)) if p else k, v) for k, v in v.items())
-        else:
-            yield p, v
-
-
-def set_flattened_tags(
-    span,  # type: Span
-    items,  # type: Iterator[Tuple[str, Any]]
-    sep=".",  # type: str
-    exclude_policy=None,  # type: Optional[Callable[[str], bool]]
-    processor=None,  # type: Optional[Callable[[Any], Any]]
-):
-    # type: (...) -> None
-    for prefix, value in items:
-        for tag, v in _flatten(value, sep, prefix, exclude_policy):
-            span.set_tag(tag, processor(v) if processor is not None else v)
-
-
-def set_user(
-    tracer,  # type: Tracer
-    user_id,  # type: str
-    name=None,  # type: Optional[str]
-    email=None,  # type: Optional[str]
-    scope=None,  # type: Optional[str]
-    role=None,  # type: Optional[str]
-    session_id=None,  # type: Optional[str]
-    propagate=False,  # type bool
-    span=None,  # type: Optional[Span]
-):
-    # type: (...) -> None
-    """Set user tags.
-    https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#user-related-attributes
-    https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/?tab=set_tag&code-lang=python
-    """
-    if span is None:
-        span = tracer.current_root_span()
-    if span:
-        if user_id:
-            str_user_id = str(user_id)
-            span.set_tag_str(user.ID, str_user_id)
-            if propagate:
-                span.context.dd_user_id = str_user_id
-
-        # All other fields are optional
-        if name:
-            span.set_tag_str(user.NAME, name)
-        if email:
-            span.set_tag_str(user.EMAIL, email)
-        if scope:
-            span.set_tag_str(user.SCOPE, scope)
-        if role:
-            span.set_tag_str(user.ROLE, role)
-        if session_id:
-            span.set_tag_str(user.SESSION_ID, session_id)
-
-        if asm_config._asm_enabled:
-            exc = core.dispatch_with_results("set_user_for_asm", [tracer, user_id]).block_user.exception
-            if exc:
-                raise exc
-
-    else:
-        log.warning(
-            "No root span in the current execution. Skipping set_user tags. "
-            "See https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/"
-            "?tab=set_user&code-lang=python for more information.",
-        )
-
-
-def extract_netloc_and_query_info_from_url(url):
-    # type: (str) -> Tuple[str, str]
-    parse_result = parse.urlparse(url)
-    query = parse_result.query
-
-    # Relative URLs don't have a netloc, so we force them
-    if not parse_result.netloc:
-        parse_result = parse.urlparse("//{url}".format(url=url))
-
-    netloc = parse_result.netloc.split("@", 1)[-1]  # Discard auth info
-    netloc = netloc.split(":", 1)[0]  # Discard port information
-    return netloc, query
-
-
-class InterruptException(Exception):
-    pass
 
+    if name in globals():
+        return globals()[name]
 
-def _convert_to_string(attr):
-    # ensures attribute is converted to a string
-    if attr:
-        if isinstance(attr, int) or isinstance(attr, float):
-            return str(attr)
-        else:
-            return ensure_text(attr)
-    return attr
+    raise AttributeError("%s has no attribute %s", __name__, name)
diff --git a/ddtrace/contrib/trace_utils_async.py b/ddtrace/contrib/trace_utils_async.py
index f58cc4e34bb..73258af46b9 100644
--- a/ddtrace/contrib/trace_utils_async.py
+++ b/ddtrace/contrib/trace_utils_async.py
@@ -1,39 +1,11 @@
-"""
-async tracing utils
+from ddtrace.contrib.internal.trace_utils_async import *  # noqa: F403
+from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning
+from ddtrace.vendor.debtcollector import deprecate
 
-Note that this module should only be imported in Python 3.5+.
-"""
-from ddtrace.internal.logger import get_logger
-from ddtrace.trace import Pin
 
-
-log = get_logger(__name__)
-
-
-def with_traced_module(func):
-    """Async version of trace_utils.with_traced_module.
-    Usage::
-
-        @with_traced_module
-        async def my_traced_wrapper(django, pin, func, instance, args, kwargs):
-            # Do tracing stuff
-            pass
-
-        def patch():
-            import django
-            wrap(django.somefunc, my_traced_wrapper(django))
-    """
-
-    def with_mod(mod):
-        async def wrapper(wrapped, instance, args, kwargs):
-            pin = Pin._find(instance, mod)
-            if pin and not pin.enabled():
-                return await wrapped(*args, **kwargs)
-            elif not pin:
-                log.debug("Pin not found for traced method %r", wrapped)
-                return await wrapped(*args, **kwargs)
-            return await func(mod, pin, wrapped, instance, args, kwargs)
-
-        return wrapper
-
-    return with_mod
+deprecate(
+    "The ddtrace.contrib.internal.trace_utils_async module is deprecated",
+    message="Import from ``ddtrace.contrib.internal.trace_utils`` instead.",
+    category=DDTraceDeprecationWarning,
+    removal_version="3.0.0",
+)
diff --git a/ddtrace/contrib/trace_utils_redis.py b/ddtrace/contrib/trace_utils_redis.py
index 8df16c3ce4d..ad30d38a86e 100644
--- a/ddtrace/contrib/trace_utils_redis.py
+++ b/ddtrace/contrib/trace_utils_redis.py
@@ -1,13 +1,14 @@
-from ddtrace.contrib.redis_utils import determine_row_count
-from ddtrace.contrib.redis_utils import stringify_cache_args
+from ddtrace.contrib.internal.redis_utils import determine_row_count
+from ddtrace.contrib.internal.redis_utils import stringify_cache_args
 from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning
 from ddtrace.vendor.debtcollector import deprecate
 
 
 deprecate(
-    "The ddtrace.contrib.trace_utils_redis module is deprecated and will be removed.",
-    message="A new interface will be provided by the ddtrace.contrib.redis_utils module",
+    "The ddtrace.contrib.internal.trace_utils_redis module is deprecated",
+    message="Import from ``ddtrace.contrib.internal.trace_utils`` instead.",
     category=DDTraceDeprecationWarning,
+    removal_version="3.0.0",
 )
 
 
diff --git a/ddtrace/llmobs/_integrations/base.py b/ddtrace/llmobs/_integrations/base.py
index a171b22867a..e7e405827b3 100644
--- a/ddtrace/llmobs/_integrations/base.py
+++ b/ddtrace/llmobs/_integrations/base.py
@@ -10,7 +10,7 @@
 from ddtrace._trace.sampler import RateSampler
 from ddtrace._trace.span import Span
 from ddtrace.constants import SPAN_MEASURED_KEY
-from ddtrace.contrib.trace_utils import int_service
+from ddtrace.contrib.internal.trace_utils import int_service
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.agent import get_stats_url
 from ddtrace.internal.dogstatsd import get_dogstatsd_client
diff --git a/releasenotes/notes/munir-refactor-trace-utils-e887e8da8a01430b.yaml b/releasenotes/notes/munir-refactor-trace-utils-e887e8da8a01430b.yaml
new file mode 100644
index 00000000000..40e80ed00a7
--- /dev/null
+++ b/releasenotes/notes/munir-refactor-trace-utils-e887e8da8a01430b.yaml
@@ -0,0 +1,6 @@
+---
+deprecations:
+  - |
+    tracing: Deprecates all attributes in ``ddtrace.contrib.trace_utils_async`` and ``ddtrace.contrib.redis_utils``.
+    Replaces ``ddtrace.contrib.trace_utils_async.with_traced_module(...)`` with ``ddtrace.contrib.trace_utils.with_traced_module_async(...)``.  
+    Moves public attributes defined in ``ddtrace.contrib.redis_utils.*`` to ``ddtrace.contrib.trace_utils``.
\ No newline at end of file
diff --git a/tests/appsec/appsec/test_appsec_trace_utils.py b/tests/appsec/appsec/test_appsec_trace_utils.py
index b550b103782..b4dbf97c8e2 100644
--- a/tests/appsec/appsec/test_appsec_trace_utils.py
+++ b/tests/appsec/appsec/test_appsec_trace_utils.py
@@ -11,7 +11,7 @@
 from ddtrace.appsec.trace_utils import track_user_login_failure_event
 from ddtrace.appsec.trace_utils import track_user_login_success_event
 from ddtrace.appsec.trace_utils import track_user_signup_event
-from ddtrace.contrib.trace_utils import set_user
+from ddtrace.contrib.internal.trace_utils import set_user
 from ddtrace.ext import user
 import tests.appsec.rules as rules
 from tests.appsec.utils import asm_context
diff --git a/tests/appsec/appsec/test_asm_standalone.py b/tests/appsec/appsec/test_asm_standalone.py
index 16390888055..4ed15628fb3 100644
--- a/tests/appsec/appsec/test_asm_standalone.py
+++ b/tests/appsec/appsec/test_asm_standalone.py
@@ -4,7 +4,7 @@
 import pytest
 
 import ddtrace
-from ddtrace.contrib.trace_utils import set_http_meta
+from ddtrace.contrib.internal.trace_utils import set_http_meta
 from ddtrace.ext import SpanTypes
 from tests.utils import override_env
 
diff --git a/tests/appsec/appsec/test_processor.py b/tests/appsec/appsec/test_processor.py
index d3b133b2e21..117a55175f9 100644
--- a/tests/appsec/appsec/test_processor.py
+++ b/tests/appsec/appsec/test_processor.py
@@ -14,7 +14,7 @@
 from ddtrace.appsec._processor import _transform_headers
 from ddtrace.appsec._utils import get_triggers
 from ddtrace.constants import USER_KEEP
-from ddtrace.contrib.trace_utils import set_http_meta
+from ddtrace.contrib.internal.trace_utils import set_http_meta
 from ddtrace.ext import SpanTypes
 from ddtrace.internal import core
 import tests.appsec.rules as rules
diff --git a/tests/appsec/appsec/test_remoteconfiguration.py b/tests/appsec/appsec/test_remoteconfiguration.py
index 6f521de457a..6cecfeb2da8 100644
--- a/tests/appsec/appsec/test_remoteconfiguration.py
+++ b/tests/appsec/appsec/test_remoteconfiguration.py
@@ -19,7 +19,7 @@
 from ddtrace.appsec._remoteconfiguration import disable_appsec_rc
 from ddtrace.appsec._remoteconfiguration import enable_appsec_rc
 from ddtrace.appsec._utils import get_triggers
-from ddtrace.contrib.trace_utils import set_http_meta
+from ddtrace.contrib.internal.trace_utils import set_http_meta
 from ddtrace.internal import core
 from ddtrace.internal.remoteconfig.client import AgentPayload
 from ddtrace.internal.remoteconfig.client import ConfigMetadata
diff --git a/tests/appsec/appsec/test_telemetry.py b/tests/appsec/appsec/test_telemetry.py
index 47b58222dde..3ad151c4e8d 100644
--- a/tests/appsec/appsec/test_telemetry.py
+++ b/tests/appsec/appsec/test_telemetry.py
@@ -10,7 +10,7 @@
 import ddtrace.appsec._ddwaf.ddwaf_types
 from ddtrace.appsec._deduplications import deduplication
 from ddtrace.appsec._processor import AppSecSpanProcessor
-from ddtrace.contrib.trace_utils import set_http_meta
+from ddtrace.contrib.internal.trace_utils import set_http_meta
 from ddtrace.ext import SpanTypes
 from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE
 from ddtrace.internal.telemetry.constants import TELEMETRY_TYPE_DISTRIBUTION
diff --git a/tests/contrib/asyncpg/test_asyncpg.py b/tests/contrib/asyncpg/test_asyncpg.py
index 032a0f91731..c60bfae1456 100644
--- a/tests/contrib/asyncpg/test_asyncpg.py
+++ b/tests/contrib/asyncpg/test_asyncpg.py
@@ -8,7 +8,7 @@
 from ddtrace import tracer
 from ddtrace.contrib.internal.asyncpg.patch import patch
 from ddtrace.contrib.internal.asyncpg.patch import unpatch
-from ddtrace.contrib.trace_utils import iswrapped
+from ddtrace.contrib.internal.trace_utils import iswrapped
 from ddtrace.trace import Pin
 from tests.contrib.asyncio.utils import AsyncioTestCase
 from tests.contrib.asyncio.utils import mark_asyncio
diff --git a/tests/contrib/django/views.py b/tests/contrib/django/views.py
index b908fa2eee0..f1989c374d7 100644
--- a/tests/contrib/django/views.py
+++ b/tests/contrib/django/views.py
@@ -16,7 +16,7 @@
 from django.views.generic import View
 
 from ddtrace import tracer
-from ddtrace.contrib.trace_utils import set_user
+from ddtrace.contrib.internal.trace_utils import set_user
 
 
 class UserList(ListView):
diff --git a/tests/contrib/flask/app.py b/tests/contrib/flask/app.py
index c19b34c4216..d76ec8fb8b1 100644
--- a/tests/contrib/flask/app.py
+++ b/tests/contrib/flask/app.py
@@ -8,7 +8,7 @@
 
 from ddtrace import tracer
 from ddtrace.appsec._trace_utils import block_request_if_user_blocked
-from ddtrace.contrib.trace_utils import set_user
+from ddtrace.contrib.internal.trace_utils import set_user
 from tests.webclient import PingFilter
 
 
diff --git a/tests/contrib/openai/test_openai_v0.py b/tests/contrib/openai/test_openai_v0.py
index 3fa262e8a4a..0dbd537c3ff 100644
--- a/tests/contrib/openai/test_openai_v0.py
+++ b/tests/contrib/openai/test_openai_v0.py
@@ -11,7 +11,7 @@
 import ddtrace
 from ddtrace import patch
 from ddtrace.contrib.internal.openai.utils import _est_tokens
-from ddtrace.contrib.trace_utils import iswrapped
+from ddtrace.contrib.internal.trace_utils import iswrapped
 from ddtrace.internal.utils.version import parse_version
 from tests.contrib.openai.utils import chat_completion_custom_functions
 from tests.contrib.openai.utils import chat_completion_input_description
diff --git a/tests/contrib/openai/test_openai_v1.py b/tests/contrib/openai/test_openai_v1.py
index 918d3eadae9..47ed05ea1bd 100644
--- a/tests/contrib/openai/test_openai_v1.py
+++ b/tests/contrib/openai/test_openai_v1.py
@@ -7,7 +7,7 @@
 import ddtrace
 from ddtrace import patch
 from ddtrace.contrib.internal.openai.utils import _est_tokens
-from ddtrace.contrib.trace_utils import iswrapped
+from ddtrace.contrib.internal.trace_utils import iswrapped
 from ddtrace.internal.utils.version import parse_version
 from tests.contrib.openai.utils import chat_completion_custom_functions
 from tests.contrib.openai.utils import chat_completion_input_description
diff --git a/tests/internal/test_module.py b/tests/internal/test_module.py
index c84c2c740d6..e62440325fe 100644
--- a/tests/internal/test_module.py
+++ b/tests/internal/test_module.py
@@ -572,10 +572,9 @@ def __getattr__(name):
 
     assert missing_deprecations == set(
         [
-            # Note: The following ddtrace.contrib modules are expected to be part of the public API
-            # TODO: Revisit whether integration utils should be part of the public API
-            "ddtrace.contrib.redis_utils",
             "ddtrace.contrib.trace_utils",
+            # Note: The modules below are deprecated but they do not follow the template above
+            "ddtrace.contrib.redis_utils",
             "ddtrace.contrib.trace_utils_async",
             "ddtrace.contrib.trace_utils_redis",
         ]
diff --git a/tests/tracer/test_trace_utils.py b/tests/tracer/test_trace_utils.py
index a7604636b62..38ba097f308 100644
--- a/tests/tracer/test_trace_utils.py
+++ b/tests/tracer/test_trace_utils.py
@@ -17,8 +17,8 @@
 from ddtrace import config
 from ddtrace._trace.context import Context
 from ddtrace._trace.span import Span
-from ddtrace.contrib import trace_utils
-from ddtrace.contrib.trace_utils import _get_request_header_client_ip
+from ddtrace.contrib.internal import trace_utils
+from ddtrace.contrib.internal.trace_utils import _get_request_header_client_ip
 from ddtrace.ext import SpanTypes
 from ddtrace.ext import http
 from ddtrace.ext import net
@@ -316,7 +316,7 @@ def test_ext_service(int_config, pin, config_val, default, expected):
 def test_set_http_meta_with_http_header_tags_config():
     from ddtrace import config
     from ddtrace._trace.span import Span
-    from ddtrace.contrib.trace_utils import set_http_meta
+    from ddtrace.contrib.internal.trace_utils import set_http_meta
 
     assert config._trace_http_header_tags == {
         "header1": "",
@@ -512,7 +512,7 @@ def test_set_http_meta_custom_errors(mock_log, span, int_config, error_codes, st
 def test_set_http_meta_custom_errors_via_env():
     from ddtrace import config
     from ddtrace import tracer
-    from ddtrace.contrib.trace_utils import set_http_meta
+    from ddtrace.contrib.internal.trace_utils import set_http_meta
 
     config._add("myint", dict())
     with tracer.trace("error") as span1:
@@ -528,7 +528,7 @@ def test_set_http_meta_custom_errors_via_env():
         assert span3.error == 0
 
 
-@mock.patch("ddtrace.contrib.trace_utils._store_headers")
+@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers")
 def test_set_http_meta_no_headers(mock_store_headers, span, int_config):
     assert int_config.myint.is_header_tracing_configured is False
     trace_utils.set_http_meta(
@@ -543,7 +543,7 @@ def test_set_http_meta_no_headers(mock_store_headers, span, int_config):
     mock_store_headers.assert_not_called()
 
 
-@mock.patch("ddtrace.contrib.trace_utils._store_headers")
+@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers")
 @pytest.mark.parametrize(
     "user_agent_key,user_agent_value,expected_keys,expected",
     [
@@ -572,7 +572,7 @@ def test_set_http_meta_headers_useragent(
     mock_store_headers.assert_called()
 
 
-@mock.patch("ddtrace.contrib.trace_utils._store_headers")
+@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers")
 def test_set_http_meta_case_sensitive_headers(mock_store_headers, span, int_config):
     int_config.myint.http._header_tags = {"enabled": True}
     trace_utils.set_http_meta(
@@ -585,7 +585,7 @@ def test_set_http_meta_case_sensitive_headers(mock_store_headers, span, int_conf
     mock_store_headers.assert_called()
 
 
-@mock.patch("ddtrace.contrib.trace_utils._store_headers")
+@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers")
 def test_set_http_meta_case_sensitive_headers_notfound(mock_store_headers, span, int_config):
     int_config.myint.http._header_tags = {"enabled": True}
     trace_utils.set_http_meta(
@@ -810,7 +810,7 @@ def test_ip_subnet_regression():
     assert not ip_network(req_ip).subnet_of(ip_network(del_ip))
 
 
-@mock.patch("ddtrace.contrib.trace_utils._store_headers")
+@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers")
 @pytest.mark.parametrize(
     "user_agent_value, expected_keys ,expected",
     [
@@ -835,7 +835,7 @@ def test_set_http_meta_headers_useragent(  # noqa:F811
     mock_store_headers.assert_not_called()
 
 
-@mock.patch("ddtrace.contrib.trace_utils.log")
+@mock.patch("ddtrace.contrib.internal.trace_utils.log")
 @pytest.mark.parametrize(
     "val, bad",
     [
@@ -1042,7 +1042,7 @@ def test_sanitized_url_in_http_meta(span, int_config):
 def test_url_in_http_with_empty_obfuscation_regex():
     from ddtrace import config
     from ddtrace import tracer
-    from ddtrace.contrib.trace_utils import set_http_meta
+    from ddtrace.contrib.internal.trace_utils import set_http_meta
     from ddtrace.ext import http
 
     assert config._obfuscation_query_string_pattern.pattern == b"", config._obfuscation_query_string_pattern
@@ -1067,7 +1067,7 @@ def test_url_in_http_with_obfuscation_enabled_and_empty_regex():
     # and obfuscation is enabled (not disabled xD)
     from ddtrace import config
     from ddtrace import tracer
-    from ddtrace.contrib.trace_utils import set_http_meta
+    from ddtrace.contrib.internal.trace_utils import set_http_meta
     from ddtrace.ext import http
 
     # assert obfuscation is disabled when the regex is an empty string
diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py
index 451e465b5a9..e647397cc50 100644
--- a/tests/tracer/test_tracer.py
+++ b/tests/tracer/test_tracer.py
@@ -30,7 +30,7 @@
 from ddtrace.constants import USER_KEEP
 from ddtrace.constants import USER_REJECT
 from ddtrace.constants import VERSION_KEY
-from ddtrace.contrib.trace_utils import set_user
+from ddtrace.contrib.internal.trace_utils import set_user
 from ddtrace.ext import user
 from ddtrace.internal._encoding import MsgpackEncoderV04
 from ddtrace.internal._encoding import MsgpackEncoderV05