From 2121b27c3c4dc043a4a1065dbcb9254ddcb54e7b Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 10 Feb 2018 20:54:12 -0500 Subject: [PATCH 1/7] Add rosconsole echo --- clients/rospy/src/rospy/rosconsole.py | 104 ++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/clients/rospy/src/rospy/rosconsole.py b/clients/rospy/src/rospy/rosconsole.py index 0ccf57200e..a41e3c87e8 100644 --- a/clients/rospy/src/rospy/rosconsole.py +++ b/clients/rospy/src/rospy/rosconsole.py @@ -33,15 +33,23 @@ from __future__ import print_function import os +import re import socket import sys +from datetime import datetime +from dateutil.tz import tzlocal + +from argparse import ArgumentParser + import rosgraph import rospy from .logger_level_service_caller import LoggerLevelServiceCaller from .logger_level_service_caller import ROSConsoleException +from rosgraph_msgs.msg import Log + NAME = 'rosconsole' @@ -135,6 +143,99 @@ def _rosconsole_cmd_get(argv): print(logger_level._current_levels[args[1]]) +class RosConsoleEcho(object): + LEVEL_STRING_NO_COLOR = { + Log.DEBUG: 'DEBUG', + Log.INFO : 'INFO ', + Log.WARN : 'WARN ', + Log.ERROR: 'ERROR', + Log.FATAL: 'FATAL', + } + + LEVEL_STRING_COLOR = { + Log.DEBUG: '\033[92mDEBUG\033[0m', + Log.INFO : '\033[97mINFO \033[0m', + Log.WARN : '\033[93mWARN \033[0m', + Log.ERROR: '\033[91mERROR\033[0m', + Log.FATAL: '\033[95mFATAL\033[0m', + } + + STRING_LEVEL = { + 'debug': Log.DEBUG, + 'info' : Log.INFO, + 'warn' : Log.WARN, + 'error': Log.ERROR, + 'fatal': Log.FATAL, + } + + def __init__(self, options): + self._level_string_map = self.LEVEL_STRING_NO_COLOR if options.nocolor else \ + self.LEVEL_STRING_COLOR + + self._filter = re.compile(options.filter) + self._level = self.STRING_LEVEL[options.level.lower()] + self._detail = options.detail + + callback = self._once_callback if options.once else self._callback + rospy.Subscriber(options.topic, Log, callback) + + def _print(self, msg): + print('[ {} ] [\033[1m{}\033[21m]: {}'.format( + self._level_string_map[msg.level], msg.name, msg.msg)) + + if self._detail: + stamp_sec = msg.header.stamp.to_sec() + stamp_tz = datetime.fromtimestamp(stamp_sec, tzlocal()) + + print(' [{} ({:.6f})] [{}]: {}:{}'.format( + stamp_tz, stamp_sec, msg.function, msg.file, msg.line)) + + def _callback(self, msg): + if self._filter.search(msg.name) and msg.level >= self._level: + self._print(msg) + + def _once_callback(self, msg): + self._callback(msg) + rospy.signal_shutdown('Done') + + +def _get_cmd_echo_argparse(prog): + parser = ArgumentParser(prog=prog, description='Print logger messages') + + parser.add_argument('filter', metavar='FILTER', type=str, nargs='?', default='.*', + help='regular expression to filter the logger name (default: %(default)s)') + + parser.add_argument('level', metavar='LEVEL', type=str, nargs='?', default='warn', + choices=RosConsoleEcho.STRING_LEVEL.keys(), + help='minimum logger level to print (default: %(default)s)') + + parser.add_argument('-1', '--once', action='store_true', dest='once', + help='prints one logger message and exits') + + parser.add_argument('--topic', action='store', metavar='TOPIC', + type=str, default='/rosout', dest='topic', + help='topic to read the logger messages from (default: %(default)s)') + + parser.add_argument('--nocolor', action='store_true', help='output without color') + + parser.add_argument('-d', '--detail', action='store_true', help='print full logger details') + + return parser + + +def _rosconsole_cmd_echo(argv): + parser = _get_cmd_echo_argparse(' '.join([os.path.basename(argv[0]), argv[1]])) + args = parser.parse_args(argv[2:]) + + rospy.init_node('rosconsole', anonymous=True) + + rosconsole = RosConsoleEcho(args) + + rospy.spin() + + del rosconsole + + def _fullusage(): print("""rosconsole is a command-line tool for configuring the logger level of ROS nodes. @@ -142,6 +243,7 @@ def _fullusage(): \trosconsole get\tdisplay level for a logger \trosconsole list\tlist loggers for a node \trosconsole set\tset level for a logger +\trosconsole echo\tprint logger messages Type rosconsole -h for more detailed usage, e.g. 'rosconsole list -h' """) @@ -169,6 +271,8 @@ def main(argv=None): _rosconsole_cmd_list(argv) elif command == 'set': _rosconsole_cmd_set(argv) + elif command == 'echo': + _rosconsole_cmd_echo(argv) else: _fullusage() except socket.error as e: From e30cf18438eacf20b6c062fac98724377b8e11f9 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Tue, 13 Feb 2018 19:03:26 -0500 Subject: [PATCH 2/7] Change detail argument to verbose --- clients/rospy/src/rospy/rosconsole.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clients/rospy/src/rospy/rosconsole.py b/clients/rospy/src/rospy/rosconsole.py index a41e3c87e8..aa1c6dbebd 100644 --- a/clients/rospy/src/rospy/rosconsole.py +++ b/clients/rospy/src/rospy/rosconsole.py @@ -174,7 +174,7 @@ def __init__(self, options): self._filter = re.compile(options.filter) self._level = self.STRING_LEVEL[options.level.lower()] - self._detail = options.detail + self._verbose = options.verbose callback = self._once_callback if options.once else self._callback rospy.Subscriber(options.topic, Log, callback) @@ -183,7 +183,7 @@ def _print(self, msg): print('[ {} ] [\033[1m{}\033[21m]: {}'.format( self._level_string_map[msg.level], msg.name, msg.msg)) - if self._detail: + if self._verbose: stamp_sec = msg.header.stamp.to_sec() stamp_tz = datetime.fromtimestamp(stamp_sec, tzlocal()) @@ -218,7 +218,7 @@ def _get_cmd_echo_argparse(prog): parser.add_argument('--nocolor', action='store_true', help='output without color') - parser.add_argument('-d', '--detail', action='store_true', help='print full logger details') + parser.add_argument('-v', '--verbose', action='store_true', help='print full logger details') return parser From 38d7d5842fc8fa8267718ceeffc60cc423583804 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Tue, 13 Feb 2018 19:04:35 -0500 Subject: [PATCH 3/7] Remove explicit del call --- clients/rospy/src/rospy/rosconsole.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/clients/rospy/src/rospy/rosconsole.py b/clients/rospy/src/rospy/rosconsole.py index aa1c6dbebd..7b1883669b 100644 --- a/clients/rospy/src/rospy/rosconsole.py +++ b/clients/rospy/src/rospy/rosconsole.py @@ -233,8 +233,6 @@ def _rosconsole_cmd_echo(argv): rospy.spin() - del rosconsole - def _fullusage(): print("""rosconsole is a command-line tool for configuring the logger level of ROS nodes. From 941e572113cbad48f81182cd2f9041ae1679f429 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Tue, 13 Feb 2018 19:08:10 -0500 Subject: [PATCH 4/7] Use getattr instead of custom STRING_LEVEL --- clients/rospy/src/rospy/rosconsole.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/clients/rospy/src/rospy/rosconsole.py b/clients/rospy/src/rospy/rosconsole.py index 7b1883669b..f16e07c99c 100644 --- a/clients/rospy/src/rospy/rosconsole.py +++ b/clients/rospy/src/rospy/rosconsole.py @@ -160,20 +160,12 @@ class RosConsoleEcho(object): Log.FATAL: '\033[95mFATAL\033[0m', } - STRING_LEVEL = { - 'debug': Log.DEBUG, - 'info' : Log.INFO, - 'warn' : Log.WARN, - 'error': Log.ERROR, - 'fatal': Log.FATAL, - } - def __init__(self, options): self._level_string_map = self.LEVEL_STRING_NO_COLOR if options.nocolor else \ self.LEVEL_STRING_COLOR self._filter = re.compile(options.filter) - self._level = self.STRING_LEVEL[options.level.lower()] + self._level = getattr(Log, options.level.upper()) self._verbose = options.verbose callback = self._once_callback if options.once else self._callback From cbf04de430d815c1435c882f29c8c269a60006c7 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Tue, 13 Feb 2018 19:57:21 -0500 Subject: [PATCH 5/7] Simplify level_string_map init --- clients/rospy/src/rospy/rosconsole.py | 40 +++++++++++++++------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/clients/rospy/src/rospy/rosconsole.py b/clients/rospy/src/rospy/rosconsole.py index f16e07c99c..a7a28c7dd6 100644 --- a/clients/rospy/src/rospy/rosconsole.py +++ b/clients/rospy/src/rospy/rosconsole.py @@ -144,33 +144,37 @@ def _rosconsole_cmd_get(argv): class RosConsoleEcho(object): - LEVEL_STRING_NO_COLOR = { - Log.DEBUG: 'DEBUG', - Log.INFO : 'INFO ', - Log.WARN : 'WARN ', - Log.ERROR: 'ERROR', - Log.FATAL: 'FATAL', - } - - LEVEL_STRING_COLOR = { - Log.DEBUG: '\033[92mDEBUG\033[0m', - Log.INFO : '\033[97mINFO \033[0m', - Log.WARN : '\033[93mWARN \033[0m', - Log.ERROR: '\033[91mERROR\033[0m', - Log.FATAL: '\033[95mFATAL\033[0m', + # See ANSI/VT100 terminal color codes here: + # https://misc.flogisoft.com/bash/tip_colors_and_formatting + LEVEL_COLOR = { + 'DEBUG': 92, # Light green + 'INFO' : 97, # White + 'WARN' : 93, # Light yellow + 'ERROR': 91, # Light red + 'FATAL': 95, # Light magenta } def __init__(self, options): - self._level_string_map = self.LEVEL_STRING_NO_COLOR if options.nocolor else \ - self.LEVEL_STRING_COLOR - self._filter = re.compile(options.filter) self._level = getattr(Log, options.level.upper()) + self._nocolor = options.nocolor self._verbose = options.verbose + self._level_string_map = {getattr(Log, level): self._stringify(level) for level in self.LEVEL_COLOR.keys()} + callback = self._once_callback if options.once else self._callback rospy.Subscriber(options.topic, Log, callback) + def _stringify(self, level): + string = level.ljust(5) + + return string if self._nocolor else '\033[{}m{}\033[0m'.format(self.LEVEL_COLOR[level], string) + + @staticmethod + def get_levels(): + """Get levels sorted by increasing severity.""" + return sorted(RosConsoleEcho.LEVEL_COLOR.keys(), key=lambda level: getattr(Log, level)) + def _print(self, msg): print('[ {} ] [\033[1m{}\033[21m]: {}'.format( self._level_string_map[msg.level], msg.name, msg.msg)) @@ -198,7 +202,7 @@ def _get_cmd_echo_argparse(prog): help='regular expression to filter the logger name (default: %(default)s)') parser.add_argument('level', metavar='LEVEL', type=str, nargs='?', default='warn', - choices=RosConsoleEcho.STRING_LEVEL.keys(), + choices=[level.lower() for level in RosConsoleEcho.get_levels()], help='minimum logger level to print (default: %(default)s)') parser.add_argument('-1', '--once', action='store_true', dest='once', From 47e303bcdbc8d6769aec32cac15ea194c141c7d2 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Thu, 15 Feb 2018 18:17:31 -0500 Subject: [PATCH 6/7] Change level positional arg to flag --- clients/rospy/src/rospy/rosconsole.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clients/rospy/src/rospy/rosconsole.py b/clients/rospy/src/rospy/rosconsole.py index a7a28c7dd6..ac4ab98136 100644 --- a/clients/rospy/src/rospy/rosconsole.py +++ b/clients/rospy/src/rospy/rosconsole.py @@ -201,7 +201,8 @@ def _get_cmd_echo_argparse(prog): parser.add_argument('filter', metavar='FILTER', type=str, nargs='?', default='.*', help='regular expression to filter the logger name (default: %(default)s)') - parser.add_argument('level', metavar='LEVEL', type=str, nargs='?', default='warn', + parser.add_argument('-l', '--level', action='store', metavar='LEVEL', + type=str, default='warn', dest='level', choices=[level.lower() for level in RosConsoleEcho.get_levels()], help='minimum logger level to print (default: %(default)s)') From 8a0d76721290eba1ec4af504267cbff8018a5292 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Mon, 19 Feb 2018 14:02:59 -0500 Subject: [PATCH 7/7] Pre-compute level max length --- clients/rospy/src/rospy/rosconsole.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/clients/rospy/src/rospy/rosconsole.py b/clients/rospy/src/rospy/rosconsole.py index ac4ab98136..118d9d4c02 100644 --- a/clients/rospy/src/rospy/rosconsole.py +++ b/clients/rospy/src/rospy/rosconsole.py @@ -154,6 +154,8 @@ class RosConsoleEcho(object): 'FATAL': 95, # Light magenta } + LEVEL_MAX_LENGTH = max([len(level) for level in LEVEL_COLOR.keys()]) + def __init__(self, options): self._filter = re.compile(options.filter) self._level = getattr(Log, options.level.upper()) @@ -166,9 +168,10 @@ def __init__(self, options): rospy.Subscriber(options.topic, Log, callback) def _stringify(self, level): - string = level.ljust(5) + string = level.ljust(RosConsoleEcho.LEVEL_MAX_LENGTH) - return string if self._nocolor else '\033[{}m{}\033[0m'.format(self.LEVEL_COLOR[level], string) + return string if self._nocolor else \ + '\033[{}m{}\033[0m'.format(self.LEVEL_COLOR[level], string) @staticmethod def get_levels():