Skip to content

Commit 6489bf5

Browse files
authored
Add Span.set_attributes method (open-telemetry#1520)
1 parent b32365b commit 6489bf5

File tree

4 files changed

+56
-23
lines changed

4 files changed

+56
-23
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Added
1010
- Added `end_on_exit` argument to `start_as_current_span`
1111
([#1519](https://github.com/open-telemetry/opentelemetry-python/pull/1519)])
12+
- Add `Span.set_attributes` method to set multiple values with one call
13+
([#1520](https://github.com/open-telemetry/opentelemetry-python/pull/1520))
1214

1315
## [0.17b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v0.17b0) - 2021-01-20
1416

opentelemetry-api/src/opentelemetry/trace/span.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ def get_span_context(self) -> "SpanContext":
4141
A :class:`opentelemetry.trace.SpanContext` with a copy of this span's immutable state.
4242
"""
4343

44+
@abc.abstractmethod
45+
def set_attributes(
46+
self, attributes: typing.Dict[str, types.AttributeValue]
47+
) -> None:
48+
"""Sets Attributes.
49+
50+
Sets Attributes with the key and value passed as arguments dict.
51+
52+
Note: The behavior of `None` value attributes is undefined, and hence strongly discouraged.
53+
"""
54+
4455
@abc.abstractmethod
4556
def set_attribute(self, key: str, value: types.AttributeValue) -> None:
4657
"""Sets an Attribute.
@@ -450,6 +461,11 @@ def is_recording(self) -> bool:
450461
def end(self, end_time: typing.Optional[int] = None) -> None:
451462
pass
452463

464+
def set_attributes(
465+
self, attributes: typing.Dict[str, types.AttributeValue]
466+
) -> None:
467+
pass
468+
453469
def set_attribute(self, key: str, value: types.AttributeValue) -> None:
454470
pass
455471

opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from typing import (
2727
Any,
2828
Callable,
29+
Dict,
2930
Iterator,
3031
MutableSequence,
3132
Optional,
@@ -582,29 +583,35 @@ def to_json(self, indent=4):
582583
def get_span_context(self):
583584
return self.context
584585

585-
def set_attribute(self, key: str, value: types.AttributeValue) -> None:
586-
if not _is_valid_attribute_value(value):
587-
return
588-
589-
if not key:
590-
logger.warning("invalid key (empty or null)")
591-
return
592-
586+
def set_attributes(
587+
self, attributes: Dict[str, types.AttributeValue]
588+
) -> None:
593589
with self._lock:
594590
if self.end_time is not None:
595591
logger.warning("Setting attribute on ended span.")
596592
return
597593

598-
# Freeze mutable sequences defensively
599-
if isinstance(value, MutableSequence):
600-
value = tuple(value)
601-
if isinstance(value, bytes):
602-
try:
603-
value = value.decode()
604-
except ValueError:
605-
logger.warning("Byte attribute could not be decoded.")
606-
return
607-
self.attributes[key] = value
594+
for key, value in attributes.items():
595+
if not _is_valid_attribute_value(value):
596+
continue
597+
598+
if not key:
599+
logger.warning("invalid key `%s` (empty or null)", key)
600+
continue
601+
602+
# Freeze mutable sequences defensively
603+
if isinstance(value, MutableSequence):
604+
value = tuple(value)
605+
if isinstance(value, bytes):
606+
try:
607+
value = value.decode()
608+
except ValueError:
609+
logger.warning("Byte attribute could not be decoded.")
610+
return
611+
self.attributes[key] = value
612+
613+
def set_attribute(self, key: str, value: types.AttributeValue) -> None:
614+
return self.set_attributes({key: value})
608615

609616
@_check_span_ended
610617
def _add_event(self, event: EventBase) -> None:

opentelemetry-sdk/tests/trace/test_trace.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -531,11 +531,14 @@ def test_basic_span(self):
531531

532532
def test_attributes(self):
533533
with self.tracer.start_as_current_span("root") as root:
534-
root.set_attribute("component", "http")
535-
root.set_attribute("http.method", "GET")
536-
root.set_attribute(
537-
"http.url", "https://example.com:779/path/12/?q=d#123"
534+
root.set_attributes(
535+
{
536+
"component": "http",
537+
"http.method": "GET",
538+
"http.url": "https://example.com:779/path/12/?q=d#123",
539+
}
538540
)
541+
539542
root.set_attribute("http.status_code", 200)
540543
root.set_attribute("http.status_text", "OK")
541544
root.set_attribute("misc.pi", 3.14)
@@ -593,6 +596,10 @@ def test_attributes(self):
593596

594597
def test_invalid_attribute_values(self):
595598
with self.tracer.start_as_current_span("root") as root:
599+
root.set_attributes(
600+
{"correct-value": "foo", "non-primitive-data-type": dict()}
601+
)
602+
596603
root.set_attribute("non-primitive-data-type", dict())
597604
root.set_attribute(
598605
"list-of-mixed-data-types-numeric-first",
@@ -609,7 +616,8 @@ def test_invalid_attribute_values(self):
609616
root.set_attribute("", 123)
610617
root.set_attribute(None, 123)
611618

612-
self.assertEqual(len(root.attributes), 0)
619+
self.assertEqual(len(root.attributes), 1)
620+
self.assertEqual(root.attributes["correct-value"], "foo")
613621

614622
def test_byte_type_attribute_value(self):
615623
with self.tracer.start_as_current_span("root") as root:

0 commit comments

Comments
 (0)