Repository: open-telemetry/opentelemetry-python

Issue:
AttributeError: 'NonRecordingSpan' object has no attribute 'parent'
[Environment]
------------------------------------------
python==3.7.1
opentelemetry-api==1.7.1
opentelemetry-exporter-jaeger==1.7.1
opentelemetry-propagator-jaeger==1.7.1
opentelemetry-sdk==1.7.1

OTEL_TRACES_SAMPLER=traceidratio
OTEL_TRACES_SAMPLER_ARG=0

My Code :
![image](https://user-images.githubusercontent.com/16707489/147833466-782cd3df-9490-4a30-b44c-c258681e1931.png)


[Issue]
------------------------------------------
0 rate for TraceIdRatioBased sampler to create the NonRecordingSpan, which with no `parent` attribute. 
Then I try to inject carrier with Jaeger Propagator. Following exception happened:
`
'NonRecordingSpan' object has no attribute 'parent'
 File "/usr/local/lib/python3.7/dist-packages/opentelemetry/propagators/jaeger/__init__.py", line 84, in inject
    span_parent_id = span.parent.span_id if span.parent else 0
`

[expected behavior]
----------------------------------
It should return with empty carrier without any exception.

AttributeError: 'NonRecordingSpan' object has no attribute 'parent'
[Environment]
------------------------------------------
python==3.7.1
opentelemetry-api==1.7.1
opentelemetry-exporter-jaeger==1.7.1
opentelemetry-propagator-jaeger==1.7.1
opentelemetry-sdk==1.7.1

OTEL_TRACES_SAMPLER=traceidratio
OTEL_TRACES_SAMPLER_ARG=0

My Code :
![image](https://user-images.githubusercontent.com/16707489/147833466-782cd3df-9490-4a30-b44c-c258681e1931.png)


[Issue]
------------------------------------------
0 rate for TraceIdRatioBased sampler to create the NonRecordingSpan, which with no `parent` attribute. 
Then I try to inject carrier with Jaeger Propagator. Following exception happened:
`
'NonRecordingSpan' object has no attribute 'parent'
 File "/usr/local/lib/python3.7/dist-packages/opentelemetry/propagators/jaeger/__init__.py", line 84, in inject
    span_parent_id = span.parent.span_id if span.parent else 0
`

[expected behavior]
----------------------------------
It should return with empty carrier without any exception.



Discussion:
We've been running into this as well. Seems like it should be a simple fix, just checking if the parent attribute is present. For now I think I'll work around it by setting a real span if it's a non-recording span.
@bluesliverx this issue is a bit old, do you have a reproducible example that uses the latest version of our components?
> @bluesliverx this issue is a bit old, do you have a reproducible example that uses the latest version of our components?

Nevermind, I see you submitted #2762, thanks!
We've been running into this as well. Seems like it should be a simple fix, just checking if the parent attribute is present. For now I think I'll work around it by setting a real span if it's a non-recording span.
@bluesliverx this issue is a bit old, do you have a reproducible example that uses the latest version of our components?
> @bluesliverx this issue is a bit old, do you have a reproducible example that uses the latest version of our components?

Nevermind, I see you submitted #2762, thanks!

Resolving patch:
diff --git a/propagator/opentelemetry-propagator-jaeger/src/opentelemetry/propagators/jaeger/__init__.py b/propagator/opentelemetry-propagator-jaeger/src/opentelemetry/propagators/jaeger/__init__.py
--- a/propagator/opentelemetry-propagator-jaeger/src/opentelemetry/propagators/jaeger/__init__.py
+++ b/propagator/opentelemetry-propagator-jaeger/src/opentelemetry/propagators/jaeger/__init__.py
@@ -81,7 +81,10 @@ def inject(
         if span_context == trace.INVALID_SPAN_CONTEXT:
             return
 
-        span_parent_id = span.parent.span_id if span.parent else 0
+        # Non-recording spans do not have a parent
+        span_parent_id = (
+            span.parent.span_id if span.is_recording() and span.parent else 0
+        )
         trace_flags = span_context.trace_flags
         if trace_flags.sampled:
             trace_flags |= self.DEBUG_FLAG


Test patch:
diff --git a/propagator/opentelemetry-propagator-jaeger/tests/test_jaeger_propagator.py b/propagator/opentelemetry-propagator-jaeger/tests/test_jaeger_propagator.py
--- a/propagator/opentelemetry-propagator-jaeger/tests/test_jaeger_propagator.py
+++ b/propagator/opentelemetry-propagator-jaeger/tests/test_jaeger_propagator.py
@@ -230,3 +230,13 @@ def test_extract_invalid_uber_trace_id_header_to_implicit_ctx(self):
 
                 ctx = FORMAT.extract(carrier)
                 self.assertDictEqual(Context(), ctx)
+
+    def test_non_recording_span_does_not_crash(self):
+        """Make sure propagator does not crash when working with NonRecordingSpan"""
+        mock_setter = Mock()
+        span = trace_api.NonRecordingSpan(trace_api.SpanContext(1, 1, True))
+        with trace_api.use_span(span, end_on_exit=True):
+            try:
+                FORMAT.inject({}, setter=mock_setter)
+            except Exception as exc:  # pylint: disable=broad-except
+                self.fail(f"Injecting failed for NonRecordingSpan with {exc}")