Repository: open-telemetry/opentelemetry-python

Issue:
Global span limit of 0 does not work
https://github.com/open-telemetry/opentelemetry-python/blob/fb605388dda13933b2242a208e0039518eec01ce/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py#L597

It's the only attributes limit that is enforced as non-zero, without informing the user.

In addition - In the weird case where a user wants no attributes, ever, they'd set the `global_max_attributes = 0`.
If they then don't set the model specific limit for say, span attributes, which defaults to `_DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT`:

- If a model specific limit is set, it will be used. 
  - **Correct, model limit is not set**
- Else if the corresponding global limit is set, it will be used. 
  - **Incorrect**, `global_max_attributes || DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT == _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT`
- ~~Else if the model specific limit has a default value, the default value will be used.~~
- ~~Else if the global limit has a default value, the default value will be used.~~


Discussion:
Looks like a bug. We should be checking if `global_max_attributes` is `None` and then falling  back to the default. PRs welcome :) 

Resolving patch:
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
--- a/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
@@ -594,23 +594,31 @@ def __init__(
             max_attributes, OTEL_ATTRIBUTE_COUNT_LIMIT
         )
         self.max_attributes = (
-            global_max_attributes or _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT
+            global_max_attributes
+            if global_max_attributes is not None
+            else _DEFAULT_OTEL_ATTRIBUTE_COUNT_LIMIT
         )
 
         self.max_span_attributes = self._from_env_if_absent(
             max_span_attributes,
             OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
-            global_max_attributes or _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
+            global_max_attributes
+            if global_max_attributes is not None
+            else _DEFAULT_OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT,
         )
         self.max_event_attributes = self._from_env_if_absent(
             max_event_attributes,
             OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
-            global_max_attributes or _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
+            global_max_attributes
+            if global_max_attributes is not None
+            else _DEFAULT_OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT,
         )
         self.max_link_attributes = self._from_env_if_absent(
             max_link_attributes,
             OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
-            global_max_attributes or _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
+            global_max_attributes
+            if global_max_attributes is not None
+            else _DEFAULT_OTEL_LINK_ATTRIBUTE_COUNT_LIMIT,
         )
 
         # attribute length


Test patch:
diff --git a/opentelemetry-sdk/tests/trace/test_trace.py b/opentelemetry-sdk/tests/trace/test_trace.py
--- a/opentelemetry-sdk/tests/trace/test_trace.py
+++ b/opentelemetry-sdk/tests/trace/test_trace.py
@@ -1594,6 +1594,53 @@ def test_span_no_limits_code(self):
             )
         )
 
+    def test_span_zero_global_limit(self):
+        self._test_span_limits(
+            new_tracer(
+                span_limits=trace.SpanLimits(
+                    max_attributes=0,
+                    max_events=0,
+                    max_links=0,
+                )
+            ),
+            0,
+            0,
+            0,
+            0,
+            0,
+        )
+
+    def test_span_zero_global_nonzero_model(self):
+        self._test_span_limits(
+            new_tracer(
+                span_limits=trace.SpanLimits(
+                    max_attributes=0,
+                    max_events=0,
+                    max_links=0,
+                    max_span_attributes=15,
+                    max_span_attribute_length=25,
+                )
+            ),
+            15,
+            0,
+            0,
+            0,
+            25,
+        )
+
+    def test_span_zero_global_unset_model(self):
+        self._test_span_no_limits(
+            new_tracer(
+                span_limits=trace.SpanLimits(
+                    max_attributes=0,
+                    max_span_attributes=trace.SpanLimits.UNSET,
+                    max_links=trace.SpanLimits.UNSET,
+                    max_events=trace.SpanLimits.UNSET,
+                    max_attribute_length=trace.SpanLimits.UNSET,
+                )
+            )
+        )
+
     def test_dropped_attributes(self):
         span = get_span_with_dropped_attributes_events_links()
         self.assertEqual(1, span.dropped_links)