diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b8a8a48d8f8..84e6c873642 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -115,6 +115,8 @@ ddtrace/contrib/flask_login/ @DataDog/asm-python ddtrace/contrib/webbrowser @DataDog/asm-python ddtrace/contrib/urllib @DataDog/asm-python ddtrace/internal/_exceptions.py @DataDog/asm-python +ddtrace/internal/appsec/ @DataDog/asm-python +ddtrace/internal/iast/ @DataDog/asm-python tests/appsec/ @DataDog/asm-python tests/contrib/dbapi/test_dbapi_appsec.py @DataDog/asm-python tests/contrib/subprocess @DataDog/asm-python diff --git a/.github/workflows/build-and-publish-image.yml b/.github/workflows/build-and-publish-image.yml index 2482d03a924..55647245109 100644 --- a/.github/workflows/build-and-publish-image.yml +++ b/.github/workflows/build-and-publish-image.yml @@ -26,6 +26,9 @@ on: jobs: build_push: runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - uses: actions/checkout@v4 with: diff --git a/.github/workflows/pypa_musllinux_1_2_i686.yml b/.github/workflows/pypa_musllinux_1_2_i686.yml index f8f21f88a5b..4c86e5b4011 100644 --- a/.github/workflows/pypa_musllinux_1_2_i686.yml +++ b/.github/workflows/pypa_musllinux_1_2_i686.yml @@ -12,6 +12,9 @@ on: jobs: build-and-publish: uses: ./.github/workflows/build-and-publish-image.yml + permissions: + contents: read + packages: write with: tags: 'ghcr.io/datadog/dd-trace-py/pypa_musllinux_1_2_i686:${{ github.sha }},ghcr.io/datadog/dd-trace-py/pypa_musllinux_1_2_i686:latest' platforms: 'linux/386' diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index 4bd448eaf18..3241a0b763e 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - extension: ["src/core"] + extension: ["src/native"] steps: - uses: actions/checkout@v4 with: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 748942af278..b29f243c474 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,18 +59,20 @@ onboarding_tests_installer: matrix: - ONBOARDING_FILTER_WEBLOG: [test-app-python,test-app-python-container,test-app-python-alpine] - onboarding_tests_k8s_injection: parallel: matrix: - - WEBLOG_VARIANT: - - dd-lib-python-init-test-django - - dd-lib-python-init-test-django-gunicorn - - dd-lib-python-init-test-django-gunicorn-alpine - - dd-lib-python-init-test-django-preinstalled - - dd-lib-python-init-test-django-unsupported-package-force - - dd-lib-python-init-test-django-uvicorn - - dd-lib-python-init-test-protobuf-old + - WEBLOG_VARIANT: [dd-lib-python-init-test-django, ] + SCENARIO: [K8S_LIB_INJECTION, K8S_LIB_INJECTION_UDS, K8S_LIB_INJECTION_NO_AC, K8S_LIB_INJECTION_NO_AC_UDS, K8S_LIB_INJECTION_PROFILING_DISABLED, K8S_LIB_INJECTION_PROFILING_ENABLED, K8S_LIB_INJECTION_PROFILING_OVERRIDE] + K8S_CLUSTER_VERSION: ['7.56.2', '7.59.0'] + + - WEBLOG_VARIANT: [dd-lib-python-init-test-django-gunicorn, dd-lib-python-init-test-django-gunicorn-alpine, dd-lib-python-init-test-django-unsupported-package-force, dd-lib-python-init-test-django-uvicorn, dd-lib-python-init-test-protobuf-old ] + SCENARIO: [K8S_LIB_INJECTION, K8S_LIB_INJECTION_PROFILING_ENABLED] + K8S_CLUSTER_VERSION: ['7.56.2', '7.59.0'] + + - WEBLOG_VARIANT: [dd-lib-python-init-test-django-preinstalled] + SCENARIO: [K8S_LIB_INJECTION, K8S_LIB_INJECTION_UDS, K8S_LIB_INJECTION_NO_AC, K8S_LIB_INJECTION_NO_AC_UDS] + K8S_CLUSTER_VERSION: ['7.56.2', '7.59.0'] deploy_to_di_backend:manual: stage: shared-pipeline diff --git a/.gitlab/benchmarks.yml b/.gitlab/benchmarks.yml index 9d56afcdf09..6c7b48aea3a 100644 --- a/.gitlab/benchmarks.yml +++ b/.gitlab/benchmarks.yml @@ -25,7 +25,6 @@ variables: paths: - reports/ expire_in: 3 months - allow_failure: true # Allow failure, so partial results are uploaded variables: UPSTREAM_PROJECT_ID: $CI_PROJECT_ID # The ID of the current project. This ID is unique across all projects on the GitLab instance. UPSTREAM_PROJECT_NAME: $CI_PROJECT_NAME # "dd-trace-py" @@ -78,6 +77,21 @@ benchmarks-pr-comment: UPSTREAM_COMMIT_SHA: $CI_COMMIT_SHA # The commit revision the project is built for. KUBERNETES_SERVICE_ACCOUNT_OVERWRITE: dd-trace-py +check-big-regressions: + stage: benchmarks + needs: [ microbenchmarks, benchmark-serverless ] + when: always + tags: ["arch:amd64"] + image: $MICROBENCHMARKS_CI_IMAGE + script: + - export ARTIFACTS_DIR="$(pwd)/reports/" + - git config --global url."https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.ddbuild.io/DataDog/".insteadOf "https://github.com/DataDog/" + - git clone --branch dd-trace-py https://github.com/DataDog/benchmarking-platform /platform && cd /platform + - bp-runner bp-runner.fail-on-regression.yml --debug + variables: + # Gitlab and BP specific env vars. Do not modify. + KUBERNETES_SERVICE_ACCOUNT_OVERWRITE: dd-trace-py + benchmark-serverless: stage: benchmarks image: $SLS_CI_IMAGE diff --git a/benchmarks/http_propagation_extract/scenario.py b/benchmarks/http_propagation_extract/scenario.py index 8aa67f651c7..cdfe9f8dadd 100644 --- a/benchmarks/http_propagation_extract/scenario.py +++ b/benchmarks/http_propagation_extract/scenario.py @@ -29,6 +29,8 @@ def generate_headers(self): def run(self): if self.styles: config._propagation_style_extract = self.styles.split(",") if ("," in self.styles) else [self.styles] + if "none" in config._propagation_style_extract: + config._propagation_style_extract.remove("none") headers = self.generate_headers() diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index b93b0cb1fc4..75c70114ef2 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -176,9 +176,13 @@ def _on_import_factory(module, prefix="ddtrace.contrib", raise_errors=True, patc def on_import(hook): # Import and patch module - path = "%s.%s" % (prefix, module) try: - imported_module = importlib.import_module(path) + try: + imported_module = importlib.import_module("%s.internal.%s.patch" % (prefix, module)) + except ImportError: + # Some integrations do not have an internal patch module, so we use the public one + # FIXME: This is a temporary solution until we refactor the patching logic. + imported_module = importlib.import_module("%s.%s" % (prefix, module)) imported_module.patch() if hasattr(imported_module, "patch_submodules"): imported_module.patch_submodules(patch_indicator) @@ -204,7 +208,8 @@ def on_import(hook): telemetry.telemetry_writer.add_integration( name, True, PATCH_MODULES.get(module) is True, "", version=v ) - else: + elif hasattr(imported_module, "get_version"): + # TODO: Ensure every integration defines either get_version or get_versions in their patch.py module version = imported_module.get_version() telemetry.telemetry_writer.add_integration( module, True, PATCH_MODULES.get(module) is True, "", version=version diff --git a/ddtrace/_trace/context.py b/ddtrace/_trace/context.py index 07bc3960b56..c69496c865e 100644 --- a/ddtrace/_trace/context.py +++ b/ddtrace/_trace/context.py @@ -12,9 +12,9 @@ from ddtrace._trace._span_link import SpanLink from ddtrace._trace.types import _MetaDictType from ddtrace._trace.types import _MetricDictType -from ddtrace.constants import ORIGIN_KEY -from ddtrace.constants import SAMPLING_PRIORITY_KEY -from ddtrace.constants import USER_ID_KEY +from ddtrace.constants import _ORIGIN_KEY +from ddtrace.constants import _SAMPLING_PRIORITY_KEY +from ddtrace.constants import _USER_ID_KEY from ddtrace.internal.compat import NumericType from ddtrace.internal.constants import MAX_UINT_64BITS as _MAX_UINT_64BITS from ddtrace.internal.constants import W3C_TRACEPARENT_KEY @@ -72,9 +72,9 @@ def __init__( self._is_remote: bool = is_remote if dd_origin is not None and _DD_ORIGIN_INVALID_CHARS_REGEX.search(dd_origin) is None: - self._meta[ORIGIN_KEY] = dd_origin + self._meta[_ORIGIN_KEY] = dd_origin if sampling_priority is not None: - self._metrics[SAMPLING_PRIORITY_KEY] = sampling_priority + self._metrics[_SAMPLING_PRIORITY_KEY] = sampling_priority if span_links is not None: self._span_links = span_links else: @@ -127,16 +127,16 @@ def _update_tags(self, span: "Span") -> None: @property def sampling_priority(self) -> Optional[NumericType]: """Return the context sampling priority for the trace.""" - return self._metrics.get(SAMPLING_PRIORITY_KEY) + return self._metrics.get(_SAMPLING_PRIORITY_KEY) @sampling_priority.setter def sampling_priority(self, value: Optional[NumericType]) -> None: with self._lock: if value is None: - if SAMPLING_PRIORITY_KEY in self._metrics: - del self._metrics[SAMPLING_PRIORITY_KEY] + if _SAMPLING_PRIORITY_KEY in self._metrics: + del self._metrics[_SAMPLING_PRIORITY_KEY] return - self._metrics[SAMPLING_PRIORITY_KEY] = value + self._metrics[_SAMPLING_PRIORITY_KEY] = value @property def _traceparent(self) -> str: @@ -180,22 +180,22 @@ def _tracestate(self) -> str: @property def dd_origin(self) -> Optional[Text]: """Get the origin of the trace.""" - return self._meta.get(ORIGIN_KEY) + return self._meta.get(_ORIGIN_KEY) @dd_origin.setter def dd_origin(self, value: Optional[Text]) -> None: """Set the origin of the trace.""" with self._lock: if value is None: - if ORIGIN_KEY in self._meta: - del self._meta[ORIGIN_KEY] + if _ORIGIN_KEY in self._meta: + del self._meta[_ORIGIN_KEY] return - self._meta[ORIGIN_KEY] = value + self._meta[_ORIGIN_KEY] = value @property def dd_user_id(self) -> Optional[Text]: """Get the user ID of the trace.""" - user_id = self._meta.get(USER_ID_KEY) + user_id = self._meta.get(_USER_ID_KEY) if user_id: return str(base64.b64decode(user_id), encoding="utf-8") return None @@ -205,10 +205,10 @@ def dd_user_id(self, value: Optional[Text]) -> None: """Set the user ID of the trace.""" with self._lock: if value is None: - if USER_ID_KEY in self._meta: - del self._meta[USER_ID_KEY] + if _USER_ID_KEY in self._meta: + del self._meta[_USER_ID_KEY] return - self._meta[USER_ID_KEY] = str(base64.b64encode(bytes(value, encoding="utf-8")), encoding="utf-8") + self._meta[_USER_ID_KEY] = str(base64.b64encode(bytes(value, encoding="utf-8")), encoding="utf-8") @property def _trace_id_64bits(self): diff --git a/ddtrace/_trace/processor/__init__.py b/ddtrace/_trace/processor/__init__.py index fc59a64828b..0437b65b364 100644 --- a/ddtrace/_trace/processor/__init__.py +++ b/ddtrace/_trace/processor/__init__.py @@ -14,7 +14,7 @@ from ddtrace._trace.span import _get_64_highest_order_bits_as_hex from ddtrace._trace.span import _is_top_level from ddtrace.constants import _APM_ENABLED_METRIC_KEY as MK_APM_ENABLED -from ddtrace.constants import SAMPLING_PRIORITY_KEY +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import USER_KEEP from ddtrace.internal import gitmetadata from ddtrace.internal import telemetry @@ -165,7 +165,7 @@ def process_trace(self, trace: List[Span]) -> Optional[List[Span]]: # In order to ensure that the agent does not update priority sampling rates # due to single spans sampling, we set all of these spans to manual keep. if config._trace_compute_stats: - span.set_metric(SAMPLING_PRIORITY_KEY, USER_KEEP) + span.set_metric(_SAMPLING_PRIORITY_KEY, USER_KEEP) break return trace diff --git a/ddtrace/_trace/sampler.py b/ddtrace/_trace/sampler.py index 6e2515ee152..961526e3f26 100644 --- a/ddtrace/_trace/sampler.py +++ b/ddtrace/_trace/sampler.py @@ -12,7 +12,7 @@ from typing import Tuple # noqa:F401 from ddtrace import config -from ddtrace.constants import SAMPLING_LIMIT_DECISION +from ddtrace.constants import _SAMPLING_LIMIT_DECISION from ..constants import ENV_KEY from ..internal.constants import _PRIORITY_CATEGORY @@ -342,7 +342,7 @@ def sample(self, span): # uses DatadogSampler._rate_limit_always_on to override this functionality. if sampled: sampled = self.limiter.is_allowed() - span.set_metric(SAMPLING_LIMIT_DECISION, self.limiter.effective_rate) + span.set_metric(_SAMPLING_LIMIT_DECISION, self.limiter.effective_rate) _set_sampling_tags( span, sampled, diff --git a/ddtrace/_trace/span.py b/ddtrace/_trace/span.py index afb3496db80..446239a8091 100644 --- a/ddtrace/_trace/span.py +++ b/ddtrace/_trace/span.py @@ -24,17 +24,17 @@ from ddtrace._trace.types import _MetricDictType from ddtrace._trace.types import _TagNameType from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SAMPLING_AGENT_DECISION +from ddtrace.constants import _SAMPLING_LIMIT_DECISION +from ddtrace.constants import _SAMPLING_RULE_DECISION +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE from ddtrace.constants import MANUAL_DROP_KEY from ddtrace.constants import MANUAL_KEEP_KEY -from ddtrace.constants import SAMPLING_AGENT_DECISION -from ddtrace.constants import SAMPLING_LIMIT_DECISION -from ddtrace.constants import SAMPLING_RULE_DECISION from ddtrace.constants import SERVICE_KEY from ddtrace.constants import SERVICE_VERSION_KEY -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.constants import USER_KEEP from ddtrace.constants import USER_REJECT from ddtrace.constants import VERSION_KEY @@ -327,7 +327,7 @@ def _override_sampling_decision(self, decision: Optional[NumericType]): self.context.sampling_priority = decision set_sampling_decision_maker(self.context, SamplingMechanism.MANUAL) if self._local_root: - for key in (SAMPLING_RULE_DECISION, SAMPLING_AGENT_DECISION, SAMPLING_LIMIT_DECISION): + for key in (_SAMPLING_RULE_DECISION, _SAMPLING_AGENT_DECISION, _SAMPLING_LIMIT_DECISION): if key in self._local_root._metrics: del self._local_root._metrics[key] @@ -401,7 +401,7 @@ def set_tag(self, key: _TagNameType, value: Any = None) -> None: # Also set the `version` tag to the same value # DEV: Note that we do no return, we want to set both self.set_tag(VERSION_KEY, value) - elif key == SPAN_MEASURED_KEY: + elif key == _SPAN_MEASURED_KEY: # Set `_dd.measured` tag as a metric # DEV: `set_metric` will ensure it is an integer 0 or 1 if value is None: @@ -458,7 +458,7 @@ def set_tags(self, tags: Dict[_TagNameType, Any]) -> None: def set_metric(self, key: _TagNameType, value: NumericType) -> None: """This method sets a numeric tag value for the given key.""" # Enforce a specific constant for `_dd.measured` - if key == SPAN_MEASURED_KEY: + if key == _SPAN_MEASURED_KEY: try: value = int(bool(value)) except (ValueError, TypeError): diff --git a/ddtrace/_trace/trace_handlers.py b/ddtrace/_trace/trace_handlers.py index 7c2ba02d6b4..dab3f743146 100644 --- a/ddtrace/_trace/trace_handlers.py +++ b/ddtrace/_trace/trace_handlers.py @@ -18,11 +18,11 @@ ) from ddtrace._trace.utils_botocore.span_tags import set_botocore_response_metadata_tags from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.botocore.constants import BOTOCORE_STEPFUNCTIONS_INPUT_KEY -from ddtrace.contrib.trace_utils import _set_url_tag +from ddtrace.contrib.internal.trace_utils import _set_url_tag from ddtrace.ext import SpanKind from ddtrace.ext import db from ddtrace.ext import http @@ -334,7 +334,7 @@ def _on_request_span_modifier( # RequestContext` and possibly a url rule span.resource = " ".join((request.method, request.path)) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) # set analytics sample rate with global config enabled sample_rate = flask_config.get_analytics_sample_rate(use_global_config=True) if sample_rate is not None: @@ -366,7 +366,7 @@ def _on_request_span_modifier_post(ctx, flask_config, request, req_body): def _on_traced_get_response_pre(_, ctx: core.ExecutionContext, request, before_request_tags): before_request_tags(ctx["pin"], ctx.span, request) - ctx.span._metrics[SPAN_MEASURED_KEY] = 1 + ctx.span._metrics[_SPAN_MEASURED_KEY] = 1 def _on_django_finalize_response_pre(ctx, after_request_tags, request, response): diff --git a/ddtrace/_trace/tracer.py b/ddtrace/_trace/tracer.py index 84b9252930a..6e595bbe7c1 100644 --- a/ddtrace/_trace/tracer.py +++ b/ddtrace/_trace/tracer.py @@ -30,8 +30,8 @@ from ddtrace._trace.sampler import DatadogSampler from ddtrace._trace.span import Span from ddtrace.appsec._constants import APPSEC +from ddtrace.constants import _HOSTNAME_KEY from ddtrace.constants import ENV_KEY -from ddtrace.constants import HOSTNAME_KEY from ddtrace.constants import PID from ddtrace.constants import VERSION_KEY from ddtrace.internal import agent @@ -966,7 +966,7 @@ def _start_span( on_finish=[self._on_span_finish], ) if config._report_hostname: - span.set_tag_str(HOSTNAME_KEY, hostname.get_hostname()) + span.set_tag_str(_HOSTNAME_KEY, hostname.get_hostname()) if not span._parent: span.set_tag_str("runtime-id", get_runtime_id()) diff --git a/ddtrace/_trace/utils_botocore/span_tags.py b/ddtrace/_trace/utils_botocore/span_tags.py index 5394c2b397a..f90cbd60a51 100644 --- a/ddtrace/_trace/utils_botocore/span_tags.py +++ b/ddtrace/_trace/utils_botocore/span_tags.py @@ -7,8 +7,8 @@ from ddtrace import config from ddtrace._trace.utils_botocore.aws_payload_tagging import AWSPayloadTagging from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.ext import SpanKind from ddtrace.ext import aws from ddtrace.ext import http @@ -23,7 +23,7 @@ def set_botocore_patched_api_call_span_tags(span: Span, instance, args, params, span.set_tag_str(COMPONENT, config.botocore.integration_name) # set span.kind to the type of request being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) if args: # DEV: join is the fastest way of concatenating strings that is compatible diff --git a/ddtrace/_trace/utils_redis.py b/ddtrace/_trace/utils_redis.py index 0ea1388e255..fde9c291cb5 100644 --- a/ddtrace/_trace/utils_redis.py +++ b/ddtrace/_trace/utils_redis.py @@ -7,10 +7,10 @@ from typing import Optional from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils -from ddtrace.contrib.redis_utils import _extract_conn_tags +from ddtrace.contrib.internal.redis_utils import _extract_conn_tags from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -30,7 +30,7 @@ def _set_span_tags( span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) span.set_tag_str(COMPONENT, config_integration.integration_name) span.set_tag_str(db.SYSTEM, redisx.APP) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) if query is not None: span_name = schematize_cache_operation(redisx.RAWCMD, cache_provider=redisx.APP) # type: ignore[operator] span.set_tag_str(span_name, query) diff --git a/ddtrace/appsec/_deduplications.py b/ddtrace/appsec/_deduplications.py index bef013433d5..c4c669b4991 100644 --- a/ddtrace/appsec/_deduplications.py +++ b/ddtrace/appsec/_deduplications.py @@ -28,9 +28,12 @@ def _reset_cache(self): """ self.reported_logs.clear() + def _check_deduplication(self): + return asm_config._asm_deduplication_enabled + def __call__(self, *args, **kwargs): result = None - if asm_config._deduplication_enabled: + if self._check_deduplication(): raw_log_hash = hash("".join([str(arg) for arg in self._extract(args)])) last_reported_timestamp = self.reported_logs.get(raw_log_hash, M_INF) + self._time_lapse current = monotonic() diff --git a/ddtrace/appsec/_handlers.py b/ddtrace/appsec/_handlers.py index 42464f9fe40..d34b2da3517 100644 --- a/ddtrace/appsec/_handlers.py +++ b/ddtrace/appsec/_handlers.py @@ -6,8 +6,8 @@ from ddtrace.appsec._asm_request_context import get_blocked from ddtrace.appsec._constants import SPAN_DATA_NAMES from ddtrace.contrib import trace_utils -from ddtrace.contrib.trace_utils import _get_request_header_user_agent -from ddtrace.contrib.trace_utils import _set_url_tag +from ddtrace.contrib.internal.trace_utils import _get_request_header_user_agent +from ddtrace.contrib.internal.trace_utils import _set_url_tag from ddtrace.ext import SpanTypes from ddtrace.ext import http from ddtrace.internal import core diff --git a/ddtrace/appsec/_iast/_ast/ast_patching.py b/ddtrace/appsec/_iast/_ast/ast_patching.py index bb3a9c74d44..44bef969bff 100644 --- a/ddtrace/appsec/_iast/_ast/ast_patching.py +++ b/ddtrace/appsec/_iast/_ast/ast_patching.py @@ -33,6 +33,7 @@ "beautifulsoup4.", "cachetools.", "cryptography.", + "django.", "docutils.", "idna.", "iniconfig.", diff --git a/ddtrace/appsec/_iast/_handlers.py b/ddtrace/appsec/_iast/_handlers.py index e873d8a6a5a..4d4628d910e 100644 --- a/ddtrace/appsec/_iast/_handlers.py +++ b/ddtrace/appsec/_iast/_handlers.py @@ -5,7 +5,8 @@ from wrapt import wrap_function_wrapper as _w from ddtrace.appsec._iast import _is_iast_enabled -from ddtrace.appsec._iast._iast_request_context import in_iast_context +from ddtrace.appsec._iast._iast_request_context import get_iast_stacktrace_reported +from ddtrace.appsec._iast._iast_request_context import set_iast_stacktrace_reported from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_source from ddtrace.appsec._iast._patch import _iast_instrument_starlette_request from ddtrace.appsec._iast._patch import _iast_instrument_starlette_request_body @@ -56,7 +57,7 @@ def _on_set_http_meta_iast( def _on_request_init(wrapped, instance, args, kwargs): wrapped(*args, **kwargs) - if _is_iast_enabled() and in_iast_context(): + if _is_iast_enabled() and is_iast_request_enabled(): try: instance.query_string = taint_pyobject( pyobject=instance.query_string, @@ -105,9 +106,6 @@ def _on_flask_patch(flask_version): _set_metric_iast_instrumented_source(OriginType.PATH) _set_metric_iast_instrumented_source(OriginType.QUERY) - # Instrumented on _ddtrace.appsec._asm_request_context._on_wrapped_view - _set_metric_iast_instrumented_source(OriginType.PATH_PARAMETER) - try_wrap_function_wrapper( "werkzeug.wrappers.request", "Request.get_data", @@ -129,9 +127,17 @@ def _on_flask_patch(flask_version): ) _set_metric_iast_instrumented_source(OriginType.QUERY) + # Instrumented on _ddtrace.appsec._asm_request_context._on_wrapped_view + _set_metric_iast_instrumented_source(OriginType.PATH_PARAMETER) + + # Instrumented on _on_set_request_tags_iast + _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) + _set_metric_iast_instrumented_source(OriginType.COOKIE) + _set_metric_iast_instrumented_source(OriginType.PARAMETER_NAME) + def _on_wsgi_environ(wrapped, _instance, args, kwargs): - if _is_iast_enabled() and args and in_iast_context(): + if _is_iast_enabled() and args and is_iast_request_enabled(): return wrapped(*((taint_structure(args[0], OriginType.HEADER_NAME, OriginType.HEADER),) + args[1:]), **kwargs) return wrapped(*args, **kwargs) @@ -140,6 +146,13 @@ def _on_wsgi_environ(wrapped, _instance, args, kwargs): def _on_django_patch(): if _is_iast_enabled(): try: + when_imported("django.http.request")( + lambda m: try_wrap_function_wrapper( + m, + "QueryDict.__getitem__", + functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), + ) + ) # we instrument those sources on _on_django_func_wrapped _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) _set_metric_iast_instrumented_source(OriginType.HEADER) @@ -150,13 +163,7 @@ def _on_django_patch(): _set_metric_iast_instrumented_source(OriginType.PARAMETER) _set_metric_iast_instrumented_source(OriginType.PARAMETER_NAME) _set_metric_iast_instrumented_source(OriginType.BODY) - when_imported("django.http.request")( - lambda m: try_wrap_function_wrapper( - m, - "QueryDict.__getitem__", - functools.partial(if_iast_taint_returned_object_for, OriginType.PARAMETER), - ) - ) + except Exception: log.debug("Unexpected exception while patch IAST functions", exc_info=True) @@ -165,7 +172,7 @@ def _on_django_func_wrapped(fn_args, fn_kwargs, first_arg_expected_type, *_): # If IAST is enabled, and we're wrapping a Django view call, taint the kwargs (view's # path parameters) if _is_iast_enabled() and fn_args and isinstance(fn_args[0], first_arg_expected_type): - if not in_iast_context(): + if not is_iast_request_enabled(): return http_req = fn_args[0] @@ -278,18 +285,16 @@ def _on_grpc_response(message): def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs): - if _is_iast_enabled(): - if not is_iast_request_enabled(): - for key, value in wrapped(*args, **kwargs): - yield key, value - else: + if _is_iast_enabled() and is_iast_request_enabled(): + try: for key, value in wrapped(*args, **kwargs): new_key = taint_pyobject(pyobject=key, source_name=key, source_value=key, source_origin=origins[0]) new_value = taint_pyobject( pyobject=value, source_name=key, source_value=value, source_origin=origins[1] ) yield new_key, new_value - + except Exception: + log.debug("Unexpected exception while tainting pyobject", exc_info=True) else: for key, value in wrapped(*args, **kwargs): yield key, value @@ -297,7 +302,6 @@ def if_iast_taint_yield_tuple_for(origins, wrapped, instance, args, kwargs): def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs): value = wrapped(*args, **kwargs) - if _is_iast_enabled() and is_iast_request_enabled(): try: if not is_pyobject_tainted(value): @@ -310,6 +314,29 @@ def if_iast_taint_returned_object_for(origin, wrapped, instance, args, kwargs): return value +def if_iast_taint_starlette_datastructures(origin, wrapped, instance, args, kwargs): + value = wrapped(*args, **kwargs) + if _is_iast_enabled() and is_iast_request_enabled(): + try: + res = [] + for element in value: + if not is_pyobject_tainted(element): + res.append( + taint_pyobject( + pyobject=element, + source_name=element, + source_value=element, + source_origin=origin, + ) + ) + else: + res.append(element) + return res + except Exception: + log.debug("Unexpected exception while tainting pyobject", exc_info=True) + return value + + def _on_iast_fastapi_patch(): # Cookies sources try_wrap_function_wrapper( @@ -333,6 +360,13 @@ def _on_iast_fastapi_patch(): ) _set_metric_iast_instrumented_source(OriginType.PARAMETER) + try_wrap_function_wrapper( + "starlette.datastructures", + "QueryParams.keys", + functools.partial(if_iast_taint_starlette_datastructures, OriginType.PARAMETER_NAME), + ) + _set_metric_iast_instrumented_source(OriginType.PARAMETER_NAME) + # Header sources try_wrap_function_wrapper( "starlette.datastructures", @@ -346,6 +380,13 @@ def _on_iast_fastapi_patch(): ) _set_metric_iast_instrumented_source(OriginType.HEADER) + try_wrap_function_wrapper( + "starlette.datastructures", + "Headers.keys", + functools.partial(if_iast_taint_starlette_datastructures, OriginType.HEADER_NAME), + ) + _set_metric_iast_instrumented_source(OriginType.HEADER_NAME) + # Path source try_wrap_function_wrapper("starlette.datastructures", "URL.__init__", _iast_instrument_starlette_url) _set_metric_iast_instrumented_source(OriginType.PATH) @@ -363,6 +404,12 @@ def _on_iast_fastapi_patch(): "FormData.get", functools.partial(if_iast_taint_returned_object_for, OriginType.BODY), ) + try_wrap_function_wrapper( + "starlette.datastructures", + "FormData.keys", + functools.partial(if_iast_taint_starlette_datastructures, OriginType.PARAMETER_NAME), + ) + _set_metric_iast_instrumented_source(OriginType.BODY) # Instrumented on _iast_starlette_scope_taint @@ -375,14 +422,7 @@ def _on_pre_tracedrequest_iast(ctx): def _on_set_request_tags_iast(request, span, flask_config): - if _is_iast_enabled(): - _set_metric_iast_instrumented_source(OriginType.COOKIE_NAME) - _set_metric_iast_instrumented_source(OriginType.COOKIE) - _set_metric_iast_instrumented_source(OriginType.PARAMETER_NAME) - - if not is_iast_request_enabled(): - return - + if _is_iast_enabled() and is_iast_request_enabled(): request.cookies = taint_structure( request.cookies, OriginType.COOKIE_NAME, @@ -403,3 +443,69 @@ def _on_set_request_tags_iast(request, span, flask_config): OriginType.PARAMETER, override_pyobject_tainted=True, ) + + +def _on_django_finalize_response_pre(ctx, after_request_tags, request, response): + if not response or not _is_iast_enabled() or not is_iast_request_enabled() or get_iast_stacktrace_reported(): + return + + try: + from .taint_sinks.stacktrace_leak import asm_check_stacktrace_leak + + content = response.content.decode("utf-8", errors="ignore") + asm_check_stacktrace_leak(content) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak", exc_info=True) + + +def _on_django_technical_500_response(request, response, exc_type, exc_value, tb): + if not exc_value or not _is_iast_enabled() or not is_iast_request_enabled(): + return + + try: + from .taint_sinks.stacktrace_leak import asm_report_stacktrace_leak_from_django_debug_page + + exc_name = exc_type.__name__ + module = tb.tb_frame.f_globals.get("__name__", "") + asm_report_stacktrace_leak_from_django_debug_page(exc_name, module) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak on 500 response view", exc_info=True) + + +def _on_flask_finalize_request_post(response, _): + if not response or not _is_iast_enabled() or not is_iast_request_enabled() or get_iast_stacktrace_reported(): + return + + try: + from .taint_sinks.stacktrace_leak import asm_check_stacktrace_leak + + content = response[0].decode("utf-8", errors="ignore") + asm_check_stacktrace_leak(content) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak", exc_info=True) + + +def _on_asgi_finalize_response(body, _): + if not body or not _is_iast_enabled() or not is_iast_request_enabled(): + return + + try: + from .taint_sinks.stacktrace_leak import asm_check_stacktrace_leak + + content = body.decode("utf-8", errors="ignore") + asm_check_stacktrace_leak(content) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak", exc_info=True) + + +def _on_werkzeug_render_debugger_html(html): + if not html or not _is_iast_enabled() or not is_iast_request_enabled(): + return + + try: + from .taint_sinks.stacktrace_leak import asm_check_stacktrace_leak + + asm_check_stacktrace_leak(html) + set_iast_stacktrace_reported(True) + except Exception: + log.debug("Unexpected exception checking for stacktrace leak", exc_info=True) diff --git a/ddtrace/appsec/_iast/_iast_request_context.py b/ddtrace/appsec/_iast/_iast_request_context.py index 07ad4c9c238..e9c985d0b3d 100644 --- a/ddtrace/appsec/_iast/_iast_request_context.py +++ b/ddtrace/appsec/_iast/_iast_request_context.py @@ -14,7 +14,7 @@ from ddtrace.appsec._iast._taint_tracking._context import create_context as create_propagation_context from ddtrace.appsec._iast._taint_tracking._context import reset_context as reset_propagation_context from ddtrace.appsec._iast.reporter import IastSpanReporter -from ddtrace.constants import ORIGIN_KEY +from ddtrace.constants import _ORIGIN_KEY from ddtrace.internal import core from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.formats import asbool @@ -46,6 +46,7 @@ def __init__(self, span: Optional[Span] = None): self.iast_reporter: Optional[IastSpanReporter] = None self.iast_span_metrics: Dict[str, int] = {} self.iast_stack_trace_id: int = 0 + self.iast_stack_trace_reported: bool = False def _get_iast_context() -> Optional[IASTEnvironment]: @@ -88,6 +89,19 @@ def get_iast_reporter() -> Optional[IastSpanReporter]: return None +def get_iast_stacktrace_reported() -> bool: + env = _get_iast_context() + if env: + return env.iast_stack_trace_reported + return False + + +def set_iast_stacktrace_reported(reported: bool) -> None: + env = _get_iast_context() + if env: + env.iast_stack_trace_reported = reported + + def get_iast_stacktrace_id() -> int: env = _get_iast_context() if env: @@ -133,8 +147,8 @@ def _create_and_attach_iast_report_to_span(req_span: Span, existing_data: Option set_iast_request_enabled(False) end_iast_context(req_span) - if req_span.get_tag(ORIGIN_KEY) is None: - req_span.set_tag_str(ORIGIN_KEY, APPSEC.ORIGIN_VALUE) + if req_span.get_tag(_ORIGIN_KEY) is None: + req_span.set_tag_str(_ORIGIN_KEY, APPSEC.ORIGIN_VALUE) oce.release_request() diff --git a/ddtrace/appsec/_iast/_listener.py b/ddtrace/appsec/_iast/_listener.py index 953e22b1288..37f721438d6 100644 --- a/ddtrace/appsec/_iast/_listener.py +++ b/ddtrace/appsec/_iast/_listener.py @@ -1,11 +1,16 @@ +from ddtrace.appsec._iast._handlers import _on_asgi_finalize_response +from ddtrace.appsec._iast._handlers import _on_django_finalize_response_pre from ddtrace.appsec._iast._handlers import _on_django_func_wrapped from ddtrace.appsec._iast._handlers import _on_django_patch +from ddtrace.appsec._iast._handlers import _on_django_technical_500_response +from ddtrace.appsec._iast._handlers import _on_flask_finalize_request_post from ddtrace.appsec._iast._handlers import _on_flask_patch from ddtrace.appsec._iast._handlers import _on_grpc_response from ddtrace.appsec._iast._handlers import _on_pre_tracedrequest_iast from ddtrace.appsec._iast._handlers import _on_request_init from ddtrace.appsec._iast._handlers import _on_set_http_meta_iast from ddtrace.appsec._iast._handlers import _on_set_request_tags_iast +from ddtrace.appsec._iast._handlers import _on_werkzeug_render_debugger_html from ddtrace.appsec._iast._handlers import _on_wsgi_environ from ddtrace.appsec._iast._iast_request_context import _iast_end_request from ddtrace.internal import core @@ -18,11 +23,16 @@ def iast_listen(): core.on("set_http_meta_for_asm", _on_set_http_meta_iast) core.on("django.patch", _on_django_patch) core.on("django.wsgi_environ", _on_wsgi_environ, "wrapped_result") + core.on("django.finalize_response.pre", _on_django_finalize_response_pre) core.on("django.func.wrapped", _on_django_func_wrapped) + core.on("django.technical_500_response", _on_django_technical_500_response) core.on("flask.patch", _on_flask_patch) core.on("flask.request_init", _on_request_init) core.on("flask._patched_request", _on_pre_tracedrequest_iast) core.on("flask.set_request_tags", _on_set_request_tags_iast) + core.on("flask.finalize_request.post", _on_flask_finalize_request_post) + core.on("asgi.finalize_response", _on_asgi_finalize_response) + core.on("werkzeug.render_debugger_html", _on_werkzeug_render_debugger_html) core.on("context.ended.wsgi.__call__", _iast_end_request) core.on("context.ended.asgi.__call__", _iast_end_request) diff --git a/ddtrace/appsec/_iast/constants.py b/ddtrace/appsec/_iast/constants.py index 55ec5a5e740..83284094dcf 100644 --- a/ddtrace/appsec/_iast/constants.py +++ b/ddtrace/appsec/_iast/constants.py @@ -1,3 +1,4 @@ +import re from typing import Any from typing import Dict @@ -14,6 +15,7 @@ VULN_HEADER_INJECTION = "HEADER_INJECTION" VULN_CODE_INJECTION = "CODE_INJECTION" VULN_SSRF = "SSRF" +VULN_STACKTRACE_LEAK = "STACKTRACE_LEAK" VULNERABILITY_TOKEN_TYPE = Dict[int, Dict[str, Any]] @@ -27,6 +29,12 @@ RC2_DEF = "rc2" RC4_DEF = "rc4" IDEA_DEF = "idea" +STACKTRACE_RE_DETECT = re.compile(r"Traceback \(most recent call last\):") +HTML_TAGS_REMOVE = re.compile(r"|<[^>]*>|&#\w+;") +STACKTRACE_FILE_LINE = re.compile(r"File (.*?), line (\d+), in (.+)") +STACKTRACE_EXCEPTION_REGEX = re.compile( + r"^(?P[A-Za-z_]\w*(?:Error|Exception|Interrupt|Fault|Warning))" r"(?:\s*:\s*(?P.*))?$" +) DEFAULT_WEAK_HASH_ALGORITHMS = {MD5_DEF, SHA1_DEF} diff --git a/ddtrace/appsec/_iast/taint_sinks/_base.py b/ddtrace/appsec/_iast/taint_sinks/_base.py index 543246581cd..a934c8c5788 100644 --- a/ddtrace/appsec/_iast/taint_sinks/_base.py +++ b/ddtrace/appsec/_iast/taint_sinks/_base.py @@ -5,10 +5,11 @@ from typing import Text from ddtrace import tracer +from ddtrace.appsec._deduplications import deduplication from ddtrace.appsec._trace_utils import _asm_manual_keep from ddtrace.internal.logger import get_logger +from ddtrace.settings.asm import config as asm_config -from ..._deduplications import deduplication from .._iast_request_context import get_iast_reporter from .._iast_request_context import is_iast_request_enabled from .._iast_request_context import set_iast_reporter @@ -27,6 +28,9 @@ class taint_sink_deduplication(deduplication): + def _check_deduplication(self): + return asm_config._iast_deduplication_enabled + def _extract(self, args): # We skip positions 0 and 1 because they represent the 'cls' and 'span' respectively return args[2:] diff --git a/ddtrace/appsec/_iast/taint_sinks/stacktrace_leak.py b/ddtrace/appsec/_iast/taint_sinks/stacktrace_leak.py new file mode 100644 index 00000000000..2fa9a0016a9 --- /dev/null +++ b/ddtrace/appsec/_iast/taint_sinks/stacktrace_leak.py @@ -0,0 +1,102 @@ +import os +import re + +from ..._constants import IAST_SPAN_TAGS +from .. import oce +from .._iast_request_context import set_iast_stacktrace_reported +from .._metrics import _set_metric_iast_executed_sink +from .._metrics import increment_iast_span_metric +from .._taint_tracking._errors import iast_taint_log_error +from ..constants import HTML_TAGS_REMOVE +from ..constants import STACKTRACE_EXCEPTION_REGEX +from ..constants import STACKTRACE_FILE_LINE +from ..constants import VULN_STACKTRACE_LEAK +from ..taint_sinks._base import VulnerabilityBase + + +@oce.register +class StacktraceLeak(VulnerabilityBase): + vulnerability_type = VULN_STACKTRACE_LEAK + skip_location = True + + +def asm_report_stacktrace_leak_from_django_debug_page(exc_name, module): + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, StacktraceLeak.vulnerability_type) + _set_metric_iast_executed_sink(StacktraceLeak.vulnerability_type) + evidence = "Module: %s\nException: %s" % (module, exc_name) + StacktraceLeak.report(evidence_value=evidence) + set_iast_stacktrace_reported(True) + + +def asm_check_stacktrace_leak(content: str) -> None: + if not content: + return + + try: + # Quick check to avoid the slower operations if on stacktrace + if "Traceback (most recent call last):" not in content: + return + + text = HTML_TAGS_REMOVE.sub("", content) + lines = [line.strip() for line in text.splitlines() if line.strip()] + + file_lines = [] + exception_line = "" + + for i, line in enumerate(lines): + if line.startswith("Traceback (most recent call last):"): + # from here until we find an exception line + continue + + # See if this line is a "File ..." line + m_file = STACKTRACE_FILE_LINE.match(line) + if m_file: + file_lines.append(m_file.groups()) + continue + + # See if this line might be the exception line + m_exc = STACKTRACE_EXCEPTION_REGEX.match(line) + if m_exc: + # We consider it as the "final" exception line. Keep it. + exception_line = m_exc.group("exc") + # We won't break immediately because sometimes Django + # HTML stack traces can have repeated exception lines, etc. + # But typically the last match is the real final exception + # We'll keep updating exception_line if we see multiple + continue + + if not file_lines and not exception_line: + return + + module_path = None + if file_lines: + # file_lines looks like [ ("/path/to/file.py", "line_no", "funcname"), ... ] + last_file_entry = file_lines[-1] + module_path = last_file_entry[0] # the path in quotes + + # Attempt to convert a path like "/myproj/foo/bar.py" into "foo.bar" + # or "myproj.foo.bar" depending on your directory structure. + # This is a *best effort* approach (it can be environment-specific). + module_name = "" + if module_path: + mod_no_ext = re.sub(r"\.py$", "", module_path) + parts: list[str] = [] + while True: + head, tail = os.path.split(mod_no_ext) + if tail: + parts.insert(0, tail) + mod_no_ext = head + else: + # might still have a leftover 'head' if it’s not just root + break + + module_name = ".".join(parts) + if not module_name: + module_name = module_path # fallback: just the path + + increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, StacktraceLeak.vulnerability_type) + _set_metric_iast_executed_sink(StacktraceLeak.vulnerability_type) + evidence = "Module: %s\nException: %s" % (module_name.strip(), exception_line.strip()) + StacktraceLeak.report(evidence_value=evidence) + except Exception as e: + iast_taint_log_error("[IAST] error in check stacktrace leak. {}".format(e)) diff --git a/ddtrace/appsec/_processor.py b/ddtrace/appsec/_processor.py index fb18c268fdb..6102ba1ded2 100644 --- a/ddtrace/appsec/_processor.py +++ b/ddtrace/appsec/_processor.py @@ -27,8 +27,8 @@ from ddtrace.appsec._exploit_prevention.stack_traces import report_stack from ddtrace.appsec._trace_utils import _asm_manual_keep from ddtrace.appsec._utils import has_triggers -from ddtrace.constants import ORIGIN_KEY -from ddtrace.constants import RUNTIME_FAMILY +from ddtrace.constants import _ORIGIN_KEY +from ddtrace.constants import _RUNTIME_FAMILY from ddtrace.ext import SpanTypes from ddtrace.internal import core from ddtrace.internal._unpatched import unpatched_open as open # noqa: A001 @@ -101,7 +101,7 @@ def get_rules() -> str: def _set_headers(span: Span, headers: Any, kind: str, only_asm_enabled: bool = False) -> None: - from ddtrace.contrib.trace_utils import _normalize_tag_name + from ddtrace.contrib.internal.trace_utils import _normalize_tag_name for k in headers: if isinstance(k, tuple): @@ -218,7 +218,7 @@ def rasp_sqli_enabled(self) -> bool: return WAF_DATA_NAMES.SQLI_ADDRESS in self._addresses_to_keep def on_span_start(self, span: Span) -> None: - from ddtrace.contrib import trace_utils + from ddtrace.contrib.internal import trace_utils if not hasattr(self, "_ddwaf"): self.delayed_init() @@ -235,7 +235,7 @@ def on_span_start(self, span: Span) -> None: headers_case_sensitive = _asm_request_context.get_headers_case_sensitive() span.set_metric(APPSEC.ENABLED, 1.0) - span.set_tag_str(RUNTIME_FAMILY, "python") + span.set_tag_str(_RUNTIME_FAMILY, "python") def waf_callable(custom_data=None, **kwargs): return self._waf_action(span._local_root or span, ctx, custom_data, **kwargs) @@ -391,8 +391,8 @@ def _waf_action( # Right now, we overwrite any value that could be already there. We need to reconsider when ASM/AppSec's # specs are updated. _asm_manual_keep(span) - if span.get_tag(ORIGIN_KEY) is None: - span.set_tag_str(ORIGIN_KEY, APPSEC.ORIGIN_VALUE) + if span.get_tag(_ORIGIN_KEY) is None: + span.set_tag_str(_ORIGIN_KEY, APPSEC.ORIGIN_VALUE) return waf_results def _is_needed(self, address: str) -> bool: diff --git a/ddtrace/appsec/_remoteconfiguration.py b/ddtrace/appsec/_remoteconfiguration.py index e4785cd5f40..723710d7cc3 100644 --- a/ddtrace/appsec/_remoteconfiguration.py +++ b/ddtrace/appsec/_remoteconfiguration.py @@ -8,7 +8,6 @@ from ddtrace import Tracer from ddtrace.appsec._capabilities import _asm_feature_is_required from ddtrace.appsec._constants import PRODUCTS -from ddtrace.internal import forksafe from ddtrace.internal.logger import get_logger from ddtrace.internal.remoteconfig._connectors import PublisherSubscriberConnector from ddtrace.internal.remoteconfig._publishers import RemoteConfigPublisherMergeDicts @@ -72,7 +71,6 @@ def enable_appsec_rc(test_tracer: Optional[Tracer] = None) -> None: load_common_appsec_modules() - forksafe.register(_forksafe_appsec_rc) telemetry_writer.product_activated(TELEMETRY_APM_PRODUCT.APPSEC, True) diff --git a/ddtrace/appsec/_trace_utils.py b/ddtrace/appsec/_trace_utils.py index 77cb1aaca3a..56f34d81f99 100644 --- a/ddtrace/appsec/_trace_utils.py +++ b/ddtrace/appsec/_trace_utils.py @@ -11,7 +11,7 @@ from ddtrace.appsec._constants import LOGIN_EVENTS_MODE from ddtrace.appsec._constants import WAF_ACTIONS from ddtrace.appsec._utils import _hash_user_id -from ddtrace.contrib.trace_utils import set_user +from ddtrace.contrib.internal.trace_utils import set_user from ddtrace.ext import SpanTypes from ddtrace.ext import user from ddtrace.internal import core diff --git a/ddtrace/appsec/_utils.py b/ddtrace/appsec/_utils.py index bb8739654c5..79f8f8b5311 100644 --- a/ddtrace/appsec/_utils.py +++ b/ddtrace/appsec/_utils.py @@ -22,7 +22,7 @@ def parse_response_body(raw_body): from ddtrace.appsec import _asm_request_context from ddtrace.appsec._constants import SPAN_DATA_NAMES - from ddtrace.contrib.trace_utils import _get_header_value_case_insensitive + from ddtrace.contrib.internal.trace_utils import _get_header_value_case_insensitive if not raw_body: return diff --git a/ddtrace/bootstrap/preload.py b/ddtrace/bootstrap/preload.py index 0018162beaa..95dc8f4cd55 100644 --- a/ddtrace/bootstrap/preload.py +++ b/ddtrace/bootstrap/preload.py @@ -6,7 +6,6 @@ import os # noqa:I001 from ddtrace import config # noqa:F401 -from ddtrace.appsec._iast._utils import _is_iast_enabled from ddtrace.settings.profiling import config as profiling_config # noqa:F401 from ddtrace.internal.logger import get_logger # noqa:F401 from ddtrace.internal.module import ModuleWatchdog # noqa:F401 @@ -15,7 +14,6 @@ from ddtrace.internal.tracemethods import _install_trace_methods # noqa:F401 from ddtrace.internal.utils.formats import asbool # noqa:F401 from ddtrace.internal.utils.formats import parse_tags_str # noqa:F401 -from ddtrace.settings.asm import config as asm_config # noqa:F401 from ddtrace.settings.crashtracker import config as crashtracker_config from ddtrace import tracer @@ -72,21 +70,6 @@ def register_post_preload(func: t.Callable) -> None: if config._runtime_metrics_enabled: RuntimeWorker.enable() -if _is_iast_enabled(): - """ - This is the entry point for the IAST instrumentation. `enable_iast_propagation` is called on patch_all function - too but patch_all depends of DD_TRACE_ENABLED environment variable. This is the reason why we need to call it - here and it's not a duplicate call due to `enable_iast_propagation` has a global variable to avoid multiple calls. - """ - from ddtrace.appsec._iast import enable_iast_propagation - - enable_iast_propagation() - -if asm_config._asm_enabled or config._remote_config_enabled: - from ddtrace.appsec._remoteconfiguration import enable_appsec_rc - - enable_appsec_rc() - if config._otel_enabled: @ModuleWatchdog.after_module_imported("opentelemetry.trace") diff --git a/ddtrace/constants.py b/ddtrace/constants.py index 1a112f677b8..b4694e24345 100644 --- a/ddtrace/constants.py +++ b/ddtrace/constants.py @@ -4,36 +4,36 @@ # TODO: Deprecate and remove the SAMPLE_RATE_METRIC_KEY constant. # This key enables legacy trace sampling support in the Datadog agent. -SAMPLE_RATE_METRIC_KEY = "_sample_rate" -SAMPLING_PRIORITY_KEY = "_sampling_priority_v1" +_SAMPLE_RATE_METRIC_KEY = SAMPLE_RATE_METRIC_KEY = "_sample_rate" +_SAMPLING_PRIORITY_KEY = SAMPLING_PRIORITY_KEY = "_sampling_priority_v1" _ANALYTICS_SAMPLE_RATE_KEY = ANALYTICS_SAMPLE_RATE_KEY = "_dd1.sr.eausr" -SAMPLING_AGENT_DECISION = "_dd.agent_psr" -SAMPLING_RULE_DECISION = "_dd.rule_psr" -SAMPLING_LIMIT_DECISION = "_dd.limit_psr" +_SAMPLING_AGENT_DECISION = SAMPLING_AGENT_DECISION = "_dd.agent_psr" +_SAMPLING_RULE_DECISION = SAMPLING_RULE_DECISION = "_dd.rule_psr" +_SAMPLING_LIMIT_DECISION = SAMPLING_LIMIT_DECISION = "_dd.limit_psr" _SINGLE_SPAN_SAMPLING_MECHANISM = "_dd.span_sampling.mechanism" _SINGLE_SPAN_SAMPLING_RATE = "_dd.span_sampling.rule_rate" _SINGLE_SPAN_SAMPLING_MAX_PER_SEC = "_dd.span_sampling.max_per_second" _SINGLE_SPAN_SAMPLING_MAX_PER_SEC_NO_LIMIT = -1 _APM_ENABLED_METRIC_KEY = "_dd.apm.enabled" -ORIGIN_KEY = "_dd.origin" -USER_ID_KEY = "_dd.p.usr.id" -HOSTNAME_KEY = "_dd.hostname" -RUNTIME_FAMILY = "_dd.runtime_family" +_ORIGIN_KEY = ORIGIN_KEY = "_dd.origin" +_USER_ID_KEY = USER_ID_KEY = "_dd.p.usr.id" +_HOSTNAME_KEY = HOSTNAME_KEY = "_dd.hostname" +_RUNTIME_FAMILY = RUNTIME_FAMILY = "_dd.runtime_family" ENV_KEY = "env" VERSION_KEY = "version" SERVICE_KEY = "service.name" -BASE_SERVICE_KEY = "_dd.base_service" +_BASE_SERVICE_KEY = BASE_SERVICE_KEY = "_dd.base_service" SERVICE_VERSION_KEY = "service.version" SPAN_KIND = "span.kind" -SPAN_MEASURED_KEY = "_dd.measured" -KEEP_SPANS_RATE_KEY = "_dd.tracer_kr" -MULTIPLE_IP_HEADERS = "_dd.multiple-ip-headers" +_SPAN_MEASURED_KEY = SPAN_MEASURED_KEY = "_dd.measured" +_KEEP_SPANS_RATE_KEY = KEEP_SPANS_RATE_KEY = "_dd.tracer_kr" +_MULTIPLE_IP_HEADERS = MULTIPLE_IP_HEADERS = "_dd.multiple-ip-headers" APPSEC_ENV = "DD_APPSEC_ENABLED" -CONFIG_ENDPOINT_ENV = "_DD_CONFIG_ENDPOINT" -CONFIG_ENDPOINT_RETRIES_ENV = "_DD_CONFIG_ENDPOINT_RETRIES" -CONFIG_ENDPOINT_TIMEOUT_ENV = "_DD_CONFIG_ENDPOINT_TIMEOUT" +_CONFIG_ENDPOINT_ENV = CONFIG_ENDPOINT_ENV = "_DD_CONFIG_ENDPOINT" +_CONFIG_ENDPOINT_RETRIES_ENV = CONFIG_ENDPOINT_RETRIES_ENV = "_DD_CONFIG_ENDPOINT_RETRIES" +_CONFIG_ENDPOINT_TIMEOUT_ENV = CONFIG_ENDPOINT_TIMEOUT_ENV = "_DD_CONFIG_ENDPOINT_TIMEOUT" IAST_ENV = "DD_IAST_ENABLED" MANUAL_DROP_KEY = "manual.drop" @@ -57,6 +57,22 @@ _DEPRECATED_MODULE_ATTRIBUTES = [ "ANALYTICS_SAMPLE_RATE_KEY", + "SAMPLE_RATE_METRIC_KEY", + "SAMPLING_PRIORITY_KEY", + "SAMPLING_AGENT_DECISION", + "SAMPLING_RULE_DECISION", + "SAMPLING_LIMIT_DECISION", + "USER_ID_KEY", + "ORIGIN_KEY", + "HOSTNAME_KEY", + "RUNTIME_FAMILY", + "BASE_SERVICE_KEY", + "SPAN_MEASURED_KEY", + "KEEP_SPANS_RATE_KEY", + "MULTIPLE_IP_HEADERS", + "CONFIG_ENDPOINT_ENV", + "CONFIG_ENDPOINT_RETRIES_ENV", + "CONFIG_ENDPOINT_TIMEOUT_ENV", ] diff --git a/ddtrace/contrib/__init__.py b/ddtrace/contrib/__init__.py index 1c75b9d0e44..cb5b2c1de09 100644 --- a/ddtrace/contrib/__init__.py +++ b/ddtrace/contrib/__init__.py @@ -1,4 +1,73 @@ from ddtrace._trace import trace_handlers # noqa:F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.internal.utils.importlib import func_name # noqa:F401 from ddtrace.internal.utils.importlib import module_name # noqa:F401 from ddtrace.internal.utils.importlib import require_modules # noqa:F401 +from ddtrace.vendor.debtcollector import deprecate + + +def __getattr__(name): + if name in ( + "aiohttp", + "asgi", + "bottle", + "celery", + "cherrypy", + "falcon", + "flask_cache", + "pylibmc", + "pyramid", + "requests", + "sqlalchemy", + "wsgi", + "trace_utils", + "internal", + ): + # following packages/modules are not deprecated and will not be removed in 3.0 + pass + elif name in ("trace_handlers", "func_name", "module_name", "require_modules"): + # the following attributes are exposed in ddtrace.contrib.__init__ and should be + # removed in v3.0 + deprecate( + ("ddtrace.contrib.%s is deprecated" % name), + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + elif name in ("aiobotocore", "httplib", "kombu", "snowflake", "sqlalchemy", "tornado", "urllib3"): + # following integrations are not enabled by default and require a unique deprecation message + deprecate( + f"ddtrace.contrib.{name} is deprecated", + message="Avoid using this package directly. " + f"Set DD_TRACE_{name.upper()}_ENABLED=true and use ``ddtrace.auto`` or the " + "``ddtrace-run`` command to enable and configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + elif name in ("redis_utils", "trace_utils_redis", "trace_utils_async"): + deprecate( + f"The ddtrace.contrib.{name} module is deprecated", + message="Import from ``ddtrace.contrib.trace_utils`` instead.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + elif name == "flask_login": + deprecate( + """The flask_login integration is deprecated and will be deleted. + We recommend customers to switch to manual instrumentation. + https://docs.datadoghq.com/security/application_security/threats/add-user-info/?tab=loginsuccess&code-lang=python#adding-business-logic-information-login-success-login-failure-any-business-logic-to-traces + """, + message="", + category=DDTraceDeprecationWarning, + ) + else: + deprecate( + f"ddtrace.contrib.{name} is deprecated", + message="Avoid using this package directly. " + f"Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to enable and configure {name}.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) diff --git a/ddtrace/contrib/aiobotocore/__init__.py b/ddtrace/contrib/aiobotocore/__init__.py index 47f16f51796..5c4527d11f3 100644 --- a/ddtrace/contrib/aiobotocore/__init__.py +++ b/ddtrace/contrib/aiobotocore/__init__.py @@ -35,9 +35,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.aiobotocore.patch import get_version -from ddtrace.contrib.internal.aiobotocore.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.aiobotocore.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.aiobotocore.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/aiohttp/__init__.py b/ddtrace/contrib/aiohttp/__init__.py index 2bd88e734de..3075ca744fa 100644 --- a/ddtrace/contrib/aiohttp/__init__.py +++ b/ddtrace/contrib/aiohttp/__init__.py @@ -94,11 +94,25 @@ async def home_handler(request): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 from ddtrace.contrib.internal.aiohttp.middlewares import trace_app -from ddtrace.contrib.internal.aiohttp.patch import get_version +from ddtrace.contrib.internal.aiohttp.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.aiohttp.patch import patch # noqa: F401 +from ddtrace.contrib.internal.aiohttp.patch import unpatch # noqa: F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate -# Expose public methods -from ddtrace.contrib.internal.aiohttp.patch import patch -from ddtrace.contrib.internal.aiohttp.patch import unpatch +def __getattr__(name): + if name in ("patch", "get_version", "unpatch"): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) -__all__ = ["patch", "unpatch", "trace_app", "get_version"] + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) + + +__all__ = ["trace_app"] diff --git a/ddtrace/contrib/aiohttp_jinja2/__init__.py b/ddtrace/contrib/aiohttp_jinja2/__init__.py index ef9c76f3da8..90833b19484 100644 --- a/ddtrace/contrib/aiohttp_jinja2/__init__.py +++ b/ddtrace/contrib/aiohttp_jinja2/__init__.py @@ -23,9 +23,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.aiohttp_jinja2.patch import get_version -from ddtrace.contrib.internal.aiohttp_jinja2.patch import patch -from ddtrace.contrib.internal.aiohttp_jinja2.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.aiohttp_jinja2.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.aiohttp_jinja2.patch import patch # noqa: F401 +from ddtrace.contrib.internal.aiohttp_jinja2.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/aiomysql/__init__.py b/ddtrace/contrib/aiomysql/__init__.py index 06cd9987d81..49926545da7 100644 --- a/ddtrace/contrib/aiomysql/__init__.py +++ b/ddtrace/contrib/aiomysql/__init__.py @@ -45,9 +45,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.aiomysql.patch import get_version -from ddtrace.contrib.internal.aiomysql.patch import patch -from ddtrace.contrib.internal.aiomysql.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.aiomysql.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.aiomysql.patch import patch # noqa: F401 +from ddtrace.contrib.internal.aiomysql.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/aiopg/__init__.py b/ddtrace/contrib/aiopg/__init__.py index c4cd51fdaa2..de5dcf490e2 100644 --- a/ddtrace/contrib/aiopg/__init__.py +++ b/ddtrace/contrib/aiopg/__init__.py @@ -26,8 +26,5 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.aiopg.patch import get_version -from ddtrace.contrib.internal.aiopg.patch import patch - - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.aiopg.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.aiopg.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/aioredis/__init__.py b/ddtrace/contrib/aioredis/__init__.py index b390185d48d..3dd70747723 100644 --- a/ddtrace/contrib/aioredis/__init__.py +++ b/ddtrace/contrib/aioredis/__init__.py @@ -1,12 +1,3 @@ -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate - - -deprecate( - "The aioredis integration is deprecated.", - message="Please use the redis integration with redis>=4.2.0 instead.", - category=DDTraceDeprecationWarning, -) """ The aioredis integration instruments aioredis requests. Version 1.3 and above are fully supported. @@ -75,11 +66,8 @@ with _w.catch_warnings(): _w.simplefilter("ignore", DeprecationWarning) # Required to allow users to import from `ddtrace.contrib.aioredis.patch` directly - from . import patch as _ # noqa: F401, I001 - -from ddtrace.contrib.internal.aioredis.patch import get_version # noqa: E402 -from ddtrace.contrib.internal.aioredis.patch import patch # noqa: E402 -from ddtrace.contrib.internal.aioredis.patch import unpatch # noqa: E402 - + from . import patch as _ # noqa: I001,F401 -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.aioredis.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.aioredis.patch import patch # noqa: F401 +from ddtrace.contrib.internal.aioredis.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/algoliasearch/__init__.py b/ddtrace/contrib/algoliasearch/__init__.py index b594d6238cc..8c1034951e8 100644 --- a/ddtrace/contrib/algoliasearch/__init__.py +++ b/ddtrace/contrib/algoliasearch/__init__.py @@ -31,9 +31,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.algoliasearch.patch import get_version -from ddtrace.contrib.internal.algoliasearch.patch import patch -from ddtrace.contrib.internal.algoliasearch.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.algoliasearch.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.algoliasearch.patch import patch # noqa: F401 +from ddtrace.contrib.internal.algoliasearch.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/anthropic/__init__.py b/ddtrace/contrib/anthropic/__init__.py index f2d8ea8f353..333123c2ac8 100644 --- a/ddtrace/contrib/anthropic/__init__.py +++ b/ddtrace/contrib/anthropic/__init__.py @@ -91,9 +91,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.anthropic.patch import get_version -from ddtrace.contrib.internal.anthropic.patch import patch -from ddtrace.contrib.internal.anthropic.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.anthropic.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.anthropic.patch import patch # noqa: F401 +from ddtrace.contrib.internal.anthropic.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/aredis/__init__.py b/ddtrace/contrib/aredis/__init__.py index 1d651b9c616..460734a13f9 100644 --- a/ddtrace/contrib/aredis/__init__.py +++ b/ddtrace/contrib/aredis/__init__.py @@ -75,8 +75,5 @@ async def example(): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.aredis.patch import get_version -from ddtrace.contrib.internal.aredis.patch import patch - - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.aredis.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.aredis.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/asgi/__init__.py b/ddtrace/contrib/asgi/__init__.py index 8c30f4e609e..36c67f1f4d0 100644 --- a/ddtrace/contrib/asgi/__init__.py +++ b/ddtrace/contrib/asgi/__init__.py @@ -57,8 +57,24 @@ def handle_request(scope, send): from ddtrace.contrib.internal.asgi.middleware import TraceMiddleware -from ddtrace.contrib.internal.asgi.middleware import get_version +from ddtrace.contrib.internal.asgi.middleware import get_version # noqa: F401 from ddtrace.contrib.internal.asgi.middleware import span_from_scope +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate -__all__ = ["TraceMiddleware", "span_from_scope", "get_version"] +def __getattr__(name): + if name in ("get_version",): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) + + +__all__ = ["TraceMiddleware", "span_from_scope"] diff --git a/ddtrace/contrib/asyncio/__init__.py b/ddtrace/contrib/asyncio/__init__.py index 205935222fb..a52166389c7 100644 --- a/ddtrace/contrib/asyncio/__init__.py +++ b/ddtrace/contrib/asyncio/__init__.py @@ -3,7 +3,7 @@ of concurrent execution of ``asyncio.Task``. """ # Required to allow users to import from `ddtrace.contrib.asyncio.patch` directly -# Expose public methods + import warnings as _w # noqa:E402 @@ -11,15 +11,12 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 from ddtrace._trace.provider import DefaultContextProvider -from ddtrace.contrib.internal.asyncio.helpers import ensure_future -from ddtrace.contrib.internal.asyncio.helpers import run_in_executor -from ddtrace.contrib.internal.asyncio.helpers import set_call_context -from ddtrace.contrib.internal.asyncio.patch import get_version -from ddtrace.contrib.internal.asyncio.patch import patch +from ddtrace.contrib.internal.asyncio.helpers import ensure_future # noqa: F401 +from ddtrace.contrib.internal.asyncio.helpers import run_in_executor # noqa: F401 +from ddtrace.contrib.internal.asyncio.helpers import set_call_context # noqa: F401 +from ddtrace.contrib.internal.asyncio.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.asyncio.patch import patch # noqa: F401 from ddtrace.contrib.internal.asyncio.patch import unpatch # noqa: F401 context_provider = DefaultContextProvider() - - -__all__ = ["context_provider", "set_call_context", "ensure_future", "run_in_executor", "patch", "get_version"] diff --git a/ddtrace/contrib/asyncpg/__init__.py b/ddtrace/contrib/asyncpg/__init__.py index 029cfd97790..9cd624678e8 100644 --- a/ddtrace/contrib/asyncpg/__init__.py +++ b/ddtrace/contrib/asyncpg/__init__.py @@ -53,9 +53,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.asyncpg.patch import get_version -from ddtrace.contrib.internal.asyncpg.patch import patch -from ddtrace.contrib.internal.asyncpg.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.asyncpg.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.asyncpg.patch import patch # noqa: F401 +from ddtrace.contrib.internal.asyncpg.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/avro/__init__.py b/ddtrace/contrib/avro/__init__.py index 328704bbfea..5e84b184f77 100644 --- a/ddtrace/contrib/avro/__init__.py +++ b/ddtrace/contrib/avro/__init__.py @@ -15,9 +15,6 @@ ~~~~~~~~~~~~~ """ -# Expose public methods -from ..internal.avro.patch import get_version -from ..internal.avro.patch import patch - -__all__ = ["patch", "get_version"] +from ..internal.avro.patch import get_version # noqa: F401 +from ..internal.avro.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/aws_lambda/__init__.py b/ddtrace/contrib/aws_lambda/__init__.py index f3d1c903374..6cd9b97da39 100644 --- a/ddtrace/contrib/aws_lambda/__init__.py +++ b/ddtrace/contrib/aws_lambda/__init__.py @@ -39,9 +39,6 @@ `Instrumenting Python Serverless Applications by Datadog `_. """ -from ddtrace.contrib.internal.aws_lambda.patch import get_version -from ddtrace.contrib.internal.aws_lambda.patch import patch -from ddtrace.contrib.internal.aws_lambda.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.aws_lambda.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.aws_lambda.patch import patch # noqa: F401 +from ddtrace.contrib.internal.aws_lambda.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/azure_functions/__init__.py b/ddtrace/contrib/azure_functions/__init__.py index 208b971efaa..9fb5a7a27b4 100644 --- a/ddtrace/contrib/azure_functions/__init__.py +++ b/ddtrace/contrib/azure_functions/__init__.py @@ -23,7 +23,6 @@ Default: ``"azure_functions"`` """ - from ddtrace.internal.utils.importlib import require_modules @@ -38,9 +37,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.azure_functions.patch import get_version - from ddtrace.contrib.internal.azure_functions.patch import patch - from ddtrace.contrib.internal.azure_functions.patch import unpatch - - __all__ = ["patch", "unpatch", "get_version"] + from ddtrace.contrib.internal.azure_functions.patch import get_version # noqa: F401 + from ddtrace.contrib.internal.azure_functions.patch import patch # noqa: F401 + from ddtrace.contrib.internal.azure_functions.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/boto/__init__.py b/ddtrace/contrib/boto/__init__.py index 6b967b28b2b..955c1bd0f2a 100644 --- a/ddtrace/contrib/boto/__init__.py +++ b/ddtrace/contrib/boto/__init__.py @@ -37,8 +37,5 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.boto.patch import get_version -from ddtrace.contrib.internal.boto.patch import patch - - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.boto.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.boto.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/botocore/__init__.py b/ddtrace/contrib/botocore/__init__.py index 60b01d39839..6662b43e599 100644 --- a/ddtrace/contrib/botocore/__init__.py +++ b/ddtrace/contrib/botocore/__init__.py @@ -161,9 +161,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.botocore.patch import get_version -from ddtrace.contrib.internal.botocore.patch import patch -from ddtrace.contrib.internal.botocore.patch import patch_submodules - - -__all__ = ["patch", "patch_submodules", "get_version"] +from ddtrace.contrib.internal.botocore.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.botocore.patch import patch # noqa: F401 +from ddtrace.contrib.internal.botocore.patch import patch_submodules # noqa: F401 diff --git a/ddtrace/contrib/bottle/__init__.py b/ddtrace/contrib/bottle/__init__.py index 448c709b952..12d046d8128 100644 --- a/ddtrace/contrib/bottle/__init__.py +++ b/ddtrace/contrib/bottle/__init__.py @@ -42,9 +42,25 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.bottle.patch import get_version -from ddtrace.contrib.internal.bottle.patch import patch +from ddtrace.contrib.internal.bottle.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.bottle.patch import patch # noqa: F401 from ddtrace.contrib.internal.bottle.trace import TracePlugin +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate -__all__ = ["TracePlugin", "patch", "get_version"] +def __getattr__(name): + if name in ("get_version", "patch"): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) + + +__all__ = ["TracePlugin"] diff --git a/ddtrace/contrib/cassandra/__init__.py b/ddtrace/contrib/cassandra/__init__.py index bcce866ad27..5b2247156d6 100644 --- a/ddtrace/contrib/cassandra/__init__.py +++ b/ddtrace/contrib/cassandra/__init__.py @@ -32,9 +32,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.cassandra.patch import patch -from ddtrace.contrib.internal.cassandra.session import get_version - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.cassandra.patch import patch # noqa: F401 +from ddtrace.contrib.internal.cassandra.session import get_version # noqa: F401 diff --git a/ddtrace/contrib/celery/__init__.py b/ddtrace/contrib/celery/__init__.py index 83ad7a870a3..514d0100388 100644 --- a/ddtrace/contrib/celery/__init__.py +++ b/ddtrace/contrib/celery/__init__.py @@ -61,12 +61,28 @@ def run(self): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods + from ddtrace.contrib.internal.celery.app import patch_app from ddtrace.contrib.internal.celery.app import unpatch_app -from ddtrace.contrib.internal.celery.patch import get_version -from ddtrace.contrib.internal.celery.patch import patch -from ddtrace.contrib.internal.celery.patch import unpatch +from ddtrace.contrib.internal.celery.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.celery.patch import patch # noqa: F401 +from ddtrace.contrib.internal.celery.patch import unpatch # noqa: F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate + + +def __getattr__(name): + if name in ("patch", "unpatch", "get_version"): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) -__all__ = ["patch", "patch_app", "unpatch", "unpatch_app", "get_version"] +__all__ = ["patch_app", "unpatch_app"] diff --git a/ddtrace/contrib/cherrypy/__init__.py b/ddtrace/contrib/cherrypy/__init__.py index efd1210db6c..b8fbd426e00 100644 --- a/ddtrace/contrib/cherrypy/__init__.py +++ b/ddtrace/contrib/cherrypy/__init__.py @@ -55,7 +55,23 @@ def index(self): from ddtrace.contrib.internal.cherrypy.middleware import TraceMiddleware -from ddtrace.contrib.internal.cherrypy.middleware import get_version +from ddtrace.contrib.internal.cherrypy.middleware import get_version # noqa: F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate -__all__ = ["TraceMiddleware", "get_version"] +def __getattr__(name): + if name in ("get_version",): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) + + +__all__ = ["TraceMiddleware"] diff --git a/ddtrace/contrib/consul/__init__.py b/ddtrace/contrib/consul/__init__.py index 433c70c0e80..d0a6a064683 100644 --- a/ddtrace/contrib/consul/__init__.py +++ b/ddtrace/contrib/consul/__init__.py @@ -29,10 +29,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.consul.patch import get_version -from ddtrace.contrib.internal.consul.patch import patch -from ddtrace.contrib.internal.consul.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.consul.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.consul.patch import patch # noqa: F401 +from ddtrace.contrib.internal.consul.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/coverage/__init__.py b/ddtrace/contrib/coverage/__init__.py index 6ae090e3fe0..9932f73acfe 100644 --- a/ddtrace/contrib/coverage/__init__.py +++ b/ddtrace/contrib/coverage/__init__.py @@ -23,13 +23,11 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.coverage.patch import get_version -from ddtrace.contrib.internal.coverage.patch import patch -from ddtrace.contrib.internal.coverage.patch import unpatch + +from ddtrace.contrib.internal.coverage.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.coverage.patch import patch # noqa: F401 +from ddtrace.contrib.internal.coverage.patch import unpatch # noqa: F401 from ddtrace.internal.logger import get_logger log = get_logger(__name__) - -__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/dbapi/__init__.py b/ddtrace/contrib/dbapi/__init__.py index 0b772ac04ec..fa733c19a63 100644 --- a/ddtrace/contrib/dbapi/__init__.py +++ b/ddtrace/contrib/dbapi/__init__.py @@ -14,15 +14,15 @@ from ...appsec._constants import IAST_SPAN_TAGS from ...appsec._iast._metrics import increment_iast_span_metric from ...constants import _ANALYTICS_SAMPLE_RATE_KEY +from ...constants import _SPAN_MEASURED_KEY from ...constants import SPAN_KIND -from ...constants import SPAN_MEASURED_KEY from ...ext import SpanKind from ...ext import SpanTypes from ...ext import db from ...ext import sql from ...trace import Pin -from ..trace_utils import ext_service -from ..trace_utils import iswrapped +from ..internal.trace_utils import ext_service +from ..internal.trace_utils import iswrapped log = get_logger(__name__) @@ -92,7 +92,7 @@ def _trace_method(self, method, name, resource, extra_tags, dbm_propagator, *arg name, service=ext_service(pin, self._self_config), resource=resource, span_type=SpanTypes.SQL ) as s: if measured: - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) # No reason to tag the query since it is set as the resource by the agent. See: # https://github.com/DataDog/datadog-trace-agent/blob/bda1ebbf170dd8c5879be993bdd4dbae70d10fda/obfuscate/sql.go#L232 s.set_tags(pin.tags) diff --git a/ddtrace/contrib/dbapi_async/__init__.py b/ddtrace/contrib/dbapi_async/__init__.py index d0c43fc1c2b..a6ae676f4bd 100644 --- a/ddtrace/contrib/dbapi_async/__init__.py +++ b/ddtrace/contrib/dbapi_async/__init__.py @@ -9,15 +9,15 @@ from ...appsec._constants import IAST_SPAN_TAGS from ...appsec._iast._metrics import increment_iast_span_metric from ...constants import _ANALYTICS_SAMPLE_RATE_KEY +from ...constants import _SPAN_MEASURED_KEY from ...constants import SPAN_KIND -from ...constants import SPAN_MEASURED_KEY from ...ext import SpanKind from ...ext import SpanTypes from ...trace import Pin from ..dbapi import TracedConnection from ..dbapi import TracedCursor -from ..trace_utils import ext_service -from ..trace_utils import iswrapped +from ..internal.trace_utils import ext_service +from ..internal.trace_utils import iswrapped log = get_logger(__name__) @@ -67,7 +67,7 @@ async def _trace_method(self, method, name, resource, extra_tags, dbm_propagator name, service=ext_service(pin, self._self_config), resource=resource, span_type=SpanTypes.SQL ) as s: if measured: - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) # No reason to tag the query since it is set as the resource by the agent. See: # https://github.com/DataDog/datadog-trace-agent/blob/bda1ebbf170dd8c5879be993bdd4dbae70d10fda/obfuscate/sql.go#L232 s.set_tags(pin.tags) diff --git a/ddtrace/contrib/django/__init__.py b/ddtrace/contrib/django/__init__.py index 2eeea57b2c6..c1ab65d494d 100644 --- a/ddtrace/contrib/django/__init__.py +++ b/ddtrace/contrib/django/__init__.py @@ -210,11 +210,8 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.django.patch import get_version -from ddtrace.contrib.internal.django.patch import patch -from ddtrace.contrib.internal.django.patch import patch as _patch -from ddtrace.contrib.internal.django.patch import unpatch - -__all__ = ["patch", "unpatch", "_patch", "get_version"] +from ddtrace.contrib.internal.django.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.django.patch import patch # noqa: F401 +from ddtrace.contrib.internal.django.patch import patch as _patch # noqa: F401 +from ddtrace.contrib.internal.django.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/dogpile_cache/__init__.py b/ddtrace/contrib/dogpile_cache/__init__.py index 63898aa4a5a..c1b4f1b58ed 100644 --- a/ddtrace/contrib/dogpile_cache/__init__.py +++ b/ddtrace/contrib/dogpile_cache/__init__.py @@ -46,10 +46,7 @@ def hello(name): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.dogpile_cache.patch import get_version -from ddtrace.contrib.internal.dogpile_cache.patch import patch -from ddtrace.contrib.internal.dogpile_cache.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.dogpile_cache.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.dogpile_cache.patch import patch # noqa: F401 +from ddtrace.contrib.internal.dogpile_cache.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/dramatiq/__init__.py b/ddtrace/contrib/dramatiq/__init__.py index 2e5f37c4cba..2d135ba2966 100644 --- a/ddtrace/contrib/dramatiq/__init__.py +++ b/ddtrace/contrib/dramatiq/__init__.py @@ -38,10 +38,7 @@ def my_other_task(content): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.dramatiq.patch import get_version -from ddtrace.contrib.internal.dramatiq.patch import patch -from ddtrace.contrib.internal.dramatiq.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.dramatiq.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.dramatiq.patch import patch # noqa: F401 +from ddtrace.contrib.internal.dramatiq.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/elasticsearch/__init__.py b/ddtrace/contrib/elasticsearch/__init__.py index db01bc85fea..b0be91a11da 100644 --- a/ddtrace/contrib/elasticsearch/__init__.py +++ b/ddtrace/contrib/elasticsearch/__init__.py @@ -49,9 +49,6 @@ # Override service name config.elasticsearch['service'] = 'custom-service-name' """ -from ddtrace.contrib.internal.elasticsearch.patch import get_version -from ddtrace.contrib.internal.elasticsearch.patch import get_versions -from ddtrace.contrib.internal.elasticsearch.patch import patch - - -__all__ = ["patch", "get_version", "get_versions"] +from ddtrace.contrib.internal.elasticsearch.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.elasticsearch.patch import get_versions # noqa: F401 +from ddtrace.contrib.internal.elasticsearch.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/falcon/__init__.py b/ddtrace/contrib/falcon/__init__.py index c54b58dbd9c..a521689273b 100644 --- a/ddtrace/contrib/falcon/__init__.py +++ b/ddtrace/contrib/falcon/__init__.py @@ -54,8 +54,24 @@ def on_falcon_request(span, request, response): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 from ddtrace.contrib.internal.falcon.middleware import TraceMiddleware -from ddtrace.contrib.internal.falcon.patch import get_version -from ddtrace.contrib.internal.falcon.patch import patch +from ddtrace.contrib.internal.falcon.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.falcon.patch import patch # noqa: F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate -__all__ = ["TraceMiddleware", "patch", "get_version"] +def __getattr__(name): + if name in ("patch", "get_version"): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) + + +__all__ = ["TraceMiddleware"] diff --git a/ddtrace/contrib/fastapi/__init__.py b/ddtrace/contrib/fastapi/__init__.py index df989abf766..726a308f14d 100644 --- a/ddtrace/contrib/fastapi/__init__.py +++ b/ddtrace/contrib/fastapi/__init__.py @@ -60,10 +60,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.fastapi.patch import get_version -from ddtrace.contrib.internal.fastapi.patch import patch -from ddtrace.contrib.internal.fastapi.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.fastapi.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.fastapi.patch import patch # noqa: F401 +from ddtrace.contrib.internal.fastapi.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/flask/__init__.py b/ddtrace/contrib/flask/__init__.py index 22e90ffb604..0562240d090 100644 --- a/ddtrace/contrib/flask/__init__.py +++ b/ddtrace/contrib/flask/__init__.py @@ -104,9 +104,6 @@ def index(): with _w.catch_warnings(): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -from ddtrace.contrib.internal.flask.patch import get_version -from ddtrace.contrib.internal.flask.patch import patch -from ddtrace.contrib.internal.flask.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.flask.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.flask.patch import patch # noqa: F401 +from ddtrace.contrib.internal.flask.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/flask_cache/__init__.py b/ddtrace/contrib/flask_cache/__init__.py index f11b006f723..ae6fcf003db 100644 --- a/ddtrace/contrib/flask_cache/__init__.py +++ b/ddtrace/contrib/flask_cache/__init__.py @@ -44,9 +44,25 @@ def counter(): """ -# Expose public methods + from ddtrace.contrib.internal.flask_cache.tracers import get_traced_cache -from ddtrace.contrib.internal.flask_cache.tracers import get_version +from ddtrace.contrib.internal.flask_cache.tracers import get_version # noqa: F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate + + +def __getattr__(name): + if name in ("get_version",): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) -__all__ = ["get_traced_cache", "get_version"] +__all__ = ["get_traced_cache"] diff --git a/ddtrace/contrib/flask_login/__init__.py b/ddtrace/contrib/flask_login/__init__.py index 86c2a247bba..311eee7fa10 100644 --- a/ddtrace/contrib/flask_login/__init__.py +++ b/ddtrace/contrib/flask_login/__init__.py @@ -1,31 +1,10 @@ -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate - - -deprecate( - """The flask_login module is deprecated and will be deleted. -We recommend customers to switch to manual instrumentation. -https://docs.datadoghq.com/security/application_security/threats/add-user-info/?tab=loginsuccess&code-lang=python#adding-business-logic-information-login-success-login-failure-any-business-logic-to-traces -""", - message="", - category=DDTraceDeprecationWarning, -) - - def get_version() -> str: - deprecate( - "The flask_login module is deprecated and will be deleted.", message="", category=DDTraceDeprecationWarning - ) return "" def patch(): - deprecate( - "The flask_login module is deprecated and will be deleted.", message="", category=DDTraceDeprecationWarning - ) + pass def unpatch(): - deprecate( - "The flask_login module is deprecated and will be deleted.", message="", category=DDTraceDeprecationWarning - ) + pass diff --git a/ddtrace/contrib/freezegun/__init__.py b/ddtrace/contrib/freezegun/__init__.py index 8c072c74d5a..2b643662c1d 100644 --- a/ddtrace/contrib/freezegun/__init__.py +++ b/ddtrace/contrib/freezegun/__init__.py @@ -13,10 +13,7 @@ The freezegun integration is not configurable, but may be disabled using DD_PATCH_MODULES=freezegun:false . """ -# Expose public methods -from ..internal.freezegun.patch import get_version -from ..internal.freezegun.patch import patch -from ..internal.freezegun.patch import unpatch - -__all__ = ["get_version", "patch", "unpatch"] +from ..internal.freezegun.patch import get_version # noqa: F401 +from ..internal.freezegun.patch import patch # noqa: F401 +from ..internal.freezegun.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/futures/__init__.py b/ddtrace/contrib/futures/__init__.py index 1ba8741ebe9..3ac420b2263 100644 --- a/ddtrace/contrib/futures/__init__.py +++ b/ddtrace/contrib/futures/__init__.py @@ -26,14 +26,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.futures.patch import get_version -from ddtrace.contrib.internal.futures.patch import patch -from ddtrace.contrib.internal.futures.patch import unpatch - -__all__ = [ - "get_version", - "patch", - "unpatch", -] +from ddtrace.contrib.internal.futures.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.futures.patch import patch # noqa: F401 +from ddtrace.contrib.internal.futures.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/gevent/__init__.py b/ddtrace/contrib/gevent/__init__.py index fb051e5f88b..5ee22f591b4 100644 --- a/ddtrace/contrib/gevent/__init__.py +++ b/ddtrace/contrib/gevent/__init__.py @@ -45,14 +45,15 @@ def worker_function(): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.gevent.patch import get_version -from ddtrace.contrib.internal.gevent.patch import patch -from ddtrace.contrib.internal.gevent.patch import unpatch + +from ddtrace.contrib.internal.gevent.patch import get_version #noqa: F401 +from ddtrace.contrib.internal.gevent.patch import patch #noqa: F401 +from ddtrace.contrib.internal.gevent.patch import unpatch #noqa: F401 from ...provider import DefaultContextProvider as _DefaultContextProvider context_provider = _DefaultContextProvider() -__all__ = ["patch", "unpatch", "context_provider", "get_version"] + + diff --git a/ddtrace/contrib/google_generativeai/__init__.py b/ddtrace/contrib/google_generativeai/__init__.py index 5066fc4f9a2..45333c58173 100644 --- a/ddtrace/contrib/google_generativeai/__init__.py +++ b/ddtrace/contrib/google_generativeai/__init__.py @@ -79,10 +79,6 @@ Pin.override(genai, service="my-gemini-service") """ # noqa: E501 - -from ..internal.google_generativeai.patch import get_version -from ..internal.google_generativeai.patch import patch -from ..internal.google_generativeai.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ..internal.google_generativeai.patch import get_version # noqa: F401 +from ..internal.google_generativeai.patch import patch # noqa: F401 +from ..internal.google_generativeai.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/graphql/__init__.py b/ddtrace/contrib/graphql/__init__.py index e7ad66745d4..f12880efda1 100644 --- a/ddtrace/contrib/graphql/__init__.py +++ b/ddtrace/contrib/graphql/__init__.py @@ -54,10 +54,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.graphql.patch import get_version -from ddtrace.contrib.internal.graphql.patch import patch -from ddtrace.contrib.internal.graphql.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.graphql.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.graphql.patch import patch # noqa: F401 +from ddtrace.contrib.internal.graphql.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/grpc/__init__.py b/ddtrace/contrib/grpc/__init__.py index 8ad2a705233..ea2db0b4ac3 100644 --- a/ddtrace/contrib/grpc/__init__.py +++ b/ddtrace/contrib/grpc/__init__.py @@ -84,10 +84,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.grpc.patch import get_version -from ddtrace.contrib.internal.grpc.patch import patch -from ddtrace.contrib.internal.grpc.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.grpc.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.grpc.patch import patch # noqa: F401 +from ddtrace.contrib.internal.grpc.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/httplib/__init__.py b/ddtrace/contrib/httplib/__init__.py index 948b885199a..ae85051517e 100644 --- a/ddtrace/contrib/httplib/__init__.py +++ b/ddtrace/contrib/httplib/__init__.py @@ -65,11 +65,8 @@ with _w.catch_warnings(): _w.simplefilter("ignore", DeprecationWarning) - from . import patch as _ # noqa: F401, I001 + from . import patch as _ # noqa: I001,F401 -from ddtrace.contrib.internal.httplib.patch import get_version -from ddtrace.contrib.internal.httplib.patch import patch -from ddtrace.contrib.internal.httplib.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.httplib.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.httplib.patch import patch # noqa: F401 +from ddtrace.contrib.internal.httplib.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/httpx/__init__.py b/ddtrace/contrib/httpx/__init__.py index 95762604687..39b191ad139 100644 --- a/ddtrace/contrib/httpx/__init__.py +++ b/ddtrace/contrib/httpx/__init__.py @@ -87,10 +87,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.httpx.patch import get_version -from ddtrace.contrib.internal.httpx.patch import patch -from ddtrace.contrib.internal.httpx.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.httpx.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.httpx.patch import patch # noqa: F401 +from ddtrace.contrib.internal.httpx.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/internal/aiobotocore/patch.py b/ddtrace/contrib/internal/aiobotocore/patch.py index 7431bd5c592..4b0fea48b96 100644 --- a/ddtrace/contrib/internal/aiobotocore/patch.py +++ b/ddtrace/contrib/internal/aiobotocore/patch.py @@ -5,10 +5,10 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY -from ddtrace.contrib.trace_utils import ext_service -from ddtrace.contrib.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import unwrap from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import aws @@ -125,7 +125,7 @@ async def _wrapped_api_call(original_func, instance, args, kwargs): # set span.kind tag equal to type of request span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) try: operation = get_argument_value(args, kwargs, 0, "operation_name") diff --git a/ddtrace/contrib/internal/aiohttp/middlewares.py b/ddtrace/contrib/internal/aiohttp/middlewares.py index 59045d7bcc9..4f7abe5a12f 100644 --- a/ddtrace/contrib/internal/aiohttp/middlewares.py +++ b/ddtrace/contrib/internal/aiohttp/middlewares.py @@ -3,8 +3,8 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.asyncio import context_provider from ddtrace.ext import SpanKind @@ -50,7 +50,7 @@ async def attach_context(request): service=service, span_type=SpanTypes.WEB, ) - request_span.set_tag(SPAN_MEASURED_KEY) + request_span.set_tag(_SPAN_MEASURED_KEY) request_span.set_tag_str(COMPONENT, config.aiohttp.integration_name) diff --git a/ddtrace/contrib/internal/aiohttp/patch.py b/ddtrace/contrib/internal/aiohttp/patch.py index e0f0bc869e9..900a8d26e41 100644 --- a/ddtrace/contrib/internal/aiohttp/patch.py +++ b/ddtrace/contrib/internal/aiohttp/patch.py @@ -6,13 +6,13 @@ from ddtrace import config from ddtrace.constants import SPAN_KIND -from ddtrace.contrib.trace_utils import ext_service -from ddtrace.contrib.trace_utils import extract_netloc_and_query_info_from_url -from ddtrace.contrib.trace_utils import set_http_meta -from ddtrace.contrib.trace_utils import unwrap -from ddtrace.contrib.trace_utils import with_traced_module as with_traced_module_sync -from ddtrace.contrib.trace_utils import wrap -from ddtrace.contrib.trace_utils_async import with_traced_module +from ddtrace.contrib.internal.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import extract_netloc_and_query_info_from_url +from ddtrace.contrib.internal.trace_utils import set_http_meta +from ddtrace.contrib.internal.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import with_traced_module as with_traced_module_sync +from ddtrace.contrib.internal.trace_utils import wrap +from ddtrace.contrib.internal.trace_utils_async import with_traced_module from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.internal.constants import COMPONENT diff --git a/ddtrace/contrib/internal/aiohttp_jinja2/patch.py b/ddtrace/contrib/internal/aiohttp_jinja2/patch.py index 84553899c39..74987483a20 100644 --- a/ddtrace/contrib/internal/aiohttp_jinja2/patch.py +++ b/ddtrace/contrib/internal/aiohttp_jinja2/patch.py @@ -1,9 +1,9 @@ import aiohttp_jinja2 from ddtrace import config -from ddtrace.contrib.trace_utils import unwrap -from ddtrace.contrib.trace_utils import with_traced_module -from ddtrace.contrib.trace_utils import wrap +from ddtrace.contrib.internal.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import with_traced_module +from ddtrace.contrib.internal.trace_utils import wrap from ddtrace.ext import SpanTypes from ddtrace.internal.constants import COMPONENT from ddtrace.internal.utils import get_argument_value diff --git a/ddtrace/contrib/internal/aiomysql/patch.py b/ddtrace/contrib/internal/aiomysql/patch.py index 0053e4f8a5b..8f33a4d6343 100644 --- a/ddtrace/contrib/internal/aiomysql/patch.py +++ b/ddtrace/contrib/internal/aiomysql/patch.py @@ -3,10 +3,11 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import dbapi from ddtrace.contrib import trace_utils +from ddtrace.contrib.internal.trace_utils import _convert_to_string from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -48,7 +49,7 @@ async def patched_connect(connect_func, _, args, kwargs): tags = {} for tag, attr in CONN_ATTR_BY_TAG.items(): if hasattr(conn, attr): - tags[tag] = trace_utils._convert_to_string(getattr(conn, attr, None)) + tags[tag] = _convert_to_string(getattr(conn, attr, None)) tags[db.SYSTEM] = "mysql" c = AIOTracedConnection(conn) @@ -81,7 +82,7 @@ async def _trace_method(self, method, resource, extra_tags, *args, **kwargs): # set span.kind to the type of request being performed s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) s.set_tags(pin.tags) s.set_tags(extra_tags) diff --git a/ddtrace/contrib/internal/aiopg/connection.py b/ddtrace/contrib/internal/aiopg/connection.py index 1daf84b2987..b63a6352a27 100644 --- a/ddtrace/contrib/internal/aiopg/connection.py +++ b/ddtrace/contrib/internal/aiopg/connection.py @@ -4,8 +4,8 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import dbapi from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind @@ -47,7 +47,7 @@ async def _trace_method(self, method, resource, extra_tags, *args, **kwargs): # set span.kind to the type of request being performed s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) s.set_tags(pin.tags) s.set_tags(extra_tags) diff --git a/ddtrace/contrib/internal/aioredis/patch.py b/ddtrace/contrib/internal/aioredis/patch.py index dc6004b9caa..62b8ec9f80c 100644 --- a/ddtrace/contrib/internal/aioredis/patch.py +++ b/ddtrace/contrib/internal/aioredis/patch.py @@ -9,11 +9,11 @@ from ddtrace._trace.utils_redis import _instrument_redis_cmd from ddtrace._trace.utils_redis import _instrument_redis_execute_pipeline from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils +from ddtrace.contrib.internal.redis_utils import _run_redis_command_async from ddtrace.contrib.redis_utils import ROW_RETURNING_COMMANDS -from ddtrace.contrib.redis_utils import _run_redis_command_async from ddtrace.contrib.redis_utils import determine_row_count from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -149,7 +149,7 @@ def traced_13_execute_command(func, instance, args, kwargs): span.set_tag_str(COMPONENT, config.aioredis.integration_name) span.set_tag_str(db.SYSTEM, redisx.APP) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) span.set_tag_str(redisx.RAWCMD, query) if pin.tags: span.set_tags(pin.tags) @@ -225,7 +225,7 @@ async def traced_13_execute_pipeline(func, instance, args, kwargs): } ) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) span.set_tag_str(redisx.RAWCMD, cmds_string) span.set_metric(redisx.PIPELINE_LEN, len(instance._pipeline)) # set analytics sample rate if enabled diff --git a/ddtrace/contrib/internal/algoliasearch/patch.py b/ddtrace/contrib/internal/algoliasearch/patch.py index e3074225570..bc1ba23a279 100644 --- a/ddtrace/contrib/internal/algoliasearch/patch.py +++ b/ddtrace/contrib/internal/algoliasearch/patch.py @@ -1,8 +1,8 @@ from wrapt import wrap_function_wrapper as _w from ddtrace import config +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -129,7 +129,7 @@ def _patched_search(func, instance, wrapt_args, wrapt_kwargs): # set span.kind to the type of request being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) if span.context.sampling_priority is not None and span.context.sampling_priority <= 0: return func(*wrapt_args, **wrapt_kwargs) diff --git a/ddtrace/contrib/internal/anthropic/patch.py b/ddtrace/contrib/internal/anthropic/patch.py index 24f72f2b511..2bb35836580 100644 --- a/ddtrace/contrib/internal/anthropic/patch.py +++ b/ddtrace/contrib/internal/anthropic/patch.py @@ -11,9 +11,9 @@ from ddtrace.contrib.internal.anthropic.utils import tag_params_on_span from ddtrace.contrib.internal.anthropic.utils import tag_tool_result_input_on_span from ddtrace.contrib.internal.anthropic.utils import tag_tool_use_input_on_span -from ddtrace.contrib.trace_utils import unwrap -from ddtrace.contrib.trace_utils import with_traced_module -from ddtrace.contrib.trace_utils import wrap +from ddtrace.contrib.internal.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import with_traced_module +from ddtrace.contrib.internal.trace_utils import wrap from ddtrace.internal.logger import get_logger from ddtrace.internal.utils import get_argument_value from ddtrace.llmobs._integrations import AnthropicIntegration diff --git a/ddtrace/contrib/internal/aredis/patch.py b/ddtrace/contrib/internal/aredis/patch.py index bd8c5b4c750..4374f98c601 100644 --- a/ddtrace/contrib/internal/aredis/patch.py +++ b/ddtrace/contrib/internal/aredis/patch.py @@ -6,7 +6,7 @@ from ddtrace import config from ddtrace._trace.utils_redis import _instrument_redis_cmd from ddtrace._trace.utils_redis import _instrument_redis_execute_pipeline -from ddtrace.contrib.redis_utils import _run_redis_command_async +from ddtrace.contrib.internal.redis_utils import _run_redis_command_async from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils.formats import CMD_MAX_LEN from ddtrace.internal.utils.formats import asbool diff --git a/ddtrace/contrib/internal/asyncpg/patch.py b/ddtrace/contrib/internal/asyncpg/patch.py index ac1347a7de6..4eeae4d193e 100644 --- a/ddtrace/contrib/internal/asyncpg/patch.py +++ b/ddtrace/contrib/internal/asyncpg/patch.py @@ -9,7 +9,7 @@ import wrapt from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -19,10 +19,10 @@ from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils import get_argument_value from ddtrace.propagation._database_monitoring import _DBM_Propagator -from ddtrace.contrib.trace_utils import ext_service -from ddtrace.contrib.trace_utils import unwrap -from ddtrace.contrib.trace_utils import wrap -from ddtrace.contrib.trace_utils_async import with_traced_module +from ddtrace.contrib.internal.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import wrap +from ddtrace.contrib.internal.trace_utils_async import with_traced_module if TYPE_CHECKING: # pragma: no cover @@ -117,7 +117,7 @@ async def _traced_query(pin, method, query, args, kwargs): # set span.kind to the type of request being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) span.set_tags(pin.tags) # dispatch DBM diff --git a/ddtrace/contrib/internal/azure_functions/patch.py b/ddtrace/contrib/internal/azure_functions/patch.py index 1c0c658a9eb..7690368a852 100644 --- a/ddtrace/contrib/internal/azure_functions/patch.py +++ b/ddtrace/contrib/internal/azure_functions/patch.py @@ -2,8 +2,8 @@ from wrapt import wrap_function_wrapper as _w from ddtrace import config -from ddtrace.contrib.trace_utils import int_service -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import int_service +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.ext import SpanTypes from ddtrace.internal import core from ddtrace.internal.schema import schematize_cloud_faas_operation diff --git a/ddtrace/contrib/internal/boto/patch.py b/ddtrace/contrib/internal/boto/patch.py index e7418aba878..87094751f9b 100644 --- a/ddtrace/contrib/internal/boto/patch.py +++ b/ddtrace/contrib/internal/boto/patch.py @@ -7,8 +7,8 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import aws @@ -93,7 +93,7 @@ def patched_query_request(original_func, instance, args, kwargs): # set span.kind to the type of request being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) operation_name = None if args: @@ -164,7 +164,7 @@ def patched_auth_request(original_func, instance, args, kwargs): service=schematize_service_name("{}.{}".format(pin.service, endpoint_name)), span_type=SpanTypes.HTTP, ) as span: - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) if args: http_method = get_argument_value(args, kwargs, 0, "method") span.resource = "%s.%s" % (endpoint_name, http_method.lower()) diff --git a/ddtrace/contrib/internal/botocore/patch.py b/ddtrace/contrib/internal/botocore/patch.py index 07c0bd403e4..734c429d798 100644 --- a/ddtrace/contrib/internal/botocore/patch.py +++ b/ddtrace/contrib/internal/botocore/patch.py @@ -17,9 +17,9 @@ from ddtrace import config from ddtrace.constants import SPAN_KIND -from ddtrace.contrib.trace_utils import ext_service -from ddtrace.contrib.trace_utils import unwrap -from ddtrace.contrib.trace_utils import with_traced_module +from ddtrace.contrib.internal.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import with_traced_module from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.internal import core diff --git a/ddtrace/contrib/internal/botocore/services/bedrock.py b/ddtrace/contrib/internal/botocore/services/bedrock.py index 00e9aa5756f..4f237b67342 100644 --- a/ddtrace/contrib/internal/botocore/services/bedrock.py +++ b/ddtrace/contrib/internal/botocore/services/bedrock.py @@ -7,7 +7,7 @@ import wrapt from ddtrace import config -from ddtrace.contrib.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import ext_service from ddtrace.ext import SpanTypes from ddtrace.internal import core from ddtrace.internal.logger import get_logger diff --git a/ddtrace/contrib/internal/botocore/services/kinesis.py b/ddtrace/contrib/internal/botocore/services/kinesis.py index 0287c29d2bc..f1f71d0b819 100644 --- a/ddtrace/contrib/internal/botocore/services/kinesis.py +++ b/ddtrace/contrib/internal/botocore/services/kinesis.py @@ -10,7 +10,7 @@ import botocore.exceptions from ddtrace import config -from ddtrace.contrib.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import ext_service from ddtrace.ext import SpanTypes from ddtrace.internal import core from ddtrace.internal.logger import get_logger diff --git a/ddtrace/contrib/internal/botocore/services/sqs.py b/ddtrace/contrib/internal/botocore/services/sqs.py index 084a6f77dc7..5bf238c8cfd 100644 --- a/ddtrace/contrib/internal/botocore/services/sqs.py +++ b/ddtrace/contrib/internal/botocore/services/sqs.py @@ -7,7 +7,7 @@ import botocore.exceptions from ddtrace import config -from ddtrace.contrib.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import ext_service from ddtrace.ext import SpanTypes from ddtrace.internal import core from ddtrace.internal.logger import get_logger diff --git a/ddtrace/contrib/internal/botocore/services/stepfunctions.py b/ddtrace/contrib/internal/botocore/services/stepfunctions.py index 9e3c1d2db13..8bf2c1433ee 100644 --- a/ddtrace/contrib/internal/botocore/services/stepfunctions.py +++ b/ddtrace/contrib/internal/botocore/services/stepfunctions.py @@ -6,7 +6,7 @@ from ddtrace import config from ddtrace.contrib.internal.botocore.constants import BOTOCORE_STEPFUNCTIONS_INPUT_KEY -from ddtrace.contrib.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import ext_service from ddtrace.ext import SpanTypes from ddtrace.internal import core from ddtrace.internal.logger import get_logger diff --git a/ddtrace/contrib/internal/bottle/trace.py b/ddtrace/contrib/internal/bottle/trace.py index 2f86b1e780a..3aabb4ccc81 100644 --- a/ddtrace/contrib/internal/bottle/trace.py +++ b/ddtrace/contrib/internal/bottle/trace.py @@ -6,8 +6,8 @@ import ddtrace from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -57,7 +57,7 @@ def wrapped(*args, **kwargs): # set span.kind to the type of request being performed s.set_tag_str(SPAN_KIND, SpanKind.SERVER) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) # set analytics sample rate with global config enabled s.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, config.bottle.get_analytics_sample_rate(use_global_config=True)) diff --git a/ddtrace/contrib/internal/cassandra/patch.py b/ddtrace/contrib/internal/cassandra/patch.py index d82e620f753..ad375683633 100644 --- a/ddtrace/contrib/internal/cassandra/patch.py +++ b/ddtrace/contrib/internal/cassandra/patch.py @@ -1,5 +1,14 @@ -from .session import patch -from .session import unpatch +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate +from .session import patch # noqa: F401 +from .session import unpatch # noqa: F401 -__all__ = ["patch", "unpatch"] + +deprecate( + ("%s is deprecated" % (__name__)), + message="Avoid using this package directly. " + "Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to enable and configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", +) diff --git a/ddtrace/contrib/internal/cassandra/session.py b/ddtrace/contrib/internal/cassandra/session.py index 7f02d8c0af6..3ccd44bb616 100644 --- a/ddtrace/contrib/internal/cassandra/session.py +++ b/ddtrace/contrib/internal/cassandra/session.py @@ -23,10 +23,10 @@ from ddtrace import Span from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_TYPE from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import cassandra as cassx @@ -214,7 +214,7 @@ def _start_span_and_set_tags( span.set_tag_str(COMPONENT, config.cassandra.integration_name) span.set_tag_str(db.SYSTEM, "cassandra") span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) span.set_tags(additional_tags) span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, config.cassandra.get_analytics_sample_rate()) if query is not None: diff --git a/ddtrace/contrib/internal/celery/app.py b/ddtrace/contrib/internal/celery/app.py index 54ad5834769..f338816243b 100644 --- a/ddtrace/contrib/internal/celery/app.py +++ b/ddtrace/contrib/internal/celery/app.py @@ -6,8 +6,8 @@ from ddtrace import config from ddtrace._trace.pin import _DD_PIN_NAME from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.celery.signals import trace_after_publish from ddtrace.contrib.internal.celery.signals import trace_before_publish @@ -102,7 +102,7 @@ def _traced_beat_inner(func, instance, args, kwargs): rate = config.celery.get_analytics_sample_rate() if rate is not None: span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, rate) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) return func(*args, **kwargs) diff --git a/ddtrace/contrib/internal/celery/signals.py b/ddtrace/contrib/internal/celery/signals.py index ea9d8c15863..dd03662e12f 100644 --- a/ddtrace/contrib/internal/celery/signals.py +++ b/ddtrace/contrib/internal/celery/signals.py @@ -5,8 +5,8 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.celery import constants as c from ddtrace.contrib.internal.celery.utils import attach_span @@ -65,7 +65,7 @@ def trace_prerun(*args, **kwargs): if rate is not None: span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, rate) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) attach_span(task, task_id, span) if config.celery["distributed_tracing"]: attach_span_context(task, task_id, span) @@ -139,7 +139,7 @@ def trace_before_publish(*args, **kwargs): if rate is not None: span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, rate) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) span.set_tag_str(c.TASK_TAG_KEY, c.TASK_APPLY_ASYNC) span.set_tag_str("celery.id", task_id) set_tags_from_context(span, kwargs) diff --git a/ddtrace/contrib/internal/celery/utils.py b/ddtrace/contrib/internal/celery/utils.py index 8945fb5857b..5c7a4c077e8 100644 --- a/ddtrace/contrib/internal/celery/utils.py +++ b/ddtrace/contrib/internal/celery/utils.py @@ -3,7 +3,7 @@ from weakref import WeakValueDictionary from ddtrace._trace.span import Span -from ddtrace.contrib.trace_utils import set_flattened_tags +from ddtrace.contrib.internal.trace_utils import set_flattened_tags from ddtrace.propagation.http import HTTPPropagator from .constants import CTX_KEY diff --git a/ddtrace/contrib/internal/consul/patch.py b/ddtrace/contrib/internal/consul/patch.py index b24b138b632..d1761fdb05d 100644 --- a/ddtrace/contrib/internal/consul/patch.py +++ b/ddtrace/contrib/internal/consul/patch.py @@ -3,8 +3,8 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import consul as consulx @@ -73,7 +73,7 @@ def trace_func(wrapped, instance, args, kwargs): # set span.kind to the type of request being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) rate = config.consul.get_analytics_sample_rate() if rate is not None: span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, rate) diff --git a/ddtrace/contrib/internal/django/patch.py b/ddtrace/contrib/internal/django/patch.py index 8bc523dd1c1..c51c789aab7 100644 --- a/ddtrace/contrib/internal/django/patch.py +++ b/ddtrace/contrib/internal/django/patch.py @@ -23,7 +23,8 @@ from ddtrace.constants import SPAN_KIND from ddtrace.contrib import dbapi from ddtrace.contrib import trace_utils -from ddtrace.contrib.trace_utils import _get_request_header_user_agent +from ddtrace.contrib.internal.trace_utils import _convert_to_string +from ddtrace.contrib.internal.trace_utils import _get_request_header_user_agent from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -127,7 +128,7 @@ def patch_conn(django, conn): for tag, attr in DB_CONN_ATTR_BY_TAG.items(): if attr in settings_dict: try: - tags[tag] = trace_utils._convert_to_string(conn.settings_dict.get(attr)) + tags[tag] = _convert_to_string(conn.settings_dict.get(attr)) except Exception: tags[tag] = str(conn.settings_dict.get(attr)) conn._datadog_tags = tags @@ -695,6 +696,23 @@ def traced_as_view(django, pin, func, instance, args, kwargs): return wrapt.FunctionWrapper(view, traced_func(django, "django.view", resource=func_name(instance))) +@trace_utils.with_traced_module +def traced_technical_500_response(django, pin, func, instance, args, kwargs): + """ + Wrapper for django's views.debug.technical_500_response + """ + response = func(*args, **kwargs) + try: + request = get_argument_value(args, kwargs, 0, "request") + exc_type = get_argument_value(args, kwargs, 1, "exc_type") + exc_value = get_argument_value(args, kwargs, 2, "exc_value") + tb = get_argument_value(args, kwargs, 3, "tb") + core.dispatch("django.technical_500_response", (request, response, exc_type, exc_value, tb)) + except Exception: + log.debug("Error while trying to trace Django technical 500 response", exc_info=True) + return response + + @trace_utils.with_traced_module def traced_get_asgi_application(django, pin, func, instance, args, kwargs): from ddtrace.contrib.asgi import TraceMiddleware @@ -890,6 +908,9 @@ def _(m): trace_utils.wrap(m, "re_path", traced_urls_path(django)) when_imported("django.views.generic.base")(lambda m: trace_utils.wrap(m, "View.as_view", traced_as_view(django))) + when_imported("django.views.debug")( + lambda m: trace_utils.wrap(m, "technical_500_response", traced_technical_500_response(django)) + ) @when_imported("channels.routing") def _(m): @@ -934,6 +955,7 @@ def _unpatch(django): trace_utils.unwrap(django.conf.urls, "url") trace_utils.unwrap(django.contrib.auth.login, "login") trace_utils.unwrap(django.contrib.auth.authenticate, "authenticate") + trace_utils.unwrap(django.view.debug.technical_500_response, "technical_500_response") if django.VERSION >= (2, 0, 0): trace_utils.unwrap(django.urls, "path") trace_utils.unwrap(django.urls, "re_path") diff --git a/ddtrace/contrib/internal/django/restframework.py b/ddtrace/contrib/internal/django/restframework.py index d9a7266fb03..e1bebeaea73 100644 --- a/ddtrace/contrib/internal/django/restframework.py +++ b/ddtrace/contrib/internal/django/restframework.py @@ -1,8 +1,8 @@ import rest_framework.views from wrapt import wrap_function_wrapper as wrap -from ddtrace.contrib.trace_utils import iswrapped -from ddtrace.contrib.trace_utils import with_traced_module +from ddtrace.contrib.internal.trace_utils import iswrapped +from ddtrace.contrib.internal.trace_utils import with_traced_module @with_traced_module diff --git a/ddtrace/contrib/internal/django/utils.py b/ddtrace/contrib/internal/django/utils.py index 14f5e3dd4e6..a8d4c469e66 100644 --- a/ddtrace/contrib/internal/django/utils.py +++ b/ddtrace/contrib/internal/django/utils.py @@ -15,7 +15,7 @@ from ddtrace import config from ddtrace._trace.span import Span from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY -from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.django.compat import get_resolver from ddtrace.contrib.internal.django.compat import user_is_authenticated @@ -255,7 +255,7 @@ def _before_request_tags(pin, span, request): # has explicitly set it during the request lifetime span.service = trace_utils.int_service(pin, config.django) span.span_type = SpanTypes.WEB - span._metrics[SPAN_MEASURED_KEY] = 1 + span._metrics[_SPAN_MEASURED_KEY] = 1 analytics_sr = config.django.get_analytics_sample_rate(use_global_config=True) if analytics_sr is not None: diff --git a/ddtrace/contrib/internal/dogpile_cache/region.py b/ddtrace/contrib/internal/dogpile_cache/region.py index 0c89d2d84d9..8b67f5bd2be 100644 --- a/ddtrace/contrib/internal/dogpile_cache/region.py +++ b/ddtrace/contrib/internal/dogpile_cache/region.py @@ -1,6 +1,6 @@ import dogpile -from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.ext import SpanTypes from ddtrace.ext import db from ddtrace.internal.constants import COMPONENT @@ -23,7 +23,7 @@ def _wrap_get_create(func, instance, args, kwargs): span_type=SpanTypes.CACHE, ) as span: span.set_tag_str(COMPONENT, "dogpile_cache") - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) span.set_tag("key", key) span.set_tag("region", instance.name) span.set_tag("backend", instance.actual_backend.__class__.__name__) @@ -45,7 +45,7 @@ def _wrap_get_create_multi(func, instance, args, kwargs): span_type="cache", ) as span: span.set_tag_str(COMPONENT, "dogpile_cache") - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) span.set_tag("keys", keys) span.set_tag("region", instance.name) span.set_tag("backend", instance.actual_backend.__class__.__name__) diff --git a/ddtrace/contrib/internal/elasticsearch/patch.py b/ddtrace/contrib/internal/elasticsearch/patch.py index 7c408db55a5..fa182ea3063 100644 --- a/ddtrace/contrib/internal/elasticsearch/patch.py +++ b/ddtrace/contrib/internal/elasticsearch/patch.py @@ -6,11 +6,11 @@ from ddtrace import config from ddtrace._trace import _limits from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib.internal.elasticsearch.quantize import quantize -from ddtrace.contrib.trace_utils import ext_service -from ddtrace.contrib.trace_utils import extract_netloc_and_query_info_from_url +from ddtrace.contrib.internal.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import extract_netloc_and_query_info_from_url from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import elasticsearch as metadata @@ -140,7 +140,7 @@ def _perform_request(func, instance, args, kwargs): # set span.kind to the type of request being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) method, target = args params = kwargs.get("params") diff --git a/ddtrace/contrib/internal/falcon/middleware.py b/ddtrace/contrib/internal/falcon/middleware.py index b6fe9498b1a..b4ec5434777 100644 --- a/ddtrace/contrib/internal/falcon/middleware.py +++ b/ddtrace/contrib/internal/falcon/middleware.py @@ -2,8 +2,8 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -39,7 +39,7 @@ def process_request(self, req, resp): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.SERVER) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) # set analytics sample rate with global config enabled span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, config.falcon.get_analytics_sample_rate(use_global_config=True)) diff --git a/ddtrace/contrib/internal/flask/patch.py b/ddtrace/contrib/internal/flask/patch.py index 010df5218c5..9476485b314 100644 --- a/ddtrace/contrib/internal/flask/patch.py +++ b/ddtrace/contrib/internal/flask/patch.py @@ -30,8 +30,8 @@ from wrapt import wrap_function_wrapper as _w from ddtrace import config +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.contrib.internal.wsgi.wsgi import _DDWSGIMiddlewareBase -from ddtrace.contrib.trace_utils import unwrap as _u from ddtrace.internal.logger import get_logger from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils.importlib import func_name @@ -224,6 +224,10 @@ def patch(): _w("flask.templating", "_render", patched_render) _w("flask", "render_template", _build_render_template_wrapper("render_template")) _w("flask", "render_template_string", _build_render_template_wrapper("render_template_string")) + try: + _w("werkzeug.debug.tbtools", "DebugTraceback.render_debugger_html", patched_render_debugger_html) + except AttributeError: + log.debug("Failed to patch DebugTraceback.render_debugger_html, not supported by this werkzeug version") bp_hooks = [ "after_app_request", @@ -380,12 +384,8 @@ def patched_finalize_request(wrapped, instance, args, kwargs): Wrapper for flask.app.Flask.finalize_request """ rv = wrapped(*args, **kwargs) - response = None - headers = None if getattr(rv, "is_sequence", False): - response = rv.response - headers = rv.headers - core.dispatch("flask.finalize_request.post", (response, headers)) + core.dispatch("flask.finalize_request.post", (rv.response, rv.headers)) return rv @@ -419,6 +419,12 @@ def _wrap(rule, endpoint=None, view_func=None, **kwargs): return _wrap(*args, **kwargs) +def patched_render_debugger_html(wrapped, instance, args, kwargs): + res = wrapped(*args, **kwargs) + core.dispatch("werkzeug.render_debugger_html", (res,)) + return res + + def patched_add_url_rule(wrapped, instance, args, kwargs): """Wrapper for flask.app.Flask.add_url_rule to wrap all views attached to this app""" diff --git a/ddtrace/contrib/internal/flask_cache/tracers.py b/ddtrace/contrib/internal/flask_cache/tracers.py index 2431ab2cbb7..9170bd4bd45 100644 --- a/ddtrace/contrib/internal/flask_cache/tracers.py +++ b/ddtrace/contrib/internal/flask_cache/tracers.py @@ -7,7 +7,7 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY -from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.ext import SpanTypes from ddtrace.ext import db from ddtrace.internal.constants import COMPONENT @@ -88,7 +88,7 @@ def __trace(self, cmd): s.set_tag_str(COMPONENT, config.flask_cache.integration_name) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) # set span tags s.set_tag_str(CACHE_BACKEND, self.config.get("CACHE_TYPE")) s.set_tags(self._datadog_meta) diff --git a/ddtrace/contrib/internal/google_generativeai/patch.py b/ddtrace/contrib/internal/google_generativeai/patch.py index 3564f9ec1ec..9cd28a531f4 100644 --- a/ddtrace/contrib/internal/google_generativeai/patch.py +++ b/ddtrace/contrib/internal/google_generativeai/patch.py @@ -9,9 +9,9 @@ from ddtrace.contrib.internal.google_generativeai._utils import _extract_api_key from ddtrace.contrib.internal.google_generativeai._utils import tag_request from ddtrace.contrib.internal.google_generativeai._utils import tag_response -from ddtrace.contrib.trace_utils import unwrap -from ddtrace.contrib.trace_utils import with_traced_module -from ddtrace.contrib.trace_utils import wrap +from ddtrace.contrib.internal.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import with_traced_module +from ddtrace.contrib.internal.trace_utils import wrap from ddtrace.llmobs._integrations import GeminiIntegration from ddtrace.llmobs._integrations.utils import extract_model_name_google from ddtrace.trace import Pin diff --git a/ddtrace/contrib/internal/graphql/patch.py b/ddtrace/contrib/internal/graphql/patch.py index 18916f4222a..589bef80d4b 100644 --- a/ddtrace/contrib/internal/graphql/patch.py +++ b/ddtrace/contrib/internal/graphql/patch.py @@ -24,9 +24,9 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_TYPE -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.ext import SpanTypes from ddtrace.internal.constants import COMPONENT @@ -177,7 +177,7 @@ def _traced_execute(func, args, kwargs): ) as span: span.set_tag_str(COMPONENT, config.graphql.integration_name) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) _set_span_operation_tags(span, document) span.set_tag_str(_GRAPHQL_SOURCE, source_str) @@ -207,7 +207,7 @@ def _traced_query(func, args, kwargs): span.set_tag_str(COMPONENT, config.graphql.integration_name) # mark span as measured and set sample rate - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) sample_rate = config.graphql.get_analytics_sample_rate() if sample_rate is not None: span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, sample_rate) diff --git a/ddtrace/contrib/internal/grpc/aio_client_interceptor.py b/ddtrace/contrib/internal/grpc/aio_client_interceptor.py index 5c03d1b8527..76455b1627f 100644 --- a/ddtrace/contrib/internal/grpc/aio_client_interceptor.py +++ b/ddtrace/contrib/internal/grpc/aio_client_interceptor.py @@ -14,10 +14,10 @@ from ddtrace import Span from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_TYPE from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.grpc import constants from ddtrace.contrib.internal.grpc import utils @@ -151,7 +151,7 @@ def _intercept_client_call(self, method_kind, client_call_details): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) utils.set_grpc_method_meta(span, method_as_str, method_kind) utils.set_grpc_client_meta(span, self._host, self._port) diff --git a/ddtrace/contrib/internal/grpc/aio_server_interceptor.py b/ddtrace/contrib/internal/grpc/aio_server_interceptor.py index d5ec9ed32ab..d2e7efb0e00 100644 --- a/ddtrace/contrib/internal/grpc/aio_server_interceptor.py +++ b/ddtrace/contrib/internal/grpc/aio_server_interceptor.py @@ -16,10 +16,10 @@ from ddtrace import Span # noqa:F401 from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_TYPE from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.grpc import constants from ddtrace.contrib.internal.grpc.utils import set_grpc_method_meta @@ -191,7 +191,7 @@ def _create_span(pin, method, invocation_metadata, method_kind): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.SERVER) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) set_grpc_method_meta(span, method, method_kind) span.set_tag_str(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_SERVER) diff --git a/ddtrace/contrib/internal/grpc/client_interceptor.py b/ddtrace/contrib/internal/grpc/client_interceptor.py index b389bbe71c8..b2f259c2f3e 100644 --- a/ddtrace/contrib/internal/grpc/client_interceptor.py +++ b/ddtrace/contrib/internal/grpc/client_interceptor.py @@ -5,11 +5,11 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.grpc import constants from ddtrace.contrib.internal.grpc import utils @@ -196,7 +196,7 @@ def _intercept_client_call(self, method_kind, client_call_details): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) utils.set_grpc_method_meta(span, client_call_details.method, method_kind) utils.set_grpc_client_meta(span, self._host, self._port) diff --git a/ddtrace/contrib/internal/grpc/patch.py b/ddtrace/contrib/internal/grpc/patch.py index 122893b030f..5e1053af28a 100644 --- a/ddtrace/contrib/internal/grpc/patch.py +++ b/ddtrace/contrib/internal/grpc/patch.py @@ -7,7 +7,7 @@ from ddtrace.contrib.internal.grpc.client_interceptor import create_client_interceptor from ddtrace.contrib.internal.grpc.client_interceptor import intercept_channel from ddtrace.contrib.internal.grpc.server_interceptor import create_server_interceptor -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.internal.logger import get_logger from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils import get_argument_value diff --git a/ddtrace/contrib/internal/grpc/server_interceptor.py b/ddtrace/contrib/internal/grpc/server_interceptor.py index e221f170e80..482ca8b23c5 100644 --- a/ddtrace/contrib/internal/grpc/server_interceptor.py +++ b/ddtrace/contrib/internal/grpc/server_interceptor.py @@ -3,10 +3,10 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_TYPE from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.grpc import constants from ddtrace.contrib.internal.grpc.utils import set_grpc_method_meta @@ -100,7 +100,7 @@ def _fn(self, method_kind, behavior, args, kwargs): # set span.kind tag equal to type of span span.set_tag_str(SPAN_KIND, SpanKind.SERVER) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) set_grpc_method_meta(span, self._handler_call_details.method, method_kind) span.set_tag_str(constants.GRPC_SPAN_KIND_KEY, constants.GRPC_SPAN_KIND_VALUE_SERVER) diff --git a/ddtrace/contrib/internal/httplib/patch.py b/ddtrace/contrib/internal/httplib/patch.py index 3e354aeedea..a1e367af3a1 100644 --- a/ddtrace/contrib/internal/httplib/patch.py +++ b/ddtrace/contrib/internal/httplib/patch.py @@ -9,7 +9,7 @@ from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY from ddtrace.constants import SPAN_KIND from ddtrace.contrib import trace_utils -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.internal.compat import httplib diff --git a/ddtrace/contrib/internal/httpx/patch.py b/ddtrace/contrib/internal/httpx/patch.py index 8a9e4eebc3a..1cf4e6f00e1 100644 --- a/ddtrace/contrib/internal/httpx/patch.py +++ b/ddtrace/contrib/internal/httpx/patch.py @@ -6,11 +6,11 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY -from ddtrace.contrib.trace_utils import distributed_tracing_enabled -from ddtrace.contrib.trace_utils import ext_service -from ddtrace.contrib.trace_utils import set_http_meta +from ddtrace.contrib.internal.trace_utils import distributed_tracing_enabled +from ddtrace.contrib.internal.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.internal.compat import ensure_binary @@ -86,7 +86,7 @@ def _get_service_name(pin, request): def _init_span(span, request): # type: (Span, httpx.Request) -> None - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) if distributed_tracing_enabled(config.httpx): HTTPPropagator.inject(span.context, request.headers) diff --git a/ddtrace/contrib/internal/jinja2/patch.py b/ddtrace/contrib/internal/jinja2/patch.py index cdf1254527d..6d044eb8ab8 100644 --- a/ddtrace/contrib/internal/jinja2/patch.py +++ b/ddtrace/contrib/internal/jinja2/patch.py @@ -4,8 +4,8 @@ from wrapt import wrap_function_wrapper as _w from ddtrace import config -from ddtrace.constants import SPAN_MEASURED_KEY -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.constants import _SPAN_MEASURED_KEY +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.ext import SpanTypes from ddtrace.internal.constants import COMPONENT from ddtrace.internal.utils import ArgumentError @@ -64,7 +64,7 @@ def _wrap_render(wrapped, instance, args, kwargs): with pin.tracer.trace("jinja2.render", pin.service, span_type=SpanTypes.TEMPLATE) as span: span.set_tag_str(COMPONENT, config.jinja2.integration_name) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) try: return wrapped(*args, **kwargs) finally: diff --git a/ddtrace/contrib/internal/kafka/patch.py b/ddtrace/contrib/internal/kafka/patch.py index 6f69cda3239..6818afbede0 100644 --- a/ddtrace/contrib/internal/kafka/patch.py +++ b/ddtrace/contrib/internal/kafka/patch.py @@ -6,8 +6,8 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -192,7 +192,7 @@ def traced_produce(func, instance, args, kwargs): span.set_tag(kafkax.PARTITION, partition) span.set_tag_str(kafkax.TOMBSTONE, str(value is None)) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) if instance._dd_bootstrap_servers is not None: span.set_tag_str(kafkax.HOST_LIST, instance._dd_bootstrap_servers) rate = config.kafka.get_analytics_sample_rate() @@ -289,7 +289,7 @@ def _instrument_message(messages, pin, start_ns, instance, err): pass span.set_tag_str(kafkax.TOMBSTONE, str(is_tombstone)) span.set_tag(kafkax.MESSAGE_OFFSET, message_offset) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) rate = config.kafka.get_analytics_sample_rate() if rate is not None: span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, rate) diff --git a/ddtrace/contrib/internal/kombu/patch.py b/ddtrace/contrib/internal/kombu/patch.py index fa63e5c4f86..9cbe8ac94a0 100644 --- a/ddtrace/contrib/internal/kombu/patch.py +++ b/ddtrace/contrib/internal/kombu/patch.py @@ -6,8 +6,8 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY # project from ddtrace.contrib import trace_utils @@ -117,7 +117,7 @@ def traced_receive(func, instance, args, kwargs): # set span.kind to the type of operation being performed s.set_tag_str(SPAN_KIND, SpanKind.CONSUMER) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) # run the command exchange = message.delivery_info["exchange"] s.resource = exchange @@ -147,7 +147,7 @@ def traced_publish(func, instance, args, kwargs): # set span.kind to the type of operation being performed s.set_tag_str(SPAN_KIND, SpanKind.PRODUCER) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) exchange_name = get_exchange_from_args(args) s.resource = exchange_name s.set_tag_str(kombux.EXCHANGE, exchange_name) diff --git a/ddtrace/contrib/internal/langchain/patch.py b/ddtrace/contrib/internal/langchain/patch.py index f9c58249cb2..430700e2782 100644 --- a/ddtrace/contrib/internal/langchain/patch.py +++ b/ddtrace/contrib/internal/langchain/patch.py @@ -51,9 +51,9 @@ from ddtrace.contrib.internal.langchain.constants import vectorstore_classes from ddtrace.contrib.internal.langchain.utils import shared_stream from ddtrace.contrib.internal.langchain.utils import tag_general_message_input -from ddtrace.contrib.trace_utils import unwrap -from ddtrace.contrib.trace_utils import with_traced_module -from ddtrace.contrib.trace_utils import wrap +from ddtrace.contrib.internal.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import with_traced_module +from ddtrace.contrib.internal.trace_utils import wrap from ddtrace.internal.logger import get_logger from ddtrace.internal.utils import ArgumentError from ddtrace.internal.utils import get_argument_value diff --git a/ddtrace/contrib/internal/logbook/patch.py b/ddtrace/contrib/internal/logbook/patch.py index 075b00787bc..7a1c1ae1484 100644 --- a/ddtrace/contrib/internal/logbook/patch.py +++ b/ddtrace/contrib/internal/logbook/patch.py @@ -9,7 +9,7 @@ from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_TRACE_ID from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_VALUE_EMPTY from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_VERSION -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.internal.utils import get_argument_value diff --git a/ddtrace/contrib/internal/logging/patch.py b/ddtrace/contrib/internal/logging/patch.py index 51508bae28f..11edd5d938f 100644 --- a/ddtrace/contrib/internal/logging/patch.py +++ b/ddtrace/contrib/internal/logging/patch.py @@ -4,7 +4,7 @@ import ddtrace from ddtrace import config -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.internal.utils import get_argument_value from .constants import RECORD_ATTR_ENV diff --git a/ddtrace/contrib/internal/loguru/patch.py b/ddtrace/contrib/internal/loguru/patch.py index e25aba4cff4..1dff8046f80 100644 --- a/ddtrace/contrib/internal/loguru/patch.py +++ b/ddtrace/contrib/internal/loguru/patch.py @@ -3,7 +3,7 @@ import ddtrace from ddtrace import config -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ..logging.constants import RECORD_ATTR_ENV from ..logging.constants import RECORD_ATTR_SERVICE diff --git a/ddtrace/contrib/internal/mako/patch.py b/ddtrace/contrib/internal/mako/patch.py index d39a51238a2..1f65ce7e34a 100644 --- a/ddtrace/contrib/internal/mako/patch.py +++ b/ddtrace/contrib/internal/mako/patch.py @@ -3,10 +3,10 @@ from mako.template import Template from ddtrace import config -from ddtrace.constants import SPAN_MEASURED_KEY -from ddtrace.contrib.trace_utils import int_service -from ddtrace.contrib.trace_utils import unwrap as _u -from ddtrace.contrib.trace_utils import wrap as _w +from ddtrace.constants import _SPAN_MEASURED_KEY +from ddtrace.contrib.internal.trace_utils import int_service +from ddtrace.contrib.internal.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import wrap as _w from ddtrace.ext import SpanTypes from ddtrace.internal.constants import COMPONENT from ddtrace.internal.schema import schematize_service_name @@ -63,7 +63,7 @@ def _wrap_render(wrapped, instance, args, kwargs): ) as span: span.set_tag_str(COMPONENT, "mako") - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) try: return wrapped(*args, **kwargs) finally: diff --git a/ddtrace/contrib/internal/molten/patch.py b/ddtrace/contrib/internal/molten/patch.py index 7c60d37d0d6..38fa949243c 100644 --- a/ddtrace/contrib/internal/molten/patch.py +++ b/ddtrace/contrib/internal/molten/patch.py @@ -6,10 +6,10 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.internal.compat import urlencode @@ -105,7 +105,7 @@ def patch_app_call(wrapped, instance, args, kwargs): # set span.kind tag equal to type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.SERVER) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) # set analytics sample rate with global config enabled span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, config.molten.get_analytics_sample_rate(use_global_config=True)) diff --git a/ddtrace/contrib/internal/mysql/patch.py b/ddtrace/contrib/internal/mysql/patch.py index d18d357d107..6425bd33766 100644 --- a/ddtrace/contrib/internal/mysql/patch.py +++ b/ddtrace/contrib/internal/mysql/patch.py @@ -7,7 +7,7 @@ from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION from ddtrace.contrib.dbapi import TracedConnection -from ddtrace.contrib.trace_utils import _convert_to_string +from ddtrace.contrib.internal.trace_utils import _convert_to_string from ddtrace.ext import db from ddtrace.ext import net from ddtrace.internal.schema import schematize_database_operation diff --git a/ddtrace/contrib/internal/mysqldb/patch.py b/ddtrace/contrib/internal/mysqldb/patch.py index 8b6aa7bb7f2..cde0f58629f 100644 --- a/ddtrace/contrib/internal/mysqldb/patch.py +++ b/ddtrace/contrib/internal/mysqldb/patch.py @@ -6,11 +6,11 @@ from ddtrace import config from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib.dbapi import TracedConnection -from ddtrace.contrib.trace_utils import _convert_to_string -from ddtrace.contrib.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import _convert_to_string +from ddtrace.contrib.internal.trace_utils import ext_service from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -101,7 +101,7 @@ def _connect(func, instance, args, kwargs): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) conn = func(*args, **kwargs) return patch_conn(conn, *args, **kwargs) diff --git a/ddtrace/contrib/internal/psycopg/async_connection.py b/ddtrace/contrib/internal/psycopg/async_connection.py index 72c8d70e7ec..f17b4c7a953 100644 --- a/ddtrace/contrib/internal/psycopg/async_connection.py +++ b/ddtrace/contrib/internal/psycopg/async_connection.py @@ -1,11 +1,11 @@ from ddtrace import config +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import dbapi_async from ddtrace.contrib.internal.psycopg.async_cursor import Psycopg3FetchTracedAsyncCursor from ddtrace.contrib.internal.psycopg.async_cursor import Psycopg3TracedAsyncCursor from ddtrace.contrib.internal.psycopg.connection import patch_conn -from ddtrace.contrib.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import ext_service from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -58,7 +58,7 @@ async def patched_connect_async(connect_func, _, args, kwargs): if span.get_tag(db.SYSTEM) is None: span.set_tag_str(db.SYSTEM, pin._config.dbms_name) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) conn = await connect_func(*args, **kwargs) return patch_conn(conn, pin=pin, traced_conn_cls=traced_conn_cls) diff --git a/ddtrace/contrib/internal/psycopg/connection.py b/ddtrace/contrib/internal/psycopg/connection.py index a5e5353ad13..6e9190421cb 100644 --- a/ddtrace/contrib/internal/psycopg/connection.py +++ b/ddtrace/contrib/internal/psycopg/connection.py @@ -1,13 +1,13 @@ from ddtrace import config +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import dbapi from ddtrace.contrib.internal.psycopg.cursor import Psycopg2FetchTracedCursor from ddtrace.contrib.internal.psycopg.cursor import Psycopg2TracedCursor from ddtrace.contrib.internal.psycopg.cursor import Psycopg3FetchTracedCursor from ddtrace.contrib.internal.psycopg.cursor import Psycopg3TracedCursor from ddtrace.contrib.internal.psycopg.extensions import _patch_extensions -from ddtrace.contrib.trace_utils import ext_service +from ddtrace.contrib.internal.trace_utils import ext_service from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -102,7 +102,7 @@ def patched_connect(connect_func, _, args, kwargs): if span.get_tag(db.SYSTEM) is None: span.set_tag_str(db.SYSTEM, pin._config.dbms_name) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) conn = connect_func(*args, **kwargs) return patch_conn(conn, pin=pin, traced_conn_cls=traced_conn_cls) diff --git a/ddtrace/contrib/internal/psycopg/extensions.py b/ddtrace/contrib/internal/psycopg/extensions.py index cebe5c1cdf8..f5960e073aa 100644 --- a/ddtrace/contrib/internal/psycopg/extensions.py +++ b/ddtrace/contrib/internal/psycopg/extensions.py @@ -6,8 +6,8 @@ import wrapt from ddtrace import config +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -42,7 +42,7 @@ def execute(self, query, vars=None): # noqa: A002 # set span.kind to the type of operation being performed s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) if s.context.sampling_priority is None or s.context.sampling_priority <= 0: return super(TracedCursor, self).execute(query, vars) diff --git a/ddtrace/contrib/internal/pylibmc/client.py b/ddtrace/contrib/internal/pylibmc/client.py index 3ea6f09c62c..5321b533293 100644 --- a/ddtrace/contrib/internal/pylibmc/client.py +++ b/ddtrace/contrib/internal/pylibmc/client.py @@ -8,8 +8,8 @@ import ddtrace from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib.internal.pylibmc.addrs import parse_addresses from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -177,7 +177,7 @@ def _span(self, cmd_name): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) try: self._tag_span(span) diff --git a/ddtrace/contrib/internal/pymemcache/client.py b/ddtrace/contrib/internal/pymemcache/client.py index 37e14842a94..574332586e5 100644 --- a/ddtrace/contrib/internal/pymemcache/client.py +++ b/ddtrace/contrib/internal/pymemcache/client.py @@ -18,8 +18,8 @@ # project from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -319,7 +319,7 @@ def _trace(func, p, method_name, *args, **kwargs): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) # set analytics sample rate span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, config.pymemcache.get_analytics_sample_rate()) diff --git a/ddtrace/contrib/internal/pymongo/client.py b/ddtrace/contrib/internal/pymongo/client.py index 2cdf2185586..269a3120fbd 100644 --- a/ddtrace/contrib/internal/pymongo/client.py +++ b/ddtrace/contrib/internal/pymongo/client.py @@ -12,8 +12,8 @@ import ddtrace from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -141,7 +141,7 @@ def _datadog_trace_operation(operation, wrapped): # set span.kind to the operation type being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) span.set_tag_str(mongox.DB, cmd.db) span.set_tag_str(mongox.COLLECTION, cmd.coll) span.set_tag_str(db.SYSTEM, mongox.SERVICE) @@ -265,7 +265,7 @@ def _trace_cmd(cmd, socket_instance, address): # set span.kind to the type of operation being performed s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) if cmd.db: s.set_tag_str(mongox.DB, cmd.db) if cmd: diff --git a/ddtrace/contrib/internal/pymongo/patch.py b/ddtrace/contrib/internal/pymongo/patch.py index 200a4a902b8..f37a07be5cd 100644 --- a/ddtrace/contrib/internal/pymongo/patch.py +++ b/ddtrace/contrib/internal/pymongo/patch.py @@ -3,8 +3,8 @@ import pymongo from ddtrace import config +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -129,7 +129,7 @@ def traced_get_socket(func, args, kwargs): with func(*args, **kwargs) as sock_info: set_address_tags(span, sock_info.address) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) # Ensure the pin used on the traced mongo client is passed down to the socket instance # (via the server instance) Pin.get_from(instance).onto(sock_info) diff --git a/ddtrace/contrib/internal/pymysql/patch.py b/ddtrace/contrib/internal/pymysql/patch.py index a9a16d50608..eab651bb784 100644 --- a/ddtrace/contrib/internal/pymysql/patch.py +++ b/ddtrace/contrib/internal/pymysql/patch.py @@ -5,7 +5,7 @@ from ddtrace import config from ddtrace.contrib.dbapi import TracedConnection -from ddtrace.contrib.trace_utils import _convert_to_string +from ddtrace.contrib.internal.trace_utils import _convert_to_string from ddtrace.ext import db from ddtrace.ext import net from ddtrace.internal.schema import schematize_database_operation diff --git a/ddtrace/contrib/internal/pynamodb/patch.py b/ddtrace/contrib/internal/pynamodb/patch.py index be4ba00c893..29bf44b8df9 100644 --- a/ddtrace/contrib/internal/pynamodb/patch.py +++ b/ddtrace/contrib/internal/pynamodb/patch.py @@ -7,10 +7,10 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils -from ddtrace.contrib.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import unwrap from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -70,7 +70,7 @@ def patched_api_call(original_func, instance, args, kwargs): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) try: operation = get_argument_value(args, kwargs, 0, "operation_name") diff --git a/ddtrace/contrib/internal/pyodbc/patch.py b/ddtrace/contrib/internal/pyodbc/patch.py index 180895a202e..8b51c5b425a 100644 --- a/ddtrace/contrib/internal/pyodbc/patch.py +++ b/ddtrace/contrib/internal/pyodbc/patch.py @@ -5,8 +5,8 @@ from ddtrace import config from ddtrace.contrib.dbapi import TracedConnection from ddtrace.contrib.dbapi import TracedCursor -from ddtrace.contrib.trace_utils import unwrap -from ddtrace.contrib.trace_utils import wrap +from ddtrace.contrib.internal.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import wrap from ddtrace.ext import db from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils.formats import asbool diff --git a/ddtrace/contrib/internal/pyramid/trace.py b/ddtrace/contrib/internal/pyramid/trace.py index cd0569f62df..9942c673d4e 100644 --- a/ddtrace/contrib/internal/pyramid/trace.py +++ b/ddtrace/contrib/internal/pyramid/trace.py @@ -7,8 +7,8 @@ import ddtrace from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -83,7 +83,7 @@ def trace_tween(request): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.SERVER) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) # Configure trace search sample rate # DEV: pyramid is special case maintains separate configuration from config api analytics_enabled = settings.get(SETTINGS_ANALYTICS_ENABLED) diff --git a/ddtrace/contrib/internal/pytest/_retry_utils.py b/ddtrace/contrib/internal/pytest/_retry_utils.py index eab45f049be..98133ec505a 100644 --- a/ddtrace/contrib/internal/pytest/_retry_utils.py +++ b/ddtrace/contrib/internal/pytest/_retry_utils.py @@ -117,7 +117,7 @@ def _retry_run_when(item, when, outcomes: RetryOutcomes) -> t.Tuple[CallInfo, _p ) else: call = CallInfo.from_call(lambda: hook(item=item), when=when) - report = pytest.TestReport.from_item_and_call(item=item, call=call) + report = item.ihook.pytest_runtest_makereport(item=item, call=call) if report.outcome == "passed": report.outcome = outcomes.PASSED elif report.outcome == "failed" or report.outcome == "error": diff --git a/ddtrace/contrib/internal/redis/asyncio_patch.py b/ddtrace/contrib/internal/redis/asyncio_patch.py index 7c5bad354ab..66a0fa8ba4c 100644 --- a/ddtrace/contrib/internal/redis/asyncio_patch.py +++ b/ddtrace/contrib/internal/redis/asyncio_patch.py @@ -2,7 +2,7 @@ from ddtrace._trace.utils_redis import _instrument_redis_cmd from ddtrace._trace.utils_redis import _instrument_redis_execute_async_cluster_pipeline from ddtrace._trace.utils_redis import _instrument_redis_execute_pipeline -from ddtrace.contrib.redis_utils import _run_redis_command_async +from ddtrace.contrib.internal.redis_utils import _run_redis_command_async from ddtrace.internal.utils.formats import stringify_cache_args from ddtrace.trace import Pin diff --git a/ddtrace/contrib/internal/redis/patch.py b/ddtrace/contrib/internal/redis/patch.py index 33520e5894d..28beeb4d979 100644 --- a/ddtrace/contrib/internal/redis/patch.py +++ b/ddtrace/contrib/internal/redis/patch.py @@ -6,9 +6,9 @@ from ddtrace import config from ddtrace._trace.utils_redis import _instrument_redis_cmd from ddtrace._trace.utils_redis import _instrument_redis_execute_pipeline +from ddtrace.contrib.internal.trace_utils import unwrap from ddtrace.contrib.redis_utils import ROW_RETURNING_COMMANDS from ddtrace.contrib.redis_utils import determine_row_count -from ddtrace.contrib.trace_utils import unwrap from ddtrace.internal import core from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils.formats import CMD_MAX_LEN diff --git a/ddtrace/contrib/internal/redis_utils.py b/ddtrace/contrib/internal/redis_utils.py new file mode 100644 index 00000000000..63d950389c8 --- /dev/null +++ b/ddtrace/contrib/internal/redis_utils.py @@ -0,0 +1,84 @@ +from typing import Dict +from typing import List +from typing import Optional +from typing import Union + +from ddtrace.ext import net +from ddtrace.ext import redis as redisx +from ddtrace.internal import core +from ddtrace.internal.utils.formats import stringify_cache_args + + +SINGLE_KEY_COMMANDS = [ + "GET", + "GETDEL", + "GETEX", + "GETRANGE", + "GETSET", + "LINDEX", + "LRANGE", + "RPOP", + "LPOP", + "HGET", + "HGETALL", + "HKEYS", + "HMGET", + "HRANDFIELD", + "HVALS", +] +MULTI_KEY_COMMANDS = ["MGET"] +ROW_RETURNING_COMMANDS = SINGLE_KEY_COMMANDS + MULTI_KEY_COMMANDS + + +def _extract_conn_tags(conn_kwargs): + """Transform redis conn info into dogtrace metas""" + try: + conn_tags = { + net.TARGET_HOST: conn_kwargs["host"], + net.TARGET_PORT: conn_kwargs["port"], + net.SERVER_ADDRESS: conn_kwargs["host"], + redisx.DB: conn_kwargs.get("db") or 0, + } + client_name = conn_kwargs.get("client_name") + if client_name: + conn_tags[redisx.CLIENT_NAME] = client_name + return conn_tags + except Exception: + return {} + + +def determine_row_count(redis_command: str, result: Optional[Union[List, Dict, str]]) -> int: + empty_results = [b"", [], {}, None] + # result can be an empty list / dict / string + if result not in empty_results: + if redis_command == "MGET": + # only include valid key results within count + result = [x for x in result if x not in empty_results] + return len(result) + elif redis_command == "HMGET": + # only include valid key results within count + result = [x for x in result if x not in empty_results] + return 1 if len(result) > 0 else 0 + else: + return 1 + else: + return 0 + + +async def _run_redis_command_async(ctx: core.ExecutionContext, func, args, kwargs): + parsed_command = stringify_cache_args(args) + redis_command = parsed_command.split(" ")[0] + rowcount = None + result = None + try: + result = await func(*args, **kwargs) + return result + except BaseException: + rowcount = 0 + raise + finally: + if rowcount is None: + rowcount = determine_row_count(redis_command=redis_command, result=result) + if redis_command not in ROW_RETURNING_COMMANDS: + rowcount = None + core.dispatch("redis.async_command.post", [ctx, rowcount]) diff --git a/ddtrace/contrib/internal/rediscluster/patch.py b/ddtrace/contrib/internal/rediscluster/patch.py index c550df7e9ea..23b37502310 100644 --- a/ddtrace/contrib/internal/rediscluster/patch.py +++ b/ddtrace/contrib/internal/rediscluster/patch.py @@ -7,8 +7,8 @@ # project from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.redis.patch import instrumented_execute_command from ddtrace.contrib.internal.redis.patch import instrumented_pipeline @@ -102,7 +102,7 @@ def traced_execute_pipeline(func, instance, args, kwargs): s.set_tag_str(SPAN_KIND, SpanKind.CLIENT) s.set_tag_str(COMPONENT, config.rediscluster.integration_name) s.set_tag_str(db.SYSTEM, redisx.APP) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) s.set_tag_str(redisx.RAWCMD, resource) s.set_metric(redisx.PIPELINE_LEN, len(instance.command_stack)) diff --git a/ddtrace/contrib/internal/requests/connection.py b/ddtrace/contrib/internal/requests/connection.py index 06d3347f0a1..c6d7706ef54 100644 --- a/ddtrace/contrib/internal/requests/connection.py +++ b/ddtrace/contrib/internal/requests/connection.py @@ -3,9 +3,10 @@ import ddtrace from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils +from ddtrace.contrib.internal.trace_utils import _sanitized_url from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.internal.compat import parse @@ -67,7 +68,7 @@ def _wrap_send(func, instance, args, kwargs): if not request: return func(*args, **kwargs) - url = trace_utils._sanitized_url(request.url) + url = _sanitized_url(request.url) method = "" if request.method is not None: method = request.method.upper() @@ -92,7 +93,7 @@ def _wrap_send(func, instance, args, kwargs): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) # Configure trace search sample rate # DEV: analytics enabled on per-session basis diff --git a/ddtrace/contrib/internal/requests/patch.py b/ddtrace/contrib/internal/requests/patch.py index d4ec1f5182d..6485bb7aed8 100644 --- a/ddtrace/contrib/internal/requests/patch.py +++ b/ddtrace/contrib/internal/requests/patch.py @@ -7,7 +7,7 @@ from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_request from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink from ddtrace.appsec._iast.constants import VULN_SSRF -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils.formats import asbool from ddtrace.settings.asm import config as asm_config diff --git a/ddtrace/contrib/internal/snowflake/patch.py b/ddtrace/contrib/internal/snowflake/patch.py index d28844ea992..86d11bb9372 100644 --- a/ddtrace/contrib/internal/snowflake/patch.py +++ b/ddtrace/contrib/internal/snowflake/patch.py @@ -5,7 +5,7 @@ from ddtrace import config from ddtrace.contrib.dbapi import TracedConnection from ddtrace.contrib.dbapi import TracedCursor -from ddtrace.contrib.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import unwrap from ddtrace.ext import db from ddtrace.ext import net from ddtrace.internal.schema import schematize_service_name diff --git a/ddtrace/contrib/internal/sqlalchemy/engine.py b/ddtrace/contrib/internal/sqlalchemy/engine.py index 3b5f96be9e7..6e123c97db8 100644 --- a/ddtrace/contrib/internal/sqlalchemy/engine.py +++ b/ddtrace/contrib/internal/sqlalchemy/engine.py @@ -19,8 +19,8 @@ import ddtrace from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.ext import db @@ -102,7 +102,7 @@ def _before_cur_exec(self, conn, cursor, statement, *args): # set span.kind to the type of operation being performed span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) if not _set_tags_from_url(span, conn.engine.url): _set_tags_from_cursor(span, self.vendor, cursor) diff --git a/ddtrace/contrib/internal/sqlalchemy/patch.py b/ddtrace/contrib/internal/sqlalchemy/patch.py index 399a3bfdb32..916cc53daa4 100644 --- a/ddtrace/contrib/internal/sqlalchemy/patch.py +++ b/ddtrace/contrib/internal/sqlalchemy/patch.py @@ -3,7 +3,7 @@ from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION -from ddtrace.contrib.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import unwrap from ddtrace.settings.asm import config as asm_config from .engine import _wrap_create_engine diff --git a/ddtrace/contrib/internal/starlette/patch.py b/ddtrace/contrib/internal/starlette/patch.py index 064722b67f1..e2ea8f9f4dc 100644 --- a/ddtrace/contrib/internal/starlette/patch.py +++ b/ddtrace/contrib/internal/starlette/patch.py @@ -17,7 +17,7 @@ from ddtrace.appsec._iast import _is_iast_enabled from ddtrace.contrib import trace_utils from ddtrace.contrib.asgi import TraceMiddleware -from ddtrace.contrib.trace_utils import with_traced_module +from ddtrace.contrib.internal.trace_utils import with_traced_module from ddtrace.ext import http from ddtrace.internal import core from ddtrace.internal._exceptions import BlockingException diff --git a/ddtrace/contrib/internal/structlog/patch.py b/ddtrace/contrib/internal/structlog/patch.py index b0ccd3c88db..d529b8d33a6 100644 --- a/ddtrace/contrib/internal/structlog/patch.py +++ b/ddtrace/contrib/internal/structlog/patch.py @@ -8,8 +8,8 @@ from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_TRACE_ID from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_VALUE_EMPTY from ddtrace.contrib.internal.logging.constants import RECORD_ATTR_VERSION -from ddtrace.contrib.trace_utils import unwrap as _u -from ddtrace.contrib.trace_utils import wrap as _w +from ddtrace.contrib.internal.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import wrap as _w from ddtrace.internal.utils import get_argument_value from ddtrace.internal.utils import set_argument_value diff --git a/ddtrace/contrib/internal/tornado/handlers.py b/ddtrace/contrib/internal/tornado/handlers.py index f5d6955cdee..3c4a046bfb9 100644 --- a/ddtrace/contrib/internal/tornado/handlers.py +++ b/ddtrace/contrib/internal/tornado/handlers.py @@ -4,10 +4,10 @@ from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils -from ddtrace.contrib.trace_utils import set_http_meta +from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes from ddtrace.internal.constants import COMPONENT @@ -50,7 +50,7 @@ def execute(func, handler, args, kwargs): # set span.kind to the type of operation being performed request_span.set_tag_str(SPAN_KIND, SpanKind.SERVER) - request_span.set_tag(SPAN_MEASURED_KEY) + request_span.set_tag(_SPAN_MEASURED_KEY) # set analytics sample rate # DEV: tornado is special case maintains separate configuration from config api analytics_enabled = settings["analytics_enabled"] diff --git a/ddtrace/contrib/internal/trace_utils.py b/ddtrace/contrib/internal/trace_utils.py new file mode 100644 index 00000000000..56901934e83 --- /dev/null +++ b/ddtrace/contrib/internal/trace_utils.py @@ -0,0 +1,707 @@ +""" +This module contains utility functions for writing ddtrace integrations. +""" + +from collections import deque +import ipaddress +import re +from typing import TYPE_CHECKING # noqa:F401 +from typing import Any # noqa:F401 +from typing import Callable # noqa:F401 +from typing import Dict # noqa:F401 +from typing import Generator # noqa:F401 +from typing import Iterator # noqa:F401 +from typing import List # noqa:F401 +from typing import Mapping # noqa:F401 +from typing import Optional # noqa:F401 +from typing import Tuple # noqa:F401 +from typing import Union # noqa:F401 +from typing import cast # noqa:F401 + +import wrapt + +from ddtrace import config +from ddtrace.ext import http +from ddtrace.ext import net +from ddtrace.ext import user +from ddtrace.internal import core +from ddtrace.internal.compat import ensure_text +from ddtrace.internal.compat import ip_is_global +from ddtrace.internal.compat import parse +from ddtrace.internal.logger import get_logger +from ddtrace.internal.utils.cache import cached +from ddtrace.internal.utils.http import normalize_header_name +from ddtrace.internal.utils.http import redact_url +from ddtrace.internal.utils.http import strip_query_string +import ddtrace.internal.utils.wrappers +from ddtrace.propagation.http import HTTPPropagator +from ddtrace.settings.asm import config as asm_config +from ddtrace.trace import Pin + + +if TYPE_CHECKING: # pragma: no cover + from ddtrace import Span # noqa:F401 + from ddtrace import Tracer # noqa:F401 + from ddtrace.settings import IntegrationConfig # noqa:F401 + + +log = get_logger(__name__) + +wrap = wrapt.wrap_function_wrapper +unwrap = ddtrace.internal.utils.wrappers.unwrap +iswrapped = ddtrace.internal.utils.wrappers.iswrapped + +REQUEST = "request" +RESPONSE = "response" + +# Tag normalization based on: https://docs.datadoghq.com/tagging/#defining-tags +# With the exception of '.' in header names which are replaced with '_' to avoid +# starting a "new object" on the UI. +NORMALIZE_PATTERN = re.compile(r"([^a-z0-9_\-:/]){1}") + +# Possible User Agent header. +USER_AGENT_PATTERNS = ("http-user-agent", "user-agent") + +IP_PATTERNS = ( + "x-forwarded-for", + "x-real-ip", + "true-client-ip", + "x-client-ip", + "forwarded-for", + "x-cluster-client-ip", + "fastly-client-ip", + "cf-connecting-ip", + "cf-connecting-ipv6", +) + + +@cached() +def _normalized_header_name(header_name): + # type: (str) -> str + return NORMALIZE_PATTERN.sub("_", normalize_header_name(header_name)) + + +def _get_header_value_case_insensitive(headers, keyname): + # type: (Mapping[str, str], str) -> Optional[str] + """ + Get a header in a case insensitive way. This function is meant for frameworks + like Django < 2.2 that don't store the headers in a case insensitive mapping. + """ + # just in case we are lucky + shortcut_value = headers.get(keyname) + if shortcut_value is not None: + return shortcut_value + + for key, value in headers.items(): + if key.lower().replace("_", "-") == keyname: + return value + + return None + + +def _normalize_tag_name(request_or_response, header_name): + # type: (str, str) -> str + """ + Given a tag name, e.g. 'Content-Type', returns a corresponding normalized tag name, i.e + 'http.request.headers.content_type'. Rules applied actual header name are: + - any letter is converted to lowercase + - any digit is left unchanged + - any block of any length of different ASCII chars is converted to a single underscore '_' + :param request_or_response: The context of the headers: request|response + :param header_name: The header's name + :type header_name: str + :rtype: str + """ + # Looking at: + # - http://www.iana.org/assignments/message-headers/message-headers.xhtml + # - https://tools.ietf.org/html/rfc6648 + # and for consistency with other language integrations seems safe to assume the following algorithm for header + # names normalization: + # - any letter is converted to lowercase + # - any digit is left unchanged + # - any block of any length of different ASCII chars is converted to a single underscore '_' + normalized_name = _normalized_header_name(header_name) + return "http.{}.headers.{}".format(request_or_response, normalized_name) + + +def _store_headers(headers, span, integration_config, request_or_response): + # type: (Dict[str, str], Span, IntegrationConfig, str) -> None + """ + :param headers: A dict of http headers to be stored in the span + :type headers: dict or list + :param span: The Span instance where tags will be stored + :type span: ddtrace._trace.span.Span + :param integration_config: An integration specific config object. + :type integration_config: ddtrace.settings.IntegrationConfig + """ + if not isinstance(headers, dict): + try: + headers = dict(headers) + except Exception: + return + + if integration_config is None: + log.debug("Skipping headers tracing as no integration config was provided") + return + + for header_name, header_value in headers.items(): + # config._header_tag_name gets an element of the dictionary in config._trace_http_header_tags + # which gets the value from DD_TRACE_HEADER_TAGS environment variable.""" + tag_name = integration_config._header_tag_name(header_name) + if tag_name is None: + continue + # An empty tag defaults to a http..headers.
tag + span.set_tag_str(tag_name or _normalize_tag_name(request_or_response, header_name), header_value) + + +def _get_request_header_user_agent(headers, headers_are_case_sensitive=False): + # type: (Mapping[str, str], bool) -> str + """Get user agent from request headers + :param headers: A dict of http headers to be stored in the span + :type headers: dict or list + """ + for key_pattern in USER_AGENT_PATTERNS: + if not headers_are_case_sensitive: + user_agent = headers.get(key_pattern) + else: + user_agent = _get_header_value_case_insensitive(headers, key_pattern) + + if user_agent: + return user_agent + return "" + + +def _get_request_header_client_ip(headers, peer_ip=None, headers_are_case_sensitive=False): + # type: (Optional[Mapping[str, str]], Optional[str], bool) -> str + + def get_header_value(key): # type: (str) -> Optional[str] + if not headers_are_case_sensitive: + return headers.get(key) + + return _get_header_value_case_insensitive(headers, key) + + if not headers: + try: + _ = ipaddress.ip_address(str(peer_ip)) + except ValueError: + return "" + return peer_ip + + ip_header_value = "" + user_configured_ip_header = config._client_ip_header + if user_configured_ip_header: + # Used selected the header to use to get the IP + ip_header_value = get_header_value( + user_configured_ip_header.lower().replace("_", "-") + if headers_are_case_sensitive + else user_configured_ip_header + ) + if not ip_header_value: + log.debug("DD_TRACE_CLIENT_IP_HEADER configured but '%s' header missing", user_configured_ip_header) + return "" + + try: + _ = ipaddress.ip_address(str(ip_header_value)) + except ValueError: + log.debug("Invalid IP address from configured %s header: %s", user_configured_ip_header, ip_header_value) + return "" + + else: + if headers_are_case_sensitive: + new_headers = {k.lower().replace("_", "-"): v for k, v in headers.items()} + for ip_header in IP_PATTERNS: + if ip_header in new_headers: + ip_header_value = new_headers[ip_header] + break + else: + for ip_header in IP_PATTERNS: + if ip_header in headers: + ip_header_value = headers[ip_header] + break + + private_ip_from_headers = "" + + if ip_header_value: + # At this point, we have one IP header, check its value and retrieve the first public IP + ip_list = ip_header_value.split(",") + for ip in ip_list: + ip = ip.strip() + if not ip: + continue + + try: + if ip_is_global(ip): + return ip + elif not private_ip_from_headers: + # IP is private, store it just in case we don't find a public one later + private_ip_from_headers = ip + except ValueError: # invalid IP + continue + + # At this point we have none or maybe one private ip from the headers: check the peer ip in + # case it's public and, if not, return either the private_ip from the headers (if we have one) + # or the peer private ip + try: + if ip_is_global(peer_ip) or not private_ip_from_headers: + return peer_ip + except ValueError: + pass + + return private_ip_from_headers + + +def _store_request_headers(headers, span, integration_config): + # type: (Dict[str, str], Span, IntegrationConfig) -> None + """ + Store request headers as a span's tags + :param headers: All the request's http headers, will be filtered through the whitelist + :type headers: dict or list + :param span: The Span instance where tags will be stored + :type span: ddtrace.Span + :param integration_config: An integration specific config object. + :type integration_config: ddtrace.settings.IntegrationConfig + """ + _store_headers(headers, span, integration_config, REQUEST) + + +def _store_response_headers(headers, span, integration_config): + # type: (Dict[str, str], Span, IntegrationConfig) -> None + """ + Store response headers as a span's tags + :param headers: All the response's http headers, will be filtered through the whitelist + :type headers: dict or list + :param span: The Span instance where tags will be stored + :type span: ddtrace.Span + :param integration_config: An integration specific config object. + :type integration_config: ddtrace.settings.IntegrationConfig + """ + _store_headers(headers, span, integration_config, RESPONSE) + + +def _sanitized_url(url): + # type: (str) -> str + """ + Sanitize url by removing parts with potential auth info + """ + if "@" in url: + parsed = parse.urlparse(url) + netloc = parsed.netloc + + if "@" not in netloc: + # Safe url, `@` not in netloc + return url + + netloc = netloc[netloc.index("@") + 1 :] + return parse.urlunparse( + ( + parsed.scheme, + netloc, + parsed.path, + "", + parsed.query, + "", + ) + ) + + return url + + +def with_traced_module(func): + """Helper for providing tracing essentials (module and pin) for tracing + wrappers. + + This helper enables tracing wrappers to dynamically be disabled when the + corresponding pin is disabled. + + Usage:: + + @with_traced_module + def my_traced_wrapper(django, pin, func, instance, args, kwargs): + # Do tracing stuff + pass + + def patch(): + import django + wrap(django.somefunc, my_traced_wrapper(django)) + """ + + def with_mod(mod): + def wrapper(wrapped, instance, args, kwargs): + pin = Pin._find(instance, mod) + if pin and not pin.enabled(): + return wrapped(*args, **kwargs) + elif not pin: + log.debug("Pin not found for traced method %r", wrapped) + return wrapped(*args, **kwargs) + return func(mod, pin, wrapped, instance, args, kwargs) + + return wrapper + + return with_mod + + +def distributed_tracing_enabled(int_config, default=False): + # type: (IntegrationConfig, bool) -> bool + """Returns whether distributed tracing is enabled for this integration config""" + if "distributed_tracing_enabled" in int_config and int_config.distributed_tracing_enabled is not None: + return int_config.distributed_tracing_enabled + elif "distributed_tracing" in int_config and int_config.distributed_tracing is not None: + return int_config.distributed_tracing + return default + + +def int_service(pin, int_config, default=None): + # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str] + """Returns the service name for an integration which is internal + to the application. Internal meaning that the work belongs to the + user's application. Eg. Web framework, sqlalchemy, web servers. + + For internal integrations we prioritize overrides, then global defaults and + lastly the default provided by the integration. + """ + # Pin has top priority since it is user defined in code + if pin is not None and pin.service: + return pin.service + + # Config is next since it is also configured via code + # Note that both service and service_name are used by + # integrations. + if "service" in int_config and int_config.service is not None: + return cast(str, int_config.service) + if "service_name" in int_config and int_config.service_name is not None: + return cast(str, int_config.service_name) + + global_service = int_config.global_config._get_service() + # We check if global_service != _inferred_base_service since global service (config.service) + # defaults to _inferred_base_service when no DD_SERVICE is set. In this case, we want to not + # use the inferred base service value, and instead use the integration default service. If we + # didn't do this, we would have a massive breaking change from adding inferred_base_service. + if global_service and global_service != int_config.global_config._inferred_base_service: + return cast(str, global_service) + + if "_default_service" in int_config and int_config._default_service is not None: + return cast(str, int_config._default_service) + + if default is None and global_service: + return cast(str, global_service) + + return default + + +def ext_service(pin, int_config, default=None): + # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str] + """Returns the service name for an integration which is external + to the application. External meaning that the integration generates + spans wrapping code that is outside the scope of the user's application. Eg. A database, RPC, cache, etc. + """ + if pin is not None and pin.service: + return pin.service + + if "service" in int_config and int_config.service is not None: + return cast(str, int_config.service) + if "service_name" in int_config and int_config.service_name is not None: + return cast(str, int_config.service_name) + + if "_default_service" in int_config and int_config._default_service is not None: + return cast(str, int_config._default_service) + + # A default is required since it's an external service. + return default + + +def _set_url_tag(integration_config, span, url, query): + # type: (IntegrationConfig, Span, str, str) -> None + if not integration_config.http_tag_query_string: + span.set_tag_str(http.URL, strip_query_string(url)) + elif config._global_query_string_obfuscation_disabled: + # TODO(munir): This case exists for backwards compatibility. To remove query strings from URLs, + # users should set ``DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING=False``. This case should be + # removed when config.global_query_string_obfuscation_disabled is removed (v3.0). + span.set_tag_str(http.URL, url) + elif getattr(config._obfuscation_query_string_pattern, "pattern", None) == b"": + # obfuscation is disabled when DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP="" + span.set_tag_str(http.URL, strip_query_string(url)) + else: + span.set_tag_str(http.URL, redact_url(url, config._obfuscation_query_string_pattern, query)) + + +def set_http_meta( + span, # type: Span + integration_config, # type: IntegrationConfig + method=None, # type: Optional[str] + url=None, # type: Optional[str] + target_host=None, # type: Optional[str] + server_address=None, # type: Optional[str] + status_code=None, # type: Optional[Union[int, str]] + status_msg=None, # type: Optional[str] + query=None, # type: Optional[str] + parsed_query=None, # type: Optional[Mapping[str, str]] + request_headers=None, # type: Optional[Mapping[str, str]] + response_headers=None, # type: Optional[Mapping[str, str]] + retries_remain=None, # type: Optional[Union[int, str]] + raw_uri=None, # type: Optional[str] + request_cookies=None, # type: Optional[Dict[str, str]] + request_path_params=None, # type: Optional[Dict[str, str]] + request_body=None, # type: Optional[Union[str, Dict[str, List[str]]]] + peer_ip=None, # type: Optional[str] + headers_are_case_sensitive=False, # type: bool + route=None, # type: Optional[str] + response_cookies=None, # type: Optional[Dict[str, str]] +): + # type: (...) -> None + """ + Set HTTP metas on the span + + :param method: the HTTP method + :param url: the HTTP URL + :param status_code: the HTTP status code + :param status_msg: the HTTP status message + :param query: the HTTP query part of the URI as a string + :param parsed_query: the HTTP query part of the URI as parsed by the framework and forwarded to the user code + :param request_headers: the HTTP request headers + :param response_headers: the HTTP response headers + :param raw_uri: the full raw HTTP URI (including ports and query) + :param request_cookies: the HTTP request cookies as a dict + :param request_path_params: the parameters of the HTTP URL as set by the framework: /posts/ would give us + { "id": } + """ + if method is not None: + span.set_tag_str(http.METHOD, method) + + if url is not None: + url = _sanitized_url(url) + _set_url_tag(integration_config, span, url, query) + + if target_host is not None: + span.set_tag_str(net.TARGET_HOST, target_host) + + if server_address is not None: + span.set_tag_str(net.SERVER_ADDRESS, server_address) + + if status_code is not None: + try: + int_status_code = int(status_code) + except (TypeError, ValueError): + log.debug("failed to convert http status code %r to int", status_code) + else: + span.set_tag_str(http.STATUS_CODE, str(status_code)) + if config.http_server.is_error_code(int_status_code): + span.error = 1 + + if status_msg is not None: + span.set_tag_str(http.STATUS_MSG, status_msg) + + if query is not None and integration_config.trace_query_string: + span.set_tag_str(http.QUERY_STRING, query) + + request_ip = peer_ip + if request_headers: + user_agent = _get_request_header_user_agent(request_headers, headers_are_case_sensitive) + if user_agent: + span.set_tag_str(http.USER_AGENT, user_agent) + + # We always collect the IP if appsec is enabled to report it on potential vulnerabilities. + # https://datadoghq.atlassian.net/wiki/spaces/APS/pages/2118779066/Client+IP+addresses+resolution + if asm_config._asm_enabled or config._retrieve_client_ip: + # Retrieve the IP if it was calculated on AppSecProcessor.on_span_start + request_ip = core.get_item("http.request.remote_ip", span=span) + + if not request_ip: + # Not calculated: framework does not support IP blocking or testing env + request_ip = ( + _get_request_header_client_ip(request_headers, peer_ip, headers_are_case_sensitive) or peer_ip + ) + + if request_ip: + span.set_tag_str(http.CLIENT_IP, request_ip) + span.set_tag_str("network.client.ip", request_ip) + + if integration_config.is_header_tracing_configured: + """We should store both http..headers. and + http.. The last one + is the DD standardized tag for user-agent""" + _store_request_headers(dict(request_headers), span, integration_config) + + if response_headers is not None and integration_config.is_header_tracing_configured: + _store_response_headers(dict(response_headers), span, integration_config) + + if retries_remain is not None: + span.set_tag_str(http.RETRIES_REMAIN, str(retries_remain)) + + core.dispatch( + "set_http_meta_for_asm", + [ + span, + request_ip, + raw_uri, + route, + method, + request_headers, + request_cookies, + parsed_query, + request_path_params, + request_body, + status_code, + response_headers, + response_cookies, + ], + ) + + if route is not None: + span.set_tag_str(http.ROUTE, route) + + +def activate_distributed_headers(tracer, int_config=None, request_headers=None, override=None): + # type: (Tracer, Optional[IntegrationConfig], Optional[Dict[str, str]], Optional[bool]) -> None + """ + Helper for activating a distributed trace headers' context if enabled in integration config. + int_config will be used to check if distributed trace headers context will be activated, but + override will override whatever value is set in int_config if passed any value other than None. + """ + if override is False: + return None + + if override or (int_config and distributed_tracing_enabled(int_config)): + context = HTTPPropagator.extract(request_headers) + + # Only need to activate the new context if something was propagated + # The new context must have one of these values in order for it to be activated + if not context.trace_id and not context._baggage and not context._span_links: + return None + # Do not reactivate a context with the same trace id + # DEV: An example could be nested web frameworks, when one layer already + # parsed request headers and activated them. + # + # Example:: + # + # app = Flask(__name__) # Traced via Flask instrumentation + # app = DDWSGIMiddleware(app) # Extra layer on top for WSGI + current_context = tracer.current_trace_context() + + # We accept incoming contexts with only baggage or only span_links, however if we + # already have a current_context then an incoming context not + # containing a trace_id or containing the same trace_id + # should not be activated. + if current_context and ( + not context.trace_id or (context.trace_id and context.trace_id == current_context.trace_id) + ): + log.debug( + "will not activate extracted Context(trace_id=%r, span_id=%r), a context with that trace id is already active", # noqa: E501 + context.trace_id, + context.span_id, + ) + return None + + # We have parsed a trace id from headers, and we do not already + # have a context with the same trace id active + tracer.context_provider.activate(context) + + +def _flatten( + obj, # type: Any + sep=".", # type: str + prefix="", # type: str + exclude_policy=None, # type: Optional[Callable[[str], bool]] +): + # type: (...) -> Generator[Tuple[str, Any], None, None] + s = deque() # type: ignore + s.append((prefix, obj)) + while s: + p, v = s.pop() + if exclude_policy is not None and exclude_policy(p): + continue + if isinstance(v, dict): + s.extend((sep.join((p, k)) if p else k, v) for k, v in v.items()) + else: + yield p, v + + +def set_flattened_tags( + span, # type: Span + items, # type: Iterator[Tuple[str, Any]] + sep=".", # type: str + exclude_policy=None, # type: Optional[Callable[[str], bool]] + processor=None, # type: Optional[Callable[[Any], Any]] +): + # type: (...) -> None + for prefix, value in items: + for tag, v in _flatten(value, sep, prefix, exclude_policy): + span.set_tag(tag, processor(v) if processor is not None else v) + + +def set_user( + tracer, # type: Tracer + user_id, # type: str + name=None, # type: Optional[str] + email=None, # type: Optional[str] + scope=None, # type: Optional[str] + role=None, # type: Optional[str] + session_id=None, # type: Optional[str] + propagate=False, # type bool + span=None, # type: Optional[Span] +): + # type: (...) -> None + """Set user tags. + https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#user-related-attributes + https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/?tab=set_tag&code-lang=python + """ + if span is None: + span = tracer.current_root_span() + if span: + if user_id: + str_user_id = str(user_id) + span.set_tag_str(user.ID, str_user_id) + if propagate: + span.context.dd_user_id = str_user_id + + # All other fields are optional + if name: + span.set_tag_str(user.NAME, name) + if email: + span.set_tag_str(user.EMAIL, email) + if scope: + span.set_tag_str(user.SCOPE, scope) + if role: + span.set_tag_str(user.ROLE, role) + if session_id: + span.set_tag_str(user.SESSION_ID, session_id) + + if asm_config._asm_enabled: + exc = core.dispatch_with_results("set_user_for_asm", [tracer, user_id]).block_user.exception + if exc: + raise exc + + else: + log.warning( + "No root span in the current execution. Skipping set_user tags. " + "See https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/" + "?tab=set_user&code-lang=python for more information.", + ) + + +def extract_netloc_and_query_info_from_url(url): + # type: (str) -> Tuple[str, str] + parse_result = parse.urlparse(url) + query = parse_result.query + + # Relative URLs don't have a netloc, so we force them + if not parse_result.netloc: + parse_result = parse.urlparse("//{url}".format(url=url)) + + netloc = parse_result.netloc.split("@", 1)[-1] # Discard auth info + netloc = netloc.split(":", 1)[0] # Discard port information + return netloc, query + + +class InterruptException(Exception): + pass + + +def _convert_to_string(attr): + # ensures attribute is converted to a string + if attr: + if isinstance(attr, int) or isinstance(attr, float): + return str(attr) + else: + return ensure_text(attr) + return attr diff --git a/ddtrace/contrib/internal/trace_utils_async.py b/ddtrace/contrib/internal/trace_utils_async.py new file mode 100644 index 00000000000..f58cc4e34bb --- /dev/null +++ b/ddtrace/contrib/internal/trace_utils_async.py @@ -0,0 +1,39 @@ +""" +async tracing utils + +Note that this module should only be imported in Python 3.5+. +""" +from ddtrace.internal.logger import get_logger +from ddtrace.trace import Pin + + +log = get_logger(__name__) + + +def with_traced_module(func): + """Async version of trace_utils.with_traced_module. + Usage:: + + @with_traced_module + async def my_traced_wrapper(django, pin, func, instance, args, kwargs): + # Do tracing stuff + pass + + def patch(): + import django + wrap(django.somefunc, my_traced_wrapper(django)) + """ + + def with_mod(mod): + async def wrapper(wrapped, instance, args, kwargs): + pin = Pin._find(instance, mod) + if pin and not pin.enabled(): + return await wrapped(*args, **kwargs) + elif not pin: + log.debug("Pin not found for traced method %r", wrapped) + return await wrapped(*args, **kwargs) + return await func(mod, pin, wrapped, instance, args, kwargs) + + return wrapper + + return with_mod diff --git a/ddtrace/contrib/internal/urllib/patch.py b/ddtrace/contrib/internal/urllib/patch.py index b71b2a85392..ed5e2891f06 100644 --- a/ddtrace/contrib/internal/urllib/patch.py +++ b/ddtrace/contrib/internal/urllib/patch.py @@ -5,7 +5,7 @@ from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_open from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink from ddtrace.appsec._iast.constants import VULN_SSRF -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.settings.asm import config as asm_config diff --git a/ddtrace/contrib/internal/vertexai/_utils.py b/ddtrace/contrib/internal/vertexai/_utils.py index 129b97fd920..81b0c13df2d 100644 --- a/ddtrace/contrib/internal/vertexai/_utils.py +++ b/ddtrace/contrib/internal/vertexai/_utils.py @@ -177,13 +177,13 @@ def _tag_request_content(span, integration, content, content_idx): tag_request_content_part_google("vertexai", span, integration, part, part_idx, content_idx) -def tag_request(span, integration, instance, args, kwargs): +def tag_request(span, integration, instance, args, kwargs, is_chat): """Tag the generation span with request details. Includes capturing generation configuration, system prompt, and messages. """ # instance is either a chat session or a model itself model_instance = instance if isinstance(instance, GenerativeModel) else instance._model - contents = get_argument_value(args, kwargs, 0, "contents") + contents = get_argument_value(args, kwargs, 0, "content" if is_chat else "contents") history = _get_attr(instance, "_history", []) if history: if isinstance(contents, list): diff --git a/ddtrace/contrib/internal/vertexai/patch.py b/ddtrace/contrib/internal/vertexai/patch.py index bc6e46903c3..54222b87528 100644 --- a/ddtrace/contrib/internal/vertexai/patch.py +++ b/ddtrace/contrib/internal/vertexai/patch.py @@ -4,13 +4,13 @@ import vertexai from ddtrace import config +from ddtrace.contrib.internal.trace_utils import unwrap +from ddtrace.contrib.internal.trace_utils import with_traced_module +from ddtrace.contrib.internal.trace_utils import wrap from ddtrace.contrib.internal.vertexai._utils import TracedAsyncVertexAIStreamResponse from ddtrace.contrib.internal.vertexai._utils import TracedVertexAIStreamResponse from ddtrace.contrib.internal.vertexai._utils import tag_request from ddtrace.contrib.internal.vertexai._utils import tag_response -from ddtrace.contrib.trace_utils import unwrap -from ddtrace.contrib.trace_utils import with_traced_module -from ddtrace.contrib.trace_utils import wrap from ddtrace.llmobs._integrations import VertexAIIntegration from ddtrace.llmobs._integrations.utils import extract_model_name_google from ddtrace.trace import Pin @@ -64,7 +64,7 @@ def _traced_generate(vertexai, pin, func, instance, args, kwargs, model_instance # history must be copied since it is modified during the LLM interaction history = getattr(instance, "history", [])[:] try: - tag_request(span, integration, instance, args, kwargs) + tag_request(span, integration, instance, args, kwargs, is_chat) generations = func(*args, **kwargs) if stream: return TracedVertexAIStreamResponse( @@ -99,7 +99,7 @@ async def _traced_agenerate(vertexai, pin, func, instance, args, kwargs, model_i # history must be copied since it is modified during the LLM interaction history = getattr(instance, "history", [])[:] try: - tag_request(span, integration, instance, args, kwargs) + tag_request(span, integration, instance, args, kwargs, is_chat) generations = await func(*args, **kwargs) if stream: return TracedAsyncVertexAIStreamResponse( diff --git a/ddtrace/contrib/internal/vertica/patch.py b/ddtrace/contrib/internal/vertica/patch.py index b365ade8c05..b223a17275d 100644 --- a/ddtrace/contrib/internal/vertica/patch.py +++ b/ddtrace/contrib/internal/vertica/patch.py @@ -5,8 +5,8 @@ import ddtrace from ddtrace import config from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import SPAN_KIND -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.contrib import trace_utils from ddtrace.ext import SpanKind from ddtrace.ext import SpanTypes @@ -233,7 +233,7 @@ def wrapper(wrapped, instance, args, kwargs): span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) if conf.get("measured", False): - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) span.set_tags(pin.tags) if "span_start" in conf: diff --git a/ddtrace/contrib/internal/webbrowser/patch.py b/ddtrace/contrib/internal/webbrowser/patch.py index 965425d9ad5..1387df37ac9 100644 --- a/ddtrace/contrib/internal/webbrowser/patch.py +++ b/ddtrace/contrib/internal/webbrowser/patch.py @@ -5,7 +5,7 @@ from ddtrace.appsec._common_module_patches import wrapped_request_D8CB81E472AF98A2 as _wrap_open from ddtrace.appsec._iast._metrics import _set_metric_iast_instrumented_sink from ddtrace.appsec._iast.constants import VULN_SSRF -from ddtrace.contrib.trace_utils import unwrap as _u +from ddtrace.contrib.internal.trace_utils import unwrap as _u from ddtrace.settings.asm import config as asm_config diff --git a/ddtrace/contrib/internal/yaaredis/patch.py b/ddtrace/contrib/internal/yaaredis/patch.py index 58c5a47bda4..f9cba77b5bb 100644 --- a/ddtrace/contrib/internal/yaaredis/patch.py +++ b/ddtrace/contrib/internal/yaaredis/patch.py @@ -6,7 +6,7 @@ from ddtrace import config from ddtrace._trace.utils_redis import _instrument_redis_cmd from ddtrace._trace.utils_redis import _instrument_redis_execute_pipeline -from ddtrace.contrib.redis_utils import _run_redis_command_async +from ddtrace.contrib.internal.redis_utils import _run_redis_command_async from ddtrace.internal.schema import schematize_service_name from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.internal.utils.formats import CMD_MAX_LEN diff --git a/ddtrace/contrib/jinja2/__init__.py b/ddtrace/contrib/jinja2/__init__.py index b10c98609a8..cc8c6786b02 100644 --- a/ddtrace/contrib/jinja2/__init__.py +++ b/ddtrace/contrib/jinja2/__init__.py @@ -36,10 +36,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.jinja2.patch import get_version -from ddtrace.contrib.internal.jinja2.patch import patch -from ddtrace.contrib.internal.jinja2.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.jinja2.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.jinja2.patch import patch # noqa: F401 +from ddtrace.contrib.internal.jinja2.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/kafka/__init__.py b/ddtrace/contrib/kafka/__init__.py index 355d3a99f48..de857bcb040 100644 --- a/ddtrace/contrib/kafka/__init__.py +++ b/ddtrace/contrib/kafka/__init__.py @@ -51,10 +51,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.kafka.patch import get_version -from ddtrace.contrib.internal.kafka.patch import patch -from ddtrace.contrib.internal.kafka.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.kafka.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.kafka.patch import patch # noqa: F401 +from ddtrace.contrib.internal.kafka.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/kombu/__init__.py b/ddtrace/contrib/kombu/__init__.py index 1a010892a7b..1c853adbd3f 100644 --- a/ddtrace/contrib/kombu/__init__.py +++ b/ddtrace/contrib/kombu/__init__.py @@ -32,7 +32,6 @@ # Use a pin to specify metadata related to this client Pin.override(producer, service='kombu-consumer') """ - from ddtrace.internal.utils.importlib import require_modules @@ -47,8 +46,5 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 - # Expose public methods - from ddtrace.contrib.internal.kombu.patch import get_version - from ddtrace.contrib.internal.kombu.patch import patch - - __all__ = ["patch", "get_version"] + from ddtrace.contrib.internal.kombu.patch import get_version # noqa: F401 + from ddtrace.contrib.internal.kombu.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/langchain/__init__.py b/ddtrace/contrib/langchain/__init__.py index 9f306fe07ed..1aa04d7a516 100644 --- a/ddtrace/contrib/langchain/__init__.py +++ b/ddtrace/contrib/langchain/__init__.py @@ -113,11 +113,10 @@ ``openai`` integration which traces requests to the OpenAI library. Alternatively, use :func:`patch() ` to manually enable the LangChain integration:: - from ddtrace import config, patch # Note: be sure to configure the integration before calling ``patch()``! - # eg. config.langchain["logs_enabled"] = True + # config.langchain["logs_enabled"] = True patch(langchain=True) @@ -216,10 +215,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.langchain.patch import get_version -from ddtrace.contrib.internal.langchain.patch import patch -from ddtrace.contrib.internal.langchain.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.langchain.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.langchain.patch import patch # noqa: F401 +from ddtrace.contrib.internal.langchain.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/logbook/__init__.py b/ddtrace/contrib/logbook/__init__.py index d42491706f2..e2a8f244820 100644 --- a/ddtrace/contrib/logbook/__init__.py +++ b/ddtrace/contrib/logbook/__init__.py @@ -57,10 +57,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.logbook.patch import get_version -from ddtrace.contrib.internal.logbook.patch import patch -from ddtrace.contrib.internal.logbook.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.logbook.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.logbook.patch import patch # noqa: F401 +from ddtrace.contrib.internal.logbook.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/logging/__init__.py b/ddtrace/contrib/logging/__init__.py index 24deca593a5..75454ce7c55 100644 --- a/ddtrace/contrib/logging/__init__.py +++ b/ddtrace/contrib/logging/__init__.py @@ -70,10 +70,7 @@ def hello(): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.logging.patch import get_version -from ddtrace.contrib.internal.logging.patch import patch -from ddtrace.contrib.internal.logging.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.logging.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.logging.patch import patch # noqa: F401 +from ddtrace.contrib.internal.logging.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/loguru/__init__.py b/ddtrace/contrib/loguru/__init__.py index 4fb56886108..9501fadd703 100644 --- a/ddtrace/contrib/loguru/__init__.py +++ b/ddtrace/contrib/loguru/__init__.py @@ -72,10 +72,7 @@ def log_format(record): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.loguru.patch import get_version -from ddtrace.contrib.internal.loguru.patch import patch -from ddtrace.contrib.internal.loguru.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.loguru.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.loguru.patch import patch # noqa: F401 +from ddtrace.contrib.internal.loguru.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/mako/__init__.py b/ddtrace/contrib/mako/__init__.py index 2dda665270e..5de3f87f6c5 100644 --- a/ddtrace/contrib/mako/__init__.py +++ b/ddtrace/contrib/mako/__init__.py @@ -1,11 +1,10 @@ """ The ``mako`` integration traces templates rendering. -Auto instrumentation is available using the ``patch``. The following is an example:: +Auto instrumentation is available using ``import ddtrace.auto``. The following is an example:: - from ddtrace import patch - from mako.template import Template + import ddtrace.auto - patch(mako=True) + from mako.template import Template t = Template(filename="index.html") @@ -19,11 +18,3 @@ with _w.catch_warnings(): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 - -# Expose public methods -from ddtrace.contrib.internal.mako.patch import get_version -from ddtrace.contrib.internal.mako.patch import patch -from ddtrace.contrib.internal.mako.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] diff --git a/ddtrace/contrib/mariadb/__init__.py b/ddtrace/contrib/mariadb/__init__.py index ea245ab37e7..8329fca2b63 100644 --- a/ddtrace/contrib/mariadb/__init__.py +++ b/ddtrace/contrib/mariadb/__init__.py @@ -62,10 +62,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.mariadb.patch import get_version -from ddtrace.contrib.internal.mariadb.patch import patch -from ddtrace.contrib.internal.mariadb.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.mariadb.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.mariadb.patch import patch # noqa: F401 +from ddtrace.contrib.internal.mariadb.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/molten/__init__.py b/ddtrace/contrib/molten/__init__.py index 4116e4fd8c7..44ef08de9bb 100644 --- a/ddtrace/contrib/molten/__init__.py +++ b/ddtrace/contrib/molten/__init__.py @@ -44,10 +44,7 @@ def hello(name: str, age: int) -> str: _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.molten.patch import get_version -from ddtrace.contrib.internal.molten.patch import patch -from ddtrace.contrib.internal.molten.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.molten.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.molten.patch import patch # noqa: F401 +from ddtrace.contrib.internal.molten.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/mongoengine/__init__.py b/ddtrace/contrib/mongoengine/__init__.py index eed76b32f4c..793d9cf4322 100644 --- a/ddtrace/contrib/mongoengine/__init__.py +++ b/ddtrace/contrib/mongoengine/__init__.py @@ -27,9 +27,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.mongoengine.patch import get_version -from ddtrace.contrib.internal.mongoengine.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.mongoengine.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.mongoengine.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/mysql/__init__.py b/ddtrace/contrib/mysql/__init__.py index 5fa835be709..60a4d638f78 100644 --- a/ddtrace/contrib/mysql/__init__.py +++ b/ddtrace/contrib/mysql/__init__.py @@ -75,9 +75,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.mysql.patch import get_version -from ddtrace.contrib.internal.mysql.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.mysql.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.mysql.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/mysqldb/__init__.py b/ddtrace/contrib/mysqldb/__init__.py index 81dd4b62c37..7649807e623 100644 --- a/ddtrace/contrib/mysqldb/__init__.py +++ b/ddtrace/contrib/mysqldb/__init__.py @@ -84,9 +84,6 @@ with _w.catch_warnings(): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.mysqldb.patch import get_version -from ddtrace.contrib.internal.mysqldb.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.mysqldb.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.mysqldb.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/openai/__init__.py b/ddtrace/contrib/openai/__init__.py index bf482c93913..88090b5f85a 100644 --- a/ddtrace/contrib/openai/__init__.py +++ b/ddtrace/contrib/openai/__init__.py @@ -257,10 +257,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.openai.patch import get_version -from ddtrace.contrib.internal.openai.patch import patch -from ddtrace.contrib.internal.openai.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.openai.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.openai.patch import patch # noqa: F401 +from ddtrace.contrib.internal.openai.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/protobuf/__init__.py b/ddtrace/contrib/protobuf/__init__.py index dd429d3aa83..41f0c76d101 100644 --- a/ddtrace/contrib/protobuf/__init__.py +++ b/ddtrace/contrib/protobuf/__init__.py @@ -15,10 +15,6 @@ ~~~~~~~~~~~~~ """ -# Expose public methods -from ..internal.protobuf.patch import get_version -from ..internal.protobuf.patch import patch -from ..internal.protobuf.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ..internal.protobuf.patch import get_version # noqa: F401 +from ..internal.protobuf.patch import patch # noqa: F401 +from ..internal.protobuf.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/psycopg/__init__.py b/ddtrace/contrib/psycopg/__init__.py index a747be2310a..22baf6ef522 100644 --- a/ddtrace/contrib/psycopg/__init__.py +++ b/ddtrace/contrib/psycopg/__init__.py @@ -68,10 +68,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.psycopg.patch import get_version -from ddtrace.contrib.internal.psycopg.patch import get_versions -from ddtrace.contrib.internal.psycopg.patch import patch - -__all__ = ["patch", "get_version", "get_versions"] +from ddtrace.contrib.internal.psycopg.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.psycopg.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/pylibmc/__init__.py b/ddtrace/contrib/pylibmc/__init__.py index a4ca6aa7692..6c21eab770e 100644 --- a/ddtrace/contrib/pylibmc/__init__.py +++ b/ddtrace/contrib/pylibmc/__init__.py @@ -28,10 +28,26 @@ with _w.catch_warnings(): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods + from ddtrace.contrib.internal.pylibmc.client import TracedClient -from ddtrace.contrib.internal.pylibmc.patch import get_version -from ddtrace.contrib.internal.pylibmc.patch import patch +from ddtrace.contrib.internal.pylibmc.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.pylibmc.patch import patch # noqa: F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate + + +def __getattr__(name): + if name in ("patch", "get_version"): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) -__all__ = ["TracedClient", "patch", "get_version"] +__all__ = ["TracedClient"] diff --git a/ddtrace/contrib/pymemcache/__init__.py b/ddtrace/contrib/pymemcache/__init__.py index 894359fb007..ef2e7b3ee82 100644 --- a/ddtrace/contrib/pymemcache/__init__.py +++ b/ddtrace/contrib/pymemcache/__init__.py @@ -40,10 +40,7 @@ with _w.catch_warnings(): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.pymemcache.patch import get_version -from ddtrace.contrib.internal.pymemcache.patch import patch -from ddtrace.contrib.internal.pymemcache.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.pymemcache.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.pymemcache.patch import patch # noqa: F401 +from ddtrace.contrib.internal.pymemcache.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/pymongo/__init__.py b/ddtrace/contrib/pymongo/__init__.py index a9363e65a04..9f030de942b 100644 --- a/ddtrace/contrib/pymongo/__init__.py +++ b/ddtrace/contrib/pymongo/__init__.py @@ -46,9 +46,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.pymongo.patch import get_version -from ddtrace.contrib.internal.pymongo.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.pymongo.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.pymongo.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/pymysql/__init__.py b/ddtrace/contrib/pymysql/__init__.py index bd0e36c6be8..b999934f285 100644 --- a/ddtrace/contrib/pymysql/__init__.py +++ b/ddtrace/contrib/pymysql/__init__.py @@ -64,9 +64,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.pymysql.patch import get_version -from ddtrace.contrib.internal.pymysql.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.pymysql.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.pymysql.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/pynamodb/__init__.py b/ddtrace/contrib/pynamodb/__init__.py index 3a84e603243..1362429219f 100644 --- a/ddtrace/contrib/pynamodb/__init__.py +++ b/ddtrace/contrib/pynamodb/__init__.py @@ -37,9 +37,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.pynamodb.patch import get_version -from ddtrace.contrib.internal.pynamodb.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.pynamodb.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.pynamodb.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/pyodbc/__init__.py b/ddtrace/contrib/pyodbc/__init__.py index bc7d2b3e9b3..0a9bfa60fab 100644 --- a/ddtrace/contrib/pyodbc/__init__.py +++ b/ddtrace/contrib/pyodbc/__init__.py @@ -63,9 +63,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.pyodbc.patch import get_version -from ddtrace.contrib.internal.pyodbc.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.pyodbc.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.pyodbc.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/pyramid/__init__.py b/ddtrace/contrib/pyramid/__init__.py index 72ef2613117..87aab191815 100644 --- a/ddtrace/contrib/pyramid/__init__.py +++ b/ddtrace/contrib/pyramid/__init__.py @@ -49,12 +49,28 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.pyramid.patch import get_version -from ddtrace.contrib.internal.pyramid.patch import patch + +from ddtrace.contrib.internal.pyramid.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.pyramid.patch import patch # noqa: F401 from ddtrace.contrib.internal.pyramid.trace import includeme from ddtrace.contrib.internal.pyramid.trace import trace_pyramid from ddtrace.contrib.internal.pyramid.trace import trace_tween_factory +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate + + +def __getattr__(name): + if name in ("patch", "get_version"): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) -__all__ = ["patch", "trace_pyramid", "trace_tween_factory", "includeme", "get_version"] +__all__ = ["trace_pyramid", "trace_tween_factory", "includeme"] diff --git a/ddtrace/contrib/pytest/__init__.py b/ddtrace/contrib/pytest/__init__.py index 0037949af50..31533b05e2b 100644 --- a/ddtrace/contrib/pytest/__init__.py +++ b/ddtrace/contrib/pytest/__init__.py @@ -61,14 +61,3 @@ Default: ``"pytest.test"`` """ from ddtrace.contrib.internal.pytest.patch import get_version # noqa: F401 -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate - - -deprecate( - ("%s is deprecated" % (__name__)), - message="Avoid using this package directly. " - "Use ``ddtrace.auto`` or the ``ddtrace-run`` command to enable and configure this integration.", - category=DDTraceDeprecationWarning, - removal_version="3.0.0", -) diff --git a/ddtrace/contrib/pytest_bdd/__init__.py b/ddtrace/contrib/pytest_bdd/__init__.py index 2e91392914d..62846aea395 100644 --- a/ddtrace/contrib/pytest_bdd/__init__.py +++ b/ddtrace/contrib/pytest_bdd/__init__.py @@ -21,18 +21,4 @@ for more details. """ -from ddtrace.contrib.internal.pytest_bdd.patch import get_version -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate - - -deprecate( - ("%s is deprecated" % (__name__)), - message="Avoid using this package directly. " - "Use ``ddtrace.auto`` or the ``ddtrace-run`` command to enable and configure this integration.", - category=DDTraceDeprecationWarning, - removal_version="3.0.0", -) - - -__all__ = ["get_version"] +from ddtrace.contrib.internal.pytest_bdd.patch import get_version # noqa: F401 diff --git a/ddtrace/contrib/pytest_benchmark/__init__.py b/ddtrace/contrib/pytest_benchmark/__init__.py index 3829deeb38a..e69de29bb2d 100644 --- a/ddtrace/contrib/pytest_benchmark/__init__.py +++ b/ddtrace/contrib/pytest_benchmark/__init__.py @@ -1,11 +0,0 @@ -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate - - -deprecate( - ("%s is deprecated" % (__name__)), - message="Avoid using this package directly. " - "Use ``ddtrace.auto`` or the ``ddtrace-run`` command to enable and configure this integration.", - category=DDTraceDeprecationWarning, - removal_version="3.0.0", -) diff --git a/ddtrace/contrib/redis/__init__.py b/ddtrace/contrib/redis/__init__.py index 9b498614e4b..28280510b0c 100644 --- a/ddtrace/contrib/redis/__init__.py +++ b/ddtrace/contrib/redis/__init__.py @@ -76,9 +76,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.redis.patch import get_version -from ddtrace.contrib.internal.redis.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.redis.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.redis.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/redis_utils.py b/ddtrace/contrib/redis_utils.py index 63d950389c8..08012fb2c65 100644 --- a/ddtrace/contrib/redis_utils.py +++ b/ddtrace/contrib/redis_utils.py @@ -1,84 +1 @@ -from typing import Dict -from typing import List -from typing import Optional -from typing import Union - -from ddtrace.ext import net -from ddtrace.ext import redis as redisx -from ddtrace.internal import core -from ddtrace.internal.utils.formats import stringify_cache_args - - -SINGLE_KEY_COMMANDS = [ - "GET", - "GETDEL", - "GETEX", - "GETRANGE", - "GETSET", - "LINDEX", - "LRANGE", - "RPOP", - "LPOP", - "HGET", - "HGETALL", - "HKEYS", - "HMGET", - "HRANDFIELD", - "HVALS", -] -MULTI_KEY_COMMANDS = ["MGET"] -ROW_RETURNING_COMMANDS = SINGLE_KEY_COMMANDS + MULTI_KEY_COMMANDS - - -def _extract_conn_tags(conn_kwargs): - """Transform redis conn info into dogtrace metas""" - try: - conn_tags = { - net.TARGET_HOST: conn_kwargs["host"], - net.TARGET_PORT: conn_kwargs["port"], - net.SERVER_ADDRESS: conn_kwargs["host"], - redisx.DB: conn_kwargs.get("db") or 0, - } - client_name = conn_kwargs.get("client_name") - if client_name: - conn_tags[redisx.CLIENT_NAME] = client_name - return conn_tags - except Exception: - return {} - - -def determine_row_count(redis_command: str, result: Optional[Union[List, Dict, str]]) -> int: - empty_results = [b"", [], {}, None] - # result can be an empty list / dict / string - if result not in empty_results: - if redis_command == "MGET": - # only include valid key results within count - result = [x for x in result if x not in empty_results] - return len(result) - elif redis_command == "HMGET": - # only include valid key results within count - result = [x for x in result if x not in empty_results] - return 1 if len(result) > 0 else 0 - else: - return 1 - else: - return 0 - - -async def _run_redis_command_async(ctx: core.ExecutionContext, func, args, kwargs): - parsed_command = stringify_cache_args(args) - redis_command = parsed_command.split(" ")[0] - rowcount = None - result = None - try: - result = await func(*args, **kwargs) - return result - except BaseException: - rowcount = 0 - raise - finally: - if rowcount is None: - rowcount = determine_row_count(redis_command=redis_command, result=result) - if redis_command not in ROW_RETURNING_COMMANDS: - rowcount = None - core.dispatch("redis.async_command.post", [ctx, rowcount]) +from ddtrace.contrib.internal.redis_utils import * # noqa: F403 diff --git a/ddtrace/contrib/rediscluster/__init__.py b/ddtrace/contrib/rediscluster/__init__.py index 65209053b97..d7b0e320fac 100644 --- a/ddtrace/contrib/rediscluster/__init__.py +++ b/ddtrace/contrib/rediscluster/__init__.py @@ -58,9 +58,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.rediscluster.patch import get_version -from ddtrace.contrib.internal.rediscluster.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.rediscluster.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.rediscluster.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/requests/__init__.py b/ddtrace/contrib/requests/__init__.py index 727e8219339..efcb20f1219 100644 --- a/ddtrace/contrib/requests/__init__.py +++ b/ddtrace/contrib/requests/__init__.py @@ -82,11 +82,27 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.requests.patch import get_version -from ddtrace.contrib.internal.requests.patch import patch -from ddtrace.contrib.internal.requests.patch import unpatch + +from ddtrace.contrib.internal.requests.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.requests.patch import patch # noqa: F401 +from ddtrace.contrib.internal.requests.patch import unpatch # noqa: F401 from ddtrace.contrib.internal.requests.session import TracedSession +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate + + +def __getattr__(name): + if name in ("patch", "unpatch", "get_version"): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) -__all__ = ["patch", "unpatch", "TracedSession", "get_version"] +__all__ = ["TracedSession"] diff --git a/ddtrace/contrib/rq/__init__.py b/ddtrace/contrib/rq/__init__.py index 0ce23d17984..0a4d83983eb 100644 --- a/ddtrace/contrib/rq/__init__.py +++ b/ddtrace/contrib/rq/__init__.py @@ -77,14 +77,3 @@ """ from ddtrace.contrib.internal.rq.patch import * # noqa: F403 -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate - - -deprecate( - ("%s is deprecated" % (__name__)), - message="Avoid using this package directly. " - "Use ``ddtrace.auto`` or the ``ddtrace-run`` command to enable and configure this integration.", - category=DDTraceDeprecationWarning, - removal_version="3.0.0", -) diff --git a/ddtrace/contrib/sanic/__init__.py b/ddtrace/contrib/sanic/__init__.py index 96f47d156ac..7fab620b8fd 100644 --- a/ddtrace/contrib/sanic/__init__.py +++ b/ddtrace/contrib/sanic/__init__.py @@ -65,10 +65,7 @@ def index(request): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.sanic.patch import get_version -from ddtrace.contrib.internal.sanic.patch import patch -from ddtrace.contrib.internal.sanic.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.sanic.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.sanic.patch import patch # noqa: F401 +from ddtrace.contrib.internal.sanic.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/selenium/__init__.py b/ddtrace/contrib/selenium/__init__.py index f5016ce0254..411fd47f7ee 100644 --- a/ddtrace/contrib/selenium/__init__.py +++ b/ddtrace/contrib/selenium/__init__.py @@ -22,10 +22,7 @@ DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS: The time in milliseconds to wait after flushing the RUM session. """ -# Expose public methods -from ..internal.selenium.patch import get_version -from ..internal.selenium.patch import patch -from ..internal.selenium.patch import unpatch - -__all__ = ["get_version", "patch", "unpatch"] +from ..internal.selenium.patch import get_version # noqa: F401 +from ..internal.selenium.patch import patch # noqa: F401 +from ..internal.selenium.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/snowflake/__init__.py b/ddtrace/contrib/snowflake/__init__.py index db137130214..3bf4e42d5a5 100644 --- a/ddtrace/contrib/snowflake/__init__.py +++ b/ddtrace/contrib/snowflake/__init__.py @@ -74,10 +74,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.snowflake.patch import get_version -from ddtrace.contrib.internal.snowflake.patch import patch -from ddtrace.contrib.internal.snowflake.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.snowflake.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.snowflake.patch import patch # noqa: F401 +from ddtrace.contrib.internal.snowflake.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/sqlalchemy/__init__.py b/ddtrace/contrib/sqlalchemy/__init__.py index cc4d1775a44..c9b0f47715b 100644 --- a/ddtrace/contrib/sqlalchemy/__init__.py +++ b/ddtrace/contrib/sqlalchemy/__init__.py @@ -30,11 +30,29 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.sqlalchemy.engine import trace_engine -from ddtrace.contrib.internal.sqlalchemy.patch import get_version -from ddtrace.contrib.internal.sqlalchemy.patch import patch -from ddtrace.contrib.internal.sqlalchemy.patch import unpatch - -__all__ = ["trace_engine", "patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.sqlalchemy.engine import trace_engine +from ddtrace.contrib.internal.sqlalchemy.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.sqlalchemy.patch import patch # noqa: F401 +from ddtrace.contrib.internal.sqlalchemy.patch import unpatch # noqa: F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate + + +def __getattr__(name): + if name in ("patch", "unpatch", "get_version"): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Avoid using this package directly. " + "Set DD_TRACE_SQLALCHEMY_ENABLED=true and use ``import ddtrace.auto`` or the " + "``ddtrace-run`` command to enable and configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) + + +__all__ = ["trace_engine"] diff --git a/ddtrace/contrib/sqlite3/__init__.py b/ddtrace/contrib/sqlite3/__init__.py index adf3ea061d6..3be49e96642 100644 --- a/ddtrace/contrib/sqlite3/__init__.py +++ b/ddtrace/contrib/sqlite3/__init__.py @@ -63,9 +63,6 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.sqlite3.patch import get_version -from ddtrace.contrib.internal.sqlite3.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.sqlite3.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.sqlite3.patch import patch # noqa: F401 diff --git a/ddtrace/contrib/starlette/__init__.py b/ddtrace/contrib/starlette/__init__.py index d3327feded4..2fe121ef976 100644 --- a/ddtrace/contrib/starlette/__init__.py +++ b/ddtrace/contrib/starlette/__init__.py @@ -67,10 +67,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.starlette.patch import get_version -from ddtrace.contrib.internal.starlette.patch import patch -from ddtrace.contrib.internal.starlette.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.starlette.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.starlette.patch import patch # noqa: F401 +from ddtrace.contrib.internal.starlette.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/structlog/__init__.py b/ddtrace/contrib/structlog/__init__.py index 0a9bcb56395..77770bb3da5 100644 --- a/ddtrace/contrib/structlog/__init__.py +++ b/ddtrace/contrib/structlog/__init__.py @@ -47,10 +47,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.structlog.patch import get_version -from ddtrace.contrib.internal.structlog.patch import patch -from ddtrace.contrib.internal.structlog.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.structlog.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.structlog.patch import patch # noqa: F401 +from ddtrace.contrib.internal.structlog.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/subprocess/__init__.py b/ddtrace/contrib/subprocess/__init__.py index 2e4969648d1..8e95b25b646 100644 --- a/ddtrace/contrib/subprocess/__init__.py +++ b/ddtrace/contrib/subprocess/__init__.py @@ -28,10 +28,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.subprocess.patch import get_version -from ddtrace.contrib.internal.subprocess.patch import patch -from ddtrace.contrib.internal.subprocess.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.subprocess.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.subprocess.patch import patch # noqa: F401 +from ddtrace.contrib.internal.subprocess.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/tornado/__init__.py b/ddtrace/contrib/tornado/__init__.py index 10390e77e6e..403739448be 100644 --- a/ddtrace/contrib/tornado/__init__.py +++ b/ddtrace/contrib/tornado/__init__.py @@ -109,20 +109,17 @@ def log_exception(self, typ, value, tb): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.tornado.patch import get_version -from ddtrace.contrib.internal.tornado.patch import patch -from ddtrace.contrib.internal.tornado.patch import unpatch + +from ddtrace.contrib.internal.tornado.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.tornado.patch import patch # noqa: F401 +from ddtrace.contrib.internal.tornado.patch import unpatch # noqa: F401 from ddtrace.contrib.internal.tornado.stack_context import TracerStackContext from ddtrace.contrib.internal.tornado.stack_context import context_provider from ddtrace.contrib.internal.tornado.stack_context import run_with_trace_context __all__ = [ - "patch", - "unpatch", "context_provider", "run_with_trace_context", "TracerStackContext", - "get_version", ] diff --git a/ddtrace/contrib/trace_utils.py b/ddtrace/contrib/trace_utils.py index 56901934e83..c1d2054d23e 100644 --- a/ddtrace/contrib/trace_utils.py +++ b/ddtrace/contrib/trace_utils.py @@ -1,707 +1,42 @@ -""" -This module contains utility functions for writing ddtrace integrations. -""" - -from collections import deque -import ipaddress -import re -from typing import TYPE_CHECKING # noqa:F401 -from typing import Any # noqa:F401 -from typing import Callable # noqa:F401 -from typing import Dict # noqa:F401 -from typing import Generator # noqa:F401 -from typing import Iterator # noqa:F401 -from typing import List # noqa:F401 -from typing import Mapping # noqa:F401 -from typing import Optional # noqa:F401 -from typing import Tuple # noqa:F401 -from typing import Union # noqa:F401 -from typing import cast # noqa:F401 - -import wrapt - -from ddtrace import config -from ddtrace.ext import http -from ddtrace.ext import net -from ddtrace.ext import user -from ddtrace.internal import core -from ddtrace.internal.compat import ensure_text -from ddtrace.internal.compat import ip_is_global -from ddtrace.internal.compat import parse -from ddtrace.internal.logger import get_logger -from ddtrace.internal.utils.cache import cached -from ddtrace.internal.utils.http import normalize_header_name -from ddtrace.internal.utils.http import redact_url -from ddtrace.internal.utils.http import strip_query_string -import ddtrace.internal.utils.wrappers -from ddtrace.propagation.http import HTTPPropagator -from ddtrace.settings.asm import config as asm_config -from ddtrace.trace import Pin - - -if TYPE_CHECKING: # pragma: no cover - from ddtrace import Span # noqa:F401 - from ddtrace import Tracer # noqa:F401 - from ddtrace.settings import IntegrationConfig # noqa:F401 - - -log = get_logger(__name__) - -wrap = wrapt.wrap_function_wrapper -unwrap = ddtrace.internal.utils.wrappers.unwrap -iswrapped = ddtrace.internal.utils.wrappers.iswrapped - -REQUEST = "request" -RESPONSE = "response" - -# Tag normalization based on: https://docs.datadoghq.com/tagging/#defining-tags -# With the exception of '.' in header names which are replaced with '_' to avoid -# starting a "new object" on the UI. -NORMALIZE_PATTERN = re.compile(r"([^a-z0-9_\-:/]){1}") - -# Possible User Agent header. -USER_AGENT_PATTERNS = ("http-user-agent", "user-agent") - -IP_PATTERNS = ( - "x-forwarded-for", - "x-real-ip", - "true-client-ip", - "x-client-ip", - "forwarded-for", - "x-cluster-client-ip", - "fastly-client-ip", - "cf-connecting-ip", - "cf-connecting-ipv6", -) - - -@cached() -def _normalized_header_name(header_name): - # type: (str) -> str - return NORMALIZE_PATTERN.sub("_", normalize_header_name(header_name)) - - -def _get_header_value_case_insensitive(headers, keyname): - # type: (Mapping[str, str], str) -> Optional[str] - """ - Get a header in a case insensitive way. This function is meant for frameworks - like Django < 2.2 that don't store the headers in a case insensitive mapping. - """ - # just in case we are lucky - shortcut_value = headers.get(keyname) - if shortcut_value is not None: - return shortcut_value - - for key, value in headers.items(): - if key.lower().replace("_", "-") == keyname: - return value - - return None - - -def _normalize_tag_name(request_or_response, header_name): - # type: (str, str) -> str - """ - Given a tag name, e.g. 'Content-Type', returns a corresponding normalized tag name, i.e - 'http.request.headers.content_type'. Rules applied actual header name are: - - any letter is converted to lowercase - - any digit is left unchanged - - any block of any length of different ASCII chars is converted to a single underscore '_' - :param request_or_response: The context of the headers: request|response - :param header_name: The header's name - :type header_name: str - :rtype: str - """ - # Looking at: - # - http://www.iana.org/assignments/message-headers/message-headers.xhtml - # - https://tools.ietf.org/html/rfc6648 - # and for consistency with other language integrations seems safe to assume the following algorithm for header - # names normalization: - # - any letter is converted to lowercase - # - any digit is left unchanged - # - any block of any length of different ASCII chars is converted to a single underscore '_' - normalized_name = _normalized_header_name(header_name) - return "http.{}.headers.{}".format(request_or_response, normalized_name) - - -def _store_headers(headers, span, integration_config, request_or_response): - # type: (Dict[str, str], Span, IntegrationConfig, str) -> None - """ - :param headers: A dict of http headers to be stored in the span - :type headers: dict or list - :param span: The Span instance where tags will be stored - :type span: ddtrace._trace.span.Span - :param integration_config: An integration specific config object. - :type integration_config: ddtrace.settings.IntegrationConfig - """ - if not isinstance(headers, dict): - try: - headers = dict(headers) - except Exception: - return - - if integration_config is None: - log.debug("Skipping headers tracing as no integration config was provided") - return - - for header_name, header_value in headers.items(): - # config._header_tag_name gets an element of the dictionary in config._trace_http_header_tags - # which gets the value from DD_TRACE_HEADER_TAGS environment variable.""" - tag_name = integration_config._header_tag_name(header_name) - if tag_name is None: - continue - # An empty tag defaults to a http..headers.
tag - span.set_tag_str(tag_name or _normalize_tag_name(request_or_response, header_name), header_value) - - -def _get_request_header_user_agent(headers, headers_are_case_sensitive=False): - # type: (Mapping[str, str], bool) -> str - """Get user agent from request headers - :param headers: A dict of http headers to be stored in the span - :type headers: dict or list - """ - for key_pattern in USER_AGENT_PATTERNS: - if not headers_are_case_sensitive: - user_agent = headers.get(key_pattern) - else: - user_agent = _get_header_value_case_insensitive(headers, key_pattern) - - if user_agent: - return user_agent - return "" - - -def _get_request_header_client_ip(headers, peer_ip=None, headers_are_case_sensitive=False): - # type: (Optional[Mapping[str, str]], Optional[str], bool) -> str - - def get_header_value(key): # type: (str) -> Optional[str] - if not headers_are_case_sensitive: - return headers.get(key) - - return _get_header_value_case_insensitive(headers, key) - - if not headers: - try: - _ = ipaddress.ip_address(str(peer_ip)) - except ValueError: - return "" - return peer_ip - - ip_header_value = "" - user_configured_ip_header = config._client_ip_header - if user_configured_ip_header: - # Used selected the header to use to get the IP - ip_header_value = get_header_value( - user_configured_ip_header.lower().replace("_", "-") - if headers_are_case_sensitive - else user_configured_ip_header - ) - if not ip_header_value: - log.debug("DD_TRACE_CLIENT_IP_HEADER configured but '%s' header missing", user_configured_ip_header) - return "" - - try: - _ = ipaddress.ip_address(str(ip_header_value)) - except ValueError: - log.debug("Invalid IP address from configured %s header: %s", user_configured_ip_header, ip_header_value) - return "" - - else: - if headers_are_case_sensitive: - new_headers = {k.lower().replace("_", "-"): v for k, v in headers.items()} - for ip_header in IP_PATTERNS: - if ip_header in new_headers: - ip_header_value = new_headers[ip_header] - break - else: - for ip_header in IP_PATTERNS: - if ip_header in headers: - ip_header_value = headers[ip_header] - break - - private_ip_from_headers = "" - - if ip_header_value: - # At this point, we have one IP header, check its value and retrieve the first public IP - ip_list = ip_header_value.split(",") - for ip in ip_list: - ip = ip.strip() - if not ip: - continue - - try: - if ip_is_global(ip): - return ip - elif not private_ip_from_headers: - # IP is private, store it just in case we don't find a public one later - private_ip_from_headers = ip - except ValueError: # invalid IP - continue - - # At this point we have none or maybe one private ip from the headers: check the peer ip in - # case it's public and, if not, return either the private_ip from the headers (if we have one) - # or the peer private ip - try: - if ip_is_global(peer_ip) or not private_ip_from_headers: - return peer_ip - except ValueError: - pass - - return private_ip_from_headers - - -def _store_request_headers(headers, span, integration_config): - # type: (Dict[str, str], Span, IntegrationConfig) -> None - """ - Store request headers as a span's tags - :param headers: All the request's http headers, will be filtered through the whitelist - :type headers: dict or list - :param span: The Span instance where tags will be stored - :type span: ddtrace.Span - :param integration_config: An integration specific config object. - :type integration_config: ddtrace.settings.IntegrationConfig - """ - _store_headers(headers, span, integration_config, REQUEST) - - -def _store_response_headers(headers, span, integration_config): - # type: (Dict[str, str], Span, IntegrationConfig) -> None - """ - Store response headers as a span's tags - :param headers: All the response's http headers, will be filtered through the whitelist - :type headers: dict or list - :param span: The Span instance where tags will be stored - :type span: ddtrace.Span - :param integration_config: An integration specific config object. - :type integration_config: ddtrace.settings.IntegrationConfig - """ - _store_headers(headers, span, integration_config, RESPONSE) - - -def _sanitized_url(url): - # type: (str) -> str - """ - Sanitize url by removing parts with potential auth info - """ - if "@" in url: - parsed = parse.urlparse(url) - netloc = parsed.netloc - - if "@" not in netloc: - # Safe url, `@` not in netloc - return url - - netloc = netloc[netloc.index("@") + 1 :] - return parse.urlunparse( - ( - parsed.scheme, - netloc, - parsed.path, - "", - parsed.query, - "", - ) - ) - - return url - - -def with_traced_module(func): - """Helper for providing tracing essentials (module and pin) for tracing - wrappers. - - This helper enables tracing wrappers to dynamically be disabled when the - corresponding pin is disabled. - - Usage:: - - @with_traced_module - def my_traced_wrapper(django, pin, func, instance, args, kwargs): - # Do tracing stuff - pass - - def patch(): - import django - wrap(django.somefunc, my_traced_wrapper(django)) - """ - - def with_mod(mod): - def wrapper(wrapped, instance, args, kwargs): - pin = Pin._find(instance, mod) - if pin and not pin.enabled(): - return wrapped(*args, **kwargs) - elif not pin: - log.debug("Pin not found for traced method %r", wrapped) - return wrapped(*args, **kwargs) - return func(mod, pin, wrapped, instance, args, kwargs) - - return wrapper - - return with_mod - - -def distributed_tracing_enabled(int_config, default=False): - # type: (IntegrationConfig, bool) -> bool - """Returns whether distributed tracing is enabled for this integration config""" - if "distributed_tracing_enabled" in int_config and int_config.distributed_tracing_enabled is not None: - return int_config.distributed_tracing_enabled - elif "distributed_tracing" in int_config and int_config.distributed_tracing is not None: - return int_config.distributed_tracing - return default - - -def int_service(pin, int_config, default=None): - # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str] - """Returns the service name for an integration which is internal - to the application. Internal meaning that the work belongs to the - user's application. Eg. Web framework, sqlalchemy, web servers. - - For internal integrations we prioritize overrides, then global defaults and - lastly the default provided by the integration. - """ - # Pin has top priority since it is user defined in code - if pin is not None and pin.service: - return pin.service - - # Config is next since it is also configured via code - # Note that both service and service_name are used by - # integrations. - if "service" in int_config and int_config.service is not None: - return cast(str, int_config.service) - if "service_name" in int_config and int_config.service_name is not None: - return cast(str, int_config.service_name) - - global_service = int_config.global_config._get_service() - # We check if global_service != _inferred_base_service since global service (config.service) - # defaults to _inferred_base_service when no DD_SERVICE is set. In this case, we want to not - # use the inferred base service value, and instead use the integration default service. If we - # didn't do this, we would have a massive breaking change from adding inferred_base_service. - if global_service and global_service != int_config.global_config._inferred_base_service: - return cast(str, global_service) - - if "_default_service" in int_config and int_config._default_service is not None: - return cast(str, int_config._default_service) - - if default is None and global_service: - return cast(str, global_service) - - return default - - -def ext_service(pin, int_config, default=None): - # type: (Optional[Pin], IntegrationConfig, Optional[str]) -> Optional[str] - """Returns the service name for an integration which is external - to the application. External meaning that the integration generates - spans wrapping code that is outside the scope of the user's application. Eg. A database, RPC, cache, etc. - """ - if pin is not None and pin.service: - return pin.service - - if "service" in int_config and int_config.service is not None: - return cast(str, int_config.service) - if "service_name" in int_config and int_config.service_name is not None: - return cast(str, int_config.service_name) - - if "_default_service" in int_config and int_config._default_service is not None: - return cast(str, int_config._default_service) - - # A default is required since it's an external service. - return default - - -def _set_url_tag(integration_config, span, url, query): - # type: (IntegrationConfig, Span, str, str) -> None - if not integration_config.http_tag_query_string: - span.set_tag_str(http.URL, strip_query_string(url)) - elif config._global_query_string_obfuscation_disabled: - # TODO(munir): This case exists for backwards compatibility. To remove query strings from URLs, - # users should set ``DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING=False``. This case should be - # removed when config.global_query_string_obfuscation_disabled is removed (v3.0). - span.set_tag_str(http.URL, url) - elif getattr(config._obfuscation_query_string_pattern, "pattern", None) == b"": - # obfuscation is disabled when DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP="" - span.set_tag_str(http.URL, strip_query_string(url)) - else: - span.set_tag_str(http.URL, redact_url(url, config._obfuscation_query_string_pattern, query)) - - -def set_http_meta( - span, # type: Span - integration_config, # type: IntegrationConfig - method=None, # type: Optional[str] - url=None, # type: Optional[str] - target_host=None, # type: Optional[str] - server_address=None, # type: Optional[str] - status_code=None, # type: Optional[Union[int, str]] - status_msg=None, # type: Optional[str] - query=None, # type: Optional[str] - parsed_query=None, # type: Optional[Mapping[str, str]] - request_headers=None, # type: Optional[Mapping[str, str]] - response_headers=None, # type: Optional[Mapping[str, str]] - retries_remain=None, # type: Optional[Union[int, str]] - raw_uri=None, # type: Optional[str] - request_cookies=None, # type: Optional[Dict[str, str]] - request_path_params=None, # type: Optional[Dict[str, str]] - request_body=None, # type: Optional[Union[str, Dict[str, List[str]]]] - peer_ip=None, # type: Optional[str] - headers_are_case_sensitive=False, # type: bool - route=None, # type: Optional[str] - response_cookies=None, # type: Optional[Dict[str, str]] -): - # type: (...) -> None - """ - Set HTTP metas on the span - - :param method: the HTTP method - :param url: the HTTP URL - :param status_code: the HTTP status code - :param status_msg: the HTTP status message - :param query: the HTTP query part of the URI as a string - :param parsed_query: the HTTP query part of the URI as parsed by the framework and forwarded to the user code - :param request_headers: the HTTP request headers - :param response_headers: the HTTP response headers - :param raw_uri: the full raw HTTP URI (including ports and query) - :param request_cookies: the HTTP request cookies as a dict - :param request_path_params: the parameters of the HTTP URL as set by the framework: /posts/ would give us - { "id": } - """ - if method is not None: - span.set_tag_str(http.METHOD, method) - - if url is not None: - url = _sanitized_url(url) - _set_url_tag(integration_config, span, url, query) - - if target_host is not None: - span.set_tag_str(net.TARGET_HOST, target_host) - - if server_address is not None: - span.set_tag_str(net.SERVER_ADDRESS, server_address) - - if status_code is not None: - try: - int_status_code = int(status_code) - except (TypeError, ValueError): - log.debug("failed to convert http status code %r to int", status_code) - else: - span.set_tag_str(http.STATUS_CODE, str(status_code)) - if config.http_server.is_error_code(int_status_code): - span.error = 1 - - if status_msg is not None: - span.set_tag_str(http.STATUS_MSG, status_msg) - - if query is not None and integration_config.trace_query_string: - span.set_tag_str(http.QUERY_STRING, query) - - request_ip = peer_ip - if request_headers: - user_agent = _get_request_header_user_agent(request_headers, headers_are_case_sensitive) - if user_agent: - span.set_tag_str(http.USER_AGENT, user_agent) - - # We always collect the IP if appsec is enabled to report it on potential vulnerabilities. - # https://datadoghq.atlassian.net/wiki/spaces/APS/pages/2118779066/Client+IP+addresses+resolution - if asm_config._asm_enabled or config._retrieve_client_ip: - # Retrieve the IP if it was calculated on AppSecProcessor.on_span_start - request_ip = core.get_item("http.request.remote_ip", span=span) - - if not request_ip: - # Not calculated: framework does not support IP blocking or testing env - request_ip = ( - _get_request_header_client_ip(request_headers, peer_ip, headers_are_case_sensitive) or peer_ip - ) - - if request_ip: - span.set_tag_str(http.CLIENT_IP, request_ip) - span.set_tag_str("network.client.ip", request_ip) - - if integration_config.is_header_tracing_configured: - """We should store both http..headers. and - http.. The last one - is the DD standardized tag for user-agent""" - _store_request_headers(dict(request_headers), span, integration_config) - - if response_headers is not None and integration_config.is_header_tracing_configured: - _store_response_headers(dict(response_headers), span, integration_config) - - if retries_remain is not None: - span.set_tag_str(http.RETRIES_REMAIN, str(retries_remain)) - - core.dispatch( - "set_http_meta_for_asm", - [ - span, - request_ip, - raw_uri, - route, - method, - request_headers, - request_cookies, - parsed_query, - request_path_params, - request_body, - status_code, - response_headers, - response_cookies, - ], - ) - - if route is not None: - span.set_tag_str(http.ROUTE, route) - - -def activate_distributed_headers(tracer, int_config=None, request_headers=None, override=None): - # type: (Tracer, Optional[IntegrationConfig], Optional[Dict[str, str]], Optional[bool]) -> None - """ - Helper for activating a distributed trace headers' context if enabled in integration config. - int_config will be used to check if distributed trace headers context will be activated, but - override will override whatever value is set in int_config if passed any value other than None. - """ - if override is False: - return None - - if override or (int_config and distributed_tracing_enabled(int_config)): - context = HTTPPropagator.extract(request_headers) - - # Only need to activate the new context if something was propagated - # The new context must have one of these values in order for it to be activated - if not context.trace_id and not context._baggage and not context._span_links: - return None - # Do not reactivate a context with the same trace id - # DEV: An example could be nested web frameworks, when one layer already - # parsed request headers and activated them. - # - # Example:: - # - # app = Flask(__name__) # Traced via Flask instrumentation - # app = DDWSGIMiddleware(app) # Extra layer on top for WSGI - current_context = tracer.current_trace_context() - - # We accept incoming contexts with only baggage or only span_links, however if we - # already have a current_context then an incoming context not - # containing a trace_id or containing the same trace_id - # should not be activated. - if current_context and ( - not context.trace_id or (context.trace_id and context.trace_id == current_context.trace_id) - ): - log.debug( - "will not activate extracted Context(trace_id=%r, span_id=%r), a context with that trace id is already active", # noqa: E501 - context.trace_id, - context.span_id, - ) - return None - - # We have parsed a trace id from headers, and we do not already - # have a context with the same trace id active - tracer.context_provider.activate(context) - - -def _flatten( - obj, # type: Any - sep=".", # type: str - prefix="", # type: str - exclude_policy=None, # type: Optional[Callable[[str], bool]] -): - # type: (...) -> Generator[Tuple[str, Any], None, None] - s = deque() # type: ignore - s.append((prefix, obj)) - while s: - p, v = s.pop() - if exclude_policy is not None and exclude_policy(p): - continue - if isinstance(v, dict): - s.extend((sep.join((p, k)) if p else k, v) for k, v in v.items()) - else: - yield p, v - - -def set_flattened_tags( - span, # type: Span - items, # type: Iterator[Tuple[str, Any]] - sep=".", # type: str - exclude_policy=None, # type: Optional[Callable[[str], bool]] - processor=None, # type: Optional[Callable[[Any], Any]] -): - # type: (...) -> None - for prefix, value in items: - for tag, v in _flatten(value, sep, prefix, exclude_policy): - span.set_tag(tag, processor(v) if processor is not None else v) - - -def set_user( - tracer, # type: Tracer - user_id, # type: str - name=None, # type: Optional[str] - email=None, # type: Optional[str] - scope=None, # type: Optional[str] - role=None, # type: Optional[str] - session_id=None, # type: Optional[str] - propagate=False, # type bool - span=None, # type: Optional[Span] -): - # type: (...) -> None - """Set user tags. - https://docs.datadoghq.com/logs/log_configuration/attributes_naming_convention/#user-related-attributes - https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/?tab=set_tag&code-lang=python - """ - if span is None: - span = tracer.current_root_span() - if span: - if user_id: - str_user_id = str(user_id) - span.set_tag_str(user.ID, str_user_id) - if propagate: - span.context.dd_user_id = str_user_id - - # All other fields are optional - if name: - span.set_tag_str(user.NAME, name) - if email: - span.set_tag_str(user.EMAIL, email) - if scope: - span.set_tag_str(user.SCOPE, scope) - if role: - span.set_tag_str(user.ROLE, role) - if session_id: - span.set_tag_str(user.SESSION_ID, session_id) - - if asm_config._asm_enabled: - exc = core.dispatch_with_results("set_user_for_asm", [tracer, user_id]).block_user.exception - if exc: - raise exc - - else: - log.warning( - "No root span in the current execution. Skipping set_user tags. " - "See https://docs.datadoghq.com/security_platform/application_security/setup_and_configure/" - "?tab=set_user&code-lang=python for more information.", +from ddtrace.contrib.internal.redis_utils import MULTI_KEY_COMMANDS # noqa: F401 +from ddtrace.contrib.internal.redis_utils import ROW_RETURNING_COMMANDS # noqa: F401 +from ddtrace.contrib.internal.redis_utils import SINGLE_KEY_COMMANDS # noqa: F401 +from ddtrace.contrib.internal.redis_utils import determine_row_count # noqa: F401 +from ddtrace.contrib.internal.trace_utils import IP_PATTERNS # noqa: F401 +from ddtrace.contrib.internal.trace_utils import NORMALIZE_PATTERN # noqa: F401 +from ddtrace.contrib.internal.trace_utils import REQUEST # noqa: F401 +from ddtrace.contrib.internal.trace_utils import RESPONSE # noqa: F401 +from ddtrace.contrib.internal.trace_utils import USER_AGENT_PATTERNS # noqa: F401 +from ddtrace.contrib.internal.trace_utils import activate_distributed_headers # noqa: F401 +from ddtrace.contrib.internal.trace_utils import distributed_tracing_enabled # noqa: F401 +from ddtrace.contrib.internal.trace_utils import ext_service # noqa: F401 +from ddtrace.contrib.internal.trace_utils import extract_netloc_and_query_info_from_url # noqa: F401 +from ddtrace.contrib.internal.trace_utils import int_service # noqa: F401 +from ddtrace.contrib.internal.trace_utils import iswrapped # noqa: F401 +from ddtrace.contrib.internal.trace_utils import set_flattened_tags # noqa: F401 +from ddtrace.contrib.internal.trace_utils import set_http_meta # noqa: F401 +from ddtrace.contrib.internal.trace_utils import set_user # noqa: F401 +from ddtrace.contrib.internal.trace_utils import unwrap # noqa: F401 +from ddtrace.contrib.internal.trace_utils import with_traced_module # noqa: F401 +from ddtrace.contrib.internal.trace_utils import wrap # noqa: F401 +from ddtrace.contrib.internal.trace_utils_async import with_traced_module as with_traced_module_async # noqa: F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate + + +# TODO: Add trace utils that should not be public to the list below +_DEPRECATED_MODULE_ATTRIBUTES = [] + + +def __getattr__(name): + if name in _DEPRECATED_MODULE_ATTRIBUTES: + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + category=DDTraceDeprecationWarning, + removal_version="3.0.0", ) + if name in globals(): + return globals()[name] -def extract_netloc_and_query_info_from_url(url): - # type: (str) -> Tuple[str, str] - parse_result = parse.urlparse(url) - query = parse_result.query - - # Relative URLs don't have a netloc, so we force them - if not parse_result.netloc: - parse_result = parse.urlparse("//{url}".format(url=url)) - - netloc = parse_result.netloc.split("@", 1)[-1] # Discard auth info - netloc = netloc.split(":", 1)[0] # Discard port information - return netloc, query - - -class InterruptException(Exception): - pass - - -def _convert_to_string(attr): - # ensures attribute is converted to a string - if attr: - if isinstance(attr, int) or isinstance(attr, float): - return str(attr) - else: - return ensure_text(attr) - return attr + raise AttributeError("%s has no attribute %s", __name__, name) diff --git a/ddtrace/contrib/trace_utils_async.py b/ddtrace/contrib/trace_utils_async.py index f58cc4e34bb..46d4e8762a3 100644 --- a/ddtrace/contrib/trace_utils_async.py +++ b/ddtrace/contrib/trace_utils_async.py @@ -1,39 +1 @@ -""" -async tracing utils - -Note that this module should only be imported in Python 3.5+. -""" -from ddtrace.internal.logger import get_logger -from ddtrace.trace import Pin - - -log = get_logger(__name__) - - -def with_traced_module(func): - """Async version of trace_utils.with_traced_module. - Usage:: - - @with_traced_module - async def my_traced_wrapper(django, pin, func, instance, args, kwargs): - # Do tracing stuff - pass - - def patch(): - import django - wrap(django.somefunc, my_traced_wrapper(django)) - """ - - def with_mod(mod): - async def wrapper(wrapped, instance, args, kwargs): - pin = Pin._find(instance, mod) - if pin and not pin.enabled(): - return await wrapped(*args, **kwargs) - elif not pin: - log.debug("Pin not found for traced method %r", wrapped) - return await wrapped(*args, **kwargs) - return await func(mod, pin, wrapped, instance, args, kwargs) - - return wrapper - - return with_mod +from ddtrace.contrib.internal.trace_utils_async import * # noqa: F403 diff --git a/ddtrace/contrib/trace_utils_redis.py b/ddtrace/contrib/trace_utils_redis.py index 8df16c3ce4d..b8aff5eef23 100644 --- a/ddtrace/contrib/trace_utils_redis.py +++ b/ddtrace/contrib/trace_utils_redis.py @@ -1,14 +1,5 @@ -from ddtrace.contrib.redis_utils import determine_row_count -from ddtrace.contrib.redis_utils import stringify_cache_args -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate - - -deprecate( - "The ddtrace.contrib.trace_utils_redis module is deprecated and will be removed.", - message="A new interface will be provided by the ddtrace.contrib.redis_utils module", - category=DDTraceDeprecationWarning, -) +from ddtrace.contrib.internal.redis_utils import determine_row_count +from ddtrace.contrib.internal.redis_utils import stringify_cache_args format_command_args = stringify_cache_args diff --git a/ddtrace/contrib/unittest/__init__.py b/ddtrace/contrib/unittest/__init__.py index 43a1e8a740c..173d22cac50 100644 --- a/ddtrace/contrib/unittest/__init__.py +++ b/ddtrace/contrib/unittest/__init__.py @@ -34,18 +34,6 @@ Default: ``True`` """ -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning -from ddtrace.vendor.debtcollector import deprecate - from ..internal.unittest.patch import get_version # noqa: F401 from ..internal.unittest.patch import patch # noqa: F401 from ..internal.unittest.patch import unpatch # noqa: F401 - - -deprecate( - ("%s is deprecated" % (__name__)), - message="Avoid using this package directly. " - "Use ``ddtrace.auto`` or the ``ddtrace-run`` command to enable and configure this integration.", - category=DDTraceDeprecationWarning, - removal_version="3.0.0", -) diff --git a/ddtrace/contrib/urllib/__init__.py b/ddtrace/contrib/urllib/__init__.py index 596251e99ad..bc9fae37949 100644 --- a/ddtrace/contrib/urllib/__init__.py +++ b/ddtrace/contrib/urllib/__init__.py @@ -14,10 +14,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.urllib.patch import get_version -from ddtrace.contrib.internal.urllib.patch import patch -from ddtrace.contrib.internal.urllib.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.urllib.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.urllib.patch import patch # noqa: F401 +from ddtrace.contrib.internal.urllib.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/urllib3/__init__.py b/ddtrace/contrib/urllib3/__init__.py index 0f2ebf2b12f..e0cec320abd 100644 --- a/ddtrace/contrib/urllib3/__init__.py +++ b/ddtrace/contrib/urllib3/__init__.py @@ -60,10 +60,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.urllib3.patch import get_version -from ddtrace.contrib.internal.urllib3.patch import patch -from ddtrace.contrib.internal.urllib3.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.urllib3.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.urllib3.patch import patch # noqa: F401 +from ddtrace.contrib.internal.urllib3.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/vertexai/__init__.py b/ddtrace/contrib/vertexai/__init__.py index 80597e0c8ff..5a372a9bd52 100644 --- a/ddtrace/contrib/vertexai/__init__.py +++ b/ddtrace/contrib/vertexai/__init__.py @@ -84,9 +84,6 @@ """ # noqa: E501 -from ddtrace.contrib.internal.vertexai.patch import get_version -from ddtrace.contrib.internal.vertexai.patch import patch -from ddtrace.contrib.internal.vertexai.patch import unpatch - - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.vertexai.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.vertexai.patch import patch # noqa: F401 +from ddtrace.contrib.internal.vertexai.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/vertica/__init__.py b/ddtrace/contrib/vertica/__init__.py index 7271c1c92ad..2bcc2c3d987 100644 --- a/ddtrace/contrib/vertica/__init__.py +++ b/ddtrace/contrib/vertica/__init__.py @@ -48,10 +48,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.vertica.patch import get_version -from ddtrace.contrib.internal.vertica.patch import patch -from ddtrace.contrib.internal.vertica.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.vertica.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.vertica.patch import patch # noqa: F401 +from ddtrace.contrib.internal.vertica.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/webbrowser/__init__.py b/ddtrace/contrib/webbrowser/__init__.py index 49a251cad50..74160b052e3 100644 --- a/ddtrace/contrib/webbrowser/__init__.py +++ b/ddtrace/contrib/webbrowser/__init__.py @@ -14,10 +14,7 @@ _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.webbrowser.patch import get_version -from ddtrace.contrib.internal.webbrowser.patch import patch -from ddtrace.contrib.internal.webbrowser.patch import unpatch - -__all__ = ["patch", "unpatch", "get_version"] +from ddtrace.contrib.internal.webbrowser.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.webbrowser.patch import patch # noqa: F401 +from ddtrace.contrib.internal.webbrowser.patch import unpatch # noqa: F401 diff --git a/ddtrace/contrib/wsgi/__init__.py b/ddtrace/contrib/wsgi/__init__.py index 06e840ed6d0..9e674993be3 100644 --- a/ddtrace/contrib/wsgi/__init__.py +++ b/ddtrace/contrib/wsgi/__init__.py @@ -37,7 +37,23 @@ """ from ddtrace.contrib.internal.wsgi.wsgi import DDWSGIMiddleware -from ddtrace.contrib.internal.wsgi.wsgi import get_version +from ddtrace.contrib.internal.wsgi.wsgi import get_version # noqa: F401 +from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning +from ddtrace.vendor.debtcollector import deprecate -__all__ = ["DDWSGIMiddleware", "get_version"] +def __getattr__(name): + if name in ("get_version",): + deprecate( + ("%s.%s is deprecated" % (__name__, name)), + message="Use ``import ddtrace.auto`` or the ``ddtrace-run`` command to configure this integration.", + category=DDTraceDeprecationWarning, + removal_version="3.0.0", + ) + + if name in globals(): + return globals()[name] + raise AttributeError("%s has no attribute %s", __name__, name) + + +__all__ = ["DDWSGIMiddleware"] diff --git a/ddtrace/contrib/yaaredis/__init__.py b/ddtrace/contrib/yaaredis/__init__.py index 7c0c9bd1b21..66aa7cc5e6e 100644 --- a/ddtrace/contrib/yaaredis/__init__.py +++ b/ddtrace/contrib/yaaredis/__init__.py @@ -75,9 +75,6 @@ async def example(): _w.simplefilter("ignore", DeprecationWarning) from . import patch as _ # noqa: F401, I001 -# Expose public methods -from ddtrace.contrib.internal.yaaredis.patch import get_version -from ddtrace.contrib.internal.yaaredis.patch import patch - -__all__ = ["patch", "get_version"] +from ddtrace.contrib.internal.yaaredis.patch import get_version # noqa: F401 +from ddtrace.contrib.internal.yaaredis.patch import patch # noqa: F401 diff --git a/ddtrace/debugging/_exception/replay.py b/ddtrace/debugging/_exception/replay.py index 080b4cbfc61..c9da69bb5b2 100644 --- a/ddtrace/debugging/_exception/replay.py +++ b/ddtrace/debugging/_exception/replay.py @@ -21,6 +21,7 @@ from ddtrace.internal.rate_limiter import BudgetRateLimiterWithJitter as RateLimiter from ddtrace.internal.rate_limiter import RateLimitExceeded from ddtrace.internal.utils.time import HourGlass +from ddtrace.settings.exception_replay import config log = get_logger(__name__) @@ -225,7 +226,9 @@ def on_span_exception( seq = count(1) # 1-based sequence number - while chain: + frames_captured = 0 + + while chain and frames_captured <= config.max_frames: exc, _tb = chain.pop() # LIFO: reverse the chain if _tb is None or _tb.tb_frame is None: @@ -233,7 +236,7 @@ def on_span_exception( continue # DEV: We go from the handler up to the root exception - while _tb: + while _tb and frames_captured <= config.max_frames: frame = _tb.tb_frame code = frame.f_code seq_nr = next(seq) @@ -263,6 +266,9 @@ def on_span_exception( # Memoize frame.f_locals[SNAPSHOT_KEY] = snapshot_id = snapshot.uuid + # Count + frames_captured += 1 + # Add correlation tags on the span span.set_tag_str(FRAME_SNAPSHOT_ID_TAG % seq_nr, snapshot_id) span.set_tag_str(FRAME_FUNCTION_TAG % seq_nr, code.co_name) diff --git a/ddtrace/debugging/_signal/tracing.py b/ddtrace/debugging/_signal/tracing.py index 3c9eb3f447e..d509e336d52 100644 --- a/ddtrace/debugging/_signal/tracing.py +++ b/ddtrace/debugging/_signal/tracing.py @@ -4,7 +4,7 @@ import ddtrace from ddtrace._trace.span import Span -from ddtrace.constants import ORIGIN_KEY +from ddtrace.constants import _ORIGIN_KEY from ddtrace.debugging._expressions import DDExpressionEvaluationError from ddtrace.debugging._probe.model import Probe from ddtrace.debugging._probe.model import SpanDecorationFunctionProbe @@ -52,7 +52,7 @@ def enter(self, scope: t.Mapping[str, t.Any]) -> None: span.set_tags(probe.tags) # type: ignore[arg-type] span.set_tag_str(PROBE_ID_TAG_NAME, probe.probe_id) - span.set_tag_str(ORIGIN_KEY, "di") + span.set_tag_str(_ORIGIN_KEY, "di") def exit(self, retval: t.Any, exc_info: ExcInfoType, duration: float, scope: t.Mapping[str, t.Any]) -> None: if self._span_cm is not None: diff --git a/ddtrace/internal/_encoding.pyx b/ddtrace/internal/_encoding.pyx index f85fe7c6776..8db5bad7544 100644 --- a/ddtrace/internal/_encoding.pyx +++ b/ddtrace/internal/_encoding.pyx @@ -19,7 +19,7 @@ from ._utils cimport PyBytesLike_Check # DEV: This only occurs because there is a `constants.py` module # in both `ddtrace` and `ddtrace.internal` -from ..constants import ORIGIN_KEY +from ..constants import _ORIGIN_KEY as ORIGIN_KEY from .constants import SPAN_LINKS_KEY from .constants import SPAN_EVENTS_KEY from .constants import MAX_UINT_64BITS diff --git a/ddtrace/internal/appsec/product.py b/ddtrace/internal/appsec/product.py new file mode 100644 index 00000000000..126d6d2a04f --- /dev/null +++ b/ddtrace/internal/appsec/product.py @@ -0,0 +1,31 @@ +from ddtrace import config +from ddtrace.settings.asm import config as asm_config + + +requires = ["remote-configuration"] + + +def post_preload(): + pass + + +def start(): + if asm_config._asm_enabled or config._remote_config_enabled: + from ddtrace.appsec._remoteconfiguration import enable_appsec_rc + + enable_appsec_rc() + + +def restart(join=False): + if asm_config._asm_enabled or config._remote_config_enabled: + from ddtrace.appsec._remoteconfiguration import _forksafe_appsec_rc + + _forksafe_appsec_rc() + + +def stop(join=False): + pass + + +def at_exit(join=False): + pass diff --git a/ddtrace/internal/core/__init__.py b/ddtrace/internal/core/__init__.py index da31218f73c..3c2169d4cb1 100644 --- a/ddtrace/internal/core/__init__.py +++ b/ddtrace/internal/core/__init__.py @@ -115,7 +115,6 @@ def _on_jsonify_context_started_flask(ctx): from ..utils.deprecations import DDTraceDeprecationWarning from . import event_hub # noqa:F401 -from ._core import DDSketch # noqa:F401 from .event_hub import EventResultDict # noqa:F401 from .event_hub import dispatch from .event_hub import dispatch_with_results # noqa:F401 diff --git a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake index 3a96fbeb353..7ba7e78164c 100644 --- a/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake +++ b/ddtrace/internal/datadog/profiling/cmake/FindLibdatadog.cmake @@ -17,17 +17,17 @@ include(FetchContent) # Set version if not already set if(NOT DEFINED TAG_LIBDATADOG) set(TAG_LIBDATADOG - "v14.3.1" + "v15.0.0" CACHE STRING "libdatadog github tag") endif() if(NOT DEFINED DD_CHECKSUMS) set(DD_CHECKSUMS - "57f83aff275628bb1af89c22bb4bd696726daf2a9e09b6cd0d966b29e65a7ad6 libdatadog-aarch64-alpine-linux-musl.tar.gz" - "2be2efa98dfc32f109abdd79242a8e046a7a300c77634135eb293e000ecd4a4c libdatadog-aarch64-apple-darwin.tar.gz" - "36db8d50ccabb71571158ea13835c0f1d05d30b32135385f97c16343cfb6ddd4 libdatadog-aarch64-unknown-linux-gnu.tar.gz" - "2f61fd21cf2f8147743e414b4a8c77250a17be3aecc42a69ffe54f0a603d5c92 libdatadog-x86_64-alpine-linux-musl.tar.gz" - "f01f05600591063eba4faf388f54c155ab4e6302e5776c7855e3734955f7daf7 libdatadog-x86_64-unknown-linux-gnu.tar.gz") + "d5b969b293e5a9e5e36404a553bbafdd55ff6af0b089698bd989a878534df0c7 libdatadog-aarch64-alpine-linux-musl.tar.gz" + "4540ffb8ccb671550a39ba79226117086582c1eaf9714180a9e26bd6bb175860 libdatadog-aarch64-apple-darwin.tar.gz" + "31bceab4f56873b03b3728760d30e3abc493d32ca8fdc9e1f2ec2147ef4d5424 libdatadog-aarch64-unknown-linux-gnu.tar.gz" + "530348c4b02cc7096de2231476ec12db82e2cc6de12a87e5b28af47ea73d4e56 libdatadog-x86_64-alpine-linux-musl.tar.gz" + "5073ffc657bc4698f8bdd4935475734577bfb18c54dcbebc4f7d8c7595626e52 libdatadog-x86_64-unknown-linux-gnu.tar.gz") endif() # Determine platform-specific tarball name in a way that conforms to the libdatadog naming scheme in Github releases diff --git a/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt b/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt index c23a3e3ddce..8165613c07d 100644 --- a/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/crashtracker/CMakeLists.txt @@ -46,6 +46,10 @@ add_library(${EXTENSION_NAME} SHARED ${CRASHTRACKER_CPP_SRC}) add_ddup_config(${EXTENSION_NAME}) # Cython generates code that produces errors for the following, so relax compile options target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address) +# tp_print is marked deprecated in Python 3.8, but cython still generates code using it +if("${Python3_VERSION_MINOR}" STREQUAL "8") + target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-deprecated-declarations) +endif() # cmake may mutate the name of the library (e.g., lib- and -.so for dynamic libraries). This suppresses that behavior, # which is required to ensure all paths can be inferred correctly by setup.py. diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker.cpp index d14c5380e19..5519489fc2b 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/crashtracker.cpp @@ -207,8 +207,8 @@ Datadog::Crashtracker::start() auto result = ddog_crasht_init(config, receiver_config, metadata); ddog_Vec_Tag_drop(tags); - if (result.tag != DDOG_CRASHT_RESULT_OK) { // NOLINT (cppcoreguidelines-pro-type-union-access) - auto err = result.err; // NOLINT (cppcoreguidelines-pro-type-union-access) + if (result.tag != DDOG_VOID_RESULT_OK) { // NOLINT (cppcoreguidelines-pro-type-union-access) + auto err = result.err; // NOLINT (cppcoreguidelines-pro-type-union-access) std::string errmsg = err_to_msg(&err, "Error initializing crash tracker"); std::cerr << errmsg << std::endl; ddog_Error_drop(&err); diff --git a/ddtrace/internal/datadog/profiling/dd_wrapper/src/receiver_interface.cpp b/ddtrace/internal/datadog/profiling/dd_wrapper/src/receiver_interface.cpp index b95ef95604e..81a02eddcff 100644 --- a/ddtrace/internal/datadog/profiling/dd_wrapper/src/receiver_interface.cpp +++ b/ddtrace/internal/datadog/profiling/dd_wrapper/src/receiver_interface.cpp @@ -18,8 +18,8 @@ crashtracker_receiver_entry() // cppcheck-suppress unusedFunction { // Assumes that this will be called only in the receiver binary, which is a // fresh process - ddog_crasht_Result new_result = ddog_crasht_receiver_entry_point_stdin(); - if (new_result.tag != DDOG_CRASHT_RESULT_OK) { + ddog_VoidResult new_result = ddog_crasht_receiver_entry_point_stdin(); + if (new_result.tag != DDOG_VOID_RESULT_OK) { ddog_CharSlice message = ddog_Error_message(&new_result.err); //`write` may not write what we want it to write, but there's nothing we can do about it, diff --git a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt index 6a4cb4e8803..12ceab6fcb6 100644 --- a/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt +++ b/ddtrace/internal/datadog/profiling/ddup/CMakeLists.txt @@ -49,6 +49,10 @@ add_library(${EXTENSION_NAME} SHARED ${DDUP_CPP_SRC}) add_ddup_config(${EXTENSION_NAME}) # Cython generates code that produces errors for the following, so relax compile options target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-old-style-cast -Wno-shadow -Wno-address) +# tp_print is marked deprecated in Python 3.8, but cython still generates code using it +if("${Python3_VERSION_MINOR}" STREQUAL "8") + target_compile_options(${EXTENSION_NAME} PRIVATE -Wno-deprecated-declarations) +endif() # cmake may mutate the name of the library (e.g., lib- and -.so for dynamic libraries). This suppresses that behavior, # which is required to ensure all paths can be inferred correctly by setup.py. diff --git a/ddtrace/internal/datastreams/processor.py b/ddtrace/internal/datastreams/processor.py index 5afdb07c9a2..deb9388537b 100644 --- a/ddtrace/internal/datastreams/processor.py +++ b/ddtrace/internal/datastreams/processor.py @@ -20,7 +20,7 @@ from ddtrace.internal import compat from ddtrace.internal.atexit import register_on_exit_signal from ddtrace.internal.constants import DEFAULT_SERVICE_NAME -from ddtrace.internal.core import DDSketch +from ddtrace.internal.native import DDSketch from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter from .._encoding import packb diff --git a/ddtrace/internal/iast/product.py b/ddtrace/internal/iast/product.py new file mode 100644 index 00000000000..ccbc61b2f5a --- /dev/null +++ b/ddtrace/internal/iast/product.py @@ -0,0 +1,29 @@ +""" +This is the entry point for the IAST instrumentation. `enable_iast_propagation` is called on patch_all function +too but patch_all depends of DD_TRACE_ENABLED environment variable. This is the reason why we need to call it +here and it's not a duplicate call due to `enable_iast_propagation` has a global variable to avoid multiple calls. +""" +from ddtrace.appsec._iast._utils import _is_iast_enabled + + +def post_preload(): + pass + + +def start(): + if _is_iast_enabled(): + from ddtrace.appsec._iast import enable_iast_propagation + + enable_iast_propagation() + + +def restart(join=False): + pass + + +def stop(join=False): + pass + + +def at_exit(join=False): + pass diff --git a/ddtrace/internal/native/__init__.py b/ddtrace/internal/native/__init__.py new file mode 100644 index 00000000000..0c85a824b44 --- /dev/null +++ b/ddtrace/internal/native/__init__.py @@ -0,0 +1 @@ +from ._native import DDSketch # noqa: F401 diff --git a/ddtrace/internal/core/_core.pyi b/ddtrace/internal/native/_native.pyi similarity index 100% rename from ddtrace/internal/core/_core.pyi rename to ddtrace/internal/native/_native.pyi diff --git a/ddtrace/internal/processor/stats.py b/ddtrace/internal/processor/stats.py index f79f460582e..295a6e289a2 100644 --- a/ddtrace/internal/processor/stats.py +++ b/ddtrace/internal/processor/stats.py @@ -8,10 +8,10 @@ from ddtrace._trace.processor import SpanProcessor from ddtrace._trace.span import _is_top_level from ddtrace.internal import compat -from ddtrace.internal.core import DDSketch +from ddtrace.internal.native import DDSketch from ddtrace.internal.utils.retry import fibonacci_backoff_with_jitter -from ...constants import SPAN_MEASURED_KEY +from ...constants import _SPAN_MEASURED_KEY from .._encoding import packb from ..agent import get_connection from ..compat import get_connection_response @@ -38,7 +38,7 @@ def _is_measured(span): # type: (Span) -> bool """Return whether the span is flagged to be measured or not.""" - return span._metrics.get(SPAN_MEASURED_KEY) == 1 + return span._metrics.get(_SPAN_MEASURED_KEY) == 1 """ diff --git a/ddtrace/internal/sampling.py b/ddtrace/internal/sampling.py index 997d3af77fd..b1d3f7957f4 100644 --- a/ddtrace/internal/sampling.py +++ b/ddtrace/internal/sampling.py @@ -15,12 +15,12 @@ from typing_extensions import TypedDict from ddtrace._trace.sampling_rule import SamplingRule # noqa:F401 +from ddtrace.constants import _SAMPLING_AGENT_DECISION +from ddtrace.constants import _SAMPLING_RULE_DECISION from ddtrace.constants import _SINGLE_SPAN_SAMPLING_MAX_PER_SEC from ddtrace.constants import _SINGLE_SPAN_SAMPLING_MAX_PER_SEC_NO_LIMIT from ddtrace.constants import _SINGLE_SPAN_SAMPLING_MECHANISM from ddtrace.constants import _SINGLE_SPAN_SAMPLING_RATE -from ddtrace.constants import SAMPLING_AGENT_DECISION -from ddtrace.constants import SAMPLING_RULE_DECISION from ddtrace.internal.constants import _CATEGORY_TO_PRIORITIES from ddtrace.internal.constants import _KEEP_PRIORITY_INDEX from ddtrace.internal.constants import _REJECT_PRIORITY_INDEX @@ -282,18 +282,18 @@ def _set_sampling_tags(span, sampled, sample_rate, priority_category): # type: (Span, bool, float, str) -> None mechanism = SamplingMechanism.TRACE_SAMPLING_RULE if priority_category == PriorityCategory.RULE_DEFAULT: - span.set_metric(SAMPLING_RULE_DECISION, sample_rate) + span.set_metric(_SAMPLING_RULE_DECISION, sample_rate) if priority_category == PriorityCategory.RULE_CUSTOMER: - span.set_metric(SAMPLING_RULE_DECISION, sample_rate) + span.set_metric(_SAMPLING_RULE_DECISION, sample_rate) mechanism = SamplingMechanism.REMOTE_USER_RULE if priority_category == PriorityCategory.RULE_DYNAMIC: - span.set_metric(SAMPLING_RULE_DECISION, sample_rate) + span.set_metric(_SAMPLING_RULE_DECISION, sample_rate) mechanism = SamplingMechanism.REMOTE_DYNAMIC_RULE elif priority_category == PriorityCategory.DEFAULT: mechanism = SamplingMechanism.DEFAULT elif priority_category == PriorityCategory.AUTO: mechanism = SamplingMechanism.AGENT_RATE - span.set_metric(SAMPLING_AGENT_DECISION, sample_rate) + span.set_metric(_SAMPLING_AGENT_DECISION, sample_rate) priorities = _CATEGORY_TO_PRIORITIES[priority_category] _set_priority(span, priorities[_KEEP_PRIORITY_INDEX] if sampled else priorities[_REJECT_PRIORITY_INDEX]) set_sampling_decision_maker(span.context, mechanism) diff --git a/ddtrace/internal/schema/processor.py b/ddtrace/internal/schema/processor.py index 33d9b431c1b..91b0feff5dc 100644 --- a/ddtrace/internal/schema/processor.py +++ b/ddtrace/internal/schema/processor.py @@ -1,6 +1,6 @@ from ddtrace import config from ddtrace._trace.processor import TraceProcessor -from ddtrace.constants import BASE_SERVICE_KEY +from ddtrace.constants import _BASE_SERVICE_KEY from . import schematize_service_name @@ -22,4 +22,4 @@ def process_trace(self, trace): return trace def _update_dd_base_service(self, span): - span.set_tag_str(key=BASE_SERVICE_KEY, value=self._global_service) + span.set_tag_str(key=_BASE_SERVICE_KEY, value=self._global_service) diff --git a/ddtrace/internal/utils/http.py b/ddtrace/internal/utils/http.py index 7e85ce01356..f9c13827d3d 100644 --- a/ddtrace/internal/utils/http.py +++ b/ddtrace/internal/utils/http.py @@ -16,7 +16,7 @@ from typing import Tuple # noqa:F401 from typing import Union # noqa:F401 -from ddtrace.constants import USER_ID_KEY +from ddtrace.constants import _USER_ID_KEY from ddtrace.internal import compat from ddtrace.internal._unpatched import unpatched_open as open # noqa: A001 from ddtrace.internal.compat import parse @@ -164,13 +164,13 @@ def w3c_get_dd_list_member(context): "t.dm:{}".format((w3c_encode_tag((_W3C_TRACESTATE_INVALID_CHARS_REGEX_VALUE, "_", sampling_decision)))) ) # since this can change, we need to grab the value off the current span - usr_id = context._meta.get(USER_ID_KEY) + usr_id = context._meta.get(_USER_ID_KEY) if usr_id: tags.append("t.usr.id:{}".format(w3c_encode_tag((_W3C_TRACESTATE_INVALID_CHARS_REGEX_VALUE, "_", usr_id)))) current_tags_len = sum(len(i) for i in tags) for k, v in _get_metas_to_propagate(context): - if k not in [SAMPLING_DECISION_TRACE_TAG_KEY, USER_ID_KEY]: + if k not in [SAMPLING_DECISION_TRACE_TAG_KEY, _USER_ID_KEY]: # for key replace ",", "=", and characters outside the ASCII range 0x20 to 0x7E # for value replace ",", ";", "~" and characters outside the ASCII range 0x20 to 0x7E k = k.replace("_dd.p.", "t.") diff --git a/ddtrace/internal/writer/writer.py b/ddtrace/internal/writer/writer.py index 01b05515984..c494aa206a2 100644 --- a/ddtrace/internal/writer/writer.py +++ b/ddtrace/internal/writer/writer.py @@ -18,7 +18,7 @@ from ddtrace.settings.asm import config as asm_config from ddtrace.vendor.dogstatsd import DogStatsd -from ...constants import KEEP_SPANS_RATE_KEY +from ...constants import _KEEP_SPANS_RATE_KEY from ...internal.utils.formats import parse_tags_str from ...internal.utils.http import Response from ...internal.utils.time import StopWatch @@ -219,7 +219,7 @@ def _set_drop_rate(self) -> None: def _set_keep_rate(self, trace): if trace: - trace[0].set_metric(KEEP_SPANS_RATE_KEY, 1.0 - self._drop_sma.get()) + trace[0].set_metric(_KEEP_SPANS_RATE_KEY, 1.0 - self._drop_sma.get()) def _reset_connection(self) -> None: with self._conn_lck: diff --git a/ddtrace/llmobs/_constants.py b/ddtrace/llmobs/_constants.py index 57cbd5bb179..3c61053df68 100644 --- a/ddtrace/llmobs/_constants.py +++ b/ddtrace/llmobs/_constants.py @@ -50,9 +50,9 @@ # Used to differentiate traces of Datadog-run operations vs user-application operations. RUNNER_IS_INTEGRATION_SPAN_TAG = "runner.integration" -# The ml app of all ragas traces have this prefix that we use to detect -# whether a span is generated from the ragas evaluation itself. -RAGAS_ML_APP_PREFIX = "dd-ragas" +# All ragas traces have this context item set so we can differentiate +# spans generated from the ragas integration vs user application spans. +IS_EVALUATION_SPAN = "_ml_obs.evaluation_span" ANNOTATIONS_CONTEXT_ID = "annotations_context_id" INTERNAL_CONTEXT_VARIABLE_KEYS = "_dd_context_variable_keys" diff --git a/ddtrace/llmobs/_evaluators/ragas/answer_relevancy.py b/ddtrace/llmobs/_evaluators/ragas/answer_relevancy.py index 9a640e08454..5fd6e6b7c0c 100644 --- a/ddtrace/llmobs/_evaluators/ragas/answer_relevancy.py +++ b/ddtrace/llmobs/_evaluators/ragas/answer_relevancy.py @@ -5,6 +5,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.llmobs._constants import EVALUATION_SPAN_METADATA +from ddtrace.llmobs._constants import IS_EVALUATION_SPAN from ddtrace.llmobs._evaluators.ragas.base import BaseRagasEvaluator from ddtrace.llmobs._evaluators.ragas.base import _get_ml_app_for_ragas_trace @@ -84,6 +85,7 @@ def evaluate(self, span_event: dict) -> Tuple[Union[float, str], Optional[dict]] with self.llmobs_service.workflow( "dd-ragas.answer_relevancy", ml_app=_get_ml_app_for_ragas_trace(span_event) ) as ragas_ar_workflow: + ragas_ar_workflow._set_ctx_item(IS_EVALUATION_SPAN, True) try: evaluation_metadata[EVALUATION_SPAN_METADATA] = self.llmobs_service.export_span(span=ragas_ar_workflow) diff --git a/ddtrace/llmobs/_evaluators/ragas/base.py b/ddtrace/llmobs/_evaluators/ragas/base.py index 798c8e2fccc..2f6522496dd 100644 --- a/ddtrace/llmobs/_evaluators/ragas/base.py +++ b/ddtrace/llmobs/_evaluators/ragas/base.py @@ -4,6 +4,7 @@ from typing import Tuple from typing import Union +from ddtrace import config from ddtrace.internal.logger import get_logger from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_LOG_LEVEL @@ -11,7 +12,6 @@ from ddtrace.internal.utils.version import parse_version from ddtrace.llmobs._constants import INTERNAL_CONTEXT_VARIABLE_KEYS from ddtrace.llmobs._constants import INTERNAL_QUERY_VARIABLE_KEYS -from ddtrace.llmobs._constants import RAGAS_ML_APP_PREFIX logger = get_logger(__name__) @@ -94,9 +94,7 @@ def _get_ml_app_for_ragas_trace(span_event: dict) -> str: if isinstance(tag, str) and tag.startswith("ml_app:"): ml_app = tag.split(":")[1] break - if not ml_app: - return RAGAS_ML_APP_PREFIX - return "{}-{}".format(RAGAS_ML_APP_PREFIX, ml_app) + return ml_app or config._llmobs_ml_app or "unknown-ml-app" class BaseRagasEvaluator: diff --git a/ddtrace/llmobs/_evaluators/ragas/context_precision.py b/ddtrace/llmobs/_evaluators/ragas/context_precision.py index 990302931c8..13ccb1d593a 100644 --- a/ddtrace/llmobs/_evaluators/ragas/context_precision.py +++ b/ddtrace/llmobs/_evaluators/ragas/context_precision.py @@ -6,6 +6,7 @@ from ddtrace.internal.logger import get_logger from ddtrace.llmobs._constants import EVALUATION_KIND_METADATA from ddtrace.llmobs._constants import EVALUATION_SPAN_METADATA +from ddtrace.llmobs._constants import IS_EVALUATION_SPAN from ddtrace.llmobs._evaluators.ragas.base import BaseRagasEvaluator from ddtrace.llmobs._evaluators.ragas.base import _get_ml_app_for_ragas_trace @@ -82,6 +83,7 @@ def evaluate(self, span_event: dict) -> Tuple[Union[float, str], Optional[dict]] with self.llmobs_service.workflow( "dd-ragas.context_precision", ml_app=_get_ml_app_for_ragas_trace(span_event) ) as ragas_cp_workflow: + ragas_cp_workflow._set_ctx_item(IS_EVALUATION_SPAN, True) try: evaluation_metadata[EVALUATION_SPAN_METADATA] = self.llmobs_service.export_span(span=ragas_cp_workflow) diff --git a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py index 98725b1f27e..2c413f2cec7 100644 --- a/ddtrace/llmobs/_evaluators/ragas/faithfulness.py +++ b/ddtrace/llmobs/_evaluators/ragas/faithfulness.py @@ -9,6 +9,7 @@ from ddtrace.llmobs._constants import EVALUATION_KIND_METADATA from ddtrace.llmobs._constants import EVALUATION_SPAN_METADATA from ddtrace.llmobs._constants import FAITHFULNESS_DISAGREEMENTS_METADATA +from ddtrace.llmobs._constants import IS_EVALUATION_SPAN from ddtrace.llmobs._evaluators.ragas.base import BaseRagasEvaluator from ddtrace.llmobs._evaluators.ragas.base import _get_ml_app_for_ragas_trace @@ -96,6 +97,7 @@ def evaluate(self, span_event: dict) -> Tuple[Union[float, str], Optional[dict]] with self.llmobs_service.workflow( "dd-ragas.faithfulness", ml_app=_get_ml_app_for_ragas_trace(span_event) ) as ragas_faithfulness_workflow: + ragas_faithfulness_workflow._set_ctx_item(IS_EVALUATION_SPAN, True) try: evaluation_metadata[EVALUATION_SPAN_METADATA] = self.llmobs_service.export_span( span=ragas_faithfulness_workflow diff --git a/ddtrace/llmobs/_integrations/base.py b/ddtrace/llmobs/_integrations/base.py index a171b22867a..9b6b5eadcda 100644 --- a/ddtrace/llmobs/_integrations/base.py +++ b/ddtrace/llmobs/_integrations/base.py @@ -9,8 +9,8 @@ from ddtrace import config from ddtrace._trace.sampler import RateSampler from ddtrace._trace.span import Span -from ddtrace.constants import SPAN_MEASURED_KEY -from ddtrace.contrib.trace_utils import int_service +from ddtrace.constants import _SPAN_MEASURED_KEY +from ddtrace.contrib.internal.trace_utils import int_service from ddtrace.ext import SpanTypes from ddtrace.internal.agent import get_stats_url from ddtrace.internal.dogstatsd import get_dogstatsd_client @@ -127,7 +127,7 @@ def trace(self, pin: Pin, operation_id: str, submit_to_llmobs: bool = False, **k span_type=SpanTypes.LLM if (submit_to_llmobs and self.llmobs_enabled) else None, ) # Enable trace metrics for these spans so users can see per-service openai usage in APM. - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) self._set_base_span_tags(span, **kwargs) if submit_to_llmobs and self.llmobs_enabled: if span.get_tag(PROPAGATED_PARENT_ID_KEY) is None: diff --git a/ddtrace/llmobs/_integrations/vertexai.py b/ddtrace/llmobs/_integrations/vertexai.py index 4019268e0c4..933cd685a1f 100644 --- a/ddtrace/llmobs/_integrations/vertexai.py +++ b/ddtrace/llmobs/_integrations/vertexai.py @@ -5,6 +5,7 @@ from typing import Optional from ddtrace import Span +from ddtrace.internal.utils import ArgumentError from ddtrace.internal.utils import get_argument_value from ddtrace.llmobs._constants import INPUT_MESSAGES from ddtrace.llmobs._constants import METADATA @@ -45,7 +46,11 @@ def _llmobs_set_tags( metadata = llmobs_get_metadata_google(kwargs, instance) system_instruction = get_system_instructions_from_google_model(instance) - input_contents = get_argument_value(args, kwargs, 0, "contents") + input_contents = None + try: + input_contents = get_argument_value(args, kwargs, 0, "content") + except ArgumentError: + input_contents = get_argument_value(args, kwargs, 0, "contents") input_messages = self._extract_input_message(input_contents, history, system_instruction) output_messages = [{"content": ""}] diff --git a/ddtrace/llmobs/_llmobs.py b/ddtrace/llmobs/_llmobs.py index 9fec36a9d10..809afcf9011 100644 --- a/ddtrace/llmobs/_llmobs.py +++ b/ddtrace/llmobs/_llmobs.py @@ -5,7 +5,6 @@ from typing import Dict from typing import List from typing import Optional -from typing import Tuple from typing import Union import ddtrace @@ -28,10 +27,10 @@ from ddtrace.internal.service import ServiceStatusError from ddtrace.internal.telemetry import telemetry_writer from ddtrace.internal.telemetry.constants import TELEMETRY_APM_PRODUCT -from ddtrace.internal.utils.deprecations import DDTraceDeprecationWarning from ddtrace.internal.utils.formats import asbool from ddtrace.internal.utils.formats import parse_tags_str from ddtrace.llmobs import _constants as constants +from ddtrace.llmobs._constants import AGENTLESS_BASE_URL from ddtrace.llmobs._constants import ANNOTATIONS_CONTEXT_ID from ddtrace.llmobs._constants import INPUT_DOCUMENTS from ddtrace.llmobs._constants import INPUT_MESSAGES @@ -60,6 +59,7 @@ from ddtrace.llmobs._utils import _get_session_id from ddtrace.llmobs._utils import _get_span_name from ddtrace.llmobs._utils import _inject_llmobs_parent_id +from ddtrace.llmobs._utils import _is_evaluation_span from ddtrace.llmobs._utils import safe_json from ddtrace.llmobs._utils import validate_prompt from ddtrace.llmobs._writer import LLMObsEvalMetricWriter @@ -68,7 +68,6 @@ from ddtrace.llmobs.utils import ExportedLLMObsSpan from ddtrace.llmobs.utils import Messages from ddtrace.propagation.http import HTTPPropagator -from ddtrace.vendor.debtcollector import deprecate log = get_logger(__name__) @@ -93,6 +92,7 @@ def __init__(self, tracer=None): self.tracer = tracer or ddtrace.tracer self._llmobs_span_writer = LLMObsSpanWriter( is_agentless=config._llmobs_agentless_enabled, + agentless_url="%s.%s" % (AGENTLESS_BASE_URL, config._dd_site), interval=float(os.getenv("_DD_LLMOBS_WRITER_INTERVAL", 1.0)), timeout=float(os.getenv("_DD_LLMOBS_WRITER_TIMEOUT", 5.0)), ) @@ -123,23 +123,21 @@ def _on_span_finish(self, span): def _submit_llmobs_span(self, span: Span) -> None: """Generate and submit an LLMObs span event to be sent to LLMObs.""" span_event = None - is_llm_span = span._get_ctx_item(SPAN_KIND) == "llm" - is_ragas_integration_span = False try: - span_event, is_ragas_integration_span = self._llmobs_span_event(span) + span_event = self._llmobs_span_event(span) self._llmobs_span_writer.enqueue(span_event) except (KeyError, TypeError): log.error( "Error generating LLMObs span event for span %s, likely due to malformed span", span, exc_info=True ) finally: - if not span_event or not is_llm_span or is_ragas_integration_span: + if not span_event or not span._get_ctx_item(SPAN_KIND) == "llm" or _is_evaluation_span(span): return if self._evaluator_runner: self._evaluator_runner.enqueue(span_event, span) @classmethod - def _llmobs_span_event(cls, span: Span) -> Tuple[Dict[str, Any], bool]: + def _llmobs_span_event(cls, span: Span) -> Dict[str, Any]: """Span event object structure.""" span_kind = span._get_ctx_item(SPAN_KIND) if not span_kind: @@ -154,13 +152,13 @@ def _llmobs_span_event(cls, span: Span) -> Tuple[Dict[str, Any], bool]: if span_kind == "llm" and span._get_ctx_item(INPUT_MESSAGES) is not None: meta["input"]["messages"] = span._get_ctx_item(INPUT_MESSAGES) if span._get_ctx_item(INPUT_VALUE) is not None: - meta["input"]["value"] = safe_json(span._get_ctx_item(INPUT_VALUE)) + meta["input"]["value"] = safe_json(span._get_ctx_item(INPUT_VALUE), ensure_ascii=False) if span_kind == "llm" and span._get_ctx_item(OUTPUT_MESSAGES) is not None: meta["output"]["messages"] = span._get_ctx_item(OUTPUT_MESSAGES) if span_kind == "embedding" and span._get_ctx_item(INPUT_DOCUMENTS) is not None: meta["input"]["documents"] = span._get_ctx_item(INPUT_DOCUMENTS) if span._get_ctx_item(OUTPUT_VALUE) is not None: - meta["output"]["value"] = safe_json(span._get_ctx_item(OUTPUT_VALUE)) + meta["output"]["value"] = safe_json(span._get_ctx_item(OUTPUT_VALUE), ensure_ascii=False) if span_kind == "retrieval" and span._get_ctx_item(OUTPUT_DOCUMENTS) is not None: meta["output"]["documents"] = span._get_ctx_item(OUTPUT_DOCUMENTS) if span._get_ctx_item(INPUT_PROMPT) is not None: @@ -186,11 +184,6 @@ def _llmobs_span_event(cls, span: Span) -> Tuple[Dict[str, Any], bool]: metrics = span._get_ctx_item(METRICS) or {} ml_app = _get_ml_app(span) - is_ragas_integration_span = False - - if ml_app.startswith(constants.RAGAS_ML_APP_PREFIX): - is_ragas_integration_span = True - span._set_ctx_item(ML_APP, ml_app) parent_id = str(_get_llmobs_parent_id(span) or "undefined") @@ -210,20 +203,16 @@ def _llmobs_span_event(cls, span: Span) -> Tuple[Dict[str, Any], bool]: span._set_ctx_item(SESSION_ID, session_id) llmobs_span_event["session_id"] = session_id - llmobs_span_event["tags"] = cls._llmobs_tags( - span, ml_app, session_id, is_ragas_integration_span=is_ragas_integration_span - ) + llmobs_span_event["tags"] = cls._llmobs_tags(span, ml_app, session_id) span_links = span._get_ctx_item(SPAN_LINKS) if isinstance(span_links, list): llmobs_span_event["span_links"] = span_links - return llmobs_span_event, is_ragas_integration_span + return llmobs_span_event @staticmethod - def _llmobs_tags( - span: Span, ml_app: str, session_id: Optional[str] = None, is_ragas_integration_span: bool = False - ) -> List[str]: + def _llmobs_tags(span: Span, ml_app: str, session_id: Optional[str] = None) -> List[str]: tags = { "version": config.version or "", "env": config.env or "", @@ -239,7 +228,7 @@ def _llmobs_tags( tags["error_type"] = err_type if session_id: tags["session_id"] = session_id - if is_ragas_integration_span: + if _is_evaluation_span(span): tags[constants.RUNNER_IS_INTEGRATION_SPAN_TAG] = "ragas" existing_tags = span._get_ctx_item(TAGS) if existing_tags is not None: @@ -278,10 +267,6 @@ def _start_service(self) -> None: log.debug("Error starting evaluator runner") def _stop_service(self) -> None: - # Remove listener hooks for span events - core.reset_listeners("trace.span_start", self._on_span_start) - core.reset_listeners("trace.span_finish", self._on_span_finish) - try: self._evaluator_runner.stop() # flush remaining evaluation spans & evaluations @@ -296,6 +281,10 @@ def _stop_service(self) -> None: except ServiceStatusError: log.debug("Error stopping LLMObs writers") + # Remove listener hooks for span events + core.reset_listeners("trace.span_start", self._on_span_start) + core.reset_listeners("trace.span_finish", self._on_span_finish) + forksafe.unregister(self._child_after_fork) @classmethod @@ -1053,13 +1042,6 @@ def submit_evaluation( timestamp_ms: Optional[int] = None, metadata: Optional[Dict[str, object]] = None, ) -> None: - deprecate( - "Using `LLMObs.submit_evaluation` is deprecated", - message="Please use `LLMObs.submit_evaluation_for` instead.", - removal_version="3.0.0", - category=DDTraceDeprecationWarning, - ) - """ Submits a custom evaluation metric for a given span ID and trace ID. diff --git a/ddtrace/llmobs/_utils.py b/ddtrace/llmobs/_utils.py index 1799eb5548d..c2f44689a05 100644 --- a/ddtrace/llmobs/_utils.py +++ b/ddtrace/llmobs/_utils.py @@ -12,6 +12,7 @@ from ddtrace.llmobs._constants import GEMINI_APM_SPAN_NAME from ddtrace.llmobs._constants import INTERNAL_CONTEXT_VARIABLE_KEYS from ddtrace.llmobs._constants import INTERNAL_QUERY_VARIABLE_KEYS +from ddtrace.llmobs._constants import IS_EVALUATION_SPAN from ddtrace.llmobs._constants import LANGCHAIN_APM_SPAN_NAME from ddtrace.llmobs._constants import ML_APP from ddtrace.llmobs._constants import NAME @@ -128,6 +129,23 @@ def _get_span_name(span: Span) -> str: return span._get_ctx_item(NAME) or span.name +def _is_evaluation_span(span: Span) -> bool: + """ + Return whether or not a span is an evaluation span by checking the span's + nearest LLMObs span ancestor. Default to 'False' + """ + is_evaluation_span = span._get_ctx_item(IS_EVALUATION_SPAN) + if is_evaluation_span: + return is_evaluation_span + llmobs_parent = _get_nearest_llmobs_ancestor(span) + while llmobs_parent: + is_evaluation_span = llmobs_parent._get_ctx_item(IS_EVALUATION_SPAN) + if is_evaluation_span: + return is_evaluation_span + llmobs_parent = _get_nearest_llmobs_ancestor(llmobs_parent) + return False + + def _get_ml_app(span: Span) -> str: """ Return the ML app name for a given span, by checking the span's nearest LLMObs span ancestor. @@ -185,10 +203,10 @@ def _unserializable_default_repr(obj): return default_repr -def safe_json(obj): +def safe_json(obj, ensure_ascii=True): if isinstance(obj, str): return obj try: - return json.dumps(obj, ensure_ascii=False, skipkeys=True, default=_unserializable_default_repr) + return json.dumps(obj, ensure_ascii=ensure_ascii, skipkeys=True, default=_unserializable_default_repr) except Exception: log.error("Failed to serialize object to JSON.", exc_info=True) diff --git a/ddtrace/llmobs/_writer.py b/ddtrace/llmobs/_writer.py index 5880019d67f..c172c9adba9 100644 --- a/ddtrace/llmobs/_writer.py +++ b/ddtrace/llmobs/_writer.py @@ -22,7 +22,6 @@ from ddtrace.internal.periodic import PeriodicService from ddtrace.internal.writer import HTTPWriter from ddtrace.internal.writer import WriterClientBase -from ddtrace.llmobs._constants import AGENTLESS_BASE_URL from ddtrace.llmobs._constants import AGENTLESS_ENDPOINT from ddtrace.llmobs._constants import DROPPED_IO_COLLECTION_ERROR from ddtrace.llmobs._constants import DROPPED_VALUE_TEXT @@ -243,6 +242,7 @@ def __init__( interval: float, timeout: float, is_agentless: bool = True, + agentless_url: str = "", dogstatsd=None, sync_mode=False, reuse_connections=None, @@ -250,8 +250,10 @@ def __init__( headers = {} clients = [] # type: List[WriterClientBase] if is_agentless: + if not agentless_url: + raise ValueError("agentless_url is required for agentless mode") clients.append(LLMObsAgentlessEventClient()) - intake_url = "%s.%s" % (AGENTLESS_BASE_URL, config._dd_site) + intake_url = agentless_url headers["DD-API-KEY"] = config._dd_api_key else: clients.append(LLMObsProxiedEventClient()) diff --git a/ddtrace/settings/asm.py b/ddtrace/settings/asm.py index 0aadda674f5..16937399501 100644 --- a/ddtrace/settings/asm.py +++ b/ddtrace/settings/asm.py @@ -132,7 +132,7 @@ class ASMConfig(Env): help_type=float, help="Timeout in milliseconds for WAF computations", ) - + _asm_deduplication_enabled = Env.var(bool, "_DD_APPSEC_DEDUPLICATION_ENABLED", default=True) _asm_obfuscation_parameter_key_regexp = Env.var( str, APPSEC.OBFUSCATION_PARAMETER_KEY_REGEXP, default=DEFAULT.APPSEC_OBFUSCATION_PARAMETER_KEY_REGEXP ) @@ -167,7 +167,7 @@ class ASMConfig(Env): default=2, ) _iast_lazy_taint = Env.var(bool, IAST.LAZY_TAINT, default=False) - _deduplication_enabled = Env.var(bool, "_DD_APPSEC_DEDUPLICATION_ENABLED", default=True) + _iast_deduplication_enabled = Env.var(bool, "DD_IAST_DEDUPLICATION_ENABLED", default=True) # default will be set to True once the feature is GA. For now it's always False _ep_enabled = Env.var(bool, EXPLOIT_PREVENTION.EP_ENABLED, default=True) @@ -226,13 +226,14 @@ class ASMConfig(Env): "_iast_max_concurrent_requests", "_iast_max_vulnerabilities_per_requests", "_iast_lazy_taint", + "_iast_deduplication_enabled", "_ep_stack_trace_enabled", "_ep_max_stack_traces", "_ep_max_stack_trace_depth", "_ep_stack_top_percent", "_iast_stack_trace_enabled", "_asm_config_keys", - "_deduplication_enabled", + "_asm_deduplication_enabled", "_django_include_user_name", "_django_include_user_email", "_django_include_user_login", diff --git a/ddtrace/settings/config.py b/ddtrace/settings/config.py index 81ed7a3ab19..536ea0131bc 100644 --- a/ddtrace/settings/config.py +++ b/ddtrace/settings/config.py @@ -150,15 +150,13 @@ def _parse_propagation_styles(styles_str): category=DDTraceDeprecationWarning, ) style = PROPAGATION_STYLE_B3_SINGLE + # None has no propagator so we pull it out 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/settings/endpoint_config.py b/ddtrace/settings/endpoint_config.py index e721df65a10..aee1fdc5cad 100644 --- a/ddtrace/settings/endpoint_config.py +++ b/ddtrace/settings/endpoint_config.py @@ -5,9 +5,9 @@ """ import os -from ddtrace.constants import CONFIG_ENDPOINT_ENV -from ddtrace.constants import CONFIG_ENDPOINT_RETRIES_ENV -from ddtrace.constants import CONFIG_ENDPOINT_TIMEOUT_ENV +from ddtrace.constants import _CONFIG_ENDPOINT_ENV +from ddtrace.constants import _CONFIG_ENDPOINT_RETRIES_ENV +from ddtrace.constants import _CONFIG_ENDPOINT_TIMEOUT_ENV from ddtrace.internal.constants import DEFAULT_TIMEOUT from ddtrace.internal.logger import get_logger from ddtrace.internal.utils.http import Response @@ -20,10 +20,10 @@ RETRIES = 1 try: - if CONFIG_ENDPOINT_RETRIES_ENV in os.environ: - RETRIES = int(os.getenv(CONFIG_ENDPOINT_RETRIES_ENV, str(RETRIES))) + if _CONFIG_ENDPOINT_RETRIES_ENV in os.environ: + RETRIES = int(os.getenv(_CONFIG_ENDPOINT_RETRIES_ENV, str(RETRIES))) except ValueError: - log.error("Invalid value for %s. Using default value: %s", CONFIG_ENDPOINT_RETRIES_ENV, RETRIES) + log.error("Invalid value for %s. Using default value: %s", _CONFIG_ENDPOINT_RETRIES_ENV, RETRIES) def _get_retries(): @@ -32,10 +32,10 @@ def _get_retries(): TIMEOUT = DEFAULT_TIMEOUT try: - if CONFIG_ENDPOINT_TIMEOUT_ENV in os.environ: - TIMEOUT = int(os.getenv(CONFIG_ENDPOINT_TIMEOUT_ENV, str(TIMEOUT))) + if _CONFIG_ENDPOINT_TIMEOUT_ENV in os.environ: + TIMEOUT = int(os.getenv(_CONFIG_ENDPOINT_TIMEOUT_ENV, str(TIMEOUT))) except ValueError: - log.error("Invalid value for %s. Using default value: %s", CONFIG_ENDPOINT_TIMEOUT_ENV, TIMEOUT) + log.error("Invalid value for %s. Using default value: %s", _CONFIG_ENDPOINT_TIMEOUT_ENV, TIMEOUT) def _get_timeout(): @@ -59,7 +59,7 @@ def fetch_config_from_endpoint() -> dict: """ Fetch the configuration from the configuration endpoint. """ - config_endpoint = os.getenv(CONFIG_ENDPOINT_ENV, None) + config_endpoint = os.getenv(_CONFIG_ENDPOINT_ENV, None) if config_endpoint is None: log.debug("Configuration endpoint not set. Skipping fetching configuration.") diff --git a/ddtrace/settings/exception_replay.py b/ddtrace/settings/exception_replay.py index c78ae29b73d..723a63288a0 100644 --- a/ddtrace/settings/exception_replay.py +++ b/ddtrace/settings/exception_replay.py @@ -14,6 +14,13 @@ class ExceptionReplayConfig(En): help="Enable automatic capturing of exception debugging information", deprecations=[("debugging.enabled", None, "3.0")], ) + max_frames = En.v( + int, + "replay.capture_max_frames", + default=8, + help_type="int", + help="The maximum number of frames to capture for each exception", + ) config = ExceptionReplayConfig() diff --git a/docker-compose.yml b/docker-compose.yml index 118ad8cc5db..701b5a7d0f0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -205,7 +205,7 @@ services: - DD_REMOTE_CONFIGURATION_ENABLED=true - DD_AGENT_PORT=8126 - DD_TRACE_AGENT_URL=http://testagent:8126 - - _DD_APPSEC_DEDUPLICATION_ENABLED=false + - DD_IAST_DEDUPLICATION_ENABLED=false volumes: ddagent: diff --git a/docs/configuration.rst b/docs/configuration.rst index 455272f318d..853050195fe 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -485,6 +485,11 @@ AppSec default: 2 description: Number of requests analyzed at the same time. + DD_IAST_DEDUPLICATION_ENABLED: + type: Integer + default: True + description: Avoid sending vulnerabilities in the span if they have already been reported in the last hour. + DD_IAST_REDACTION_ENABLED: type: Boolean default: True diff --git a/hatch.toml b/hatch.toml index 7269db2d497..d0063aaa751 100644 --- a/hatch.toml +++ b/hatch.toml @@ -57,7 +57,7 @@ checks = [ "suitespec-check", ] spelling = [ - "codespell -I docs/spelling_wordlist.txt --skip='ddwaf.h,*cassettes*,tests/tracer/fixtures/urls.txt' {args:ddtrace/ tests/ releasenotes/ docs/}", + "codespell -I docs/spelling_wordlist.txt --skip='ddwaf.h,*cassettes*,tests/tracer/fixtures/urls.txt,tests/appsec/iast/fixtures/*' {args:ddtrace/ tests/ releasenotes/ docs/}", ] typing = [ "mypy {args}", @@ -317,7 +317,7 @@ dependencies = [ test = [ "uname -a", "pip freeze", - "DD_CIVISIBILITY_ITR_ENABLED=0 DD_IAST_REQUEST_SAMPLING=100 _DD_APPSEC_DEDUPLICATION_ENABLED=false python -m pytest tests/appsec/iast_packages", + "DD_CIVISIBILITY_ITR_ENABLED=0 DD_IAST_REQUEST_SAMPLING=100 DD_IAST_DEDUPLICATION_ENABLED=false python -m pytest tests/appsec/iast_packages", ] [[envs.appsec_iast_packages.matrix]] @@ -342,7 +342,7 @@ dependencies = [ test = [ "uname -a", "pip freeze", - "DD_CIVISIBILITY_ITR_ENABLED=0 DD_IAST_REQUEST_SAMPLING=100 _DD_APPSEC_DEDUPLICATION_ENABLED=false python -m pytest -vvv {args:tests/appsec/integrations/django_tests/}", + "DD_TRACE_AGENT_URL=\"http://testagent:9126\" DD_CIVISIBILITY_ITR_ENABLED=0 DD_IAST_REQUEST_SAMPLING=100 DD_IAST_DEDUPLICATION_ENABLED=false python -m pytest -vvv {args:tests/appsec/integrations/django_tests/}", ] [[envs.appsec_integrations_django.matrix]] @@ -374,7 +374,7 @@ dependencies = [ test = [ "uname -a", "pip freeze", - "DD_TRACE_AGENT_URL=http://localhost:9126 DD_CIVISIBILITY_ITR_ENABLED=0 DD_IAST_REQUEST_SAMPLING=100 _DD_APPSEC_DEDUPLICATION_ENABLED=false python -m pytest -vvv {args:tests/appsec/integrations/flask_tests/}", + "DD_TRACE_AGENT_URL=\"http://testagent:9126\" DD_CIVISIBILITY_ITR_ENABLED=0 DD_IAST_REQUEST_SAMPLING=100 DD_IAST_DEDUPLICATION_ENABLED=false python -m pytest -vvv {args:tests/appsec/integrations/flask_tests/}", ] [[envs.appsec_integrations_flask.matrix]] @@ -444,8 +444,8 @@ fastapi = ["~=0.114.2"] ## ASM Appsec Aggregated Leak Testing -[envs.appsec_aggregated_leak_testing] -template = "appsec_aggregated_leak_testing" +[envs.iast_aggregated_leak_testing] +template = "iast_aggregated_leak_testing" dependencies = [ "pytest", "pytest-cov", @@ -457,19 +457,20 @@ dependencies = [ "pydantic-settings", ] -[envs.appsec_aggregated_leak_testing.env-vars] +[envs.iast_aggregated_leak_testing.env-vars] CMAKE_BUILD_PARALLEL_LEVEL = "12" DD_IAST_ENABLED = "true" +_DD_IAST_PATCH_MODULES = "scripts.iast" -[envs.appsec_aggregated_leak_testing.scripts] +[envs.iast_aggregated_leak_testing.scripts] test = [ "uname -a", "pip freeze", "python -m pytest tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py", ] -[[envs.appsec_aggregated_leak_testing.matrix]] -python = ["3.10", "3.11", "3.12", "3.13"] +[[envs.iast_aggregated_leak_testing.matrix]] +python = ["3.10", "3.11", "3.12"] diff --git a/pyproject.toml b/pyproject.toml index 03560d1b171..a959a80931f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,8 @@ ddtrace = "ddtrace.contrib.internal.pytest.plugin" "exception-replay" = "ddtrace.debugging._products.exception_replay" "remote-configuration" = "ddtrace.internal.remoteconfig.product" "symbol-database" = "ddtrace.internal.symbol_db.product" +"appsec" = "ddtrace.internal.appsec.product" +"iast" = "ddtrace.internal.iast.product" [project.urls] "Bug Tracker" = "https://github.com/DataDog/dd-trace-py/issues" diff --git a/releasenotes/notes/ci_visibility-fix-pytest-classes-b54582b09727fdc1.yaml b/releasenotes/notes/ci_visibility-fix-pytest-classes-b54582b09727fdc1.yaml new file mode 100644 index 00000000000..829c05fa761 --- /dev/null +++ b/releasenotes/notes/ci_visibility-fix-pytest-classes-b54582b09727fdc1.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + CI Visibility: fixes an issue where Auto Test Retries with pytest would always consider retries of tests defined + inside unittest classes to be successful. diff --git a/releasenotes/notes/fix-llmobs-json-encoding-ascii-f8da77867a910de1.yaml b/releasenotes/notes/fix-llmobs-json-encoding-ascii-f8da77867a910de1.yaml new file mode 100644 index 00000000000..352ffefc369 --- /dev/null +++ b/releasenotes/notes/fix-llmobs-json-encoding-ascii-f8da77867a910de1.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + LLM Observability: This fix resolves an issue where annotating a span with non latin-1 (but valid utf-8) input/output values resulted in encoding errors. diff --git a/releasenotes/notes/fix-vertexai-content-extraction-b216207bd8192e5f.yaml b/releasenotes/notes/fix-vertexai-content-extraction-b216207bd8192e5f.yaml new file mode 100644 index 00000000000..9bd0bba789b --- /dev/null +++ b/releasenotes/notes/fix-vertexai-content-extraction-b216207bd8192e5f.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - | + vertexai: Resolves an issue with ``chat.send_message()`` where the content keyword argument was not parsed correctly. diff --git a/releasenotes/notes/internalize-constants-3-0-73b6970b5a6f3aa5.yaml b/releasenotes/notes/internalize-constants-3-0-73b6970b5a6f3aa5.yaml new file mode 100644 index 00000000000..4f62108293e --- /dev/null +++ b/releasenotes/notes/internalize-constants-3-0-73b6970b5a6f3aa5.yaml @@ -0,0 +1,21 @@ +--- +deprecations: + - | + tracing: Deprecates the following constants in ``ddtrace.constants`` module: + - ANALYTICS_SAMPLE_RATE_KEY + - SAMPLE_RATE_METRIC_KEY + - SAMPLING_PRIORITY_KEY + - SAMPLING_AGENT_DECISION + - SAMPLING_RULE_DECISION + - SAMPLING_LIMIT_DECISION + - ORIGIN_KEY + - USER_ID_KEY + - HOSTNAME_KEY + - RUNTIME_FAMILY + - BASE_SERVICE_KEY + - SPAN_MEASURED_KEY + - KEEP_SPANS_RATE_KEY + - MULTIPLE_IP_HEADERS + - CONFIG_ENDPOINT_ENV + - CONFIG_ENDPOINT_RETRIES_ENV + - CONFIG_ENDPOINT_TIMEOUT_ENV diff --git a/releasenotes/notes/libdatadog-v15.0.0-1004e5bb7f452d9a.yaml b/releasenotes/notes/libdatadog-v15.0.0-1004e5bb7f452d9a.yaml new file mode 100644 index 00000000000..2a6a71020ee --- /dev/null +++ b/releasenotes/notes/libdatadog-v15.0.0-1004e5bb7f452d9a.yaml @@ -0,0 +1,2 @@ +upgrade: + - Bumps libdatadog dependency to v15.0.0. diff --git a/releasenotes/notes/munir-refactor-trace-utils-e887e8da8a01430b.yaml b/releasenotes/notes/munir-refactor-trace-utils-e887e8da8a01430b.yaml new file mode 100644 index 00000000000..40e80ed00a7 --- /dev/null +++ b/releasenotes/notes/munir-refactor-trace-utils-e887e8da8a01430b.yaml @@ -0,0 +1,6 @@ +--- +deprecations: + - | + tracing: Deprecates all attributes in ``ddtrace.contrib.trace_utils_async`` and ``ddtrace.contrib.redis_utils``. + Replaces ``ddtrace.contrib.trace_utils_async.with_traced_module(...)`` with ``ddtrace.contrib.trace_utils.with_traced_module_async(...)``. + Moves public attributes defined in ``ddtrace.contrib.redis_utils.*`` to ``ddtrace.contrib.trace_utils``. \ No newline at end of file diff --git a/releasenotes/notes/remove-patch-unpatch-get-version-from-ints-pub-7bcbcf706a99b70a.yaml b/releasenotes/notes/remove-patch-unpatch-get-version-from-ints-pub-7bcbcf706a99b70a.yaml new file mode 100644 index 00000000000..8cb6bf7d841 --- /dev/null +++ b/releasenotes/notes/remove-patch-unpatch-get-version-from-ints-pub-7bcbcf706a99b70a.yaml @@ -0,0 +1,6 @@ +--- +deprecations: + - | + integrations: Ensures the implementation details of ddtrace integrations are internal to ddtrace library. + In ``ddtrace>=3.0.0`` integrations should only be enabled and configured via ``ddtrace.patch(..)``, ``import ddtrace.auto`` or the ``ddtrace-run`` command. + Unpatching integrations or getting the version of an integration is no longer supported. diff --git a/releasenotes/notes/stacktrace-leak-d6840ab48b29af99.yaml b/releasenotes/notes/stacktrace-leak-d6840ab48b29af99.yaml new file mode 100644 index 00000000000..937c4a8687c --- /dev/null +++ b/releasenotes/notes/stacktrace-leak-d6840ab48b29af99.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Code Security: Implement the detection of the Stacktrace-Leak vulnerability for + Django, Flask and FastAPI. diff --git a/releasenotes/notes/submit-evaluation-for-01096d803d969e3e.yaml b/releasenotes/notes/submit-evaluation-for-01096d803d969e3e.yaml deleted file mode 100644 index c2e4b25f255..00000000000 --- a/releasenotes/notes/submit-evaluation-for-01096d803d969e3e.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -features: - - | - LLM Observability: This introduces the `LLMObs.submit_evaluation_for` method, which provides the ability to join a custom evaluation - to a span using a tag key-value pair on the span. The tag key-value pair is expected to uniquely identify a single span. - Tag-based joining is an alternative to the existing method of joining evaluations to spans using trace and span IDs. - Example usage: - - Evaluation joined by tag: `LLMObs.submit_evaluation_for(span_with_tag_value={"tag_key": "message_id", "tag_value": "dummy_message_id"}, label="rating", ...)`. - - Evaluation joined by trace/span ID: `LLMObs.submit_evaluation_for(span={"trace_id": "...", "span_id": "..."}, label="rating", ...)`. -deprecations: - - | - LLM Observability: `LLMObs.submit_evaluation` is deprecated and will be removed in ddtrace 3.0.0. - As an alternative to `LLMObs.submit_evaluation`, you can use `LLMObs.submit_evaluation_for` instead. - To migrate, replace `LLMObs.submit_evaluation(span_context={"span_id": ..., "trace_id": ...}, ...)` with: - `LLMObs.submit_evaluation_for(span={"span_id": ..., "trace_id": ...}, ...) - You may also join an evaluation to a span using a tag key-value pair like so: - `LLMObs.submit_evaluation_for(span_with_tag_value={"tag_key": ..., "tag_val": ...}, ...)`. diff --git a/riotfile.py b/riotfile.py index b52fa77cbe1..aa51b9349a6 100644 --- a/riotfile.py +++ b/riotfile.py @@ -160,7 +160,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT env={ "DD_CIVISIBILITY_ITR_ENABLED": "0", "DD_IAST_REQUEST_SAMPLING": "100", # Override default 30% to analyze all IAST requests - "_DD_APPSEC_DEDUPLICATION_ENABLED": "false", + "DD_IAST_DEDUPLICATION_ENABLED": "false", }, ), Venv( @@ -180,7 +180,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT env={ "DD_CIVISIBILITY_ITR_ENABLED": "0", "DD_IAST_REQUEST_SAMPLING": "100", # Override default 30% to analyze all IAST requests - "_DD_APPSEC_DEDUPLICATION_ENABLED": "false", + "DD_IAST_DEDUPLICATION_ENABLED": "false", }, ), Venv( @@ -196,7 +196,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT env={ "DD_CIVISIBILITY_ITR_ENABLED": "0", "DD_IAST_REQUEST_SAMPLING": "100", # Override default 30% to analyze all IAST requests - "_DD_APPSEC_DEDUPLICATION_ENABLED": "false", + "DD_IAST_DEDUPLICATION_ENABLED": "false", }, ), Venv( @@ -224,7 +224,7 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT env={ "DD_CIVISIBILITY_ITR_ENABLED": "0", "DD_IAST_REQUEST_SAMPLING": "100", # Override default 30% to analyze all IAST requests - "_DD_APPSEC_DEDUPLICATION_ENABLED": "false", + "DD_IAST_DEDUPLICATION_ENABLED": "false", }, ), Venv( diff --git a/scripts/gen_gitlab_config.py b/scripts/gen_gitlab_config.py index 96dfd5a4ff0..05827fb14ec 100644 --- a/scripts/gen_gitlab_config.py +++ b/scripts/gen_gitlab_config.py @@ -50,7 +50,8 @@ def __str__(self) -> str: if wait_for: lines.append(" before_script:") lines.append(f" - !reference [{base}, before_script]") - lines.append(f" - riot -v run -s --pass-env wait -- {' '.join(wait_for)}") + if self.runner == "riot": + lines.append(f" - riot -v run -s --pass-env wait -- {' '.join(wait_for)}") env = self.env if not env or "SUITE_NAME" not in env: diff --git a/scripts/iast/.env b/scripts/iast/.env index 8c4f081928b..c91476ecc7d 100644 --- a/scripts/iast/.env +++ b/scripts/iast/.env @@ -7,6 +7,6 @@ export DD_TRACE_ENABLED=true export DD_IAST_ENABLED=true export _DD_IAST_DEBUG=true export DD_IAST_REQUEST_SAMPLING=100 -export _DD_APPSEC_DEDUPLICATION_ENABLED=false +export DD_IAST_DEDUPLICATION_ENABLED=false export DD_INSTRUMENTATION_TELEMETRY_ENABLED=true export DD_REMOTE_CONFIGURATION_ENABLED=false \ No newline at end of file diff --git a/scripts/release.py b/scripts/release.py index 0d7344f621d..2a262b0e826 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -332,7 +332,8 @@ def create_notebook(dd_repo, name, rn, base): "We need DD_API_KEY_STAGING and DD_APP_KEY_STAGING values. Please follow the instructions in the script." ) if int(name[-1]) == 1: - last_version = base[:-1] + str(int(base[-1]) - 1) + major, minor = base.split(".", maxsplit=1) + last_version = f"{major}.{str(int(minor) - 1)}" else: print( "Since this is not the RC1 for this release." diff --git a/setup.py b/setup.py index dfaa5f6bf97..89adf166b74 100644 --- a/setup.py +++ b/setup.py @@ -59,9 +59,9 @@ LIBDDWAF_VERSION = "1.22.0" -# DEV: update this accordingly when src/core upgrades libdatadog dependency. -# libdatadog v14.1.0 requires rust 1.76. -RUST_MINIMUM_VERSION = "1.76" +# DEV: update this accordingly when src/native upgrades libdatadog dependency. +# libdatadog v15.0.0 requires rust 1.78. +RUST_MINIMUM_VERSION = "1.78" # Set macOS SDK default deployment target to 10.14 for C++17 support (if unset, may default to 10.9) if CURRENT_OS == "Darwin": @@ -660,8 +660,8 @@ def get_exts_for(name): + get_exts_for("psutil"), rust_extensions=[ RustExtension( - "ddtrace.internal.core._core", - path="src/core/Cargo.toml", + "ddtrace.internal.native._native", + path="src/native/Cargo.toml", py_limited_api="auto", binding=Binding.PyO3, debug=os.getenv("_DD_RUSTC_DEBUG") == "1", diff --git a/src/core/Cargo.lock b/src/native/Cargo.lock similarity index 98% rename from src/core/Cargo.lock rename to src/native/Cargo.lock index f840798f96e..bbf189de0c2 100644 --- a/src/core/Cargo.lock +++ b/src/native/Cargo.lock @@ -28,8 +28,8 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "datadog-ddsketch" -version = "14.3.1" -source = "git+https://github.com/DataDog/libdatadog?rev=v14.3.1#48240f2588665a03c2061879345566ec7e70fabf" +version = "15.0.0" +source = "git+https://github.com/DataDog/libdatadog?rev=v15.0.0#0ef49864317b0728648b2b7f26fe2f1deeeeebc4" dependencies = [ "prost", ] diff --git a/src/core/Cargo.toml b/src/native/Cargo.toml similarity index 90% rename from src/core/Cargo.toml rename to src/native/Cargo.toml index 94eeb6d7a3e..a73bab58a05 100644 --- a/src/core/Cargo.toml +++ b/src/native/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "ddtrace-core" +name = "ddtrace-native" version = "0.1.0" edition = "2021" @@ -10,13 +10,13 @@ opt-level = 3 [dependencies] pyo3 = { version = "0.22.3", features = ["extension-module"] } -datadog-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "v14.3.1" } +datadog-ddsketch = { git = "https://github.com/DataDog/libdatadog", rev = "v15.0.0" } [build-dependencies] pyo3-build-config = "0.21.2" [lib] -name = "_core" +name = "_native" path = "lib.rs" crate-type = ["cdylib"] diff --git a/src/core/ddsketch.rs b/src/native/ddsketch.rs similarity index 91% rename from src/core/ddsketch.rs rename to src/native/ddsketch.rs index fdac5d4a36a..b98279f88e1 100644 --- a/src/core/ddsketch.rs +++ b/src/native/ddsketch.rs @@ -4,7 +4,7 @@ use pyo3::types::PyBytes; use datadog_ddsketch::DDSketch; -#[pyclass(name = "DDSketch", module = "ddtrace.internal._core")] +#[pyclass(name = "DDSketch", module = "ddtrace.internal._native")] pub struct DDSketchPy { ddsketch: DDSketch, } diff --git a/src/core/lib.rs b/src/native/lib.rs similarity index 66% rename from src/core/lib.rs rename to src/native/lib.rs index bb2868fb89a..a3e2825e21a 100644 --- a/src/core/lib.rs +++ b/src/native/lib.rs @@ -3,7 +3,7 @@ mod ddsketch; use pyo3::prelude::*; #[pymodule] -fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> { +fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::()?; Ok(()) } diff --git a/tests/appsec/appsec/test_appsec_trace_utils.py b/tests/appsec/appsec/test_appsec_trace_utils.py index b550b103782..b4dbf97c8e2 100644 --- a/tests/appsec/appsec/test_appsec_trace_utils.py +++ b/tests/appsec/appsec/test_appsec_trace_utils.py @@ -11,7 +11,7 @@ from ddtrace.appsec.trace_utils import track_user_login_failure_event from ddtrace.appsec.trace_utils import track_user_login_success_event from ddtrace.appsec.trace_utils import track_user_signup_event -from ddtrace.contrib.trace_utils import set_user +from ddtrace.contrib.internal.trace_utils import set_user from ddtrace.ext import user import tests.appsec.rules as rules from tests.appsec.utils import asm_context diff --git a/tests/appsec/appsec/test_asm_standalone.py b/tests/appsec/appsec/test_asm_standalone.py index 16390888055..4ed15628fb3 100644 --- a/tests/appsec/appsec/test_asm_standalone.py +++ b/tests/appsec/appsec/test_asm_standalone.py @@ -4,7 +4,7 @@ import pytest import ddtrace -from ddtrace.contrib.trace_utils import set_http_meta +from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.ext import SpanTypes from tests.utils import override_env diff --git a/tests/appsec/appsec/test_processor.py b/tests/appsec/appsec/test_processor.py index d3b133b2e21..117a55175f9 100644 --- a/tests/appsec/appsec/test_processor.py +++ b/tests/appsec/appsec/test_processor.py @@ -14,7 +14,7 @@ from ddtrace.appsec._processor import _transform_headers from ddtrace.appsec._utils import get_triggers from ddtrace.constants import USER_KEEP -from ddtrace.contrib.trace_utils import set_http_meta +from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.ext import SpanTypes from ddtrace.internal import core import tests.appsec.rules as rules diff --git a/tests/appsec/appsec/test_remoteconfiguration.py b/tests/appsec/appsec/test_remoteconfiguration.py index 6f521de457a..6cecfeb2da8 100644 --- a/tests/appsec/appsec/test_remoteconfiguration.py +++ b/tests/appsec/appsec/test_remoteconfiguration.py @@ -19,7 +19,7 @@ from ddtrace.appsec._remoteconfiguration import disable_appsec_rc from ddtrace.appsec._remoteconfiguration import enable_appsec_rc from ddtrace.appsec._utils import get_triggers -from ddtrace.contrib.trace_utils import set_http_meta +from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.internal import core from ddtrace.internal.remoteconfig.client import AgentPayload from ddtrace.internal.remoteconfig.client import ConfigMetadata diff --git a/tests/appsec/appsec/test_telemetry.py b/tests/appsec/appsec/test_telemetry.py index 47b58222dde..10c8a0c0307 100644 --- a/tests/appsec/appsec/test_telemetry.py +++ b/tests/appsec/appsec/test_telemetry.py @@ -10,7 +10,7 @@ import ddtrace.appsec._ddwaf.ddwaf_types from ddtrace.appsec._deduplications import deduplication from ddtrace.appsec._processor import AppSecSpanProcessor -from ddtrace.contrib.trace_utils import set_http_meta +from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.ext import SpanTypes from ddtrace.internal.telemetry.constants import TELEMETRY_NAMESPACE from ddtrace.internal.telemetry.constants import TELEMETRY_TYPE_DISTRIBUTION @@ -104,7 +104,7 @@ def test_log_metric_error_ddwaf_init(telemetry_writer): with override_global_config( dict( _asm_enabled=True, - _deduplication_enabled=False, + _asm_deduplication_enabled=False, _asm_static_rule_file=os.path.join(rules.ROOT_DIR, "rules-with-2-errors.json"), ) ): @@ -124,7 +124,7 @@ def test_log_metric_error_ddwaf_timeout(telemetry_writer, tracer): config = dict( _asm_enabled=True, _waf_timeout=0.0, - _deduplication_enabled=False, + _asm_deduplication_enabled=False, _asm_static_rule_file=rules.RULES_GOOD_PATH, ) with asm_context(tracer=tracer, ip_addr=rules._IP.BLOCKED, span_name="test", config=config) as span: @@ -149,7 +149,7 @@ def test_log_metric_error_ddwaf_timeout(telemetry_writer, tracer): def test_log_metric_error_ddwaf_update(telemetry_writer): - with override_global_config(dict(_asm_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_asm_enabled=True, _asm_deduplication_enabled=False)): span_processor = AppSecSpanProcessor() span_processor._update_rules(invalid_rule_update) @@ -170,7 +170,7 @@ def _wrapped_run(*args, **kwargs): @mock.patch.object(ddtrace.appsec._ddwaf, "ddwaf_run", new=_wrapped_run) def test_log_metric_error_ddwaf_internal_error(telemetry_writer): """Test that an internal error is logged when the WAF returns an internal error.""" - with override_global_config(dict(_asm_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_asm_enabled=True, _asm_deduplication_enabled=False)): with tracer.trace("test", span_type=SpanTypes.WEB, service="test") as span: span_processor = AppSecSpanProcessor() span_processor.on_span_start(span) diff --git a/tests/appsec/appsec_utils.py b/tests/appsec/appsec_utils.py index cbcc913a7af..a7b12464bfe 100644 --- a/tests/appsec/appsec_utils.py +++ b/tests/appsec/appsec_utils.py @@ -99,7 +99,7 @@ def appsec_application_server( if iast_enabled is not None and iast_enabled != "false": env[IAST.ENV] = iast_enabled env[IAST.ENV_REQUEST_SAMPLING] = "100" - env["_DD_APPSEC_DEDUPLICATION_ENABLED"] = "false" + env["DD_IAST_DEDUPLICATION_ENABLED"] = "false" env[IAST.ENV_NO_DIR_PATCH] = "false" if assert_debug: env["_" + IAST.ENV_DEBUG] = iast_enabled diff --git a/tests/appsec/iast/_ast/conftest.py b/tests/appsec/iast/_ast/conftest.py index 5942aa90e4b..06a98ce50f7 100644 --- a/tests/appsec/iast/_ast/conftest.py +++ b/tests/appsec/iast/_ast/conftest.py @@ -7,7 +7,9 @@ @pytest.fixture(autouse=True) def iast_create_context(): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): _start_iast_context_and_oce() yield _end_iast_context_and_oce() diff --git a/tests/appsec/iast/aspects/conftest.py b/tests/appsec/iast/aspects/conftest.py index 51e9d7b2190..814ef4e3b21 100644 --- a/tests/appsec/iast/aspects/conftest.py +++ b/tests/appsec/iast/aspects/conftest.py @@ -33,7 +33,9 @@ def _iast_patched_module(module_name, new_module_object=False): @pytest.fixture(autouse=True) def iast_create_context(): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100) + ): _start_iast_context_and_oce() yield _end_iast_context_and_oce() diff --git a/tests/appsec/iast/conftest.py b/tests/appsec/iast/conftest.py index 85d516dd154..cf295f9190f 100644 --- a/tests/appsec/iast/conftest.py +++ b/tests/appsec/iast/conftest.py @@ -85,12 +85,12 @@ def iast_context(env, request_sampling=100.0, deduplication=False, asm_enabled=F class MockSpan: _trace_id_64bits = 17577308072598193742 - env.update({"_DD_APPSEC_DEDUPLICATION_ENABLED": str(deduplication)}) + env.update({"DD_IAST_DEDUPLICATION_ENABLED": str(deduplication)}) with override_global_config( dict( _asm_enabled=asm_enabled, _iast_enabled=True, - _deduplication_enabled=deduplication, + _iast_deduplication_enabled=deduplication, _iast_request_sampling=request_sampling, ) ), override_env(env): diff --git a/tests/appsec/iast/fixtures/django_debug_page.html b/tests/appsec/iast/fixtures/django_debug_page.html new file mode 100644 index 00000000000..217a3350ecf --- /dev/null +++ b/tests/appsec/iast/fixtures/django_debug_page.html @@ -0,0 +1,2023 @@ + + + + + + IndexError + at / + + + + + + +
+

IndexError + at /

+
No exception message supplied
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Request Method:GET
Request URL:http://localhost:8000/
Django Version:5.1.5
Exception Type:IndexError
Exception Location:/home/foobaruser/sources/minimal-django-example/app.py, line 20, in index_view
Raised during:__main__.index_view
Python Executable:/home/foobaruser/.pyenv/versions/testsca/bin/python
Python Version:3.12.5
Python Path:
['/home/foobaruser/sources/minimal-django-example',
+ '/home/foobaruser/.pyenv/versions/3.12.5/lib/python312.zip',
+ '/home/foobaruser/.pyenv/versions/3.12.5/lib/python3.12',
+ '/home/foobaruser/.pyenv/versions/3.12.5/lib/python3.12/lib-dynload',
+ '/home/foobaruser/.pyenv/versions/testsca/lib/python3.12/site-packages']
Server time:Mon, 20 Jan 2025 04:38:00 -0600
+
+ +
+ + + + +
+

Traceback + Switch to copy-and-paste view +

+
+
    + + +
  • + + /home/foobaruser/.pyenv/versions/testsca/lib/python3.12/site-packages/django/core/handlers/exception.py, line 55, in inner + + + +
    + +
      + +
    1. + +
    2.         return inner
    3. + +
    4.     else:
    5. + +
    6. + +
    7.         @wraps(get_response)
    8. + +
    9.         def inner(request):
    10. + +
    11.             try:
    12. + +
    + +
      +
    1.                 response = get_response(request)
      +                               ^^^^^^^^^^^^^^^^^^^^^
    2. +
    + +
      + +
    1.             except Exception as exc:
    2. + +
    3.                 response = response_for_exception(request, exc)
    4. + +
    5.             return response
    6. + +
    7. + +
    8.         return inner
    9. + +
    10. + +
    + +
    + + + + +
    + Local vars + + + + + + + + + + + + + + + + + + + + + + + + + + +
    VariableValue
    exc
    IndexError()
    get_response
    <bound method BaseHandler._get_response of <django.core.handlers.wsgi.WSGIHandler object at 0x739fa54fbf20>>
    request
    <WSGIRequest: GET '/'>
    +
    + +
  • + + +
  • + + /home/foobaruser/.pyenv/versions/testsca/lib/python3.12/site-packages/django/core/handlers/base.py, line 197, in _get_response + + + +
    + +
      + +
    1. + +
    2.         if response is None:
    3. + +
    4.             wrapped_callback = self.make_view_atomic(callback)
    5. + +
    6.             # If it is an asynchronous view, run it in a subthread.
    7. + +
    8.             if iscoroutinefunction(wrapped_callback):
    9. + +
    10.                 wrapped_callback = async_to_sync(wrapped_callback)
    11. + +
    12.             try:
    13. + +
    + +
      +
    1.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)
      +                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    2. +
    + +
      + +
    1.             except Exception as e:
    2. + +
    3.                 response = self.process_exception_by_middleware(e, request)
    4. + +
    5.                 if response is None:
    6. + +
    7.                     raise
    8. + +
    9. + +
    10.         # Complain if the view returned None (a common error).
    11. + +
    + +
    + + + + +
    + Local vars + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    VariableValue
    callback
    <function index_view at 0x739fa76b4d60>
    callback_args
    ()
    callback_kwargs
    {}
    request
    <WSGIRequest: GET '/'>
    response
    None
    self
    <django.core.handlers.wsgi.WSGIHandler object at 0x739fa54fbf20>
    wrapped_callback
    <function index_view at 0x739fa76b4d60>
    +
    + +
  • + + +
  • + + /home/foobaruser/sources/minimal-django-example/app.py, line 20, in index_view + + + +
    + +
      + +
    1.             "DIRS": ["templates"],
    2. + +
    3.         },
    4. + +
    5.     ],
    6. + +
    7. )
    8. + +
    9. + +
    10. + +
    11. def index_view(request):
    12. + +
    + +
      +
    1.     raise IndexError()
      +        ^^^^^^^^^^^^^^^^^^
    2. +
    + +
      + +
    1.     return HttpResponse("<h1>Hello World From Django!</h1>")
    2. + +
    3. + +
    4. + +
    5. def hello_view(request, name):
    6. + +
    7.     return render(request, "template.html", {"name": name})
    8. + +
    9. + +
    + +
    + + + + +
    + Local vars + + + + + + + + + + + + + + + + +
    VariableValue
    request
    <WSGIRequest: GET '/'>
    +
    + +
  • + +
+
+ +
+
+ + + + + +

+ +
+
+ +
+ + +
+

Request information

+ + + +

USER

+

[unable to retrieve the current user]

+ + +

GET

+ +

No GET data

+ + +

POST

+ +

No POST data

+ + +

FILES

+ +

No FILES data

+ + + + + + + + + + + + + + + + + + + +
VariableValue
csrftoken
'********************'
+ + +

META

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
VariableValue
AWS_ASSUME_ROLE_TTL
'1h'
AWS_SESSION_TTL
'24h'
AWS_VAULT_BACKEND
'secret-service'
AWS_VAULT_KEYCHAIN_NAME
'********************'
COLORTERM
'truecolor'
CONTENT_LENGTH
''
CONTENT_TYPE
'text/plain'
DBUS_SESSION_BUS_ADDRESS
'unix:path=/run/user/1000/bus,guid=c3ac961ccc4c263877782e00678e0c9e'
DBUS_STARTER_ADDRESS
'unix:path=/run/user/1000/bus,guid=c3ac961ccc4c263877782e00678e0c9e'
DBUS_STARTER_BUS_TYPE
'session'
DESKTOP_SESSION
'ubuntu'
DISPLAY
':0'
EDITOR
'vim'
EMAIL
'Juanjo Alvarez <juanjo@juanjoalvarez.net>'
GATEWAY_INTERFACE
'CGI/1.1'
GDMSESSION
'ubuntu'
GITLAB_TOKEN
'********************'
GNOME_DESKTOP_SESSION_ID
'this-is-deprecated'
GNOME_SETUP_DISPLAY
':1'
GNOME_SHELL_SESSION_MODE
'ubuntu'
GOROOT
'/home/foobaruser/go'
GTK_MODULES
'gail:atk-bridge'
HDIV_PHP_EXTENSION_PATH
'/home/foobaruser/php/hdiv-php-extension/'
HOME
'/home/foobaruser'
HTTP_ACCEPT
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7'
HTTP_ACCEPT_ENCODING
'gzip, deflate, br, zstd'
HTTP_ACCEPT_LANGUAGE
'en-US,en;q=0.9'
HTTP_CACHE_CONTROL
'max-age=0'
HTTP_CONNECTION
'keep-alive'
HTTP_COOKIE
'********************'
HTTP_DNT
'1'
HTTP_HOST
'localhost:8000'
HTTP_SEC_CH_UA
'"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"'
HTTP_SEC_CH_UA_MOBILE
'?0'
HTTP_SEC_CH_UA_PLATFORM
'"Linux"'
HTTP_SEC_FETCH_DEST
'document'
HTTP_SEC_FETCH_MODE
'navigate'
HTTP_SEC_FETCH_SITE
'none'
HTTP_SEC_FETCH_USER
'?1'
HTTP_UPGRADE_INSECURE_REQUESTS
'1'
HTTP_USER_AGENT
('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) '
+ 'Chrome/131.0.0.0 Safari/537.36')
IM_CONFIG_CHECK_ENV
'1'
IM_CONFIG_PHASE
'1'
INVOCATION_ID
'161a1822b4c24d129b1f0e3ef745a30f'
JOURNAL_STREAM
'8:61973'
LANG
'en_US.UTF-8'
LC_ADDRESS
'es_ES.UTF-8'
LC_IDENTIFICATION
'es_ES.UTF-8'
LC_MEASUREMENT
'es_ES.UTF-8'
LC_MONETARY
'es_ES.UTF-8'
LC_NAME
'es_ES.UTF-8'
LC_NUMERIC
'es_ES.UTF-8'
LC_PAPER
'es_ES.UTF-8'
LC_TELEPHONE
'es_ES.UTF-8'
LC_TIME
'es_ES.UTF-8'
LOGNAME
'foobaruser'
LS_COLORS
''
LS_OPTIONS
'-N --color=auto -h'
MANAGERPID
'4414'
MANPATH
':/opt/puppetlabs/puppet/share/man'
OMF_CONFIG
'/home/foobaruser/.config/omf'
OMF_PATH
'/home/foobaruser/.local/share/omf'
PATH
'/home/foobaruser/.pyenv/versions/testsca/bin:/home/foobaruser/.pyenv/libexec:/home/foobaruser/.pyenv/plugins/python-build/bin:/home/foobaruser/.pyenv/plugins/pyenv-virtualenv/bin:/home/foobaruser/.pyenv/plugins/pyenv-update/bin:/home/foobaruser/.pyenv/plugins/pyenv-installer/bin:/home/foobaruser/.pyenv/plugins/pyenv-doctor/bin:/home/foobaruser/.pyenv/plugins/pyenv-virtualenv/shims:/home/foobaruser/.pyenv/shims:/home/foobaruser/.pyenv/bin:/home/foobaruser/pyenv/bin:/home/foobaruser/.local/bin:/home/foobaruser/sources/graalvm-ce-java11-20.1.0/bin:/home/foobaruser/.fzf/bin:/home/foobaruser/.cargo/bin:/home/foobaruser/.krew/bin:/home/foobaruser/.tfenv/bin:/home/foobaruser/dd/devtools/bin:/usr/local/bin:/home/foobaruser/.local/bin:/home/foobaruser/.krew/bin:/home/foobaruser/.pyenv/bin:/home/foobaruser/.tfenv/bin:/home/foobaruser/dd/devtools/bin:/usr/local/bin:/home/foobaruser/.yarn/bin:/home/foobaruser/.config/yarn/global/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin:/opt/puppetlabs/bin:/home/foobaruser/bin:/home/foobaruser/sync/work/d/dmd2:/home/foobaruser/sources/nim/bin:/home/foobaruser/.nimble/bin'
PATH_INFO
'/'
PWD
'/home/foobaruser/sources/minimal-django-example'
PYENV_DIR
'/home/foobaruser/sources/minimal-django-example'
PYENV_HOOK_PATH
'/home/foobaruser/.pyenv/pyenv.d:/usr/etc/pyenv.d:/usr/local/etc/pyenv.d:/etc/pyenv.d:/usr/lib/pyenv/hooks:/home/foobaruser/.pyenv/plugins/pyenv-virtualenv/etc/pyenv.d:/home/foobaruser/.pyenv/plugins/pyenv-which-ext/etc/pyenv.d'
PYENV_ROOT
'/home/foobaruser/.pyenv'
PYENV_SHELL
'fish'
PYENV_VERSION
'testsca'
PYENV_VIRTUALENV_INIT
'1'
PYENV_VIRTUAL_ENV
'/home/foobaruser/.pyenv/versions/3.12.5/envs/testsca'
QT_ACCESSIBILITY
'1'
QT_IM_MODULE
'ibus'
QUERY_STRING
''
REMOTE_ADDR
'127.0.0.1'
REMOTE_HOST
''
REQUEST_METHOD
'GET'
RSYNC_PASSWORD
'********************'
RUN_MAIN
'true'
SCRIPT_NAME
''
SERVER_NAME
'localhost'
SERVER_PORT
'8000'
SERVER_PROTOCOL
'HTTP/1.1'
SERVER_SOFTWARE
'WSGIServer/0.2'
SESSION_MANAGER
'local/foobaruser-ThinkPad-P15v-Gen-2i:@/tmp/.ICE-unix/5619,unix/foobaruser-ThinkPad-P15v-Gen-2i:/tmp/.ICE-unix/5619'
SHELL
'/bin/bash'
SHLVL
'1'
SSH_AGENT_LAUNCHER
'gnome-keyring'
SSH_AUTH_SOCK
'/run/user/1000/keyring/ssh'
SYSTEMD_EXEC_PID
'5619'
TERM
'xterm-256color'
TILIX_ID
'62221076-149e-426c-bba3-cf14d3a9099a'
USER
'foobaruser'
USERNAME
'foobaruser'
VIRTUAL_ENV
'/home/foobaruser/.pyenv/versions/3.12.5/envs/testsca'
VTE_VERSION
'6800'
WAYLAND_DISPLAY
'wayland-0'
XAUTHORITY
'/run/user/1000/.mutter-Xwaylandauth.50H8Z2'
XDG_CONFIG_DIRS
'/etc/xdg/xdg-ubuntu:/etc/xdg'
XDG_CURRENT_DESKTOP
'ubuntu:GNOME'
XDG_DATA_DIRS
'/usr/share/ubuntu:/home/foobaruser/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share/:/usr/share/:/var/lib/snapd/desktop'
XDG_MENU_PREFIX
'gnome-'
XDG_RUNTIME_DIR
'/run/user/1000'
XDG_SESSION_CLASS
'user'
XDG_SESSION_DESKTOP
'ubuntu'
XDG_SESSION_TYPE
'wayland'
XMODIFIERS
'@im=ibus'
wsgi.errors
<_io.TextIOWrapper name='<stderr>' mode='w' encoding='utf-8'>
wsgi.file_wrapper
<class 'wsgiref.util.FileWrapper'>
wsgi.input
<django.core.handlers.wsgi.LimitedStream object at 0x739fa53ad4b0>
wsgi.multiprocess
False
wsgi.multithread
True
wsgi.run_once
False
wsgi.url_scheme
'http'
wsgi.version
(1, 0)
+ + +

Settings

+

Using settings module

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SettingValue
ABSOLUTE_URL_OVERRIDES
{}
ADMINS
[]
ALLOWED_HOSTS
[]
APPEND_SLASH
True
AUTHENTICATION_BACKENDS
['django.contrib.auth.backends.ModelBackend']
AUTH_PASSWORD_VALIDATORS
'********************'
AUTH_USER_MODEL
'auth.User'
CACHES
{'default': {'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'}}
CACHE_MIDDLEWARE_ALIAS
'default'
CACHE_MIDDLEWARE_KEY_PREFIX
'********************'
CACHE_MIDDLEWARE_SECONDS
600
CSRF_COOKIE_AGE
31449600
CSRF_COOKIE_DOMAIN
None
CSRF_COOKIE_HTTPONLY
False
CSRF_COOKIE_NAME
'csrftoken'
CSRF_COOKIE_PATH
'/'
CSRF_COOKIE_SAMESITE
'Lax'
CSRF_COOKIE_SECURE
False
CSRF_FAILURE_VIEW
'django.views.csrf.csrf_failure'
CSRF_HEADER_NAME
'HTTP_X_CSRFTOKEN'
CSRF_TRUSTED_ORIGINS
[]
CSRF_USE_SESSIONS
False
DATABASES
{'default': {'ATOMIC_REQUESTS': False,
+             'AUTOCOMMIT': True,
+             'CONN_HEALTH_CHECKS': False,
+             'CONN_MAX_AGE': 0,
+             'ENGINE': 'django.db.backends.dummy',
+             'HOST': '',
+             'NAME': '',
+             'OPTIONS': {},
+             'PASSWORD': '********************',
+             'PORT': '',
+             'TEST': {'CHARSET': None,
+                      'COLLATION': None,
+                      'MIGRATE': True,
+                      'MIRROR': None,
+                      'NAME': None},
+             'TIME_ZONE': None,
+             'USER': ''}}
DATABASE_ROUTERS
[]
DATA_UPLOAD_MAX_MEMORY_SIZE
2621440
DATA_UPLOAD_MAX_NUMBER_FIELDS
1000
DATA_UPLOAD_MAX_NUMBER_FILES
100
DATETIME_FORMAT
'N j, Y, P'
DATETIME_INPUT_FORMATS
['%Y-%m-%d %H:%M:%S',
+ '%Y-%m-%d %H:%M:%S.%f',
+ '%Y-%m-%d %H:%M',
+ '%m/%d/%Y %H:%M:%S',
+ '%m/%d/%Y %H:%M:%S.%f',
+ '%m/%d/%Y %H:%M',
+ '%m/%d/%y %H:%M:%S',
+ '%m/%d/%y %H:%M:%S.%f',
+ '%m/%d/%y %H:%M']
DATE_FORMAT
'N j, Y'
DATE_INPUT_FORMATS
['%Y-%m-%d',
+ '%m/%d/%Y',
+ '%m/%d/%y',
+ '%b %d %Y',
+ '%b %d, %Y',
+ '%d %b %Y',
+ '%d %b, %Y',
+ '%B %d %Y',
+ '%B %d, %Y',
+ '%d %B %Y',
+ '%d %B, %Y']
DEBUG
True
DEBUG_PROPAGATE_EXCEPTIONS
False
DECIMAL_SEPARATOR
'.'
DEFAULT_AUTO_FIELD
'django.db.models.AutoField'
DEFAULT_CHARSET
'utf-8'
DEFAULT_EXCEPTION_REPORTER
'django.views.debug.ExceptionReporter'
DEFAULT_EXCEPTION_REPORTER_FILTER
'django.views.debug.SafeExceptionReporterFilter'
DEFAULT_FROM_EMAIL
'webmaster@localhost'
DEFAULT_INDEX_TABLESPACE
''
DEFAULT_TABLESPACE
''
DISALLOWED_USER_AGENTS
[]
EMAIL_BACKEND
'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST
'localhost'
EMAIL_HOST_PASSWORD
'********************'
EMAIL_HOST_USER
''
EMAIL_PORT
25
EMAIL_SSL_CERTFILE
None
EMAIL_SSL_KEYFILE
'********************'
EMAIL_SUBJECT_PREFIX
'[Django] '
EMAIL_TIMEOUT
None
EMAIL_USE_LOCALTIME
False
EMAIL_USE_SSL
False
EMAIL_USE_TLS
False
FILE_UPLOAD_DIRECTORY_PERMISSIONS
None
FILE_UPLOAD_HANDLERS
['django.core.files.uploadhandler.MemoryFileUploadHandler',
+ 'django.core.files.uploadhandler.TemporaryFileUploadHandler']
FILE_UPLOAD_MAX_MEMORY_SIZE
2621440
FILE_UPLOAD_PERMISSIONS
420
FILE_UPLOAD_TEMP_DIR
None
FIRST_DAY_OF_WEEK
0
FIXTURE_DIRS
[]
FORCE_SCRIPT_NAME
None
FORMAT_MODULE_PATH
None
FORMS_URLFIELD_ASSUME_HTTPS
False
FORM_RENDERER
'django.forms.renderers.DjangoTemplates'
IGNORABLE_404_URLS
[]
INSTALLED_APPS
[]
INTERNAL_IPS
[]
LANGUAGES
[('af', 'Afrikaans'),
+ ('ar', 'Arabic'),
+ ('ar-dz', 'Algerian Arabic'),
+ ('ast', 'Asturian'),
+ ('az', 'Azerbaijani'),
+ ('bg', 'Bulgarian'),
+ ('be', 'Belarusian'),
+ ('bn', 'Bengali'),
+ ('br', 'Breton'),
+ ('bs', 'Bosnian'),
+ ('ca', 'Catalan'),
+ ('ckb', 'Central Kurdish (Sorani)'),
+ ('cs', 'Czech'),
+ ('cy', 'Welsh'),
+ ('da', 'Danish'),
+ ('de', 'German'),
+ ('dsb', 'Lower Sorbian'),
+ ('el', 'Greek'),
+ ('en', 'English'),
+ ('en-au', 'Australian English'),
+ ('en-gb', 'British English'),
+ ('eo', 'Esperanto'),
+ ('es', 'Spanish'),
+ ('es-ar', 'Argentinian Spanish'),
+ ('es-co', 'Colombian Spanish'),
+ ('es-mx', 'Mexican Spanish'),
+ ('es-ni', 'Nicaraguan Spanish'),
+ ('es-ve', 'Venezuelan Spanish'),
+ ('et', 'Estonian'),
+ ('eu', 'Basque'),
+ ('fa', 'Persian'),
+ ('fi', 'Finnish'),
+ ('fr', 'French'),
+ ('fy', 'Frisian'),
+ ('ga', 'Irish'),
+ ('gd', 'Scottish Gaelic'),
+ ('gl', 'Galician'),
+ ('he', 'Hebrew'),
+ ('hi', 'Hindi'),
+ ('hr', 'Croatian'),
+ ('hsb', 'Upper Sorbian'),
+ ('hu', 'Hungarian'),
+ ('hy', 'Armenian'),
+ ('ia', 'Interlingua'),
+ ('id', 'Indonesian'),
+ ('ig', 'Igbo'),
+ ('io', 'Ido'),
+ ('is', 'Icelandic'),
+ ('it', 'Italian'),
+ ('ja', 'Japanese'),
+ ('ka', 'Georgian'),
+ ('kab', 'Kabyle'),
+ ('kk', 'Kazakh'),
+ ('km', 'Khmer'),
+ ('kn', 'Kannada'),
+ ('ko', 'Korean'),
+ ('ky', 'Kyrgyz'),
+ ('lb', 'Luxembourgish'),
+ ('lt', 'Lithuanian'),
+ ('lv', 'Latvian'),
+ ('mk', 'Macedonian'),
+ ('ml', 'Malayalam'),
+ ('mn', 'Mongolian'),
+ ('mr', 'Marathi'),
+ ('ms', 'Malay'),
+ ('my', 'Burmese'),
+ ('nb', 'Norwegian Bokmål'),
+ ('ne', 'Nepali'),
+ ('nl', 'Dutch'),
+ ('nn', 'Norwegian Nynorsk'),
+ ('os', 'Ossetic'),
+ ('pa', 'Punjabi'),
+ ('pl', 'Polish'),
+ ('pt', 'Portuguese'),
+ ('pt-br', 'Brazilian Portuguese'),
+ ('ro', 'Romanian'),
+ ('ru', 'Russian'),
+ ('sk', 'Slovak'),
+ ('sl', 'Slovenian'),
+ ('sq', 'Albanian'),
+ ('sr', 'Serbian'),
+ ('sr-latn', 'Serbian Latin'),
+ ('sv', 'Swedish'),
+ ('sw', 'Swahili'),
+ ('ta', 'Tamil'),
+ ('te', 'Telugu'),
+ ('tg', 'Tajik'),
+ ('th', 'Thai'),
+ ('tk', 'Turkmen'),
+ ('tr', 'Turkish'),
+ ('tt', 'Tatar'),
+ ('udm', 'Udmurt'),
+ ('ug', 'Uyghur'),
+ ('uk', 'Ukrainian'),
+ ('ur', 'Urdu'),
+ ('uz', 'Uzbek'),
+ ('vi', 'Vietnamese'),
+ ('zh-hans', 'Simplified Chinese'),
+ ('zh-hant', 'Traditional Chinese')]
LANGUAGES_BIDI
['he', 'ar', 'ar-dz', 'ckb', 'fa', 'ug', 'ur']
LANGUAGE_CODE
'en-us'
LANGUAGE_COOKIE_AGE
None
LANGUAGE_COOKIE_DOMAIN
None
LANGUAGE_COOKIE_HTTPONLY
False
LANGUAGE_COOKIE_NAME
'django_language'
LANGUAGE_COOKIE_PATH
'/'
LANGUAGE_COOKIE_SAMESITE
None
LANGUAGE_COOKIE_SECURE
False
LOCALE_PATHS
[]
LOGGING
{}
LOGGING_CONFIG
'logging.config.dictConfig'
LOGIN_REDIRECT_URL
'/accounts/profile/'
LOGIN_URL
'/accounts/login/'
LOGOUT_REDIRECT_URL
None
MANAGERS
[]
MEDIA_ROOT
''
MEDIA_URL
'/'
MESSAGE_STORAGE
'django.contrib.messages.storage.fallback.FallbackStorage'
MIDDLEWARE
[]
MIGRATION_MODULES
{}
MONTH_DAY_FORMAT
'F j'
NUMBER_GROUPING
0
PASSWORD_HASHERS
'********************'
PASSWORD_RESET_TIMEOUT
'********************'
PREPEND_WWW
False
ROOT_URLCONF
'__main__'
SECRET_KEY
'********************'
SECRET_KEY_FALLBACKS
'********************'
SECURE_CONTENT_TYPE_NOSNIFF
True
SECURE_CROSS_ORIGIN_OPENER_POLICY
'same-origin'
SECURE_HSTS_INCLUDE_SUBDOMAINS
False
SECURE_HSTS_PRELOAD
False
SECURE_HSTS_SECONDS
0
SECURE_PROXY_SSL_HEADER
None
SECURE_REDIRECT_EXEMPT
[]
SECURE_REFERRER_POLICY
'same-origin'
SECURE_SSL_HOST
None
SECURE_SSL_REDIRECT
False
SERVER_EMAIL
'root@localhost'
SESSION_CACHE_ALIAS
'default'
SESSION_COOKIE_AGE
1209600
SESSION_COOKIE_DOMAIN
None
SESSION_COOKIE_HTTPONLY
True
SESSION_COOKIE_NAME
'sessionid'
SESSION_COOKIE_PATH
'/'
SESSION_COOKIE_SAMESITE
'Lax'
SESSION_COOKIE_SECURE
False
SESSION_ENGINE
'django.contrib.sessions.backends.db'
SESSION_EXPIRE_AT_BROWSER_CLOSE
False
SESSION_FILE_PATH
None
SESSION_SAVE_EVERY_REQUEST
False
SESSION_SERIALIZER
'django.contrib.sessions.serializers.JSONSerializer'
SHORT_DATETIME_FORMAT
'm/d/Y P'
SHORT_DATE_FORMAT
'm/d/Y'
SIGNING_BACKEND
'django.core.signing.TimestampSigner'
SILENCED_SYSTEM_CHECKS
[]
STATICFILES_DIRS
[]
STATICFILES_FINDERS
['django.contrib.staticfiles.finders.FileSystemFinder',
+ 'django.contrib.staticfiles.finders.AppDirectoriesFinder']
STATIC_ROOT
None
STATIC_URL
None
STORAGES
{'default': {'BACKEND': 'django.core.files.storage.FileSystemStorage'},
+ 'staticfiles': {'BACKEND': 'django.contrib.staticfiles.storage.StaticFilesStorage'}}
TEMPLATES
[{'BACKEND': 'django.template.backends.django.DjangoTemplates',
+  'DIRS': ['templates']}]
TEST_NON_SERIALIZED_APPS
[]
TEST_RUNNER
'django.test.runner.DiscoverRunner'
THOUSAND_SEPARATOR
','
TIME_FORMAT
'P'
TIME_INPUT_FORMATS
['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
TIME_ZONE
'America/Chicago'
USE_I18N
True
USE_THOUSAND_SEPARATOR
False
USE_TZ
True
USE_X_FORWARDED_HOST
False
USE_X_FORWARDED_PORT
False
WSGI_APPLICATION
None
X_FRAME_OPTIONS
'DENY'
YEAR_MONTH_FORMAT
'F Y'
+ +
+
+ + +
+

+ You’re seeing this error because you have DEBUG = True in your + Django settings file. Change that to False, and Django will + display a standard page generated by the handler for this status code. +

+
+ + + diff --git a/tests/appsec/iast/fixtures/plain_stacktrace.txt b/tests/appsec/iast/fixtures/plain_stacktrace.txt new file mode 100644 index 00000000000..be648d208ae --- /dev/null +++ b/tests/appsec/iast/fixtures/plain_stacktrace.txt @@ -0,0 +1,35 @@ +Environment: + + +Request Method: GET +Request URL: http://localhost:8000/ + +Django Version: 5.1.5 +Python Version: 3.12.5 +Installed Applications: +[] +Installed Middleware: +[] + +Traceback (most recent call last): + File "/usr/local/lib/python3.9/site-packages/some_module.py", line 42, in process_data + result = complex_calculation(data) + File "/usr/local/lib/python3.9/site-packages/another_module.py", line 158, in complex_calculation + intermediate = perform_subtask(data_slice) + File "/usr/local/lib/python3.9/site-packages/subtask_module.py", line 27, in perform_subtask + processed = handle_special_case(data_slice) + File "/usr/local/lib/python3.9/site-packages/special_cases.py", line 84, in handle_special_case + return apply_algorithm(data_slice, params) + File "/usr/local/lib/python3.9/site-packages/algorithm_module.py", line 112, in apply_algorithm + step_result = execute_step(data, params) + File "/usr/local/lib/python3.9/site-packages/step_execution.py", line 55, in execute_step + temp = pre_process(data) + File "/usr/local/lib/python3.9/site-packages/pre_processing.py", line 33, in pre_process + validated_data = validate_input(data) + File "/usr/local/lib/python3.9/site-packages/validation.py", line 66, in validate_input + check_constraints(data) + File "/usr/local/lib/python3.9/site-packages/constraints.py", line 19, in check_constraints + raise ValueError("Constraint violation at step 9") +ValueError: Constraint violation at step 9 + +Lorem Ipsum Foobar diff --git a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py index 01645cf1d39..e00af701427 100644 --- a/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py +++ b/tests/appsec/iast/taint_sinks/test_sql_injection_redacted.py @@ -29,7 +29,7 @@ list(get_parametrize(VULN_SQL_INJECTION, ignore_list=_ignore_list)), ) def test_sqli_redaction_suite(evidence_input, sources_expected, vulnerabilities_expected, iast_context_defaults): - with override_global_config(dict(_deduplication_enabled=False)): + with override_global_config(dict(_iast_deduplication_enabled=False)): tainted_object = _taint_pyobject_multiranges( evidence_input["value"], [ diff --git a/tests/appsec/iast/taint_sinks/test_stacktrace_leak.py b/tests/appsec/iast/taint_sinks/test_stacktrace_leak.py new file mode 100644 index 00000000000..45c40f43df7 --- /dev/null +++ b/tests/appsec/iast/taint_sinks/test_stacktrace_leak.py @@ -0,0 +1,39 @@ +import os + +from ddtrace.appsec._iast.constants import VULN_STACKTRACE_LEAK +from ddtrace.appsec._iast.taint_sinks.stacktrace_leak import asm_check_stacktrace_leak +from tests.appsec.iast.taint_sinks.conftest import _get_span_report + + +def _load_html_django_stacktrace(): + return open(os.path.join(os.path.dirname(__file__), "../fixtures/django_debug_page.html")).read() + + +def _load_text_stacktrace(): + return open(os.path.join(os.path.dirname(__file__), "../fixtures/plain_stacktrace.txt")).read() + + +def test_asm_check_stacktrace_leak_html(iast_context_defaults): + asm_check_stacktrace_leak(_load_html_django_stacktrace()) + span_report = _get_span_report() + vulnerabilities = list(span_report.vulnerabilities) + vulnerabilities_types = [vuln.type for vuln in vulnerabilities] + assert len(vulnerabilities) == 1 + assert VULN_STACKTRACE_LEAK in vulnerabilities_types + assert ( + vulnerabilities[0].evidence.value + == 'Module: ".home.foobaruser.sources.minimal-django-example.app.py"\nException: IndexError' + ) + + +def test_asm_check_stacktrace_leak_text(iast_context_defaults): + asm_check_stacktrace_leak(_load_text_stacktrace()) + span_report = _get_span_report() + vulnerabilities = list(span_report.vulnerabilities) + vulnerabilities_types = [vuln.type for vuln in vulnerabilities] + assert len(vulnerabilities) == 1 + assert VULN_STACKTRACE_LEAK in vulnerabilities_types + assert ( + vulnerabilities[0].evidence.value + == 'Module: ".usr.local.lib.python3.9.site-packages.constraints.py"\nException: ValueError' + ) diff --git a/tests/appsec/iast/taint_tracking/conftest.py b/tests/appsec/iast/taint_tracking/conftest.py index b08bb398a27..831506e8137 100644 --- a/tests/appsec/iast/taint_tracking/conftest.py +++ b/tests/appsec/iast/taint_tracking/conftest.py @@ -7,7 +7,7 @@ @pytest.fixture(autouse=True) def iast_create_context(): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, request_sampling=100.0)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False, request_sampling=100.0)): _start_iast_context_and_oce() yield _end_iast_context_and_oce() diff --git a/tests/appsec/iast/test_processor.py b/tests/appsec/iast/test_processor.py index 3deaa60a530..3bb5eaa5015 100644 --- a/tests/appsec/iast/test_processor.py +++ b/tests/appsec/iast/test_processor.py @@ -5,8 +5,8 @@ from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._iast_request_context import get_iast_reporter +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import AUTO_KEEP -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.constants import USER_KEEP from ddtrace.ext import SpanTypes from tests.utils import DummyTracer @@ -61,7 +61,7 @@ def test_appsec_iast_processor_ensure_span_is_manual_keep(iast_context_defaults, result = span.get_tag(IAST.JSON) assert len(json.loads(result)["vulnerabilities"]) == 1 - assert span.get_metric(SAMPLING_PRIORITY_KEY) is USER_KEEP + assert span.get_metric(_SAMPLING_PRIORITY_KEY) is USER_KEEP @pytest.mark.skip_iast_check_logs @@ -74,7 +74,7 @@ def test_appsec_iast_processor_ensure_span_is_sampled(iast_context_defaults, sam with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=sampling_rate, ) ): @@ -87,9 +87,9 @@ def test_appsec_iast_processor_ensure_span_is_sampled(iast_context_defaults, sam result = span.get_tag(IAST.JSON) if sampling_rate == 0.0: assert result is None - assert span.get_metric(SAMPLING_PRIORITY_KEY) is AUTO_KEEP + assert span.get_metric(_SAMPLING_PRIORITY_KEY) is AUTO_KEEP assert span.get_metric(IAST.ENABLED) == 0.0 else: assert len(json.loads(result)["vulnerabilities"]) == 1 - assert span.get_metric(SAMPLING_PRIORITY_KEY) is USER_KEEP + assert span.get_metric(_SAMPLING_PRIORITY_KEY) is USER_KEEP assert span.get_metric(IAST.ENABLED) == 1.0 diff --git a/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py b/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py index 4980259a3a6..2430c872658 100644 --- a/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py +++ b/tests/appsec/iast_aggregated_memcheck/test_aggregated_memleaks.py @@ -5,7 +5,9 @@ @pytest.mark.asyncio async def test_aggregated_leaks(): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): from scripts.iast.leak_functions import iast_leaks result = await iast_leaks(60000, 0.2, 500) == 0 diff --git a/tests/appsec/integrations/django_tests/conftest.py b/tests/appsec/integrations/django_tests/conftest.py index 76ffa4a3763..e24eb07081e 100644 --- a/tests/appsec/integrations/django_tests/conftest.py +++ b/tests/appsec/integrations/django_tests/conftest.py @@ -22,7 +22,7 @@ def pytest_configure(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -55,7 +55,7 @@ def test_spans(tracer): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): diff --git a/tests/appsec/integrations/django_tests/django_app/urls.py b/tests/appsec/integrations/django_tests/django_app/urls.py index be2d142baa2..c9dffbde8d8 100644 --- a/tests/appsec/integrations/django_tests/django_app/urls.py +++ b/tests/appsec/integrations/django_tests/django_app/urls.py @@ -81,4 +81,6 @@ def shutdown(request): handler("appsec/validate_querydict/$", views.validate_querydict, name="validate_querydict"), path("appsec/path-params///", views.path_params_view, name="path-params-view"), path("appsec/checkuser//", views.checkuser_view, name="checkuser"), + path("appsec/stacktrace_leak/", views.stacktrace_leak_view), + path("appsec/stacktrace_leak_500/", views.stacktrace_leak_500_view), ] diff --git a/tests/appsec/integrations/django_tests/django_app/views.py b/tests/appsec/integrations/django_tests/django_app/views.py index ef4fd78b138..74cc239cf34 100644 --- a/tests/appsec/integrations/django_tests/django_app/views.py +++ b/tests/appsec/integrations/django_tests/django_app/views.py @@ -273,3 +273,20 @@ def validate_querydict(request): return HttpResponse( "x=%s, all=%s, keys=%s, urlencode=%s" % (str(res), str(lres), str(keys), qd.urlencode()), status=200 ) + + +def stacktrace_leak_view(request): + from tests.appsec.iast.taint_sinks.test_stacktrace_leak import _load_html_django_stacktrace + + return HttpResponse(_load_html_django_stacktrace()) + + +def stacktrace_leak_500_view(request): + try: + raise Exception("FooBar Exception") + except Exception: + import sys + + from django.views.debug import technical_500_response + + return technical_500_response(request, *sys.exc_info()) diff --git a/tests/appsec/integrations/django_tests/test_django_appsec_iast.py b/tests/appsec/integrations/django_tests/test_django_appsec_iast.py index d2c52337482..8f4768d8a8c 100644 --- a/tests/appsec/integrations/django_tests/test_django_appsec_iast.py +++ b/tests/appsec/integrations/django_tests/test_django_appsec_iast.py @@ -3,6 +3,7 @@ import pytest +from ddtrace.appsec._asm_request_context import start_context from ddtrace.appsec._constants import IAST from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._patch_modules import patch_iast @@ -11,6 +12,8 @@ from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION from ddtrace.appsec._iast.constants import VULN_INSECURE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION +from ddtrace.appsec._iast.constants import VULN_STACKTRACE_LEAK +from ddtrace.ext import SpanTypes from ddtrace.internal.compat import urlencode from tests.appsec.iast.iast_utils import get_line_and_hash from tests.utils import override_env @@ -22,9 +25,7 @@ @pytest.fixture(autouse=True) def iast_context(): - with override_env( - {IAST.ENV: "True", IAST.ENV_REQUEST_SAMPLING: "100", "_DD_APPSEC_DEDUPLICATION_ENABLED": "false"} - ): + with override_env({IAST.ENV: "True", IAST.ENV_REQUEST_SAMPLING: "100", "DD_IAST_DEDUPLICATION_ENABLED": "false"}): yield @@ -84,7 +85,7 @@ def _aux_appsec_get_root_span_with_exception( @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_weak_hash(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): oce.reconfigure() patch_iast({"weak_hash": True}) root_span, _ = _aux_appsec_get_root_span(client, test_spans, tracer, url="/appsec/weak-hash/") @@ -97,7 +98,7 @@ def test_django_weak_hash(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -154,7 +155,7 @@ def test_django_view_with_exception(client, test_spans, tracer, payload, content @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_disabled(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=False, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=False, _iast_deduplication_enabled=False)): oce.reconfigure() root_span, response = _aux_appsec_get_root_span( @@ -176,7 +177,9 @@ def test_django_tainted_user_agent_iast_disabled(client, test_spans, tracer): @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -222,8 +225,10 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter(clie @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter_name_get(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): +def test_django_sqli_http_request_parameter_name_get(client, test_spans, tracer): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -271,8 +276,10 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter_name @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter_name_post(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): +def test_django_sqli_http_request_parameter_name_post(client, test_spans, tracer): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -321,8 +328,8 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_parameter_name @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_user_agent_iast_enabled_sqli_http_request_header_value(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): +def test_django_sqli_http_request_header_value(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -359,7 +366,7 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_header_value(c @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_user_agent_iast_disabled_sqli_http_request_header_value(client, test_spans, tracer): +def test_django_iast_disabled_sqli_http_request_header_value(client, test_spans, tracer): with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, @@ -379,8 +386,8 @@ def test_django_tainted_user_agent_iast_disabled_sqli_http_request_header_value( @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_user_agent_iast_enabled_sqli_http_request_header_name(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): +def test_django_sqli_http_request_header_name(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -417,7 +424,7 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_request_header_name(cl @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_user_agent_iast_disabled_sqli_http_request_header_name(client, test_spans, tracer): +def test_django_iast_disabled_sqli_http_request_header_name(client, test_spans, tracer): with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, @@ -437,7 +444,7 @@ def test_django_tainted_user_agent_iast_disabled_sqli_http_request_header_name(c @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_iast_enabled_full_sqli_http_path_parameter(client, test_spans, tracer): +def test_django_sqli_http_path_parameter(client, test_spans, tracer): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -472,7 +479,7 @@ def test_django_iast_enabled_full_sqli_http_path_parameter(client, test_spans, t @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_iast_disabled_full_sqli_http_path_parameter(client, test_spans, tracer): +def test_django_iast_disabled_sqli_http_path_parameter(client, test_spans, tracer): with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, @@ -490,8 +497,8 @@ def test_django_iast_disabled_full_sqli_http_path_parameter(client, test_spans, @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_name(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): +def test_django_sqli_http_cookies_name(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -530,7 +537,7 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_name(client, t @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_iast_disabled_sqli_http_cookies_name(client, test_spans, tracer): +def test_django_iast_disabled_sqli_http_cookies_name(client, test_spans, tracer): with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, @@ -548,8 +555,8 @@ def test_django_tainted_iast_disabled_sqli_http_cookies_name(client, test_spans, @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_value(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): +def test_django_sqli_http_cookies_value(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -590,7 +597,7 @@ def test_django_tainted_user_agent_iast_enabled_sqli_http_cookies_value(client, @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_iast_disabled_sqli_http_cookies_value(client, test_spans, tracer): +def test_django_iast_disabled_sqli_http_cookies_value(client, test_spans, tracer): with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, @@ -615,8 +622,8 @@ def test_django_tainted_iast_disabled_sqli_http_cookies_value(client, test_spans ) @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_user_agent_iast_enabled_sqli_http_body(client, test_spans, tracer, payload, content_type): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): +def test_django_sqli_http_body(client, test_spans, tracer, payload, content_type): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, test_spans, @@ -692,7 +699,7 @@ def test_django_tainted_http_body_empty(client, test_spans, tracer, payload, con @pytest.mark.django_db() @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_django_tainted_iast_disabled_sqli_http_body(client, test_spans, tracer): +def test_django_iast_disabled_sqli_http_body(client, test_spans, tracer): with override_global_config(dict(_iast_enabled=False)): root_span, response = _aux_appsec_get_root_span( client, @@ -710,7 +717,7 @@ def test_django_tainted_iast_disabled_sqli_http_body(client, test_spans, tracer) @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") -def test_querydict_django_with_iast(client, test_spans, tracer): +def test_django_querydict(client, test_spans, tracer): with override_global_config(dict(_iast_enabled=True)): root_span, response = _aux_appsec_get_root_span( client, @@ -729,7 +736,7 @@ def test_querydict_django_with_iast(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_command_injection(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): oce.reconfigure() patch_iast({"command_injection": True}) from ddtrace.appsec._common_module_patches import patch_common_modules @@ -762,7 +769,7 @@ def test_django_command_injection(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_header_injection(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): oce.reconfigure() patch_iast({"header_injection": True}) root_span, _ = _aux_appsec_get_root_span( @@ -790,7 +797,7 @@ def test_django_header_injection(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_insecure_cookie(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): oce.reconfigure() root_span, _ = _aux_appsec_get_root_span( client, @@ -815,7 +822,7 @@ def test_django_insecure_cookie(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_insecure_cookie_secure(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): oce.reconfigure() root_span, _ = _aux_appsec_get_root_span( client, @@ -831,7 +838,7 @@ def test_django_insecure_cookie_secure(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_insecure_cookie_empty_cookie(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): oce.reconfigure() root_span, _ = _aux_appsec_get_root_span( client, @@ -847,7 +854,7 @@ def test_django_insecure_cookie_empty_cookie(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_insecure_cookie_2_insecure_1_secure(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): oce.reconfigure() root_span, _ = _aux_appsec_get_root_span( client, @@ -865,7 +872,7 @@ def test_django_insecure_cookie_2_insecure_1_secure(client, test_spans, tracer): @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") def test_django_insecure_cookie_special_characters(client, test_spans, tracer): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with override_global_config(dict(_iast_enabled=True, _iast_deduplication_enabled=False)): oce.reconfigure() root_span, _ = _aux_appsec_get_root_span( client, @@ -886,3 +893,69 @@ def test_django_insecure_cookie_special_characters(client, test_spans, tracer): assert "line" not in vulnerability["location"].keys() assert vulnerability["location"]["spanId"] assert vulnerability["hash"] + + +@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") +def test_django_stacktrace_leak(client, test_spans, tracer): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + oce.reconfigure() + root_span, _ = _aux_appsec_get_root_span( + client, + test_spans, + tracer, + url="/appsec/stacktrace_leak/", + ) + + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK + assert vulnerability["evidence"] == { + "valueParts": [ + {"value": 'Module: ".home.foobaruser.sources.minimal-django-example.app.py"\nException: IndexError'} + ] + } + assert vulnerability["hash"] + + +@pytest.fixture +def debug_mode(): + from django.conf import settings + + original_debug = settings.DEBUG + settings.DEBUG = True + yield + settings.DEBUG = original_debug + + +@pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") +def test_django_stacktrace_from_technical_500_response(client, test_spans, tracer, debug_mode): + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False)): + with tracer.trace("test", span_type=SpanTypes.WEB, service="test") as span: + start_context(span) + oce.reconfigure() + root_span, response = _aux_appsec_get_root_span( + client, + test_spans, + tracer, + url="/appsec/stacktrace_leak_500/", + content_type="text/html", + ) + + assert response.status_code == 500, "Expected a 500 status code" + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK + assert vulnerability["evidence"] == { + "valueParts": [ + {"value": "Module: tests.appsec.integrations.django_tests.django_app.views\nException: Exception"} + ] + } + assert vulnerability["hash"] diff --git a/tests/appsec/integrations/flask_tests/test_flask_remoteconfig.py b/tests/appsec/integrations/flask_tests/test_flask_remoteconfig.py index 4ddf1ee8f64..3c9b934416a 100644 --- a/tests/appsec/integrations/flask_tests/test_flask_remoteconfig.py +++ b/tests/appsec/integrations/flask_tests/test_flask_remoteconfig.py @@ -187,7 +187,6 @@ def _request_403(client, debug_mode=False, max_retries=40, sleep_time=1): raise AssertionError("request_403 failed, max_retries=%d, sleep_time=%f" % (max_retries, sleep_time)) -@flaky(until=1706677200, reason="TODO(avara1986): We need to migrate testagent to gitlab") @pytest.mark.skipif(sys.version_info >= (3, 11), reason="Gunicorn is only supported up to 3.10") def test_load_testing_appsec_ip_blocking_gunicorn_rc_disabled(): token = "test_load_testing_appsec_ip_blocking_gunicorn_rc_disabled_{}".format(str(uuid.uuid4())) @@ -203,7 +202,6 @@ def test_load_testing_appsec_ip_blocking_gunicorn_rc_disabled(): _unblock_ip(token) -@flaky(until=1706677200, reason="TODO(avara1986): We need to migrate testagent to gitlab") @pytest.mark.skipif(sys.version_info >= (3, 11), reason="Gunicorn is only supported up to 3.10") def test_load_testing_appsec_ip_blocking_gunicorn_block(): token = "test_load_testing_appsec_ip_blocking_gunicorn_block_{}".format(str(uuid.uuid4())) @@ -221,7 +219,6 @@ def test_load_testing_appsec_ip_blocking_gunicorn_block(): _request_200(gunicorn_client) -@flaky(until=1706677200, reason="TODO(avara1986): We need to migrate testagent to gitlab") @pytest.mark.skipif(list(sys.version_info[:2]) != [3, 10], reason="Run this tests in python 3.10") def test_load_testing_appsec_ip_blocking_gunicorn_block_and_kill_child_worker(): token = "test_load_testing_appsec_ip_blocking_gunicorn_block_and_kill_child_worker_{}".format(str(uuid.uuid4())) diff --git a/tests/appsec/integrations/flask_tests/test_iast_flask_telemetry.py b/tests/appsec/integrations/flask_tests/test_iast_flask_telemetry.py index 99e00112e6f..0d5899d0a7f 100644 --- a/tests/appsec/integrations/flask_tests/test_iast_flask_telemetry.py +++ b/tests/appsec/integrations/flask_tests/test_iast_flask_telemetry.py @@ -25,15 +25,18 @@ def test_flask_instrumented_metrics(telemetry_writer): metrics_result = telemetry_writer._namespace._metrics_data metrics_source_tags_result = [metric._tags[0][1] for metric in metrics_result["generate-metrics"]["iast"].values()] - assert len(metrics_source_tags_result) == 8 + assert len(metrics_source_tags_result) == 11 assert VULN_PATH_TRAVERSAL in metrics_source_tags_result assert origin_to_str(OriginType.HEADER_NAME) in metrics_source_tags_result assert origin_to_str(OriginType.HEADER) in metrics_source_tags_result + assert origin_to_str(OriginType.PARAMETER_NAME) in metrics_source_tags_result assert origin_to_str(OriginType.PARAMETER) in metrics_source_tags_result assert origin_to_str(OriginType.PATH) in metrics_source_tags_result assert origin_to_str(OriginType.PATH_PARAMETER) in metrics_source_tags_result assert origin_to_str(OriginType.QUERY) in metrics_source_tags_result assert origin_to_str(OriginType.BODY) in metrics_source_tags_result + assert origin_to_str(OriginType.COOKIE_NAME) in metrics_source_tags_result + assert origin_to_str(OriginType.COOKIE) in metrics_source_tags_result def test_flask_instrumented_metrics_iast_disabled(telemetry_writer): diff --git a/tests/appsec/integrations/flask_tests/test_iast_psycopg2.py b/tests/appsec/integrations/flask_tests/test_iast_psycopg2.py index d6d25f7ffc2..1522e1136ca 100644 --- a/tests/appsec/integrations/flask_tests/test_iast_psycopg2.py +++ b/tests/appsec/integrations/flask_tests/test_iast_psycopg2.py @@ -11,7 +11,9 @@ @pytest.fixture(autouse=True) def iast_create_context(): - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): _start_iast_context_and_oce() yield _end_iast_context_and_oce() diff --git a/tests/appsec/suitespec.yml b/tests/appsec/suitespec.yml index f075ba2da4a..cde49287a2a 100644 --- a/tests/appsec/suitespec.yml +++ b/tests/appsec/suitespec.yml @@ -73,12 +73,18 @@ suites: - '@remoteconfig' retry: 2 runner: hatch + iast_aggregated_leak_testing: + parallelism: 3 + paths: + - '@appsec_iast' + - tests/appsec/iast_aggregated_memcheck/* + runner: hatch + timeout: 50m appsec_iast_packages: parallelism: 4 paths: - '@appsec_iast' - tests/appsec/iast_packages/* - retry: 2 runner: hatch timeout: 50m appsec_integrations_pygoat: @@ -107,7 +113,9 @@ suites: - tests/appsec/integrations/flask_tests/* retry: 2 runner: hatch - timeout: 30m + services: + - testagent + timeout: 40m appsec_integrations_django: parallelism: 6 paths: @@ -120,6 +128,8 @@ suites: - tests/appsec/integrations/django_tests/* retry: 2 runner: hatch + services: + - testagent timeout: 30m appsec_threats_django: parallelism: 12 diff --git a/tests/conftest.py b/tests/conftest.py index 5ee2933f187..abd0f0dc25e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -131,7 +131,8 @@ def auto_enable_crashtracking(): def enable_crashtracking(auto_enable_crashtracking): if auto_enable_crashtracking: crashtracking.start() - assert crashtracking.is_started() + # JJJ + # assert crashtracking.is_started() yield diff --git a/tests/contrib/aiohttp/test_middleware.py b/tests/contrib/aiohttp/test_middleware.py index 30d40654314..097548d7b2b 100644 --- a/tests/contrib/aiohttp/test_middleware.py +++ b/tests/contrib/aiohttp/test_middleware.py @@ -4,8 +4,8 @@ import pytest from ddtrace._trace.sampler import RateSampler +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import ERROR_MSG -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.constants import USER_KEEP from ddtrace.contrib.internal.aiohttp.middlewares import CONFIG_KEY from ddtrace.contrib.internal.aiohttp.middlewares import trace_app @@ -381,7 +381,7 @@ async def test_distributed_tracing(app_tracer, aiohttp_client): # with the right trace_id and parent_id assert span.trace_id == 100 assert span.parent_id == 42 - assert span.get_metric(SAMPLING_PRIORITY_KEY) is USER_KEEP + assert span.get_metric(_SAMPLING_PRIORITY_KEY) is USER_KEEP @flaky(1735812000) @@ -408,7 +408,7 @@ async def test_distributed_tracing_with_sampling_true(app_tracer, aiohttp_client # with the right trace_id and parent_id assert 100 == span.trace_id assert 42 == span.parent_id - assert 1 == span.get_metric(SAMPLING_PRIORITY_KEY) + assert 1 == span.get_metric(_SAMPLING_PRIORITY_KEY) @flaky(1735812000) @@ -435,7 +435,7 @@ async def test_distributed_tracing_with_sampling_false(app_tracer, aiohttp_clien # with the right trace_id and parent_id assert 100 == span.trace_id assert 42 == span.parent_id - assert 0 == span.get_metric(SAMPLING_PRIORITY_KEY) + assert 0 == span.get_metric(_SAMPLING_PRIORITY_KEY) async def test_distributed_tracing_disabled(app_tracer, aiohttp_client): @@ -487,11 +487,11 @@ async def test_distributed_tracing_sub_span(app_tracer, aiohttp_client): # with the right trace_id and parent_id assert 100 == span.trace_id assert 42 == span.parent_id - assert 0 == span.get_metric(SAMPLING_PRIORITY_KEY) + assert 0 == span.get_metric(_SAMPLING_PRIORITY_KEY) # check parenting is OK with custom sub-span created within server code assert 100 == sub_span.trace_id assert span.span_id == sub_span.parent_id - assert sub_span.get_metric(SAMPLING_PRIORITY_KEY) is None + assert sub_span.get_metric(_SAMPLING_PRIORITY_KEY) is None def _assert_200_parenting(client, traces): diff --git a/tests/contrib/asyncpg/test_asyncpg.py b/tests/contrib/asyncpg/test_asyncpg.py index 032a0f91731..c60bfae1456 100644 --- a/tests/contrib/asyncpg/test_asyncpg.py +++ b/tests/contrib/asyncpg/test_asyncpg.py @@ -8,7 +8,7 @@ from ddtrace import tracer from ddtrace.contrib.internal.asyncpg.patch import patch from ddtrace.contrib.internal.asyncpg.patch import unpatch -from ddtrace.contrib.trace_utils import iswrapped +from ddtrace.contrib.internal.trace_utils import iswrapped from ddtrace.trace import Pin from tests.contrib.asyncio.utils import AsyncioTestCase from tests.contrib.asyncio.utils import mark_asyncio diff --git a/tests/contrib/cherrypy/test_middleware.py b/tests/contrib/cherrypy/test_middleware.py index 000f15610a0..9bc4a600136 100644 --- a/tests/contrib/cherrypy/test_middleware.py +++ b/tests/contrib/cherrypy/test_middleware.py @@ -11,10 +11,10 @@ import ddtrace from ddtrace import config +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.contrib.internal.cherrypy.middleware import TraceMiddleware from ddtrace.ext import http from tests.contrib.patch import emit_integration_and_version_to_test_agent @@ -286,7 +286,7 @@ def test_propagation(self): # ensure the propagation worked well assert s.trace_id == 1234 assert s.parent_id == 4567 - assert s.get_metric(SAMPLING_PRIORITY_KEY) == 2 + assert s.get_metric(_SAMPLING_PRIORITY_KEY) == 2 def test_disabled_distributed_tracing_config(self): previous_distributed_tracing = config.cherrypy["distributed_tracing"] @@ -313,7 +313,7 @@ def test_disabled_distributed_tracing_config(self): # ensure the propagation worked well assert s.trace_id != 1234 assert s.parent_id != 4567 - assert s.get_metric(SAMPLING_PRIORITY_KEY) != 2 + assert s.get_metric(_SAMPLING_PRIORITY_KEY) != 2 config.cherrypy["distributed_tracing"] = previous_distributed_tracing @@ -342,7 +342,7 @@ def test_disabled_distributed_tracing_middleware(self): # ensure the propagation worked well assert s.trace_id != 1234 assert s.parent_id != 4567 - assert s.get_metric(SAMPLING_PRIORITY_KEY) != 2 + assert s.get_metric(_SAMPLING_PRIORITY_KEY) != 2 cherrypy.tools.tracer.use_distributed_tracing = previous_distributed_tracing diff --git a/tests/contrib/dbapi/test_dbapi_appsec.py b/tests/contrib/dbapi/test_dbapi_appsec.py index d43d9c37e3c..085166df575 100644 --- a/tests/contrib/dbapi/test_dbapi_appsec.py +++ b/tests/contrib/dbapi/test_dbapi_appsec.py @@ -19,7 +19,7 @@ def setUp(self): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -29,7 +29,7 @@ def setUp(self): def tearDown(self): with override_global_config( - dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0) + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) ): _end_iast_context_and_oce() diff --git a/tests/contrib/django/test_django.py b/tests/contrib/django/test_django.py index 7b8a0e18ef7..79baceb1652 100644 --- a/tests/contrib/django/test_django.py +++ b/tests/contrib/django/test_django.py @@ -18,10 +18,10 @@ import wrapt from ddtrace import config +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.constants import USER_KEEP from ddtrace.contrib import trace_utils from ddtrace.contrib.internal.django.patch import instrument_view @@ -1729,7 +1729,7 @@ def test_django_request_distributed(client, test_spans): trace_id=12345, parent_id=78910, metrics={ - SAMPLING_PRIORITY_KEY: USER_KEEP, + _SAMPLING_PRIORITY_KEY: USER_KEEP, }, ) assert root.get_tag("span.kind") == "server" diff --git a/tests/contrib/django/views.py b/tests/contrib/django/views.py index b908fa2eee0..f1989c374d7 100644 --- a/tests/contrib/django/views.py +++ b/tests/contrib/django/views.py @@ -16,7 +16,7 @@ from django.views.generic import View from ddtrace import tracer -from ddtrace.contrib.trace_utils import set_user +from ddtrace.contrib.internal.trace_utils import set_user class UserList(ListView): diff --git a/tests/contrib/fastapi/test_fastapi_appsec_iast.py b/tests/contrib/fastapi/test_fastapi_appsec_iast.py index 91963f21297..23174d81abf 100644 --- a/tests/contrib/fastapi/test_fastapi_appsec_iast.py +++ b/tests/contrib/fastapi/test_fastapi_appsec_iast.py @@ -19,14 +19,18 @@ from ddtrace.appsec._iast import oce from ddtrace.appsec._iast._handlers import _on_iast_fastapi_patch from ddtrace.appsec._iast._patch_modules import patch_iast +from ddtrace.appsec._iast._taint_tracking import origin_to_str +from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges from ddtrace.appsec._iast.constants import VULN_HEADER_INJECTION from ddtrace.appsec._iast.constants import VULN_INSECURE_COOKIE from ddtrace.appsec._iast.constants import VULN_NO_HTTPONLY_COOKIE from ddtrace.appsec._iast.constants import VULN_NO_SAMESITE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION +from ddtrace.appsec._iast.constants import VULN_STACKTRACE_LEAK from ddtrace.contrib.internal.fastapi.patch import patch as patch_fastapi from ddtrace.contrib.internal.sqlite3.patch import patch as patch_sqlite_sqli from tests.appsec.iast.iast_utils import get_line_and_hash +from tests.appsec.iast.taint_sinks.test_stacktrace_leak import _load_text_stacktrace from tests.utils import override_env from tests.utils import override_global_config @@ -34,8 +38,6 @@ TEST_FILE_PATH = "tests/contrib/fastapi/test_fastapi_appsec_iast.py" fastapi_version = tuple([int(v) for v in _fastapi_version.split(".")]) -if sys.version_info > (3, 12): - pytest.skip(reason="IAST only supports Py3.12 and older", allow_module_level=True) def _aux_appsec_prepare_tracer(tracer): @@ -78,9 +80,6 @@ def check_native_code_exception_in_each_fastapi_test(request, caplog, telemetry_ def test_query_param_source(fastapi_application, client, tracer, test_spans): @fastapi_application.get("/index.html") async def test_route(request: Request): - from ddtrace.appsec._iast._taint_tracking import origin_to_str - from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges - query_params = request.query_params.get("iast_queryparam") ranges_result = get_tainted_ranges(query_params) @@ -110,12 +109,82 @@ async def test_route(request: Request): assert result["ranges_origin"] == "http.request.parameter" -def test_header_value_source(fastapi_application, client, tracer, test_spans): +def test_query_param_name_source_get(fastapi_application, client, tracer, test_spans): @fastapi_application.get("/index.html") async def test_route(request: Request): - from ddtrace.appsec._iast._taint_tracking import origin_to_str - from ddtrace.appsec._iast._taint_tracking._taint_objects import get_tainted_ranges + query_params = [k for k in request.query_params.keys() if k == "iast_queryparam"][0] + ranges_result = get_tainted_ranges(query_params) + + return JSONResponse( + { + "result": query_params, + "is_tainted": len(ranges_result), + "ranges_start": ranges_result[0].start, + "ranges_length": ranges_result[0].length, + "ranges_origin": origin_to_str(ranges_result[0].source.origin), + "ranges_origin_name": ranges_result[0].source.name, + "ranges_origin_value": ranges_result[0].source.value, + } + ) + + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): + # disable callback + _aux_appsec_prepare_tracer(tracer) + resp = client.get( + "/index.html?iast_queryparam=test1234", + headers={"Content-Type": "application/json"}, + ) + assert resp.status_code == 200 + result = json.loads(get_response_body(resp)) + assert result["result"] == "iast_queryparam" + assert result["is_tainted"] == 1 + assert result["ranges_start"] == 0 + assert result["ranges_length"] == 15 + assert result["ranges_origin"] == "http.request.parameter.name" + assert result["ranges_origin_name"] == "iast_queryparam" + assert result["ranges_origin_value"] == "iast_queryparam" + + +def test_query_param_name_source_post(fastapi_application, client, tracer, test_spans): + @fastapi_application.post("/index.html") + async def test_route(request: Request): + form_data = await request.form() + query_params = [k for k in form_data.keys() if k == "iast_queryparam"][0] + ranges_result = get_tainted_ranges(query_params) + + return JSONResponse( + { + "result": query_params, + "is_tainted": len(ranges_result), + "ranges_start": ranges_result[0].start, + "ranges_length": ranges_result[0].length, + "ranges_origin": origin_to_str(ranges_result[0].source.origin), + "ranges_origin_name": ranges_result[0].source.name, + "ranges_origin_value": ranges_result[0].source.value, + } + ) + + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): + # disable callback + _aux_appsec_prepare_tracer(tracer) + resp = client.post( + "/index.html", + data={"iast_queryparam": "test1234"}, + ) + assert resp.status_code == 200 + result = json.loads(get_response_body(resp)) + assert result["result"] == "iast_queryparam" + assert result["is_tainted"] == 1 + assert result["ranges_start"] == 0 + assert result["ranges_length"] == 15 + assert result["ranges_origin"] == "http.request.parameter.name" + assert result["ranges_origin_name"] == "iast_queryparam" + assert result["ranges_origin_value"] == "iast_queryparam" + +def test_header_value_source(fastapi_application, client, tracer, test_spans): + @fastapi_application.get("/index.html") + async def test_route(request: Request): query_params = request.headers.get("iast_header") ranges_result = get_tainted_ranges(query_params) @@ -145,6 +214,42 @@ async def test_route(request: Request): assert result["ranges_origin"] == "http.request.header" +def test_header_name_source(fastapi_application, client, tracer, test_spans): + @fastapi_application.get("/index.html") + async def test_route(request: Request): + query_params = [k for k in request.headers.keys() if k == "iast_header"][0] + ranges_result = get_tainted_ranges(query_params) + + return JSONResponse( + { + "result": query_params, + "is_tainted": len(ranges_result), + "ranges_start": ranges_result[0].start, + "ranges_length": ranges_result[0].length, + "ranges_origin": origin_to_str(ranges_result[0].source.origin), + "ranges_origin_name": ranges_result[0].source.name, + "ranges_origin_value": ranges_result[0].source.value, + } + ) + + with override_global_config(dict(_iast_enabled=True, _iast_request_sampling=100.0)): + # disable callback + _aux_appsec_prepare_tracer(tracer) + resp = client.get( + "/index.html", + headers={"iast_header": "test1234"}, + ) + assert resp.status_code == 200 + result = json.loads(get_response_body(resp)) + assert result["result"] == "iast_header" + assert result["is_tainted"] == 1 + assert result["ranges_start"] == 0 + assert result["ranges_length"] == 11 + assert result["ranges_origin"] == "http.request.header.name" + assert result["ranges_origin_name"] == "iast_header" + assert result["ranges_origin_value"] == "iast_header" + + @pytest.mark.skipif(sys.version_info < (3, 9), reason="typing.Annotated was introduced on 3.9") @pytest.mark.skipif(fastapi_version < (0, 95, 0), reason="Header annotation doesn't work on fastapi 94 or lower") def test_header_value_source_typing_param(fastapi_application, client, tracer, test_spans): @@ -543,7 +648,9 @@ async def test_route(param_str): # label test_fastapi_sqli_path_parameter cur.execute(add_aspect("SELECT 1 FROM ", param_str)) - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): # disable callback _aux_appsec_prepare_tracer(tracer) resp = client.get( @@ -599,7 +706,9 @@ def insecure_cookie(request: Request): return response - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): _aux_appsec_prepare_tracer(tracer) resp = client.get( "/insecure_cookie/?iast_queryparam=insecure", @@ -640,7 +749,9 @@ def insecure_cookie(request: Request): return response - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): _aux_appsec_prepare_tracer(tracer) resp = client.get( "/insecure_cookie/?iast_queryparam=insecure", @@ -675,7 +786,9 @@ def insecure_cookie(request: Request): return response - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): _aux_appsec_prepare_tracer(tracer) resp = client.get( "/insecure_cookie/?iast_queryparam=insecure", @@ -751,7 +864,9 @@ def insecure_cookie(request: Request): return response - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): _aux_appsec_prepare_tracer(tracer) resp = client.get( "/insecure_cookie/?iast_queryparam=insecure", @@ -786,7 +901,9 @@ async def header_injection(request: Request): return result_response - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): _aux_appsec_prepare_tracer(tracer) patch_iast({"header_injection": True}) resp = client.get( @@ -825,7 +942,9 @@ async def header_injection_inline_response(request: Request): headers={"Header-Injection": tainted_string, "Vary": tainted_string, "Foo": "bar"}, ) - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=100.0) + ): _aux_appsec_prepare_tracer(tracer) patch_iast({"header_injection": True}) resp = client.get( @@ -843,3 +962,28 @@ async def header_injection_inline_response(request: Request): assert len(loaded["vulnerabilities"]) == 1 vulnerability = loaded["vulnerabilities"][0] assert vulnerability["type"] == VULN_HEADER_INJECTION + + +def test_fastapi_stacktrace_leak(fastapi_application, client, tracer, test_spans): + @fastapi_application.get("/stacktrace_leak/", response_class=PlainTextResponse) + async def stacktrace_leak_inline_response(request: Request): + return PlainTextResponse( + content=_load_text_stacktrace(), + ) + + with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=100.0)): + _aux_appsec_prepare_tracer(tracer) + resp = client.get( + "/stacktrace_leak/", + ) + assert resp.status_code == 200 + + span = test_spans.pop_traces()[0][0] + assert span.get_metric(IAST.ENABLED) == 1.0 + + iast_tag = span.get_tag(IAST.JSON) + assert iast_tag is not None + loaded = json.loads(iast_tag) + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK diff --git a/tests/contrib/flask/app.py b/tests/contrib/flask/app.py index c19b34c4216..d76ec8fb8b1 100644 --- a/tests/contrib/flask/app.py +++ b/tests/contrib/flask/app.py @@ -8,7 +8,7 @@ from ddtrace import tracer from ddtrace.appsec._trace_utils import block_request_if_user_blocked -from ddtrace.contrib.trace_utils import set_user +from ddtrace.contrib.internal.trace_utils import set_user from tests.webclient import PingFilter diff --git a/tests/contrib/flask/test_flask_appsec_iast.py b/tests/contrib/flask/test_flask_appsec_iast.py index bedf2b58bdc..ceeb7ecadc8 100644 --- a/tests/contrib/flask/test_flask_appsec_iast.py +++ b/tests/contrib/flask/test_flask_appsec_iast.py @@ -1,5 +1,6 @@ import json import sys +import traceback from flask import request from importlib_metadata import version @@ -15,6 +16,7 @@ from ddtrace.appsec._iast.constants import VULN_NO_HTTPONLY_COOKIE from ddtrace.appsec._iast.constants import VULN_NO_SAMESITE_COOKIE from ddtrace.appsec._iast.constants import VULN_SQL_INJECTION +from ddtrace.appsec._iast.constants import VULN_STACKTRACE_LEAK from ddtrace.appsec._iast.taint_sinks.header_injection import patch as patch_header_injection from ddtrace.contrib.internal.sqlite3.patch import patch as patch_sqlite_sqli from tests.appsec.iast.iast_utils import get_line_and_hash @@ -39,7 +41,7 @@ def setUp(self): with override_env({"_DD_IAST_USE_ROOT_SPAN": "false"}), override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -71,7 +73,7 @@ def sqli_1(param_str): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -123,7 +125,7 @@ def sqli_2(param_str): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.post( @@ -304,7 +306,7 @@ def sqli_5(param_str, param_int): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -337,7 +339,9 @@ def sqli_6(param_str): class MockSpan: _trace_id_64bits = 17577308072598193742 - with override_global_config(dict(_iast_enabled=True, _deduplication_enabled=False, _iast_request_sampling=0.0)): + with override_global_config( + dict(_iast_enabled=True, _iast_deduplication_enabled=False, _iast_request_sampling=0.0) + ): oce.reconfigure() _iast_start_request(MockSpan()) resp = self.client.post("/sqli/hello/?select%20from%20table", data={"name": "test"}) @@ -367,7 +371,7 @@ def sqli_7(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -434,7 +438,7 @@ def sqli_8(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): if tuple(map(int, werkzeug_version.split("."))) >= (2, 3): @@ -495,7 +499,7 @@ def sqli_9(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.get("/sqli/parameter/?table=sqlite_master") @@ -550,7 +554,7 @@ def sqli_13(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -608,7 +612,7 @@ def sqli_14(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -673,7 +677,7 @@ def sqli_10(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -1078,7 +1082,7 @@ def sqli_10(): _iast_enabled=True, _asm_enabled=True, _api_security_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -1171,7 +1175,7 @@ def header_injection(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.post("/header_injection/", data={"name": "test"}) @@ -1208,7 +1212,7 @@ def header_injection(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.post("/header_injection/", data={"name": "test"}) @@ -1237,7 +1241,7 @@ def header_injection(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.post("/header_injection/", data={"name": "test"}) @@ -1266,7 +1270,7 @@ def insecure_cookie(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.post("/insecure_cookie/", data={"name": "test"}) @@ -1304,7 +1308,7 @@ def insecure_cookie_empty(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.post("/insecure_cookie_empty/", data={"name": "test"}) @@ -1334,7 +1338,7 @@ def no_http_only_cookie(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.post("/no_http_only_cookie/", data={"name": "test"}) @@ -1372,7 +1376,7 @@ def no_http_only_cookie_empty(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -1403,7 +1407,7 @@ def no_samesite_cookie(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.post("/no_samesite_cookie/", data={"name": "test"}) @@ -1441,7 +1445,7 @@ def no_samesite_cookie_empty(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, ) ): resp = self.client.post("/no_samesite_cookie_empty/", data={"name": "test"}) @@ -1469,7 +1473,7 @@ def cookie_secure(): with override_global_config( dict( _iast_enabled=True, - _deduplication_enabled=False, + _iast_deduplication_enabled=False, _iast_request_sampling=100.0, ) ): @@ -1482,6 +1486,105 @@ def cookie_secure(): loaded = root_span.get_tag(IAST.JSON) assert loaded is None + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_stacktrace_leak(self): + @self.app.route("/stacktrace_leak/") + def stacktrace_leak(): + from flask import Response + + return Response( + """Traceback (most recent call last): + File "/usr/local/lib/python3.9/site-packages/some_module.py", line 42, in process_data + result = complex_calculation(data) + File "/usr/local/lib/python3.9/site-packages/another_module.py", line 158, in complex_calculation + intermediate = perform_subtask(data_slice) + File "/usr/local/lib/python3.9/site-packages/subtask_module.py", line 27, in perform_subtask + processed = handle_special_case(data_slice) + File "/usr/local/lib/python3.9/site-packages/special_cases.py", line 84, in handle_special_case + return apply_algorithm(data_slice, params) + File "/usr/local/lib/python3.9/site-packages/algorithm_module.py", line 112, in apply_algorithm + step_result = execute_step(data, params) + File "/usr/local/lib/python3.9/site-packages/step_execution.py", line 55, in execute_step + temp = pre_process(data) + File "/usr/local/lib/python3.9/site-packages/pre_processing.py", line 33, in pre_process + validated_data = validate_input(data) + File "/usr/local/lib/python3.9/site-packages/validation.py", line 66, in validate_input + check_constraints(data) + File "/usr/local/lib/python3.9/site-packages/constraints.py", line 19, in check_constraints + raise ValueError("Constraint violation at step 9") +ValueError: Constraint violation at step 9 + +Lorem Ipsum Foobar +""" + ) + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.get("/stacktrace_leak/") + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK + assert vulnerability["evidence"] == { + "valueParts": [ + {"value": 'Module: ".usr.local.lib.python3.9.site-packages.constraints.py"\nException: ValueError'} + ] + } + + @pytest.mark.skipif(not python_supported_by_iast(), reason="Python version not supported by IAST") + def test_flask_stacktrace_leak_from_debug_page(self): + try: + from werkzeug.debug.tbtools import DebugTraceback + except ImportError: + return # this version of werkzeug does not have the DebugTraceback + + @self.app.route("/stacktrace_leak_debug/") + def stacktrace_leak(): + from flask import Response + + try: + raise ValueError() + except ValueError as exc: + dt = DebugTraceback( + exc, + traceback.TracebackException.from_exception(exc), + ) + + # Render the debugger HTML + html = dt.render_debugger_html(evalex=False, secret="test_secret", evalex_trusted=False) + return Response(html, mimetype="text/html") + + with override_global_config( + dict( + _iast_enabled=True, + _deduplication_enabled=False, + ) + ): + resp = self.client.get("/stacktrace_leak_debug/") + assert resp.status_code == 200 + + root_span = self.pop_spans()[0] + assert root_span.get_metric(IAST.ENABLED) == 1.0 + + loaded = json.loads(root_span.get_tag(IAST.JSON)) + assert loaded["sources"] == [] + assert len(loaded["vulnerabilities"]) == 1 + vulnerability = loaded["vulnerabilities"][0] + assert vulnerability["type"] == VULN_STACKTRACE_LEAK + assert "valueParts" in vulnerability["evidence"] + assert "tests.contrib.flask.test_flask_appsec_iast" in vulnerability["evidence"]["valueParts"][0]["value"] + assert "Exception: ValueError" in vulnerability["evidence"]["valueParts"][0]["value"] + class FlaskAppSecIASTDisabledTestCase(BaseFlaskTestCase): @pytest.fixture(autouse=True) diff --git a/tests/contrib/gevent/test_tracer.py b/tests/contrib/gevent/test_tracer.py index c34f06d41d6..d804a4e9c59 100644 --- a/tests/contrib/gevent/test_tracer.py +++ b/tests/contrib/gevent/test_tracer.py @@ -6,7 +6,7 @@ import ddtrace from ddtrace.constants import ERROR_MSG -from ddtrace.constants import SAMPLING_PRIORITY_KEY +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import USER_KEEP from ddtrace._trace.context import Context from ddtrace.contrib.internal.gevent.patch import patch @@ -143,9 +143,9 @@ def green_2(): worker_1 = spans[1] worker_2 = spans[2] # check sampling priority - assert parent_span.get_metric(SAMPLING_PRIORITY_KEY) == USER_KEEP - assert worker_1.get_metric(SAMPLING_PRIORITY_KEY) is None - assert worker_2.get_metric(SAMPLING_PRIORITY_KEY) is None + assert parent_span.get_metric(_SAMPLING_PRIORITY_KEY) == USER_KEEP + assert worker_1.get_metric(_SAMPLING_PRIORITY_KEY) is None + assert worker_2.get_metric(_SAMPLING_PRIORITY_KEY) is None def test_trace_spawn_multiple_greenlets_multiple_traces(self): # multiple greenlets must be part of the same trace diff --git a/tests/contrib/langgraph/conftest.py b/tests/contrib/langgraph/conftest.py index 19d01c018aa..a521ff367fb 100644 --- a/tests/contrib/langgraph/conftest.py +++ b/tests/contrib/langgraph/conftest.py @@ -11,11 +11,15 @@ from ddtrace.contrib.internal.langgraph.patch import patch from ddtrace.contrib.internal.langgraph.patch import unpatch from ddtrace.llmobs import LLMObs as llmobs_service +from ddtrace.llmobs._constants import AGENTLESS_BASE_URL from ddtrace.llmobs._writer import LLMObsSpanWriter from tests.utils import DummyTracer from tests.utils import override_global_config +DATADOG_SITE = "datad0g.com" + + @pytest.fixture def mock_tracer(): yield DummyTracer() @@ -48,7 +52,8 @@ def enqueue(self, event): @pytest.fixture def llmobs_span_writer(): - yield TestLLMObsSpanWriter(interval=1.0, timeout=1.0) + agentless_url = "{}.{}".format(AGENTLESS_BASE_URL, DATADOG_SITE) + yield TestLLMObsSpanWriter(is_agentless=True, agentless_url=agentless_url, interval=1.0, timeout=1.0) @pytest.fixture diff --git a/tests/contrib/langgraph/test_langgraph.py b/tests/contrib/langgraph/test_langgraph.py index 1894b8b9420..1ea7f8714ab 100644 --- a/tests/contrib/langgraph/test_langgraph.py +++ b/tests/contrib/langgraph/test_langgraph.py @@ -1,134 +1,159 @@ +from collections import Counter + + +def assert_has_spans(spans, expected): + resources = [span.resource for span in spans] + assert len(resources) == len(expected) + assert Counter(resources) == Counter(expected) + + def assert_simple_graph_spans(spans): - assert len(spans) == 3 - assert spans[0].resource == "langgraph.graph.state.CompiledStateGraph.LangGraph" - assert spans[1].resource == "langgraph.utils.runnable.RunnableSeq.a" - assert spans[2].resource == "langgraph.utils.runnable.RunnableSeq.b" + assert_has_spans( + spans, + expected=[ + "langgraph.graph.state.CompiledStateGraph.LangGraph", + "langgraph.utils.runnable.RunnableSeq.a", + "langgraph.utils.runnable.RunnableSeq.b", + ], + ) def assert_conditional_graph_spans(spans, which): - assert len(spans) == 3 - assert spans[0].resource == "langgraph.graph.state.CompiledStateGraph.LangGraph" - assert spans[1].resource == "langgraph.utils.runnable.RunnableSeq.a" - assert spans[2].resource == f"langgraph.utils.runnable.RunnableSeq.{which}" + assert_has_spans( + spans, + expected=[ + "langgraph.graph.state.CompiledStateGraph.LangGraph", + "langgraph.utils.runnable.RunnableSeq.a", + f"langgraph.utils.runnable.RunnableSeq.{which}", + ], + ) def assert_subgraph_spans(spans): - assert len(spans) == 6 - assert spans[0].resource == "langgraph.graph.state.CompiledStateGraph.LangGraph" - assert spans[1].resource == "langgraph.utils.runnable.RunnableSeq.a" - assert spans[2].resource == "langgraph.graph.state.CompiledStateGraph.LangGraph" - assert spans[3].resource == "langgraph.utils.runnable.RunnableSeq.b1" - assert spans[4].resource == "langgraph.utils.runnable.RunnableSeq.b2" - assert spans[5].resource == "langgraph.utils.runnable.RunnableSeq.b3" + assert_has_spans( + spans, + expected=[ + "langgraph.graph.state.CompiledStateGraph.LangGraph", + "langgraph.utils.runnable.RunnableSeq.a", + "langgraph.graph.state.CompiledStateGraph.LangGraph", + "langgraph.utils.runnable.RunnableSeq.b1", + "langgraph.utils.runnable.RunnableSeq.b2", + "langgraph.utils.runnable.RunnableSeq.b3", + ], + ) def assert_fanning_graph_spans(spans): - assert len(spans) == 5 - assert spans[0].resource == "langgraph.graph.state.CompiledStateGraph.LangGraph" - assert spans[1].resource == "langgraph.utils.runnable.RunnableSeq.a" - assert spans[2].resource == "langgraph.utils.runnable.RunnableSeq.b" - assert spans[3].resource == "langgraph.utils.runnable.RunnableSeq.c" - assert spans[4].resource == "langgraph.utils.runnable.RunnableSeq.d" - - -def test_simple_graph(langgraph, simple_graph, mock_tracer): + assert_has_spans( + spans, + expected=[ + "langgraph.graph.state.CompiledStateGraph.LangGraph", + "langgraph.utils.runnable.RunnableSeq.a", + "langgraph.utils.runnable.RunnableSeq.b", + "langgraph.utils.runnable.RunnableSeq.c", + "langgraph.utils.runnable.RunnableSeq.d", + ], + ) + + +def test_simple_graph(simple_graph, mock_tracer): simple_graph.invoke({"a_list": [], "which": "a"}) spans = mock_tracer.pop_traces()[0] assert_simple_graph_spans(spans) -async def test_simple_graph_async(langgraph, simple_graph, mock_tracer): +async def test_simple_graph_async(simple_graph, mock_tracer): await simple_graph.ainvoke({"a_list": [], "which": "a"}) spans = mock_tracer.pop_traces()[0] assert_simple_graph_spans(spans) -def test_simple_graph_stream(langgraph, simple_graph, mock_tracer): +def test_simple_graph_stream(simple_graph, mock_tracer): for _ in simple_graph.stream({"a_list": [], "which": "a"}): pass spans = mock_tracer.pop_traces()[0] assert_simple_graph_spans(spans) -async def test_simple_graph_stream_async(langgraph, simple_graph, mock_tracer): +async def test_simple_graph_stream_async(simple_graph, mock_tracer): async for _ in simple_graph.astream({"a_list": [], "which": "a"}): pass spans = mock_tracer.pop_traces()[0] assert_simple_graph_spans(spans) -def test_conditional_graph(langgraph, conditional_graph, mock_tracer): +def test_conditional_graph(conditional_graph, mock_tracer): conditional_graph.invoke({"a_list": [], "which": "c"}) spans = mock_tracer.pop_traces()[0] assert_conditional_graph_spans(spans, which="c") -async def test_conditional_graph_async(langgraph, conditional_graph, mock_tracer): +async def test_conditional_graph_async(conditional_graph, mock_tracer): await conditional_graph.ainvoke({"a_list": [], "which": "b"}) spans = mock_tracer.pop_traces()[0] assert_conditional_graph_spans(spans, which="b") -def test_conditional_graph_stream(langgraph, conditional_graph, mock_tracer): +def test_conditional_graph_stream(conditional_graph, mock_tracer): for _ in conditional_graph.stream({"a_list": [], "which": "c"}): pass spans = mock_tracer.pop_traces()[0] assert_conditional_graph_spans(spans, which="c") -async def test_conditional_graph_stream_async(langgraph, conditional_graph, mock_tracer): +async def test_conditional_graph_stream_async(conditional_graph, mock_tracer): async for _ in conditional_graph.astream({"a_list": [], "which": "b"}): pass spans = mock_tracer.pop_traces()[0] assert_conditional_graph_spans(spans, which="b") -def test_subgraph(langgraph, complex_graph, mock_tracer): +def test_subgraph(complex_graph, mock_tracer): complex_graph.invoke({"a_list": [], "which": "b"}) spans = mock_tracer.pop_traces()[0] assert_subgraph_spans(spans) -async def test_subgraph_async(langgraph, complex_graph, mock_tracer): +async def test_subgraph_async(complex_graph, mock_tracer): await complex_graph.ainvoke({"a_list": [], "which": "b"}) spans = mock_tracer.pop_traces()[0] assert_subgraph_spans(spans) -def test_subgraph_stream(langgraph, complex_graph, mock_tracer): +def test_subgraph_stream(complex_graph, mock_tracer): for _ in complex_graph.stream({"a_list": [], "which": "b"}): pass spans = mock_tracer.pop_traces()[0] assert_subgraph_spans(spans) -async def test_subgraph_stream_async(langgraph, complex_graph, mock_tracer): +async def test_subgraph_stream_async(complex_graph, mock_tracer): async for _ in complex_graph.astream({"a_list": [], "which": "b"}): pass spans = mock_tracer.pop_traces()[0] assert_subgraph_spans(spans) -def test_fanning_graph(langgraph, fanning_graph, mock_tracer): +def test_fanning_graph(fanning_graph, mock_tracer): fanning_graph.invoke({"a_list": [], "which": "b"}) spans = mock_tracer.pop_traces()[0] assert_fanning_graph_spans(spans) -async def test_fanning_graph_async(langgraph, fanning_graph, mock_tracer): +async def test_fanning_graph_async(fanning_graph, mock_tracer): await fanning_graph.ainvoke({"a_list": [], "which": "b"}) spans = mock_tracer.pop_traces()[0] assert_fanning_graph_spans(spans) -def test_fanning_graph_stream(langgraph, fanning_graph, mock_tracer): +def test_fanning_graph_stream(fanning_graph, mock_tracer): for _ in fanning_graph.stream({"a_list": [], "which": "b"}): pass spans = mock_tracer.pop_traces()[0] assert_fanning_graph_spans(spans) -async def test_fanning_graph_stream_async(langgraph, fanning_graph, mock_tracer): +async def test_fanning_graph_stream_async(fanning_graph, mock_tracer): async for _ in fanning_graph.astream({"a_list": [], "which": "b"}): pass spans = mock_tracer.pop_traces()[0] diff --git a/tests/contrib/langgraph/test_langgraph_llmobs.py b/tests/contrib/langgraph/test_langgraph_llmobs.py index 968a98bdb26..46728dcdab0 100644 --- a/tests/contrib/langgraph/test_langgraph_llmobs.py +++ b/tests/contrib/langgraph/test_langgraph_llmobs.py @@ -6,8 +6,8 @@ def _assert_span_link(from_span_event, to_span_event, from_io, to_io): Assert that a span link exists between two span events, specifically the correct span ID and from/to specification. """ found = False - expected_to_span_id = "undefined" if not to_span_event else to_span_event["span_id"] - for span_link in from_span_event["span_links"]: + expected_to_span_id = "undefined" if not from_span_event else from_span_event["span_id"] + for span_link in to_span_event["span_links"]: if span_link["span_id"] == expected_to_span_id: assert span_link["attributes"] == {"from": from_io, "to": to_io} found = True @@ -15,20 +15,24 @@ def _assert_span_link(from_span_event, to_span_event, from_io, to_io): assert found +def _find_span_by_name(llmobs_events, name): + for event in llmobs_events: + if event["name"] == name: + return event + assert False, f"Span '{name}' not found in llmobs span events" + + class TestLangGraphLLMObs: def test_simple_graph(self, llmobs_events, simple_graph): simple_graph.invoke({"a_list": [], "which": "a"}, stream_mode=["values"]) - graph_span = llmobs_events[2] - a_span = llmobs_events[0] - b_span = llmobs_events[1] + graph_span = _find_span_by_name(llmobs_events, "LangGraph") + a_span = _find_span_by_name(llmobs_events, "a") + b_span = _find_span_by_name(llmobs_events, "b") - assert graph_span["name"] == "LangGraph" - _assert_span_link(graph_span, None, "input", "input") - _assert_span_link(graph_span, b_span, "output", "output") - assert a_span["name"] == "a" - _assert_span_link(a_span, graph_span, "input", "input") - assert b_span["name"] == "b" - _assert_span_link(b_span, a_span, "output", "input") + _assert_span_link(None, graph_span, "input", "input") + _assert_span_link(b_span, graph_span, "output", "output") + _assert_span_link(graph_span, a_span, "input", "input") + _assert_span_link(a_span, b_span, "output", "input") # Check that the graph span has the appropriate output # stream_mode=["values"] should result in the last yield being a tuple @@ -36,17 +40,14 @@ def test_simple_graph(self, llmobs_events, simple_graph): async def test_simple_graph_async(self, llmobs_events, simple_graph): await simple_graph.ainvoke({"a_list": [], "which": "a"}) - graph_span = llmobs_events[2] - a_span = llmobs_events[0] - b_span = llmobs_events[1] + graph_span = _find_span_by_name(llmobs_events, "LangGraph") + a_span = _find_span_by_name(llmobs_events, "a") + b_span = _find_span_by_name(llmobs_events, "b") - assert graph_span["name"] == "LangGraph" - _assert_span_link(graph_span, None, "input", "input") - _assert_span_link(graph_span, b_span, "output", "output") - assert a_span["name"] == "a" - _assert_span_link(a_span, graph_span, "input", "input") - assert b_span["name"] == "b" - _assert_span_link(b_span, a_span, "output", "input") + _assert_span_link(None, graph_span, "input", "input") + _assert_span_link(b_span, graph_span, "output", "output") + _assert_span_link(graph_span, a_span, "input", "input") + _assert_span_link(a_span, b_span, "output", "input") # Check that the graph span has the appropriate output # default stream_mode of "values" should result in the last yield being an object @@ -54,118 +55,91 @@ async def test_simple_graph_async(self, llmobs_events, simple_graph): def test_conditional_graph(self, llmobs_events, conditional_graph): conditional_graph.invoke({"a_list": [], "which": "c"}) - graph_span = llmobs_events[2] - a_span = llmobs_events[0] - c_span = llmobs_events[1] + graph_span = _find_span_by_name(llmobs_events, "LangGraph") + a_span = _find_span_by_name(llmobs_events, "a") + c_span = _find_span_by_name(llmobs_events, "c") - assert graph_span["name"] == "LangGraph" - _assert_span_link(graph_span, None, "input", "input") - _assert_span_link(graph_span, c_span, "output", "output") - assert a_span["name"] == "a" - _assert_span_link(a_span, graph_span, "input", "input") - assert c_span["name"] == "c" - _assert_span_link(c_span, a_span, "output", "input") + _assert_span_link(None, graph_span, "input", "input") + _assert_span_link(c_span, graph_span, "output", "output") + _assert_span_link(graph_span, a_span, "input", "input") + _assert_span_link(a_span, c_span, "output", "input") async def test_conditional_graph_async(self, llmobs_events, conditional_graph): await conditional_graph.ainvoke({"a_list": [], "which": "b"}) - graph_span = llmobs_events[2] - a_span = llmobs_events[0] - c_span = llmobs_events[1] + graph_span = _find_span_by_name(llmobs_events, "LangGraph") + a_span = _find_span_by_name(llmobs_events, "a") + b_span = _find_span_by_name(llmobs_events, "b") - assert graph_span["name"] == "LangGraph" - _assert_span_link(graph_span, None, "input", "input") - _assert_span_link(graph_span, c_span, "output", "output") - assert a_span["name"] == "a" - _assert_span_link(a_span, graph_span, "input", "input") - assert c_span["name"] == "b" - _assert_span_link(c_span, a_span, "output", "input") + _assert_span_link(None, graph_span, "input", "input") + _assert_span_link(b_span, graph_span, "output", "output") + _assert_span_link(graph_span, a_span, "input", "input") + _assert_span_link(a_span, b_span, "output", "input") def test_subgraph(self, llmobs_events, complex_graph): complex_graph.invoke({"a_list": [], "which": "b"}) - graph_span = llmobs_events[5] - a_span = llmobs_events[0] - b_span = llmobs_events[4] - b1_span = llmobs_events[1] - b2_span = llmobs_events[2] - b3_span = llmobs_events[3] - - assert graph_span["name"] == "LangGraph" - _assert_span_link(graph_span, None, "input", "input") - _assert_span_link(graph_span, b_span, "output", "output") - assert a_span["name"] == "a" - _assert_span_link(a_span, graph_span, "input", "input") - assert b_span["name"] == "b" - _assert_span_link(b_span, a_span, "output", "input") - _assert_span_link(b_span, b3_span, "output", "output") - assert b1_span["name"] == "b1" - _assert_span_link(b1_span, b_span, "input", "input") - assert b2_span["name"] == "b2" - _assert_span_link(b2_span, b1_span, "output", "input") - assert b3_span["name"] == "b3" - _assert_span_link(b3_span, b2_span, "output", "input") + graph_span = _find_span_by_name(llmobs_events, "LangGraph") + a_span = _find_span_by_name(llmobs_events, "a") + b_span = _find_span_by_name(llmobs_events, "b") + b1_span = _find_span_by_name(llmobs_events, "b1") + b2_span = _find_span_by_name(llmobs_events, "b2") + b3_span = _find_span_by_name(llmobs_events, "b3") + + _assert_span_link(None, graph_span, "input", "input") + _assert_span_link(b3_span, b_span, "output", "output") + _assert_span_link(graph_span, a_span, "input", "input") + _assert_span_link(a_span, b_span, "output", "input") + _assert_span_link(b3_span, b_span, "output", "output") + _assert_span_link(b_span, b1_span, "input", "input") + _assert_span_link(b1_span, b2_span, "output", "input") + _assert_span_link(b2_span, b3_span, "output", "input") async def test_subgraph_async(self, llmobs_events, complex_graph): await complex_graph.ainvoke({"a_list": [], "which": "b"}) - graph_span = llmobs_events[5] - a_span = llmobs_events[0] - b_span = llmobs_events[4] - b1_span = llmobs_events[1] - b2_span = llmobs_events[2] - b3_span = llmobs_events[3] - - assert graph_span["name"] == "LangGraph" - _assert_span_link(graph_span, None, "input", "input") - _assert_span_link(graph_span, b_span, "output", "output") - assert a_span["name"] == "a" - _assert_span_link(a_span, graph_span, "input", "input") - assert b_span["name"] == "b" - _assert_span_link(b_span, a_span, "output", "input") - _assert_span_link(b_span, b3_span, "output", "output") - assert b1_span["name"] == "b1" - _assert_span_link(b1_span, b_span, "input", "input") - assert b2_span["name"] == "b2" - _assert_span_link(b2_span, b1_span, "output", "input") - assert b3_span["name"] == "b3" - _assert_span_link(b3_span, b2_span, "output", "input") + graph_span = _find_span_by_name(llmobs_events, "LangGraph") + a_span = _find_span_by_name(llmobs_events, "a") + b_span = _find_span_by_name(llmobs_events, "b") + b1_span = _find_span_by_name(llmobs_events, "b1") + b2_span = _find_span_by_name(llmobs_events, "b2") + b3_span = _find_span_by_name(llmobs_events, "b3") + + _assert_span_link(None, graph_span, "input", "input") + _assert_span_link(b3_span, b_span, "output", "output") + _assert_span_link(graph_span, a_span, "input", "input") + _assert_span_link(a_span, b_span, "output", "input") + _assert_span_link(b3_span, b_span, "output", "output") + _assert_span_link(b_span, b1_span, "input", "input") + _assert_span_link(b1_span, b2_span, "output", "input") + _assert_span_link(b2_span, b3_span, "output", "input") def test_fanning_graph(self, llmobs_events, fanning_graph): fanning_graph.invoke({"a_list": [], "which": ""}) - graph_span = llmobs_events[4] - a_span = llmobs_events[0] - b_span = llmobs_events[1] - c_span = llmobs_events[2] - d_span = llmobs_events[3] - - assert graph_span["name"] == "LangGraph" - _assert_span_link(graph_span, None, "input", "input") - _assert_span_link(graph_span, d_span, "output", "output") - assert a_span["name"] == "a" - _assert_span_link(a_span, graph_span, "input", "input") - assert b_span["name"] == "b" - _assert_span_link(b_span, a_span, "output", "input") - assert c_span["name"] == "c" - _assert_span_link(c_span, a_span, "output", "input") - assert d_span["name"] == "d" - _assert_span_link(d_span, b_span, "output", "input") - _assert_span_link(d_span, c_span, "output", "input") + graph_span = _find_span_by_name(llmobs_events, "LangGraph") + a_span = _find_span_by_name(llmobs_events, "a") + b_span = _find_span_by_name(llmobs_events, "b") + c_span = _find_span_by_name(llmobs_events, "c") + d_span = _find_span_by_name(llmobs_events, "d") + + _assert_span_link(None, graph_span, "input", "input") + _assert_span_link(d_span, graph_span, "output", "output") + _assert_span_link(graph_span, a_span, "input", "input") + _assert_span_link(a_span, b_span, "output", "input") + _assert_span_link(a_span, c_span, "output", "input") + _assert_span_link(b_span, d_span, "output", "input") + _assert_span_link(c_span, d_span, "output", "input") async def test_fanning_graph_async(self, llmobs_events, fanning_graph): await fanning_graph.ainvoke({"a_list": [], "which": ""}) - graph_span = llmobs_events[4] - a_span = llmobs_events[0] - b_span = llmobs_events[1] - c_span = llmobs_events[2] - d_span = llmobs_events[3] + graph_span = _find_span_by_name(llmobs_events, "LangGraph") + a_span = _find_span_by_name(llmobs_events, "a") + b_span = _find_span_by_name(llmobs_events, "b") + c_span = _find_span_by_name(llmobs_events, "c") + d_span = _find_span_by_name(llmobs_events, "d") assert graph_span["name"] == "LangGraph" - _assert_span_link(graph_span, None, "input", "input") - _assert_span_link(graph_span, d_span, "output", "output") - assert a_span["name"] == "a" - _assert_span_link(a_span, graph_span, "input", "input") - assert b_span["name"] == "b" - _assert_span_link(b_span, a_span, "output", "input") - assert c_span["name"] == "c" - _assert_span_link(c_span, a_span, "output", "input") - assert d_span["name"] == "d" - _assert_span_link(d_span, b_span, "output", "input") - _assert_span_link(d_span, c_span, "output", "input") + _assert_span_link(None, graph_span, "input", "input") + _assert_span_link(d_span, graph_span, "output", "output") + _assert_span_link(graph_span, a_span, "input", "input") + _assert_span_link(a_span, b_span, "output", "input") + _assert_span_link(a_span, c_span, "output", "input") + _assert_span_link(b_span, d_span, "output", "input") + _assert_span_link(c_span, d_span, "output", "input") diff --git a/tests/contrib/openai/test_openai_v0.py b/tests/contrib/openai/test_openai_v0.py index 3fa262e8a4a..0dbd537c3ff 100644 --- a/tests/contrib/openai/test_openai_v0.py +++ b/tests/contrib/openai/test_openai_v0.py @@ -11,7 +11,7 @@ import ddtrace from ddtrace import patch from ddtrace.contrib.internal.openai.utils import _est_tokens -from ddtrace.contrib.trace_utils import iswrapped +from ddtrace.contrib.internal.trace_utils import iswrapped from ddtrace.internal.utils.version import parse_version from tests.contrib.openai.utils import chat_completion_custom_functions from tests.contrib.openai.utils import chat_completion_input_description diff --git a/tests/contrib/openai/test_openai_v1.py b/tests/contrib/openai/test_openai_v1.py index 918d3eadae9..47ed05ea1bd 100644 --- a/tests/contrib/openai/test_openai_v1.py +++ b/tests/contrib/openai/test_openai_v1.py @@ -7,7 +7,7 @@ import ddtrace from ddtrace import patch from ddtrace.contrib.internal.openai.utils import _est_tokens -from ddtrace.contrib.trace_utils import iswrapped +from ddtrace.contrib.internal.trace_utils import iswrapped from ddtrace.internal.utils.version import parse_version from tests.contrib.openai.utils import chat_completion_custom_functions from tests.contrib.openai.utils import chat_completion_input_description diff --git a/tests/contrib/pyramid/test_pyramid.py b/tests/contrib/pyramid/test_pyramid.py index 3cc9b2688ca..28fd7616cdd 100644 --- a/tests/contrib/pyramid/test_pyramid.py +++ b/tests/contrib/pyramid/test_pyramid.py @@ -4,8 +4,8 @@ import pytest from ddtrace import config -from ddtrace.constants import ORIGIN_KEY -from ddtrace.constants import SAMPLING_PRIORITY_KEY +from ddtrace.constants import _ORIGIN_KEY +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from tests.utils import TracerTestCase from tests.utils import flaky @@ -89,8 +89,8 @@ def test_distributed_tracing(self): assert span.get_tag("span.kind") == "server" assert span.trace_id == 100 assert span.parent_id == 42 - assert span.get_metric(SAMPLING_PRIORITY_KEY) == 2 - assert span.get_tag(ORIGIN_KEY) == "synthetics" + assert span.get_metric(_SAMPLING_PRIORITY_KEY) == 2 + assert span.get_tag(_ORIGIN_KEY) == "synthetics" def test_distributed_tracing_patterned(self): # ensure the Context is properly created @@ -112,8 +112,8 @@ def test_distributed_tracing_patterned(self): assert span.get_tag("http.route") == "/hello/{param}" assert span.trace_id == 100 assert span.parent_id == 42 - assert span.get_metric(SAMPLING_PRIORITY_KEY) == 2 - assert span.get_tag(ORIGIN_KEY) == "synthetics" + assert span.get_metric(_SAMPLING_PRIORITY_KEY) == 2 + assert span.get_tag(_ORIGIN_KEY) == "synthetics" class TestPyramidDistributedTracingDisabled(PyramidBase): @@ -141,8 +141,8 @@ def test_distributed_tracing_disabled(self): assert span.get_tag("span.kind") == "server" assert span.trace_id != 100 assert span.parent_id != 42 - assert span.get_metric(SAMPLING_PRIORITY_KEY) != 2 - assert span.get_tag(ORIGIN_KEY) != "synthetics" + assert span.get_metric(_SAMPLING_PRIORITY_KEY) != 2 + assert span.get_tag(_ORIGIN_KEY) != "synthetics" class TestSchematization(PyramidBase): diff --git a/tests/contrib/pytest/test_pytest.py b/tests/contrib/pytest/test_pytest.py index 267a9d97eac..3918b82174b 100644 --- a/tests/contrib/pytest/test_pytest.py +++ b/tests/contrib/pytest/test_pytest.py @@ -7,8 +7,8 @@ import pytest import ddtrace +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import ERROR_MSG -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.contrib.internal.pytest._utils import _USE_PLUGIN_V2 from ddtrace.contrib.internal.pytest.constants import XFAIL_REASON from ddtrace.contrib.internal.pytest.patch import get_version @@ -805,7 +805,7 @@ def test_sample_priority(): spans = self.pop_spans() assert len(spans) == 4 - assert spans[0].get_metric(SAMPLING_PRIORITY_KEY) == 1 + assert spans[0].get_metric(_SAMPLING_PRIORITY_KEY) == 1 def test_pytest_exception(self): """Test that pytest sets exception information correctly.""" diff --git a/tests/contrib/pytest/test_pytest_atr.py b/tests/contrib/pytest/test_pytest_atr.py index ebb4f8421d8..92012461ed0 100644 --- a/tests/contrib/pytest/test_pytest_atr.py +++ b/tests/contrib/pytest/test_pytest_atr.py @@ -24,17 +24,25 @@ ) _TEST_PASS_CONTENT = """ +import unittest + def test_func_pass(): assert True + +class SomeTestCase(unittest.TestCase): + def test_class_func_pass(self): + assert True """ _TEST_FAIL_CONTENT = """ import pytest +import unittest def test_func_fail(): assert False _test_func_retries_skip_count = 0 + def test_func_retries_skip(): global _test_func_retries_skip_count _test_func_retries_skip_count += 1 @@ -42,9 +50,24 @@ def test_func_retries_skip(): pytest.skip() assert False +_test_class_func_retries_skip_count = 0 + +class SomeTestCase(unittest.TestCase): + def test_class_func_fail(self): + assert False + + def test_class_func_retries_skip(self): + global _test_class_func_retries_skip_count + _test_class_func_retries_skip_count += 1 + if _test_class_func_retries_skip_count > 1: + pytest.skip() + assert False """ + _TEST_PASS_ON_RETRIES_CONTENT = """ +import unittest + _test_func_passes_4th_retry_count = 0 def test_func_passes_4th_retry(): global _test_func_passes_4th_retry_count @@ -56,10 +79,19 @@ def test_func_passes_1st_retry(): global _test_func_passes_1st_retry_count _test_func_passes_1st_retry_count += 1 assert _test_func_passes_1st_retry_count == 2 + +class SomeTestCase(unittest.TestCase): + _test_func_passes_4th_retry_count = 0 + + def test_func_passes_4th_retry(self): + SomeTestCase._test_func_passes_4th_retry_count += 1 + assert SomeTestCase._test_func_passes_4th_retry_count == 5 + """ _TEST_ERRORS_CONTENT = """ import pytest +import unittest @pytest.fixture def fixture_fails_setup(): @@ -79,6 +111,7 @@ def test_func_fails_teardown(fixture_fails_teardown): _TEST_SKIP_CONTENT = """ import pytest +import unittest @pytest.mark.skip def test_func_skip_mark(): @@ -86,6 +119,14 @@ def test_func_skip_mark(): def test_func_skip_inside(): pytest.skip() + +class SomeTestCase(unittest.TestCase): + @pytest.mark.skip + def test_class_func_skip_mark(self): + assert True + + def test_class_func_skip_inside(self): + pytest.skip() """ @@ -105,7 +146,7 @@ def test_pytest_atr_no_ddtrace_does_not_retry(self): self.testdir.makepyfile(test_pass_on_retries=_TEST_PASS_ON_RETRIES_CONTENT) self.testdir.makepyfile(test_skip=_TEST_SKIP_CONTENT) rec = self.inline_run() - rec.assertoutcome(passed=2, failed=6, skipped=2) + rec.assertoutcome(passed=3, failed=9, skipped=4) assert len(self.pop_spans()) == 0 def test_pytest_atr_env_var_disables_retrying(self): @@ -117,7 +158,7 @@ def test_pytest_atr_env_var_disables_retrying(self): with mock.patch("ddtrace.internal.ci_visibility.recorder.ddconfig", _get_default_civisibility_ddconfig()): rec = self.inline_run("--ddtrace", "-s", extra_env={"DD_CIVISIBILITY_FLAKY_RETRY_ENABLED": "0"}) - rec.assertoutcome(passed=2, failed=6, skipped=2) + rec.assertoutcome(passed=3, failed=9, skipped=4) assert len(self.pop_spans()) > 0 def test_pytest_atr_env_var_does_not_override_api(self): @@ -133,7 +174,7 @@ def test_pytest_atr_env_var_does_not_override_api(self): return_value=TestVisibilityAPISettings(flaky_test_retries_enabled=False), ): rec = self.inline_run("--ddtrace", extra_env={"DD_CIVISIBILITY_FLAKY_RETRY_ENABLED": "1"}) - rec.assertoutcome(passed=2, failed=6, skipped=2) + rec.assertoutcome(passed=3, failed=9, skipped=4) assert len(self.pop_spans()) > 0 def test_pytest_atr_spans(self): @@ -174,6 +215,15 @@ def test_pytest_atr_spans(self): func_fail_retries += 1 assert func_fail_retries == 5 + class_func_fail_spans = _get_spans_from_list(spans, "test", "SomeTestCase::test_class_func_fail") + assert len(class_func_fail_spans) == 6 + class_func_fail_retries = 0 + for class_func_fail_span in class_func_fail_spans: + assert class_func_fail_span.get_tag("test.status") == "fail" + if class_func_fail_span.get_tag("test.is_retry") == "true": + class_func_fail_retries += 1 + assert class_func_fail_retries == 5 + func_fail_skip_spans = _get_spans_from_list(spans, "test", "test_func_retries_skip") assert len(func_fail_skip_spans) == 6 func_fail_skip_retries = 0 @@ -184,6 +234,18 @@ def test_pytest_atr_spans(self): func_fail_skip_retries += 1 assert func_fail_skip_retries == 5 + class_func_fail_skip_spans = _get_spans_from_list(spans, "test", "SomeTestCase::test_class_func_retries_skip") + assert len(class_func_fail_skip_spans) == 6 + class_func_fail_skip_retries = 0 + for class_func_fail_skip_span in class_func_fail_skip_spans: + class_func_fail_skip_is_retry = class_func_fail_skip_span.get_tag("test.is_retry") == "true" + assert class_func_fail_skip_span.get_tag("test.status") == ( + "skip" if class_func_fail_skip_is_retry else "fail" + ) + if class_func_fail_skip_is_retry: + class_func_fail_skip_retries += 1 + assert class_func_fail_skip_retries == 5 + func_pass_spans = _get_spans_from_list(spans, "test", "test_func_pass") assert len(func_pass_spans) == 1 assert func_pass_spans[0].get_tag("test.status") == "pass" @@ -201,7 +263,17 @@ def test_pytest_atr_spans(self): assert func_skip_inside_spans[0].get_tag("test.status") == "skip" assert func_skip_inside_spans[0].get_tag("test.is_retry") is None - assert len(spans) == 31 + class_func_skip_mark_spans = _get_spans_from_list(spans, "test", "SomeTestCase::test_class_func_skip_mark") + assert len(class_func_skip_mark_spans) == 1 + assert class_func_skip_mark_spans[0].get_tag("test.status") == "skip" + assert class_func_skip_mark_spans[0].get_tag("test.is_retry") is None + + class_func_skip_inside_spans = _get_spans_from_list(spans, "test", "SomeTestCase::test_class_func_skip_inside") + assert len(class_func_skip_inside_spans) == 1 + assert class_func_skip_inside_spans[0].get_tag("test.status") == "skip" + assert class_func_skip_inside_spans[0].get_tag("test.is_retry") is None + + assert len(spans) == 51 def test_pytest_atr_fails_session_when_test_fails(self): self.testdir.makepyfile(test_pass=_TEST_PASS_CONTENT) @@ -212,7 +284,7 @@ def test_pytest_atr_fails_session_when_test_fails(self): rec = self.inline_run("--ddtrace") spans = self.pop_spans() assert rec.ret == 1 - assert len(spans) == 28 + assert len(spans) == 48 def test_pytest_atr_passes_session_when_test_pass(self): self.testdir.makepyfile(test_pass=_TEST_PASS_CONTENT) @@ -222,9 +294,14 @@ def test_pytest_atr_passes_session_when_test_pass(self): rec = self.inline_run("--ddtrace") spans = self.pop_spans() assert rec.ret == 0 - assert len(spans) == 15 + assert len(spans) == 23 def test_pytest_atr_does_not_retry_failed_setup_or_teardown(self): + # NOTE: This feature only works for regular pytest tests. For tests inside unittest classes, setup and teardown + # happens at the 'call' phase, and we don't have a way to detect that the error happened during setup/teardown, + # so tests will be retried as if they were failing tests. + # See . + self.testdir.makepyfile(test_errors=_TEST_ERRORS_CONTENT) rec = self.inline_run("--ddtrace") spans = self.pop_spans() diff --git a/tests/contrib/tornado/test_tornado_web.py b/tests/contrib/tornado/test_tornado_web.py index 1630f8b16b3..2a22651cdf1 100644 --- a/tests/contrib/tornado/test_tornado_web.py +++ b/tests/contrib/tornado/test_tornado_web.py @@ -2,9 +2,9 @@ import tornado from ddtrace import config +from ddtrace.constants import _ORIGIN_KEY +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import ERROR_MSG -from ddtrace.constants import ORIGIN_KEY -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.ext import http from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME from tests.opentracer.utils import init_tracer @@ -379,7 +379,7 @@ def test_propagation(self): # check propagation assert 1234 == request_span.trace_id assert 4567 == request_span.parent_id - assert 2 == request_span.get_metric(SAMPLING_PRIORITY_KEY) + assert 2 == request_span.get_metric(_SAMPLING_PRIORITY_KEY) assert request_span.get_tag("component") == "tornado" assert request_span.get_tag("span.kind") == "server" @@ -463,8 +463,8 @@ def test_no_propagation(self): # check non-propagation assert request_span.trace_id != 1234 assert request_span.parent_id != 4567 - assert request_span.get_metric(SAMPLING_PRIORITY_KEY) != 2 - assert request_span.get_tag(ORIGIN_KEY) != "synthetics" + assert request_span.get_metric(_SAMPLING_PRIORITY_KEY) != 2 + assert request_span.get_tag(_ORIGIN_KEY) != "synthetics" assert request_span.get_tag("component") == "tornado" assert request_span.get_tag("span.kind") == "server" @@ -502,8 +502,8 @@ def test_no_propagation_via_int_config(self): # check non-propagation assert request_span.trace_id != 1234 assert request_span.parent_id != 4567 - assert request_span.get_metric(SAMPLING_PRIORITY_KEY) != 2 - assert request_span.get_tag(ORIGIN_KEY) != "synthetics" + assert request_span.get_metric(_SAMPLING_PRIORITY_KEY) != 2 + assert request_span.get_tag(_ORIGIN_KEY) != "synthetics" assert request_span.get_tag("component") == "tornado" assert request_span.get_tag("span.kind") == "server" @@ -536,8 +536,8 @@ def test_no_propagation_via_env_var(self): # check non-propagation assert request_span.trace_id != 1234 assert request_span.parent_id != 4567 - assert request_span.get_metric(SAMPLING_PRIORITY_KEY) != 2 - assert request_span.get_tag(ORIGIN_KEY) != "synthetics" + assert request_span.get_metric(_SAMPLING_PRIORITY_KEY) != 2 + assert request_span.get_tag(_ORIGIN_KEY) != "synthetics" assert request_span.get_tag("component") == "tornado" assert request_span.get_tag("span.kind") == "server" diff --git a/tests/contrib/vertexai/test_vertexai.py b/tests/contrib/vertexai/test_vertexai.py index 5f07c6e177f..afcbfea39ba 100644 --- a/tests/contrib/vertexai/test_vertexai.py +++ b/tests/contrib/vertexai/test_vertexai.py @@ -42,7 +42,7 @@ def test_vertexai_completion(vertexai): llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) llm.generate_content( - "Why do bears hibernate?", + contents="Why do bears hibernate?", generation_config=vertexai.generative_models.GenerationConfig( stop_sequences=["x"], max_output_tokens=30, temperature=1.0 ), @@ -118,7 +118,7 @@ def test_vertexai_completion_stream(vertexai): (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) ] response = llm.generate_content( - "How big is the solar system?", + contents="How big is the solar system?", generation_config=vertexai.generative_models.GenerationConfig( stop_sequences=["x"], max_output_tokens=30, temperature=1.0 ), @@ -278,7 +278,7 @@ def test_vertexai_chat(vertexai): llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) chat = llm.start_chat() chat.send_message( - "Why do bears hibernate?", + content="Why do bears hibernate?", generation_config=vertexai.generative_models.GenerationConfig( stop_sequences=["x"], max_output_tokens=30, temperature=1.0 ), @@ -371,7 +371,7 @@ def test_vertexai_chat_stream(vertexai): ] chat = llm.start_chat() response = chat.send_message( - "How big is the solar system?", + content="How big is the solar system?", generation_config=vertexai.generative_models.GenerationConfig( stop_sequences=["x"], max_output_tokens=30, temperature=1.0 ), diff --git a/tests/contrib/vertexai/test_vertexai_llmobs.py b/tests/contrib/vertexai/test_vertexai_llmobs.py index 78a03bc664c..701f709e213 100644 --- a/tests/contrib/vertexai/test_vertexai_llmobs.py +++ b/tests/contrib/vertexai/test_vertexai_llmobs.py @@ -21,7 +21,7 @@ def test_completion(self, vertexai, mock_llmobs_writer, mock_tracer): llm = vertexai.generative_models.GenerativeModel("gemini-1.5-flash") llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) llm.generate_content( - "Why do bears hibernate?", + contents="Why do bears hibernate?", generation_config=vertexai.generative_models.GenerationConfig( stop_sequences=["x"], max_output_tokens=30, temperature=1.0 ), @@ -126,7 +126,7 @@ def test_completion_stream(self, vertexai, mock_llmobs_writer, mock_tracer): (_mock_completion_stream_chunk(chunk) for chunk in MOCK_COMPLETION_STREAM_CHUNKS) ] response = llm.generate_content( - "How big is the solar system?", + contents="How big is the solar system?", generation_config=vertexai.generative_models.GenerationConfig( stop_sequences=["x"], max_output_tokens=30, temperature=1.0 ), @@ -293,7 +293,7 @@ def test_chat(self, vertexai, mock_llmobs_writer, mock_tracer): llm._prediction_client.responses["generate_content"].append(_mock_completion_response(MOCK_COMPLETION_SIMPLE_1)) chat = llm.start_chat() chat.send_message( - "Why do bears hibernate?", + content="Why do bears hibernate?", generation_config=vertexai.generative_models.GenerationConfig( stop_sequences=["x"], max_output_tokens=30, temperature=1.0 ), @@ -389,7 +389,7 @@ def test_chat_stream(self, vertexai, mock_llmobs_writer, mock_tracer): ] chat = llm.start_chat() response = chat.send_message( - "How big is the solar system?", + content="How big is the solar system?", generation_config=vertexai.generative_models.GenerationConfig( stop_sequences=["x"], max_output_tokens=30, temperature=1.0 ), diff --git a/tests/debugging/test_debugger.py b/tests/debugging/test_debugger.py index bacfcbcdd45..c9f04cee6ee 100644 --- a/tests/debugging/test_debugger.py +++ b/tests/debugging/test_debugger.py @@ -8,7 +8,7 @@ import pytest import ddtrace -from ddtrace.constants import ORIGIN_KEY +from ddtrace.constants import _ORIGIN_KEY from ddtrace.debugging._debugger import DebuggerWrappingContext from ddtrace.debugging._probe.model import DDExpression from ddtrace.debugging._probe.model import MetricProbeKind @@ -929,7 +929,7 @@ def test_debugger_span_probe(self): tags = span.get_tags() assert tags["debugger.probeid"] == "span-probe" assert tags["tag"] == "value" - assert tags[ORIGIN_KEY] == "di" + assert tags[_ORIGIN_KEY] == "di" def test_debugger_span_not_created_when_condition_was_false(self): from tests.submod.stuff import mutator diff --git a/tests/integration/test_integration_snapshots.py b/tests/integration/test_integration_snapshots.py index 3c8bae602f4..dc80f9508ca 100644 --- a/tests/integration/test_integration_snapshots.py +++ b/tests/integration/test_integration_snapshots.py @@ -228,13 +228,13 @@ def test_trace_with_wrong_metrics_types_not_sent(encoding, metrics, monkeypatch) @pytest.mark.snapshot() def test_tracetagsprocessor_only_adds_new_tags(): from ddtrace import tracer + from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import AUTO_KEEP - from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.constants import USER_KEEP with tracer.trace(name="web.request") as span: span.context.sampling_priority = AUTO_KEEP - span.set_metric(SAMPLING_PRIORITY_KEY, USER_KEEP) + span.set_metric(_SAMPLING_PRIORITY_KEY, USER_KEEP) tracer.flush() diff --git a/tests/integration/test_priority_sampling.py b/tests/integration/test_priority_sampling.py index 653ef96d49e..8ea46591a1a 100644 --- a/tests/integration/test_priority_sampling.py +++ b/tests/integration/test_priority_sampling.py @@ -2,9 +2,9 @@ import pytest +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import AUTO_KEEP from ddtrace.constants import AUTO_REJECT -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.internal.encoding import JSONEncoder from ddtrace.internal.encoding import MsgpackEncoderV04 as Encoder from ddtrace.internal.writer import AgentWriter @@ -82,7 +82,7 @@ def test_priority_sampling_rate_honored(): pass t.flush() assert len(t._writer.traces) == captured_span_count - sampled_spans = [s for s in t._writer.spans if s.context._metrics[SAMPLING_PRIORITY_KEY] == AUTO_KEEP] + sampled_spans = [s for s in t._writer.spans if s.context._metrics[_SAMPLING_PRIORITY_KEY] == AUTO_KEEP] sampled_ratio = len(sampled_spans) / captured_span_count diff_magnitude = abs(sampled_ratio - rate_from_agent) assert diff_magnitude < 0.3, "the proportion of sampled spans should approximate the sample rate given by the agent" diff --git a/tests/integration/test_trace_stats.py b/tests/integration/test_trace_stats.py index 46c153bc8d5..37982dbf7b2 100644 --- a/tests/integration/test_trace_stats.py +++ b/tests/integration/test_trace_stats.py @@ -7,7 +7,7 @@ from ddtrace._trace.sampler import DatadogSampler from ddtrace._trace.sampler import SamplingRule -from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.ext import http from ddtrace.internal.processor.stats import SpanStatsProcessorV06 from tests.integration.utils import AGENT_VERSION @@ -222,7 +222,7 @@ def test_measured_span(send_once_stats_tracer): for _ in range(10): with send_once_stats_tracer.trace("parent"): # Should have stats with send_once_stats_tracer.trace("child_stats") as span: # Should have stats - span.set_tag(SPAN_MEASURED_KEY) + span.set_tag(_SPAN_MEASURED_KEY) @pytest.mark.snapshot() diff --git a/tests/internal/service_name/test_processor.py b/tests/internal/service_name/test_processor.py index 02e238b8955..a765491c67c 100644 --- a/tests/internal/service_name/test_processor.py +++ b/tests/internal/service_name/test_processor.py @@ -25,7 +25,7 @@ def test_base_service(ddtrace_run_python_code_in_subprocess, schema_version, glo import sys from ddtrace import config -from ddtrace.constants import BASE_SERVICE_KEY +from ddtrace.constants import _BASE_SERVICE_KEY from ddtrace.internal.schema.processor import BaseServiceProcessor from ddtrace._trace.span import Span from tests.internal.service_name.test_processor import processor @@ -54,11 +54,11 @@ def test(processor): ] processor.process_trace(fake_trace) - assert BASE_SERVICE_KEY not in fake_trace[0].get_tags() - assert BASE_SERVICE_KEY not in fake_trace[1].get_tags(), config.service - assert fake_trace[2].get_tag(BASE_SERVICE_KEY) is not None - assert fake_trace[2].get_tag(BASE_SERVICE_KEY) == '{}' - assert BASE_SERVICE_KEY not in fake_trace[3].get_tags(), fake_trace[3].service + fake_trace[3].get_tags() + assert _BASE_SERVICE_KEY not in fake_trace[0].get_tags() + assert _BASE_SERVICE_KEY not in fake_trace[1].get_tags(), config.service + assert fake_trace[2].get_tag(_BASE_SERVICE_KEY) is not None + assert fake_trace[2].get_tag(_BASE_SERVICE_KEY) == '{}' + assert _BASE_SERVICE_KEY not in fake_trace[3].get_tags(), fake_trace[3].service + fake_trace[3].get_tags() if __name__ == "__main__": sys.exit(pytest.main(["-x", __file__])) diff --git a/tests/internal/test_module.py b/tests/internal/test_module.py index c84c2c740d6..e62440325fe 100644 --- a/tests/internal/test_module.py +++ b/tests/internal/test_module.py @@ -572,10 +572,9 @@ def __getattr__(name): assert missing_deprecations == set( [ - # Note: The following ddtrace.contrib modules are expected to be part of the public API - # TODO: Revisit whether integration utils should be part of the public API - "ddtrace.contrib.redis_utils", "ddtrace.contrib.trace_utils", + # Note: The modules below are deprecated but they do not follow the template above + "ddtrace.contrib.redis_utils", "ddtrace.contrib.trace_utils_async", "ddtrace.contrib.trace_utils_redis", ] diff --git a/tests/llmobs/_utils.py b/tests/llmobs/_utils.py index 3583516538c..8343aee530e 100644 --- a/tests/llmobs/_utils.py +++ b/tests/llmobs/_utils.py @@ -488,7 +488,7 @@ def expected_ragas_trace_tags(): "env:", "service:tests.llmobs", "source:integration", - "ml_app:dd-ragas-unnamed-ml-app", + "ml_app:unnamed-ml-app", "ddtrace.version:{}".format(ddtrace.__version__), "language:python", "error:0", diff --git a/tests/llmobs/conftest.py b/tests/llmobs/conftest.py index 61a028e5caf..e47595f0973 100644 --- a/tests/llmobs/conftest.py +++ b/tests/llmobs/conftest.py @@ -1,4 +1,9 @@ +from http.server import BaseHTTPRequestHandler +from http.server import HTTPServer +import json import os +import threading +import time import mock import pytest @@ -195,15 +200,79 @@ def llmobs_env(): class TestLLMObsSpanWriter(LLMObsSpanWriter): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - self.events = [] + self._events = [] def enqueue(self, event): - self.events.append(event) + self._events.append(event) + super().enqueue(event) + + def events(self): + return self._events @pytest.fixture -def llmobs_span_writer(): - yield TestLLMObsSpanWriter(interval=1.0, timeout=1.0) +def llmobs_span_writer(_llmobs_backend): + url, _ = _llmobs_backend + sw = TestLLMObsSpanWriter(interval=1.0, timeout=1.0, agentless_url=url) + sw._headers["DD-API-KEY"] = "" + yield sw + + +class LLMObsServer(BaseHTTPRequestHandler): + """A mock server for the LLMObs backend used to capture the requests made by the client. + + Python's HTTPRequestHandler is a bit weird and uses a class rather than an instance + for running an HTTP server so the requests are stored in a class variable and reset in the pytest fixture. + """ + + requests = [] + + def __init__(self, *args, **kwargs) -> None: + super().__init__(*args, **kwargs) + + def do_POST(self) -> None: + content_length = int(self.headers["Content-Length"]) + body = self.rfile.read(content_length).decode("utf-8") + self.requests.append({"path": self.path, "headers": dict(self.headers), "body": body}) + self.send_response(200) + self.end_headers() + self.wfile.write(b"OK") + + +@pytest.fixture +def _llmobs_backend(): + LLMObsServer.requests = [] + # Create and start the HTTP server + server = HTTPServer(("localhost", 0), LLMObsServer) + server_thread = threading.Thread(target=server.serve_forever) + server_thread.daemon = True + server_thread.start() + + # Provide the server details to the test + server_address = f"http://{server.server_address[0]}:{server.server_address[1]}" + + yield server_address, LLMObsServer.requests + + # Stop the server after the test + server.shutdown() + server.server_close() + + +@pytest.fixture +def llmobs_backend(_llmobs_backend): + _, reqs = _llmobs_backend + + class _LLMObsBackend: + def wait_for_num_events(self, num, attempts=1000): + for _ in range(attempts): + if len(reqs) == num: + return [json.loads(r["body"]) for r in reqs] + # time.sleep will yield the GIL so the server can process the request + time.sleep(0.001) + else: + raise TimeoutError(f"Expected {num} events, got {len(reqs)}") + + return _LLMObsBackend() @pytest.fixture @@ -231,4 +300,4 @@ def llmobs( @pytest.fixture def llmobs_events(llmobs, llmobs_span_writer): - return llmobs_span_writer.events + return llmobs_span_writer.events() diff --git a/tests/llmobs/test_llmobs.py b/tests/llmobs/test_llmobs.py index 6cf19fc3e2c..004b77b5764 100644 --- a/tests/llmobs/test_llmobs.py +++ b/tests/llmobs/test_llmobs.py @@ -245,3 +245,30 @@ def test_only_generate_span_events_from_llmobs_spans(tracer, llmobs_events): assert len(llmobs_events) == 2 assert llmobs_events[1] == _expected_llmobs_llm_span_event(root_span, "llm") assert llmobs_events[0] == expected_grandchild_llmobs_span + + +def test_utf_non_ascii_io(llmobs, llmobs_backend): + with llmobs.workflow() as workflow_span: + with llmobs.llm(model_name="gpt-3.5-turbo-0125") as llm_span: + llmobs.annotate(llm_span, input_data="안녕, 지금 몇 시야?") + llmobs.annotate(workflow_span, input_data="안녕, 지금 몇 시야?") + events = llmobs_backend.wait_for_num_events(num=1) + assert len(events) == 1 + assert events[0]["spans"][0]["meta"]["input"]["messages"][0]["content"] == "안녕, 지금 몇 시야?" + assert events[0]["spans"][1]["meta"]["input"]["value"] == "안녕, 지금 몇 시야?" + + +def test_non_utf8_inputs_outputs(llmobs, llmobs_backend): + """Test that latin1 encoded inputs and outputs are correctly decoded.""" + with llmobs.llm(model_name="gpt-3.5-turbo-0125") as span: + llmobs.annotate( + span, + input_data="The first Super Bowl (aka First AFL–NFL World Championship Game), was played in 1967.", + ) + + events = llmobs_backend.wait_for_num_events(num=1) + assert len(events) == 1 + assert ( + events[0]["spans"][0]["meta"]["input"]["messages"][0]["content"] + == "The first Super Bowl (aka First AFL–NFL World Championship Game), was played in 1967." + ) diff --git a/tests/llmobs/test_llmobs_service.py b/tests/llmobs/test_llmobs_service.py index dad6accdcfb..de428999147 100644 --- a/tests/llmobs/test_llmobs_service.py +++ b/tests/llmobs/test_llmobs_service.py @@ -16,6 +16,7 @@ from ddtrace.llmobs._constants import INPUT_PARAMETERS from ddtrace.llmobs._constants import INPUT_PROMPT from ddtrace.llmobs._constants import INPUT_VALUE +from ddtrace.llmobs._constants import IS_EVALUATION_SPAN from ddtrace.llmobs._constants import METADATA from ddtrace.llmobs._constants import METRICS from ddtrace.llmobs._constants import MODEL_NAME @@ -24,7 +25,6 @@ from ddtrace.llmobs._constants import OUTPUT_MESSAGES from ddtrace.llmobs._constants import OUTPUT_VALUE from ddtrace.llmobs._constants import PROPAGATED_PARENT_ID_KEY -from ddtrace.llmobs._constants import RAGAS_ML_APP_PREFIX from ddtrace.llmobs._constants import SESSION_ID from ddtrace.llmobs._constants import SPAN_KIND from ddtrace.llmobs._constants import SPAN_START_WHILE_DISABLED_WARNING @@ -1538,8 +1538,10 @@ def test_llmobs_with_evaluator_runner(llmobs, mock_llmobs_evaluator_runner): def test_llmobs_with_evaluator_runner_does_not_enqueue_evaluation_spans(mock_llmobs_evaluator_runner, llmobs): - with llmobs.llm(model_name="test_model", ml_app="{}-dummy".format(RAGAS_ML_APP_PREFIX)): - pass + with llmobs.agent(name="test") as agent: + agent._set_ctx_item(IS_EVALUATION_SPAN, True) + with llmobs.llm(model_name="test_model"): + pass time.sleep(0.1) assert llmobs._instance._evaluator_runner.enqueue.call_count == 0 diff --git a/tests/llmobs/test_llmobs_span_agent_writer.py b/tests/llmobs/test_llmobs_span_agent_writer.py index d16bb9f0e2c..55f0a56e4d5 100644 --- a/tests/llmobs/test_llmobs_span_agent_writer.py +++ b/tests/llmobs/test_llmobs_span_agent_writer.py @@ -50,7 +50,7 @@ def test_flush_queue_when_event_cause_queue_to_exceed_payload_limit( def test_truncating_oversized_events(mock_writer_logs, mock_http_writer_send_payload_response): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=1000, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=False, interval=1000, timeout=1) llmobs_span_writer.enqueue(_oversized_llm_event()) llmobs_span_writer.enqueue(_oversized_retrieval_event()) llmobs_span_writer.enqueue(_oversized_workflow_event()) diff --git a/tests/llmobs/test_llmobs_span_agentless_writer.py b/tests/llmobs/test_llmobs_span_agentless_writer.py index cac0d926a74..8a1a0697752 100644 --- a/tests/llmobs/test_llmobs_span_agentless_writer.py +++ b/tests/llmobs/test_llmobs_span_agentless_writer.py @@ -20,14 +20,14 @@ def test_writer_start(mock_writer_logs): with override_global_config(dict(_dd_api_key="foobar.baz", _dd_site=DATADOG_SITE)): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=1000, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=1000, timeout=1) llmobs_span_writer.start() mock_writer_logs.debug.assert_has_calls([mock.call("started %r to %r", "LLMObsSpanWriter", INTAKE_URL)]) def test_buffer_limit(mock_writer_logs, mock_http_writer_send_payload_response): with override_global_config(dict(_dd_api_key="foobar.baz", _dd_site=DATADOG_SITE)): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=1000, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=1000, timeout=1) for _ in range(1001): llmobs_span_writer.enqueue({}) mock_writer_logs.warning.assert_called_with( @@ -39,7 +39,7 @@ def test_flush_queue_when_event_cause_queue_to_exceed_payload_limit( mock_writer_logs, mock_http_writer_send_payload_response ): with override_global_config(dict(_dd_api_key="foobar.baz", _dd_site=DATADOG_SITE)): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=1000, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=1000, timeout=1) llmobs_span_writer.enqueue(_large_event()) llmobs_span_writer.enqueue(_large_event()) llmobs_span_writer.enqueue(_large_event()) @@ -56,7 +56,7 @@ def test_flush_queue_when_event_cause_queue_to_exceed_payload_limit( def test_truncating_oversized_events(mock_writer_logs, mock_http_writer_send_payload_response): with override_global_config(dict(_dd_api_key="foobar.baz", _dd_site=DATADOG_SITE)): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=1000, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=1000, timeout=1) llmobs_span_writer.enqueue(_oversized_llm_event()) llmobs_span_writer.enqueue(_oversized_retrieval_event()) llmobs_span_writer.enqueue(_oversized_workflow_event()) @@ -77,7 +77,7 @@ def test_truncating_oversized_events(mock_writer_logs, mock_http_writer_send_pay def test_send_completion_event(mock_writer_logs, mock_http_writer_send_payload_response): with override_global_config(dict(_dd_site=DATADOG_SITE, _dd_api_key="foobar.baz")): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=1, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=1, timeout=1) llmobs_span_writer.start() llmobs_span_writer.enqueue(_completion_event()) llmobs_span_writer.periodic() @@ -86,7 +86,7 @@ def test_send_completion_event(mock_writer_logs, mock_http_writer_send_payload_r def test_send_chat_completion_event(mock_writer_logs, mock_http_writer_send_payload_response): with override_global_config(dict(_dd_site=DATADOG_SITE, _dd_api_key="foobar.baz")): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=1, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=1, timeout=1) llmobs_span_writer.start() llmobs_span_writer.enqueue(_chat_completion_event()) llmobs_span_writer.periodic() @@ -96,7 +96,7 @@ def test_send_chat_completion_event(mock_writer_logs, mock_http_writer_send_payl @mock.patch("ddtrace.internal.writer.writer.log") def test_send_completion_bad_api_key(mock_http_writer_logs, mock_http_writer_put_response_forbidden): with override_global_config(dict(_dd_site=DATADOG_SITE, _dd_api_key="")): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=1, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=1, timeout=1) llmobs_span_writer.start() llmobs_span_writer.enqueue(_completion_event()) llmobs_span_writer.periodic() @@ -110,7 +110,7 @@ def test_send_completion_bad_api_key(mock_http_writer_logs, mock_http_writer_put def test_send_timed_events(mock_writer_logs, mock_http_writer_send_payload_response): with override_global_config(dict(_dd_site=DATADOG_SITE, _dd_api_key="foobar.baz")): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=0.01, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=0.01, timeout=1) llmobs_span_writer.start() mock_writer_logs.reset_mock() @@ -125,7 +125,7 @@ def test_send_timed_events(mock_writer_logs, mock_http_writer_send_payload_respo def test_send_multiple_events(mock_writer_logs, mock_http_writer_send_payload_response): with override_global_config(dict(_dd_site=DATADOG_SITE, _dd_api_key="foobar.baz")): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=0.01, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=0.01, timeout=1) llmobs_span_writer.start() mock_writer_logs.reset_mock() @@ -156,6 +156,7 @@ def test_send_on_exit(run_python_code_in_subprocess): from ddtrace.internal.utils.http import Response from ddtrace.llmobs._writer import LLMObsSpanWriter +from tests.llmobs.test_llmobs_span_agentless_writer import INTAKE_URL from tests.llmobs.test_llmobs_span_agentless_writer import _completion_event with mock.patch( @@ -165,7 +166,7 @@ def test_send_on_exit(run_python_code_in_subprocess): body="{}", ), ): - llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, interval=0.01, timeout=1) + llmobs_span_writer = LLMObsSpanWriter(is_agentless=True, agentless_url=INTAKE_URL, interval=0.01, timeout=1) llmobs_span_writer.start() llmobs_span_writer.enqueue(_completion_event()) """, diff --git a/tests/profiling_v2/collector/test_stack.py b/tests/profiling_v2/collector/test_stack.py index 774e15fb70d..03d5b1e7eff 100644 --- a/tests/profiling_v2/collector/test_stack.py +++ b/tests/profiling_v2/collector/test_stack.py @@ -29,7 +29,6 @@ env=dict( DD_PROFILING_MAX_FRAMES="5", DD_PROFILING_OUTPUT_PPROF="/tmp/test_collect_truncate", - DD_PROFILING_STACK_V2_ENABLED="1", ) ) @pytest.mark.skipif(sys.version_info[:2] == (3, 7), reason="stack_v2 is not supported on Python 3.7") @@ -61,9 +60,8 @@ def test_collect_truncate(): assert len(sample.location_id) <= max_nframes + 2, len(sample.location_id) -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_stack_locations(stack_v2_enabled, tmp_path): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_stack_locations(tmp_path): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") test_name = "test_stack_locations" @@ -83,7 +81,7 @@ def bar(): def foo(): bar() - with stack.StackCollector(None, _stack_collector_v2_enabled=stack_v2_enabled): + with stack.StackCollector(None, _stack_collector_v2_enabled=True): for _ in range(10): foo() ddup.upload() @@ -117,9 +115,8 @@ def foo(): pprof_utils.assert_profile_has_sample(profile, samples=samples, expected_sample=expected_sample) -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_push_span(stack_v2_enabled, tmp_path, tracer): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_push_span(tmp_path, tracer): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") test_name = "test_push_span" @@ -140,7 +137,7 @@ def test_push_span(stack_v2_enabled, tmp_path, tracer): tracer=tracer, endpoint_collection_enabled=True, ignore_profiler=True, # this is not necessary, but it's here to trim samples - _stack_collector_v2_enabled=stack_v2_enabled, + _stack_collector_v2_enabled=True, ): with tracer.trace("foobar", resource=resource, span_type=span_type) as span: span_id = span.span_id @@ -221,9 +218,8 @@ def target_fun(): unregister_thread.assert_called_with(thread_id) -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_push_non_web_span(stack_v2_enabled, tmp_path, tracer): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_push_non_web_span(tmp_path, tracer): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") tracer._endpoint_call_counter_span_processor.enable() @@ -244,7 +240,7 @@ def test_push_non_web_span(stack_v2_enabled, tmp_path, tracer): tracer=tracer, endpoint_collection_enabled=True, ignore_profiler=True, # this is not necessary, but it's here to trim samples - _stack_collector_v2_enabled=stack_v2_enabled, + _stack_collector_v2_enabled=True, ): with tracer.trace("foobar", resource=resource, span_type=span_type) as span: span_id = span.span_id @@ -269,10 +265,9 @@ def test_push_non_web_span(stack_v2_enabled, tmp_path, tracer): ) -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_push_span_none_span_type(stack_v2_enabled, tmp_path, tracer): +def test_push_span_none_span_type(tmp_path, tracer): # Test for https://github.com/DataDog/dd-trace-py/issues/11141 - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") test_name = "test_push_span_none_span_type" @@ -292,7 +287,7 @@ def test_push_span_none_span_type(stack_v2_enabled, tmp_path, tracer): tracer=tracer, endpoint_collection_enabled=True, ignore_profiler=True, # this is not necessary, but it's here to trim samples - _stack_collector_v2_enabled=stack_v2_enabled, + _stack_collector_v2_enabled=True, ): # Explicitly set None span_type as the default could change in the # future. @@ -484,9 +479,8 @@ def test_exception_collection_trace(stack_v2_enabled, tmp_path, tracer): ) -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_collect_once_with_class(stack_v2_enabled, tmp_path): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_collect_once_with_class(tmp_path): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") class SomeClass(object): @@ -506,7 +500,7 @@ def sleep_instance(self): ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) ddup.start() - with stack.StackCollector(None, ignore_profiler=True, _stack_collector_v2_enabled=stack_v2_enabled): + with stack.StackCollector(None, ignore_profiler=True, _stack_collector_v2_enabled=True): SomeClass.sleep_class() ddup.upload() @@ -521,7 +515,6 @@ def sleep_instance(self): expected_sample=pprof_utils.StackEvent( thread_id=_thread.get_ident(), thread_name="MainThread", - class_name="SomeClass" if not stack_v2_enabled else None, locations=[ pprof_utils.StackLocation( function_name="sleep_instance", @@ -536,16 +529,15 @@ def sleep_instance(self): pprof_utils.StackLocation( function_name="test_collect_once_with_class", filename="test_stack.py", - line_no=test_collect_once_with_class.__code__.co_firstlineno + 23, + line_no=test_collect_once_with_class.__code__.co_firstlineno + 22, ), ], ), ) -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_collect_once_with_class_not_right_type(stack_v2_enabled, tmp_path): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_collect_once_with_class_not_right_type(tmp_path): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") class SomeClass(object): @@ -565,7 +557,7 @@ def sleep_instance(foobar, self): ddup.config(env="test", service=test_name, version="my_version", output_filename=pprof_prefix) ddup.start() - with stack.StackCollector(None, ignore_profiler=True, _stack_collector_v2_enabled=stack_v2_enabled): + with stack.StackCollector(None, ignore_profiler=True, _stack_collector_v2_enabled=True): SomeClass.sleep_class(123) ddup.upload() @@ -580,9 +572,6 @@ def sleep_instance(foobar, self): expected_sample=pprof_utils.StackEvent( thread_id=_thread.get_ident(), thread_name="MainThread", - # stack v1 relied on using cls and self to figure out class name - # so we can't find it here. - class_name=None, locations=[ pprof_utils.StackLocation( function_name="sleep_instance", @@ -597,7 +586,7 @@ def sleep_instance(foobar, self): pprof_utils.StackLocation( function_name="test_collect_once_with_class_not_right_type", filename="test_stack.py", - line_no=test_collect_once_with_class_not_right_type.__code__.co_firstlineno + 23, + line_no=test_collect_once_with_class_not_right_type.__code__.co_firstlineno + 22, ), ], ), diff --git a/tests/profiling_v2/collector/test_stack_asyncio.py b/tests/profiling_v2/collector/test_stack_asyncio.py index 791cceb4080..d28ad54e586 100644 --- a/tests/profiling_v2/collector/test_stack_asyncio.py +++ b/tests/profiling_v2/collector/test_stack_asyncio.py @@ -7,7 +7,6 @@ @pytest.mark.subprocess( env=dict( DD_PROFILING_OUTPUT_PPROF="/tmp/test_stack_asyncio", - DD_PROFILING_STACK_V2_ENABLED="true", ), ) def test_asyncio(): diff --git a/tests/profiling_v2/test_accuracy.py b/tests/profiling_v2/test_accuracy.py index cb1d538712f..a9239c19010 100644 --- a/tests/profiling_v2/test_accuracy.py +++ b/tests/profiling_v2/test_accuracy.py @@ -4,57 +4,7 @@ import pytest -@pytest.mark.subprocess( - env=dict(DD_PROFILING_MAX_TIME_USAGE_PCT="100", DD_PROFILING_OUTPUT_PPROF="/tmp/test_accuracy_libdd.pprof") -) -def test_accuracy_libdd(): - import collections - import os - - from ddtrace.profiling import profiler - from tests.profiling.collector import pprof_utils - from tests.profiling.test_accuracy import assert_almost_equal - from tests.profiling.test_accuracy import spend_16 - - # Set this to 100 so we don't sleep too often and mess with the precision. - p = profiler.Profiler() - p.start() - spend_16() - p.stop() - wall_times = collections.defaultdict(lambda: 0) - cpu_times = collections.defaultdict(lambda: 0) - profile = pprof_utils.parse_profile(os.environ["DD_PROFILING_OUTPUT_PPROF"] + "." + str(os.getpid())) - - for sample in profile.sample: - wall_time_index = pprof_utils.get_sample_type_index(profile, "wall-time") - - wall_time_spent_ns = sample.value[wall_time_index] - cpu_time_index = pprof_utils.get_sample_type_index(profile, "cpu-time") - cpu_time_spent_ns = sample.value[cpu_time_index] - - for location_id in sample.location_id: - location = pprof_utils.get_location_with_id(profile, location_id) - line = location.line[0] - function = pprof_utils.get_function_with_id(profile, line.function_id) - function_name = profile.string_table[function.name] - wall_times[function_name] += wall_time_spent_ns - cpu_times[function_name] += cpu_time_spent_ns - - assert_almost_equal(wall_times["spend_3"], 9e9) - assert_almost_equal(wall_times["spend_1"], 2e9) - assert_almost_equal(wall_times["spend_4"], 4e9) - assert_almost_equal(wall_times["spend_16"], 16e9) - assert_almost_equal(wall_times["spend_7"], 7e9) - - assert_almost_equal(wall_times["spend_cpu_2"], 2e9, tolerance=0.09) - assert_almost_equal(wall_times["spend_cpu_3"], 3e9, tolerance=0.09) - assert_almost_equal(cpu_times["spend_cpu_2"], 2e9, tolerance=0.09) - assert_almost_equal(cpu_times["spend_cpu_3"], 3e9, tolerance=0.09) - - -@pytest.mark.subprocess( - env=dict(DD_PROFILING_STACK_V2_ENABLED="1", DD_PROFILING_OUTPUT_PPROF="/tmp/test_accuracy_stack_v2.pprof") -) +@pytest.mark.subprocess(env=dict(DD_PROFILING_OUTPUT_PPROF="/tmp/test_accuracy_stack_v2.pprof")) @pytest.mark.skipif(sys.version_info[:2] == (3, 7), reason="stack_v2 is not supported on Python 3.7") def test_accuracy_stack_v2(): import collections diff --git a/tests/profiling_v2/test_gunicorn.py b/tests/profiling_v2/test_gunicorn.py index 90141445d3a..0ff02bbf177 100644 --- a/tests/profiling_v2/test_gunicorn.py +++ b/tests/profiling_v2/test_gunicorn.py @@ -52,9 +52,6 @@ def _run_gunicorn(*args): def gunicorn(monkeypatch): monkeypatch.setenv("DD_PROFILING_IGNORE_PROFILER", "1") monkeypatch.setenv("DD_PROFILING_ENABLED", "1") - # This was needed for the gunicorn process to start and print worker startup - # messages. Without this, the test can't find the worker PIDs. - monkeypatch.setenv("DD_PROFILING_STACK_V2_ENABLED", "1") yield _run_gunicorn diff --git a/tests/profiling_v2/test_main.py b/tests/profiling_v2/test_main.py index 3142a1fbba8..132fc8aa502 100644 --- a/tests/profiling_v2/test_main.py +++ b/tests/profiling_v2/test_main.py @@ -11,13 +11,11 @@ from tests.utils import flaky -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_call_script(stack_v2_enabled): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_call_script(): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") env = os.environ.copy() env["DD_PROFILING_ENABLED"] = "1" - env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" stdout, stderr, exitcode, _ = call_program( "ddtrace-run", sys.executable, os.path.join(os.path.dirname(__file__), "simple_program.py"), env=env ) @@ -28,28 +26,25 @@ def test_call_script(stack_v2_enabled): hello, interval, pid, stack_v2 = list(s.strip() for s in stdout.decode().strip().split("\n")) assert hello == "hello world", stdout.decode().strip() assert float(interval) >= 0.01, stdout.decode().strip() - assert stack_v2 == str(stack_v2_enabled) + assert stack_v2 == str(True) @pytest.mark.skipif(not os.getenv("DD_PROFILE_TEST_GEVENT", False), reason="Not testing gevent") -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_call_script_gevent(stack_v2_enabled): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_call_script_gevent(): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") - if sys.version_info[:2] == (3, 8) and stack_v2_enabled: + if sys.version_info[:2] == (3, 8): pytest.skip("this test is flaky on 3.8 with stack v2") env = os.environ.copy() env["DD_PROFILING_ENABLED"] = "1" - env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" stdout, stderr, exitcode, pid = call_program( sys.executable, os.path.join(os.path.dirname(__file__), "simple_program_gevent.py"), env=env ) assert exitcode == 0, (stdout, stderr) -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) -def test_call_script_pprof_output(stack_v2_enabled, tmp_path): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_call_script_pprof_output(tmp_path): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") """This checks if the pprof output and atexit register work correctly. @@ -61,7 +56,6 @@ def test_call_script_pprof_output(stack_v2_enabled, tmp_path): env["DD_PROFILING_OUTPUT_PPROF"] = filename env["DD_PROFILING_CAPTURE_PCT"] = "1" env["DD_PROFILING_ENABLED"] = "1" - env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" stdout, stderr, exitcode, _ = call_program( "ddtrace-run", sys.executable, @@ -78,17 +72,15 @@ def test_call_script_pprof_output(stack_v2_enabled, tmp_path): assert len(samples) > 0 -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) @pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") -def test_fork(stack_v2_enabled, tmp_path): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_fork(tmp_path): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") filename = str(tmp_path / "pprof") env = os.environ.copy() env["DD_PROFILING_OUTPUT_PPROF"] = filename env["DD_PROFILING_CAPTURE_PCT"] = "100" - env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" stdout, stderr, exitcode, pid = call_program( "python", os.path.join(os.path.dirname(__file__), "simple_program_fork.py"), env=env ) @@ -144,14 +136,12 @@ def test_fork(stack_v2_enabled, tmp_path): ) -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) @pytest.mark.skipif(sys.platform == "win32", reason="fork only available on Unix") @pytest.mark.skipif(not os.getenv("DD_PROFILE_TEST_GEVENT", False), reason="Not testing gevent") -def test_fork_gevent(stack_v2_enabled): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_fork_gevent(): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") env = os.environ.copy() - env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" stdout, stderr, exitcode, pid = call_program( "python", os.path.join(os.path.dirname(__file__), "../profiling", "gevent_fork.py"), env=env ) @@ -161,20 +151,18 @@ def test_fork_gevent(stack_v2_enabled): methods = multiprocessing.get_all_start_methods() -@pytest.mark.parametrize("stack_v2_enabled", [True, False]) @pytest.mark.parametrize( "method", set(methods) - {"forkserver", "fork"}, ) -def test_multiprocessing(stack_v2_enabled, method, tmp_path): - if sys.version_info[:2] == (3, 7) and stack_v2_enabled: +def test_multiprocessing(method, tmp_path): + if sys.version_info[:2] == (3, 7): pytest.skip("stack_v2 is not supported on Python 3.7") filename = str(tmp_path / "pprof") env = os.environ.copy() env["DD_PROFILING_OUTPUT_PPROF"] = filename env["DD_PROFILING_ENABLED"] = "1" env["DD_PROFILING_CAPTURE_PCT"] = "1" - env["DD_PROFILING_STACK_V2_ENABLED"] = "1" if stack_v2_enabled else "0" stdout, stderr, exitcode, _ = call_program( "ddtrace-run", sys.executable, @@ -208,8 +196,6 @@ def test_memalloc_no_init_error_on_fork(): os.waitpid(pid, 0) -# Not parametrizing with stack_v2_enabled as subprocess mark doesn't support -# parametrized tests and this only tests our start up code. @pytest.mark.subprocess( ddtrace_run=True, env=dict( diff --git a/tests/suitespec.yml b/tests/suitespec.yml index 41fabd7aa88..b135ba986c8 100644 --- a/tests/suitespec.yml +++ b/tests/suitespec.yml @@ -62,6 +62,7 @@ components: - ddtrace/internal/logger.py - ddtrace/internal/metrics.py - ddtrace/internal/module.py + - ddtrace/internal/native/* - ddtrace/internal/packages.py - ddtrace/internal/third-party.tar.gz - ddtrace/internal/periodic.py @@ -76,7 +77,7 @@ components: - ddtrace/py.typed - ddtrace/version.py - ddtrace/settings/config.py - - src/core/* + - src/native/* datastreams: - ddtrace/internal/datastreams/* - ddtrace/data_streams.py diff --git a/tests/telemetry/test_writer.py b/tests/telemetry/test_writer.py index 8d4030c84a9..228b6fd4ea1 100644 --- a/tests/telemetry/test_writer.py +++ b/tests/telemetry/test_writer.py @@ -354,9 +354,11 @@ def test_app_started_event_configuration_override(test_agent_session, run_python {"name": "DD_DYNAMIC_INSTRUMENTATION_UPLOAD_FLUSH_INTERVAL", "origin": "default", "value": 1.0}, {"name": "DD_DYNAMIC_INSTRUMENTATION_UPLOAD_TIMEOUT", "origin": "default", "value": 30}, {"name": "DD_ENV", "origin": "default", "value": None}, + {"name": "DD_EXCEPTION_REPLAY_CAPTURE_MAX_FRAMES", "origin": "default", "value": 8}, {"name": "DD_EXCEPTION_REPLAY_ENABLED", "origin": "env_var", "value": True}, {"name": "DD_EXPERIMENTAL_APPSEC_STANDALONE_ENABLED", "origin": "default", "value": False}, {"name": "DD_HTTP_CLIENT_TAG_QUERY_STRING", "origin": "default", "value": None}, + {"name": "DD_IAST_DEDUPLICATION_ENABLED", "origin": "default", "value": True}, {"name": "DD_IAST_ENABLED", "origin": "default", "value": False}, {"name": "DD_IAST_MAX_CONCURRENT_REQUESTS", "origin": "default", "value": 2}, {"name": "DD_IAST_REDACTION_ENABLED", "origin": "default", "value": True}, diff --git a/tests/tracer/test_encoders.py b/tests/tracer/test_encoders.py index f96e063502e..00321a59924 100644 --- a/tests/tracer/test_encoders.py +++ b/tests/tracer/test_encoders.py @@ -21,7 +21,7 @@ from ddtrace._trace._span_pointer import _SpanPointerDirection from ddtrace._trace.context import Context from ddtrace._trace.span import Span -from ddtrace.constants import ORIGIN_KEY +from ddtrace.constants import _ORIGIN_KEY as ORIGIN_KEY from ddtrace.ext import SpanTypes from ddtrace.ext.ci import CI_APP_TEST_ORIGIN from ddtrace.internal._encoding import BufferFull diff --git a/tests/tracer/test_processors.py b/tests/tracer/test_processors.py index f7bf413d916..ad9360deec2 100644 --- a/tests/tracer/test_processors.py +++ b/tests/tracer/test_processors.py @@ -12,13 +12,13 @@ from ddtrace._trace.processor import TraceTagsProcessor from ddtrace._trace.sampler import DatadogSampler from ddtrace._trace.span import Span +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import _SINGLE_SPAN_SAMPLING_MAX_PER_SEC from ddtrace.constants import _SINGLE_SPAN_SAMPLING_MECHANISM from ddtrace.constants import _SINGLE_SPAN_SAMPLING_RATE from ddtrace.constants import AUTO_KEEP from ddtrace.constants import AUTO_REJECT from ddtrace.constants import MANUAL_KEEP_KEY -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.constants import USER_KEEP from ddtrace.constants import USER_REJECT from ddtrace.ext import SpanTypes @@ -581,7 +581,7 @@ def assert_span_sampling_decision_tags( assert span.get_metric(_SINGLE_SPAN_SAMPLING_MAX_PER_SEC) == limit if trace_sampling_priority: - assert span.get_metric(SAMPLING_PRIORITY_KEY) == trace_sampling_priority + assert span.get_metric(_SAMPLING_PRIORITY_KEY) == trace_sampling_priority def switch_out_trace_sampling_processor(tracer, sampling_processor): diff --git a/tests/tracer/test_sampler.py b/tests/tracer/test_sampler.py index 54c9c1abef3..4bf9de2019e 100644 --- a/tests/tracer/test_sampler.py +++ b/tests/tracer/test_sampler.py @@ -12,12 +12,12 @@ from ddtrace._trace.sampler import RateSampler from ddtrace._trace.sampling_rule import SamplingRule from ddtrace._trace.span import Span +from ddtrace.constants import _SAMPLING_AGENT_DECISION +from ddtrace.constants import _SAMPLING_LIMIT_DECISION +from ddtrace.constants import _SAMPLING_PRIORITY_KEY +from ddtrace.constants import _SAMPLING_RULE_DECISION from ddtrace.constants import AUTO_KEEP from ddtrace.constants import AUTO_REJECT -from ddtrace.constants import SAMPLING_AGENT_DECISION -from ddtrace.constants import SAMPLING_LIMIT_DECISION -from ddtrace.constants import SAMPLING_PRIORITY_KEY -from ddtrace.constants import SAMPLING_RULE_DECISION from ddtrace.constants import USER_KEEP from ddtrace.constants import USER_REJECT from ddtrace.internal.rate_limiter import RateLimiter @@ -51,10 +51,10 @@ def assert_sampling_decision_tags( :param sampling_priority: expected sampling priority ``_sampling_priority_v1`` :param trace_tag: expected sampling decision trace tag ``_dd.p.dm``. Format is ``-{SAMPLINGMECHANISM}``. """ - metric_agent = span.get_metric(SAMPLING_AGENT_DECISION) - metric_limit = span.get_metric(SAMPLING_LIMIT_DECISION) - metric_rule = span.get_metric(SAMPLING_RULE_DECISION) - metric_sampling_priority = span.get_metric(SAMPLING_PRIORITY_KEY) + metric_agent = span.get_metric(_SAMPLING_AGENT_DECISION) + metric_limit = span.get_metric(_SAMPLING_LIMIT_DECISION) + metric_rule = span.get_metric(_SAMPLING_RULE_DECISION) + metric_sampling_priority = span.get_metric(_SAMPLING_PRIORITY_KEY) if agent: assert metric_agent == agent if limit: diff --git a/tests/tracer/test_single_span_sampling_rules.py b/tests/tracer/test_single_span_sampling_rules.py index 3ebffed00d5..24dfda91ad5 100644 --- a/tests/tracer/test_single_span_sampling_rules.py +++ b/tests/tracer/test_single_span_sampling_rules.py @@ -3,10 +3,10 @@ import pytest from ddtrace import Tracer +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import _SINGLE_SPAN_SAMPLING_MAX_PER_SEC from ddtrace.constants import _SINGLE_SPAN_SAMPLING_MECHANISM from ddtrace.constants import _SINGLE_SPAN_SAMPLING_RATE -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.internal.sampling import SamplingMechanism from ddtrace.internal.sampling import SpanSamplingRule from ddtrace.internal.sampling import _get_file_json @@ -41,7 +41,7 @@ def assert_sampling_decision_tags( assert span.get_metric(_SINGLE_SPAN_SAMPLING_MAX_PER_SEC) == limit if trace_sampling: - assert span.get_metric(SAMPLING_PRIORITY_KEY) > 0 + assert span.get_metric(_SAMPLING_PRIORITY_KEY) > 0 def test_single_rule_init_via_env(): diff --git a/tests/tracer/test_span.py b/tests/tracer/test_span.py index 8cdaad831f0..1725f0d7675 100644 --- a/tests/tracer/test_span.py +++ b/tests/tracer/test_span.py @@ -11,12 +11,12 @@ from ddtrace._trace._span_link import SpanLink from ddtrace._trace._span_pointer import _SpanPointerDirection from ddtrace._trace.span import Span +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.constants import ENV_KEY from ddtrace.constants import ERROR_MSG from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE from ddtrace.constants import SERVICE_VERSION_KEY -from ddtrace.constants import SPAN_MEASURED_KEY from ddtrace.constants import VERSION_KEY from ddtrace.ext import SpanTypes from ddtrace.internal import core @@ -552,7 +552,7 @@ def test_span_pointers(self): ) def test_set_tag_measured(value, assertion): s = Span(name="test.span") - s.set_tag(SPAN_MEASURED_KEY, value) + s.set_tag(_SPAN_MEASURED_KEY, value) assertion(s) @@ -564,19 +564,19 @@ def test_set_tag_measured_not_set(): def test_set_tag_measured_no_value(): s = Span(name="test.span") - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) assert_is_measured(s) def test_set_tag_measured_change_value(): s = Span(name="test.span") - s.set_tag(SPAN_MEASURED_KEY, True) + s.set_tag(_SPAN_MEASURED_KEY, True) assert_is_measured(s) - s.set_tag(SPAN_MEASURED_KEY, False) + s.set_tag(_SPAN_MEASURED_KEY, False) assert_is_not_measured(s) - s.set_tag(SPAN_MEASURED_KEY) + s.set_tag(_SPAN_MEASURED_KEY) assert_is_measured(s) diff --git a/tests/tracer/test_trace_utils.py b/tests/tracer/test_trace_utils.py index a7604636b62..38ba097f308 100644 --- a/tests/tracer/test_trace_utils.py +++ b/tests/tracer/test_trace_utils.py @@ -17,8 +17,8 @@ from ddtrace import config from ddtrace._trace.context import Context from ddtrace._trace.span import Span -from ddtrace.contrib import trace_utils -from ddtrace.contrib.trace_utils import _get_request_header_client_ip +from ddtrace.contrib.internal import trace_utils +from ddtrace.contrib.internal.trace_utils import _get_request_header_client_ip from ddtrace.ext import SpanTypes from ddtrace.ext import http from ddtrace.ext import net @@ -316,7 +316,7 @@ def test_ext_service(int_config, pin, config_val, default, expected): def test_set_http_meta_with_http_header_tags_config(): from ddtrace import config from ddtrace._trace.span import Span - from ddtrace.contrib.trace_utils import set_http_meta + from ddtrace.contrib.internal.trace_utils import set_http_meta assert config._trace_http_header_tags == { "header1": "", @@ -512,7 +512,7 @@ def test_set_http_meta_custom_errors(mock_log, span, int_config, error_codes, st def test_set_http_meta_custom_errors_via_env(): from ddtrace import config from ddtrace import tracer - from ddtrace.contrib.trace_utils import set_http_meta + from ddtrace.contrib.internal.trace_utils import set_http_meta config._add("myint", dict()) with tracer.trace("error") as span1: @@ -528,7 +528,7 @@ def test_set_http_meta_custom_errors_via_env(): assert span3.error == 0 -@mock.patch("ddtrace.contrib.trace_utils._store_headers") +@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers") def test_set_http_meta_no_headers(mock_store_headers, span, int_config): assert int_config.myint.is_header_tracing_configured is False trace_utils.set_http_meta( @@ -543,7 +543,7 @@ def test_set_http_meta_no_headers(mock_store_headers, span, int_config): mock_store_headers.assert_not_called() -@mock.patch("ddtrace.contrib.trace_utils._store_headers") +@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers") @pytest.mark.parametrize( "user_agent_key,user_agent_value,expected_keys,expected", [ @@ -572,7 +572,7 @@ def test_set_http_meta_headers_useragent( mock_store_headers.assert_called() -@mock.patch("ddtrace.contrib.trace_utils._store_headers") +@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers") def test_set_http_meta_case_sensitive_headers(mock_store_headers, span, int_config): int_config.myint.http._header_tags = {"enabled": True} trace_utils.set_http_meta( @@ -585,7 +585,7 @@ def test_set_http_meta_case_sensitive_headers(mock_store_headers, span, int_conf mock_store_headers.assert_called() -@mock.patch("ddtrace.contrib.trace_utils._store_headers") +@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers") def test_set_http_meta_case_sensitive_headers_notfound(mock_store_headers, span, int_config): int_config.myint.http._header_tags = {"enabled": True} trace_utils.set_http_meta( @@ -810,7 +810,7 @@ def test_ip_subnet_regression(): assert not ip_network(req_ip).subnet_of(ip_network(del_ip)) -@mock.patch("ddtrace.contrib.trace_utils._store_headers") +@mock.patch("ddtrace.contrib.internal.trace_utils._store_headers") @pytest.mark.parametrize( "user_agent_value, expected_keys ,expected", [ @@ -835,7 +835,7 @@ def test_set_http_meta_headers_useragent( # noqa:F811 mock_store_headers.assert_not_called() -@mock.patch("ddtrace.contrib.trace_utils.log") +@mock.patch("ddtrace.contrib.internal.trace_utils.log") @pytest.mark.parametrize( "val, bad", [ @@ -1042,7 +1042,7 @@ def test_sanitized_url_in_http_meta(span, int_config): def test_url_in_http_with_empty_obfuscation_regex(): from ddtrace import config from ddtrace import tracer - from ddtrace.contrib.trace_utils import set_http_meta + from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.ext import http assert config._obfuscation_query_string_pattern.pattern == b"", config._obfuscation_query_string_pattern @@ -1067,7 +1067,7 @@ def test_url_in_http_with_obfuscation_enabled_and_empty_regex(): # and obfuscation is enabled (not disabled xD) from ddtrace import config from ddtrace import tracer - from ddtrace.contrib.trace_utils import set_http_meta + from ddtrace.contrib.internal.trace_utils import set_http_meta from ddtrace.ext import http # assert obfuscation is disabled when the regex is an empty string diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index 451e465b5a9..1c45f424679 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -18,19 +18,19 @@ from ddtrace._trace.context import Context from ddtrace._trace.span import _is_top_level from ddtrace._trace.tracer import Tracer +from ddtrace.constants import _HOSTNAME_KEY +from ddtrace.constants import _ORIGIN_KEY +from ddtrace.constants import _SAMPLING_PRIORITY_KEY from ddtrace.constants import AUTO_KEEP from ddtrace.constants import AUTO_REJECT from ddtrace.constants import ENV_KEY -from ddtrace.constants import HOSTNAME_KEY from ddtrace.constants import MANUAL_DROP_KEY from ddtrace.constants import MANUAL_KEEP_KEY -from ddtrace.constants import ORIGIN_KEY from ddtrace.constants import PID -from ddtrace.constants import SAMPLING_PRIORITY_KEY from ddtrace.constants import USER_KEEP from ddtrace.constants import USER_REJECT from ddtrace.constants import VERSION_KEY -from ddtrace.contrib.trace_utils import set_user +from ddtrace.contrib.internal.trace_utils import set_user from ddtrace.ext import user from ddtrace.internal._encoding import MsgpackEncoderV04 from ddtrace.internal._encoding import MsgpackEncoderV05 @@ -1459,9 +1459,9 @@ def test_ctx(tracer, test_spans): assert s3.parent_id == s2.span_id assert s4.parent_id == s1.span_id assert s1.trace_id == s2.trace_id == s3.trace_id == s4.trace_id - assert s1.get_metric(SAMPLING_PRIORITY_KEY) == 1 - assert s2.get_metric(SAMPLING_PRIORITY_KEY) is None - assert ORIGIN_KEY not in s1.get_tags() + assert s1.get_metric(_SAMPLING_PRIORITY_KEY) == 1 + assert s2.get_metric(_SAMPLING_PRIORITY_KEY) is None + assert _ORIGIN_KEY not in s1.get_tags() t = test_spans.pop_traces() assert len(t) == 1 @@ -1535,8 +1535,8 @@ def test_ctx_distributed(tracer, test_spans): trace = test_spans.pop_traces() assert len(trace) == 1 - assert s2.get_metric(SAMPLING_PRIORITY_KEY) == 2 - assert s2.get_tag(ORIGIN_KEY) == "somewhere" + assert s2.get_metric(_SAMPLING_PRIORITY_KEY) == 2 + assert s2.get_tag(_ORIGIN_KEY) == "somewhere" def test_manual_keep(tracer, test_spans): @@ -1544,14 +1544,14 @@ def test_manual_keep(tracer, test_spans): with tracer.trace("asdf") as s: s.set_tag(MANUAL_KEEP_KEY) spans = test_spans.pop() - assert spans[0].get_metric(SAMPLING_PRIORITY_KEY) is USER_KEEP + assert spans[0].get_metric(_SAMPLING_PRIORITY_KEY) is USER_KEEP # On a child span with tracer.trace("asdf"): with tracer.trace("child") as s: s.set_tag(MANUAL_KEEP_KEY) spans = test_spans.pop() - assert spans[0].get_metric(SAMPLING_PRIORITY_KEY) is USER_KEEP + assert spans[0].get_metric(_SAMPLING_PRIORITY_KEY) is USER_KEEP def test_manual_keep_then_drop(tracer, test_spans): @@ -1561,7 +1561,7 @@ def test_manual_keep_then_drop(tracer, test_spans): child.set_tag(MANUAL_KEEP_KEY) root.set_tag(MANUAL_DROP_KEY) spans = test_spans.pop() - assert spans[0].get_metric(SAMPLING_PRIORITY_KEY) is USER_REJECT + assert spans[0].get_metric(_SAMPLING_PRIORITY_KEY) is USER_REJECT def test_manual_drop(tracer, test_spans): @@ -1569,14 +1569,14 @@ def test_manual_drop(tracer, test_spans): with tracer.trace("asdf") as s: s.set_tag(MANUAL_DROP_KEY) spans = test_spans.pop() - assert spans[0].get_metric(SAMPLING_PRIORITY_KEY) is USER_REJECT + assert spans[0].get_metric(_SAMPLING_PRIORITY_KEY) is USER_REJECT # On a child span with tracer.trace("asdf"): with tracer.trace("child") as s: s.set_tag(MANUAL_DROP_KEY) spans = test_spans.pop() - assert spans[0].get_metric(SAMPLING_PRIORITY_KEY) is USER_REJECT + assert spans[0].get_metric(_SAMPLING_PRIORITY_KEY) is USER_REJECT @mock.patch("ddtrace.internal.hostname.get_hostname") @@ -1590,8 +1590,8 @@ def test_get_report_hostname_enabled(get_hostname, tracer, test_spans): spans = test_spans.pop() root = spans[0] child = spans[1] - assert root.get_tag(HOSTNAME_KEY) == "test-hostname" - assert child.get_tag(HOSTNAME_KEY) is None + assert root.get_tag(_HOSTNAME_KEY) == "test-hostname" + assert child.get_tag(_HOSTNAME_KEY) is None @mock.patch("ddtrace.internal.hostname.get_hostname") @@ -1605,8 +1605,8 @@ def test_get_report_hostname_disabled(get_hostname, tracer, test_spans): spans = test_spans.pop() root = spans[0] child = spans[1] - assert root.get_tag(HOSTNAME_KEY) is None - assert child.get_tag(HOSTNAME_KEY) is None + assert root.get_tag(_HOSTNAME_KEY) is None + assert child.get_tag(_HOSTNAME_KEY) is None @mock.patch("ddtrace.internal.hostname.get_hostname") @@ -1620,8 +1620,8 @@ def test_get_report_hostname_default(get_hostname, tracer, test_spans): spans = test_spans.pop() root = spans[0] child = spans[1] - assert root.get_tag(HOSTNAME_KEY) is None - assert child.get_tag(HOSTNAME_KEY) is None + assert root.get_tag(_HOSTNAME_KEY) is None + assert child.get_tag(_HOSTNAME_KEY) is None def test_non_active_span(tracer, test_spans): @@ -1743,7 +1743,7 @@ def test_context_priority(tracer, test_spans): spans = test_spans.pop() assert len(spans) == 1, "trace should be sampled" if p in [USER_REJECT, AUTO_REJECT, AUTO_KEEP, USER_KEEP]: - assert spans[0].get_metric(SAMPLING_PRIORITY_KEY) == p + assert spans[0].get_metric(_SAMPLING_PRIORITY_KEY) == p def test_spans_sampled_out(tracer, test_spans): diff --git a/tests/tracer/test_writer.py b/tests/tracer/test_writer.py index 2089971c554..6abb7681ab8 100644 --- a/tests/tracer/test_writer.py +++ b/tests/tracer/test_writer.py @@ -15,7 +15,7 @@ import ddtrace from ddtrace import config from ddtrace._trace.span import Span -from ddtrace.constants import KEEP_SPANS_RATE_KEY +from ddtrace.constants import _KEEP_SPANS_RATE_KEY from ddtrace.internal.ci_visibility.writer import CIVisibilityWriter from ddtrace.internal.compat import get_connection_response from ddtrace.internal.compat import httplib @@ -366,7 +366,7 @@ def test_keep_rate(self): # 100% of traces kept (refers to the past). # No traces sent before now so 100% kept. for trace in payload: - assert 1.0 == trace[0]["metrics"].get(KEEP_SPANS_RATE_KEY, -1) + assert 1.0 == trace[0]["metrics"].get(_KEEP_SPANS_RATE_KEY, -1) # 2. We fail to write 4 traces because of size limitation. for trace in traces_too_big: @@ -392,7 +392,7 @@ def test_keep_rate(self): # 50% of traces kept (refers to the past). # We had 4 successfully written and 4 dropped. for trace in payload: - assert 0.5 == trace[0]["metrics"].get(KEEP_SPANS_RATE_KEY, -1) + assert 0.5 == trace[0]["metrics"].get(_KEEP_SPANS_RATE_KEY, -1) # 4. We write 1 trace successfully and fail to write 3. writer.write(traces[0]) @@ -408,7 +408,7 @@ def test_keep_rate(self): # 60% of traces kept (refers to the past). # We had 4 successfully written, then 4 dropped, then 2 written. for trace in payload: - assert 0.6 == trace[0]["metrics"].get(KEEP_SPANS_RATE_KEY, -1) + assert 0.6 == trace[0]["metrics"].get(_KEEP_SPANS_RATE_KEY, -1) class CIVisibilityWriterTests(AgentWriterTests): diff --git a/tests/utils.py b/tests/utils.py index 1932033152f..5283e27e7cf 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -20,7 +20,7 @@ from ddtrace import Tracer from ddtrace import config as dd_config from ddtrace._trace.span import Span -from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.constants import _SPAN_MEASURED_KEY from ddtrace.ext import http from ddtrace.internal import agent from ddtrace.internal import core @@ -55,18 +55,18 @@ def assert_is_measured(span): """Assert that the span has the proper _dd.measured tag set""" - assert SPAN_MEASURED_KEY in span.get_metrics() - assert SPAN_MEASURED_KEY not in span.get_tags() - assert span.get_metric(SPAN_MEASURED_KEY) == 1 + assert _SPAN_MEASURED_KEY in span.get_metrics() + assert _SPAN_MEASURED_KEY not in span.get_tags() + assert span.get_metric(_SPAN_MEASURED_KEY) == 1 def assert_is_not_measured(span): """Assert that the span does not set _dd.measured""" - assert SPAN_MEASURED_KEY not in span.get_tags() - if SPAN_MEASURED_KEY in span.get_metrics(): - assert span.get_metric(SPAN_MEASURED_KEY) == 0 + assert _SPAN_MEASURED_KEY not in span.get_tags() + if _SPAN_MEASURED_KEY in span.get_metrics(): + assert span.get_metric(_SPAN_MEASURED_KEY) == 0 else: - assert SPAN_MEASURED_KEY not in span.get_metrics() + assert _SPAN_MEASURED_KEY not in span.get_metrics() def assert_span_http_status_code(span, code):