示例#1
0
 def test_extract_links(self):
     self.assertIsNone(_extract_links([]))
     trace_id = "6e0c63257de34c92bf9efcd03927272e"
     span_id1 = "95bb5edabd45950f"
     span_id2 = "b6b86ad2915c9ddc"
     link1 = Link(
         context=SpanContext(
             trace_id=int(trace_id, 16),
             span_id=int(span_id1, 16),
             is_remote=False,
         ),
         attributes={},
     )
     link2 = Link(
         context=SpanContext(
             trace_id=int(trace_id, 16),
             span_id=int(span_id1, 16),
             is_remote=False,
         ),
         attributes=self.attributes_variety_pack,
     )
     link3 = Link(
         context=SpanContext(
             trace_id=int(trace_id, 16),
             span_id=int(span_id2, 16),
             is_remote=False,
         ),
         attributes={
             "illegal_attr_value": dict(),
             "int_attr_value": 123
         },
     )
     self.assertEqual(
         _extract_links([link1, link2, link3]),
         ProtoSpan.Links(link=[
             {
                 "trace_id": trace_id,
                 "span_id": span_id1,
                 "type": "TYPE_UNSPECIFIED",
                 "attributes": ProtoSpan.Attributes(attribute_map={}),
             },
             {
                 "trace_id": trace_id,
                 "span_id": span_id1,
                 "type": "TYPE_UNSPECIFIED",
                 "attributes": self.extracted_attributes_variety_pack,
             },
             {
                 "trace_id": trace_id,
                 "span_id": span_id2,
                 "type": "TYPE_UNSPECIFIED",
                 "attributes": {
                     "attribute_map": {
                         "int_attr_value": AttributeValue(int_value=123)
                     },
                     "dropped_attributes_count": 1,
                 },
             },
         ]),
     )
示例#2
0
 def test_too_many_links(self):
     link = Link(
         context=SpanContext(
             trace_id=int(self.example_trace_id, 16),
             span_id=int(self.example_span_id, 16),
             is_remote=False,
         ),
         attributes={},
     )
     too_many_links = [link] * (MAX_NUM_LINKS + 5)
     self.assertEqual(
         _extract_links(too_many_links),
         ProtoSpan.Links(
             link=[
                 {
                     "trace_id": self.example_trace_id,
                     "span_id": self.example_span_id,
                     "type": "TYPE_UNSPECIFIED",
                     "attributes": {},
                 }
             ]
             * MAX_NUM_LINKS,
             dropped_links_count=len(too_many_links) - MAX_NUM_LINKS,
         ),
     )
示例#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)
    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,
    )
示例#4
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,
    )
示例#5
0
def _extract_events(events: Sequence[Event]) -> ProtoSpan.TimeEvents:
    """Convert span.events to dict."""
    if not events:
        return None
    logs = []
    dropped_annontations = 0
    if len(events) > MAX_NUM_EVENTS:
        logger.warning(
            "Exporting more then %s annotations, some will be truncated",
            MAX_NUM_EVENTS,
        )
        dropped_annontations = len(events) - MAX_NUM_EVENTS
        events = events[:MAX_NUM_EVENTS]
    for event in events:
        if event.attributes and len(event.attributes) > MAX_EVENT_ATTRS:
            logger.warning(
                "Event %s has more then %s attributes, some will be truncated",
                event.name,
                MAX_EVENT_ATTRS,
            )
        logs.append({
            "time": _get_time_from_ns(event.timestamp),
            "annotation": {
                "description":
                _get_truncatable_str_object(event.name, 256),
                "attributes":
                _extract_attributes(event.attributes, MAX_EVENT_ATTRS),
            },
        })
    return ProtoSpan.TimeEvents(
        time_event=logs,
        dropped_annotations_count=dropped_annontations,
        dropped_message_events_count=0,
    )
