diff --git a/.changelog/5584.fixed b/.changelog/5584.fixed new file mode 100644 index 00000000000..b3659d71411 --- /dev/null +++ b/.changelog/5584.fixed @@ -0,0 +1 @@ +`opentelemetry-sdk`: fix instrumentation scope name matching in the tracer, meter and logger configurators so it is case-sensitive on every platform instead of relying on `fnmatch`'s OS-dependent case handling diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py b/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py index 710bd81de49..dba64abad96 100644 --- a/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py +++ b/opentelemetry-sdk/src/opentelemetry/sdk/util/instrumentation.py @@ -1,7 +1,7 @@ # Copyright The OpenTelemetry Authors # SPDX-License-Identifier: Apache-2.0 -import fnmatch from collections.abc import Callable +from fnmatch import fnmatchcase from json import dumps from typing_extensions import deprecated @@ -166,6 +166,9 @@ def _scope_name_matches_glob( glob_pattern: str, ) -> _InstrumentationScopePredicateT: def inner(scope: InstrumentationScope) -> bool: - return fnmatch.fnmatch(scope.name, glob_pattern) + # Scope name matching is case-sensitive. fnmatchcase is used instead of + # fnmatch so it does not rely on the host platform's filename case + # sensitivity (normcase), which lower-cases both operands on Windows. + return fnmatchcase(scope.name, glob_pattern) return inner diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py index d093873ee59..bfca6ed778f 100644 --- a/opentelemetry-sdk/tests/metrics/test_metrics.py +++ b/opentelemetry-sdk/tests/metrics/test_metrics.py @@ -814,6 +814,22 @@ def test_scope_name_matches_glob_no_match(self): predicate = _scope_name_matches_glob("no.match") self.assertFalse(predicate(InstrumentationScope("my.meter", "1.0"))) + def test_scope_name_matches_glob_is_case_sensitive_on_every_platform(self): + # fnmatch normcases both operands, which lower-cases them on Windows. + # Scope name matching must stay case-sensitive everywhere. + exact = _scope_name_matches_glob("my.meter") + self.assertTrue(exact(InstrumentationScope("my.meter", "1.0"))) + self.assertFalse(exact(InstrumentationScope("My.Meter", "1.0"))) + + wildcard = _scope_name_matches_glob("my.*") + self.assertTrue(wildcard(InstrumentationScope("my.meter", "1.0"))) + self.assertFalse(wildcard(InstrumentationScope("MY.meter", "1.0"))) + + def test_scope_name_matches_glob_pattern_case_is_not_normalized(self): + predicate = _scope_name_matches_glob("MY.*") + self.assertTrue(predicate(InstrumentationScope("MY.meter", "1.0"))) + self.assertFalse(predicate(InstrumentationScope("my.meter", "1.0"))) + @patch("opentelemetry.sdk.metrics._internal.SynchronousMeasurementConsumer") def test_disabled_meter_counter_skips_measurement(self, mock_sync_measurement_consumer): sync_consumer_instance = mock_sync_measurement_consumer()