def test_colorize(self): """ Passing ``colorize=True`` will colorize the output. """ fd = BytesIO() tree = Tree() tree.merge_tasks([action_task, action_task_end]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0, colorize=True) C = COLORS(colored) self.assertThat( fd.getvalue(), ExactlyEquals( u'\n'.join([ C.root(u'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4'), u'\u2514\u2500\u2500 {}'.format( C.success(u'app:action@1/started')), u' \u251c\u2500\u2500 {}: {}'.format( C.prop(u'timestamp'), u'1425356800'), u' \u2514\u2500\u2500 {}'.format( C.success(u'app:action@2/succeeded')), u' \u2514\u2500\u2500 {}: {}'.format( C.prop('timestamp'), u'1425356802'), u'\n', ]).encode('utf-8')))
def test_node(self): """ Task nodes use their own name. """ tree = Tree() tree.merge_tasks([action_task]) node = tree.nodes()[0][1] C = COLORS(colored) get_name = get_name_factory(C) self.assertThat( get_name(node), ExactlyEquals(node.name))
def test_node_failure(self): """ Task nodes indicating failure are colored. """ tree = Tree() tree.merge_tasks([action_task]) node = tree.nodes()[0][1].copy() node.success = False C = COLORS(colored) get_name = get_name_factory(C) self.assertThat( get_name(node), ExactlyEquals(C.failure(node.name)))
def build_task_nodes(files=None, select=None, task_uuid=None, human_readable=True): """ Build the task nodes given some input data, query criteria and formatting options. """ def task_transformers(): if human_readable: yield _convert_timestamp yield json.loads def filter_funcs(): if select is not None: for query in select: yield filter_by_jmespath(query) if task_uuid is not None: yield filter_by_uuid(task_uuid) if files is None: files = [sys.stdin] tree = Tree() tasks = imap(compose(*task_transformers()), chain.from_iterable(files)) return tree.nodes(tree.merge_tasks(tasks, filter_funcs())),
def test_nested(self): """ Render nested tasks in a way that visually represents that nesting. """ fd = StringIO() tree = Tree() tree.merge_tasks([action_task, nested_action_task]) render_task_nodes(fd.write, tree.nodes(), 0) self.assertThat( fd.getvalue(), Equals( 'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' '+-- app:action@1/started\n' ' `-- timestamp: 1425356800\n' ' +-- app:action:nested@1,1/started\n' ' `-- timestamp: 1425356900\n\n'))
def display_task_tree(args): """ Read the input files, apply any command-line-specified behaviour and display the task tree. """ def task_transformers(): if args.human_readable: yield _convert_timestamp yield json.loads def filter_funcs(): if args.select: for query in args.select: yield filter_by_jmespath(query) if args.task_uuid: yield filter_by_uuid(args.task_uuid) if not args.files: args.files.append(sys.stdin) tree = Tree() tasks = imap(compose(*task_transformers()), chain.from_iterable(args.files)) render_task_nodes( write=sys.stdout.write, nodes=tree.nodes(tree.merge_tasks(tasks, filter_funcs())), ignored_task_keys=set(args.ignored_task_keys) or None, field_limit=args.field_limit, )
def build_task_nodes(files=None, select=None, task_uuid=None, start=None, end=None): """ Build the task nodes given some input data, query criteria and formatting options. """ def filter_funcs(): if start: yield filter_by_start_date(start) if end: yield filter_by_end_date(end) if select is not None: for query in select: yield filter_by_jmespath(query) if task_uuid is not None: yield filter_by_uuid(task_uuid) if not files: if PY3: files = [sys.stdin] else: files = [codecs.getreader('utf-8')(sys.stdin)] tree = Tree() tasks = map(json.loads, chain.from_iterable(files)) return tree.nodes(tree.merge_tasks(tasks, filter_funcs()))
def test_nested(self): """ Render nested tasks in a way that visually represents that nesting. """ fd = BytesIO() tree = Tree() tree.merge_tasks([action_task, nested_action_task]) render_task_nodes(fd.write, tree.nodes(), 0) self.assertThat( fd.getvalue(), ExactlyEquals( u'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' u'\u2514\u2500\u2500 app:action@1/started\n' u' \u251c\u2500\u2500 timestamp: 1425356800\n' u' \u2514\u2500\u2500 app:action:nest@1,1/started\n' u' \u2514\u2500\u2500 timestamp: 1425356900\n\n' .encode('utf-8')))
def test_field_limit(self): """ Truncate task values that are longer than the field_limit if specified. """ fd = StringIO() tree = Tree() tree.merge_tasks([message_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=5) self.assertThat( fd.getvalue(), Equals( 'cdeb220d-7605-4d5f-8341-1a170222e308\n' '+-- twisted:log@1\n' ' |-- error: False\n' ' |-- message: Main [...]\n' ' `-- timestamp: 14253 [...]\n\n'))
def test_multiline_field_limit(self): """ When a field limit is specified for task values, only the first of multiple lines is output. """ fd = StringIO() tree = Tree() tree.merge_tasks([multiline_action_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=1000) self.assertThat( fd.getvalue(), Equals( 'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' '+-- app:action@1/started\n' ' |-- message: this is a [...]\n' ' `-- timestamp: 1425356800\n\n'))
def test_dict_data(self): """ Task values that are ``dict``s are rendered as tree elements. """ fd = StringIO() tree = Tree() tree.merge_tasks([dict_action_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0) self.assertThat( fd.getvalue(), Equals( 'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' '+-- app:action@1/started\n' ' |-- some_data:\n' ' `-- a: 42\n' ' `-- timestamp: 1425356800\n\n'))
def test_task_data(self): """ Task data is rendered as tree elements. """ fd = StringIO() tree = Tree() tree.merge_tasks([message_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0) self.assertThat( fd.getvalue(), Equals( 'cdeb220d-7605-4d5f-8341-1a170222e308\n' '+-- twisted:log@1\n' ' |-- error: False\n' ' |-- message: Main loop terminated.\n' ' `-- timestamp: 1425356700\n\n'))
def test_dict_data(self): """ Task values that are ``dict``s are rendered as tree elements. """ fd = BytesIO() tree = Tree() tree.merge_tasks([dict_action_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0) self.assertThat( fd.getvalue(), ExactlyEquals( u'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' u'\u2514\u2500\u2500 app:action@1/started\n' u' \u251c\u2500\u2500 some_data\n' u' \u2502 \u2514\u2500\u2500 a: 42\n' u' \u2514\u2500\u2500 timestamp: 1425356800\n\n' .encode('utf-8')))
def test_task_data(self): """ Task data is rendered as tree elements. """ fd = BytesIO() tree = Tree() tree.merge_tasks([message_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0) self.assertThat( fd.getvalue(), ExactlyEquals( u'cdeb220d-7605-4d5f-8341-1a170222e308\n' u'\u2514\u2500\u2500 twisted:log@1\n' u' \u251c\u2500\u2500 error: False\n' u' \u251c\u2500\u2500 message: Main loop terminated.\n' u' \u2514\u2500\u2500 timestamp: 1425356700\n\n' .encode('utf-8')))
def test_field_limit(self): """ Truncate task values that are longer than the field_limit if specified. """ fd = BytesIO() tree = Tree() tree.merge_tasks([message_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=5) self.assertThat( fd.getvalue(), ExactlyEquals( u'cdeb220d-7605-4d5f-8341-1a170222e308\n' u'\u2514\u2500\u2500 twisted:log@1\n' u' \u251c\u2500\u2500 error: False\n' u' \u251c\u2500\u2500 message: Main \u2026\n' u' \u2514\u2500\u2500 timestamp: 14253\u2026\n\n' .encode('utf-8')))
def test_multiline_field_limit(self): """ When a field limit is specified for task values, only the first of multiple lines is output. """ fd = BytesIO() tree = Tree() tree.merge_tasks([multiline_action_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=1000) self.assertThat( fd.getvalue(), ExactlyEquals( u'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' u'\u2514\u2500\u2500 app:action@1/started\n' u' \u251c\u2500\u2500 message: this is a\u2026\n' u' \u2514\u2500\u2500 timestamp: 1425356800\n\n' .encode('utf-8')))
def _eliottree(logs): """ Render some Eliot log events into a tree-like string. :param list[dict] logs: The Eliot log events to render. These should be dicts like those passed to an Eliot destination. :return bytes: The rendered string. """ tree = Tree() tree.merge_tasks(logs) nodes = tree.nodes() out = BytesIO() render_task_nodes( write=out.write, nodes=nodes, field_limit=0, ) return out.getvalue()
def test_tasks(self): """ Render two tasks of sequential levels, by default most standard Eliot task keys are ignored. """ fd = StringIO() tree = Tree() tree.merge_tasks([action_task, action_task_end]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0) self.assertThat( fd.getvalue(), Equals( 'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' '+-- app:action@1/started\n' ' `-- timestamp: 1425356800\n' ' +-- app:action@2/succeeded\n' ' `-- timestamp: 1425356800\n\n'))
def test_tasks(self): """ Render two tasks of sequential levels, by default most standard Eliot task keys are ignored. """ fd = BytesIO() tree = Tree() tree.merge_tasks([action_task, action_task_end]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0) self.assertThat( fd.getvalue(), ExactlyEquals( u'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' u'\u2514\u2500\u2500 app:action@1/started\n' u' \u251c\u2500\u2500 timestamp: 1425356800\n' u' \u2514\u2500\u2500 app:action@2/succeeded\n' u' \u2514\u2500\u2500 timestamp: 1425356802\n\n' .encode('utf-8')))
def test_tasks_human_readable(self): """ Render two tasks of sequential levels, by default most standard Eliot task keys are ignored, values are formatted to be human readable. """ fd = StringIO() tree = Tree() tree.merge_tasks([action_task, action_task_end]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0, human_readable=True) self.assertThat( fd.getvalue(), Equals( 'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' '+-- app:action@1/started\n' ' `-- timestamp: 2015-03-03 04:26:40\n' ' +-- app:action@2/succeeded\n' ' `-- timestamp: 2015-03-03 04:26:40\n\n'))
def test_ignored_keys(self): """ Task keys can be ignored. """ fd = StringIO() tree = Tree() tree.merge_tasks([action_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0, ignored_task_keys=set(['task_level'])) self.assertThat( fd.getvalue(), Equals( 'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' '+-- app:action@1/started\n' ' |-- action_status: started\n' ' |-- action_type: app:action\n' ' |-- task_uuid: f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' ' `-- timestamp: 1425356800\n\n'))
def test_janky_message(self): """ Task names, UUIDs, keys and values in messages all have control characters escaped. """ fd = BytesIO() tree = Tree() tree.merge_tasks([janky_message_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0) self.assertThat( fd.getvalue(), ExactlyEquals( u'cdeb220d-7605-4d5f-\u241b(08341-1a170222e308\n' u'\u2514\u2500\u2500 M\u241b(0@1\n' u' \u251c\u2500\u2500 er\u241bror: False\n' u' \u251c\u2500\u2500 mes\u240asage: ' u'Main loop\u241b(0terminated.\n' u' \u2514\u2500\u2500 timestamp: 1425356700\n\n' .encode('utf-8')))
def test_janky_action(self): """ Task names, UUIDs, keys and values in actions all have control characters escaped. """ fd = BytesIO() tree = Tree() tree.merge_tasks([janky_action_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0) self.assertThat( fd.getvalue(), ExactlyEquals( u'f3a32bb3-ea6b-457c-\u241b(0aa99-08a3d0491ab4\n' u'\u2514\u2500\u2500 A\u241b(0@1/started\n' u' \u251c\u2500\u2500 \u241b(0\n' u' \u2502 \u2514\u2500\u2500 \u241b(0: nope\n' u' \u251c\u2500\u2500 mes\u240asage: hello\u241b(0world\n' u' \u2514\u2500\u2500 timestamp: 1425356800\u241b(0\n\n' .encode('utf-8')))
def test_ignored_keys(self): """ Task keys can be ignored. """ fd = BytesIO() tree = Tree() tree.merge_tasks([action_task]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0, ignored_task_keys=set(['task_level'])) self.assertThat( fd.getvalue(), ExactlyEquals( u'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' u'\u2514\u2500\u2500 app:action@1/started\n' u' \u251c\u2500\u2500 action_status: started\n' u' \u251c\u2500\u2500 action_type: app:action\n' u' \u251c\u2500\u2500 task_uuid: ' u'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' u' \u2514\u2500\u2500 timestamp: 1425356800\n\n' .encode('utf-8')))
def test_tasks_human_readable(self): """ Render two tasks of sequential levels, by default most standard Eliot task keys are ignored, values are formatted to be human readable. """ fd = BytesIO() tree = Tree() tree.merge_tasks([action_task, action_task_end]) render_task_nodes( write=fd.write, nodes=tree.nodes(), field_limit=0, human_readable=True) self.assertThat( fd.getvalue(), ExactlyEquals( u'f3a32bb3-ea6b-457c-aa99-08a3d0491ab4\n' u'\u2514\u2500\u2500 app:action@1/started\n' u' \u251c\u2500\u2500 timestamp: 2015-03-03 04:26:40\n' u' \u2514\u2500\u2500 app:action@2/succeeded\n' u' \u2514\u2500\u2500 timestamp: 2015-03-03 04:26:42\n' u'\n' .encode('utf-8')))