Repository: open-telemetry/opentelemetry-python

Issue:
Warning message `Calling set_status() on an ended span` in examples and tests
**Describe your environment**
Python 3.6, 3.7, 3.8
Code in master, hash: a89bbc882beb44884d174d8a5c08ff36d6c5226e

**Steps to reproduce**
Add logging to stdout in pytest.ini 
```
[pytest]
log_cli = true
...
```
and run tests

OR

Run tracing example https://github.com/open-telemetry/opentelemetry-python/tree/8fa21e6bcbb0b28917039661ca2ba369d0dee6b2#tracing

**What is the expected behavior?**
No warning messages in positive tests and examples.

**What is the actual behavior?**
Multiple warning logs:
`opentelemetry.sdk.trace:__init__.py:287 Calling set_status() on an ended span.`

**Additional context**
It happens because we call `set_status` after ending a span https://github.com/open-telemetry/opentelemetry-python/blob/8fa21e6bcbb0b28917039661ca2ba369d0dee6b2/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py#L268 which check if the (already ended) span is ended  https://github.com/open-telemetry/opentelemetry-python/blob/8fa21e6bcbb0b28917039661ca2ba369d0dee6b2/opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py#L286



Discussion:
@ocelotl As I can see another bug correlated with this: default status of ended span must be 'OK' according to spec https://github.com/open-telemetry/opentelemetry-specification/blob/ccbf36374730488548703a65af40d9051d7ab944/specification/api-tracing.md#set-status, but now it's `None`

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
@@ -66,7 +66,8 @@ def on_end(self, span: "Span") -> None:
         """
 
     def shutdown(self) -> None:
-        """Called when a :class:`opentelemetry.sdk.trace.Tracer` is shutdown."""
+        """Called when a :class:`opentelemetry.sdk.trace.Tracer` is shutdown.
+        """
 
 
 class MultiSpanProcessor(SpanProcessor):
@@ -267,16 +268,17 @@ def end(self, end_time: Optional[int] = None) -> None:
                 raise RuntimeError("Calling end() on a not started span.")
             has_ended = self.end_time is not None
             if not has_ended:
+                if self.status is None:
+                    self.status = Status(canonical_code=StatusCanonicalCode.OK)
+
                 self._end_time = (
                     end_time if end_time is not None else time_ns()
                 )
+
         if has_ended:
             logger.warning("Calling end() on an ended span.")
             return
 
-        if self.status is None:
-            self.set_status(Status(canonical_code=StatusCanonicalCode.OK))
-
         self.span_processor.on_end(self)
 
     def update_name(self, name: str) -> None:


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
@@ -15,6 +15,7 @@
 import shutil
 import subprocess
 import unittest
+from logging import ERROR, WARNING
 from unittest import mock
 
 from opentelemetry import trace as trace_api
@@ -167,8 +168,10 @@ def test_instrumentation_info(self):
 
     def test_invalid_instrumentation_info(self):
         tracer_source = trace.TracerSource()
-        tracer1 = tracer_source.get_tracer("")
-        tracer2 = tracer_source.get_tracer(None)
+        with self.assertLogs(level=ERROR):
+            tracer1 = tracer_source.get_tracer("")
+        with self.assertLogs(level=ERROR):
+            tracer2 = tracer_source.get_tracer(None)
         self.assertEqual(
             tracer1.instrumentation_info, tracer2.instrumentation_info
         )
@@ -516,7 +519,8 @@ def test_start_span(self):
 
         span.start()
         start_time = span.start_time
-        span.start()
+        with self.assertLogs(level=WARNING):
+            span.start()
         self.assertEqual(start_time, span.start_time)
 
         self.assertIs(span.status, None)
@@ -545,36 +549,45 @@ def test_span_override_start_and_end_time(self):
     def test_ended_span(self):
         """"Events, attributes are not allowed after span is ended"""
 
-        with self.tracer.start_as_current_span("root") as root:
-            # everything should be empty at the beginning
-            self.assertEqual(len(root.attributes), 0)
-            self.assertEqual(len(root.events), 0)
-            self.assertEqual(len(root.links), 0)
+        root = self.tracer.start_span("root")
 
-            # call end first time
-            root.end()
-            end_time0 = root.end_time
+        # everything should be empty at the beginning
+        self.assertEqual(len(root.attributes), 0)
+        self.assertEqual(len(root.events), 0)
+        self.assertEqual(len(root.links), 0)
 
-            # call it a second time
+        # call end first time
+        root.end()
+        end_time0 = root.end_time
+
+        # call it a second time
+        with self.assertLogs(level=WARNING):
             root.end()
-            # end time shouldn't be changed
-            self.assertEqual(end_time0, root.end_time)
+        # end time shouldn't be changed
+        self.assertEqual(end_time0, root.end_time)
 
+        with self.assertLogs(level=WARNING):
             root.set_attribute("component", "http")
-            self.assertEqual(len(root.attributes), 0)
+        self.assertEqual(len(root.attributes), 0)
 
+        with self.assertLogs(level=WARNING):
             root.add_event("event1")
-            self.assertEqual(len(root.events), 0)
+        self.assertEqual(len(root.events), 0)
 
+        with self.assertLogs(level=WARNING):
             root.update_name("xxx")
-            self.assertEqual(root.name, "root")
+        self.assertEqual(root.name, "root")
 
-            new_status = trace_api.status.Status(
-                trace_api.status.StatusCanonicalCode.CANCELLED,
-                "Test description",
-            )
+        new_status = trace_api.status.Status(
+            trace_api.status.StatusCanonicalCode.CANCELLED, "Test description",
+        )
+
+        with self.assertLogs(level=WARNING):
             root.set_status(new_status)
-            self.assertIs(root.status, None)
+        self.assertEqual(
+            root.status.canonical_code,
+            trace_api.status.StatusCanonicalCode.OK,
+        )
 
     def test_error_status(self):
         try: