Repository: open-telemetry/opentelemetry-python

Issue:
Zipking Exporter: Boolean attribute values MUST use lower case strings "true" and "false" 
From [Specs](https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/sdk_exporters/zipkin.md#attribute)
>Primitive types MUST be converted to string using en-US culture settings. Boolean values MUST use lower case strings "true" and "false".


Discussion:

Resolving patch:
diff --git a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py
--- a/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py
+++ b/exporter/opentelemetry-exporter-zipkin/src/opentelemetry/exporter/zipkin/__init__.py
@@ -352,7 +352,9 @@ def _extract_tags_from_dict(self, tags_dict):
         if not tags_dict:
             return tags
         for attribute_key, attribute_value in tags_dict.items():
-            if isinstance(attribute_value, (int, bool, float, str)):
+            if isinstance(attribute_value, bool):
+                value = str(attribute_value).lower()
+            elif isinstance(attribute_value, (int, float, str)):
                 value = str(attribute_value)
             elif isinstance(attribute_value, Sequence):
                 value = self._extract_tag_value_string_from_sequence(
@@ -381,7 +383,9 @@ def _extract_tag_value_string_from_sequence(self, sequence: Sequence):
         defined_max_tag_value_length = self.max_tag_value_length > 0
 
         for element in sequence:
-            if isinstance(element, (int, bool, float, str)):
+            if isinstance(element, bool):
+                tag_value_element = str(element).lower()
+            elif isinstance(element, (int, float, str)):
                 tag_value_element = str(element)
             elif element is None:
                 tag_value_element = None
@@ -407,7 +411,7 @@ def _extract_tag_value_string_from_sequence(self, sequence: Sequence):
         return json.dumps(tag_value_elements, separators=(",", ":"))
 
     def _extract_tags_from_span(self, span: Span):
-        tags = self._extract_tags_from_dict(getattr(span, "attributes", None))
+        tags = self._extract_tags_from_dict(span.attributes)
         if span.resource:
             tags.update(self._extract_tags_from_dict(span.resource.attributes))
         return tags


Test patch:
diff --git a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py
--- a/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py
+++ b/exporter/opentelemetry-exporter-zipkin/tests/test_zipkin_exporter.py
@@ -243,7 +243,7 @@ def test_export_json(self):
                 "localEndpoint": local_endpoint,
                 "kind": span_kind,
                 "tags": {
-                    "key_bool": "False",
+                    "key_bool": "false",
                     "key_string": "hello_world",
                     "key_float": "111.22",
                     "otel.status_code": "ERROR",
@@ -471,11 +471,11 @@ def test_export_json_max_tag_length(self):
         )
         self.assertEqual(
             tags["list5"],
-            '["True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True"]',
+            '["true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true"]',
         )
         self.assertEqual(
             tags["list6"],
-            '["True","True","True","True","True","True","True","True","True","True"]',
+            '["true","true","true","true","true","true","true","true","true","true"]',
         )
         self.assertEqual(
             tags["tuple1"],
@@ -493,11 +493,11 @@ def test_export_json_max_tag_length(self):
         )
         self.assertEqual(
             tags["tuple5"],
-            '["True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True","True"]',
+            '["true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true","true"]',
         )
         self.assertEqual(
             tags["tuple6"],
-            '["True","True","True","True","True","True","True","True","True","True"]',
+            '["true","true","true","true","true","true","true","true","true","true"]',
         )
         self.assertEqual(
             tags["range1"],
@@ -580,14 +580,14 @@ def test_export_json_max_tag_length(self):
         self.assertEqual(tags["list2"], '["a","a"]')
         self.assertEqual(tags["list3"], '["2","2"]')
         self.assertEqual(tags["list4"], '["2","2"]')
-        self.assertEqual(tags["list5"], '["True"]')
-        self.assertEqual(tags["list6"], '["True"]')
+        self.assertEqual(tags["list5"], '["true"]')
+        self.assertEqual(tags["list6"], '["true"]')
         self.assertEqual(tags["tuple1"], '["a","a"]')
         self.assertEqual(tags["tuple2"], '["a","a"]')
         self.assertEqual(tags["tuple3"], '["2","2"]')
         self.assertEqual(tags["tuple4"], '["2","2"]')
-        self.assertEqual(tags["tuple5"], '["True"]')
-        self.assertEqual(tags["tuple6"], '["True"]')
+        self.assertEqual(tags["tuple5"], '["true"]')
+        self.assertEqual(tags["tuple6"], '["true"]')
         self.assertEqual(tags["range1"], '["0","1"]')
         self.assertEqual(tags["range2"], '["0","1"]')
 
@@ -606,14 +606,14 @@ def test_export_json_max_tag_length(self):
         self.assertEqual(tags["list2"], '["a","a"]')
         self.assertEqual(tags["list3"], '["2","2"]')
         self.assertEqual(tags["list4"], '["2","2"]')
-        self.assertEqual(tags["list5"], '["True"]')
-        self.assertEqual(tags["list6"], '["True"]')
+        self.assertEqual(tags["list5"], '["true"]')
+        self.assertEqual(tags["list6"], '["true"]')
         self.assertEqual(tags["tuple1"], '["a","a"]')
         self.assertEqual(tags["tuple2"], '["a","a"]')
         self.assertEqual(tags["tuple3"], '["2","2"]')
         self.assertEqual(tags["tuple4"], '["2","2"]')
-        self.assertEqual(tags["tuple5"], '["True"]')
-        self.assertEqual(tags["tuple6"], '["True"]')
+        self.assertEqual(tags["tuple5"], '["true"]')
+        self.assertEqual(tags["tuple6"], '["true"]')
         self.assertEqual(tags["range1"], '["0","1"]')
         self.assertEqual(tags["range2"], '["0","1"]')
 
@@ -632,14 +632,14 @@ def test_export_json_max_tag_length(self):
         self.assertEqual(tags["list2"], '["a","a"]')
         self.assertEqual(tags["list3"], '["2","2"]')
         self.assertEqual(tags["list4"], '["2","2"]')
-        self.assertEqual(tags["list5"], '["True"]')
-        self.assertEqual(tags["list6"], '["True"]')
+        self.assertEqual(tags["list5"], '["true"]')
+        self.assertEqual(tags["list6"], '["true"]')
         self.assertEqual(tags["tuple1"], '["a","a"]')
         self.assertEqual(tags["tuple2"], '["a","a"]')
         self.assertEqual(tags["tuple3"], '["2","2"]')
         self.assertEqual(tags["tuple4"], '["2","2"]')
-        self.assertEqual(tags["tuple5"], '["True"]')
-        self.assertEqual(tags["tuple6"], '["True"]')
+        self.assertEqual(tags["tuple5"], '["true"]')
+        self.assertEqual(tags["tuple6"], '["true"]')
         self.assertEqual(tags["range1"], '["0","1"]')
         self.assertEqual(tags["range2"], '["0","1"]')
 
@@ -768,7 +768,7 @@ def test_export_protobuf(self):
                     local_endpoint=local_endpoint,
                     kind=span_kind,
                     tags={
-                        "key_bool": "False",
+                        "key_bool": "false",
                         "key_string": "hello_world",
                         "key_float": "111.22",
                         "otel.status_code": "ERROR",