Repository: open-telemetry/opentelemetry-python

Issue:
No way to use_span withouth ending it.
use_span calls `span.end()` at the end, but it is the only way to set the current span. This makes it impossible to make the same span active multiple times.

Related open-telemetry/opentelemetry-specification#238.
No way to use_span withouth ending it.
use_span calls `span.end()` at the end, but it is the only way to set the current span. This makes it impossible to make the same span active multiple times.

Related open-telemetry/opentelemetry-specification#238.


Discussion:
Ooooh, hadn't realized this, thanks for catching it.

Yeah, we definitely need to add an optional method there, to optionally end the `Span`, i.e.

```python
def use_span(self, span, end_on_close=True): # or False as the default?
   # etc
```
I think the default should be False but start_span should call it with True. EDIT: Or the hypothetical start_and_activate method.
Btw, I had tried to summarize some points when we were trying to decide on the overall thing for OpenTracing, and why I'm slightly inclined to actually have `end_on_close=True` for `start_and_activate()` (and `False` for `use_span()`):

https://github.com/opentracing/opentracing-python/issues/107 
Ooooh, hadn't realized this, thanks for catching it.

Yeah, we definitely need to add an optional method there, to optionally end the `Span`, i.e.

```python
def use_span(self, span, end_on_close=True): # or False as the default?
   # etc
```
I think the default should be False but start_span should call it with True. EDIT: Or the hypothetical start_and_activate method.
Btw, I had tried to summarize some points when we were trying to decide on the overall thing for OpenTracing, and why I'm slightly inclined to actually have `end_on_close=True` for `start_and_activate()` (and `False` for `use_span()`):

https://github.com/opentracing/opentracing-python/issues/107 

Resolving patch:
diff --git a/opentelemetry-api/src/opentelemetry/trace/__init__.py b/opentelemetry-api/src/opentelemetry/trace/__init__.py
--- a/opentelemetry-api/src/opentelemetry/trace/__init__.py
+++ b/opentelemetry-api/src/opentelemetry/trace/__init__.py
@@ -415,7 +415,8 @@ def start_span(
         is equivalent to::
 
             span = tracer.create_span(name)
-            with tracer.use_span(span):
+            span.start()
+            with tracer.use_span(span, end_on_exit=True):
                 do_work()
 
         Args:
@@ -472,17 +473,22 @@ def create_span(
         return INVALID_SPAN
 
     @contextmanager  # type: ignore
-    def use_span(self, span: "Span") -> typing.Iterator[None]:
+    def use_span(
+        self, span: "Span", end_on_exit: bool = False
+    ) -> typing.Iterator[None]:
         """Context manager for controlling a span's lifetime.
 
-        Start the given span and set it as the current span in this tracer's
-        context.
+        Set the given span as the current span in this tracer's context.
 
-        On exiting the context manager stop the span and set its parent as the
-        current span.
+        On exiting the context manager set the span that was previously active
+        as the current span (this is usually but not necessarily the parent of
+        the given span). If ``end_on_exit`` is ``True``, then the span is also
+        ended when exiting the context manager.
 
         Args:
             span: The span to start and make current.
+            end_on_exit: Whether to end the span automatically when leaving the
+                context manager.
         """
         # pylint: disable=unused-argument,no-self-use
         yield
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
@@ -423,7 +423,10 @@ def start_span(
         kind: trace_api.SpanKind = trace_api.SpanKind.INTERNAL,
     ) -> typing.Iterator["Span"]:
         """See `opentelemetry.trace.Tracer.start_span`."""
-        with self.use_span(self.create_span(name, parent, kind)) as span:
+
+        span = self.create_span(name, parent, kind)
+        span.start()
+        with self.use_span(span, end_on_exit=True):
             yield span
 
     def create_span(
@@ -460,16 +463,20 @@ def create_span(
         )
 
     @contextmanager
-    def use_span(self, span: "Span") -> typing.Iterator["Span"]:
+    def use_span(
+        self, span: Span, end_on_exit: bool = False
+    ) -> typing.Iterator[Span]:
         """See `opentelemetry.trace.Tracer.use_span`."""
-        span.start()
-        span_snapshot = self._current_span_slot.get()
-        self._current_span_slot.set(span)
         try:
-            yield span
+            span_snapshot = self._current_span_slot.get()
+            self._current_span_slot.set(span)
+            try:
+                yield span
+            finally:
+                self._current_span_slot.set(span_snapshot)
         finally:
-            self._current_span_slot.set(span_snapshot)
-            span.end()
+            if end_on_exit:
+                span.end()
 
     def add_span_processor(self, span_processor: SpanProcessor) -> None:
         """Registers a new :class:`SpanProcessor` for this `Tracer`.


Test patch: