Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
87576cc
Added support for exclude list in attribute keys
saurabh-saraswat Jun 19, 2026
dc14a6d
corrected lint issues
saurabh-saraswat Jun 19, 2026
15256cc
Updated changelog fragment file
saurabh-saraswat Jun 19, 2026
65a652b
Update .changelog/5332.fixed
saurabh-saraswat Jul 1, 2026
498b25b
Renamed changelog fragment
saurabh-saraswat Jul 2, 2026
39ae4d8
Incorporated review comments
saurabh-saraswat Jul 6, 2026
3d15f96
Updated for comment related to assertions
saurabh-saraswat Jul 13, 2026
512dace
Merge branch 'main' into exclude_attribute
saurabh-saraswat Jul 13, 2026
e5e2f3c
Merge branch 'main' into exclude_attribute
saurabh-saraswat Jul 13, 2026
ac4e44a
Merge branch 'main' into exclude_attribute
xrmx Jul 14, 2026
e70b832
Updated measurement based on exclude attributes
saurabh-saraswat Jul 15, 2026
7b4046e
Merge branch 'main' into exclude_attribute
saurabh-saraswat Jul 15, 2026
87d042c
Merge branch 'main' into exclude_attribute
saurabh-saraswat Jul 16, 2026
aa4e89b
Merge branch 'main' into exclude_attribute
saurabh-saraswat Jul 17, 2026
7a36cb9
Apply suggestion from @herin049
saurabh-saraswat Jul 21, 2026
6ae5263
Apply suggestion from @herin049
saurabh-saraswat Jul 21, 2026
8a8d139
Apply suggestion from @herin049
saurabh-saraswat Jul 21, 2026
0172b13
Incorporated review comments related type of attribute_keys, exclude_…
saurabh-saraswat Jul 21, 2026
d1b0626
Merge branch 'main' into exclude_attribute
saurabh-saraswat Jul 27, 2026
70da89a
Based on review comment from Herin, refactored the code
saurabh-saraswat Jul 27, 2026
7251a05
Incorporated review comments related to testcase and cloning of measu…
saurabh-saraswat Jul 30, 2026
6e1d27b
Merge branch 'main' into exclude_attribute
saurabh-saraswat Jul 30, 2026
f5a367d
Merge branch 'main' into exclude_attribute
saurabh-saraswat Jul 31, 2026
494aa4f
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 3, 2026
de4ed2c
Incorporated review comment from lzchen by materializing the iterator…
saurabh-saraswat Aug 4, 2026
4771f97
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 5, 2026
bfacf31
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 6, 2026
fbf6f3e
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 12, 2026
39d17b4
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 13, 2026
b951d97
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 14, 2026
b38d113
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 21, 2026
0b4b678
Merge branch 'main' into exclude_attribute
lzchen Aug 21, 2026
f5765e8
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 24, 2026
f29dd82
Corrected header inclusion
saurabh-saraswat Aug 24, 2026
c799615
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 25, 2026
1c5de9b
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 26, 2026
4f360ac
Merge branch 'main' into exclude_attribute
saurabh-saraswat Aug 31, 2026
093da4e
Updated failing testcase to use hash
saurabh-saraswat Aug 31, 2026
3e0a9f9
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 1, 2026
d190888
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 1, 2026
71a1985
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 2, 2026
050795a
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 4, 2026
6483e65
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 7, 2026
517a8ac
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 8, 2026
32ebe70
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 9, 2026
71b1c3d
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 15, 2026
f4622df
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 17, 2026
12454d2
Update opentelemetry-sdk/src/opentelemetry/sdk/metrics/_internal/view.py
saurabh-saraswat Sep 21, 2026
fd56da6
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 21, 2026
21c7bd5
Incorporated review comments related to specfic exception, eliminatin…
saurabh-saraswat Sep 22, 2026
7596c15
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 22, 2026
0d4bb7d
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 24, 2026
55b392f
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 25, 2026
833951d
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 29, 2026
f0bd477
Merge branch 'main' into exclude_attribute
saurabh-saraswat Sep 30, 2026
915b5f3
Merge branch 'main' into exclude_attribute
saurabh-saraswat Oct 5, 2026
878213e
Merge branch 'main' into exclude_attribute
saurabh-saraswat Oct 6, 2026
bb588d3
Incorprated review comment related to wildcard handling
saurabh-saraswat Oct 7, 2026
3ffd0c7
Merge branch 'main' into exclude_attribute
saurabh-saraswat Oct 7, 2026
feb8082
Merge branch 'main' into exclude_attribute
saurabh-saraswat Oct 13, 2026
282c0ff
Merge branch 'main' into exclude_attribute
saurabh-saraswat Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changelog/5332.added
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
`opentelemetry-sdk`: add support for parameter `exclude_attribute_keys` in `View`
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,10 @@ def _create_view(config: ViewConfig) -> View:
)

