diff --git a/ddtrace/__init__.py b/ddtrace/__init__.py
index 835291fadb7..b555d1117ca 100644
--- a/ddtrace/__init__.py
+++ b/ddtrace/__init__.py
@@ -1,4 +1,5 @@
 import sys
+import os
 import warnings
 
 
@@ -42,29 +43,29 @@
 # initialization, which added this module to sys.modules. We catch deprecation
 # warnings as this is only to retain a side effect of the package
 # initialization.
+# TODO: Remove this in v3.0 when the ddtrace/tracer.py module is removed
 with warnings.catch_warnings():
     warnings.simplefilter("ignore")
     from .tracer import Tracer as _
 
-
 __version__ = get_version()
 
-# a global tracer instance with integration settings
-tracer = Tracer()
+# TODO: Deprecate accessing tracer from ddtrace.__init__ module in v4.0
+if os.environ.get("_DD_GLOBAL_TRACER_INIT", "true").lower() in ("1", "true"):
+    from ddtrace.trace import tracer  # noqa: F401
 
 __all__ = [
     "patch",
     "patch_all",
     "Pin",
     "Span",
-    "tracer",
     "Tracer",
     "config",
     "DDTraceDeprecationWarning",
 ]
 
 
-_DEPRECATED_MODULE_ATTRIBUTES = [
+_DEPRECATED_TRACE_ATTRIBUTES = [
     "Span",
     "Tracer",
     "Pin",
@@ -72,10 +73,12 @@
 
 
 def __getattr__(name):
-    if name in _DEPRECATED_MODULE_ATTRIBUTES:
+    if name in _DEPRECATED_TRACE_ATTRIBUTES:
         debtcollector.deprecate(
             ("%s.%s is deprecated" % (__name__, name)),
+            message="Import from ddtrace.trace instead.",
             category=DDTraceDeprecationWarning,
+            removal_version="3.0.0",
         )
 
     if name in globals():
diff --git a/ddtrace/_trace/pin.py b/ddtrace/_trace/pin.py
index d12303a57ea..7dd83474749 100644
--- a/ddtrace/_trace/pin.py
+++ b/ddtrace/_trace/pin.py
@@ -6,6 +6,7 @@
 import wrapt
 
 import ddtrace
+from ddtrace.vendor.debtcollector import deprecate
 
 from ..internal.logger import get_logger
 
@@ -41,6 +42,12 @@ def __init__(
         _config=None,  # type: Optional[Dict[str, Any]]
     ):
         # type: (...) -> None
+        if tracer is not None and tracer is not ddtrace.tracer:
+            deprecate(
+                "Initializing ddtrace.Pin with `tracer` argument is deprecated",
+                message="All Pin instances should use the global tracer instance",
+                removal_version="3.0.0",
+            )
         tracer = tracer or ddtrace.tracer
         self.tags = tags
         self.tracer = tracer
@@ -72,15 +79,15 @@ def __repr__(self):
     def _find(*objs):
         # type: (Any) -> Optional[Pin]
         """
-        Return the first :class:`ddtrace.trace.Pin` found on any of the provided objects or `None` if none were found
+        Return the first :class:`ddtrace.pin.Pin` found on any of the provided objects or `None` if none were found
 
 
             >>> pin = Pin._find(wrapper, instance, conn)
 
-        :param objs: The objects to search for a :class:`ddtrace.trace.Pin` on
+        :param objs: The objects to search for a :class:`ddtrace.pin.Pin` on
         :type objs: List of objects
-        :rtype: :class:`ddtrace.trace.Pin`, None
-        :returns: The first found :class:`ddtrace.trace.Pin` or `None` is none was found
+        :rtype: :class:`ddtrace.pin.Pin`, None
+        :returns: The first found :class:`ddtrace.pin.Pin` or `None` is none was found
         """
         for obj in objs:
             pin = Pin.get_from(obj)
@@ -98,10 +105,10 @@ def get_from(obj):
 
             >>> pin = Pin.get_from(conn)
 
-        :param obj: The object to look for a :class:`ddtrace.trace.Pin` on
+        :param obj: The object to look for a :class:`ddtrace.pin.Pin` on
         :type obj: object
-        :rtype: :class:`ddtrace.trace.Pin`, None
-        :returns: :class:`ddtrace.trace.Pin` associated with the object or None
+        :rtype: :class:`ddtrace.pin.Pin`, None
+        :returns: :class:`ddtrace.pin.Pin` associated with the object, or None if none was found
         """
         if hasattr(obj, "__getddpin__"):
             return obj.__getddpin__()
@@ -132,6 +139,12 @@ def override(
             >>> # Override a pin for a specific connection
             >>> Pin.override(conn, service='user-db')
         """
+        if tracer is not None:
+            deprecate(
+                "Calling ddtrace.Pin.override(...) with the `tracer` argument is deprecated",
+                message="All Pin instances should use the global tracer instance",
+                removal_version="3.0.0",
+            )
         if not obj:
             return
 
@@ -193,6 +206,13 @@ def clone(
         if not tags and self.tags:
             tags = self.tags.copy()
 
+        if tracer is not None:
+            deprecate(
+                "Initializing ddtrace.Pin with `tracer` argument is deprecated",
+                message="All Pin instances should use the global tracer instance",
+                removal_version="3.0.0",
+            )
+
         # we use a copy instead of a deepcopy because we expect configurations
         # to have only a root level dictionary without nested objects. Using
         # deepcopy introduces a big overhead:
diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py
index 83d2d580c2d..5bde36ef480 100644
--- a/ddtrace/_trace/tracer.py
+++ b/ddtrace/_trace/tracer.py
@@ -18,6 +18,7 @@
 from ddtrace import _hooks
 from ddtrace import config
 from ddtrace._trace.context import Context
+from ddtrace._trace.filters import TraceFilter
 from ddtrace._trace.processor import SpanAggregator
 from ddtrace._trace.processor import SpanProcessor
 from ddtrace._trace.processor import TopLevelSpanProcessor
@@ -68,7 +69,6 @@
 from ddtrace.settings import Config
 from ddtrace.settings.asm import config as asm_config
 from ddtrace.settings.peer_service import _ps_config
-from ddtrace.trace import TraceFilter
 from ddtrace.vendor.debtcollector import deprecate
 
 
diff --git a/ddtrace/appsec/_constants.py b/ddtrace/appsec/_constants.py
index d16de0e1379..92b9e239900 100644
--- a/ddtrace/appsec/_constants.py
+++ b/ddtrace/appsec/_constants.py
@@ -182,6 +182,7 @@ class WAF_DATA_NAMES(metaclass=Constant_Class):
     REQUEST_COOKIES: Literal["server.request.cookies"] = "server.request.cookies"
     REQUEST_HTTP_IP: Literal["http.client_ip"] = "http.client_ip"
     REQUEST_USER_ID: Literal["usr.id"] = "usr.id"
+    REQUEST_USERNAME: Literal["usr.login"] = "usr.login"
     RESPONSE_STATUS: Literal["server.response.status"] = "server.response.status"
     RESPONSE_HEADERS_NO_COOKIES: Literal["server.response.headers.no_cookies"] = "server.response.headers.no_cookies"
     RESPONSE_BODY: Literal["server.response.body"] = "server.response.body"
@@ -196,6 +197,7 @@ class WAF_DATA_NAMES(metaclass=Constant_Class):
             REQUEST_COOKIES,
             REQUEST_HTTP_IP,
             REQUEST_USER_ID,
+            REQUEST_USERNAME,
             RESPONSE_STATUS,
             RESPONSE_HEADERS_NO_COOKIES,
             RESPONSE_BODY,
diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py
index c4a224c2a02..bb3a9c74d44 100644
--- a/ddtrace/appsec/_iast/_ast/ast_patching.py
+++ b/ddtrace/appsec/_iast/_ast/ast_patching.py
@@ -9,6 +9,7 @@
 from types import ModuleType
 from typing import Iterable
 from typing import Optional
+from typing import Set
 from typing import Text
 from typing import Tuple
 
@@ -26,57 +27,43 @@
 _PREFIX = IAST.PATCH_ADDED_SYMBOL_PREFIX
 
 # Prefixes for modules where IAST patching is allowed
-IAST_ALLOWLIST: Tuple[Text, ...] = ("tests.appsec.iast.",)
+# Only packages that have the test_propagation=True in test_packages and are not in the denylist must be here
+IAST_ALLOWLIST: Tuple[Text, ...] = (
+    "attrs.",
+    "beautifulsoup4.",
+    "cachetools.",
+    "cryptography.",
+    "docutils.",
+    "idna.",
+    "iniconfig.",
+    "jinja2.",
+    "lxml.",
+    "multidict.",
+    "platformdirs",
+    "pygments.",
+    "pynacl.",
+    "pyparsing.",
+    "multipart",
+    "sqlalchemy.",
+    "tomli",
+    "yarl.",
+)
+
+# NOTE: For testing reasons, don't add astunparse here, see test_ast_patching.py
 IAST_DENYLIST: Tuple[Text, ...] = (
-    "altgraph.",
-    "dipy.",
-    "black.",
-    "mypy.",
-    "mypy_extensions.",
-    "autopep8.",
-    "pycodestyle.",
-    "pydicom.",
-    "pyinstaller.",
-    "pystray.",
-    "contourpy.",
-    "cx_logging.",
-    "dateutil.",
-    "pytz.",
-    "wcwidth.",
-    "win32ctypes.",
-    "xlib.",
-    "cycler.",
-    "cython.",
-    "dnspython.",
-    "elasticdeform.",
-    "numpy.",
-    "matplotlib.",
-    "skbase.",
-    "scipy.",
-    "networkx.",
-    "imageio.",
-    "fonttools.",
-    "nibabel.",
-    "nilearn.",
-    "gprof2dot.",
-    "h5py.",
-    "kiwisolver.",
-    "pandas.",
-    "pdf2image.",
-    "pefile.",
-    "pil.",
-    "threadpoolctl.",
-    "tifffile.",
-    "tqdm.",
-    "trx.",
-    "flask.",
-    "werkzeug.",
+    "_psycopg.",  # PostgreSQL adapter for Python (v3)
+    "_pytest.",
     "aiohttp._helpers.",
     "aiohttp._http_parser.",
     "aiohttp._http_writer.",
     "aiohttp._websocket.",
     "aiohttp.log.",
     "aiohttp.tcp_helpers.",
+    "aioquic.",
+    "altgraph.",
+    "anyio.",
+    "api_pb2.",  # Patching crashes with these auto-generated modules, propagation is not needed
+    "api_pb2_grpc.",  # Patching crashes with these auto-generated modules, propagation is not needed
     "asyncio.base_events.",
     "asyncio.base_futures.",
     "asyncio.base_subprocess.",
@@ -99,11 +86,15 @@
     "asyncio.transports.",
     "asyncio.trsock.",
     "asyncio.unix_events.",
+    "asyncpg.pgproto.",
     "attr._config.",
     "attr._next_gen.",
     "attr.filters.",
     "attr.setters.",
+    "autopep8.",
     "backports.",
+    "black.",
+    "blinker.",
     "boto3.docs.docstring.",
     "boto3.s3.",
     "botocore.docs.bcdoc.",
@@ -111,6 +102,8 @@
     "botocore.vendored.requests.",
     "brotli.",
     "brotlicffi.",
+    "bytecode.",
+    "cattrs.",
     "cchardet.",
     "certifi.",
     "cffi.",
@@ -145,14 +138,23 @@
     "colorama.",
     "concurrent.futures.",
     "configparser.",
+    "contourpy.",
     "coreschema.",
     "crispy_forms.",
+    "crypto.",  # This module is patched by the IAST patch methods, propagation is not needed
+    "cx_logging.",
+    "cycler.",
+    "cython.",
+    "dateutil.",
     "dateutil.",
+    "ddsketch.",
+    "ddtrace.",
     "defusedxml.",
+    "deprecated.",
     "difflib.",
     "dill.info.",
     "dill.settings.",
-    "silk.",  # django-silk package
+    "dipy.",
     "django.apps.config.",
     "django.apps.registry.",
     "django.conf.",
@@ -298,72 +300,87 @@
     "django_filters.rest_framework.filterset.",
     "django_filters.utils.",
     "django_filters.widgets.",
-    "crypto.",  # This module is patched by the IAST patch methods, propagation is not needed
-    "deprecated.",
-    "api_pb2.",  # Patching crashes with these auto-generated modules, propagation is not needed
-    "api_pb2_grpc.",  # Patching crashes with these auto-generated modules, propagation is not needed
-    "asyncpg.pgproto.",
-    "blinker.",
-    "bytecode.",
-    "cattrs.",
-    "ddsketch.",
-    "ddtrace.",
+    "dnspython.",
+    "elasticdeform.",
     "envier.",
     "exceptiongroup.",
+    "flask.",
+    "fonttools.",
     "freezegun.",  # Testing utilities for time manipulation
+    "google.auth.",
+    "googlecloudsdk.",
+    "gprof2dot.",
+    "h11.",
+    "h5py.",
+    "httpcore.",
+    "httptools.",
+    "httpx.",
     "hypothesis.",  # Testing utilities
+    "imageio.",
     "importlib_metadata.",
     "inspect.",  # this package is used to get the stack frames, propagation is not needed
     "itsdangerous.",
+    "kiwisolver.",
+    "matplotlib.",
     "moto.",  # used for mocking AWS, propagation is not needed
+    "mypy.",
+    "mypy_extensions.",
+    "networkx.",
+    "nibabel.",
+    "nilearn.",
+    "numba.",
+    "numpy.",
     "opentelemetry-api.",
     "packaging.",
+    "pandas.",
+    "pdf2image.",
+    "pefile.",
+    "pil.",
     "pip.",
     "pkg_resources.",
     "pluggy.",
     "protobuf.",
     "psycopg.",  # PostgreSQL adapter for Python (v3)
-    "_psycopg.",  # PostgreSQL adapter for Python (v3)
     "psycopg2.",  # PostgreSQL adapter for Python (v2)
+    "pycodestyle.",
     "pycparser.",  # this package is called when a module is imported, propagation is not needed
+    "pydicom.",
+    "pyinstaller.",
+    "pynndescent.",
+    "pystray.",
     "pytest.",  # Testing framework
-    "_pytest.",
+    "pytz.",
+    "rich.",
+    "sanic.",
+    "scipy.",
     "setuptools.",
+    "silk.",  # django-silk package
+    "skbase.",
     "sklearn.",  # Machine learning library
+    "sniffio.",
     "sqlalchemy.orm.interfaces.",  # Performance optimization
+    "threadpoolctl.",
+    "tifffile.",
+    "tqdm.",
+    "trx.",
     "typing_extensions.",
+    "umap.",
     "unittest.mock.",
-    "uvloop.",
     "urlpatterns_reverse.tests.",  # assertRaises eat exceptions in native code, so we don't call the original function
-    "wrapt.",
-    "zipp.",
-    # This is a workaround for Sanic failures:
+    "uvicorn.",
+    "uvloop.",
+    "wcwidth.",
     "websocket.",
-    "h11.",
-    "aioquic.",
-    "httptools.",
-    "sniffio.",
-    "sanic.",
-    "rich.",
-    "httpx.",
     "websockets.",
-    "uvicorn.",
-    "anyio.",
-    "httpcore.",
-    "google.auth.",
-    "googlecloudsdk.",
-    "umap.",
-    "pynndescent.",
-    "numba.",
+    "werkzeug.",
+    "win32ctypes.",
+    "wrapt.",
+    "xlib.",
+    "zipp.",
 )
 
-
-if IAST.PATCH_MODULES in os.environ:
-    IAST_ALLOWLIST += tuple(os.environ[IAST.PATCH_MODULES].split(IAST.SEP_MODULES))
-
-if IAST.DENY_MODULES in os.environ:
-    IAST_DENYLIST += tuple(os.environ[IAST.DENY_MODULES].split(IAST.SEP_MODULES))
-
+USER_ALLOWLIST = tuple(os.environ.get(IAST.PATCH_MODULES, "").split(IAST.SEP_MODULES))
+USER_DENYLIST = tuple(os.environ.get(IAST.DENY_MODULES, "").split(IAST.SEP_MODULES))
 
 ENCODING = ""
 
@@ -399,6 +416,8 @@ def build_trie(words: Iterable[str]) -> _TrieNode:
 
 _TRIE_ALLOWLIST = build_trie(IAST_ALLOWLIST)
 _TRIE_DENYLIST = build_trie(IAST_DENYLIST)
+_TRIE_USER_ALLOWLIST = build_trie(USER_ALLOWLIST)
+_TRIE_USER_DENYLIST = build_trie(USER_DENYLIST)
 
 
 def _trie_has_prefix_for(trie: _TrieNode, string: str) -> bool:
@@ -429,11 +448,26 @@ def get_encoding(module_path: Text) -> Text:
 
 _NOT_PATCH_MODULE_NAMES = {i.lower() for i in _stdlib_for_python_version() | set(builtin_module_names)}
 
+_IMPORTLIB_PACKAGES: Set[str] = set()
+
 
 def _in_python_stdlib(module_name: str) -> bool:
     return module_name.split(".")[0].lower() in _NOT_PATCH_MODULE_NAMES
 
 
+def _is_first_party(module_name: str):
+    global _IMPORTLIB_PACKAGES
+    if "vendor." in module_name or "vendored." in module_name:
+        return False
+
+    if not _IMPORTLIB_PACKAGES:
+        from ddtrace.internal.packages import get_package_distributions
+
+        _IMPORTLIB_PACKAGES = set(get_package_distributions())
+
+    return module_name.split(".")[0] not in _IMPORTLIB_PACKAGES
+
+
 def _should_iast_patch(module_name: Text) -> bool:
     """
     select if module_name should be patch from the longest prefix that match in allow or deny list.
@@ -444,17 +478,30 @@ def _should_iast_patch(module_name: Text) -> bool:
     # max_deny = max((len(prefix) for prefix in IAST_DENYLIST if module_name.startswith(prefix)), default=-1)
     # diff = max_allow - max_deny
     # return diff > 0 or (diff == 0 and not _in_python_stdlib_or_third_party(module_name))
+    if _in_python_stdlib(module_name):
+        log.debug("IAST: denying %s. it's in the _in_python_stdlib", module_name)
+        return False
+
+    if _is_first_party(module_name):
+        return True
+
+    # else: third party. Check that is in the allow list and not in the deny list
     dotted_module_name = module_name.lower() + "."
+
+    # User allow or deny list set by env var have priority
+    if _trie_has_prefix_for(_TRIE_USER_ALLOWLIST, dotted_module_name):
+        return True
+
+    if _trie_has_prefix_for(_TRIE_USER_DENYLIST, dotted_module_name):
+        return False
+
     if _trie_has_prefix_for(_TRIE_ALLOWLIST, dotted_module_name):
+        if _trie_has_prefix_for(_TRIE_DENYLIST, dotted_module_name):
+            return False
         log.debug("IAST: allowing %s. it's in the IAST_ALLOWLIST", module_name)
         return True
-    if _trie_has_prefix_for(_TRIE_DENYLIST, dotted_module_name):
-        log.debug("IAST: denying %s. it's in the IAST_DENYLIST", module_name)
-        return False
-    if _in_python_stdlib(module_name):
-        log.debug("IAST: denying %s. it's in the _in_python_stdlib", module_name)
-        return False
-    return True
+    log.debug("IAST: denying %s. it's in the IAST_DENYLIST", module_name)
+    return False
 
 
 def visit_ast(
diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py
index 54a9f624afe..030399f8a50 100644
--- a/ddtrace/appsec/_processor.py
+++ b/ddtrace/appsec/_processor.py
@@ -262,6 +262,7 @@ def _waf_action(
         custom_data: Optional[Dict[str, Any]] = None,
         crop_trace: Optional[str] = None,
         rule_type: Optional[str] = None,
+        force_sent: bool = False,
     ) -> Optional[DDWaf_result]:
         """
         Call the `WAF` with the given parameters. If `custom_data_names` is specified as
@@ -293,7 +294,7 @@ def _waf_action(
         force_keys = custom_data.get("PROCESSOR_SETTINGS", {}).get("extract-schema", False) if custom_data else False
 
         for key, waf_name in iter_data:  # type: ignore[attr-defined]
-            if key in data_already_sent:
+            if key in data_already_sent and not force_sent:
                 continue
             # ensure ephemeral addresses are sent, event when value is None
             if waf_name not in WAF_DATA_NAMES.PERSISTENT_ADDRESSES and custom_data:
diff --git a/ddtrace/appsec/_trace_utils.py b/ddtrace/appsec/_trace_utils.py
index 8609344f05a..77cb1aaca3a 100644
--- a/ddtrace/appsec/_trace_utils.py
+++ b/ddtrace/appsec/_trace_utils.py
@@ -9,11 +9,13 @@
 from ddtrace.appsec._asm_request_context import in_asm_context
 from ddtrace.appsec._constants import APPSEC
 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.ext import SpanTypes
 from ddtrace.ext import user
 from ddtrace.internal import core
+from ddtrace.internal._exceptions import BlockingException
 from ddtrace.internal.logger import get_logger
 from ddtrace.settings.asm import config as asm_config
 
@@ -121,21 +123,31 @@ def track_user_login_success_event(
     real_mode = login_events_mode if login_events_mode != LOGIN_EVENTS_MODE.AUTO else asm_config._user_event_mode
     if real_mode == LOGIN_EVENTS_MODE.DISABLED:
         return
+    initial_login = login
+    initial_user_id = user_id
     if real_mode == LOGIN_EVENTS_MODE.ANON:
-        login = name = email = None
+        name = email = None
+        login = None if login is None else _hash_user_id(str(login))
     span = _track_user_login_common(tracer, True, metadata, login_events_mode, login, name, email, span)
     if not span:
         return
-
     if real_mode == LOGIN_EVENTS_MODE.ANON and isinstance(user_id, str):
         user_id = _hash_user_id(user_id)
 
-    if in_asm_context():
-        call_waf_callback(custom_data={"REQUEST_USER_ID": str(user_id), "LOGIN_SUCCESS": real_mode})
-
     if login_events_mode != LOGIN_EVENTS_MODE.SDK:
         span.set_tag_str(APPSEC.USER_LOGIN_USERID, str(user_id))
     set_user(tracer, user_id, name, email, scope, role, session_id, propagate, span)
+    if in_asm_context():
+        res = call_waf_callback(
+            custom_data={
+                "REQUEST_USER_ID": str(initial_user_id) if initial_user_id else None,
+                "REQUEST_USERNAME": initial_login,
+                "LOGIN_SUCCESS": real_mode,
+            },
+            force_sent=True,
+        )
+        if res and any(action in [WAF_ACTIONS.BLOCK_ACTION, WAF_ACTIONS.REDIRECT_ACTION] for action in res.actions):
+            raise BlockingException(get_blocked())
 
 
 def track_user_login_failure_event(
@@ -159,6 +171,8 @@ def track_user_login_failure_event(
     real_mode = login_events_mode if login_events_mode != LOGIN_EVENTS_MODE.AUTO else asm_config._user_event_mode
     if real_mode == LOGIN_EVENTS_MODE.DISABLED:
         return
+    if real_mode == LOGIN_EVENTS_MODE.ANON and isinstance(login, str):
+        login = _hash_user_id(login)
     span = _track_user_login_common(tracer, False, metadata, login_events_mode, login)
     if not span:
         return
@@ -265,7 +279,7 @@ def should_block_user(tracer: Tracer, userid: str) -> bool:
     if get_blocked():
         return True
 
-    _asm_request_context.call_waf_callback(custom_data={"REQUEST_USER_ID": str(userid)})
+    _asm_request_context.call_waf_callback(custom_data={"REQUEST_USER_ID": str(userid)}, force_sent=True)
     return bool(get_blocked())
 
 
diff --git a/ddtrace/contrib/aredis/__init__.py b/ddtrace/contrib/aredis/__init__.py
index 8448740104f..1d651b9c616 100644
--- a/ddtrace/contrib/aredis/__init__.py
+++ b/ddtrace/contrib/aredis/__init__.py
@@ -50,7 +50,7 @@
 Instance Configuration
 ~~~~~~~~~~~~~~~~~~~~~~
 
-To configure particular aredis instances use the :class:`Pin <ddtrace.Pin>` API::
+To configure particular aredis instances use the :class:`Pin <ddtrace.trace.Pin>` API::
 
     import aredis
     from ddtrace.trace import Pin
diff --git a/ddtrace/contrib/grpc/__init__.py b/ddtrace/contrib/grpc/__init__.py
index c95633b4024..8ad2a705233 100644
--- a/ddtrace/contrib/grpc/__init__.py
+++ b/ddtrace/contrib/grpc/__init__.py
@@ -48,6 +48,7 @@
     from ddtrace import patch
     from ddtrace.trace import Pin
 
+
     patch(grpc=True)
 
     # override the pin on the client
@@ -62,7 +63,7 @@
     from grpc.framework.foundation import logging_pool
 
     from ddtrace import patch
-    from ddtrace.trace import Pin, Tracer
+    from ddtrace.trace import Pin
 
     patch(grpc=True)
 
diff --git a/ddtrace/contrib/httpx/__init__.py b/ddtrace/contrib/httpx/__init__.py
index 28621de44f2..95762604687 100644
--- a/ddtrace/contrib/httpx/__init__.py
+++ b/ddtrace/contrib/httpx/__init__.py
@@ -57,7 +57,7 @@
 Instance Configuration
 ~~~~~~~~~~~~~~~~~~~~~~
 
-To configure particular ``httpx`` client instances use the :class:`Pin <ddtrace.Pin>` API::
+To configure particular ``httpx`` client instances use the :class:`Pin <ddtrace.trace.Pin>` API::
 
     import httpx
     from ddtrace.trace import Pin
diff --git a/ddtrace/contrib/internal/django/patch.py b/ddtrace/contrib/internal/django/patch.py
index 98a6163a6e5..8bc523dd1c1 100644
--- a/ddtrace/contrib/internal/django/patch.py
+++ b/ddtrace/contrib/internal/django/patch.py
@@ -17,6 +17,7 @@
 import wrapt
 from wrapt.importer import when_imported
 
+import ddtrace
 from ddtrace import config
 from ddtrace.appsec._utils import _UserInfoRetriever
 from ddtrace.constants import SPAN_KIND
@@ -147,7 +148,12 @@ def cursor(django, pin, func, instance, args, kwargs):
         tags = {"django.db.vendor": vendor, "django.db.alias": alias}
         tags.update(getattr(conn, "_datadog_tags", {}))
 
-        pin = Pin(service, tags=tags, tracer=pin.tracer)
+        # Calling ddtrace.pin.Pin(...) with the `tracer` argument generates a deprecation warning.
+        # Remove this if statement when the `tracer` argument is removed
+        if pin.tracer is ddtrace.tracer:
+            pin = Pin(service, tags=tags)
+        else:
+            pin = Pin(service, tags=tags, tracer=pin.tracer)
 
         cursor = func(*args, **kwargs)
 
diff --git a/ddtrace/contrib/internal/mongoengine/trace.py b/ddtrace/contrib/internal/mongoengine/trace.py
index c5f3e834aed..93868e096ce 100644
--- a/ddtrace/contrib/internal/mongoengine/trace.py
+++ b/ddtrace/contrib/internal/mongoengine/trace.py
@@ -23,12 +23,17 @@ class WrappedConnect(wrapt.ObjectProxy):
 
     def __init__(self, connect):
         super(WrappedConnect, self).__init__(connect)
-        ddtrace.Pin(_SERVICE, tracer=ddtrace.tracer).onto(self)
+        ddtrace.trace.Pin(_SERVICE).onto(self)
 
     def __call__(self, *args, **kwargs):
         client = self.__wrapped__(*args, **kwargs)
-        pin = ddtrace.Pin.get_from(self)
+        pin = ddtrace.trace.Pin.get_from(self)
         if pin:
-            ddtrace.Pin(service=pin.service, tracer=pin.tracer).onto(client)
+            # Calling ddtrace.pin.Pin(...) with the `tracer` argument generates a deprecation warning.
+            # Remove this if statement when the `tracer` argument is removed
+            if pin.tracer is ddtrace.tracer:
+                ddtrace.trace.Pin(service=pin.service).onto(client)
+            else:
+                ddtrace.trace.Pin(service=pin.service, tracer=pin.tracer).onto(client)
 
         return client
diff --git a/ddtrace/contrib/internal/pylibmc/client.py b/ddtrace/contrib/internal/pylibmc/client.py
index 917a42b293e..3ea6f09c62c 100644
--- a/ddtrace/contrib/internal/pylibmc/client.py
+++ b/ddtrace/contrib/internal/pylibmc/client.py
@@ -51,7 +51,12 @@ def __init__(self, client=None, service=memcached.SERVICE, tracer=None, *args, *
         super(TracedClient, self).__init__(client)
 
         schematized_service = schematize_service_name(service)
-        pin = ddtrace.Pin(service=schematized_service, tracer=tracer)
+        # Calling ddtrace.pin.Pin(...) with the `tracer` argument generates a deprecation warning.
+        # Remove this if statement when the `tracer` argument is removed
+        if tracer is ddtrace.tracer:
+            pin = ddtrace.trace.Pin(service=schematized_service)
+        else:
+            pin = ddtrace.trace.Pin(service=schematized_service, tracer=tracer)
         pin.onto(self)
 
         # attempt to collect the pool of urls this client talks to
@@ -64,7 +69,7 @@ def clone(self, *args, **kwargs):
         # rewrap new connections.
         cloned = self.__wrapped__.clone(*args, **kwargs)
         traced_client = TracedClient(cloned)
-        pin = ddtrace.Pin.get_from(self)
+        pin = ddtrace.trace.Pin.get_from(self)
         if pin:
             pin.clone().onto(traced_client)
         return traced_client
@@ -155,7 +160,7 @@ def _no_span(self):
 
     def _span(self, cmd_name):
         """Return a span timing the given command."""
-        pin = ddtrace.Pin.get_from(self)
+        pin = ddtrace.trace.Pin.get_from(self)
         if not pin or not pin.enabled():
             return self._no_span()
 
diff --git a/ddtrace/contrib/internal/pymongo/client.py b/ddtrace/contrib/internal/pymongo/client.py
index 426d205f9da..2cdf2185586 100644
--- a/ddtrace/contrib/internal/pymongo/client.py
+++ b/ddtrace/contrib/internal/pymongo/client.py
@@ -61,7 +61,7 @@ def __setddpin__(client, pin):
         pin.onto(client._topology)
 
     def __getddpin__(client):
-        return ddtrace.Pin.get_from(client._topology)
+        return ddtrace.trace.Pin.get_from(client._topology)
 
     # Set a pin on the mongoclient pin on the topology object
     # This allows us to pass the same pin to the server objects
@@ -103,7 +103,7 @@ def _trace_topology_select_server(func, args, kwargs):
     # Ensure the pin used on the traced mongo client is passed down to the topology instance
     # This allows us to pass the same pin in traced server objects.
     topology_instance = get_argument_value(args, kwargs, 0, "self")
-    pin = ddtrace.Pin.get_from(topology_instance)
+    pin = ddtrace.trace.Pin.get_from(topology_instance)
 
     if pin is not None:
         pin.onto(server)
@@ -125,7 +125,7 @@ def _datadog_trace_operation(operation, wrapped):
             log.exception("error parsing query")
 
     # Gets the pin from the mogno client (through the topology object)
-    pin = ddtrace.Pin.get_from(wrapped)
+    pin = ddtrace.trace.Pin.get_from(wrapped)
     # if we couldn't parse or shouldn't trace the message, just go.
     if not cmd or not pin or not pin.enabled():
         return None
@@ -220,7 +220,7 @@ def _trace_socket_command(func, args, kwargs):
     except Exception:
         log.exception("error parsing spec. skipping trace")
 
-    pin = ddtrace.Pin.get_from(socket_instance)
+    pin = ddtrace.trace.Pin.get_from(socket_instance)
     # skip tracing if we don't have a piece of data we need
     if not dbname or not cmd or not pin or not pin.enabled():
         return func(*args, **kwargs)
@@ -239,7 +239,7 @@ def _trace_socket_write_command(func, args, kwargs):
     except Exception:
         log.exception("error parsing msg")
 
-    pin = ddtrace.Pin.get_from(socket_instance)
+    pin = ddtrace.trace.Pin.get_from(socket_instance)
     # if we couldn't parse it, don't try to trace it.
     if not cmd or not pin or not pin.enabled():
         return func(*args, **kwargs)
@@ -252,7 +252,7 @@ def _trace_socket_write_command(func, args, kwargs):
 
 
 def _trace_cmd(cmd, socket_instance, address):
-    pin = ddtrace.Pin.get_from(socket_instance)
+    pin = ddtrace.trace.Pin.get_from(socket_instance)
     s = pin.tracer.trace(
         schematize_database_operation("pymongo.cmd", database_provider="mongodb"),
         span_type=SpanTypes.MONGODB,
diff --git a/ddtrace/contrib/internal/sqlalchemy/engine.py b/ddtrace/contrib/internal/sqlalchemy/engine.py
index 57b6db4e9fc..3b5f96be9e7 100644
--- a/ddtrace/contrib/internal/sqlalchemy/engine.py
+++ b/ddtrace/contrib/internal/sqlalchemy/engine.py
@@ -67,7 +67,12 @@ def __init__(self, tracer, service, engine):
         self.name = schematize_database_operation("%s.query" % self.vendor, database_provider=self.vendor)
 
         # attach the PIN
-        Pin(tracer=tracer, service=self.service).onto(engine)
+        # Calling ddtrace.pin.Pin(...) with the `tracer` argument generates a deprecation warning.
+        # Remove this if statement when the `tracer` argument is removed
+        if self.tracer is ddtrace.tracer:
+            Pin(service=self.service).onto(engine)
+        else:
+            Pin(tracer=tracer, service=self.service).onto(engine)
 
         listen(engine, "before_cursor_execute", self._before_cur_exec)
         listen(engine, "after_cursor_execute", self._after_cur_exec)
diff --git a/ddtrace/contrib/internal/tornado/application.py b/ddtrace/contrib/internal/tornado/application.py
index 3a7dc832b5e..86794689835 100644
--- a/ddtrace/contrib/internal/tornado/application.py
+++ b/ddtrace/contrib/internal/tornado/application.py
@@ -55,4 +55,9 @@ def tracer_config(__init__, app, args, kwargs):
         tracer.set_tags(tags)
 
     # configure the PIN object for template rendering
-    ddtrace.Pin(service=service, tracer=tracer).onto(template)
+    # Required for backwards compatibility. Remove the else clause when
+    # the `ddtrace.Pin` object no longer accepts the Pin argument.
+    if tracer is ddtrace.tracer:
+        ddtrace.trace.Pin(service=service).onto(template)
+    else:
+        ddtrace.trace.Pin(service=service, tracer=tracer).onto(template)
diff --git a/ddtrace/contrib/redis/__init__.py b/ddtrace/contrib/redis/__init__.py
index 638d08b0a79..9b498614e4b 100644
--- a/ddtrace/contrib/redis/__init__.py
+++ b/ddtrace/contrib/redis/__init__.py
@@ -52,7 +52,7 @@
 Instance Configuration
 ~~~~~~~~~~~~~~~~~~~~~~
 
-To configure particular redis instances use the :class:`Pin <ddtrace.Pin>` API::
+To configure particular redis instances use the :class:`Pin <ddtrace.trace.Pin>` API::
 
     import redis
     from ddtrace.trace import Pin
diff --git a/ddtrace/contrib/tornado/__init__.py b/ddtrace/contrib/tornado/__init__.py
index ad0adef2dd5..10390e77e6e 100644
--- a/ddtrace/contrib/tornado/__init__.py
+++ b/ddtrace/contrib/tornado/__init__.py
@@ -76,11 +76,6 @@ def log_exception(self, typ, value, tb):
             'default_service': 'my-tornado-app',
             'tags': {'env': 'production'},
             'distributed_tracing': False,
-            'settings': {
-                'FILTERS':  [
-                    FilterRequestsOnUrl(r'http://test\\.example\\.com'),
-                ],
-            },
         },
     }
 
diff --git a/ddtrace/contrib/vertica/__init__.py b/ddtrace/contrib/vertica/__init__.py
index 4da8a844e83..7271c1c92ad 100644
--- a/ddtrace/contrib/vertica/__init__.py
+++ b/ddtrace/contrib/vertica/__init__.py
@@ -28,14 +28,14 @@
 ``Pin`` API::
 
     from ddtrace import patch
-    from ddtrace.trace import Pin, Tracer
+    from ddtrace.trace import Pin
     patch(vertica=True)
 
     import vertica_python
 
     conn = vertica_python.connect(**YOUR_VERTICA_CONFIG)
 
-    # override the service and tracer to be used
+    # override the service
     Pin.override(conn, service='myverticaservice')
 """
 
diff --git a/ddtrace/contrib/yaaredis/__init__.py b/ddtrace/contrib/yaaredis/__init__.py
index 2eefb3beb93..7c0c9bd1b21 100644
--- a/ddtrace/contrib/yaaredis/__init__.py
+++ b/ddtrace/contrib/yaaredis/__init__.py
@@ -50,7 +50,7 @@
 Instance Configuration
 ~~~~~~~~~~~~~~~~~~~~~~
 
-To configure particular yaaredis instances use the :class:`Pin <ddtrace.Pin>` API::
+To configure particular yaaredis instances use the :class:`Pin <ddtrace.trace.Pin>` API::
 
     import yaaredis
     from ddtrace.trace import Pin
diff --git a/ddtrace/filters.py b/ddtrace/filters.py
index 3c9c42892b8..bd6367d5635 100644
--- a/ddtrace/filters.py
+++ b/ddtrace/filters.py
@@ -4,7 +4,7 @@
 
 
 deprecate(
-    "The ddtrace.filters module is deprecated and will be removed.",
-    message="Import ``TraceFilter`` and/or ``FilterRequestsOnUrl`` from the ddtrace.trace package.",
+    "The ddtrace.filters module and the ``FilterRequestsOnUrl`` class is deprecated and will be removed.",
+    message="Import ``TraceFilter`` from the ddtrace.trace package.",
     category=DDTraceDeprecationWarning,
 )
diff --git a/ddtrace/llmobs/_evaluators/ragas/base.py b/ddtrace/llmobs/_evaluators/ragas/base.py
index 17cf5807af0..798c8e2fccc 100644
--- a/ddtrace/llmobs/_evaluators/ragas/base.py
+++ b/ddtrace/llmobs/_evaluators/ragas/base.py
@@ -26,8 +26,10 @@ class RagasDependencies:
     def __init__(self):
         import ragas
 
-        self.ragas_version = parse_version(ragas.__version__)
-        if self.ragas_version >= (0, 2, 0) or self.ragas_version < (0, 1, 10):
+        self.ragas_version = ragas.__version__  # type: str
+
+        parsed_version = parse_version(ragas.__version__)
+        if parsed_version >= (0, 2, 0) or parsed_version < (0, 1, 10):
             raise NotImplementedError(
                 "Ragas version: {} is not supported".format(self.ragas_version),
             )
diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py
index 0cd5b69db46..fdaf97410ad 100644
--- a/ddtrace/propagation/http.py
+++ b/ddtrace/propagation/http.py
@@ -42,7 +42,6 @@
 from ..internal.compat import ensure_text
 from ..internal.constants import _PROPAGATION_BEHAVIOR_RESTART
 from ..internal.constants import _PROPAGATION_STYLE_BAGGAGE
-from ..internal.constants import _PROPAGATION_STYLE_NONE
 from ..internal.constants import _PROPAGATION_STYLE_W3C_TRACECONTEXT
 from ..internal.constants import DD_TRACE_BAGGAGE_MAX_BYTES
 from ..internal.constants import DD_TRACE_BAGGAGE_MAX_ITEMS
@@ -879,20 +878,6 @@ def _inject(span_context, headers):
                 headers[_HTTP_HEADER_TRACESTATE] = span_context._tracestate
 
 
-class _NOP_Propagator:
-    @staticmethod
-    def _extract(headers):
-        # type: (Dict[str, str]) -> None
-        return None
-
-    # this method technically isn't needed with the current way we have HTTPPropagator.inject setup
-    # but if it changes then we might want it
-    @staticmethod
-    def _inject(span_context, headers):
-        # type: (Context , Dict[str, str]) -> Dict[str, str]
-        return headers
-
-
 class _BaggageHeader:
     """Helper class to inject/extract Baggage Headers"""
 
@@ -964,7 +949,6 @@ def _extract(headers: Dict[str, str]) -> Context:
     PROPAGATION_STYLE_B3_MULTI: _B3MultiHeader,
     PROPAGATION_STYLE_B3_SINGLE: _B3SingleHeader,
     _PROPAGATION_STYLE_W3C_TRACECONTEXT: _TraceContext,
-    _PROPAGATION_STYLE_NONE: _NOP_Propagator,
     _PROPAGATION_STYLE_BAGGAGE: _BaggageHeader,
 }
 
@@ -1068,6 +1052,8 @@ def parent_call():
         :param dict headers: HTTP headers to extend with tracing attributes.
         :param Span non_active_span: Only to be used if injecting a non-active span.
         """
+        if not config._propagation_style_inject:
+            return
         if non_active_span is not None and non_active_span.context is not span_context:
             log.error(
                 "span_context and non_active_span.context are not the same, but should be. non_active_span.context "
@@ -1141,7 +1127,7 @@ def my_controller(url, headers):
         :return: New `Context` with propagated attributes.
         """
         context = Context()
-        if not headers:
+        if not headers or not config._propagation_style_extract:
             return context
         try:
             style = ""
diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py
index adcd9505a52..81ed7a3ab19 100644
--- a/ddtrace/settings/config.py
+++ b/ddtrace/settings/config.py
@@ -150,12 +150,15 @@ def _parse_propagation_styles(styles_str):
                 category=DDTraceDeprecationWarning,
             )
             style = PROPAGATION_STYLE_B3_SINGLE
-        if not style:
+        if not style or style == _PROPAGATION_STYLE_NONE:
             continue
         if style not in PROPAGATION_STYLE_ALL:
             log.warning("Unknown DD_TRACE_PROPAGATION_STYLE: {!r}, allowed values are %r", style, PROPAGATION_STYLE_ALL)
             continue
         styles.append(style)
+    # Remove "none" if it's present since it lacks a propagator
+    if _PROPAGATION_STYLE_NONE in styles:
+        styles.remove(_PROPAGATION_STYLE_NONE)
     return styles
 
 
diff --git a/ddtrace/trace/__init__.py b/ddtrace/trace/__init__.py
index dcd3aeb928e..f709310d589 100644
--- a/ddtrace/trace/__init__.py
+++ b/ddtrace/trace/__init__.py
@@ -1,8 +1,18 @@
 from ddtrace._trace.context import Context
-from ddtrace._trace.filters import FilterRequestsOnUrl
 from ddtrace._trace.filters import TraceFilter
 from ddtrace._trace.pin import Pin
+from ddtrace._trace.span import Span
+from ddtrace._trace.tracer import Tracer
 
 
-# TODO: Move `ddtrace.Tracer`, `ddtrace.Span`, and `ddtrace.tracer` to this module
-__all__ = ["Context", "Pin", "TraceFilter", "FilterRequestsOnUrl"]
+# a global tracer instance with integration settings
+tracer = Tracer()
+
+__all__ = [
+    "Context",
+    "Pin",
+    "TraceFilter",
+    "Tracer",
+    "Span",
+    "tracer",
+]
diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst
index 9906fddea89..c1c41df00c0 100644
--- a/docs/advanced_usage.rst
+++ b/docs/advanced_usage.rst
@@ -332,24 +332,25 @@ configuring the tracer with a filters list. For instance, to filter out
 all traces of incoming requests to a specific url::
 
     from ddtrace import tracer
+    from ddtrace.trace import TraceFilter
+
+    class FilterbyName(TraceFilter):
+        def process_trace(self, trace):
+            for span in trace:
+                if span.name == "some_name"
+                    # drop the full trace chunk
+                    return None
+            return trace
 
     tracer.configure(settings={
         'FILTERS': [
-            FilterRequestsOnUrl(r'http://test\.example\.com'),
+            FilterbyName(),
         ],
     })
 
 The filters in the filters list will be applied sequentially to each trace
 and the resulting trace will either be sent to the Agent or discarded.
 
-**Built-in filters**
-
-The library comes with a ``FilterRequestsOnUrl`` filter that can be used to
-filter out incoming requests to specific urls:
-
-.. autoclass:: ddtrace.trace.FilterRequestsOnUrl
-    :members:
-
 **Writing a custom filter**
 
 Create a filter by implementing a class with a ``process_trace`` method and
diff --git a/docs/api.rst b/docs/api.rst
index 4c52e37808f..d4b4e80674a 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -19,7 +19,7 @@ Tracing
 .. autoclass:: ddtrace.Span
     :members:
 
-.. autoclass:: ddtrace.Pin
+.. autoclass:: ddtrace.trace.Pin
     :members:
 
 .. autoclass:: ddtrace.trace.Context
diff --git a/lib-injection/sources/sitecustomize.py b/lib-injection/sources/sitecustomize.py
index 0f87b770edd..32ab1c31ff3 100644
--- a/lib-injection/sources/sitecustomize.py
+++ b/lib-injection/sources/sitecustomize.py
@@ -45,6 +45,7 @@ def parse_version(version):
 TELEMETRY_ENABLED = "DD_INJECTION_ENABLED" in os.environ
 DEBUG_MODE = os.environ.get("DD_TRACE_DEBUG", "").lower() in ("true", "1", "t")
 INSTALLED_PACKAGES = {}
+DDTRACE_VERSION = "unknown"
 PYTHON_VERSION = "unknown"
 PYTHON_RUNTIME = "unknown"
 PKGS_ALLOW_LIST = {}
@@ -133,7 +134,7 @@ def create_count_metric(metric, tags=None):
     }
 
 
-def gen_telemetry_payload(telemetry_events, ddtrace_version="unknown"):
+def gen_telemetry_payload(telemetry_events, ddtrace_version):
     return {
         "metadata": {
             "language_name": "python",
@@ -233,6 +234,7 @@ def get_first_incompatible_sysarg():
 
 
 def _inject():
+    global DDTRACE_VERSION
     global INSTALLED_PACKAGES
     global PYTHON_VERSION
     global PYTHON_RUNTIME
@@ -353,10 +355,7 @@ def _inject():
         if not os.path.exists(site_pkgs_path):
             _log("ddtrace site-packages not found in %r, aborting" % site_pkgs_path, level="error")
             TELEMETRY_DATA.append(
-                gen_telemetry_payload(
-                    [create_count_metric("library_entrypoint.abort", ["reason:missing_" + site_pkgs_path])],
-                    DDTRACE_VERSION,
-                )
+                create_count_metric("library_entrypoint.abort", ["reason:missing_" + site_pkgs_path]),
             )
             return
 
@@ -369,14 +368,9 @@ def _inject():
         except BaseException as e:
             _log("failed to load ddtrace module: %s" % e, level="error")
             TELEMETRY_DATA.append(
-                gen_telemetry_payload(
-                    [
-                        create_count_metric(
-                            "library_entrypoint.error", ["error_type:import_ddtrace_" + type(e).__name__.lower()]
-                        )
-                    ],
-                    DDTRACE_VERSION,
-                )
+                create_count_metric(
+                    "library_entrypoint.error", ["error_type:import_ddtrace_" + type(e).__name__.lower()]
+                ),
             )
 
             return
@@ -408,28 +402,18 @@ def _inject():
 
                 _log("successfully configured ddtrace package, python path is %r" % os.environ["PYTHONPATH"])
                 TELEMETRY_DATA.append(
-                    gen_telemetry_payload(
+                    create_count_metric(
+                        "library_entrypoint.complete",
                         [
-                            create_count_metric(
-                                "library_entrypoint.complete",
-                                [
-                                    "injection_forced:" + str(runtime_incomp or integration_incomp).lower(),
-                                ],
-                            )
+                            "injection_forced:" + str(runtime_incomp or integration_incomp).lower(),
                         ],
-                        DDTRACE_VERSION,
-                    )
+                    ),
                 )
             except Exception as e:
                 TELEMETRY_DATA.append(
-                    gen_telemetry_payload(
-                        [
-                            create_count_metric(
-                                "library_entrypoint.error", ["error_type:init_ddtrace_" + type(e).__name__.lower()]
-                            )
-                        ],
-                        DDTRACE_VERSION,
-                    )
+                    create_count_metric(
+                        "library_entrypoint.error", ["error_type:init_ddtrace_" + type(e).__name__.lower()]
+                    ),
                 )
                 _log("failed to load ddtrace.bootstrap.sitecustomize: %s" % e, level="error")
                 return
@@ -451,12 +435,11 @@ def _inject():
         _inject()
     except Exception as e:
         TELEMETRY_DATA.append(
-            gen_telemetry_payload(
-                [create_count_metric("library_entrypoint.error", ["error_type:main_" + type(e).__name__.lower()])]
-            )
+            create_count_metric("library_entrypoint.error", ["error_type:main_" + type(e).__name__.lower()])
         )
     finally:
         if TELEMETRY_DATA:
-            send_telemetry(TELEMETRY_DATA)
+            payload = gen_telemetry_payload(TELEMETRY_DATA, DDTRACE_VERSION)
+            send_telemetry(payload)
 except Exception:
     pass  # absolutely never allow exceptions to propagate to the app
diff --git a/releasenotes/notes/ddtrace-resourcefilter-deprecated-52b1c92d388b0518.yaml b/releasenotes/notes/ddtrace-resourcefilter-deprecated-52b1c92d388b0518.yaml
new file mode 100644
index 00000000000..183249aa688
--- /dev/null
+++ b/releasenotes/notes/ddtrace-resourcefilter-deprecated-52b1c92d388b0518.yaml
@@ -0,0 +1,4 @@
+---
+deprecations:
+  - |
+    tracing: Deprecates ``ddtrace.filters.FilterRequestsOnUrl``. Spans should be filtered/sampled using DD_TRACE_SAMPLING_RULES configuration.
diff --git a/releasenotes/notes/fix-ssi-telemetry-events-a0a01ad0b6ef63b5.yaml b/releasenotes/notes/fix-ssi-telemetry-events-a0a01ad0b6ef63b5.yaml
new file mode 100644
index 00000000000..a1eba938bb8
--- /dev/null
+++ b/releasenotes/notes/fix-ssi-telemetry-events-a0a01ad0b6ef63b5.yaml
@@ -0,0 +1,4 @@
+---
+fixes:
+  - |
+    lib-injection: Fixes incorrect telemetry data payload format.
diff --git a/releasenotes/notes/remove-multi-tracer-support-from-pin-f2f20ca3fa731929.yaml b/releasenotes/notes/remove-multi-tracer-support-from-pin-f2f20ca3fa731929.yaml
new file mode 100644
index 00000000000..18c70a15b04
--- /dev/null
+++ b/releasenotes/notes/remove-multi-tracer-support-from-pin-f2f20ca3fa731929.yaml
@@ -0,0 +1,4 @@
+---
+deprecations:
+  - |
+    tracer: Deprecates the ability to use multiple tracer instances with ddtrace.Pin. In v3.0.0 pin objects will only use the global tracer.
diff --git a/tests/appsec/appsec/test_processor.py b/tests/appsec/appsec/test_processor.py
index 3fec599237b..ad3c9e6827a 100644
--- a/tests/appsec/appsec/test_processor.py
+++ b/tests/appsec/appsec/test_processor.py
@@ -638,7 +638,23 @@ def test_asm_context_registration(tracer):
             "name": "test required",
             "tags": {"category": "attack_attempt", "custom": "1", "type": "custom"},
             "transformers": [],
-        }
+        },
+        {
+            "conditions": [
+                {
+                    "operator": "match_regex",
+                    "parameters": {
+                        "inputs": [{"address": "usr.login"}],
+                        "options": {"case_sensitive": False},
+                        "regex": "GET",
+                    },
+                }
+            ],
+            "id": "32b243c7-26eb-4046-bbbb-custom",
+            "name": "test required",
+            "tags": {"category": "attack_attempt", "custom": "1", "type": "custom"},
+            "transformers": [],
+        },
     ]
 }
 
@@ -672,6 +688,7 @@ def test_required_addresses():
         "server.request.query",
         "server.response.headers.no_cookies",
         "usr.id",
+        "usr.login",
     }
 
 
diff --git a/tests/appsec/contrib_appsec/utils.py b/tests/appsec/contrib_appsec/utils.py
index e614e33a51b..6400cacb625 100644
--- a/tests/appsec/contrib_appsec/utils.py
+++ b/tests/appsec/contrib_appsec/utils.py
@@ -1480,6 +1480,8 @@ def test_auto_user_events(
                         assert get_tag("_dd.appsec.events.users.login.success.sdk") is None
                     if mode == "identification":
                         assert get_tag("_dd.appsec.usr.login") == user
+                    elif mode == "anonymization":
+                        assert get_tag("_dd.appsec.usr.login") == _hash_user_id(user)
                 else:
                     assert get_tag("appsec.events.users.login.success.track") == "true"
                     assert get_tag("usr.id") == user_id_hash
diff --git a/tests/appsec/iast/_ast/test_ast_patching.py b/tests/appsec/iast/_ast/test_ast_patching.py
index d014496942b..213737ecbce 100644
--- a/tests/appsec/iast/_ast/test_ast_patching.py
+++ b/tests/appsec/iast/_ast/test_ast_patching.py
@@ -1,5 +1,6 @@
 #!/usr/bin/env python3
 import logging
+import os
 import sys
 
 import astunparse
@@ -20,6 +21,15 @@
 _PREFIX = IAST.PATCH_ADDED_SYMBOL_PREFIX
 
 
+@pytest.fixture(autouse=True, scope="module")
+def clear_iast_env_vars():
+    if IAST.PATCH_MODULES in os.environ:
+        os.environ.pop("_DD_IAST_PATCH_MODULES")
+    if IAST.DENY_MODULES in os.environ:
+        os.environ.pop("_DD_IAST_DENY_MODULES")
+    yield
+
+
 @pytest.mark.parametrize(
     "source_text, module_path, module_name",
     [
@@ -148,15 +158,34 @@ def test_astpatch_source_unchanged(module_name):
     assert ("", None) == astpatch_module(__import__(module_name, fromlist=[None]))
 
 
-def test_module_should_iast_patch():
+def test_should_iast_patch_allow_first_party():
+    assert _should_iast_patch("tests.appsec.iast.integration.main")
+    assert _should_iast_patch("tests.appsec.iast.integration.print_str")
+
+
+def test_should_not_iast_patch_if_vendored():
+    assert not _should_iast_patch("foobar.vendor.requests")
+    assert not _should_iast_patch(("vendored.foobar.requests"))
+
+
+def test_should_iast_patch_deny_by_default_if_third_party():
+    # note that modules here must be in the ones returned by get_package_distributions()
+    # but not in ALLOWLIST or DENYLIST. So please don't put astunparse there :)
+    assert not _should_iast_patch("astunparse.foo.bar.not.in.deny.or.allow.list")
+
+
+def test_should_not_iast_patch_if_in_denylist():
     assert not _should_iast_patch("ddtrace.internal.module")
     assert not _should_iast_patch("ddtrace.appsec._iast")
+    assert not _should_iast_patch("pip.foo.bar")
+
+
+def test_should_not_iast_patch_if_stdlib():
     assert not _should_iast_patch("base64")
-    assert not _should_iast_patch("envier")
     assert not _should_iast_patch("itertools")
     assert not _should_iast_patch("http")
-    assert _should_iast_patch("tests.appsec.iast.integration.main")
-    assert _should_iast_patch("tests.appsec.iast.integration.print_str")
+    assert not _should_iast_patch("os.path")
+    assert not _should_iast_patch("sys.platform")
 
 
 @pytest.mark.parametrize(
diff --git a/tests/appsec/iast_packages/test_packages.py b/tests/appsec/iast_packages/test_packages.py
index 6da439dbeb5..83e53ae92c9 100644
--- a/tests/appsec/iast_packages/test_packages.py
+++ b/tests/appsec/iast_packages/test_packages.py
@@ -216,24 +216,29 @@ def uninstall(self, python_cmd):
         import_module_to_validate="boto3.session",
     ),
     PackageForTesting("botocore", "1.34.110", "", "", "", test_e2e=False),
-    PackageForTesting(
-        "cffi", "1.16.0", "", 30, "", import_module_to_validate="cffi.model", extras=[("setuptools", "72.1.0")]
-    ),
-    PackageForTesting(
-        "certifi", "2024.2.2", "", "The path to the CA bundle is", "", import_module_to_validate="certifi.core"
-    ),
-    PackageForTesting(
-        "charset-normalizer",
-        "3.3.2",
-        "my-bytes-string",
-        "my-bytes-string",
-        "",
-        import_name="charset_normalizer",
-        import_module_to_validate="charset_normalizer.api",
-        test_propagation=True,
-        fixme_propagation_fails=True,
-    ),
-    PackageForTesting("click", "8.1.7", "", "Hello World!\nHello World!\n", "", import_module_to_validate="click.core"),
+    ## Skip due to cffi added to the denylist
+    # PackageForTesting(
+    #     "cffi", "1.16.0", "", 30, "", import_module_to_validate="cffi.model", extras=[("setuptools", "72.1.0")]
+    # ),
+    ## Skip due to certifi added to the denylist
+    # PackageForTesting(
+    #     "certifi", "2024.2.2", "", "The path to the CA bundle is", "", import_module_to_validate="certifi.core"
+    # ),
+    ## Skip due to charset-normalizer added to the denylist
+    # PackageForTesting(
+    #     "charset-normalizer",
+    #     "3.3.2",
+    #     "my-bytes-string",
+    #     "my-bytes-string",
+    #     "",
+    #     import_name="charset_normalizer",
+    #     import_module_to_validate="charset_normalizer.api",
+    #     test_propagation=True,
+    #     fixme_propagation_fails=True,
+    # ),
+    ## Skip due to click added to the denylist
+    # PackageForTesting("click", "8.1.7", "", "Hello World!\nHello World!\n", "",
+    # import_module_to_validate="click.core"),
     PackageForTesting(
         "cryptography",
         "42.0.7",
@@ -247,15 +252,16 @@ def uninstall(self, python_cmd):
     PackageForTesting(
         "distlib", "0.3.8", "", "Name: example-package\nVersion: 0.1", "", import_module_to_validate="distlib.util"
     ),
-    PackageForTesting(
-        "exceptiongroup",
-        "1.2.1",
-        "foobar",
-        "ValueError: First error with foobar\nTypeError: Second error with foobar",
-        "",
-        import_module_to_validate="exceptiongroup._formatting",
-        test_propagation=True,
-    ),
+    ## Skip due to docopt added to the denylist
+    # PackageForTesting(
+    #     "exceptiongroup",
+    #     "1.2.1",
+    #     "foobar",
+    #     "ValueError: First error with foobar\nTypeError: Second error with foobar",
+    #     "",
+    #     import_module_to_validate="exceptiongroup._formatting",
+    #     test_propagation=True,
+    # ),
     PackageForTesting(
         "filelock",
         "3.14.0",
@@ -327,14 +333,15 @@ def uninstall(self, python_cmd):
         "",
         import_module_to_validate="isodate.duration",
     ),
-    PackageForTesting(
-        "itsdangerous",
-        "2.2.0",
-        "foobar",
-        "Signed value: foobar.generated_signature\nUnsigned value: foobar",
-        "",
-        import_module_to_validate="itsdangerous.serializer",
-    ),
+    ## Skip due to itsdangerous added to the denylist
+    # PackageForTesting(
+    #     "itsdangerous",
+    #     "2.2.0",
+    #     "foobar",
+    #     "Signed value: foobar.generated_signature\nUnsigned value: foobar",
+    #     "",
+    #     import_module_to_validate="itsdangerous.serializer",
+    # ),
     PackageForTesting(
         "jinja2",
         "3.1.4",
@@ -424,13 +431,15 @@ def uninstall(self, python_cmd):
     PackageForTesting(
         "openpyxl", "3.1.2", "foobar", "Written value: foobar", "", import_module_to_validate="openpyxl.chart.axis"
     ),
-    PackageForTesting(
-        "packaging",
-        "24.0",
-        "",
-        {"is_version_valid": True, "requirement": "example-package>=1.0.0", "specifier": ">=1.0.0", "version": "1.2.3"},
-        "",
-    ),
+    ## Skip due to packaging added to the denylist
+    # PackageForTesting(
+    #     "packaging",
+    #     "24.0",
+    #     "",
+    #     {"is_version_valid": True, "requirement": "example-package>=1.0.0",
+    #     "specifier": ">=1.0.0", "version": "1.2.3"},
+    #     "",
+    # ),
     ## Skip due to pandas added to the denylist
     # Pandas dropped Python 3.8 support in pandas>2.0.3
     # PackageForTesting("pandas", "2.2.2", "foobar", "Written value: foobar", "", skip_python_version=[(3, 8)]),
@@ -443,14 +452,15 @@ def uninstall(self, python_cmd):
         import_module_to_validate="platformdirs.unix",
         test_propagation=True,
     ),
-    PackageForTesting(
-        "pluggy",
-        "1.5.0",
-        "foobar",
-        "Hook result: Plugin received: foobar",
-        "",
-        import_module_to_validate="pluggy._hooks",
-    ),
+    ## Skip due to pluggy added to the denylist
+    # PackageForTesting(
+    #     "pluggy",
+    #     "1.5.0",
+    #     "foobar",
+    #     "Hook result: Plugin received: foobar",
+    #     "",
+    #     import_module_to_validate="pluggy._hooks",
+    # ),
     PackageForTesting(
         "pyasn1",
         "0.6.0",
@@ -461,7 +471,8 @@ def uninstall(self, python_cmd):
         test_propagation=True,
         fixme_propagation_fails=True,
     ),
-    PackageForTesting("pycparser", "2.22", "", "", ""),
+    ## Skip due to pygments added to the denylist
+    # PackageForTesting("pycparser", "2.22", "", "", ""),
     PackageForTesting(
         "pydantic",
         "2.7.1",
@@ -619,15 +630,16 @@ def uninstall(self, python_cmd):
         test_propagation=True,
         fixme_propagation_fails=True,
     ),
-    PackageForTesting(
-        "werkzeug",
-        "3.0.3",
-        "your-password",
-        "Original password: your-password\nHashed password: replaced_hashed\nPassword match: True",
-        "",
-        import_module_to_validate="werkzeug.http",
-        skip_python_version=[(3, 6), (3, 7), (3, 8)],
-    ),
+    ## Skip due to werkzeug added to the denylist
+    # PackageForTesting(
+    #     "werkzeug",
+    #     "3.0.3",
+    #     "your-password",
+    #     "Original password: your-password\nHashed password: replaced_hashed\nPassword match: True",
+    #     "",
+    #     import_module_to_validate="werkzeug.http",
+    #     skip_python_version=[(3, 6), (3, 7), (3, 8)],
+    # ),
     PackageForTesting(
         "yarl",
         "1.9.4",
@@ -640,24 +652,26 @@ def uninstall(self, python_cmd):
         test_propagation=True,
         fixme_propagation_fails=True,
     ),
-    PackageForTesting(
-        "zipp",
-        "3.18.2",
-        "example.zip",
-        "Contents of example.zip: ['example.zip/example.txt']",
-        "",
-        skip_python_version=[(3, 6), (3, 7), (3, 8)],
-    ),
-    PackageForTesting(
-        "typing-extensions",
-        "4.11.0",
-        "",
-        "",
-        "",
-        import_name="typing_extensions",
-        test_e2e=False,
-        skip_python_version=[(3, 6), (3, 7), (3, 8)],
-    ),
+    ## Skip due to zipp added to the denylist
+    # PackageForTesting(
+    #     "zipp",
+    #     "3.18.2",
+    #     "example.zip",
+    #     "Contents of example.zip: ['example.zip/example.txt']",
+    #     "",
+    #     skip_python_version=[(3, 6), (3, 7), (3, 8)],
+    # ),
+    ## Skip due to typing-extensions added to the denylist
+    # PackageForTesting(
+    #     "typing-extensions",
+    #     "4.11.0",
+    #     "",
+    #     "",
+    #     "",
+    #     import_name="typing_extensions",
+    #     test_e2e=False,
+    #     skip_python_version=[(3, 6), (3, 7), (3, 8)],
+    # ),
     PackageForTesting(
         "six",
         "1.16.0",
@@ -687,14 +701,15 @@ def uninstall(self, python_cmd):
         "",
         import_name="jwt",
     ),
-    PackageForTesting(
-        "wrapt",
-        "1.16.0",
-        "some-value",
-        "Function executed with param: some-value",
-        "",
-        test_propagation=True,
-    ),
+    ## Skip due to pyarrow added to the denylist
+    # PackageForTesting(
+    #     "wrapt",
+    #     "1.16.0",
+    #     "some-value",
+    #     "Function executed with param: some-value",
+    #     "",
+    #     test_propagation=True,
+    # ),
     PackageForTesting(
         "cachetools",
         "5.3.3",
@@ -804,16 +819,17 @@ def uninstall(self, python_cmd):
         "",
         import_name="OpenSSL.SSL",
     ),
-    PackageForTesting(
-        "moto[s3]",
-        "5.0.11",
-        "some_bucket",
-        "right_result",
-        "",
-        import_name="moto.s3.models",
-        test_e2e=True,
-        extras=[("boto3", "1.34.143")],
-    ),
+    ## Skip due to pyarrow added to the denylist
+    # PackageForTesting(
+    #     "moto[s3]",
+    #     "5.0.11",
+    #     "some_bucket",
+    #     "right_result",
+    #     "",
+    #     import_name="moto.s3.models",
+    #     test_e2e=True,
+    #     extras=[("boto3", "1.34.143")],
+    # ),
     PackageForTesting("decorator", "5.1.1", "World", "Decorated result: Hello, World!", ""),
     # TODO: e2e implemented but fails unpatched: "RateLimiter object has no attribute _is_allowed"
     PackageForTesting(
diff --git a/tests/appsec/integrations/django_tests/test_django_appsec.py b/tests/appsec/integrations/django_tests/test_django_appsec.py
index 3c5cb399739..2a00657e14a 100644
--- a/tests/appsec/integrations/django_tests/test_django_appsec.py
+++ b/tests/appsec/integrations/django_tests/test_django_appsec.py
@@ -235,7 +235,9 @@ def test_django_login_sucess_anonymization(client, test_spans, tracer, use_login
         assert login_span.get_tag(user.ID) == "1"
         assert login_span.get_tag("appsec.events.users.login.success.track") == "true"
         assert login_span.get_tag(APPSEC.AUTO_LOGIN_EVENTS_SUCCESS_MODE) == LOGIN_EVENTS_MODE.ANON
-        assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.login") is None
+        assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.login") == (
+            "anon_d1ad1f735a4381c2e8dbed0222db1136" if use_login else None
+        )
         assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.email") is None
         assert login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.username") is None
 
@@ -368,7 +370,10 @@ def test_django_login_sucess_anonymization_but_user_set_login(client, test_spans
         assert login_span.get_tag(user.ID) == "anon_d1ad1f735a4381c2e8dbed0222db1136"
         assert login_span.get_tag("appsec.events.users.login.success.track") == "true"
         assert login_span.get_tag(APPSEC.AUTO_LOGIN_EVENTS_SUCCESS_MODE) == LOGIN_EVENTS_MODE.ANON
-        assert not login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.login")
+        assert (
+            login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX + ".success.login")
+            == "anon_d1ad1f735a4381c2e8dbed0222db1136"
+        )
         assert not login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC + ".success.email")
         assert not login_span.get_tag(APPSEC.USER_LOGIN_EVENT_PREFIX_PUBLIC + ".success.username")
 
diff --git a/tests/profiling_v2/gunicorn.conf.py b/tests/profiling_v2/gunicorn.conf.py
new file mode 100644
index 00000000000..c45f27ce11c
--- /dev/null
+++ b/tests/profiling_v2/gunicorn.conf.py
@@ -0,0 +1,67 @@
+from datetime import datetime
+from datetime import timezone
+import logging
+
+
+def post_fork(server, worker):
+    """Log the startup time of each worker."""
+    logging.info("Worker %s started", worker.pid)
+
+
+def post_worker_init(worker):
+    logging.info("Worker %s initialized", worker.pid)
+
+
+class CustomFormatter(logging.Formatter):
+    """Custom formatter to include timezone offset in the log message."""
+
+    def formatTime(self, record, datefmt=None):
+        dt = datetime.fromtimestamp(record.created, tz=timezone.utc).astimezone()
+        milliseconds = int(record.msecs)
+        offset = dt.strftime("%z")  # Get timezone offset in the form +0530
+        if datefmt:
+            formatted_time = dt.strftime(datefmt)
+        else:
+            formatted_time = dt.strftime("%Y-%m-%d %H:%M:%S")
+
+        # Add milliseconds and timezone offset
+        offset = dt.strftime("%z")  # Timezone offset in the form +0530
+        return f"{formatted_time}.{milliseconds:03d} {offset}"
+
+
+logconfig_dict = {
+    "version": 1,
+    "formatters": {
+        "default": {
+            "()": CustomFormatter,  # Use the custom formatter
+            "format": "[%(asctime)s] [%(process)d] [%(levelname)s] %(message)s",
+            "datefmt": "%Y-%m-%d %H:%M:%S",
+        },
+    },
+    "handlers": {
+        "console": {
+            "class": "logging.StreamHandler",
+            "formatter": "default",
+        },
+    },
+    "loggers": {
+        "": {  # root logger
+            "handlers": ["console"],
+            "level": "INFO",
+        },
+        "gunicorn.error": {
+            "handlers": ["console"],
+            "level": "INFO",
+            "propagate": False,
+        },
+        "gunicorn.access": {
+            "handlers": ["console"],
+            "level": "INFO",
+            "propagate": False,
+        },
+    },
+    "root": {
+        "level": "INFO",
+        "handlers": ["console"],
+    },
+}
diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling_v2/test_gunicorn.py
index 4d7adbf6c95..90141445d3a 100644
--- a/tests/profiling_v2/test_gunicorn.py
+++ b/tests/profiling_v2/test_gunicorn.py
@@ -13,7 +13,7 @@
 
 # DEV: gunicorn tests are hard to debug, so keeping these print statements for
 # future debugging
-DEBUG_PRINT = False
+DEBUG_PRINT = True
 
 
 def debug_print(*args):
@@ -37,6 +37,8 @@ def _run_gunicorn(*args):
             "127.0.0.1:7644",
             "--worker-tmp-dir",
             "/dev/shm",
+            "-c",
+            os.path.dirname(__file__) + "/gunicorn.conf.py",
             "--chdir",
             os.path.dirname(__file__),
         ]
diff --git a/tests/tracer/test_filters.py b/tests/tracer/test_filters.py
index 871405517b7..d632ceb4998 100644
--- a/tests/tracer/test_filters.py
+++ b/tests/tracer/test_filters.py
@@ -2,9 +2,9 @@
 
 import pytest
 
+from ddtrace._trace.filters import FilterRequestsOnUrl
 from ddtrace._trace.span import Span
 from ddtrace.ext.http import URL
-from ddtrace.trace import FilterRequestsOnUrl
 from ddtrace.trace import TraceFilter
 
 
diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py
index c15439ae825..07c72e02ddb 100644
--- a/tests/tracer/test_propagation.py
+++ b/tests/tracer/test_propagation.py
@@ -1936,15 +1936,6 @@ def test_extract_tracecontext(headers, expected_context):
         B3_SINGLE_HEADERS_VALID,
         CONTEXT_EMPTY,
     ),
-    (
-        "baggage_case_insensitive",
-        None,
-        None,
-        {"BAgGage": "key1=val1,key2=val2"},
-        {
-            "baggage": {"key1": "val1", "key2": "val2"},
-        },
-    ),
     # All valid headers
     (
         "valid_all_headers_default_style",
@@ -2139,20 +2130,6 @@ def test_extract_tracecontext(headers, expected_context):
             "dd_origin": None,
         },
     ),
-    (
-        # name, styles, headers, expected_context,
-        "none_and_other_prop_style_still_extracts",
-        [PROPAGATION_STYLE_DATADOG, _PROPAGATION_STYLE_NONE],
-        None,
-        ALL_HEADERS,
-        {
-            "trace_id": 13088165645273925489,
-            "span_id": 5678,
-            "sampling_priority": 1,
-            "dd_origin": "synthetics",
-            "meta": {"_dd.p.dm": "-3"},
-        },
-    ),
     # Testing that order matters
     (
         "order_matters_B3_SINGLE_HEADER_first",
@@ -2413,6 +2390,15 @@ def test_extract_tracecontext(headers, expected_context):
             ],
         },
     ),
+    (
+        "baggage_case_insensitive",
+        None,
+        None,
+        {"BAgGage": "key1=val1,key2=val2"},
+        {
+            "baggage": {"key1": "val1", "key2": "val2"},
+        },
+    ),
 ]
 
 # Only add fixtures here if they can't pass both test_propagation_extract_env
@@ -2452,6 +2438,19 @@ def test_extract_tracecontext(headers, expected_context):
             },
         },
     ),
+    (
+        "none_and_other_prop_style_still_extracts",
+        [PROPAGATION_STYLE_DATADOG, _PROPAGATION_STYLE_NONE],
+        None,
+        ALL_HEADERS,
+        {
+            "trace_id": 13088165645273925489,
+            "span_id": 5678,
+            "sampling_priority": 1,
+            "dd_origin": "synthetics",
+            "meta": {"_dd.p.dm": "-3"},
+        },
+    ),
     # Only works for env since config is modified at startup to set
     # propagation_style_extract to [None] if DD_TRACE_PROPAGATION_BEHAVIOR_EXTRACT is set to ignore
     (
@@ -2461,6 +2460,20 @@ def test_extract_tracecontext(headers, expected_context):
         DATADOG_HEADERS_VALID,
         CONTEXT_EMPTY,
     ),
+    (
+        # name, styles, headers, expected_context,
+        "none_and_other_prop_style_still_extracts",
+        [PROPAGATION_STYLE_DATADOG, _PROPAGATION_STYLE_NONE],
+        None,
+        ALL_HEADERS,
+        {
+            "trace_id": 13088165645273925489,
+            "span_id": 5678,
+            "sampling_priority": 1,
+            "dd_origin": "synthetics",
+            "meta": {"_dd.p.dm": "-3"},
+        },
+    ),
 ]