Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
span.py609 linesDownload Raw Back to trace
1import abc2import logging3import re4import types as python_types5import typing6import warnings7 8from opentelemetry.trace.status import Status, StatusCode9from opentelemetry.util import types10 11# The key MUST begin with a lowercase letter or a digit,12# and can only contain lowercase letters (a-z), digits (0-9),13# underscores (_), dashes (-), asterisks (*), and forward slashes (/).14# For multi-tenant vendor scenarios, an at sign (@) can be used to15# prefix the vendor name. Vendors SHOULD set the tenant ID16# at the beginning of the key.17 18# key = ( lcalpha ) 0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )19# key = ( lcalpha / DIGIT ) 0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" ) "@" lcalpha 0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )20# lcalpha = %x61-7A ; a-z21 22_KEY_FORMAT = (23    r"[a-z][_0-9a-z\-\*\/]{0,255}|"24    r"[a-z0-9][_0-9a-z\-\*\/]{0,240}@[a-z][_0-9a-z\-\*\/]{0,13}"25)26_KEY_PATTERN = re.compile(_KEY_FORMAT)27 28# The value is an opaque string containing up to 256 printable29# ASCII [RFC0020] characters (i.e., the range 0x20 to 0x7E)30# except comma (,) and (=).31# value    = 0*255(chr) nblk-chr32# nblk-chr = %x21-2B / %x2D-3C / %x3E-7E33# chr      = %x20 / nblk-chr34 35_VALUE_FORMAT = (36    r"[\x20-\x2b\x2d-\x3c\x3e-\x7e]{0,255}[\x21-\x2b\x2d-\x3c\x3e-\x7e]"37)38_VALUE_PATTERN = re.compile(_VALUE_FORMAT)39 40 41_TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS = 3242_delimiter_pattern = re.compile(r"[ \t]*,[ \t]*")43_member_pattern = re.compile(f"({_KEY_FORMAT})(=)({_VALUE_FORMAT})[ \t]*")44_logger = logging.getLogger(__name__)45 46 47def _is_valid_pair(key: str, value: str) -> bool:48    return (49        isinstance(key, str)50        and _KEY_PATTERN.fullmatch(key) is not None51        and isinstance(value, str)52        and _VALUE_PATTERN.fullmatch(value) is not None53    )54 55 56class Span(abc.ABC):57    """A span represents a single operation within a trace."""58 59    @abc.abstractmethod60    def end(self, end_time: typing.Optional[int] = None) -> None:61        """Sets the current time as the span's end time.62 63        The span's end time is the wall time at which the operation finished.64 65        Only the first call to `end` should modify the span, and66        implementations are free to ignore or raise on further calls.67        """68 69    @abc.abstractmethod70    def get_span_context(self) -> "SpanContext":71        """Gets the span's SpanContext.72 73        Get an immutable, serializable identifier for this span that can be74        used to create new child spans.75 76        Returns:77            A :class:`opentelemetry.trace.SpanContext` with a copy of this span's immutable state.78        """79 80    @abc.abstractmethod81    def set_attributes(82        self, attributes: typing.Mapping[str, types.AttributeValue]83    ) -> None:84        """Sets Attributes.85 86        Sets Attributes with the key and value passed as arguments dict.87 88        Note: The behavior of `None` value attributes is undefined, and hence89        strongly discouraged. It is also preferred to set attributes at span90        creation, instead of calling this method later since samplers can only91        consider information already present during span creation.92        """93 94    @abc.abstractmethod95    def set_attribute(self, key: str, value: types.AttributeValue) -> None:96        """Sets an Attribute.97 98        Sets a single Attribute with the key and value passed as arguments.99 100        Note: The behavior of `None` value attributes is undefined, and hence101        strongly discouraged. It is also preferred to set attributes at span102        creation, instead of calling this method later since samplers can only103        consider information already present during span creation.104        """105 106    @abc.abstractmethod107    def add_event(108        self,109        name: str,110        attributes: types.Attributes = None,111        timestamp: typing.Optional[int] = None,112    ) -> None:113        """Adds an `Event`.114 115        Adds a single `Event` with the name and, optionally, a timestamp and116        attributes passed as arguments. Implementations should generate a117        timestamp if the `timestamp` argument is omitted.118        """119 120    def add_link(  # pylint: disable=no-self-use121        self,122        context: "SpanContext",123        attributes: types.Attributes = None,124    ) -> None:125        """Adds a `Link`.126 127        Adds a single `Link` with the `SpanContext` of the span to link to and,128        optionally, attributes passed as arguments. Implementations may ignore129        calls with an invalid span context if both attributes and TraceState130        are empty.131 132        Note: It is preferred to add links at span creation, instead of calling133        this method later since samplers can only consider information already134        present during span creation.135        """136        warnings.warn(137            "Span.add_link() not implemented and will be a no-op. "138            "Use opentelemetry-sdk >= 1.23 to add links after span creation"139        )140 141    @abc.abstractmethod142    def update_name(self, name: str) -> None:143        """Updates the `Span` name.144 145        This will override the name provided via :func:`opentelemetry.trace.Tracer.start_span`.146 147        Upon this update, any sampling behavior based on Span name will depend148        on the implementation.149        """150 151    @abc.abstractmethod152    def is_recording(self) -> bool:153        """Returns whether this span will be recorded.154 155        Returns true if this Span is active and recording information like156        events with the add_event operation and attributes using set_attribute.157        """158 159    @abc.abstractmethod160    def set_status(161        self,162        status: typing.Union[Status, StatusCode],163        description: typing.Optional[str] = None,164    ) -> None:165        """Sets the Status of the Span. If used, this will override the default166        Span status.167        """168 169    @abc.abstractmethod170    def record_exception(171        self,172        exception: BaseException,173        attributes: types.Attributes = None,174        timestamp: typing.Optional[int] = None,175        escaped: bool = False,176    ) -> None:177        """Records an exception as a span event."""178 179    def __enter__(self) -> "Span":180        """Invoked when `Span` is used as a context manager.181 182        Returns the `Span` itself.183        """184        return self185 186    def __exit__(187        self,188        exc_type: typing.Optional[typing.Type[BaseException]],189        exc_val: typing.Optional[BaseException],190        exc_tb: typing.Optional[python_types.TracebackType],191    ) -> None:192        """Ends context manager and calls `end` on the `Span`."""193 194        self.end()195 196 197class TraceFlags(int):198    """A bitmask that represents options specific to the trace.199 200    The only supported option is the "sampled" flag (``0x01``). If set, this201    flag indicates that the trace may have been sampled upstream.202 203    See the `W3C Trace Context - Traceparent`_ spec for details.204 205    .. _W3C Trace Context - Traceparent:206        https://www.w3.org/TR/trace-context/#trace-flags207    """208 209    DEFAULT = 0x00210    SAMPLED = 0x01211 212    @classmethod213    def get_default(cls) -> "TraceFlags":214        return cls(cls.DEFAULT)215 216    @property217    def sampled(self) -> bool:218        return bool(self & TraceFlags.SAMPLED)219 220 221DEFAULT_TRACE_OPTIONS = TraceFlags.get_default()222 223 224class TraceState(typing.Mapping[str, str]):225    """A list of key-value pairs representing vendor-specific trace info.226 227    Keys and values are strings of up to 256 printable US-ASCII characters.228    Implementations should conform to the `W3C Trace Context - Tracestate`_229    spec, which describes additional restrictions on valid field values.230 231    .. _W3C Trace Context - Tracestate:232        https://www.w3.org/TR/trace-context/#tracestate-field233    """234 235    def __init__(236        self,237        entries: typing.Optional[238            typing.Sequence[typing.Tuple[str, str]]239        ] = None,240    ) -> None:241        self._dict = {}  # type: dict[str, str]242        if entries is None:243            return244        if len(entries) > _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS:245            _logger.warning(246                "There can't be more than %s key/value pairs.",247                _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS,248            )249            return250 251        for key, value in entries:252            if _is_valid_pair(key, value):253                if key in self._dict:254                    _logger.warning("Duplicate key: %s found.", key)255                    continue256                self._dict[key] = value257            else:258                _logger.warning(259                    "Invalid key/value pair (%s, %s) found.", key, value260                )261 262    def __contains__(self, item: object) -> bool:263        return item in self._dict264 265    def __getitem__(self, key: str) -> str:266        return self._dict[key]267 268    def __iter__(self) -> typing.Iterator[str]:269        return iter(self._dict)270 271    def __len__(self) -> int:272        return len(self._dict)273 274    def __repr__(self) -> str:275        pairs = [276            f"{{key={key}, value={value}}}"277            for key, value in self._dict.items()278        ]279        return str(pairs)280 281    def add(self, key: str, value: str) -> "TraceState":282        """Adds a key-value pair to tracestate. The provided pair should283        adhere to w3c tracestate identifiers format.284 285        Args:286            key: A valid tracestate key to add287            value: A valid tracestate value to add288 289        Returns:290            A new TraceState with the modifications applied.291 292            If the provided key-value pair is invalid or results in tracestate293            that violates tracecontext specification, they are discarded and294            same tracestate will be returned.295        """296        if not _is_valid_pair(key, value):297            _logger.warning(298                "Invalid key/value pair (%s, %s) found.", key, value299            )300            return self301        # There can be a maximum of 32 pairs302        if len(self) >= _TRACECONTEXT_MAXIMUM_TRACESTATE_KEYS:303            _logger.warning("There can't be more 32 key/value pairs.")304            return self305        # Duplicate entries are not allowed306        if key in self._dict:307            _logger.warning("The provided key %s already exists.", key)308            return self309        new_state = [(key, value)] + list(self._dict.items())310        return TraceState(new_state)311 312    def update(self, key: str, value: str) -> "TraceState":313        """Updates a key-value pair in tracestate. The provided pair should314        adhere to w3c tracestate identifiers format.315 316        Args:317            key: A valid tracestate key to update318            value: A valid tracestate value to update for key319 320        Returns:321            A new TraceState with the modifications applied.322 323            If the provided key-value pair is invalid or results in tracestate324            that violates tracecontext specification, they are discarded and325            same tracestate will be returned.326        """327        if not _is_valid_pair(key, value):328            _logger.warning(329                "Invalid key/value pair (%s, %s) found.", key, value330            )331            return self332        prev_state = self._dict.copy()333        prev_state.pop(key, None)334        new_state = [(key, value), *prev_state.items()]335        return TraceState(new_state)336 337    def delete(self, key: str) -> "TraceState":338        """Deletes a key-value from tracestate.339 340        Args:341            key: A valid tracestate key to remove key-value pair from tracestate342 343        Returns:344            A new TraceState with the modifications applied.345 346            If the provided key-value pair is invalid or results in tracestate347            that violates tracecontext specification, they are discarded and348            same tracestate will be returned.349        """350        if key not in self._dict:351            _logger.warning("The provided key %s doesn't exist.", key)352            return self353        prev_state = self._dict.copy()354        prev_state.pop(key)355        new_state = list(prev_state.items())356        return TraceState(new_state)357 358    def to_header(self) -> str:359        """Creates a w3c tracestate header from a TraceState.360 361        Returns:362            A string that adheres to the w3c tracestate363            header format.364        """365        return ",".join(key + "=" + value for key, value in self._dict.items())366 367    @classmethod368    def from_header(cls, header_list: typing.List[str]) -> "TraceState":369        """Parses one or more w3c tracestate header into a TraceState.370 371        Args:372            header_list: one or more w3c tracestate headers.373 374        Returns:375            A valid TraceState that contains values extracted from376            the tracestate header.377 378            If the format of one headers is illegal, all values will379            be discarded and an empty tracestate will be returned.380 381            If the number of keys is beyond the maximum, all values382            will be discarded and an empty tracestate will be returned.383        """384        pairs = {}  # type: dict[str, str]385        for header in header_list:386            members: typing.List[str] = re.split(_delimiter_pattern, header)387            for member in members:388                # empty members are valid, but no need to process further.389                if not member:390                    continue391                match = _member_pattern.fullmatch(member)392                if not match:393                    _logger.warning(394                        "Member doesn't match the w3c identifiers format %s",395                        member,396                    )397                    return cls()398                groups: typing.Tuple[str, ...] = match.groups()399                key, _eq, value = groups400                # duplicate keys are not legal in header401                if key in pairs:402                    return cls()403                pairs[key] = value404        return cls(list(pairs.items()))405 406    @classmethod407    def get_default(cls) -> "TraceState":408        return cls()409 410    def keys(self) -> typing.KeysView[str]:411        return self._dict.keys()412 413    def items(self) -> typing.ItemsView[str, str]:414        return self._dict.items()415 416    def values(self) -> typing.ValuesView[str]:417        return self._dict.values()418 419 420DEFAULT_TRACE_STATE = TraceState.get_default()421_TRACE_ID_MAX_VALUE = 2**128 - 1422_SPAN_ID_MAX_VALUE = 2**64 - 1423 424 425class SpanContext(426    typing.Tuple[int, int, bool, "TraceFlags", "TraceState", bool]427):428    """The state of a Span to propagate between processes.429 430    This class includes the immutable attributes of a :class:`.Span` that must431    be propagated to a span's children and across process boundaries.432 433    Args:434        trace_id: The ID of the trace that this span belongs to.435        span_id: This span's ID.436        is_remote: True if propagated from a remote parent.437        trace_flags: Trace options to propagate.438        trace_state: Tracing-system-specific info to propagate.439    """440 441    def __new__(442        cls,443        trace_id: int,444        span_id: int,445        is_remote: bool,446        trace_flags: typing.Optional["TraceFlags"] = DEFAULT_TRACE_OPTIONS,447        trace_state: typing.Optional["TraceState"] = DEFAULT_TRACE_STATE,448    ) -> "SpanContext":449        if trace_flags is None:450            trace_flags = DEFAULT_TRACE_OPTIONS451        if trace_state is None:452            trace_state = DEFAULT_TRACE_STATE453 454        is_valid = (455            INVALID_TRACE_ID < trace_id <= _TRACE_ID_MAX_VALUE456            and INVALID_SPAN_ID < span_id <= _SPAN_ID_MAX_VALUE457        )458 459        return tuple.__new__(460            cls,461            (trace_id, span_id, is_remote, trace_flags, trace_state, is_valid),462        )463 464    def __getnewargs__(465        self,466    ) -> typing.Tuple[int, int, bool, "TraceFlags", "TraceState"]:467        return (468            self.trace_id,469            self.span_id,470            self.is_remote,471            self.trace_flags,472            self.trace_state,473        )474 475    @property476    def trace_id(self) -> int:477        return self[0]  # pylint: disable=unsubscriptable-object478 479    @property480    def span_id(self) -> int:481        return self[1]  # pylint: disable=unsubscriptable-object482 483    @property484    def is_remote(self) -> bool:485        return self[2]  # pylint: disable=unsubscriptable-object486 487    @property488    def trace_flags(self) -> "TraceFlags":489        return self[3]  # pylint: disable=unsubscriptable-object490 491    @property492    def trace_state(self) -> "TraceState":493        return self[4]  # pylint: disable=unsubscriptable-object494 495    @property496    def is_valid(self) -> bool:497        return self[5]  # pylint: disable=unsubscriptable-object498 499    def __setattr__(self, *args: str) -> None:500        _logger.debug(501            "Immutable type, ignoring call to set attribute", stack_info=True502        )503 504    def __delattr__(self, *args: str) -> None:505        _logger.debug(506            "Immutable type, ignoring call to set attribute", stack_info=True507        )508 509    def __repr__(self) -> str:510        return f"{type(self).__name__}(trace_id=0x{format_trace_id(self.trace_id)}, span_id=0x{format_span_id(self.span_id)}, trace_flags=0x{self.trace_flags:02x}, trace_state={self.trace_state!r}, is_remote={self.is_remote})"511 512 513class NonRecordingSpan(Span):514    """The Span that is used when no Span implementation is available.515 516    All operations are no-op except context propagation.517    """518 519    def __init__(self, context: "SpanContext") -> None:520        self._context = context521 522    def get_span_context(self) -> "SpanContext":523        return self._context524 525    def is_recording(self) -> bool:526        return False527 528    def end(self, end_time: typing.Optional[int] = None) -> None:529        pass530 531    def set_attributes(532        self, attributes: typing.Mapping[str, types.AttributeValue]533    ) -> None:534        pass535 536    def set_attribute(self, key: str, value: types.AttributeValue) -> None:537        pass538 539    def add_event(540        self,541        name: str,542        attributes: types.Attributes = None,543        timestamp: typing.Optional[int] = None,544    ) -> None:545        pass546 547    def add_link(548        self,549        context: "SpanContext",550        attributes: types.Attributes = None,551    ) -> None:552        pass553 554    def update_name(self, name: str) -> None:555        pass556 557    def set_status(558        self,559        status: typing.Union[Status, StatusCode],560        description: typing.Optional[str] = None,561    ) -> None:562        pass563 564    def record_exception(565        self,566        exception: BaseException,567        attributes: types.Attributes = None,568        timestamp: typing.Optional[int] = None,569        escaped: bool = False,570    ) -> None:571        pass572 573    def __repr__(self) -> str:574        return f"NonRecordingSpan({self._context!r})"575 576 577INVALID_SPAN_ID = 0x0000000000000000578INVALID_TRACE_ID = 0x00000000000000000000000000000000579INVALID_SPAN_CONTEXT = SpanContext(580    trace_id=INVALID_TRACE_ID,581    span_id=INVALID_SPAN_ID,582    is_remote=False,583    trace_flags=DEFAULT_TRACE_OPTIONS,584    trace_state=DEFAULT_TRACE_STATE,585)586INVALID_SPAN = NonRecordingSpan(INVALID_SPAN_CONTEXT)587 588 589def format_trace_id(trace_id: int) -> str:590    """Convenience trace ID formatting method591    Args:592        trace_id: Trace ID int593 594    Returns:595        The trace ID (16 bytes) cast to a 32-character hexadecimal string596    """597    return format(trace_id, "032x")598 599 600def format_span_id(span_id: int) -> str:601    """Convenience span ID formatting method602    Args:603        span_id: Span ID int604 605    Returns:606        The span ID (8 bytes) cast to a 16-character hexadecimal string607    """608    return format(span_id, "016x")609 
codekingpro/portable-devtools · Team Ai