Skip to content

Commit

Permalink
sdk: Improve console span exporter (#505)
Browse files Browse the repository at this point in the history
The current version of the exporter prints everything in a single line, making
it difficult to read. It's also missing events, links and attributes.

This commit changes the console span exporter to use multiple lines and also
adds the missing information about attributes, events and links.
  • Loading branch information
mauriciovasquezbernal authored Apr 23, 2020
1 parent 305c1f4 commit 232bfdd
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 20 deletions.
6 changes: 3 additions & 3 deletions docs/examples/basic_tracer/tests/test_tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ def test_basic_tracer(self):
(sys.executable, test_script)
).decode()

self.assertIn('name="foo"', output)
self.assertIn('name="bar"', output)
self.assertIn('name="baz"', output)
self.assertIn('"name": "foo"', output)
self.assertIn('"name": "bar"', output)
self.assertIn('"name": "baz"', output)
2 changes: 1 addition & 1 deletion docs/examples/http/tests/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def test_http(self):
output = subprocess.check_output(
(sys.executable, test_script)
).decode()
self.assertIn('name="/"', output)
self.assertIn('"name": "/"', output)

@classmethod
def teardown_class(cls):
Expand Down
89 changes: 76 additions & 13 deletions opentelemetry-sdk/src/opentelemetry/sdk/trace/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@

import abc
import atexit
import json
import logging
import os
import random
import threading
from collections import OrderedDict
from contextlib import contextmanager
from types import TracebackType
from typing import Iterator, MutableSequence, Optional, Sequence, Tuple, Type
Expand Down Expand Up @@ -276,19 +279,79 @@ def __repr__(self):
type(self).__name__, self.name, self.context
)

def __str__(self):
return (
'{}(name="{}", context={}, kind={}, '
"parent={}, start_time={}, end_time={})"
).format(
type(self).__name__,
self.name,
self.context,
self.kind,
repr(self.parent),
util.ns_to_iso_str(self.start_time) if self.start_time else "None",
util.ns_to_iso_str(self.end_time) if self.end_time else "None",
)
@staticmethod
def _format_context(context):
x_ctx = OrderedDict()
x_ctx["trace_id"] = trace_api.format_trace_id(context.trace_id)
x_ctx["span_id"] = trace_api.format_span_id(context.span_id)
x_ctx["trace_state"] = repr(context.trace_state)
return x_ctx

@staticmethod
def _format_attributes(attributes):
if isinstance(attributes, BoundedDict):
return attributes._dict # pylint: disable=protected-access
return attributes

@staticmethod
def _format_events(events):
f_events = []
for event in events:
f_event = OrderedDict()
f_event["name"] = event.name
f_event["timestamp"] = util.ns_to_iso_str(event.timestamp)
f_event["attributes"] = Span._format_attributes(event.attributes)
f_events.append(f_event)
return f_events

@staticmethod
def _format_links(links):
f_links = []
for link in links:
f_link = OrderedDict()
f_link["context"] = Span._format_context(link.context)
f_link["attributes"] = Span._format_attributes(link.attributes)
f_links.append(f_link)
return f_links

def to_json(self):
parent_id = None
if self.parent is not None:
if isinstance(self.parent, Span):
ctx = self.parent.context
parent_id = trace_api.format_span_id(ctx.span_id)
elif isinstance(self.parent, SpanContext):
parent_id = trace_api.format_span_id(self.parent.span_id)

start_time = None
if self.start_time:
start_time = util.ns_to_iso_str(self.start_time)

end_time = None
if self.end_time:
end_time = util.ns_to_iso_str(self.end_time)

if self.status is not None:
status = OrderedDict()
status["canonical_code"] = str(self.status.canonical_code.name)
if self.status.description:
status["description"] = self.status.description

f_span = OrderedDict()

f_span["name"] = self.name
f_span["context"] = self._format_context(self.context)
f_span["kind"] = str(self.kind)
f_span["parent_id"] = parent_id
f_span["start_time"] = start_time
f_span["end_time"] = end_time
if self.status is not None:
f_span["status"] = status
f_span["attributes"] = self._format_attributes(self.attributes)
f_span["events"] = self._format_events(self.events)
f_span["links"] = self._format_links(self.links)

return json.dumps(f_span, indent=4)

def get_context(self):
return self.context
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ class ConsoleSpanExporter(SpanExporter):
def __init__(
self,
out: typing.IO = sys.stdout,
formatter: typing.Callable[[Span], str] = lambda span: str(span)
formatter: typing.Callable[[Span], str] = lambda span: span.to_json()
+ os.linesep,
):
self.out = out
Expand Down
4 changes: 2 additions & 2 deletions opentelemetry-sdk/tests/trace/export/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,10 +286,10 @@ def test_export(self): # pylint: disable=no-self-use

# Mocking stdout interferes with debugging and test reporting, mock on
# the exporter instance instead.
span = trace.Span("span name", mock.Mock())
span = trace.Span("span name", trace_api.INVALID_SPAN_CONTEXT)
with mock.patch.object(exporter, "out") as mock_stdout:
exporter.export([span])
mock_stdout.write.assert_called_once_with(str(span) + os.linesep)
mock_stdout.write.assert_called_once_with(span.to_json() + os.linesep)
self.assertEqual(mock_stdout.write.call_count, 1)
self.assertEqual(mock_stdout.flush.call_count, 1)

Expand Down

0 comments on commit 232bfdd

Please sign in to comment.