attribute_keys: set[str] | None = None
exclude_attribute_keys: set[str] | None = None
Comment thread
saurabh-saraswat marked this conversation as resolved.
Outdated
if stream.attribute_keys is not None:
if stream.attribute_keys.excluded:
_logger.warning(
"attribute_keys.excluded is not supported by the Python SDK View; "
"the exclusion list will be ignored."
)
if stream.attribute_keys.excluded is not None:
exclude_attribute_keys = set(stream.attribute_keys.excluded)
Comment thread
saurabh-saraswat marked this conversation as resolved.
Outdated
if stream.attribute_keys.included is not None:
attribute_keys = set(stream.attribute_keys.included)
Comment thread
saurabh-saraswat marked this conversation as resolved.
Outdated

Expand All @@ -263,6 +261,7 @@ def _create_view(config: ViewConfig) -> View:
description=stream.description,
attribute_keys=attribute_keys,
aggregation=aggregation,
exclude_attribute_keys=exclude_attribute_keys,
)


Expand Down
13 changes: 7 additions & 6 deletions opentelemetry-configuration/tests/test_meter_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -876,15 +876,16 @@ def test_stream_attribute_keys_included(self):
)
self.assertEqual(view._attribute_keys, {"key1", "key2"})

def test_stream_attribute_keys_excluded_logs_warning(self):
def test_stream_attribute_keys_excluded_is_applied(self):
config = self._make_view_config(
stream_kwargs={"attribute_keys": IncludeExclude(excluded=["key1"])}
)
with self.assertLogs(
"opentelemetry.configuration._meter_provider", level="WARNING"
) as log:
create_meter_provider(config)
self.assertTrue(any("excluded" in msg for msg in log.output))
meter_provider = create_meter_provider(config)
views = meter_provider._sdk_config.views
self.assertEqual(len(views), 1)
view = views[0]
self.assertEqual(view._exclude_attribute_keys, frozenset({"key1"}))
self.assertIsNone(view._attribute_keys)