示例#6
0
def _extract_links(links: Sequence[trace_api.Link]) -> ProtoSpan.Links:
    """Convert span.links"""
    if not links:
        return None
    extracted_links = []
    dropped_links = 0
    if len(links) > MAX_NUM_LINKS:
        logger.warning(
            "Exporting more then %s links, some will be truncated",
            MAX_NUM_LINKS,
        )
        dropped_links = len(links) - MAX_NUM_LINKS
        links = links[:MAX_NUM_LINKS]
    for link in links:
        link_attributes = link.attributes or {}
        if len(link_attributes) > MAX_LINK_ATTRS:
            logger.warning(
                "Link has more then %s attributes, some will be truncated",
                MAX_LINK_ATTRS,
            )
        trace_id = get_hexadecimal_trace_id(link.context.trace_id)
        span_id = get_hexadecimal_span_id(link.context.span_id)
        extracted_links.append({
            "trace_id":
            trace_id,
            "span_id":
            span_id,
            "type":
            "TYPE_UNSPECIFIED",
            "attributes":
            _extract_attributes(link_attributes, MAX_LINK_ATTRS),
        })
    return ProtoSpan.Links(link=extracted_links,
                           dropped_links_count=dropped_links)
 def setUp(self):
     self.client_patcher = mock.patch(
         "opentelemetry.exporter.cloud_trace.TraceServiceClient"
     )
     self.client_patcher.start()
     self.project_id = "PROJECT"
     self.attributes_variety_pack = {
         "str_key": "str_value",
         "bool_key": False,
         "double_key": 1.421,
         "int_key": 123,
     }
     self.extracted_attributes_variety_pack = ProtoSpan.Attributes(
         attribute_map={
             "str_key": AttributeValue(
                 string_value=TruncatableString(
                     value="str_value", truncated_byte_count=0
                 )
             ),
             "bool_key": AttributeValue(bool_value=False),
             "double_key": AttributeValue(
                 string_value=TruncatableString(
                     value="1.4210", truncated_byte_count=0
                 )
             ),
             "int_key": AttributeValue(int_value=123),
         }
     )
 def setUpClass(cls):
     cls.project_id = "PROJECT"
     cls.attributes_variety_pack = {
         "str_key": "str_value",
         "bool_key": False,
         "double_key": 1.421,
         "int_key": 123,
     }
     cls.extracted_attributes_variety_pack = ProtoSpan.Attributes(
         attribute_map={
             "str_key":
             AttributeValue(string_value=TruncatableString(
                 value="str_value", truncated_byte_count=0)),
             "bool_key":
             AttributeValue(bool_value=False),
             "double_key":
             AttributeValue(string_value=TruncatableString(
                 value="1.4210", truncated_byte_count=0)),
             "int_key":
             AttributeValue(int_value=123),
         })
     cls.agent_code = _format_attribute_value(
         "opentelemetry-python {}; google-cloud-trace-exporter {}".format(
             _strip_characters(
                 pkg_resources.get_distribution(
                     "opentelemetry-sdk").version),
             _strip_characters(google_ext_version),
         ))
     cls.example_trace_id = "6e0c63257de34c92bf9efcd03927272e"
     cls.example_span_id = "95bb5edabd45950f"
     cls.example_time_in_ns = 1589919268850900051
     cls.example_time_stamp = _get_time_from_ns(cls.example_time_in_ns)
     cls.str_300 = "a" * 300
     cls.str_256 = "a" * 256
     cls.str_128 = "a" * 128
 def test_add_agent_attribute(self):
     self.assertEqual(
         _extract_attributes({}, num_attrs_limit=4, add_agent_attr=True),
         ProtoSpan.Attributes(
             attribute_map={"g.co/agent": self.agent_code},
             dropped_attributes_count=0,
         ),
     )
示例#10
0
    def test_export(self):
        trace_id = "6e0c63257de34c92bf9efcd03927272e"
        span_id = "95bb5edabd45950f"
        span_datas = [
            Span(
                name="span_name",
                context=SpanContext(
                    trace_id=int(trace_id, 16),
                    span_id=int(span_id, 16),
                    is_remote=False,
                ),
                parent=None,
                kind=SpanKind.INTERNAL,
            )
        ]

        cloud_trace_spans = {
            "name":
            "projects/{}/traces/{}/spans/{}".format(self.project_id, trace_id,
                                                    span_id),
            "span_id":
            span_id,
            "parent_span_id":
            None,
            "display_name":
            TruncatableString(value="span_name", truncated_byte_count=0),
            "attributes":
            ProtoSpan.Attributes(
                attribute_map={
                    "g.co/agent":
                    _format_attribute_value(
                        "opentelemetry-python {}; google-cloud-trace-exporter {}"
                        .format(
                            pkg_resources.get_distribution(
                                "opentelemetry-sdk").version,
                            cloud_trace_version,
                        ))
                }),
            "links":
            None,
            "status":
            None,
            "time_events":
            None,
            "start_time":
            None,
            "end_time":
            None,
        }

        client = mock.Mock()

        exporter = CloudTraceSpanExporter(self.project_id, client=client)

        exporter.export(span_datas)

        client.create_span.assert_called_with(**cloud_trace_spans)
        self.assertTrue(client.create_span.called)
