Repository: incubator-ariatosca Updated Branches: refs/heads/ARIA-146-Support-colorful-execution-logging 81bd26a7f -> e3b25d7ad
fixed and reworked alot Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/e3b25d7a Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/e3b25d7a Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/e3b25d7a Branch: refs/heads/ARIA-146-Support-colorful-execution-logging Commit: e3b25d7ade6291293a8483e703cf00315d179797 Parents: 81bd26a Author: max-orlov <[email protected]> Authored: Wed Apr 26 16:41:32 2017 +0300 Committer: max-orlov <[email protected]> Committed: Wed Apr 26 16:41:32 2017 +0300 ---------------------------------------------------------------------- aria/cli/color.py | 52 ++++++--- aria/cli/config/config.py | 13 +++ aria/cli/config/config_template.yaml | 10 +- aria/cli/core/aria.py | 6 +- aria/cli/execution_logging.py | 170 +++++++++++++++++++----------- aria/cli/helptexts.py | 1 + 6 files changed, 170 insertions(+), 82 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/e3b25d7a/aria/cli/color.py ---------------------------------------------------------------------- diff --git a/aria/cli/color.py b/aria/cli/color.py index 49675de..99d584f 100644 --- a/aria/cli/color.py +++ b/aria/cli/color.py @@ -15,6 +15,7 @@ from StringIO import StringIO import colorama +import re colorama.init() @@ -25,6 +26,39 @@ def _get_colors(color_type): yield (name.lower(), getattr(color_type, name)) +class StylizedString(object): + def __init__(self, str_, schema=None): + self._str = str_ + self._schema = schema + + def __repr__(self): + if self._schema: + return '{schema}{str}{reset}'.format( + schema=self._schema, str=str(self._str), reset=Color.Style.RESET_ALL) + return self._str + + def __add__(self, other): + return str(self) + other + + def __radd__(self, other): + return other + str(self) + + def color(self, schema): + self._schema = schema + + def replace(self, old, new, **kwargs): + self._str = self._str.replace(str(old), str(new), **kwargs) + + def format(self, *args, **kwargs): + self._str = self._str.format(*args, **kwargs) + + def highlight(self, original_value, schema, original_schema, pattern=None): + if pattern is None: + return + for match in set(re.findall(re.compile(pattern), str(original_value))): + self.replace(match, schema + match + Color.Style.RESET_ALL + original_schema) + + class Color(object): Fore = colorama.Fore Back = colorama.Back @@ -37,17 +71,16 @@ class Color(object): } class Schema(object): - def __init__(self, **kwargs): + def __init__(self, fore=None, back=None, style=None): """ It is possible to provide fore, back and style arguments. each could be either the color is lower case letter, or the actual color from colorama. """ - assert all(arg in Color._colors for arg in kwargs) - self._kwargs = kwargs + self._kwargs = dict(fore=fore, back=back, style=style) self._str = StringIO() for type_, colors in Color._colors.items(): - value = kwargs.pop(type_, None) + value = self._kwargs.get(type_, None) # the former case is if the value is a string, the latter is in case of an object. self._str.write(colors.get(value) or value) @@ -61,12 +94,5 @@ class Color(object): return str(other) + str(self) @classmethod - def stylize(cls, str_to_stylize, schema): - return schema + str(str_to_stylize) + cls.Style.RESET_ALL - - @classmethod - def markup(cls, str_to_stylize, matches, schema): - for group_index in xrange(len(matches.regs)): - match = matches.group(group_index) - str_to_stylize = str_to_stylize.replace(match, schema + match + cls.Back.RESET) - return str_to_stylize + def stylize(cls, *args, **kwargs): + return StylizedString(*args, **kwargs) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/e3b25d7a/aria/cli/config/config.py ---------------------------------------------------------------------- diff --git a/aria/cli/config/config.py b/aria/cli/config/config.py index 99f46ca..01e757c 100644 --- a/aria/cli/config/config.py +++ b/aria/cli/config/config.py @@ -71,3 +71,16 @@ class CliConfig(object): @property def loggers(self): return self._logging.get('loggers', {}) + + @property + def styling_enabled(self): + return self.styles.get('enabled', False) + + @property + def styles(self): + return self._logging.get('execution', {}).get('styles', {}) + + @property + def formats(self): + return self._logging.get('execution', {}).get('formats', {}) + http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/e3b25d7a/aria/cli/config/config_template.yaml ---------------------------------------------------------------------- diff --git a/aria/cli/config/config_template.yaml b/aria/cli/config/config_template.yaml index b8bad3b..85b72d1 100644 --- a/aria/cli/config/config_template.yaml +++ b/aria/cli/config/config_template.yaml @@ -10,15 +10,15 @@ logging: # main logger of the cli. provides basic descriptions for executed operations. aria.cli.main: info - execution_logging: - formatting: + execution: + formats: # According to verbosity level 0 - no verbose. 3 - high verbose 0: '{message}' 1: '{timestamp:%H:%M:%S} | {level[0]} | {message}' 2: '{timestamp:%H:%M:%S} | {level[0]} | {implementation} | {message}' 3: '{timestamp:%H:%M:%S} | {level[0]} | {implementation} | {inputs} | {message}' - styling: + styles: enabled: true level: @@ -45,7 +45,3 @@ logging: error: {'fore': 'red'} marker: 'lightyellow_ex' - final_states: - success: {'fore': 'green'} - cancel: {'fore': 'yellow'} - fail: {'fore': 'red'} http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/e3b25d7a/aria/cli/core/aria.py ---------------------------------------------------------------------- diff --git a/aria/cli/core/aria.py b/aria/cli/core/aria.py index 6436c3e..ed6afa1 100644 --- a/aria/cli/core/aria.py +++ b/aria/cli/core/aria.py @@ -426,13 +426,13 @@ class Options(object): help=helptexts.SERVICE_ID) @staticmethod - def mark_pattern(default=None): + def mark_pattern(): return click.option( '-m', '--mark-pattern', - help='pattern this', + help=helptexts.MARK_PATTERN, type=str, - default=default + required=False ) options = Options() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/e3b25d7a/aria/cli/execution_logging.py ---------------------------------------------------------------------- diff --git a/aria/cli/execution_logging.py b/aria/cli/execution_logging.py index 66c7d9c..022eeeb 100644 --- a/aria/cli/execution_logging.py +++ b/aria/cli/execution_logging.py @@ -22,6 +22,7 @@ from .color import Color from .env import env +FIELD_TYPE = 'field_type' LEVEL = 'level' TIMESTAMP = 'timestamp' MESSAGE = 'message' @@ -47,77 +48,122 @@ _IMPLEMENTATION_PATTERN = '.*({implementation.*?}).*' _INPUTS_PATTERN = '.*({inputs.*?}).*' _PATTERNS = { - SUCCESS_STATE: re.compile(_SUCCESSFUL_EXECUTION_PATTERN), - CANCEL_STATE: re.compile(_CANCELED_EXECUTION_PATTERN), - FAIL_STATE: re.compile(_FAILED_EXECUTION_PATTERN), - - IMPLEMENTATION: re.compile(_IMPLEMENTATION_PATTERN), - LEVEL: re.compile(_LEVEL_PATTERN), - MESSAGE: re.compile(_MESSAGE_PATTERN), - INPUTS: re.compile(_INPUTS_PATTERN), - TIMESTAMP: re.compile(_TIMESTAMP_PATTERN) + FINAL_STATES: { + SUCCESS_STATE: re.compile(_SUCCESSFUL_EXECUTION_PATTERN), + CANCEL_STATE: re.compile(_CANCELED_EXECUTION_PATTERN), + FAIL_STATE: re.compile(_FAILED_EXECUTION_PATTERN), + }, + FIELD_TYPE: { + IMPLEMENTATION: re.compile(_IMPLEMENTATION_PATTERN), + LEVEL: re.compile(_LEVEL_PATTERN), + MESSAGE: re.compile(_MESSAGE_PATTERN), + INPUTS: re.compile(_INPUTS_PATTERN), + TIMESTAMP: re.compile(_TIMESTAMP_PATTERN) + } } +_FINAL_STATES = { + SUCCESS_STATE: Color.Fore.GREEN, + CANCEL_STATE: Color.Fore.YELLOW, + FAIL_STATE: Color.Fore.RED +} + +_DEFAULT_STYLE = { + LEVEL: { + 'info': {'fore': 'lightmagenta_ex'}, + 'debug': {'fore': 'lightmagenta_ex', 'style': 'dim'}, + 'error': {'fore': 'red', 'style': 'bright'}, + }, + TIMESTAMP: { + 'info': {'fore': 'lightmagenta_ex'}, + 'debug': {'fore': 'lightmagenta_ex', 'style': 'dim'}, + 'error': {'fore': 'red', 'style': 'bright'}, + }, + MESSAGE: { + 'info': {'fore': 'lightblue_ex'}, + 'debug': {'fore': 'lightblue_ex', 'style': 'dim'}, + 'error': {'fore': 'red', 'style': 'bright'}, + }, + IMPLEMENTATION:{ + 'info': {'fore': 'lightblack_ex'}, + 'debug': {'fore': 'lightblack_ex', 'style': 'dim'}, + 'error': {'fore': 'red', 'style': 'bright'}, + }, + INPUTS: { + 'info': {'fore': 'blue'}, + 'debug': {'fore': 'blue', 'style': 'dim'}, + 'error': {'fore': 'red', 'style': 'bright'}, + }, + TRACEBACK: {'error': {'fore': 'red'}}, + + MARKER: 'lightyellow_ex' +} class _StylizedLogs(object): def _update_implementation(self, str_, implementation, log_item, mark_pattern=None): - str_ = self._stylize(str_, implementation, log_item, IMPLEMENTATION) - return self._markup(str_, implementation, mark_pattern) + schema = self._stylize(str_, implementation, log_item, IMPLEMENTATION, mark_pattern) def _update_inputs(self, str_, inputs, log_item, mark_pattern=None): - str_ = self._stylize(str_, inputs, log_item, INPUTS) - return self._markup(str_, inputs, mark_pattern) + self._stylize(str_, inputs, log_item, INPUTS, mark_pattern) def _update_timestamp(self, str_, timestamp, log_item): - return self._stylize(str_, timestamp, log_item, TIMESTAMP) + self._stylize(str_, timestamp, log_item, TIMESTAMP) - def _update_message(self, str_, message, log_item, mark_pattern): - str_ = self._stylize(str_, message, log_item, MESSAGE) - return self._markup(str_, message, mark_pattern) - - def _get_traceback(self, traceback, log_item): - schema = Color.Schema(**self._styles[TRACEBACK].get(log_item.level.lower(), {})) - return Color.stylize(traceback, schema) + def _update_message(self, str_, message, log_item, mark_pattern=None): + self._stylize(str_, message, log_item, MESSAGE, mark_pattern) def _update_level(self, str_, log_item): - return self._stylize(str_, log_item.level[0], log_item, LEVEL) + self._stylize(str_, log_item.level[0], log_item, LEVEL) - def _stylize(self, str_, msg, log_item, msg_type): - matched_str = self._find_pattern(_PATTERNS[msg_type], str_) + def _stylize(self, stylized_str, msg, log_item, msg_type, mark_pattern=None): + matched_str = self._find_pattern(_PATTERNS[FIELD_TYPE][msg_type], stylized_str ._str) if not matched_str: - return str_ + return stylized_str # if Styling was disabled if not self._is_styling_enabled: - return str_.replace(matched_str, matched_str.format(**{msg_type: msg})) - - schema = Color.Schema( - **(self._end_execution_schema(log_item.msg) or - self._styles[msg_type].get(log_item.level.lower()) or - {}) - ) - colored_string = Color.stylize(matched_str, schema) - return str_.replace(matched_str, colored_string.format(**{msg_type: msg})) - - def _markup(self, str_, original_message, mark_pattern): - if mark_pattern is None: - return str_ - else: - regex_pattern = re.compile(mark_pattern) - matches = re.search(regex_pattern, str(original_message)) - if not matches: - return str_ - return Color.markup(str_, matches, Color.Schema(back=self._styles[MARKER])) + return stylized_str.replace(matched_str, matched_str.format(**{msg_type: msg})) + + # If the log is not of a final workflow message, it could be configured through the + # config file. Thus it should be instantiated from the dict provided. + schema = self._resolve_schema(msg_type, log_item) + substring = Color.stylize(matched_str) + substring.color(schema or '') + substring.format(**{msg_type: msg}) + substring.highlight(msg, self._marker_schema, schema, mark_pattern) + stylized_str.replace(matched_str, substring) + + def _get_traceback(self, traceback, log_item, mark_pattern): + schema = Color.Schema(**self._styles[TRACEBACK].get(log_item.level.lower(), {})) + stylized_string = Color.stylize(traceback, schema) + stylized_string.highlight(traceback, self._marker_schema, schema, mark_pattern) + return stylized_string def _end_execution_schema(self, original_message): if not self.is_workflow_log: return - for state, pattern in _PATTERNS.items(): + for state, pattern in _PATTERNS[FINAL_STATES].items(): if re.match(pattern, original_message): - return self._styles[FINAL_STATES][state] + return _FINAL_STATES[state] - def _find_pattern(self, pattern, field_value): + def _resolve_schema(self, msg_type, log_item): + schema = self._end_execution_schema(log_item.msg) + + if schema is None: + typed_schema_config = self._styles[msg_type].get(log_item.level.lower()) + user_default_schema_config = self._styles[msg_type].get('default') + aria_schema_config = _DEFAULT_STYLE[msg_type].get(log_item.level.lower()) + schema = Color.Schema(**(typed_schema_config or + user_default_schema_config or + aria_schema_config)) + + return schema + + + @staticmethod + def _find_pattern(pattern, field_value): + # TODO: this finds the matching field type according to a pattern match = re.match(pattern, field_value) if match: return match.group(1) @@ -128,15 +174,19 @@ class _StylizedLogs(object): @property def _format(self): - return env.config._config['logging']['execution_logging']['formatting'][self._verbosity_level] + return env.config.logging.formats[self._verbosity_level] @property def _styles(self): - return env.config._config['logging']['execution_logging']['styling'] + return env.config.logging.styles @property def _is_styling_enabled(self): - return env.config._config['logging']['execution_logging']['styling'].get('enabled', False) + return env.config.logging.styling_enabled + + @property + def _marker_schema(self): + return Color.Schema(back=self._styles[MARKER]) def __call__(self, item, mark_pattern): @@ -153,18 +203,20 @@ class _StylizedLogs(object): self.is_workflow_log = True # TODO: use the is_workflow_log - str_ = self._update_level(self._format, item) - str_ = self._update_timestamp(str_, item.created_at, item) - str_ = self._update_message(str_, item.msg, item, mark_pattern) - str_ = self._update_inputs(str_, inputs, item, mark_pattern) - str_ = self._update_implementation(str_, implementation, item, mark_pattern) - - msg = StringIO(str_) + stylized_str = Color.stylize(self._format) + self._update_level(stylized_str, item) + self._update_timestamp(stylized_str, item.created_at, item) + self._update_message(stylized_str, item.msg, item, mark_pattern) + self._update_inputs(stylized_str, inputs, item, mark_pattern) + self._update_implementation(stylized_str, implementation, item, mark_pattern) + + msg = StringIO() + msg.write(str(stylized_str)) # Add the exception and the error msg. if item.traceback and env.logging.verbosity_level >= logger.MEDIUM_VERBOSE: msg.write(os.linesep) - for line in item.traceback.splitlines(True): - msg.write(self._get_traceback('\t' + '|' + line, item)) + msg.writelines(self._get_traceback('\t' + '|' + line, item, mark_pattern) + for line in item.traceback.splitlines(True)) return msg.getvalue() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/e3b25d7a/aria/cli/helptexts.py ---------------------------------------------------------------------- diff --git a/aria/cli/helptexts.py b/aria/cli/helptexts.py index 1a3f6c0..8641822 100644 --- a/aria/cli/helptexts.py +++ b/aria/cli/helptexts.py @@ -47,3 +47,4 @@ IGNORE_AVAILABLE_NODES = "Delete the service even if it has available nodes" SORT_BY = "Key for sorting the list" DESCENDING = "Sort list in descending order [default: False]" JSON_OUTPUT = "Output logs in a consumable JSON format" +MARK_PATTERN = "Mark a regex pattern in the logs"
