def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.attributes is Span._empty_attributes:
                    self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
        if has_ended:
            logger.warning("Setting attribute on ended span.")
            return

        if not key:
            logger.warning("invalid key (empty or null)")
            return

        if isinstance(value, Sequence):
            error_message = self._check_attribute_value_sequence(value)
            if error_message is not None:
                logger.warning("%s in attribute value sequence", error_message)
                return
            # Freeze mutable sequences defensively
            if isinstance(value, MutableSequence):
                value = tuple(value)
        elif not isinstance(value, (bool, str, int, float)):
            logger.warning("invalid type for attribute value")
            return

        self.attributes[key] = value
Beispiel #2
0
def _extract_attributes(
    attrs: types.Attributes,
    num_attrs_limit: int,
    add_agent_attr: bool = False,
) -> ProtoSpan.Attributes:
    """Convert span.attributes to dict."""
    attributes_dict = BoundedDict(num_attrs_limit)
    invalid_value_dropped_count = 0
    for key, value in attrs.items():
        key = _truncate_str(key, 128)[0]
        value = _format_attribute_value(value)

        if value:
            attributes_dict[key] = value
        else:
            invalid_value_dropped_count += 1
    if add_agent_attr:
        attributes_dict["g.co/agent"] = _format_attribute_value(
            "opentelemetry-python {}; google-cloud-trace-exporter {}".format(
                _strip_characters(
                    pkg_resources.get_distribution(
                        "opentelemetry-sdk").version),
                _strip_characters(google_ext_version),
            ))
    return ProtoSpan.Attributes(
        attribute_map=attributes_dict,
        dropped_attributes_count=attributes_dict.dropped +
        invalid_value_dropped_count,
    )
Beispiel #3
0
def _extract_attributes(
    attrs: types.Attributes,
    num_attrs_limit: int,
    add_agent_attr: bool = False,
) -> ProtoSpan.Attributes:
    """Convert span.attributes to dict."""
    attributes_dict = BoundedDict(
        num_attrs_limit)  # type: BoundedDict[str, AttributeValue]
    invalid_value_dropped_count = 0
    for key, value in attrs.items() if attrs else []:
        key = _truncate_str(key, 128)[0]
        if key in LABELS_MAPPING:  # pylint: disable=consider-using-get
            key = LABELS_MAPPING[key]
        value = _format_attribute_value(value)

        if value:
            attributes_dict[key] = value
        else:
            invalid_value_dropped_count += 1
    if add_agent_attr:
        attributes_dict["g.co/agent"] = _format_attribute_value(
            "opentelemetry-python {}; google-cloud-trace-exporter {}".format(
                _strip_characters(
                    pkg_resources.get_distribution(
                        "opentelemetry-sdk").version),
                _strip_characters(google_ext_version),
            ))
    return ProtoSpan.Attributes(
        attribute_map=attributes_dict,
        dropped_attributes_count=attributes_dict.
        dropped  # type: ignore[attr-defined]
        + invalid_value_dropped_count,
    )