def test_stream_aggregation_drop(self):
view = self._get_view(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ def consume_measurement(
else:
attributes = {}

if self._view._exclude_attribute_keys is not None:
attributes = {
key: value
for key, value in attributes.items()
if key not in self._view._exclude_attribute_keys
}

Comment thread
saurabh-saraswat marked this conversation as resolved.
Outdated
aggr_key = frozenset(attributes.items())

if aggr_key not in self._attributes_aggregation:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ class View:
instrument_unit: This is an instrument matching attribute: the unit the
instrument must have to match the view.

exclude_attribute_keys: This is a metric stream customizing attribute: this is
a set of attribute keys. If not `None` then measurement attributes whose
keys are in ``exclude_attribute_keys`` will be removed before identifying
the metric stream. Applied after ``attribute_keys`` if both are provided.
Comment thread
saurabh-saraswat marked this conversation as resolved.
Outdated


This class is not intended to be subclassed by the user.
"""

Expand All @@ -109,6 +115,7 @@ def __init__(
]
| None = None,
instrument_unit: str | None = None,
exclude_attribute_keys: set[str] | None = None,
Comment thread
saurabh-saraswat marked this conversation as resolved.
Outdated
):
if (
instrument_type
Expand Down Expand Up @@ -147,11 +154,18 @@ def __init__(
self._meter_schema_url = meter_schema_url

self._description = description
self._attribute_keys = attribute_keys
self._attribute_keys = (
frozenset(attribute_keys) if attribute_keys is not None else None
)
self._aggregation = aggregation or self._default_aggregation
self._exemplar_reservoir_factory = (
exemplar_reservoir_factory or _default_reservoir_factory
)
self._exclude_attribute_keys = (
Comment thread
herin049 marked this conversation as resolved.
Outdated
frozenset(exclude_attribute_keys)
if exclude_attribute_keys is not None
else None
)

# pylint: disable=too-many-return-statements
# pylint: disable=too-many-branches
Expand Down
119 changes: 119 additions & 0 deletions opentelemetry-sdk/tests/metrics/test_view_instrument_match.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,125 @@ def setUpClass(cls):
views=[],
Comment thread
saurabh-saraswat marked this conversation as resolved.
)

def test_view_instrument_match_exclude_attribute_keys_affects_aggregation(
self,
):
instrument1 = Mock(name="instrument1")
instrument1.instrumentation_scope = self.mock_instrumentation_scope

mock_aggregation = MagicMock()
mock_aggregation._create_aggregation.return_value = MagicMock()

instrument_class_aggregation = MagicMock()
instrument_class_aggregation.__getitem__.return_value = (
mock_aggregation
)

view = View(
instrument_name="instrument1",
exclude_attribute_keys={"user_id"},
)
match = _ViewInstrumentMatch(
view,
instrument=instrument1,
instrument_class_aggregation=instrument_class_aggregation,
)
measurement1 = Measurement(
value=1,
time_unix_nano=time_ns(),
instrument=instrument1,
context=Context(),
attributes={"method": "GET", "user_id": "u1"},
)
measurement2 = Measurement(
value=2,
time_unix_nano=time_ns(),
instrument=instrument1,
context=Context(),
attributes={"method": "GET", "user_id": "u2"},
)

match.consume_measurement(measurement1)
match.consume_measurement(measurement2)

self.assertEqual(
len(match._attributes_aggregation),
1,
)

aggr_key = list(match._attributes_aggregation.keys())[0]
self.assertDictEqual(
dict(aggr_key),
{"method": "GET"},
)

def test_view_instrument_match_exclude_removes_attributes(self):
instrument1 = Mock(name="instrument1")
instrument1.instrumentation_scope = self.mock_instrumentation_scope
mock_aggregation = MagicMock()
mock_aggregation._create_aggregation.return_value = MagicMock()

instrument_class_aggregation = MagicMock()
instrument_class_aggregation.__getitem__.return_value = (
mock_aggregation
)
view = View(
instrument_name="instrument1",
exclude_attribute_keys={"user_id"},
)
match = _ViewInstrumentMatch(
view,
instrument=instrument1,
instrument_class_aggregation=instrument_class_aggregation,
)
measurement = Measurement(
value=1,
time_unix_nano=time_ns(),
instrument=instrument1,
context=Context(),
attributes={"method": "GET", "user_id": "u1"},
)
match.consume_measurement(measurement)
aggr_key = list(match._attributes_aggregation.keys())[0]
self.assertNotIn(
"user_id",
dict(aggr_key),
)

def test_view_instrument_match_include_then_exclude(self):
instrument1 = Mock(name="instrument1")
instrument1.instrumentation_scope = self.mock_instrumentation_scope
mock_aggregation = MagicMock()
mock_aggregation._create_aggregation.return_value = MagicMock()

instrument_class_aggregation = MagicMock()
instrument_class_aggregation.__getitem__.return_value = (
mock_aggregation
)
view = View(
instrument_name="instrument1",
attribute_keys={"method", "user_id"},
exclude_attribute_keys={"user_id"},
)
match = _ViewInstrumentMatch(
view,
instrument=instrument1,
instrument_class_aggregation=instrument_class_aggregation,
)
measurement = Measurement(
value=1,
time_unix_nano=time_ns(),
instrument=instrument1,
context=Context(),
attributes={"method": "GET", "user_id": "u1", "x": "y"},
)
match.consume_measurement(measurement)
aggr_key = list(match._attributes_aggregation.keys())[0]
self.assertDictEqual(
dict(aggr_key),
{"method": "GET"},
)

def test_consume_measurement(self):
instrument1 = Mock(name="instrument1")
instrument1.instrumentation_scope = self.mock_instrumentation_scope
Expand Down
Loading