示例#11
0
    def test_extract_attributes(self):
        self.assertEqual(_extract_attributes({}, 4),
                         ProtoSpan.Attributes(attribute_map={}))
        self.assertEqual(
            _extract_attributes(self.attributes_variety_pack, 4),
            self.extracted_attributes_variety_pack,
        )
        # Test ignoring attributes with illegal value type
        self.assertEqual(
            _extract_attributes({"illegal_attribute_value": dict()}, 4),
            ProtoSpan.Attributes(attribute_map={}, dropped_attributes_count=1),
        )

        too_many_attrs = {}
        for attr_key in range(5):
            too_many_attrs[str(attr_key)] = 0
        proto_attrs = _extract_attributes(too_many_attrs, 4)
        self.assertEqual(proto_attrs.dropped_attributes_count, 1)
 def test_extract_label_mapping_attributes(self):
     attributes_labels_mapping = {
         "http.scheme":
         "http",
         "http.host":
         "172.19.0.4:8000",
         "http.method":
         "POST",
         "http.request_content_length":
         321,
         "http.response_content_length":
         123,
         "http.route":
         "/fuzzy/search",
         "http.status_code":
         200,
         "http.url":
         "http://172.19.0.4:8000/fuzzy/search",
         "http.user_agent":
         "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
     }
     extracted_attributes_labels_mapping = ProtoSpan.Attributes(
         attribute_map={
             "/http/client_protocol":
             AttributeValue(string_value=TruncatableString(
                 value="http", truncated_byte_count=0)),
             "/http/host":
             AttributeValue(string_value=TruncatableString(
                 value="172.19.0.4:8000", truncated_byte_count=0)),
             "/http/method":
             AttributeValue(string_value=TruncatableString(
                 value="POST", truncated_byte_count=0)),
             "/http/request/size":
             AttributeValue(int_value=321),
             "/http/response/size":
             AttributeValue(int_value=123),
             "/http/route":
             AttributeValue(string_value=TruncatableString(
                 value="/fuzzy/search", truncated_byte_count=0)),
             "/http/status_code":
             AttributeValue(int_value=200),
             "/http/url":
             AttributeValue(string_value=TruncatableString(
                 value="http://172.19.0.4:8000/fuzzy/search",
                 truncated_byte_count=0,
             )),
             "/http/user_agent":
             AttributeValue(string_value=TruncatableString(
                 value=
                 "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36",
                 truncated_byte_count=0,
             )),
         })
     self.assertEqual(
         _extract_attributes(attributes_labels_mapping, num_attrs_limit=9),
         extracted_attributes_labels_mapping,
     )
 def test_extract_events(self):
     self.assertIsNone(_extract_events([]))
     time_in_ns1 = 1589919268850900051
     time_in_ms_and_ns1 = {"seconds": 1589919268, "nanos": 850899968}
     time_in_ns2 = 1589919438550020326
     time_in_ms_and_ns2 = {"seconds": 1589919438, "nanos": 550020352}
     event1 = Event(
         name="event1",
         attributes=self.attributes_variety_pack,
         timestamp=time_in_ns1,
     )
     event2 = Event(
         name="event2",
         attributes={"illegal_attr_value": dict()},
         timestamp=time_in_ns2,
     )
     self.assertEqual(
         _extract_events([event1, event2]),
         ProtoSpan.TimeEvents(
             time_event=[
                 {
                     "time": time_in_ms_and_ns1,
                     "annotation": {
                         "description": TruncatableString(
                             value="event1", truncated_byte_count=0
                         ),
                         "attributes": self.extracted_attributes_variety_pack,
                     },
                 },
                 {
                     "time": time_in_ms_and_ns2,
                     "annotation": {
                         "description": TruncatableString(
                             value="event2", truncated_byte_count=0
                         ),
                         "attributes": ProtoSpan.Attributes(
                             attribute_map={}, dropped_attributes_count=1
                         ),
                     },
                 },
             ]
         ),
     )
 def test_attribute_key_truncation(self):
     self.assertEqual(
         _extract_attributes({self.str_300: "attr_value"},
                             num_attrs_limit=4),
         ProtoSpan.Attributes(
             attribute_map={
                 self.str_128:
                 AttributeValue(string_value=TruncatableString(
                     value="attr_value", truncated_byte_count=0))
             }),
     )
