diff --git a/opentelemetry-sdk/CHANGELOG.md b/opentelemetry-sdk/CHANGELOG.md index 796fa391668..c19f0bc647f 100644 --- a/opentelemetry-sdk/CHANGELOG.md +++ b/opentelemetry-sdk/CHANGELOG.md @@ -10,6 +10,8 @@ ([#1105](https://github.com/open-telemetry/opentelemetry-python/pull/1120)) - Allow for Custom Trace and Span IDs Generation - `IdsGenerator` for TracerProvider ([#1153](https://github.com/open-telemetry/opentelemetry-python/pull/1153)) +- Event attributes are now immutable + ([#1195](https://github.com/open-telemetry/opentelemetry-python/pull/1195)) - Renaming metrics Batcher to Processor ([#1203](https://github.com/open-telemetry/opentelemetry-python/pull/1203)) - Protect access to Span implementation diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py index d0626a65687..4eb6760a9ae 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py @@ -18,12 +18,11 @@ import concurrent.futures import json import logging -import random import threading import traceback from collections import OrderedDict from contextlib import contextmanager -from types import TracebackType +from types import MappingProxyType, TracebackType from typing import ( Any, Callable, @@ -340,6 +339,10 @@ def _filter_attribute_values(attributes: types.Attributes): attributes.pop(attr_key) +def _create_immutable_attributes(attributes): + return MappingProxyType(attributes.copy() if attributes else {}) + + class Span(trace_api.Span): """See `opentelemetry.trace.Span`. @@ -408,6 +411,10 @@ def __init__( 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: @@ -566,8 +573,7 @@ def add_event( timestamp: Optional[int] = None, ) -> None: _filter_attribute_values(attributes) - if not attributes: - attributes = self._new_attributes() + attributes = _create_immutable_attributes(attributes) self._add_event( Event( name=name, diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py index 6a0399c28cd..92dce44e232 100644 --- a/opentelemetry-sdk/tests/trace/test_trace.py +++ b/opentelemetry-sdk/tests/trace/test_trace.py @@ -590,7 +590,6 @@ def test_events(self): root.add_event("event0") # event name and attributes - now = time_ns() root.add_event( "event1", {"name": "pluto", "some_bools": [True, False]} ) @@ -628,6 +627,30 @@ def test_events(self): root.events[3].attributes, {"name": ("original_contents",)} ) + def test_events_are_immutable(self): + event_properties = [ + prop for prop in dir(trace.EventBase) if not prop.startswith("_") + ] + + with self.tracer.start_as_current_span("root") as root: + root.add_event("event0", {"name": ["birthday"]}) + event = root.events[0] + + for prop in event_properties: + with self.assertRaises(AttributeError): + setattr(event, prop, "something") + + def test_event_attributes_are_immutable(self): + with self.tracer.start_as_current_span("root") as root: + root.add_event("event0", {"name": ["birthday"]}) + event = root.events[0] + + with self.assertRaises(TypeError): + event.attributes["name"][0] = "happy" + + with self.assertRaises(TypeError): + event.attributes["name"] = "hello" + def test_invalid_event_attributes(self): self.assertEqual(trace_api.get_current_span(), trace_api.INVALID_SPAN)