Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .riot/requirements/1362718.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/1362718.in
#
attrs==26.1.0
coverage[toml]==7.15.4
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
opentracing==2.4.0
packaging==26.3
pluggy==1.6.0
pygments==2.21.0
pytest==9.1.1
pytest-cov==7.1.0
pytest-mock==3.15.1
pytest-randomly==4.1.0
sortedcontainers==2.4.0
20 changes: 20 additions & 0 deletions .riot/requirements/13ed954.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/13ed954.in
#
attrs==26.1.0
coverage[toml]==7.15.4
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
opentracing==2.4.0
packaging==26.3
pluggy==1.6.0
pygments==2.21.0
pytest==9.1.1
pytest-cov==7.1.0
pytest-mock==3.15.1
pytest-randomly==4.1.0
sortedcontainers==2.4.0
20 changes: 20 additions & 0 deletions .riot/requirements/1638d0f.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/1638d0f.in
#
attrs==26.1.0
coverage[toml]==7.15.4
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
opentracing==2.4.0
packaging==26.3
pluggy==1.6.0
pygments==2.21.0
pytest==9.1.1
pytest-cov==7.1.0
pytest-mock==3.15.1
pytest-randomly==4.1.0
sortedcontainers==2.4.0
25 changes: 25 additions & 0 deletions .riot/requirements/180047f.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/180047f.in
#
attrs==26.1.0
coverage[toml]==7.10.7
exceptiongroup==1.3.1
hypothesis==6.45.0
importlib-metadata==8.7.1
iniconfig==2.1.0
mock==5.2.0
opentracing==2.4.0
packaging==26.3
pluggy==1.6.0
pygments==2.21.0
pytest==8.4.2
pytest-cov==7.1.0
pytest-mock==3.15.1
pytest-randomly==4.0.1
sortedcontainers==2.4.0
tomli==2.4.1
typing-extensions==4.16.0
zipp==3.23.1
23 changes: 23 additions & 0 deletions .riot/requirements/3cb8c3c.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/3cb8c3c.in
#
attrs==26.1.0
coverage[toml]==7.15.4
exceptiongroup==1.3.1
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
opentracing==2.4.0
packaging==26.3
pluggy==1.6.0
pygments==2.21.0
pytest==9.1.1
pytest-cov==7.1.0
pytest-mock==3.15.1
pytest-randomly==4.1.0
sortedcontainers==2.4.0
tomli==2.4.1
typing-extensions==4.16.0
20 changes: 20 additions & 0 deletions .riot/requirements/43423a3.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#
# This file is autogenerated by pip-compile with Python 3.13
# by the following command:
#
# pip-compile --allow-unsafe --no-annotate .riot/requirements/43423a3.in
#
attrs==26.1.0
coverage[toml]==7.15.4
hypothesis==6.45.0
iniconfig==2.3.0
mock==5.2.0
opentracing==2.4.0
packaging==26.3
pluggy==1.6.0
pygments==2.21.0
pytest==9.1.1
pytest-cov==7.1.0
pytest-mock==3.15.1
pytest-randomly==4.1.0
sortedcontainers==2.4.0
2 changes: 2 additions & 0 deletions ddtrace/_monkey.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"aiopg": True,
"aiobotocore": False,
"httplib": False,
"http_server": True,
"urllib3": False,
"vertexai": True,
"vertica": True,
Expand Down Expand Up @@ -165,6 +166,7 @@
"azure_functions": ("azure.functions",),
"azure_servicebus": ("azure.servicebus",),
"httplib": ("http.client",),
"http_server": ("http.server",),
"kafka": ("confluent_kafka",),
"google_adk": ("google.adk",),
"google_cloud_pubsub": ("google.cloud.pubsub_v1",),
Expand Down
8 changes: 6 additions & 2 deletions ddtrace/appsec/_asm_request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,8 +400,12 @@ def finalize_asm_env(env: ASM_Environment) -> None:
entry_span._set_attribute(APPSEC.EVENT_RULE_ERROR_COUNT, info.failed)
except Exception:
logger.debug("asm_context::finalize_asm_env::exception", extra=log_extra, exc_info=True)
if asm_config._rc_client_id is not None:
entry_span.set_tag(APPSEC.RC_CLIENT_ID, asm_config._rc_client_id)
if asm_config._rc_client_id_enabled:
from ddtrace.internal.remoteconfig.worker import remoteconfig_poller

