diff --git a/ddtrace/_trace/context.py b/ddtrace/_trace/context.py index 278b68066b5..8483e395013 100644 --- a/ddtrace/_trace/context.py +++ b/ddtrace/_trace/context.py @@ -9,8 +9,6 @@ from typing import Tuple 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 @@ -25,8 +23,8 @@ _ContextState = Tuple[ Optional[int], # trace_id Optional[int], # span_id - _MetaDictType, # _meta - _MetricDictType, # _metrics + Dict[str, str], # _meta + Dict[str, NumericType], # _metrics List[SpanLink], # span_links Dict[str, Any], # baggage bool, # is_remote @@ -63,15 +61,15 @@ def __init__( span_id: Optional[int] = None, dd_origin: Optional[str] = None, sampling_priority: Optional[float] = None, - meta: Optional[_MetaDictType] = None, - metrics: Optional[_MetricDictType] = None, + meta: Optional[Dict[str, str]] = None, + metrics: Optional[Dict[str, NumericType]] = None, lock: Optional[threading.RLock] = None, span_links: Optional[List[SpanLink]] = None, baggage: Optional[Dict[str, Any]] = None, is_remote: bool = True, ): - self._meta: _MetaDictType = meta if meta is not None else {} - self._metrics: _MetricDictType = metrics if metrics is not None else {} + self._meta: Dict[str, str] = meta if meta is not None else {} + self._metrics: Dict[str, NumericType] = metrics if metrics is not None else {} self._baggage: Dict[str, Any] = baggage if baggage is not None else {} self.trace_id: Optional[int] = trace_id diff --git a/ddtrace/_trace/span.py b/ddtrace/_trace/span.py index 1acf22b10ae..bc26a1f6d1d 100644 --- a/ddtrace/_trace/span.py +++ b/ddtrace/_trace/span.py @@ -20,9 +20,6 @@ from ddtrace._trace._span_pointer import _SpanPointerDirection from ddtrace._trace.context import Context from ddtrace._trace.types import _AttributeValueType -from ddtrace._trace.types import _MetaDictType -from ddtrace._trace.types import _MetricDictType -from ddtrace._trace.types import _TagNameType from ddtrace.constants import _SAMPLING_AGENT_DECISION from ddtrace.constants import _SAMPLING_LIMIT_DECISION from ddtrace.constants import _SAMPLING_RULE_DECISION @@ -37,14 +34,10 @@ from ddtrace.constants import USER_KEEP from ddtrace.constants import USER_REJECT from ddtrace.constants import VERSION_KEY -from ddtrace.ext import http -from ddtrace.ext import net from ddtrace.internal import core from ddtrace.internal._rand import rand64bits as _rand64bits from ddtrace.internal._rand import rand128bits as _rand128bits from ddtrace.internal.compat import NumericType -from ddtrace.internal.compat import ensure_text -from ddtrace.internal.compat import is_integer from ddtrace.internal.constants import MAX_INT_64BITS as _MAX_INT_64BITS from ddtrace.internal.constants import MAX_UINT_64BITS as _MAX_UINT_64BITS from ddtrace.internal.constants import MIN_INT_64BITS as _MIN_INT_64BITS @@ -191,9 +184,9 @@ def __init__( self.span_type = span_type self._span_api = span_api - self._meta: _MetaDictType = {} + self._meta: Dict[str, str] = {} self.error = 0 - self._metrics: _MetricDictType = {} + self._metrics: Dict[str, NumericType] = {} self._meta_struct: Dict[str, Dict[str, Any]] = {} @@ -335,51 +328,17 @@ def _set_sampling_decision_maker( self.context._meta[SAMPLING_DECISION_TRACE_TAG_KEY] = value return value - def set_tag(self, key: _TagNameType, value: Any = None) -> None: + def set_tag(self, key: str, value: Optional[str] = None) -> None: """Set a tag key/value pair on the span. - Keys must be strings, values must be ``str``-able. + Keys must be strings, values must be ``str``. :param key: Key to use for the tag - :type key: str + :type key: ``str`` :param value: Value to assign for the tag - :type value: ``str``-able value + :type value: ``str`` | `None`` """ - - if not isinstance(key, str): - log.warning("Ignoring tag pair %s:%s. Key must be a string.", key, value) - return - - # Special case, force `http.status_code` as a string - # DEV: `http.status_code` *has* to be in `meta` for metrics - # calculated in the trace agent - if key == http.STATUS_CODE: - value = str(value) - - # Determine once up front - val_is_an_int = is_integer(value) - - # Explicitly try to convert expected integers to `int` - # DEV: Some integrations parse these values from strings, but don't call `int(value)` themselves - INT_TYPES = (net.TARGET_PORT,) - if key in INT_TYPES and not val_is_an_int: - try: - value = int(value) - val_is_an_int = True - except (ValueError, TypeError): - pass - - # Set integers that are less than equal to 2^53 as metrics - if value is not None and val_is_an_int and abs(value) <= 2**53: - self.set_metric(key, value) - return - - # All floats should be set as a metric - elif isinstance(value, float): - self.set_metric(key, value) - return - - elif key == MANUAL_KEEP_KEY: + if key == MANUAL_KEEP_KEY: self._override_sampling_decision(USER_KEEP) return elif key == MANUAL_DROP_KEY: @@ -390,21 +349,17 @@ def set_tag(self, key: _TagNameType, value: Any = None) -> None: elif key == SERVICE_VERSION_KEY: # 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: - # Set `_dd.measured` tag as a metric - # DEV: `set_metric` will ensure it is an integer 0 or 1 - if value is None: - value = 1 - self.set_metric(key, value) - return + if value: + self._meta[VERSION_KEY] = value + else: + self._meta.pop(VERSION_KEY, None) - try: - self._meta[key] = str(value) - if key in self._metrics: - del self._metrics[key] - except Exception: - log.warning("error setting tag %s, ignoring it", key, exc_info=True) + if value is not None: + self._meta[key] = value + else: + self._meta.pop(key, None) + # Ensure we do not have the same key in both meta and metrics + self._metrics.pop(key, None) def set_struct_tag(self, key: str, value: Dict[str, Any]) -> None: """ @@ -417,35 +372,29 @@ def get_struct_tag(self, key: str) -> Optional[Dict[str, Any]]: """Return the given struct or None if it doesn't exist.""" return self._meta_struct.get(key, None) - def set_tag_str(self, key: _TagNameType, value: Text) -> None: + def set_tag_str(self, key: str, value: str) -> None: """Set a value for a tag. Values are coerced to unicode in Python 2 and str in Python 3, with decoding errors in conversion being replaced with U+FFFD. """ - try: - self._meta[key] = ensure_text(value, errors="replace") - except Exception as e: - if config._raise: - raise e - log.warning("Failed to set text tag '%s'", key, exc_info=True) + self._meta[key] = value - def get_tag(self, key: _TagNameType) -> Optional[Text]: + def get_tag(self, key: str) -> Optional[str]: """Return the given tag or None if it doesn't exist.""" return self._meta.get(key, None) - def get_tags(self) -> _MetaDictType: + def get_tags(self) -> Dict[str, str]: """Return all tags.""" return self._meta.copy() - def set_tags(self, tags: Dict[_TagNameType, Any]) -> None: + def set_tags(self, tags: Dict[str, str]) -> None: """Set a dictionary of tags on the given span. Keys and values must be strings (or stringable) """ - if tags: - for k, v in iter(tags.items()): - self.set_tag(k, v) + for k, v in tags.items(): + self.set_tag(k, v) - def set_metric(self, key: _TagNameType, value: NumericType) -> None: + def set_metric(self, key: str, 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: @@ -455,35 +404,23 @@ def set_metric(self, key: _TagNameType, value: NumericType) -> None: log.warning("failed to convert %r tag to an integer from %r", key, value) return - # FIXME[matt] we could push this check to serialization time as well. - # only permit types that are commonly serializable (don't use - # isinstance so that we convert unserializable types like numpy - # numbers) - if not isinstance(value, (int, float)): - try: - value = float(value) - except (ValueError, TypeError): - log.debug("ignoring not number metric %s:%s", key, value) - return - # don't allow nan or inf if math.isnan(value) or math.isinf(value): log.debug("ignoring not real metric %s:%s", key, value) return - if key in self._meta: - del self._meta[key] self._metrics[key] = value + # Ensure we do not have the same key in both meta and metrics + self._meta.pop(key, None) - def set_metrics(self, metrics: _MetricDictType) -> None: + def set_metrics(self, metrics: Dict[str, NumericType]) -> None: """Set a dictionary of metrics on the given span. Keys must be must be strings (or stringable). Values must be numeric. """ - if metrics: - for k, v in metrics.items(): - self.set_metric(k, v) + for k, v in metrics.items(): + self.set_metric(k, v) - def get_metric(self, key: _TagNameType) -> Optional[NumericType]: + def get_metric(self, key: str) -> Optional[NumericType]: """Return the given metric or None if it doesn't exist.""" return self._metrics.get(key) @@ -496,7 +433,7 @@ def _add_on_finish_exception_callback(self, callback: Callable[["Span"], None]): """Add an errortracking related callback to the on_finish_callback array""" self._on_finish_callbacks.insert(0, callback) - def get_metrics(self) -> _MetricDictType: + def get_metrics(self) -> Dict[str, NumericType]: """Return all metrics.""" return self._metrics.copy() diff --git a/ddtrace/_trace/types.py b/ddtrace/_trace/types.py index f021420acde..21b2fc5e7af 100644 --- a/ddtrace/_trace/types.py +++ b/ddtrace/_trace/types.py @@ -1,14 +1,7 @@ -from typing import Dict from typing import Sequence -from typing import Text from typing import Union -from ddtrace.internal.compat import NumericType - -_TagNameType = Union[Text, bytes] -_MetaDictType = Dict[_TagNameType, Text] -_MetricDictType = Dict[_TagNameType, NumericType] _AttributeValueType = Union[ str, bool, diff --git a/ddtrace/_trace/utils_botocore/aws_payload_tagging.py b/ddtrace/_trace/utils_botocore/aws_payload_tagging.py index 12aeaf94346..fabdc87c286 100644 --- a/ddtrace/_trace/utils_botocore/aws_payload_tagging.py +++ b/ddtrace/_trace/utils_botocore/aws_payload_tagging.py @@ -198,7 +198,7 @@ def _tag_object(self, span: Span, key: str, obj: Any, depth: int = 0) -> None: """ # if we've hit the maximum allowed tags, mark the expansion as incomplete if self.current_tag_count >= config.botocore.get("payload_tagging_max_tags"): - span.set_tag(self._INCOMPLETE_TAG, True) + span.set_tag(self._INCOMPLETE_TAG, str(True)) return if obj is None: self.current_tag_count += 1 diff --git a/ddtrace/appsec/ai_guard/_api_client.py b/ddtrace/appsec/ai_guard/_api_client.py index 345e7cdbb07..6bffc223bd7 100644 --- a/ddtrace/appsec/ai_guard/_api_client.py +++ b/ddtrace/appsec/ai_guard/_api_client.py @@ -1,4 +1,5 @@ """AI Guard client for security evaluation of agentic AI workflows.""" + import json from typing import Any from typing import Dict @@ -104,11 +105,11 @@ def evaluate_tool( tool_name: str, tool_args: Dict[Union[Text, bytes], Any], output: Optional[str] = None, - tags: Optional[Dict[Union[Text, bytes], Any]] = None, + tags: Optional[Dict[str, str]] = None, ) -> bool: return self._client.evaluate_tool(tool_name, tool_args, output=output, history=self._history, tags=tags) - def evaluate_prompt(self, role: str, content: str, tags: Optional[Dict[Union[Text, bytes], Any]] = None) -> bool: + def evaluate_prompt(self, role: str, content: str, tags: Optional[Dict[str, str]] = None) -> bool: return self._client.evaluate_prompt(role, content, history=self._history, tags=tags) @@ -149,7 +150,7 @@ def evaluate_tool( tool_args: Dict[Union[Text, bytes], Any], output: Optional[str] = None, history: Optional[List[Evaluation]] = None, - tags: Optional[Dict[Union[Text, bytes], Any]] = None, + tags: Optional[Dict[str, str]] = None, ) -> bool: """Evaluate if a tool call is safe to execute. @@ -187,7 +188,7 @@ def evaluate_prompt( content: str, output: Optional[str] = None, history: Optional[List[Evaluation]] = None, - tags: Optional[Dict[Union[Text, bytes], Any]] = None, + tags: Optional[Dict[str, str]] = None, ) -> bool: """Evaluate if a prompt is safe to execute. @@ -269,7 +270,7 @@ def truncate_content(evaluation: Evaluation) -> Evaluation: TELEMETRY_NAMESPACE.APPSEC, AI_GUARD.TRUNCATED_METRIC, 1, (("type", "content"),) ) - def _evaluate(self, current: Evaluation, history: List[Evaluation], tags: Dict[Union[Text, bytes], Any]) -> bool: + def _evaluate(self, current: Evaluation, history: List[Evaluation], tags: Dict[str, str]) -> bool: """Send evaluation request to AI Guard service.""" with self._tracer.trace(AI_GUARD.RESOURCE_TYPE) as span: if tags is not None: diff --git a/ddtrace/contrib/internal/trace_utils.py b/ddtrace/contrib/internal/trace_utils.py index 55213a08bc0..c83f8d0ce62 100644 --- a/ddtrace/contrib/internal/trace_utils.py +++ b/ddtrace/contrib/internal/trace_utils.py @@ -617,7 +617,13 @@ def set_flattened_tags( # 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) + v = processor(v) if processor is not None else v + if isinstance(v, str): + span.set_tag(tag, v) + elif isinstance(v, (int, float)): + span.set_metric(tag, v) + else: + span.set_tag(tag, str(v)) def extract_netloc_and_query_info_from_url(url): diff --git a/ddtrace/contrib/internal/trace_utils_base.py b/ddtrace/contrib/internal/trace_utils_base.py index 0238114d631..7697bbd8d55 100644 --- a/ddtrace/contrib/internal/trace_utils_base.py +++ b/ddtrace/contrib/internal/trace_utils_base.py @@ -142,15 +142,22 @@ def set_user( ) -def _set_url_tag(integration_config: IntegrationConfig, span: Span, url: str, query: str) -> None: +def _set_url_tag(integration_config: IntegrationConfig, span: Span, url: str, query: Optional[str]) -> None: if not integration_config.http_tag_query_string: span.set_tag_str(http.URL, strip_query_string(url)) + return 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"": + return + + if config._obfuscation_query_string_pattern is None: + span.set_tag_str(http.URL, strip_query_string(url)) + return + + if config._obfuscation_query_string_pattern.pattern == "": # obfuscation is disabled when DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP="" span.set_tag_str(http.URL, strip_query_string(url)) else: diff --git a/ddtrace/debugging/_signal/tracing.py b/ddtrace/debugging/_signal/tracing.py index 37d90c918c1..27bd5e45005 100644 --- a/ddtrace/debugging/_signal/tracing.py +++ b/ddtrace/debugging/_signal/tracing.py @@ -50,7 +50,7 @@ def enter(self, scope: t.Mapping[str, t.Any]) -> None: ) span = self._span_cm.__enter__() - span.set_tags(probe.tags) # type: ignore[arg-type] + span.set_tags(probe.tags) span.set_tag_str(PROBE_ID_TAG_NAME, probe.probe_id) span.set_tag_str(_ORIGIN_KEY, "di") diff --git a/ddtrace/internal/ci_visibility/filters.py b/ddtrace/internal/ci_visibility/filters.py index ca3505c9fda..34e1735ca07 100644 --- a/ddtrace/internal/ci_visibility/filters.py +++ b/ddtrace/internal/ci_visibility/filters.py @@ -18,7 +18,7 @@ class TraceCiVisibilityFilter(TraceFilter): def __init__(self, tags, service): - # type: (Dict[Union[str, bytes], str], str) -> None + # type: (Dict[str, str], str) -> None self._tags = tags self._service = service diff --git a/ddtrace/internal/ci_visibility/utils.py b/ddtrace/internal/ci_visibility/utils.py index d67fce8fd30..49bf02b1558 100644 --- a/ddtrace/internal/ci_visibility/utils.py +++ b/ddtrace/internal/ci_visibility/utils.py @@ -70,9 +70,9 @@ def _add_start_end_source_file_path_data_to_span( log.debug("Tried to collect source start/end lines for test method %s but an exception was raised", test_name) span.set_tag_str(test.SOURCE_FILE, source_file_path) if start_line: - span.set_tag(test.SOURCE_START, start_line) + span.set_metric(test.SOURCE_START, start_line) if end_line: - span.set_tag(test.SOURCE_END, end_line) + span.set_metric(test.SOURCE_END, end_line) def _add_pct_covered_to_span(coverage_data: dict, span: ddtrace.trace.Span): @@ -83,7 +83,7 @@ def _add_pct_covered_to_span(coverage_data: dict, span: ddtrace.trace.Span): if not isinstance(lines_pct_value, float): log.warning("Tried to add total covered percentage to session span but the format was unexpected") return - span.set_tag(test.TEST_LINES_PCT, lines_pct_value) + span.set_metric(test.TEST_LINES_PCT, lines_pct_value) def _generate_fully_qualified_test_name(test_module_path: str, test_suite_name: str, test_name: str) -> str: diff --git a/ddtrace/internal/opentelemetry/span.py b/ddtrace/internal/opentelemetry/span.py index 79dd6d9267f..fc90adab923 100644 --- a/ddtrace/internal/opentelemetry/span.py +++ b/ddtrace/internal/opentelemetry/span.py @@ -42,7 +42,10 @@ def _ddmap(span, attribute, value): if attribute.startswith("meta") or attribute.startswith("metrics"): meta_key = attribute.split("'")[1] if len(attribute.split("'")) == 3 else None if meta_key: - span.set_tag(meta_key, value) + if isinstance(value, bytes): + span.set_tag(meta_key, value.decode("utf-8", errors="replace")) + else: + span.set_metric(meta_key, value) else: setattr(span, attribute, value) return span diff --git a/ddtrace/internal/opentelemetry/trace.py b/ddtrace/internal/opentelemetry/trace.py index 20a9e86f6e0..28559726ea3 100644 --- a/ddtrace/internal/opentelemetry/trace.py +++ b/ddtrace/internal/opentelemetry/trace.py @@ -30,7 +30,6 @@ from opentelemetry.trace import Link as OtelLink # noqa:F401 from opentelemetry.util.types import AttributeValue as OtelAttributeValue # noqa:F401 - from ddtrace._trace.span import _MetaDictType # noqa:F401 from ddtrace.trace import Tracer as DDTracer # noqa:F401 diff --git a/ddtrace/internal/utils/http.py b/ddtrace/internal/utils/http.py index 3324febb756..c48f9550203 100644 --- a/ddtrace/internal/utils/http.py +++ b/ddtrace/internal/utils/http.py @@ -76,14 +76,11 @@ def strip_query_string(url): return h + fs + f -def redact_query_string(query_string, query_string_obfuscation_pattern): - # type: (str, re.Pattern) -> Union[bytes, str] - bytes_query = query_string if isinstance(query_string, bytes) else query_string.encode("utf-8") - return query_string_obfuscation_pattern.sub(b"", bytes_query) +def redact_query_string(query_string: str, query_string_obfuscation_pattern: Pattern) -> str: + return query_string_obfuscation_pattern.sub("", query_string) -def redact_url(url, query_string_obfuscation_pattern, query_string=None): - # type: (str, re.Pattern, Optional[str]) -> Union[str,bytes] +def redact_url(url: str, query_string_obfuscation_pattern: Pattern, query_string: Optional[str] = None) -> str: parts = parse.urlparse(url) redacted_query = None @@ -93,32 +90,30 @@ def redact_url(url, query_string_obfuscation_pattern, query_string=None): redacted_query = redact_query_string(parts.query, query_string_obfuscation_pattern) if redacted_query is not None and len(parts) >= 5: - redacted_parts = parts[:4] + (redacted_query,) + parts[5:] # type: Tuple[Union[str, bytes], ...] - bytes_redacted_parts = tuple(x if isinstance(x, bytes) else x.encode("utf-8") for x in redacted_parts) - return urlunsplit(bytes_redacted_parts, url) + redacted_parts = parts[:4] + (redacted_query,) + parts[5:] + return urlunsplit(redacted_parts, url) # If no obfuscation is performed, return original url return url -def urlunsplit(components, original_url): - # type: (Tuple[bytes, ...], str) -> bytes +def urlunsplit(components: Tuple[str, ...], original_url: str) -> str: """ Adaptation from urlunsplit and urlunparse, using bytes components """ scheme, netloc, url, params, query, fragment = components if params: - url = b"%s;%s" % (url, params) - if netloc or (scheme and url[:2] != b"//"): - if url and url[:1] != b"/": - url = b"/" + url - url = b"//%s%s" % ((netloc or b""), url) + url = "%s;%s" % (url, params) + if netloc or (scheme and url[:2] != "//"): + if url and url[:1] != "/": + url = "/" + url + url = "//%s%s" % ((netloc or ""), url) if scheme: - url = b"%s:%s" % (scheme, url) - if query or (original_url and original_url[-1] in ("?", b"?")): - url = b"%s?%s" % (url, query) - if fragment or (original_url and original_url[-1] in ("#", b"#")): - url = b"%s#%s" % (url, fragment) + url = "%s:%s" % (scheme, url) + if query or (original_url and original_url[-1] == "?"): + url = "%s?%s" % (url, query) + if fragment or (original_url and original_url[-1] == "#"): + url = "%s#%s" % (url, fragment) return url diff --git a/ddtrace/opentracer/span.py b/ddtrace/opentracer/span.py index 75bb522d06f..775d404076e 100644 --- a/ddtrace/opentracer/span.py +++ b/ddtrace/opentracer/span.py @@ -13,6 +13,7 @@ from ddtrace.constants import ERROR_STACK from ddtrace.constants import ERROR_TYPE from ddtrace.internal.compat import NumericType # noqa:F401 +from ddtrace.internal.compat import ensure_text # noqa:F401 from ddtrace.internal.constants import SPAN_API_OPENTRACING from ddtrace.trace import Context as DatadogContext # noqa:F401 from ddtrace.trace import Span as DatadogSpan @@ -153,7 +154,12 @@ def set_tag(self, key, value): elif key == Tags.SAMPLING_PRIORITY: self._dd_span.context.sampling_priority = value else: - self._dd_span.set_tag(key, value) + if not isinstance(value, (str, bytes)): + try: + value = str(value) + except Exception: + value = repr(value) + self._dd_span.set_tag(ensure_text(key), ensure_text(value)) return self def _get_tag(self, key): @@ -162,7 +168,7 @@ def _get_tag(self, key): This method retrieves the tag from the underlying datadog span. """ - return self._dd_span.get_tag(key) + return self._dd_span.get_tag(ensure_text(key)) def _get_metric(self, key): # type: (_TagNameType) -> Optional[NumericType] @@ -170,7 +176,7 @@ def _get_metric(self, key): This method retrieves the metric from the underlying datadog span. """ - return self._dd_span.get_metric(key) + return self._dd_span.get_metric(ensure_text(key)) def __enter__(self): return self diff --git a/ddtrace/propagation/http.py b/ddtrace/propagation/http.py index 5a0101d351e..3d09867ff67 100644 --- a/ddtrace/propagation/http.py +++ b/ddtrace/propagation/http.py @@ -17,7 +17,6 @@ from ddtrace._trace.span import Span # noqa:F401 from ddtrace._trace.span import _get_64_highest_order_bits_as_hex from ddtrace._trace.span import _get_64_lowest_order_bits_as_int -from ddtrace._trace.types import _MetaDictType from ddtrace.appsec._constants import APPSEC from ddtrace.internal import core from ddtrace.internal.telemetry import telemetry_writer @@ -284,11 +283,8 @@ def _inject(span_context, headers): # Only propagate trace tags which means ignoring the _dd.origin tags_to_encode = { - # DEV: Context._meta is a _MetaDictType but we need Dict[str, str] - ensure_text(k): ensure_text(v) - for k, v in span_context._meta.items() - if _DatadogMultiHeader._is_valid_datadog_trace_tag_key(k) - } # type: Dict[Text, Text] + k: v for k, v in span_context._meta.items() if _DatadogMultiHeader._is_valid_datadog_trace_tag_key(k) + } if tags_to_encode: try: @@ -384,10 +380,7 @@ def _extract(headers): span_id=int(parent_span_id) or None, # type: ignore[arg-type] sampling_priority=sampling_priority, # type: ignore[arg-type] dd_origin=origin, - # DEV: This cast is needed because of the type requirements of - # span tags and trace tags which are currently implemented using - # the same type internally (_MetaDictType). - meta=cast(_MetaDictType, meta), + meta=meta, ) except (TypeError, ValueError): log.debug( @@ -829,14 +822,14 @@ def _extract(headers): log.exception("received invalid w3c traceparent: %s ", tp) return None - meta = {W3C_TRACEPARENT_KEY: tp} # type: _MetaDictType + meta = {W3C_TRACEPARENT_KEY: tp} ts = _extract_header_value(_POSSIBLE_HTTP_HEADER_TRACESTATE, headers) return _TraceContext._get_context(trace_id, span_id, trace_flag, ts, meta) @staticmethod def _get_context(trace_id, span_id, trace_flag, ts, meta=None): - # type: (int, int, Literal[0,1], Optional[str], Optional[_MetaDictType]) -> Context + # type: (int, int, Literal[0,1], Optional[str], Optional[Dict[str, str]]) -> Context if meta is None: meta = {} origin = None diff --git a/ddtrace/settings/_config.py b/ddtrace/settings/_config.py index 9a14793c080..330e34247a3 100644 --- a/ddtrace/settings/_config.py +++ b/ddtrace/settings/_config.py @@ -2,7 +2,7 @@ import os import re import sys -from typing import Any # noqa:F401 +from typing import Any, Pattern # noqa:F401 from typing import Callable # noqa:F401 from typing import Dict # noqa:F401 from typing import List # noqa:F401 @@ -606,16 +606,14 @@ def __init__(self): self._data_streams_enabled = _get_config("DD_DATA_STREAMS_ENABLED", False, asbool) self._http_client_tag_query_string = _get_config("DD_TRACE_HTTP_CLIENT_TAG_QUERY_STRING", "true") - dd_trace_obfuscation_query_string_regexp = _get_config( + dd_trace_obfuscation_query_string_regexp: str = _get_config( "DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP", DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP_DEFAULT ) self._global_query_string_obfuscation_disabled = dd_trace_obfuscation_query_string_regexp == "" - self._obfuscation_query_string_pattern = None + self._obfuscation_query_string_pattern: Optional[Pattern[str]] = None self._http_tag_query_string = True # Default behaviour of query string tagging in http.url try: - self._obfuscation_query_string_pattern = re.compile( - dd_trace_obfuscation_query_string_regexp.encode("ascii") - ) + self._obfuscation_query_string_pattern = re.compile(dd_trace_obfuscation_query_string_regexp) except Exception: log.warning("Invalid obfuscation pattern, disabling query string tracing", exc_info=True) self._http_tag_query_string = False # Disable query string tagging if malformed obfuscation pattern diff --git a/releasenotes/notes/explicit-span-tag-typing-5fabfe5f2c458e7d.yaml b/releasenotes/notes/explicit-span-tag-typing-5fabfe5f2c458e7d.yaml new file mode 100644 index 00000000000..8a2081ec70e --- /dev/null +++ b/releasenotes/notes/explicit-span-tag-typing-5fabfe5f2c458e7d.yaml @@ -0,0 +1,34 @@ +--- +upgrade: + - | + tracing: The ``Span.set_tag`` method typing has been updated to be: + + def set_tag(self, key: str, value: str | None) -> None: ... + - | + tracing: The ``Span.set_tags`` method typing has been updated to be: + + def set_tags(self, tags: dict[str, str]) -> None: ... + - | + tracing: The ``Span.get_tag`` method typing has been updated to be: + + def get_tag(self, key: str) -> str | None: ... + - | + tracing: The ``Span.get_tags`` method typing has been updated to be: + + def get_tags(self) -> dict[str, str]: ... + - | + tracing: The ``Span.set_metric`` method typing has been updated to be: + + def set_metric(self, key: str, value: int | float) -> None: ... + - | + tracing: The ``Span.set_metrics`` method typing has been updated to be: + + def set_metrics(self, metrics: dict[str, int | float]) -> None: ... + - | + tracing: The ``Span.get_metric`` method typing has been updated to be: + + def get_metric(self, key: str) -> int | float | None: ... + - | + tracing: The ``Span.get_metrics`` method typing has been updated to be: + + def get_metrics(self) -> dict[str, int | float]: ... diff --git a/tests/tracer/test_encoders.py b/tests/tracer/test_encoders.py index 468ce89a03a..5963c7c4470 100644 --- a/tests/tracer/test_encoders.py +++ b/tests/tracer/test_encoders.py @@ -81,7 +81,7 @@ def gen_trace(nspans=1000, ntags=50, key_size=15, value_size=20, nmetrics=10): span.span_type = "web" for _ in range(0, nmetrics): - span.set_tag(rands(key_size), random.randint(0, 2**16)) + span.set_metric(rands(key_size), random.randint(0, 2**16)) trace.append(span) @@ -483,14 +483,12 @@ class SubFloat(float): @pytest.mark.parametrize( "span, tags", [ - (Span("name"), {"int": SubInt(123)}), - (Span("name"), {"float": SubFloat(123.213)}), (Span(SubString("name")), {SubString("test"): SubString("test")}), (Span("name"), {"unicode": "😐"}), (Span("name"), {"😐": "😐"}), ( Span("span_name", service="test-service", resource="test-resource", span_type=SpanTypes.WEB), - {"metric1": 123, "metric2": "1", "metric3": 12.3, "metric4": "12.0", "tag1": "test", "tag2": "unicode"}, + {"metric2": "1", "metric4": "12.0", "tag1": "test", "tag2": "unicode"}, ), ], ) diff --git a/tests/tracer/test_env_vars.py b/tests/tracer/test_env_vars.py index 16b92cc49f2..f7b785cf1c3 100644 --- a/tests/tracer/test_env_vars.py +++ b/tests/tracer/test_env_vars.py @@ -8,18 +8,18 @@ "env_var_name,env_var_value,expected_obfuscation_config,expected_global_query_string_obfuscation_disabled," "expected_http_tag_query_string", [ - ("DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP", "", 're.compile(b"")', True, True), + ("DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP", "", 're.compile("")', True, True), ( "DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP", "(?i)(?:p(?:ass)?w(?:or))", - "re.compile('(?i)(?:p(?:ass)?w(?:or))'.encode('ascii'))", + "re.compile('(?i)(?:p(?:ass)?w(?:or))')", False, True, ), ( "DD_WRONG_ENV_NAME", "(?i)(?:p(?:ass)?w(?:or))", - "re.compile(DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP_DEFAULT.encode('ascii'))", + "re.compile(DD_TRACE_OBFUSCATION_QUERY_STRING_REGEXP_DEFAULT)", False, True, ), diff --git a/tests/tracer/test_global_config.py b/tests/tracer/test_global_config.py index 761115c49d4..93fe36775a1 100644 --- a/tests/tracer/test_global_config.py +++ b/tests/tracer/test_global_config.py @@ -197,7 +197,7 @@ def on_web_request(span): @self.config.web.hooks.on("request") def on_web_request2(span): - span.set_tag("web.status", 200) + span.set_metric("web.status", 200) @self.config.web.hooks.on("request") def on_web_request3(span): diff --git a/tests/tracer/test_http.py b/tests/tracer/test_http.py index 864abdb49e1..d3b555b62f8 100644 --- a/tests/tracer/test_http.py +++ b/tests/tracer/test_http.py @@ -47,15 +47,14 @@ def test_strip_query_string(url): @pytest.mark.parametrize("url", _url_fixtures()) def test_redact_url_not_redacts_without_param(url): - res = redact_url(url, re.compile(b"\\@"), None) - expected_result = url if isinstance(res, str) else url.encode("utf-8") - assert res == expected_result + res = redact_url(url, re.compile("\\@"), None) + assert res == url @pytest.mark.parametrize("url", _url_fixtures()) def test_redact_url_not_redacts_with_param(url): parsed_url = parse.urlparse(url) - assert redact_url(url, re.compile(b"\\*"), "query_string") == parse.urlunparse( + assert redact_url(url, re.compile("\\*"), "query_string") == parse.urlunparse( ( parsed_url.scheme, parsed_url.netloc, @@ -64,17 +63,17 @@ def test_redact_url_not_redacts_with_param(url): "query_string", parsed_url.fragment, ) - ).encode("utf-8") + ) @pytest.mark.parametrize( "url, regex, query_string, expected", ( - ("://?&?", re.compile(b"\\?"), None, b"://?&"), - ("://?&?", re.compile(b"\\?"), None, b"://?&"), - ("://?x", re.compile(b"x"), None, b"://?"), - ("://x", re.compile(b"x"), "x", b"://x?"), - ("://y", re.compile(b"x"), "x", b"://y?"), + ("://?&?", re.compile("\\?"), None, "://?&"), + ("://?&?", re.compile("\\?"), None, "://?&"), + ("://?x", re.compile("x"), None, "://?"), + ("://x", re.compile("x"), "x", "://x?"), + ("://y", re.compile("x"), "x", "://y?"), ), ) def test_redact_url_does_redact(url, regex, query_string, expected): diff --git a/tests/tracer/test_processors.py b/tests/tracer/test_processors.py index f91d911b3bd..bebcd8377b4 100644 --- a/tests/tracer/test_processors.py +++ b/tests/tracer/test_processors.py @@ -79,14 +79,14 @@ def test_aggregator_user_processors(): class Proc(TraceProcessor): def process_trace(self, trace): assert len(trace) == 1 - trace[0].set_tag("dd_processor") + trace[0].set_tag("dd_processor", "called") trace[0].set_tag("final_processor", "dd") return trace class UserProc(TraceProcessor): def process_trace(self, trace): assert len(trace) == 1 - trace[0].set_tag("user_processor") + trace[0].set_tag("user_processor", "called") trace[0].set_tag("final_processor", "user") return trace @@ -100,8 +100,8 @@ def process_trace(self, trace): with Span("span", on_finish=[aggr.on_span_finish]) as span: aggr.on_span_start(span) - assert span.get_tag("dd_processor") - assert span.get_tag("user_processor") + assert span.get_tag("dd_processor") == "called" + assert span.get_tag("user_processor") == "called" assert span.get_tag("final_processor") == "user" diff --git a/tests/tracer/test_propagation.py b/tests/tracer/test_propagation.py index 9232d4c2f20..74600d32c5d 100644 --- a/tests/tracer/test_propagation.py +++ b/tests/tracer/test_propagation.py @@ -194,27 +194,6 @@ def test_inject_tags_unicode(tracer): # noqa: F811 assert tags == set(["_dd.p.test=unicode"]) -def test_inject_tags_bytes(tracer): # noqa: F811 - """We properly encode when the meta key as long as it is just ascii characters""" - # Context._meta allows str and bytes for keys - # FIXME: W3C does not support byte headers - overrides = { - "_propagation_style_extract": [PROPAGATION_STYLE_DATADOG], - "_propagation_style_inject": [PROPAGATION_STYLE_DATADOG], - } - with override_global_config(overrides): - meta = {"_dd.p.test": b"bytes"} - ctx = Context(trace_id=1234, sampling_priority=2, dd_origin="synthetics", meta=meta) - tracer.context_provider.activate(ctx) - with tracer.trace("global_root_span") as span: - headers = {} - HTTPPropagator.inject(span.context, headers) - - # The ordering is non-deterministic, so compare as a list of tags - tags = set(headers[_HTTP_HEADER_TAGS].split(",")) - assert tags == set(["_dd.p.test=bytes"]) - - def test_inject_tags_unicode_error(tracer): # noqa: F811 """Unicode characters are not allowed""" meta = {"_dd.p.test": "unicode value ☺️"} @@ -324,7 +303,10 @@ def test_extract(tracer): # noqa: F811 @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation( - tracer, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 + tracer, + sca_enabled, + appsec_enabled, + iast_enabled, # noqa: F811 ): if not appsec_enabled and not iast_enabled and sca_enabled == "false": pytest.skip("SCA, AppSec or IAST must be enabled") @@ -382,7 +364,10 @@ def test_asm_standalone_minimum_trace_per_minute_has_no_downstream_propagation( @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_missing_propagation_tags_no_appsec_event_trace_dropped( - tracer, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 + tracer, + sca_enabled, + appsec_enabled, + iast_enabled, # noqa: F811 ): if not appsec_enabled and not iast_enabled and sca_enabled == "false": pytest.skip("SCA, AppSec or IAST must be enabled") @@ -461,7 +446,10 @@ def test_asm_standalone_missing_propagation_tags_appsec_event_present_trace_kept @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_missing_appsec_tag_no_appsec_event_propagation_resets( - tracer, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 + tracer, + sca_enabled, + appsec_enabled, + iast_enabled, # noqa: F811 ): if not appsec_enabled and not iast_enabled and sca_enabled == "false": pytest.skip("SCA, AppSec or IAST must be enabled") @@ -569,7 +557,11 @@ def test_asm_standalone_missing_appsec_tag_appsec_event_present_trace_kept( @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_present_appsec_tag_no_appsec_event_propagation_set_to_user_keep( - tracer, upstream_priority, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 + tracer, + upstream_priority, + sca_enabled, + appsec_enabled, + iast_enabled, # noqa: F811 ): if not appsec_enabled and not iast_enabled and sca_enabled == "false": pytest.skip("SCA, AppSec or IAST must be enabled") @@ -637,7 +629,11 @@ def test_asm_standalone_present_appsec_tag_no_appsec_event_propagation_set_to_us @pytest.mark.parametrize("appsec_enabled", [True, False]) @pytest.mark.parametrize("iast_enabled", [True, False]) def test_asm_standalone_present_appsec_tag_appsec_event_present_propagation_force_keep( - tracer, upstream_priority, sca_enabled, appsec_enabled, iast_enabled # noqa: F811 + tracer, + upstream_priority, + sca_enabled, + appsec_enabled, + iast_enabled, # noqa: F811 ): if not appsec_enabled and not iast_enabled and sca_enabled == "false": pytest.skip("SCA, AppSec or IAST must be enabled") @@ -2589,9 +2585,7 @@ def test_DD_TRACE_PROPAGATION_STYLE_EXTRACT_overrides_DD_TRACE_PROPAGATION_STYLE "sampling_priority": context.sampling_priority, "dd_origin": context.dd_origin, }})) - """.format( - headers - ) + """.format(headers) env = os.environ.copy() if styles is not None: env["DD_TRACE_PROPAGATION_STYLE"] = ",".join(styles) @@ -3293,9 +3287,7 @@ def test_propagation_inject(name, styles, context, expected_headers, run_python_ HTTPPropagator.inject(context, headers) print(json.dumps(headers)) - """.format( - context - ) + """.format(context) env = os.environ.copy() if styles is not None: @@ -3360,9 +3352,7 @@ def test_DD_TRACE_PROPAGATION_STYLE_INJECT_overrides_DD_TRACE_PROPAGATION_STYLE( HTTPPropagator.inject(context, headers) print(json.dumps(headers)) - """.format( - context - ) + """.format(context) env = os.environ.copy() if styles is not None: @@ -3443,9 +3433,9 @@ def test_baggageheader_maxbytes_inject(): # multiple baggage items to test dropping items when the total size exceeds the limit headers = {} baggage_items = { - "key1": "a" * ((DD_TRACE_BAGGAGE_MAX_BYTES // 3)), - "key2": "b" * ((DD_TRACE_BAGGAGE_MAX_BYTES // 3)), - "key3": "c" * ((DD_TRACE_BAGGAGE_MAX_BYTES // 3)), + "key1": "a" * (DD_TRACE_BAGGAGE_MAX_BYTES // 3), + "key2": "b" * (DD_TRACE_BAGGAGE_MAX_BYTES // 3), + "key3": "c" * (DD_TRACE_BAGGAGE_MAX_BYTES // 3), "key4": "d", } span_context = Context(baggage=baggage_items) diff --git a/tests/tracer/test_span.py b/tests/tracer/test_span.py index ee5245c8600..7babdd9f5ab 100644 --- a/tests/tracer/test_span.py +++ b/tests/tracer/test_span.py @@ -54,49 +54,10 @@ def test_128bit_trace_ids(self): def test_tags(self): s = Span(name="test.span") s.set_tag("a", "a") - s.set_tag("b", 1) s.set_tag("c", "1") assert s.get_tags() == dict(a="a", c="1") - assert s.get_metrics() == dict(b=1) - - def test_numeric_tags(self): - s = Span(name="test.span") - s.set_tag("negative", -1) - s.set_tag("zero", 0) - s.set_tag("positive", 1) - s.set_tag("large_int", 2**53) - s.set_tag("really_large_int", (2**53) + 1) - s.set_tag("large_negative_int", -(2**53)) - s.set_tag("really_large_negative_int", -((2**53) + 1)) - s.set_tag("float", 12.3456789) - s.set_tag("negative_float", -12.3456789) - s.set_tag("large_float", 2.0**53) - s.set_tag("really_large_float", (2.0**53) + 1) - - assert s.get_tags() == dict( - really_large_int=str(((2**53) + 1)), - really_large_negative_int=str(-((2**53) + 1)), - ) - assert s.get_metrics() == { - "negative": -1, - "zero": 0, - "positive": 1, - "large_int": 2**53, - "large_negative_int": -(2**53), - "float": 12.3456789, - "negative_float": -12.3456789, - "large_float": 2.0**53, - "really_large_float": (2.0**53) + 1, - } - - def test_set_tag_bool(self): - s = Span(name="test.span") - s.set_tag("true", True) - s.set_tag("false", False) - - assert s.get_tags() == dict(true="True", false="False") - assert len(s.get_metrics()) == 0 + assert s.get_metrics() == dict() def test_set_baggage_item(self): s = Span(name="test.span") @@ -141,37 +102,24 @@ def test_baggage_get_all(self): assert span1.context.get_all_baggage_items() == {"item1": "123", "item2": "456"} - def test_set_tag_metric(self): - s = Span(name="test.span") - - s.set_tag("test", "value") - assert s.get_tags() == dict(test="value") - assert s.get_metrics() == dict() - - s.set_tag("test", 1) - assert s.get_tags() == dict() - assert s.get_metrics() == dict(test=1) - def test_set_valid_metrics(self): s = Span(name="test.span") s.set_metric("a", 0) s.set_metric("b", -12) s.set_metric("c", 12.134) s.set_metric("d", 1231543543265475686787869123) - s.set_metric("e", "12.34") expected = { "a": 0, "b": -12, "c": 12.134, "d": 1231543543265475686787869123, - "e": 12.34, } assert s.get_metrics() == expected def test_set_invalid_metric(self): s = Span(name="test.span") - invalid_metrics = [None, {}, [], s, "quarante-douze", float("nan"), float("inf"), 1j] + invalid_metrics = [float("nan"), float("inf")] for i, m in enumerate(invalid_metrics): k = str(i) @@ -188,15 +136,6 @@ def test_set_numpy_metric(self): assert s.get_metric("a") == 1 assert type(s.get_metric("a")) == float - def test_tags_not_string(self): - # ensure we can cast as strings - class Foo(object): - def __repr__(self): - 1 / 0 - - s = Span(name="test.span") - s.set_tag("a", Foo()) - def test_finish(self): # ensure span.finish() marks the end time of the span s = Span("test.span") @@ -303,9 +242,9 @@ def wrapper(): # ZeroDivisionError: division by zero header_and_footer_lines += 1 - assert ( - len(stack.splitlines()) == tb_length_limit * multiplier + header_and_footer_lines - ), "stacktrace should contain two lines per entry" + assert len(stack.splitlines()) == tb_length_limit * multiplier + header_and_footer_lines, ( + "stacktrace should contain two lines per entry" + ) def test_ctx_mgr(self): s = Span("bar") @@ -338,11 +277,12 @@ def test_span_type(self): def test_numeric_tags_none(self, span_log): s = Span(name="test.span") s.set_tag("noneval", None) + assert len(s.get_tags()) == 0 assert len(s.get_metrics()) == 0 def test_numeric_tags_value(self): s = Span(name="test.span") - s.set_tag("point5", 0.5) + s.set_metric("point5", 0.5) expected = {"point5": 0.5} assert s.get_metrics() == expected @@ -361,7 +301,7 @@ def test_set_tag_none(self): s.set_tag("custom.key", None) - assert s.get_tags() == {"custom.key": "None"} + assert s.get_tags() == {} def test_duration_zero(self): s = Span(name="foo.bar", service="s", resource="r", start=123) @@ -635,14 +575,10 @@ def test_service_entry_span(self): @pytest.mark.parametrize( "value,assertion", [ - (None, assert_is_measured), (1, assert_is_measured), (1.0, assert_is_measured), (-1, assert_is_measured), (True, assert_is_measured), - ("true", assert_is_measured), - # DEV: Ends up being measured because we do `bool("false")` which is `True` - ("false", assert_is_measured), (0, assert_is_not_measured), (0.0, assert_is_not_measured), (False, assert_is_not_measured), @@ -650,7 +586,7 @@ def test_service_entry_span(self): ) def test_set_tag_measured(value, assertion): s = Span(name="test.span") - s.set_tag(_SPAN_MEASURED_KEY, value) + s.set_metric(_SPAN_MEASURED_KEY, value) assertion(s) @@ -660,41 +596,18 @@ def test_set_tag_measured_not_set(): assert_is_not_measured(s) -def test_set_tag_measured_no_value(): - s = Span(name="test.span") - 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_metric(_SPAN_MEASURED_KEY, True) assert_is_measured(s) - s.set_tag(_SPAN_MEASURED_KEY, False) + s.set_metric(_SPAN_MEASURED_KEY, False) assert_is_not_measured(s) - s.set_tag(_SPAN_MEASURED_KEY) + s.set_metric(_SPAN_MEASURED_KEY, 1.0) assert_is_measured(s) -@mock.patch("ddtrace._trace.span.log") -def test_span_key(span_log): - # Span tag keys must be strings - s = Span(name="test.span") - - s.set_tag(123, True) - span_log.warning.assert_called_once_with("Ignoring tag pair %s:%s. Key must be a string.", 123, True) - assert s.get_tag(123) is None - assert s.get_tag("123") is None - - span_log.reset_mock() - - s.set_tag(None, "val") - span_log.warning.assert_called_once_with("Ignoring tag pair %s:%s. Key must be a string.", None, "val") - assert s.get_tag(123.32) is None - - def test_spans_finished(): span = Span(None) assert span.finished is False @@ -725,54 +638,22 @@ def test_span_unicode_set_tag(): span.set_tag_str("😐", "😌") -@pytest.mark.skipif(sys.version_info.major != 2, reason="This test only applies Python 2") -@mock.patch("ddtrace._trace.span.log") -def test_span_binary_unicode_set_tag(span_log): - span = Span(None) - span.set_tag("key", "πŸ€”") - span.set_tag_str("key_str", "πŸ€”") - # only span.set_tag() will fail - span_log.warning.assert_called_once_with("error setting tag %s, ignoring it", "key", exc_info=True) - assert "key" not in span.get_tags() - assert span.get_tag("key_str") == "πŸ€”" - - -@pytest.mark.skipif(sys.version_info.major == 2, reason="This test does not apply to Python 2") @mock.patch("ddtrace._trace.span.log") def test_span_bytes_string_set_tag(span_log): span = Span(None) - span.set_tag("key", b"\xf0\x9f\xa4\x94") - span.set_tag_str("key_str", b"\xf0\x9f\xa4\x94") - assert span.get_tag("key") == "b'\\xf0\\x9f\\xa4\\x94'" - assert span.get_tag("key_str") == "πŸ€”" + span.set_tag("key", "😌") + span.set_tag_str("key_str", "😌") + assert span.get_tag("key") == "😌" + assert span.get_tag("key_str") == "😌" span_log.warning.assert_not_called() @mock.patch("ddtrace._trace.span.log") def test_span_encoding_set_str_tag(span_log): span = Span(None) - span.set_tag_str("foo", "/?foo=bar&baz=μ •μƒμ²˜λ¦¬".encode("euc-kr")) + span.set_tag_str("foo", "/?foo=bar&baz=μ •μƒμ²˜λ¦¬") span_log.warning.assert_not_called() - assert span.get_tag("foo") == "/?foo=bar&baz=οΏ½οΏ½οΏ½οΏ½Γ³οΏ½οΏ½" - - -def test_span_nonstring_set_str_tag_exc(): - span = Span(None) - with pytest.raises(TypeError): - span.set_tag_str("foo", dict(a=1)) - assert "foo" not in span.get_tags() - - -@mock.patch("ddtrace._trace.span.log") -def test_span_nonstring_set_str_tag_warning(span_log): - with override_global_config(dict(_raise=False)): - span = Span(None) - span.set_tag_str("foo", dict(a=1)) - span_log.warning.assert_called_once_with( - "Failed to set text tag '%s'", - "foo", - exc_info=True, - ) + assert span.get_tag("foo") == "/?foo=bar&baz=μ •μƒμ²˜λ¦¬" def test_span_ignored_exceptions(): diff --git a/tests/tracer/test_trace_utils.py b/tests/tracer/test_trace_utils.py index 65b79767706..ba38595e85e 100644 --- a/tests/tracer/test_trace_utils.py +++ b/tests/tracer/test_trace_utils.py @@ -1121,7 +1121,8 @@ def test_url_in_http_with_empty_obfuscation_regex(): from ddtrace.settings.integration import IntegrationConfig from ddtrace.trace import tracer - assert config._obfuscation_query_string_pattern.pattern == b"", config._obfuscation_query_string_pattern + assert config._obfuscation_query_string_pattern is not None + assert config._obfuscation_query_string_pattern.pattern == "", config._obfuscation_query_string_pattern SENSITIVE_URL = "http://weblog:7777/?application_key=123" config.myint = IntegrationConfig(config, "myint") diff --git a/tests/tracer/test_tracer.py b/tests/tracer/test_tracer.py index bd5b7909f87..3c67c66c64a 100644 --- a/tests/tracer/test_tracer.py +++ b/tests/tracer/test_tracer.py @@ -241,8 +241,8 @@ def i(cls): def test_tracer_wrap_factory(self): def wrap_executor(tracer, fn, args, kwargs, span_name=None, service=None, resource=None, span_type=None): with tracer.trace("wrap.overwrite") as span: - span.set_tag("args", args) - span.set_tag("kwargs", kwargs) + span.set_tag("args", str(args)) + span.set_tag("kwargs", str(kwargs)) return fn(*args, **kwargs) @self.tracer.wrap() @@ -265,8 +265,8 @@ def wrapped_function(param, kw_param=None): def test_tracer_wrap_factory_nested(self): def wrap_executor(tracer, fn, args, kwargs, span_name=None, service=None, resource=None, span_type=None): with tracer.trace("wrap.overwrite") as span: - span.set_tag("args", args) - span.set_tag("kwargs", kwargs) + span.set_tag("args", str(args)) + span.set_tag("kwargs", str(kwargs)) return fn(*args, **kwargs) @self.tracer.wrap()