Skip to content

Commit bba4b9e

Browse files
tonybaloneyaabmasssrikanthccvalrex
authored
Add a rich console exporter (#686)
* Add a rich console exporter * be more lenient on missing parent spans * Apply suggestions from code review Co-authored-by: Aaron Abbott <[email protected]> * run black over source * patch change by hand * update changelog * remove defunct statement * Clarify the simple/batch span processor * fix f-strings that dont have formatting * clarify span usage and update classifiers * make child_to_tree a private function and rename some variables Co-authored-by: Aaron Abbott <[email protected]> Co-authored-by: Srikanth Chekuri <[email protected]> Co-authored-by: alrex <[email protected]>
1 parent fbb677a commit bba4b9e

File tree

6 files changed

+296
-2
lines changed

6 files changed

+296
-2
lines changed

CHANGELOG.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Added
1313
- `opentelemetry-instrumentation-elasticsearch` Added `response_hook` and `request_hook` callbacks
1414
([#670](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/670))
15-
16-
### Added
1715
- `opentelemetry-instrumentation-redis` added request_hook and response_hook callbacks passed as arguments to the instrument method.
1816
([#669](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/669))
17+
- `opentelemetry-exporter-richconsole` Initial release
18+
([#686](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/686))
1919

2020
### Changed
2121
- `opentelemetry-instrumentation-botocore` Unpatch botocore Endpoint.prepare_request on uninstrument
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
OpenTelemetry Rich Console Exporter
2+
===================================
3+
4+
|pypi|
5+
6+
.. |pypi| image:: https://badge.fury.io/py/opentelemetry-exporter-richconsole.svg
7+
:target: https://pypi.org/project/opentelemetry-exporter-richconsole/
8+
9+
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
10+
tree and all related spans as children within the tree, including properties.
11+
12+
Installation
13+
------------
14+
15+
::
16+
17+
pip install opentelemetry-exporter-richconsole
18+
19+
20+
.. _Rich: https://rich.readthedocs.io/
21+
.. _OpenTelemetry: https://github.com/open-telemetry/opentelemetry-python/
22+
23+
24+
References
25+
----------
26+
27+
* `Rich <https://rich.readthedocs.io/>`_
28+
* `OpenTelemetry Project <https://opentelemetry.io/>`_
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
[metadata]
16+
name = opentelemetry-exporter-richconsole
17+
description = Rich Console Exporter for OpenTelemetry
18+
long_description = file: README.rst
19+
long_description_content_type = text/x-rst
20+
author = OpenTelemetry Authors
21+
author_email = [email protected]
22+
url = https://github.com/open-telemetry/opentelemetry-python-contrib/exporter/opentelemetry-exporter-richconsole
23+
platforms = any
24+
license = Apache-2.0
25+
classifiers =
26+
Development Status :: 4 - Beta
27+
Intended Audience :: Developers
28+
License :: OSI Approved :: Apache Software License
29+
Programming Language :: Python
30+
Programming Language :: Python :: 3
31+
Programming Language :: Python :: 3.6
32+
Programming Language :: Python :: 3.7
33+
Programming Language :: Python :: 3.8
34+
Programming Language :: Python :: 3.9
35+
36+
[options]
37+
python_requires = >=3.6
38+
package_dir=
39+
=src
40+
packages=find_namespace:
41+
install_requires =
42+
rich>=10.0.0
43+
opentelemetry-api ~= 1.3
44+
opentelemetry-sdk ~= 1.3
45+
opentelemetry-semantic-conventions == 0.24b0
46+
47+
[options.packages.find]
48+
where = src
49+
50+
[options.extras_require]
51+
test =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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+
import os
16+
17+
import setuptools
18+
19+
BASE_DIR = os.path.dirname(__file__)
20+
VERSION_FILENAME = os.path.join(
21+
BASE_DIR, "src", "opentelemetry", "exporter", "richconsole", "version.py"
22+
)
23+
PACKAGE_INFO = {}
24+
with open(VERSION_FILENAME) as f:
25+
exec(f.read(), PACKAGE_INFO)
26+
27+
setuptools.setup(version=PACKAGE_INFO["__version__"])
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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, service_name: Optional[str] = None,
136+
):
137+
self.service_name = service_name
138+
self.console = Console()
139+
140+
def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
141+
if not spans:
142+
return SpanExportResult.SUCCESS
143+
tree = Tree(
144+
label=f"Trace {opentelemetry.trace.format_trace_id(spans[0].context.trace_id)}"
145+
)
146+
parents = {}
147+
for span in spans:
148+
child = tree.add(
149+
label=Text.from_markup(
150+
f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
151+
)
152+
)
153+
parents[span.context.span_id] = child
154+
_child_to_tree(child, span)
155+
156+
for span in spans:
157+
if span.parent and span.parent.span_id not in parents:
158+
child = tree.add(
159+
label=Text.from_markup(
160+
f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
161+
)
162+
)
163+
else:
164+
child = parents[span.parent.span_id].add(
165+
label=Text.from_markup(
166+
f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
167+
)
168+
)
169+
parents[span.context.span_id] = child
170+
_child_to_tree(child, span)
171+
172+
self.console.print(tree)
173+
return SpanExportResult.SUCCESS
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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+
__version__ = "0.24b0"

0 commit comments

Comments
 (0)