Beispiel #4
0
    def __init__(
        self,
        name: str,
        context: trace_api.SpanContext,
        parent: Optional[trace_api.SpanContext] = None,
        sampler: Optional[sampling.Sampler] = None,
        trace_config: None = None,  # TODO
        resource: Resource = Resource.create({}),
        attributes: types.Attributes = None,
        events: Sequence[Event] = None,
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        span_processor: SpanProcessor = SpanProcessor(),
        instrumentation_info: InstrumentationInfo = None,
        record_exception: bool = True,
        set_status_on_exception: bool = True,
    ) -> None:

        self.name = name
        self.context = context
        self.parent = parent
        self.sampler = sampler
        self.trace_config = trace_config
        self.resource = resource
        self.kind = kind
        self._record_exception = record_exception
        self._set_status_on_exception = set_status_on_exception

        self.span_processor = span_processor
        self.status = Status(StatusCode.UNSET)
        self._lock = threading.Lock()

        _filter_attribute_values(attributes)
        if not attributes:
            self.attributes = self._new_attributes()
        else:
            self.attributes = BoundedDict.from_map(
                SPAN_ATTRIBUTE_COUNT_LIMIT, attributes
            )

        self.events = self._new_events()
        if events:
            for event in events:
                _filter_attribute_values(event.attributes)
                # pylint: disable=protected-access
                event._attributes = _create_immutable_attributes(
                    event.attributes
                )
                self.events.append(event)

        if links is None:
            self.links = self._new_links()
        else:
            self.links = BoundedList.from_seq(SPAN_LINK_COUNT_LIMIT, links)

        self._end_time = None  # type: Optional[int]
        self._start_time = None  # type: Optional[int]
        self.instrumentation_info = instrumentation_info
    def test_from_map(self):
        dic_len = len(self.base)
        base_copy = collections.OrderedDict(self.base)
        bdict = BoundedDict.from_map(dic_len, base_copy)

        self.assertEqual(len(bdict), dic_len)

        # modify base_copy and test that bdict is not changed
        base_copy["name"] = "Bruno"
        base_copy["age"] = 3

        for key in self.base:
            self.assertEqual(bdict[key], self.base[key])

        # test that iter yields the correct number of elements
        self.assertEqual(len(tuple(bdict)), dic_len)

        # map too big
        with self.assertRaises(ValueError):
            BoundedDict.from_map(dic_len / 2, self.base)
Beispiel #6
0
 def set_attribute(self, key: str, value: types.AttributeValue) -> None:
     with self._lock:
         if not self.is_recording_events():
             return
         has_ended = self.end_time is not None
         if not has_ended:
             if self.attributes is Span.empty_attributes:
                 self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
     if has_ended:
         logger.warning("Setting attribute on ended span.")
         return
     self.attributes[key] = value
Beispiel #7
0
    def __init__(
        self,
        name: str,
        context: trace_api.SpanContext,
        parent: Optional[trace_api.SpanContext] = None,
        sampler: Optional[sampling.Sampler] = None,
        trace_config: None = None,  # TODO
        resource: Resource = Resource.create_empty(),
        attributes: types.Attributes = None,  # TODO
        events: Sequence[Event] = None,  # TODO
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        span_processor: SpanProcessor = SpanProcessor(),
        instrumentation_info: InstrumentationInfo = None,
        set_status_on_exception: bool = True,
    ) -> None:

        self.name = name
        self.context = context
        self.parent = parent
        self.sampler = sampler
        self.trace_config = trace_config
        self.resource = resource
        self.kind = kind
        self._set_status_on_exception = set_status_on_exception

        self.span_processor = span_processor
        self.status = None
        self._lock = threading.Lock()

        self._filter_attribute_values(attributes)
        if not attributes:
            self.attributes = Span._empty_attributes
        else:
            self.attributes = BoundedDict.from_map(MAX_NUM_ATTRIBUTES,
                                                   attributes)

        if events is None:
            self.events = Span._empty_events
        else:
            self.events = BoundedList(MAX_NUM_EVENTS)
            for event in events:
                self._filter_attribute_values(event.attributes)
                self.events.append(event)

        if links is None:
            self.links = Span._empty_links
        else:
            self.links = BoundedList.from_seq(MAX_NUM_LINKS, links)

        self._end_time = None  # type: Optional[int]
        self._start_time = None  # type: Optional[int]
        self.instrumentation_info = instrumentation_info
