We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN 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**



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py`
Content:
```
1 # Copyright The OpenTelemetry Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 """
16 The **OpenTelemetry Rich Console Exporter** provides a span exporter from a batch span processor
17 to print `OpenTelemetry`_ traces using `Rich`_.
18 
19 Installation
20 ------------
21 
22 ::
23 
24     pip install opentelemetry-exporter-richconsole
25 
26 
27 Usage
28 -----
29 
30 The Rich Console Exporter is a console exporter that prints a tree view onto stdout of the traces
31 with the related spans and properties as children of that tree. For the tree view, the Rich
32 Console Exporter should be used with a BatchSpanProcessor. If used within a SimpleSpanProcessor,
33 all spans will be printed in a list.
34 
35 .. code:: python
36 
37     from opentelemetry import trace
38     from opentelemetry.sdk.trace.export import BatchSpanProcessor
39     from opentelemetry.exporter.richconsole import RichConsoleExporter
40     from opentelemetry.sdk.trace import TracerProvider
41 
42     trace.set_tracer_provider(TracerProvider())
43     tracer = trace.get_tracer(__name__)
44 
45     tracer.add_span_processor(BatchSpanProcessor(RichConsoleExporter()))
46 
47 
48 API
49 ---
50 .. _Rich: https://rich.readthedocs.io/
51 .. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/
52 """
53 # pylint: disable=import-error
54 
55 import datetime
56 import typing
57 from typing import Optional
58 
59 from rich.console import Console
60 from rich.syntax import Syntax
61 from rich.text import Text
62 from rich.tree import Tree
63 
64 import opentelemetry.trace
65 from opentelemetry.sdk.trace import ReadableSpan
66 from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
67 from opentelemetry.semconv.trace import SpanAttributes
68 
69 
70 def _ns_to_time(nanoseconds):
71     ts = datetime.datetime.utcfromtimestamp(nanoseconds / 1e9)
72     return ts.strftime("%H:%M:%S.%f")
73 
74 
75 def _child_to_tree(child: Tree, span: ReadableSpan):
76     child.add(
77         Text.from_markup(f"[bold cyan]Kind :[/bold cyan] {span.kind.name}")
78     )
79     if not span.status.is_unset:
80         if not span.status.is_ok:
81             child.add(
82                 Text.from_markup(
83                     f"[bold cyan]Status :[/bold cyan] [red]{span.status.status_code}[/red]"
84                 )
85             )
86         else:
87             child.add(
88                 Text.from_markup(
89                     f"[bold cyan]Status :[/bold cyan] {span.status.status_code}"
90                 )
91             )
92     if span.status.description:
93         child.add(
94             Text.from_markup(
95                 f"[bold cyan]Description :[/bold cyan] {span.status.description}"
96             )
97         )
98 
99     if span.events:
100         events = child.add(
101             label=Text.from_markup("[bold cyan]Events :[/bold cyan] ")
102         )
103         for event in span.events:
104             event_node = events.add(Text(event.name))
105             for key, val in event.attributes.items():
106                 event_node.add(
107                     Text.from_markup(f"[bold cyan]{key} :[/bold cyan] {val}")
108                 )
109     if span.attributes:
110         attributes = child.add(
111             label=Text.from_markup("[bold cyan]Attributes :[/bold cyan] ")
112         )
113         for attribute in span.attributes:
114             if attribute == SpanAttributes.DB_STATEMENT:
115                 attributes.add(
116                     Text.from_markup(f"[bold cyan]{attribute} :[/bold cyan] ")
117                 )
118                 attributes.add(Syntax(span.attributes[attribute], "sql"))
119             else:
120                 attributes.add(
121                     Text.from_markup(
122                         f"[bold cyan]{attribute} :[/bold cyan] {span.attributes[attribute]}"
123                     )
124                 )
125 
126 
127 class RichConsoleSpanExporter(SpanExporter):
128     """Implementation of :class:`SpanExporter` that prints spans to the
129     console.
130 
131     Should be used within a BatchSpanProcessor
132     """
133 
134     def __init__(
135         self,
136         service_name: Optional[str] = None,
137     ):
138         self.service_name = service_name
139         self.console = Console()
140 
141     def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
142         if not spans:
143             return SpanExportResult.SUCCESS
144         tree = Tree(
145             label=f"Trace {opentelemetry.trace.format_trace_id(spans[0].context.trace_id)}"
146         )
147         parents = {}
148         for span in spans:
149             child = tree.add(
150                 label=Text.from_markup(
151                     f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
152                 )
153             )
154             parents[span.context.span_id] = child
155             _child_to_tree(child, span)
156 
157         for span in spans:
158             if span.parent and span.parent.span_id not in parents:
159                 child = tree.add(
160                     label=Text.from_markup(
161                         f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
162                     )
163                 )
164             else:
165                 child = parents[span.parent.span_id].add(
166                     label=Text.from_markup(
167                         f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
168                     )
169                 )
170             parents[span.context.span_id] = child
171             _child_to_tree(child, span)
172 
173         self.console.print(tree)
174         return SpanExportResult.SUCCESS
175
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

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 @@
             _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)

{"golden_diff": "diff --git a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py\n--- a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py\n+++ b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py\n@@ -36,13 +36,13 @@\n \n     from opentelemetry import trace\n     from opentelemetry.sdk.trace.export import BatchSpanProcessor\n-    from opentelemetry.exporter.richconsole import RichConsoleExporter\n+    from opentelemetry.exporter.richconsole import RichConsoleSpanExporter\n     from opentelemetry.sdk.trace import TracerProvider\n \n     trace.set_tracer_provider(TracerProvider())\n     tracer = trace.get_tracer(__name__)\n \n-    tracer.add_span_processor(BatchSpanProcessor(RichConsoleExporter()))\n+    tracer.add_span_processor(BatchSpanProcessor(RichConsoleSpanExporter()))\n \n \n API\n@@ -155,18 +155,19 @@\n             _child_to_tree(child, span)\n \n         for span in spans:\n-            if span.parent and span.parent.span_id not in parents:\n-                child = tree.add(\n+            if span.parent and span.parent.span_id in parents:\n+                child = parents[span.parent.span_id].add(\n                     label=Text.from_markup(\n                         f\"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}\"\n                     )\n                 )\n             else:\n-                child = parents[span.parent.span_id].add(\n+                child = tree.add(\n                     label=Text.from_markup(\n                         f\"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}\"\n                     )\n                 )\n+\n             parents[span.context.span_id] = child\n             _child_to_tree(child, span)\n", "issue": "Bug in RichConsoleSpanExporter\n**Describe your environment**\r\nPython 3.9.7, Snippet from `pipenv graph`:\r\n```\r\nopentelemetry-exporter-richconsole==0.25b2\r\n  - opentelemetry-api [required: ~=1.3, installed: 1.10a0]\r\n  - opentelemetry-sdk [required: ~=1.3, installed: 1.10a0]\r\n    - opentelemetry-api [required: ==1.10a0, installed: 1.10a0]\r\n  - opentelemetry-semantic-conventions [required: ==0.25b2, installed: 0.25b2]\r\n  - rich [required: >=10.0.0, installed: 10.12.0]\r\n    - colorama [required: >=0.4.0,<0.5.0, installed: 0.4.4]\r\n    - commonmark [required: >=0.9.0,<0.10.0, installed: 0.9.1]\r\n    - pygments [required: >=2.6.0,<3.0.0, installed: 2.10.0]\r\n```\r\n\r\n**Steps to reproduce**\r\nGiven this code:\r\n```\r\nfrom opentelemetry import trace\r\nfrom opentelemetry.sdk.resources import SERVICE_NAME, Resource\r\nfrom opentelemetry.exporter.richconsole import RichConsoleSpanExporter\r\nfrom opentelemetry.sdk.trace import TracerProvider\r\nfrom opentelemetry.sdk.trace.export import BatchSpanProcessor\r\nfrom opentelemetry.sdk.trace.export import ConsoleSpanExporter\r\n\r\nAPP_SERVICE_NAME = \"fastapi-goofing\"\r\n\r\ntrace.set_tracer_provider(\r\n    TracerProvider(\r\n        resource=Resource.create({SERVICE_NAME: APP_SERVICE_NAME})\r\n    )\r\n)\r\n\r\nrich_console_exporter = RichConsoleSpanExporter(\r\n    service_name=APP_SERVICE_NAME,\r\n)\r\n\r\n\r\nconsole_exporter = ConsoleSpanExporter(\r\n    service_name=APP_SERVICE_NAME\r\n)\r\n\r\ntrace.get_tracer_provider().add_span_processor(\r\n    BatchSpanProcessor(rich_console_exporter)\r\n    #BatchSpanProcessor(console_exporter)\r\n)\r\ntracer = trace.get_tracer(__name__)\r\n\r\nwith tracer.start_as_current_span(\"foo\"):\r\n    with tracer.start_as_current_span(\"bar\"):\r\n        with tracer.start_as_current_span(\"baz\"):\r\n            print(\"Hello world from OpenTelemetry Python!\")\r\n\r\n\r\n```\r\nThe `RichConsoleSpanExporter` throws this error:\r\n```\r\nException while exporting Span batch.\r\nTraceback (most recent call last):\r\n  File \"/home/trond/Documents/projects/fastapi-goofring/.venv/lib/python3.9/site-packages/opentelemetry/sdk/trace/export/__init__.py\", line 331, in _export_batch\r\n    self.span_exporter.export(self.spans_list[:idx])  # type: ignore\r\n  File \"/home/trond/Documents/projects/fastapi-goofring/.venv/lib/python3.9/site-packages/opentelemetry/exporter/richconsole/__init__.py\", line 166, in export\r\n    child = parents[span.parent.span_id].add(\r\nAttributeError: 'NoneType' object has no attribute 'span_id'\r\n```\r\nIf I replace the Rich exporter with the regular Console exporter, everything runs nicely without problems\r\n\r\n**What is the expected behavior?**\r\nCode runs without exceptions\r\n\r\n**What is the actual behavior?**\r\nException is thrown\r\n\r\n**Additional context**\r\n\r\n\n", "before_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThe **OpenTelemetry Rich Console Exporter** provides a span exporter from a batch span processor\nto print `OpenTelemetry`_ traces using `Rich`_.\n\nInstallation\n------------\n\n::\n\n    pip install opentelemetry-exporter-richconsole\n\n\nUsage\n-----\n\nThe Rich Console Exporter is a console exporter that prints a tree view onto stdout of the traces\nwith the related spans and properties as children of that tree. For the tree view, the Rich\nConsole Exporter should be used with a BatchSpanProcessor. If used within a SimpleSpanProcessor,\nall spans will be printed in a list.\n\n.. code:: python\n\n    from opentelemetry import trace\n    from opentelemetry.sdk.trace.export import BatchSpanProcessor\n    from opentelemetry.exporter.richconsole import RichConsoleExporter\n    from opentelemetry.sdk.trace import TracerProvider\n\n    trace.set_tracer_provider(TracerProvider())\n    tracer = trace.get_tracer(__name__)\n\n    tracer.add_span_processor(BatchSpanProcessor(RichConsoleExporter()))\n\n\nAPI\n---\n.. _Rich: https://rich.readthedocs.io/\n.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/\n\"\"\"\n# pylint: disable=import-error\n\nimport datetime\nimport typing\nfrom typing import Optional\n\nfrom rich.console import Console\nfrom rich.syntax import Syntax\nfrom rich.text import Text\nfrom rich.tree import Tree\n\nimport opentelemetry.trace\nfrom opentelemetry.sdk.trace import ReadableSpan\nfrom opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult\nfrom opentelemetry.semconv.trace import SpanAttributes\n\n\ndef _ns_to_time(nanoseconds):\n    ts = datetime.datetime.utcfromtimestamp(nanoseconds / 1e9)\n    return ts.strftime(\"%H:%M:%S.%f\")\n\n\ndef _child_to_tree(child: Tree, span: ReadableSpan):\n    child.add(\n        Text.from_markup(f\"[bold cyan]Kind :[/bold cyan] {span.kind.name}\")\n    )\n    if not span.status.is_unset:\n        if not span.status.is_ok:\n            child.add(\n                Text.from_markup(\n                    f\"[bold cyan]Status :[/bold cyan] [red]{span.status.status_code}[/red]\"\n                )\n            )\n        else:\n            child.add(\n                Text.from_markup(\n                    f\"[bold cyan]Status :[/bold cyan] {span.status.status_code}\"\n                )\n            )\n    if span.status.description:\n        child.add(\n            Text.from_markup(\n                f\"[bold cyan]Description :[/bold cyan] {span.status.description}\"\n            )\n        )\n\n    if span.events:\n        events = child.add(\n            label=Text.from_markup(\"[bold cyan]Events :[/bold cyan] \")\n        )\n        for event in span.events:\n            event_node = events.add(Text(event.name))\n            for key, val in event.attributes.items():\n                event_node.add(\n                    Text.from_markup(f\"[bold cyan]{key} :[/bold cyan] {val}\")\n                )\n    if span.attributes:\n        attributes = child.add(\n            label=Text.from_markup(\"[bold cyan]Attributes :[/bold cyan] \")\n        )\n        for attribute in span.attributes:\n            if attribute == SpanAttributes.DB_STATEMENT:\n                attributes.add(\n                    Text.from_markup(f\"[bold cyan]{attribute} :[/bold cyan] \")\n                )\n                attributes.add(Syntax(span.attributes[attribute], \"sql\"))\n            else:\n                attributes.add(\n                    Text.from_markup(\n                        f\"[bold cyan]{attribute} :[/bold cyan] {span.attributes[attribute]}\"\n                    )\n                )\n\n\nclass RichConsoleSpanExporter(SpanExporter):\n    \"\"\"Implementation of :class:`SpanExporter` that prints spans to the\n    console.\n\n    Should be used within a BatchSpanProcessor\n    \"\"\"\n\n    def __init__(\n        self,\n        service_name: Optional[str] = None,\n    ):\n        self.service_name = service_name\n        self.console = Console()\n\n    def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:\n        if not spans:\n            return SpanExportResult.SUCCESS\n        tree = Tree(\n            label=f\"Trace {opentelemetry.trace.format_trace_id(spans[0].context.trace_id)}\"\n        )\n        parents = {}\n        for span in spans:\n            child = tree.add(\n                label=Text.from_markup(\n                    f\"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}\"\n                )\n            )\n            parents[span.context.span_id] = child\n            _child_to_tree(child, span)\n\n        for span in spans:\n            if span.parent and span.parent.span_id not in parents:\n                child = tree.add(\n                    label=Text.from_markup(\n                        f\"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}\"\n                    )\n                )\n            else:\n                child = parents[span.parent.span_id].add(\n                    label=Text.from_markup(\n                        f\"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}\"\n                    )\n                )\n            parents[span.context.span_id] = child\n            _child_to_tree(child, span)\n\n        self.console.print(tree)\n        return SpanExportResult.SUCCESS\n", "path": "exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py"}], "after_files": [{"content": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n\"\"\"\nThe **OpenTelemetry Rich Console Exporter** provides a span exporter from a batch span processor\nto print `OpenTelemetry`_ traces using `Rich`_.\n\nInstallation\n------------\n\n::\n\n    pip install opentelemetry-exporter-richconsole\n\n\nUsage\n-----\n\nThe Rich Console Exporter is a console exporter that prints a tree view onto stdout of the traces\nwith the related spans and properties as children of that tree. For the tree view, the Rich\nConsole Exporter should be used with a BatchSpanProcessor. If used within a SimpleSpanProcessor,\nall spans will be printed in a list.\n\n.. code:: python\n\n    from opentelemetry import trace\n    from opentelemetry.sdk.trace.export import BatchSpanProcessor\n    from opentelemetry.exporter.richconsole import RichConsoleSpanExporter\n    from opentelemetry.sdk.trace import TracerProvider\n\n    trace.set_tracer_provider(TracerProvider())\n    tracer = trace.get_tracer(__name__)\n\n    tracer.add_span_processor(BatchSpanProcessor(RichConsoleSpanExporter()))\n\n\nAPI\n---\n.. _Rich: https://rich.readthedocs.io/\n.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/\n\"\"\"\n# pylint: disable=import-error\n\nimport datetime\nimport typing\nfrom typing import Optional\n\nfrom rich.console import Console\nfrom rich.syntax import Syntax\nfrom rich.text import Text\nfrom rich.tree import Tree\n\nimport opentelemetry.trace\nfrom opentelemetry.sdk.trace import ReadableSpan\nfrom opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult\nfrom opentelemetry.semconv.trace import SpanAttributes\n\n\ndef _ns_to_time(nanoseconds):\n    ts = datetime.datetime.utcfromtimestamp(nanoseconds / 1e9)\n    return ts.strftime(\"%H:%M:%S.%f\")\n\n\ndef _child_to_tree(child: Tree, span: ReadableSpan):\n    child.add(\n        Text.from_markup(f\"[bold cyan]Kind :[/bold cyan] {span.kind.name}\")\n    )\n    if not span.status.is_unset:\n        if not span.status.is_ok:\n            child.add(\n                Text.from_markup(\n                    f\"[bold cyan]Status :[/bold cyan] [red]{span.status.status_code}[/red]\"\n                )\n            )\n        else:\n            child.add(\n                Text.from_markup(\n                    f\"[bold cyan]Status :[/bold cyan] {span.status.status_code}\"\n                )\n            )\n    if span.status.description:\n        child.add(\n            Text.from_markup(\n                f\"[bold cyan]Description :[/bold cyan] {span.status.description}\"\n            )\n        )\n\n    if span.events:\n        events = child.add(\n            label=Text.from_markup(\"[bold cyan]Events :[/bold cyan] \")\n        )\n        for event in span.events:\n            event_node = events.add(Text(event.name))\n            for key, val in event.attributes.items():\n                event_node.add(\n                    Text.from_markup(f\"[bold cyan]{key} :[/bold cyan] {val}\")\n                )\n    if span.attributes:\n        attributes = child.add(\n            label=Text.from_markup(\"[bold cyan]Attributes :[/bold cyan] \")\n        )\n        for attribute in span.attributes:\n            if attribute == SpanAttributes.DB_STATEMENT:\n                attributes.add(\n                    Text.from_markup(f\"[bold cyan]{attribute} :[/bold cyan] \")\n                )\n                attributes.add(Syntax(span.attributes[attribute], \"sql\"))\n            else:\n                attributes.add(\n                    Text.from_markup(\n                        f\"[bold cyan]{attribute} :[/bold cyan] {span.attributes[attribute]}\"\n                    )\n                )\n\n\nclass RichConsoleSpanExporter(SpanExporter):\n    \"\"\"Implementation of :class:`SpanExporter` that prints spans to the\n    console.\n\n    Should be used within a BatchSpanProcessor\n    \"\"\"\n\n    def __init__(\n        self,\n        service_name: Optional[str] = None,\n    ):\n        self.service_name = service_name\n        self.console = Console()\n\n    def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:\n        if not spans:\n            return SpanExportResult.SUCCESS\n        tree = Tree(\n            label=f\"Trace {opentelemetry.trace.format_trace_id(spans[0].context.trace_id)}\"\n        )\n        parents = {}\n        for span in spans:\n            child = tree.add(\n                label=Text.from_markup(\n                    f\"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}\"\n                )\n            )\n            parents[span.context.span_id] = child\n            _child_to_tree(child, span)\n\n        for span in spans:\n            if span.parent and span.parent.span_id in parents:\n                child = parents[span.parent.span_id].add(\n                    label=Text.from_markup(\n                        f\"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}\"\n                    )\n                )\n            else:\n                child = tree.add(\n                    label=Text.from_markup(\n                        f\"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}\"\n                    )\n                )\n\n            parents[span.context.span_id] = child\n            _child_to_tree(child, span)\n\n        self.console.print(tree)\n        return SpanExportResult.SUCCESS\n", "path": "exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py"}]}