Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
ARIA-146 Support colorful execution logging
  • Loading branch information
mxmrlv committed Apr 27, 2017
1 parent 8dc7b00 commit 5bc28b6641c2e749b0eebca05281790d21bf3c00
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 49 deletions.
@@ -0,0 +1,95 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from StringIO import StringIO
import re

import colorama

colorama.init()


class StringStylizer(object):
def __init__(self, str_, color_spec=None):
self._str = str_
self._color_spec = color_spec

def __repr__(self):
if self._color_spec:
return '{schema}{str}{reset}'.format(
schema=self._color_spec, str=str(self._str), reset=Colors.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, color_spec):
self._color_spec = color_spec

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, pattern, schema):
if pattern is None:
return
for match in set(re.findall(re.compile(pattern), self._str)):
self.replace(match, schema + match + Colors.Style.RESET_ALL + self._color_spec)


def _get_colors(color_type):
for name in dir(color_type):
if not name.startswith('_'):
yield (name.lower(), getattr(color_type, name))


class Colors(object):
Fore = colorama.Fore
Back = colorama.Back
Style = colorama.Style

_colors = {
'fore': dict(_get_colors(Fore)),
'back': dict(_get_colors(Back)),
'style': dict(_get_colors(Style))
}


class ColorSpec(object):
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.
"""
self._kwargs = dict(fore=fore, back=back, style=style)
self._str = StringIO()
for type_, colors in Colors._colors.items():
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)

def __str__(self):
return self._str.getvalue()

def __add__(self, other):
return str(self) + str(other)

def __radd__(self, other):
return str(other) + str(self)
@@ -109,6 +109,7 @@ def list(service_name,
@aria.options.dry_execution
@aria.options.task_max_attempts()
@aria.options.task_retry_interval()
@aria.options.mark_pattern()
@aria.options.verbose()
@aria.pass_model_storage
@aria.pass_resource_storage
@@ -120,6 +121,7 @@ def start(workflow_name,
dry,
task_max_attempts,
task_retry_interval,
mark_pattern,
model_storage,
resource_storage,
plugin_manager,
@@ -146,15 +148,15 @@ def start(workflow_name,
log_iterator = cli_logger.ModelLogIterator(model_storage, workflow_runner.execution_id)
try:
while execution_thread.is_alive():
execution_logging.log_list(log_iterator)
execution_logging.log_list(log_iterator, mark_pattern=mark_pattern)
execution_thread.join(1)

except KeyboardInterrupt:
_cancel_execution(workflow_runner, execution_thread, logger, log_iterator)

# It might be the case where some logs were written and the execution was terminated, thus we
# need to drain the remaining logs.
execution_logging.log_list(log_iterator)
execution_logging.log_list(log_iterator, mark_pattern=mark_pattern)

# raise any errors from the execution thread (note these are not workflow execution errors)
execution_thread.raise_error_if_exists()
@@ -18,6 +18,7 @@


@aria.group(name='logs')
@aria.options.verbose()
def logs():
"""Show logs from workflow executions
"""
@@ -28,15 +29,16 @@ def logs():
short_help='List execution logs')
@aria.argument('execution-id')
@aria.options.verbose()
@aria.options.mark_pattern()
@aria.pass_model_storage
@aria.pass_logger
def list(execution_id, model_storage, logger):
def list(execution_id, mark_pattern, model_storage, logger):
"""Display logs for an execution
"""
logger.info('Listing logs for execution id {0}'.format(execution_id))
log_iterator = ModelLogIterator(model_storage, execution_id)

any_logs = execution_logging.log_list(log_iterator)
any_logs = execution_logging.log_list(log_iterator, mark_pattern=mark_pattern)

if not any_logs:
logger.info('\tNo logs')
@@ -51,10 +51,6 @@ def create_config(cls, workdir):

return cls(config_path)

@property
def colors(self):
return self._config.get('colors', False)

@property
def logging(self):
return self.Logging(self._config.get('logging'))
@@ -71,3 +67,24 @@ def filename(self):
@property
def loggers(self):
return self._logging.get('loggers', {})

@property
def execution(self):
return self.Execution(self._logging.get('execution'))

class Execution(object):

def __init__(self, execution_logging):
self._execution_logging = execution_logging

@property
def colors_enabled(self):
return self.colors.get('enabled', False)

@property
def colors(self):
return self._execution_logging.get('colors', {})

@property
def formats(self):
return self._execution_logging.get('formats', {})
@@ -1,4 +1,3 @@
colors: {{ enable_colors }}

logging:

@@ -10,3 +9,34 @@ logging:

# main logger of the cli. provides basic descriptions for executed operations.
aria.cli.main: info

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}'

colors:
enabled: true

level:
default: {'fore': 'lightmagenta_ex'}
error: {'fore': 'red', 'style': 'bright'}
timestamp:
default: {'fore': 'lightmagenta_ex'}
error: {'fore': 'red', 'style': 'bright'}
message:
default: {'fore': 'lightblue_ex'}
error: {'fore': 'red', 'style': 'bright'}
implementation:
default: {'fore': 'lightblack_ex'}
error: {'fore': 'red', 'style': 'bright'}
inputs:
default: {'fore': 'blue'}
error: {'fore': 'red', 'style': 'bright'}
traceback:
default: {'fore': 'red'}

marker: 'lightyellow_ex'
@@ -425,5 +425,14 @@ def service_template_name(required=False):
required=required,
help=helptexts.SERVICE_ID)

@staticmethod
def mark_pattern():
return click.option(
'-m',
'--mark-pattern',
help=helptexts.MARK_PATTERN,
type=str,
required=False
)

options = Options()

0 comments on commit 5bc28b6

Please sign in to comment.