summaryrefslogtreecommitdiffstats
path: root/azure/aria/aria-extension-cloudify/src/aria/aria/cli/execution_logging.py
blob: 915038b095f593ef142dcca31b7322a852dc8c1a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# 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.

"""
Formatting for ``executions`` sub-commands.
"""

import os
import re
from StringIO import StringIO
from functools import partial

from . import (
    logger,
    color
)
from .env import env


FIELD_TYPE = 'field_type'
LEVEL = 'level'
TIMESTAMP = 'timestamp'
MESSAGE = 'message'
IMPLEMENTATION = 'implementation'
INPUTS = 'inputs'
TRACEBACK = 'traceback'
MARKER = 'marker'

FINAL_STATES = 'final_states'
SUCCESS_STATE = 'succeeded'
CANCEL_STATE = 'canceled'
FAIL_STATE = 'failed'

_EXECUTION_PATTERN = "\'.*\' workflow execution {0}".format
# In order to be able to format a string into this regex pattern, we need to provide support
# in adding this string into double curly brackets. This is an issue with python format, so we add
# this via format itself.
_FIELD_TYPE_PATTERN = partial('.*({starting}{0}{closing}).*'.format, starting='{', closing='.*?}')

_PATTERNS = {
    FINAL_STATES: {
        SUCCESS_STATE: re.compile(_EXECUTION_PATTERN(SUCCESS_STATE)),
        CANCEL_STATE: re.compile(_EXECUTION_PATTERN(CANCEL_STATE)),
        FAIL_STATE: re.compile(_EXECUTION_PATTERN(FAIL_STATE)),
    },
    FIELD_TYPE: {
        IMPLEMENTATION: re.compile(_FIELD_TYPE_PATTERN(IMPLEMENTATION)),
        LEVEL: re.compile(_FIELD_TYPE_PATTERN(LEVEL)),
        MESSAGE: re.compile(_FIELD_TYPE_PATTERN(MESSAGE)),
        INPUTS: re.compile(_FIELD_TYPE_PATTERN(INPUTS)),
        TIMESTAMP: re.compile(_FIELD_TYPE_PATTERN(TIMESTAMP))
    }
}

_FINAL_STATES = {
    SUCCESS_STATE: color.Colors.Fore.GREEN,
    CANCEL_STATE: color.Colors.Fore.YELLOW,
    FAIL_STATE: color.Colors.Fore.RED
}

_DEFAULT_COLORS = {
    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'
}

_DEFAULT_FORMATS = {
    logger.NO_VERBOSE: '{message}',
    logger.LOW_VERBOSE: '{timestamp:%H:%M:%S} | {level[0]} | {message}',
    logger.MEDIUM_VERBOSE: '{timestamp:%H:%M:%S} | {level[0]} | {implementation} | {message}',
    logger.HIGH_VERBOSE:
        '{timestamp:%H:%M:%S} | {level[0]} | {implementation} | {inputs} | {message}'
}


def stylize_log(item, mark_pattern):

    # implementation
    if item.task:
        # operation task
        implementation = item.task.function
        inputs = dict(arg.unwrapped for arg in item.task.arguments.itervalues())
    else:
        # execution task
        implementation = item.execution.workflow_name
        inputs = dict(inp.unwrapped for inp in item.execution.inputs.itervalues())

    stylized_str = color.StringStylizer(_get_format())
    _populate_level(stylized_str, item)
    _populate_timestamp(stylized_str, item)
    _populate_message(stylized_str, item, mark_pattern)
    _populate_inputs(stylized_str, inputs, item, mark_pattern)
    _populate_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)
        msg.writelines(_color_traceback('\t' + '|' + line, item, mark_pattern)
                       for line in item.traceback.splitlines(True))

    return msg.getvalue()


def log(item, mark_pattern=None, *args, **kwargs):
    leveled_log = getattr(env.logging.logger, item.level.lower())
    return leveled_log(stylize_log(item, mark_pattern), *args, **kwargs)


def log_list(iterator, mark_pattern=None):
    any_logs = False
    for item in iterator:
        log(item, mark_pattern)
        any_logs = True
    return any_logs


def _get_format():
    return (env.config.logging.execution.formats.get(env.logging.verbosity_level) or
            _DEFAULT_FORMATS.get(env.logging.verbosity_level))


def _get_styles(field_type):
    return env.config.logging.execution.colors[field_type]


def _is_color_enabled():
    # If styling is enabled and the current log_item isn't final string
    return env.config.logging.execution.colors_enabled


def _get_marker_schema():
    return color.ColorSpec(back=_get_styles(MARKER))


def _populate_implementation(str_, implementation, log_item, mark_pattern=None):
    _stylize(str_, implementation, log_item, IMPLEMENTATION, mark_pattern)


def _populate_inputs(str_, inputs, log_item, mark_pattern=None):
    _stylize(str_, inputs, log_item, INPUTS, mark_pattern)


def _populate_timestamp(str_, log_item):
    _stylize(str_, log_item.created_at, log_item, TIMESTAMP)


def _populate_message(str_, log_item, mark_pattern=None):
    _stylize(str_, log_item.msg, log_item, MESSAGE, mark_pattern)


def _populate_level(str_, log_item):
    _stylize(str_, log_item.level[0], log_item, LEVEL)


def _stylize(stylized_str, msg, log_item, msg_type, mark_pattern=None):
    match = re.match(_PATTERNS[FIELD_TYPE][msg_type], stylized_str._str)
    if not match:
        return
    matched_substr = match.group(1)

    substring = color.StringStylizer(matched_substr)

    # handle format
    substring.format(**{msg_type: msg})

    if _is_color_enabled():
        # handle color
        substring.color(_resolve_schema(msg_type, log_item))
        if not _is_end_execution_log(log_item):
            # handle highlighting
            substring.highlight(mark_pattern, _get_marker_schema())

    stylized_str.replace(matched_substr, substring)


def _color_traceback(traceback, log_item, mark_pattern):
    if _is_color_enabled():
        stylized_string = color.StringStylizer(traceback, _resolve_schema(TRACEBACK, log_item))
        stylized_string.highlight(mark_pattern, _get_marker_schema())
        return stylized_string
    return traceback


def _is_end_execution_log(log_item):
    return not log_item.task and bool(_end_execution_schema(log_item))


def _end_execution_schema(log_item):
    for state, pattern in _PATTERNS[FINAL_STATES].items():
        if re.match(pattern, log_item.msg):
            return _FINAL_STATES[state]


def _resolve_schema(msg_type, log_item):
    if _is_end_execution_log(log_item):
        return _end_execution_schema(log_item)
    else:
        return color.ColorSpec(
            **(
                # retrieve the schema from the user config according to the level
                _get_styles(msg_type).get(log_item.level.lower()) or
                # retrieve the default schema from the user config
                _get_styles(msg_type).get('default') or
                # retrieve the schema from the aria default config according to the level
                _DEFAULT_COLORS[msg_type].get(log_item.level.lower()) or
                # retrieve the default schema from the aria default config
                _DEFAULT_COLORS[msg_type].get('default')
            )
        )