Beispiel #8
0
def _extract_attributes(attrs: types.Attributes,
                        num_attrs_limit: int) -> ProtoSpan.Attributes:
    """Convert span.attributes to dict."""
    attributes_dict = BoundedDict(num_attrs_limit)

    for key, value in attrs.items():
        key = _truncate_str(key, 128)[0]
        value = _format_attribute_value(value)

        if value is not None:
            attributes_dict[key] = value
    return ProtoSpan.Attributes(
        attribute_map=attributes_dict,
        dropped_attributes_count=len(attrs) - len(attributes_dict),
    )
    def __init__(
        self,
        name: str,
        context: trace_api.SpanContext,
        parent: trace_api.ParentSpan = None,
        sampler: Optional[sampling.Sampler] = None,
        trace_config: None = None,  # TODO
        resource: None = None,  # TODO
        attributes: types.Attributes = None,  # TODO
        events: Sequence[trace_api.Event] = None,  # TODO
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        span_processor: SpanProcessor = SpanProcessor(),
    ) -> None:

        self.name = name
        self.context = context
        self.parent = parent
        self.sampler = sampler
        self.trace_config = trace_config
        self.resource = resource
        self.kind = kind

        self.span_processor = span_processor
        self.status = trace_api.Status()
        self._lock = threading.Lock()

        if attributes is None:
            self.attributes = Span.empty_attributes
        else:
            self.attributes = BoundedDict.from_map(
                MAX_NUM_ATTRIBUTES, attributes
            )

        if events is None:
            self.events = Span.empty_events
        else:
            self.events = BoundedList.from_seq(MAX_NUM_EVENTS, events)

        if links is None:
            self.links = Span.empty_links
        else:
            self.links = BoundedList.from_seq(MAX_NUM_LINKS, links)

        self.end_time = None  # type: Optional[int]
        self.start_time = None  # type: Optional[int]
    def test_bounded_dict(self):
        # create empty dict
        dic_len = len(self.base)
        bdict = BoundedDict(dic_len)
        self.assertEqual(len(bdict), 0)

        # fill dict
        for key in self.base:
            bdict[key] = self.base[key]

        self.assertEqual(len(bdict), dic_len)
        self.assertEqual(bdict.dropped, 0)

        for key in self.base:
            self.assertEqual(bdict[key], self.base[key])

        # test __iter__ in BoundedDict
        for key in bdict:
            self.assertEqual(bdict[key], self.base[key])

        # updating an existing element should not drop
        bdict["name"] = "Bruno"
        self.assertEqual(bdict.dropped, 0)

        # try to append more elements
        for key in self.base:
            bdict["new-" + key] = self.base[key]

        self.assertEqual(len(bdict), dic_len)
        self.assertEqual(bdict.dropped, dic_len)

        # test that elements in the dict are the new ones
        for key in self.base:
            self.assertEqual(bdict["new-" + key], self.base[key])

        # delete an element
        del bdict["new-name"]
        self.assertEqual(len(bdict), dic_len - 1)

        with self.assertRaises(KeyError):
            _ = bdict["new-name"]
Beispiel #11
0
    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.attributes is Span._empty_attributes:
                    self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
        if has_ended:
            logger.warning("Setting attribute on ended span.")
            return

        if not key:
            logger.warning("invalid key (empty or null)")
            return

        if _is_valid_attribute_value(value):
            # Freeze mutable sequences defensively
            if isinstance(value, MutableSequence):
                value = tuple(value)
            with self._lock:
                self.attributes[key] = value
Beispiel #12
0
    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.attributes is Span.empty_attributes:
                    self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
        if has_ended:
            logger.warning("Setting attribute on ended span.")
            return

        if isinstance(value, Sequence):
            error_message = self._check_attribute_value_sequence(value)
            if error_message is not None:
                logger.warning("%s in attribute value sequence", error_message)
                return
        elif not isinstance(value, (bool, str, Number, Sequence)):
            logger.warning("invalid type for attribute value")
            return

        self.attributes[key] = value
 def _new_attributes():
     return BoundedDict(MAX_NUM_ATTRIBUTES)
class Span(trace_api.Span):
    """See `opentelemetry.trace.Span`.

    Users should create `Span` objects via the `Tracer` instead of this
    constructor.

    Args:
        name: The name of the operation this span represents
        context: The immutable span context
        parent: This span's parent, may be a `SpanContext` if the parent is
            remote, null if this is a root span
        sampler: The sampler used to create this span
        trace_config: TODO
        resource: Entity producing telemetry
        attributes: The span's attributes to be exported
        events: Timestamped events to be exported
        links: Links to other spans to be exported
        span_processor: `SpanProcessor` to invoke when starting and ending
            this `Span`.
    """

    # Initialize these lazily assuming most spans won't have them.
    _empty_attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
    _empty_events = BoundedList(MAX_NUM_EVENTS)
    _empty_links = BoundedList(MAX_NUM_LINKS)

    def __init__(
        self,
        name: str,
        context: trace_api.SpanContext,
        parent: trace_api.ParentSpan = None,
        sampler: Optional[sampling.Sampler] = None,
        trace_config: None = None,  # TODO
        resource: None = None,
        attributes: types.Attributes = None,  # TODO
        events: Sequence[Event] = None,  # TODO
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        span_processor: SpanProcessor = SpanProcessor(),
        instrumentation_info: InstrumentationInfo = None,
        set_status_on_exception: bool = True,
    ) -> None:

        self.name = name
        self.context = context
        self.parent = parent
        self.sampler = sampler
        self.trace_config = trace_config
        self.resource = resource
        self.kind = kind
        self._set_status_on_exception = set_status_on_exception

        self.span_processor = span_processor
        self.status = None
        self._lock = threading.Lock()

        if attributes is None:
            self.attributes = Span._empty_attributes
        else:
            self.attributes = BoundedDict.from_map(
                MAX_NUM_ATTRIBUTES, attributes
            )

        if events is None:
            self.events = Span._empty_events
        else:
            self.events = BoundedList.from_seq(MAX_NUM_EVENTS, events)

        if links is None:
            self.links = Span._empty_links
        else:
            self.links = BoundedList.from_seq(MAX_NUM_LINKS, links)

        self._end_time = None  # type: Optional[int]
        self._start_time = None  # type: Optional[int]
        self.instrumentation_info = instrumentation_info

    @property
    def start_time(self):
        return self._start_time

    @property
    def end_time(self):
        return self._end_time

    def __repr__(self):
        return '{}(name="{}", context={})'.format(
            type(self).__name__, self.name, self.context
        )

    @staticmethod
    def _format_context(context):
        x_ctx = OrderedDict()
        x_ctx["trace_id"] = trace_api.format_trace_id(context.trace_id)
        x_ctx["span_id"] = trace_api.format_span_id(context.span_id)
        x_ctx["trace_state"] = repr(context.trace_state)
        return x_ctx

    @staticmethod
    def _format_attributes(attributes):
        if isinstance(attributes, BoundedDict):
            return attributes._dict  # pylint: disable=protected-access
        return attributes

    @staticmethod
    def _format_events(events):
        f_events = []
        for event in events:
            f_event = OrderedDict()
            f_event["name"] = event.name
            f_event["timestamp"] = util.ns_to_iso_str(event.timestamp)
            f_event["attributes"] = Span._format_attributes(event.attributes)
            f_events.append(f_event)
        return f_events

    @staticmethod
    def _format_links(links):
        f_links = []
        for link in links:
            f_link = OrderedDict()
            f_link["context"] = Span._format_context(link.context)
            f_link["attributes"] = Span._format_attributes(link.attributes)
            f_links.append(f_link)
        return f_links

    def to_json(self):
        parent_id = None
        if self.parent is not None:
            if isinstance(self.parent, Span):
                ctx = self.parent.context
                parent_id = trace_api.format_span_id(ctx.span_id)
            elif isinstance(self.parent, SpanContext):
                parent_id = trace_api.format_span_id(self.parent.span_id)

        start_time = None
        if self.start_time:
            start_time = util.ns_to_iso_str(self.start_time)

        end_time = None
        if self.end_time:
            end_time = util.ns_to_iso_str(self.end_time)

        if self.status is not None:
            status = OrderedDict()
            status["canonical_code"] = str(self.status.canonical_code.name)
            if self.status.description:
                status["description"] = self.status.description

        f_span = OrderedDict()

        f_span["name"] = self.name
        f_span["context"] = self._format_context(self.context)
        f_span["kind"] = str(self.kind)
        f_span["parent_id"] = parent_id
        f_span["start_time"] = start_time
        f_span["end_time"] = end_time
        if self.status is not None:
            f_span["status"] = status
        f_span["attributes"] = self._format_attributes(self.attributes)
        f_span["events"] = self._format_events(self.events)
        f_span["links"] = self._format_links(self.links)

        return json.dumps(f_span, indent=4)

    def get_context(self):
        return self.context

    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.attributes is Span._empty_attributes:
                    self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
        if has_ended:
            logger.warning("Setting attribute on ended span.")
            return

        if not key:
            logger.warning("invalid key (empty or null)")
            return

        if isinstance(value, Sequence):
            error_message = self._check_attribute_value_sequence(value)
            if error_message is not None:
                logger.warning("%s in attribute value sequence", error_message)
                return
            # Freeze mutable sequences defensively
            if isinstance(value, MutableSequence):
                value = tuple(value)
        elif not isinstance(value, (bool, str, int, float)):
            logger.warning("invalid type for attribute value")
            return

        self.attributes[key] = value

    @staticmethod
    def _check_attribute_value_sequence(sequence: Sequence) -> Optional[str]:
        """
        Checks if sequence items are valid and are of the same type
        """
        if len(sequence) == 0:
            return None

        first_element_type = type(sequence[0])

        if first_element_type not in (bool, str, int, float):
            return "invalid type"

        for element in sequence:
            if not isinstance(element, first_element_type):
                return "different type"
        return None

    def _add_event(self, event: EventBase) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.events is Span._empty_events:
                    self.events = BoundedList(MAX_NUM_EVENTS)
        if has_ended:
            logger.warning("Calling add_event() on an ended span.")
            return
        self.events.append(event)

    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
    ) -> None:
        if attributes is None:
            attributes = Span._empty_attributes
        self._add_event(
            Event(
                name=name,
                attributes=attributes,
                timestamp=time_ns() if timestamp is None else timestamp,
            )
        )

    def add_lazy_event(
        self,
        name: str,
        event_formatter: types.AttributesFormatter,
        timestamp: Optional[int] = None,
    ) -> None:
        self._add_event(
            LazyEvent(
                name=name,
                event_formatter=event_formatter,
                timestamp=time_ns() if timestamp is None else timestamp,
            )
        )

    def start(self, start_time: Optional[int] = None) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_started = self.start_time is not None
            if not has_started:
                self._start_time = (
                    start_time if start_time is not None else time_ns()
                )
        if has_started:
            logger.warning("Calling start() on a started span.")
            return
        self.span_processor.on_start(self)

    def end(self, end_time: Optional[int] = None) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            if self.start_time is None:
                raise RuntimeError("Calling end() on a not started span.")
            has_ended = self.end_time is not None
            if not has_ended:
                if self.status is None:
                    self.status = Status(canonical_code=StatusCanonicalCode.OK)

                self._end_time = (
                    end_time if end_time is not None else time_ns()
                )

        if has_ended:
            logger.warning("Calling end() on an ended span.")
            return

        self.span_processor.on_end(self)

    def update_name(self, name: str) -> None:
        with self._lock:
            has_ended = self.end_time is not None
        if has_ended:
            logger.warning("Calling update_name() on an ended span.")
            return
        self.name = name

    def is_recording_events(self) -> bool:
        return True

    def set_status(self, status: trace_api.Status) -> None:
        with self._lock:
            has_ended = self.end_time is not None
        if has_ended:
            logger.warning("Calling set_status() on an ended span.")
            return
        self.status = status

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        """Ends context manager and calls `end` on the `Span`."""

        if (
            self.status is None
            and self._set_status_on_exception
            and exc_val is not None
        ):

            self.set_status(
                Status(
                    canonical_code=StatusCanonicalCode.UNKNOWN,
                    description="{}: {}".format(exc_type.__name__, exc_val),
                )
            )

        super().__exit__(exc_type, exc_val, exc_tb)
Beispiel #15
0
class Span(trace_api.Span):
    """See `opentelemetry.trace.Span`.

    Users should create `Span`s via the `Tracer` instead of this constructor.

    Args:
        name: The name of the operation this span represents
        context: The immutable span context
        parent: This span's parent, may be a `SpanContext` if the parent is
            remote, null if this is a root span
        sampler: The sampler used to create this span
        trace_config: TODO
        resource: TODO
        attributes: The span's attributes to be exported
        events: Timestamped events to be exported
        links: Links to other spans to be exported
        span_processor: `SpanProcessor` to invoke when starting and ending
            this `Span`.
    """

    # Initialize these lazily assuming most spans won't have them.
    empty_attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
    empty_events = BoundedList(MAX_NUM_EVENTS)
    empty_links = BoundedList(MAX_NUM_LINKS)

    def __init__(
            self,
            name: str,
            context: trace_api.SpanContext,
            parent: trace_api.ParentSpan = None,
            sampler: Optional[sampling.Sampler] = None,
            trace_config: None = None,  # TODO
            resource: None = None,  # TODO
            attributes: types.Attributes = None,  # TODO
            events: Sequence[trace_api.Event] = None,  # TODO
            links: Sequence[trace_api.Link] = None,  # TODO
            kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
            span_processor: SpanProcessor = SpanProcessor(),
    ) -> None:

        self.name = name
        self.context = context
        self.parent = parent
        self.sampler = sampler
        self.trace_config = trace_config
        self.resource = resource
        self.kind = kind

        self.span_processor = span_processor
        self.status = trace_api.Status()
        self._lock = threading.Lock()

        if attributes is None:
            self.attributes = Span.empty_attributes
        else:
            self.attributes = BoundedDict.from_map(MAX_NUM_ATTRIBUTES,
                                                   attributes)

        if events is None:
            self.events = Span.empty_events
        else:
            self.events = BoundedList.from_seq(MAX_NUM_EVENTS, events)

        if links is None:
            self.links = Span.empty_links
        else:
            self.links = BoundedList.from_seq(MAX_NUM_LINKS, links)

        self.end_time = None  # type: Optional[int]
        self.start_time = None  # type: Optional[int]

    def __repr__(self):
        return '{}(name="{}", context={})'.format(
            type(self).__name__, self.name, self.context)

    def __str__(self):
        return '{}(name="{}", context={}, kind={}, parent={}, start_time={}, end_time={})'.format(
            type(self).__name__,
            self.name,
            self.context,
            self.kind,
            repr(self.parent),
            util.ns_to_iso_str(self.start_time) if self.start_time else "None",
            util.ns_to_iso_str(self.end_time) if self.end_time else "None",
        )

    def get_context(self):
        return self.context

    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.attributes is Span.empty_attributes:
                    self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
        if has_ended:
            logger.warning("Setting attribute on ended span.")
            return
        self.attributes[key] = value

    def add_event(
        self,
        name: str,
        timestamp: int = None,
        attributes: types.Attributes = None,
    ) -> None:
        self.add_lazy_event(
            trace_api.Event(
                name,
                time_ns() if timestamp is None else timestamp,
                Span.empty_attributes if attributes is None else attributes,
            ))

    def add_lazy_event(self, event: trace_api.Event) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.events is Span.empty_events:
                    self.events = BoundedList(MAX_NUM_EVENTS)
        if has_ended:
            logger.warning("Calling add_event() on an ended span.")
            return
        self.events.append(event)

    def add_link(
        self,
        link_target_context: "trace_api.SpanContext",
        attributes: types.Attributes = None,
    ) -> None:
        if attributes is None:
            attributes = (
                Span.empty_attributes
            )  # TODO: empty_attributes is not a Dict. Use Mapping?
        self.add_lazy_link(trace_api.Link(link_target_context, attributes))

    def add_lazy_link(self, link: "trace_api.Link") -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.links is Span.empty_links:
                    self.links = BoundedList(MAX_NUM_LINKS)
        if has_ended:
            logger.warning("Calling add_link() on an ended span.")
            return
        self.links.append(link)

    def start(self, start_time: Optional[int] = None) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_started = self.start_time is not None
            if not has_started:
                self.start_time = (start_time
                                   if start_time is not None else time_ns())
        if has_started:
            logger.warning("Calling start() on a started span.")
            return
        self.span_processor.on_start(self)

    def end(self, end_time: int = None) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            if self.start_time is None:
                raise RuntimeError("Calling end() on a not started span.")
            has_ended = self.end_time is not None
            if not has_ended:
                self.end_time = end_time if end_time is not None else time_ns()
        if has_ended:
            logger.warning("Calling end() on an ended span.")
            return

        self.span_processor.on_end(self)

    def update_name(self, name: str) -> None:
        with self._lock:
            has_ended = self.end_time is not None
        if has_ended:
            logger.warning("Calling update_name() on an ended span.")
            return
        self.name = name

    def is_recording_events(self) -> bool:
        return True

    def set_status(self, status: trace_api.Status) -> None:
        with self._lock:
            has_ended = self.end_time is not None
        if has_ended:
            logger.warning("Calling set_status() on an ended span.")
            return
        self.status = status
 def test_negative_maxlen(self):
     with self.assertRaises(ValueError):
         BoundedDict(-1)
