Repository: open-telemetry/opentelemetry-python-contrib

Issue:
Bug in RichConsoleSpanExporter
**Describe your environment**
Python 3.9.7, Snippet from `pipenv graph`:
```
opentelemetry-exporter-richconsole==0.25b2
  - opentelemetry-api [required: ~=1.3, installed: 1.10a0]
  - opentelemetry-sdk [required: ~=1.3, installed: 1.10a0]
    - opentelemetry-api [required: ==1.10a0, installed: 1.10a0]
  - opentelemetry-semantic-conventions [required: ==0.25b2, installed: 0.25b2]
  - rich [required: >=10.0.0, installed: 10.12.0]
    - colorama [required: >=0.4.0,<0.5.0, installed: 0.4.4]
    - commonmark [required: >=0.9.0,<0.10.0, installed: 0.9.1]
    - pygments [required: >=2.6.0,<3.0.0, installed: 2.10.0]
```

**Steps to reproduce**
Given this code:
```
from opentelemetry import trace
from opentelemetry.sdk.resources import SERVICE_NAME, Resource
from opentelemetry.exporter.richconsole import RichConsoleSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.sdk.trace.export import ConsoleSpanExporter

APP_SERVICE_NAME = "fastapi-goofing"

trace.set_tracer_provider(
    TracerProvider(
        resource=Resource.create({SERVICE_NAME: APP_SERVICE_NAME})
    )
)

rich_console_exporter = RichConsoleSpanExporter(
    service_name=APP_SERVICE_NAME,
)


console_exporter = ConsoleSpanExporter(
    service_name=APP_SERVICE_NAME
)

trace.get_tracer_provider().add_span_processor(
    BatchSpanProcessor(rich_console_exporter)
    #BatchSpanProcessor(console_exporter)
)
tracer = trace.get_tracer(__name__)

with tracer.start_as_current_span("foo"):
    with tracer.start_as_current_span("bar"):
        with tracer.start_as_current_span("baz"):
            print("Hello world from OpenTelemetry Python!")


```
The `RichConsoleSpanExporter` throws this error:
```
Exception while exporting Span batch.
Traceback (most recent call last):
  File "/home/trond/Documents/projects/fastapi-goofring/.venv/lib/python3.9/site-packages/opentelemetry/sdk/trace/export/__init__.py", line 331, in _export_batch
    self.span_exporter.export(self.spans_list[:idx])  # type: ignore
  File "/home/trond/Documents/projects/fastapi-goofring/.venv/lib/python3.9/site-packages/opentelemetry/exporter/richconsole/__init__.py", line 166, in export
    child = parents[span.parent.span_id].add(
AttributeError: 'NoneType' object has no attribute 'span_id'
```
If I replace the Rich exporter with the regular Console exporter, everything runs nicely without problems

**What is the expected behavior?**
Code runs without exceptions

**What is the actual behavior?**
Exception is thrown

**Additional context**




Discussion:
@tonybaloney fyi
> @tonybaloney fyi

Thanks for the report. Will fix today

Resolving patch:
diff --git a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py
--- a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py
+++ b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py
@@ -36,13 +36,13 @@
 
     from opentelemetry import trace
     from opentelemetry.sdk.trace.export import BatchSpanProcessor
-    from opentelemetry.exporter.richconsole import RichConsoleExporter
+    from opentelemetry.exporter.richconsole import RichConsoleSpanExporter
     from opentelemetry.sdk.trace import TracerProvider
 
     trace.set_tracer_provider(TracerProvider())
     tracer = trace.get_tracer(__name__)
 
-    tracer.add_span_processor(BatchSpanProcessor(RichConsoleExporter()))
+    tracer.add_span_processor(BatchSpanProcessor(RichConsoleSpanExporter()))
 
 
 API
@@ -155,18 +155,19 @@ def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
             _child_to_tree(child, span)
 
         for span in spans:
-            if span.parent and span.parent.span_id not in parents:
-                child = tree.add(
+            if span.parent and span.parent.span_id in parents:
+                child = parents[span.parent.span_id].add(
                     label=Text.from_markup(
                         f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
                     )
                 )
             else:
-                child = parents[span.parent.span_id].add(
+                child = tree.add(
                     label=Text.from_markup(
                         f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
                     )
                 )
+
             parents[span.context.span_id] = child
             _child_to_tree(child, span)
 


Test patch:
diff --git a/exporter/opentelemetry-exporter-richconsole/tests/__init__.py b/exporter/opentelemetry-exporter-richconsole/tests/__init__.py
new file mode 100644
diff --git a/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py b/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py
new file mode 100644
--- /dev/null
+++ b/exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py
@@ -0,0 +1,47 @@
+# Copyright The OpenTelemetry Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import pytest
+
+from opentelemetry.exporter.richconsole import RichConsoleSpanExporter
+from opentelemetry.sdk import trace
+from opentelemetry.sdk.trace.export import BatchSpanProcessor
+
+
+@pytest.fixture(name="span_processor")
+def fixture_span_processor():
+    exporter = RichConsoleSpanExporter()
+    span_processor = BatchSpanProcessor(exporter)
+
+    yield span_processor
+
+    span_processor.shutdown()
+
+
+@pytest.fixture(name="tracer_provider")
+def fixture_tracer_provider(span_processor):
+    tracer_provider = trace.TracerProvider()
+    tracer_provider.add_span_processor(span_processor)
+
+    yield tracer_provider
+
+
+def test_span_exporter(tracer_provider, span_processor, capsys):
+    tracer = tracer_provider.get_tracer(__name__)
+    span = tracer.start_span("test_span")
+    span.set_attribute("key", "V4LuE")
+    span.end()
+    span_processor.force_flush()
+    captured = capsys.readouterr()
+    assert "V4LuE" in captured.out