diff --git a/CHANGELOG.md b/CHANGELOG.md
index eccc719960..ffe128aaf3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,10 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- `opentelemetry-instrumentation-elasticsearch` Added `response_hook` and `request_hook` callbacks
([#670](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/670))
-
-### Added
- `opentelemetry-instrumentation-redis` added request_hook and response_hook callbacks passed as arguments to the instrument method.
([#669](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/669))
+- `opentelemetry-exporter-richconsole` Initial release
+ ([#686](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/686))
### Changed
- `opentelemetry-instrumentation-botocore` Unpatch botocore Endpoint.prepare_request on uninstrument
diff --git a/exporter/opentelemetry-exporter-richconsole/README.rst b/exporter/opentelemetry-exporter-richconsole/README.rst
new file mode 100644
index 0000000000..34b5f030db
--- /dev/null
+++ b/exporter/opentelemetry-exporter-richconsole/README.rst
@@ -0,0 +1,28 @@
+OpenTelemetry Rich Console Exporter
+===================================
+
+|pypi|
+
+.. |pypi| image:: https://badge.fury.io/py/opentelemetry-exporter-richconsole.svg
+ :target: https://pypi.org/project/opentelemetry-exporter-richconsole/
+
+This library is a console exporter using the Rich tree view. When used with a batch span processor, the rich console exporter will show the trace as a
+tree and all related spans as children within the tree, including properties.
+
+Installation
+------------
+
+::
+
+ pip install opentelemetry-exporter-richconsole
+
+
+.. _Rich: https://rich.readthedocs.io/
+.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/
+
+
+References
+----------
+
+* `Rich `_
+* `OpenTelemetry Project `_
diff --git a/exporter/opentelemetry-exporter-richconsole/setup.cfg b/exporter/opentelemetry-exporter-richconsole/setup.cfg
new file mode 100644
index 0000000000..436628583d
--- /dev/null
+++ b/exporter/opentelemetry-exporter-richconsole/setup.cfg
@@ -0,0 +1,51 @@
+# 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.
+#
+[metadata]
+name = opentelemetry-exporter-richconsole
+description = Rich Console Exporter for OpenTelemetry
+long_description = file: README.rst
+long_description_content_type = text/x-rst
+author = OpenTelemetry Authors
+author_email = cncf-opentelemetry-contributors@lists.cncf.io
+url = https://github.com/open-telemetry/opentelemetry-python-contrib/exporter/opentelemetry-exporter-richconsole
+platforms = any
+license = Apache-2.0
+classifiers =
+ Development Status :: 4 - Beta
+ Intended Audience :: Developers
+ License :: OSI Approved :: Apache Software License
+ Programming Language :: Python
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.6
+ Programming Language :: Python :: 3.7
+ Programming Language :: Python :: 3.8
+ Programming Language :: Python :: 3.9
+
+[options]
+python_requires = >=3.6
+package_dir=
+ =src
+packages=find_namespace:
+install_requires =
+ rich>=10.0.0
+ opentelemetry-api ~= 1.3
+ opentelemetry-sdk ~= 1.3
+ opentelemetry-semantic-conventions == 0.24b0
+
+[options.packages.find]
+where = src
+
+[options.extras_require]
+test =
diff --git a/exporter/opentelemetry-exporter-richconsole/setup.py b/exporter/opentelemetry-exporter-richconsole/setup.py
new file mode 100644
index 0000000000..4237e6f2f0
--- /dev/null
+++ b/exporter/opentelemetry-exporter-richconsole/setup.py
@@ -0,0 +1,27 @@
+# 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 os
+
+import setuptools
+
+BASE_DIR = os.path.dirname(__file__)
+VERSION_FILENAME = os.path.join(
+ BASE_DIR, "src", "opentelemetry", "exporter", "richconsole", "version.py"
+)
+PACKAGE_INFO = {}
+with open(VERSION_FILENAME) as f:
+ exec(f.read(), PACKAGE_INFO)
+
+setuptools.setup(version=PACKAGE_INFO["__version__"])
diff --git a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py
new file mode 100644
index 0000000000..38cfac0f68
--- /dev/null
+++ b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py
@@ -0,0 +1,173 @@
+# 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.
+
+"""
+The **OpenTelemetry Rich Console Exporter** provides a span exporter from a batch span processor
+to print `OpenTelemetry`_ traces using `Rich`_.
+
+Installation
+------------
+
+::
+
+ pip install opentelemetry-exporter-richconsole
+
+
+Usage
+-----
+
+The Rich Console Exporter is a console exporter that prints a tree view onto stdout of the traces
+with the related spans and properties as children of that tree. For the tree view, the Rich
+Console Exporter should be used with a BatchSpanProcessor. If used within a SimpleSpanProcessor,
+all spans will be printed in a list.
+
+.. code:: python
+
+ from opentelemetry import trace
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
+ from opentelemetry.exporter.richconsole import RichConsoleExporter
+ from opentelemetry.sdk.trace import TracerProvider
+
+ trace.set_tracer_provider(TracerProvider())
+ tracer = trace.get_tracer(__name__)
+
+ tracer.add_span_processor(BatchSpanProcessor(RichConsoleExporter()))
+
+
+API
+---
+.. _Rich: https://rich.readthedocs.io/
+.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/
+"""
+# pylint: disable=import-error
+
+import datetime
+import typing
+from typing import Optional
+
+from rich.console import Console
+from rich.syntax import Syntax
+from rich.text import Text
+from rich.tree import Tree
+
+import opentelemetry.trace
+from opentelemetry.sdk.trace import ReadableSpan
+from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
+from opentelemetry.semconv.trace import SpanAttributes
+
+
+def _ns_to_time(nanoseconds):
+ ts = datetime.datetime.utcfromtimestamp(nanoseconds / 1e9)
+ return ts.strftime("%H:%M:%S.%f")
+
+
+def _child_to_tree(child: Tree, span: ReadableSpan):
+ child.add(
+ Text.from_markup(f"[bold cyan]Kind :[/bold cyan] {span.kind.name}")
+ )
+ if not span.status.is_unset:
+ if not span.status.is_ok:
+ child.add(
+ Text.from_markup(
+ f"[bold cyan]Status :[/bold cyan] [red]{span.status.status_code}[/red]"
+ )
+ )
+ else:
+ child.add(
+ Text.from_markup(
+ f"[bold cyan]Status :[/bold cyan] {span.status.status_code}"
+ )
+ )
+ if span.status.description:
+ child.add(
+ Text.from_markup(
+ f"[bold cyan]Description :[/bold cyan] {span.status.description}"
+ )
+ )
+
+ if span.events:
+ events = child.add(
+ label=Text.from_markup("[bold cyan]Events :[/bold cyan] ")
+ )
+ for event in span.events:
+ event_node = events.add(Text(event.name))
+ for key, val in event.attributes.items():
+ event_node.add(
+ Text.from_markup(f"[bold cyan]{key} :[/bold cyan] {val}")
+ )
+ if span.attributes:
+ attributes = child.add(
+ label=Text.from_markup("[bold cyan]Attributes :[/bold cyan] ")
+ )
+ for attribute in span.attributes:
+ if attribute == SpanAttributes.DB_STATEMENT:
+ attributes.add(
+ Text.from_markup(f"[bold cyan]{attribute} :[/bold cyan] ")
+ )
+ attributes.add(Syntax(span.attributes[attribute], "sql"))
+ else:
+ attributes.add(
+ Text.from_markup(
+ f"[bold cyan]{attribute} :[/bold cyan] {span.attributes[attribute]}"
+ )
+ )
+
+
+class RichConsoleSpanExporter(SpanExporter):
+ """Implementation of :class:`SpanExporter` that prints spans to the
+ console.
+
+ Should be used within a BatchSpanProcessor
+ """
+
+ def __init__(
+ self, service_name: Optional[str] = None,
+ ):
+ self.service_name = service_name
+ self.console = Console()
+
+ def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
+ if not spans:
+ return SpanExportResult.SUCCESS
+ tree = Tree(
+ label=f"Trace {opentelemetry.trace.format_trace_id(spans[0].context.trace_id)}"
+ )
+ parents = {}
+ for span in spans:
+ 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)
+
+ for span in spans:
+ if span.parent and span.parent.span_id not in parents:
+ 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)}"
+ )
+ )
+ else:
+ 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)}"
+ )
+ )
+ parents[span.context.span_id] = child
+ _child_to_tree(child, span)
+
+ self.console.print(tree)
+ return SpanExportResult.SUCCESS
diff --git a/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/version.py b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/version.py
new file mode 100644
index 0000000000..d33bd87ce4
--- /dev/null
+++ b/exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/version.py
@@ -0,0 +1,15 @@
+# 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.
+
+__version__ = "0.24b0"