Skip to content

Commit 3641d11

Browse files
rbizosCircleCI
authored and
CircleCI
committed
Fixing RichConsoleExporter to allow for multiple traces at once (open-telemetry#1336)
1 parent 68beed6 commit 3641d11

File tree

3 files changed

+103
-30
lines changed

3 files changed

+103
-30
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- `opentelemetry-instrumentation-django` Fixed bug where auto-instrumentation fails when django is installed and settings are not configured.
1313
([#1369](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1369))
1414
- `opentelemetry-instrumentation-system-metrics` add supports to collect system thread count. ([#1339](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1339))
15+
- `opentelemetry-exporter-richconsole` Fixing RichConsoleExpoter to allow multiple traces, fixing duplicate spans and include resources ([#1336](https://github.com/open-telemetry/opentelemetry-python-contrib/pull/1336))
1516

1617
## [1.13.0-0.34b0](https://github.com/open-telemetry/opentelemetry-python/releases/tag/v1.13.0-0.34b0) - 2022-09-26
1718

exporter/opentelemetry-exporter-richconsole/src/opentelemetry/exporter/richconsole/__init__.py

+51-30
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
import datetime
5656
import typing
57-
from typing import Optional
57+
from typing import Dict, Optional
5858

5959
from rich.console import Console
6060
from rich.syntax import Syntax
@@ -76,6 +76,11 @@ def _child_to_tree(child: Tree, span: ReadableSpan):
7676
child.add(
7777
Text.from_markup(f"[bold cyan]Kind :[/bold cyan] {span.kind.name}")
7878
)
79+
_add_status(child, span)
80+
_child_add_optional_attributes(child, span)
81+
82+
83+
def _add_status(child: Tree, span: ReadableSpan):
7984
if not span.status.is_unset:
8085
if not span.status.is_ok:
8186
child.add(
@@ -96,6 +101,8 @@ def _child_to_tree(child: Tree, span: ReadableSpan):
96101
)
97102
)
98103

104+
105+
def _child_add_optional_attributes(child: Tree, span: ReadableSpan):
99106
if span.events:
100107
events = child.add(
101108
label=Text.from_markup("[bold cyan]Events :[/bold cyan] ")
@@ -122,6 +129,16 @@ def _child_to_tree(child: Tree, span: ReadableSpan):
122129
f"[bold cyan]{attribute} :[/bold cyan] {span.attributes[attribute]}"
123130
)
124131
)
132+
if span.resource:
133+
resources = child.add(
134+
label=Text.from_markup("[bold cyan]Resources :[/bold cyan] ")
135+
)
136+
for resource in span.resource.attributes:
137+
resources.add(
138+
Text.from_markup(
139+
f"[bold cyan]{resource} :[/bold cyan] {span.resource.attributes[resource]}"
140+
)
141+
)
125142

126143

127144
class RichConsoleSpanExporter(SpanExporter):
@@ -141,35 +158,39 @@ def __init__(
141158
def export(self, spans: typing.Sequence[ReadableSpan]) -> SpanExportResult:
142159
if not spans:
143160
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 in parents:
159-
child = parents[span.parent.span_id].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 = tree.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-
)
170161

171-
parents[span.context.span_id] = child
172-
_child_to_tree(child, span)
162+
for tree in self.spans_to_tree(spans).values():
163+
self.console.print(tree)
173164

174-
self.console.print(tree)
175165
return SpanExportResult.SUCCESS
166+
167+
@staticmethod
168+
def spans_to_tree(spans: typing.Sequence[ReadableSpan]) -> Dict[str, Tree]:
169+
trees = {}
170+
parents = {}
171+
spans = list(spans)
172+
while spans:
173+
for span in spans:
174+
if not span.parent:
175+
trace_id = opentelemetry.trace.format_trace_id(
176+
span.context.trace_id
177+
)
178+
trees[trace_id] = Tree(label=f"Trace {trace_id}")
179+
child = trees[trace_id].add(
180+
label=Text.from_markup(
181+
f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
182+
)
183+
)
184+
parents[span.context.span_id] = child
185+
_child_to_tree(child, span)
186+
spans.remove(span)
187+
elif span.parent and span.parent.span_id in parents:
188+
child = parents[span.parent.span_id].add(
189+
label=Text.from_markup(
190+
f"[blue][{_ns_to_time(span.start_time)}][/blue] [bold]{span.name}[/bold], span {opentelemetry.trace.format_span_id(span.context.span_id)}"
191+
)
192+
)
193+
parents[span.context.span_id] = child
194+
_child_to_tree(child, span)
195+
spans.remove(span)
196+
return trees

exporter/opentelemetry-exporter-richconsole/tests/test_rich_exporter.py

+51
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
# limitations under the License.
1414

1515
import pytest
16+
from rich.tree import Tree
1617

18+
import opentelemetry.trace
1719
from opentelemetry.exporter.richconsole import RichConsoleSpanExporter
1820
from opentelemetry.sdk import trace
1921
from opentelemetry.sdk.trace.export import BatchSpanProcessor
@@ -40,8 +42,57 @@ def fixture_tracer_provider(span_processor):
4042
def test_span_exporter(tracer_provider, span_processor, capsys):
4143
tracer = tracer_provider.get_tracer(__name__)
4244
span = tracer.start_span("test_span")
45+
4346
span.set_attribute("key", "V4LuE")
4447
span.end()
4548
span_processor.force_flush()
4649
captured = capsys.readouterr()
50+
4751
assert "V4LuE" in captured.out
52+
53+
54+
def walk_tree(root: Tree) -> int:
55+
# counts the amount of spans in a tree that contains a span
56+
return sum(walk_tree(child) for child in root.children) + int(
57+
"span" in root.label
58+
)
59+
60+
61+
def test_multiple_traces(tracer_provider):
62+
exporter = RichConsoleSpanExporter()
63+
tracer = tracer_provider.get_tracer(__name__)
64+
with tracer.start_as_current_span("parent_1") as parent_1:
65+
with tracer.start_as_current_span("child_1") as child_1:
66+
pass
67+
68+
with tracer.start_as_current_span("parent_2") as parent_2:
69+
pass
70+
71+
trees = exporter.spans_to_tree((parent_2, parent_1, child_1))
72+
# asserts that we have all traces
73+
assert len(trees) == 2
74+
traceid_1 = opentelemetry.trace.format_trace_id(parent_1.context.trace_id)
75+
76+
assert traceid_1 in trees
77+
78+
assert (
79+
opentelemetry.trace.format_trace_id(parent_2.context.trace_id) in trees
80+
)
81+
82+
# asserts that we have exactly the number of spans we exported
83+
assert sum(walk_tree(tree) for tree in trees.values()) == 3
84+
85+
# assert that the relationship is correct
86+
assert parent_1.name in trees[traceid_1].children[0].label
87+
assert any(
88+
child_1.name in child.label
89+
for child in trees[traceid_1].children[0].children
90+
)
91+
assert not any(
92+
parent_1.name in child.label
93+
for child in trees[traceid_1].children[0].children
94+
)
95+
assert not any(
96+
parent_2.name in child.label
97+
for child in trees[traceid_1].children[0].children
98+
)

0 commit comments

Comments
 (0)