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": bool(env.get("AWS_LAMBDA_MICROVM_IMAGE_ARN")),
"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
18 changes: 18 additions & 0 deletions ddtrace/contrib/internal/http_server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""
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 only in AWS Lambda MicroVM
environments. Use :ref:`ddtrace-run<ddtracerun>` or :ref:`import ddtrace.auto<ddtraceauto>`
to enable it there, or set ``DD_TRACE_HTTP_SERVER_ENABLED=true`` to force-enable it
elsewhere. 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")
1 change: 1 addition & 0 deletions ddtrace/internal/settings/_supported_configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@
"DD_TRACE_HTTPLIB_ENABLED",
"DD_TRACE_HTTPX_ENABLED",
"DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING",
"DD_TRACE_HTTP_SERVER_ENABLED",
"DD_TRACE_HTTP_SERVER_ERROR_STATUSES",
"DD_TRACE_INFERRED_PROXY_SERVICES_ENABLED",
"DD_TRACE_JINJA2_ENABLED",
Expand Down
6 changes: 6 additions & 0 deletions docs/integrations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,12 @@ gunicorn
.. automodule:: ddtrace.contrib.internal.gunicorn


.. _http_server:

http_server
^^^^^^^^^^^
.. automodule:: ddtrace.contrib.internal.http_server

.. _httplib:

httplib
Expand Down
8 changes: 8 additions & 0 deletions riotfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,14 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT
},
pys=select_pys(),
),
Venv(
name="http_server",
command="pytest {cmdargs} tests/contrib/http_server",
pkgs={
"pytest-randomly": latest,
},
pys=select_pys(),
),
Venv(
name="logging",
command="pytest -n auto --dist=worksteal {cmdargs} tests/contrib/logging",
Expand Down
4 changes: 4 additions & 0 deletions scripts/integration_registry/registry.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,10 @@ integrations:
min: 20.0.4
max: 23.0.0

- integration_name: http_server
is_external_package: false
is_tested: true

- integration_name: httplib
is_external_package: false
is_tested: true
Expand Down
6 changes: 6 additions & 0 deletions scripts/supported_configurations.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
# Do not add new values to this list; ignoring a value prevents it from working with the configuration system.
IGNORED_ENVIRONMENT_VARIABLES = ["_DD_CONTEXTVAR"]

# Integrations that are patchable via DD_TRACE_{NAME}_ENABLED but do not create spans,
# so they do not have DD_{NAME}_SERVICE[_NAME] configuration.
NO_SERVICE_CONFIGURATION = {"http_server"}

HEADER = """\
# AUTO-GENERATED from supported-configurations.json — do not edit manually.
# Run: python scripts/supported_configurations.py
Expand Down Expand Up @@ -423,6 +427,8 @@ def check_registry(data: dict) -> int:
n = name.upper()
if name not in not_patchable and f"DD_TRACE_{n}_ENABLED" not in all_known:
missing.add(f"DD_TRACE_{n}_ENABLED")
if name in NO_SERVICE_CONFIGURATION:
continue
for var in (f"DD_{n}_SERVICE", f"DD_{n}_SERVICE_NAME"):
if var not in all_known:
missing.add(var)
Expand Down
7 changes: 7 additions & 0 deletions supported-configurations.json
Original file line number Diff line number Diff line change
Expand Up @@ -4360,6 +4360,13 @@
"default": "true"
}
],
"DD_TRACE_HTTP_SERVER_ENABLED": [
{
"implementation": "A",
"type": "boolean",
"default": "true"
}
],
"DD_TRACE_HTTP_SERVER_ERROR_STATUSES": [
{
"implementation": "A",
Expand Down
Empty file.
26 changes: 26 additions & 0 deletions tests/contrib/http_server/test_http_server_patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from ddtrace.contrib.internal.http_server.patch import get_version
from ddtrace.contrib.internal.http_server.patch import patch
from ddtrace.contrib.internal.http_server.patch import unpatch
from tests.contrib.patch import PatchTestCase


class TestHttpServerPatch(PatchTestCase.Base):
__integration_name__ = "http_server"
__module_name__ = "http.server"
__patch_func__ = patch
__unpatch_func__ = unpatch
__get_version__ = get_version

def assert_module_patched(self, http_server):
self.assert_wrapped(http_server.BaseHTTPRequestHandler.parse_request)

def assert_not_module_patched(self, http_server):
self.assert_not_wrapped(http_server.BaseHTTPRequestHandler.parse_request)

def assert_not_module_double_patched(self, http_server):
self.assert_not_double_wrapped(http_server.BaseHTTPRequestHandler.parse_request)

def test_and_emit_get_version(self):
version = get_version()
assert isinstance(version, str)
assert version == ""
Loading
Loading