示例#15
0
 def test_ignore_invalid_attributes(self):
     self.assertEqual(
         _extract_attributes(
             {"illegal_attribute_value": {}, "legal_attribute": 3},
             num_attrs_limit=4,
         ),
         ProtoSpan.Attributes(
             attribute_map={"legal_attribute": AttributeValue(int_value=3)},
             dropped_attributes_count=1,
         ),
     )
 def test_extract_link_with_none_attribute(self):
     link = Link(
         context=SpanContext(
             trace_id=int(self.example_trace_id, 16),
             span_id=int(self.example_span_id, 16),
             is_remote=False,
         ),
         attributes=None,
     )
     self.assertEqual(
         _extract_links([link]),
         ProtoSpan.Links(link=[
             {
                 "trace_id": self.example_trace_id,
                 "span_id": self.example_span_id,
                 "type": "TYPE_UNSPECIFIED",
                 "attributes": ProtoSpan.Attributes(attribute_map={}),
             },
         ]),
     )
示例#17
0
 def test_extract_multiple_events(self):
     event1 = Event(
         name="event1",
         attributes=self.attributes_variety_pack,
         timestamp=self.example_time_in_ns,
     )
     event2_nanos = 1589919438550020326
     event2 = Event(
         name="event2",
         attributes={"illegal_attr_value": dict()},
         timestamp=event2_nanos,
     )
     self.assertEqual(
         _extract_events([event1, event2]),
         ProtoSpan.TimeEvents(
             time_event=[
                 {
                     "time": self.example_time_stamp,
                     "annotation": {
                         "description": TruncatableString(
                             value="event1", truncated_byte_count=0
                         ),
                         "attributes": self.extracted_attributes_variety_pack,
                     },
                 },
                 {
                     "time": _get_time_from_ns(event2_nanos),
                     "annotation": {
                         "description": TruncatableString(
                             value="event2", truncated_byte_count=0
                         ),
                         "attributes": ProtoSpan.Attributes(
                             attribute_map={}, dropped_attributes_count=1
                         ),
                     },
                 },
             ]
         ),
     )
 def test_agent_attribute_priority(self):
     # Drop existing attributes in favor of the agent attribute
     self.assertEqual(
         _extract_attributes(
             {"attribute_key": "attr_value"},
             num_attrs_limit=1,
             add_agent_attr=True,
         ),
         ProtoSpan.Attributes(
             attribute_map={"g.co/agent": self.agent_code},
             dropped_attributes_count=1,
         ),
     )