rc_client_id = remoteconfig_poller._client.id
if rc_client_id is not None:
entry_span.set_tag(APPSEC.RC_CLIENT_ID, rc_client_id)
waf_adresses = env.waf_addresses
req_headers = waf_adresses.get(SPAN_DATA_NAMES.REQUEST_HEADERS_NO_COOKIES, {})
if req_headers:
Expand Down
5 changes: 4 additions & 1 deletion ddtrace/appsec/_remoteconfiguration.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,13 @@ def enable_appsec_rc(callback: "AppSecCallback") -> None:

if asm_config._asm_enabled:
telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, True)
asm_config._rc_client_id = remoteconfig_poller._client.id

asm_config._rc_client_id_enabled = True


def disable_appsec_rc() -> None:
asm_config._rc_client_id_enabled = False

for product_name in APPSEC_PRODUCTS:
remoteconfig_poller.unregister_callback(product_name)
remoteconfig_poller.disable_product(product_name)
Expand Down
2 changes: 2 additions & 0 deletions ddtrace/contrib/internal/asgi/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ async def __call__(self, scope: Mapping[str, Any], receive: Callable, send: Call
method = "websocket"
else:
return await self.app(scope, receive, send)
if not is_subapp and scope["type"] == "http":
core.dispatch(core.WEB_REQUEST_STARTING, (method, scope["path"]))
try:
headers = extract_headers(scope)
except Exception:
Expand Down
2 changes: 2 additions & 0 deletions ddtrace/contrib/internal/bottle/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from ddtrace.internal.utils.formats import asbool

from .trace import TracePlugin
from .trace import traced_wsgi


# Configure default configuration
Expand All @@ -32,6 +33,7 @@ def patch():

bottle._datadog_patch = True
wrapt.wrap_function_wrapper("bottle", "Bottle.__init__", traced_init)
wrapt.wrap_function_wrapper("bottle", "Bottle.wsgi", traced_wsgi)


def traced_init(wrapped, instance, args, kwargs):
Expand Down
10 changes: 10 additions & 0 deletions ddtrace/contrib/internal/bottle/trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@
from ddtrace.vendor.debtcollector import deprecate


def traced_wsgi(wrapped, instance, args, kwargs):
"""Wraps Bottle's WSGI entry point so the MicroVM ``/run`` hook is detected even when no
route matches (404) or the matched route is a wildcard -- ``TracePlugin.apply()`` only
wraps callbacks for routes that already matched, so it can't see either case.
"""
environ = args[0]
core.dispatch(core.WEB_REQUEST_STARTING, (environ.get("REQUEST_METHOD"), environ.get("PATH_INFO")))
return wrapped(*args, **kwargs)


class TracePlugin(object):
name = "trace"
api = 2
Expand Down
2 changes: 2 additions & 0 deletions ddtrace/contrib/internal/cherrypy/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ def _setup(self):
cherrypy.request.hooks.attach("after_error_response", self._after_error_response, priority=5)

def _on_start_resource(self):
core.dispatch(core.WEB_REQUEST_STARTING, (cherrypy.request.method, cherrypy.request.path_info))

with core.context_with_data(
"cherrypy.request",
span_name=SPAN_NAME,
Expand Down
2 changes: 2 additions & 0 deletions ddtrace/contrib/internal/django/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ def traced_get_response(func: FunctionType, args: tuple[Any, ...], kwargs: dict[
if request is None:
return func(*args, **kwargs)

core.dispatch(core.WEB_REQUEST_STARTING, (request.method, request.path))

request_headers = utils._get_request_headers(request)

pin = Pin.get_from(instance)
Expand Down
2 changes: 2 additions & 0 deletions ddtrace/contrib/internal/falcon/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ def __init__(self, tracer=None, service=None, distributed_tracing=None):
config.falcon["distributed_tracing"] = distributed_tracing

def process_request(self, req, resp):
core.dispatch(core.WEB_REQUEST_STARTING, (req.method, req.path))

# Falcon uppercases all header names.
headers = dict((k.lower(), v) for k, v in req.headers.items())

Expand Down
1 change: 1 addition & 0 deletions ddtrace/contrib/internal/flask/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ def unpatch():

def patched_wsgi_app(wrapped, instance, args, kwargs):
environ, start_response = args
core.dispatch(core.WEB_REQUEST_STARTING, (environ.get("REQUEST_METHOD"), environ.get("PATH_INFO")))
# Registration is gated on asm_config, not tracing — keep this above the tracing short-circuit.
_collect_routes_once(instance, environ.get("SCRIPT_NAME") or "")
if not is_tracing_enabled():
Expand Down
17 changes: 17 additions & 0 deletions ddtrace/contrib/internal/http_server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
Patch the standard library ``http.server`` module (``BaseHTTPRequestHandler``).

This integration does **not** create spans. Its only purpose is detecting the AWS Lambda
MicroVM ``/run`` lifecycle hook for applications that implement that hook with a raw
``http.server`` handler instead of a supported web framework.


Enabling
~~~~~~~~

The http_server integration is enabled by default. Use
:ref:`ddtrace-run<ddtracerun>` or :ref:`import ddtrace.auto<ddtraceauto>` to enable it, and
disable it with ``DD_TRACE_HTTP_SERVER_ENABLED=false`` if needed::

DD_TRACE_HTTP_SERVER_ENABLED=false ddtrace-run ....
"""
71 changes: 71 additions & 0 deletions ddtrace/contrib/internal/http_server/patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from __future__ import annotations

import sys
from types import ModuleType
from typing import TYPE_CHECKING
from typing import Any
from typing import Callable

from wrapt import wrap_function_wrapper as _w

from ddtrace.contrib.internal.trace_utils import unwrap as _u
from ddtrace.internal import core


if TYPE_CHECKING:
import http.server


def _get_http_server() -> ModuleType:
# DEV: When patch() is called from the on-import hook, we're running from inside
# http.server's own exec_module(), before CPython's import machinery binds it as the
# "server" attribute of the "http" package (that setattr() happens only after
# exec_module() returns to _find_and_load()). Accessing it via `http.server` (attribute
# chain) at that point raises "cannot access submodule 'server' of module 'http' (most
# likely due to a circular import)". sys.modules is populated before exec_module() even
# starts, so prefer it -- falling back to a plain import for direct patch() calls that
# happen before anything has imported http.server yet (not in sys.modules at all).
loaded = sys.modules.get("http.server")
if loaded is not None:
return loaded
import http.server

return http.server


def get_version() -> str:
return ""


def _supported_versions() -> dict[str, str]:
return {"http.server": "*"}


def _wrap_parse_request(
wrapped: Callable[..., bool],
instance: "http.server.BaseHTTPRequestHandler",
args: tuple[Any, ...],
kwargs: dict[str, Any],
) -> bool:
parsed = wrapped(*args, **kwargs)
if parsed:
core.dispatch(core.WEB_REQUEST_STARTING, (instance.command, instance.path))
return parsed


def patch() -> None:
http_server = _get_http_server()
if getattr(http_server, "__datadog_patch", False):
return
http_server.__datadog_patch = True # type: ignore[attr-defined] # patch marker, not a real module attr

_w(http_server.BaseHTTPRequestHandler, "parse_request", _wrap_parse_request)


def unpatch() -> None:
http_server = _get_http_server()
if not getattr(http_server, "__datadog_patch", False):
return
http_server.__datadog_patch = False # type: ignore[attr-defined] # patch marker, not a real module attr

_u(http_server.BaseHTTPRequestHandler, "parse_request")
4 changes: 4 additions & 0 deletions ddtrace/contrib/internal/molten/patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ def unpatch():


def patch_app_call(wrapped, instance, args, kwargs):
# DEV: This is safe because this is the args for a WSGI handler
# https://www.python.org/dev/peps/pep-3333/
core.dispatch(core.WEB_REQUEST_STARTING, (args[0].get("REQUEST_METHOD"), args[0].get("PATH_INFO")))

pin = Pin.get_from(molten)

if not pin or not pin.enabled():
Expand Down
Loading
Loading