Repository: open-telemetry/opentelemetry-python

Issue:
Couple span's start_time and end_time with start and end methods
This started as a valid point raised by @toumorokoshi while discussing #358. The SDK `Span` `start_time` and `end_time` can be set by the user manually. This can be a problem because the same attributes are used to determine if the span has started or ended.

Protect these attributes from manual user setting. Raise a warning to the user when an attempt is made to do so.


Discussion:

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
@@ -171,10 +171,18 @@ def __init__(
         else:
             self.links = BoundedList.from_seq(MAX_NUM_LINKS, links)
 
-        self.end_time = None  # type: Optional[int]
-        self.start_time = None  # type: Optional[int]
+        self._end_time = None  # type: Optional[int]
+        self._start_time = None  # type: Optional[int]
         self.instrumentation_info = instrumentation_info
 
+    @property
+    def start_time(self):
+        return self._start_time
+
+    @property
+    def end_time(self):
+        return self._end_time
+
     def __repr__(self):
         return '{}(name="{}", context={})'.format(
             type(self).__name__, self.name, self.context
@@ -243,7 +251,7 @@ def start(self, start_time: Optional[int] = None) -> None:
                 return
             has_started = self.start_time is not None
             if not has_started:
-                self.start_time = (
+                self._start_time = (
                     start_time if start_time is not None else time_ns()
                 )
         if has_started:
@@ -259,7 +267,9 @@ 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:
-                self.end_time = end_time if end_time is not None else time_ns()
+                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


Test patch:
diff --git a/ext/opentelemetry-ext-jaeger/tests/test_jaeger_exporter.py b/ext/opentelemetry-ext-jaeger/tests/test_jaeger_exporter.py
--- a/ext/opentelemetry-ext-jaeger/tests/test_jaeger_exporter.py
+++ b/ext/opentelemetry-ext-jaeger/tests/test_jaeger_exporter.py
@@ -169,18 +169,18 @@ def test_translate_to_jaeger(self):
             trace.Span(name=span_names[2], context=other_context, parent=None),
         ]
 
-        otel_spans[0].start_time = start_times[0]
+        otel_spans[0].start(start_time=start_times[0])
         # added here to preserve order
         otel_spans[0].set_attribute("key_bool", False)
         otel_spans[0].set_attribute("key_string", "hello_world")
         otel_spans[0].set_attribute("key_float", 111.22)
-        otel_spans[0].end_time = end_times[0]
+        otel_spans[0].end(end_time=end_times[0])
 
-        otel_spans[1].start_time = start_times[1]
-        otel_spans[1].end_time = end_times[1]
+        otel_spans[1].start(start_time=start_times[1])
+        otel_spans[1].end(end_time=end_times[1])
 
-        otel_spans[2].start_time = start_times[2]
-        otel_spans[2].end_time = end_times[2]
+        otel_spans[2].start(start_time=start_times[2])
+        otel_spans[2].end(end_time=end_times[2])
 
         # pylint: disable=protected-access
         spans = jaeger_exporter._translate_to_jaeger(otel_spans)
diff --git a/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py b/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py
--- a/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py
+++ b/ext/opentelemetry-ext-zipkin/tests/test_zipkin_exporter.py
@@ -154,18 +154,18 @@ def test_export(self):
             trace.Span(name=span_names[2], context=other_context, parent=None),
         ]
 
-        otel_spans[0].start_time = start_times[0]
+        otel_spans[0].start(start_time=start_times[0])
         # added here to preserve order
         otel_spans[0].set_attribute("key_bool", False)
         otel_spans[0].set_attribute("key_string", "hello_world")
         otel_spans[0].set_attribute("key_float", 111.22)
-        otel_spans[0].end_time = end_times[0]
+        otel_spans[0].end(end_time=end_times[0])
 
-        otel_spans[1].start_time = start_times[1]
-        otel_spans[1].end_time = end_times[1]
+        otel_spans[1].start(start_time=start_times[1])
+        otel_spans[1].end(end_time=end_times[1])
 
-        otel_spans[2].start_time = start_times[2]
-        otel_spans[2].end_time = end_times[2]
+        otel_spans[2].start(start_time=start_times[2])
+        otel_spans[2].end(end_time=end_times[2])
 
         service_name = "test-service"
         local_endpoint = {