示例#19
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 test_extract_link_with_none_attribute(self):
     trace_id = "6e0c63257de34c92bf9efcd03927272e"
     span_id = "95bb5edabd45950f"
     link = Link(
         context=SpanContext(
             trace_id=int(trace_id, 16),
             span_id=int(span_id, 16),
             is_remote=False,
         ),
         attributes=None,
     )
     self.assertEqual(
         _extract_links([link]),
         ProtoSpan.Links(
             link=[
                 {
                     "trace_id": trace_id,
                     "span_id": span_id,
                     "type": "TYPE_UNSPECIFIED",
                     "attributes": ProtoSpan.Attributes(attribute_map={}),
                 },
             ]
         ),
     )
 def test_event_name_truncation(self):
     event1 = Event(name=self.str_300,
                    attributes={},
                    timestamp=self.example_time_in_ns)
     self.assertEqual(
         _extract_events([event1]),
         ProtoSpan.TimeEvents(time_event=[
             {
                 "time": self.example_time_stamp,
                 "annotation": {
                     "description":
                     TruncatableString(
                         value=self.str_256,
                         truncated_byte_count=300 - 256,
                     ),
                     "attributes": {},
                 },
             },
         ]),
     )
 def test_too_many_events(self):
     event = Event(name="event",
                   timestamp=self.example_time_in_ns,
                   attributes={})
     too_many_events = [event] * (MAX_NUM_EVENTS + 5)
     self.assertEqual(
         _extract_events(too_many_events),
         ProtoSpan.TimeEvents(
             time_event=[
                 {
                     "time": self.example_time_stamp,
                     "annotation": {
                         "description": TruncatableString(value="event", ),
                         "attributes": {},
                     },
                 },
             ] * MAX_NUM_EVENTS,
             dropped_annotations_count=len(too_many_events) -
             MAX_NUM_EVENTS,
         ),
     )
    def test_export(self):
        trace_id = "6e0c63257de34c92bf9efcd03927272e"
        span_id = "95bb5edabd45950f"
        resource_info = Resource(
            {
                "cloud.account.id": 123,
                "host.id": "host",
                "cloud.zone": "US",
                "cloud.provider": "gcp",
                "gcp.resource_type": "gce_instance",
            }
        )
        span_datas = [
            Span(
                name="span_name",
                context=SpanContext(
                    trace_id=int(trace_id, 16),
                    span_id=int(span_id, 16),
                    is_remote=False,
                ),
                parent=None,
                kind=SpanKind.INTERNAL,
                resource=resource_info,
                attributes={"attr_key": "attr_value"},
            )
        ]

        cloud_trace_spans = {
            "name": "projects/{}/traces/{}/spans/{}".format(
                self.project_id, trace_id, span_id
            ),
            "span_id": span_id,
            "parent_span_id": None,
            "display_name": TruncatableString(
                value="span_name", truncated_byte_count=0
            ),
            "attributes": ProtoSpan.Attributes(
                attribute_map={
                    "g.co/r/gce_instance/zone": _format_attribute_value("US"),
                    "g.co/r/gce_instance/instance_id": _format_attribute_value(
                        "host"
                    ),
                    "g.co/r/gce_instance/project_id": _format_attribute_value(
                        "123"
                    ),
                    "g.co/agent": _format_attribute_value(
                        "opentelemetry-python {}; google-cloud-trace-exporter {}".format(
                            _strip_characters(
                                pkg_resources.get_distribution(
                                    "opentelemetry-sdk"
                                ).version
                            ),
                            _strip_characters(cloud_trace_version),
                        )
                    ),
                    "attr_key": _format_attribute_value("attr_value"),
                }
            ),
            "links": None,
            "status": None,
            "time_events": None,
            "start_time": None,
            "end_time": None,
        }

        client = mock.Mock()

        exporter = CloudTraceSpanExporter(self.project_id, client=client)

        exporter.export(span_datas)

        self.assertTrue(client.batch_write_spans.called)
        client.batch_write_spans.assert_called_with(
            "projects/{}".format(self.project_id), [cloud_trace_spans]
        )
    def test_export(self):
        resource_info = Resource({
            "cloud.account.id": 123,
            "host.id": "host",
            "cloud.zone": "US",
            "cloud.provider": "gcp",
            "gcp.resource_type": "gce_instance",
        })
        span_datas = [
            Span(
                name="span_name",
                context=SpanContext(
                    trace_id=int(self.example_trace_id, 16),
                    span_id=int(self.example_span_id, 16),
                    is_remote=False,
                ),
                parent=None,
                kind=SpanKind.INTERNAL,
                resource=resource_info,
                attributes={"attr_key": "attr_value"},
            )
        ]

        cloud_trace_spans = {
            "name":
            "projects/{}/traces/{}/spans/{}".format(self.project_id,
                                                    self.example_trace_id,
                                                    self.example_span_id),
            "span_id":
            self.example_span_id,
            "parent_span_id":
            None,
            "display_name":
            TruncatableString(value="span_name", truncated_byte_count=0),
            "attributes":
            ProtoSpan.Attributes(
                attribute_map={
                    "g.co/r/gce_instance/zone":
                    _format_attribute_value("US"),
                    "g.co/r/gce_instance/instance_id":
                    _format_attribute_value("host"),
                    "g.co/r/gce_instance/project_id":
                    _format_attribute_value("123"),
                    "g.co/agent":
                    self.agent_code,
                    "attr_key":
                    _format_attribute_value("attr_value"),
                }),
            "links":
            None,
            "status":
            Status(code=StatusCode.UNSET.value),
            "time_events":
            None,
            "start_time":
            None,
            "end_time":
            None,
            # pylint: disable=no-member
            "span_kind":
            ProtoSpan.SpanKind.INTERNAL,
        }

        client = mock.Mock()

        exporter = CloudTraceSpanExporter(self.project_id, client=client)

        exporter.export(span_datas)

        self.assertTrue(client.batch_write_spans.called)
        client.batch_write_spans.assert_called_with(
            "projects/{}".format(self.project_id), [cloud_trace_spans])
    def test_truncate(self):
        """Cloud Trace API imposes limits on the length of many things,
        e.g. strings, number of events, number of attributes. We truncate
        these things before sending it to the API as an optimization.
        """
        str_300 = "a" * 300
        str_256 = "a" * 256
        str_128 = "a" * 128
        self.assertEqual(_truncate_str("aaaa", 1), ("a", 3))
        self.assertEqual(_truncate_str("aaaa", 5), ("aaaa", 0))
        self.assertEqual(_truncate_str("aaaa", 4), ("aaaa", 0))
        self.assertEqual(_truncate_str("中文翻译", 4), ("中", 9))

        self.assertEqual(
            _format_attribute_value(str_300),
            AttributeValue(
                string_value=TruncatableString(
                    value=str_256, truncated_byte_count=300 - 256
                )
            ),
        )

        self.assertEqual(
            _extract_attributes({str_300: str_300}, 4),
            ProtoSpan.Attributes(
                attribute_map={
                    str_128: AttributeValue(
                        string_value=TruncatableString(
                            value=str_256, truncated_byte_count=300 - 256
                        )
                    )
                }
            ),
        )

        time_in_ns1 = 1589919268850900051
        time_in_ms_and_ns1 = {"seconds": 1589919268, "nanos": 850899968}
        event1 = Event(name=str_300, attributes={}, timestamp=time_in_ns1)
        self.assertEqual(
            _extract_events([event1]),
            ProtoSpan.TimeEvents(
                time_event=[
                    {
                        "time": time_in_ms_and_ns1,
                        "annotation": {
                            "description": TruncatableString(
                                value=str_256, truncated_byte_count=300 - 256
                            ),
                            "attributes": {},
                        },
                    },
                ]
            ),
        )

        trace_id = "6e0c63257de34c92bf9efcd03927272e"
        span_id = "95bb5edabd45950f"
        link = Link(
            context=SpanContext(
                trace_id=int(trace_id, 16),
                span_id=int(span_id, 16),
                is_remote=False,
            ),
            attributes={},
        )
        too_many_links = [link] * (MAX_NUM_LINKS + 1)
        self.assertEqual(
            _extract_links(too_many_links),
            ProtoSpan.Links(
                link=[
                    {
                        "trace_id": trace_id,
                        "span_id": span_id,
                        "type": "TYPE_UNSPECIFIED",
                        "attributes": {},
                    }
                ]
                * MAX_NUM_LINKS,
                dropped_links_count=len(too_many_links) - MAX_NUM_LINKS,
            ),
        )

        link_attrs = {}
        for attr_key in range(MAX_LINK_ATTRS + 1):
            link_attrs[str(attr_key)] = 0
        attr_link = Link(
            context=SpanContext(
                trace_id=int(trace_id, 16),
                span_id=int(span_id, 16),
                is_remote=False,
            ),
            attributes=link_attrs,
        )

        proto_link = _extract_links([attr_link])
        self.assertEqual(
            len(proto_link.link[0].attributes.attribute_map), MAX_LINK_ATTRS
        )

        too_many_events = [event1] * (MAX_NUM_EVENTS + 1)
        self.assertEqual(
            _extract_events(too_many_events),
            ProtoSpan.TimeEvents(
                time_event=[
                    {
                        "time": time_in_ms_and_ns1,
                        "annotation": {
                            "description": TruncatableString(
                                value=str_256, truncated_byte_count=300 - 256
                            ),
                            "attributes": {},
                        },
                    },
                ]
                * MAX_NUM_EVENTS,
                dropped_annotations_count=len(too_many_events)
                - MAX_NUM_EVENTS,
            ),
        )

        time_in_ns1 = 1589919268850900051
        event_attrs = {}
        for attr_key in range(MAX_EVENT_ATTRS + 1):
            event_attrs[str(attr_key)] = 0
        proto_events = _extract_events(
            [Event(name="a", attributes=event_attrs, timestamp=time_in_ns1)]
        )
        self.assertEqual(
            len(
                proto_events.time_event[0].annotation.attributes.attribute_map
            ),
            MAX_EVENT_ATTRS,
        )
 def test_extract_empty_attributes(self):
     self.assertEqual(
         _extract_attributes({}, num_attrs_limit=4),
         ProtoSpan.Attributes(attribute_map={}),
     )