Beispiel #17
0
class Span(trace_api.Span):
    """See `opentelemetry.trace.Span`.

    Users should create `Span` objects via the `Tracer` instead of this
    constructor.

    Args:
        name: The name of the operation this span represents
        context: The immutable span context
        parent: This span's parent, may be a `SpanContext` if the parent is
            remote, null if this is a root span
        sampler: The sampler used to create this span
        trace_config: TODO
        resource: TODO
        attributes: The span's attributes to be exported
        events: Timestamped events to be exported
        links: Links to other spans to be exported
        span_processor: `SpanProcessor` to invoke when starting and ending
            this `Span`.
    """

    # Initialize these lazily assuming most spans won't have them.
    empty_attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
    empty_events = BoundedList(MAX_NUM_EVENTS)
    empty_links = BoundedList(MAX_NUM_LINKS)

    def __init__(
        self,
        name: str,
        context: trace_api.SpanContext,
        parent: trace_api.ParentSpan = None,
        sampler: Optional[sampling.Sampler] = None,
        trace_config: None = None,  # TODO
        resource: None = None,  # TODO
        attributes: types.Attributes = None,  # TODO
        events: Sequence[trace_api.Event] = None,  # TODO
        links: Sequence[trace_api.Link] = (),
        kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
        span_processor: SpanProcessor = SpanProcessor(),
        instrumentation_info: InstrumentationInfo = None,
        set_status_on_exception: bool = True,
    ) -> None:

        self.name = name
        self.context = context
        self.parent = parent
        self.sampler = sampler
        self.trace_config = trace_config
        self.resource = resource
        self.kind = kind
        self._set_status_on_exception = set_status_on_exception

        self.span_processor = span_processor
        self.status = None
        self._lock = threading.Lock()

        if attributes is None:
            self.attributes = Span.empty_attributes
        else:
            self.attributes = BoundedDict.from_map(MAX_NUM_ATTRIBUTES,
                                                   attributes)

        if events is None:
            self.events = Span.empty_events
        else:
            self.events = BoundedList.from_seq(MAX_NUM_EVENTS, events)

        if links is None:
            self.links = Span.empty_links
        else:
            self.links = BoundedList.from_seq(MAX_NUM_LINKS, links)

        self._end_time = None  # type: Optional[int]
        self._start_time = None  # type: Optional[int]
        self.instrumentation_info = instrumentation_info

    @property
    def start_time(self):
        return self._start_time

    @property
    def end_time(self):
        return self._end_time

    def __repr__(self):
        return '{}(name="{}", context={})'.format(
            type(self).__name__, self.name, self.context)

    def __str__(self):
        return ('{}(name="{}", context={}, kind={}, '
                "parent={}, start_time={}, end_time={})").format(
                    type(self).__name__,
                    self.name,
                    self.context,
                    self.kind,
                    repr(self.parent),
                    util.ns_to_iso_str(self.start_time)
                    if self.start_time else "None",
                    util.ns_to_iso_str(self.end_time)
                    if self.end_time else "None",
                )

    def get_context(self):
        return self.context

    def set_attribute(self, key: str, value: types.AttributeValue) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.attributes is Span.empty_attributes:
                    self.attributes = BoundedDict(MAX_NUM_ATTRIBUTES)
        if has_ended:
            logger.warning("Setting attribute on ended span.")
            return

        if isinstance(value, Sequence):
            error_message = self._check_attribute_value_sequence(value)
            if error_message is not None:
                logger.warning("%s in attribute value sequence", error_message)
                return
        elif not isinstance(value, (bool, str, Number, Sequence)):
            logger.warning("invalid type for attribute value")
            return

        self.attributes[key] = value

    @staticmethod
    def _check_attribute_value_sequence(sequence: Sequence) -> Optional[str]:
        """
        Checks if sequence items are valid and are of the same type
        """
        if len(sequence) == 0:
            return None

        first_element_type = type(sequence[0])

        if issubclass(first_element_type, Number):
            first_element_type = Number

        if first_element_type not in (bool, str, Number):
            return "invalid type"

        for element in sequence:
            if not isinstance(element, first_element_type):
                return "different type"
        return None

    def add_event(
        self,
        name: str,
        attributes: types.Attributes = None,
        timestamp: Optional[int] = None,
    ) -> None:
        self.add_lazy_event(
            trace_api.Event(
                name,
                Span.empty_attributes if attributes is None else attributes,
                time_ns() if timestamp is None else timestamp,
            ))

    def add_lazy_event(self, event: trace_api.Event) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_ended = self.end_time is not None
            if not has_ended:
                if self.events is Span.empty_events:
                    self.events = BoundedList(MAX_NUM_EVENTS)
        if has_ended:
            logger.warning("Calling add_event() on an ended span.")
            return
        self.events.append(event)

    def start(self, start_time: Optional[int] = None) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            has_started = self.start_time is not None
            if not has_started:
                self._start_time = (start_time
                                    if start_time is not None else time_ns())
        if has_started:
            logger.warning("Calling start() on a started span.")
            return
        self.span_processor.on_start(self)

    def end(self, end_time: Optional[int] = None) -> None:
        with self._lock:
            if not self.is_recording_events():
                return
            if self.start_time is None:
                raise RuntimeError("Calling end() on a not started span.")
            has_ended = self.end_time is not None
            if not has_ended:
                if self.status is None:
                    self.status = Status(canonical_code=StatusCanonicalCode.OK)

                self._end_time = (end_time
                                  if end_time is not None else time_ns())

        if has_ended:
            logger.warning("Calling end() on an ended span.")
            return

        self.span_processor.on_end(self)

    def update_name(self, name: str) -> None:
        with self._lock:
            has_ended = self.end_time is not None
        if has_ended:
            logger.warning("Calling update_name() on an ended span.")
            return
        self.name = name

    def is_recording_events(self) -> bool:
        return True

    def set_status(self, status: trace_api.Status) -> None:
        with self._lock:
            has_ended = self.end_time is not None
        if has_ended:
            logger.warning("Calling set_status() on an ended span.")
            return
        self.status = status

    def __exit__(
        self,
        exc_type: Optional[Type[BaseException]],
        exc_val: Optional[BaseException],
        exc_tb: Optional[TracebackType],
    ) -> None:
        """Ends context manager and calls `end` on the `Span`."""

        if (self.status is None and self._set_status_on_exception
                and exc_val is not None):

            self.set_status(
                Status(
                    canonical_code=StatusCanonicalCode.UNKNOWN,
                    description="{}: {}".format(exc_type.__name__, exc_val),
                ))

        super().__exit__(exc_type, exc_val, exc_tb)
Beispiel #18
0
 def _new_attributes():
     return BoundedDict(SPAN_ATTRIBUTE_COUNT_LIMIT)