Skip to content

Commit b3f3c30

Browse files
committed
Make colors customizable
1 parent fd36947 commit b3f3c30

File tree

7 files changed

+62
-42
lines changed

7 files changed

+62
-42
lines changed

examples/exapp2

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,24 @@ def sample_json_handler():
125125
return result
126126

127127

128+
def sample_logger_handler():
129+
"""
130+
Print logs to stderr.
131+
"""
132+
print("""This is a demo for logging. The logging level can be controlled with:
133+
--only-show-errors: Show ERROR logs and above
134+
<default>: Show WARNING logs and above
135+
--verbose: Show INFO logs and above
136+
--debug: Show DEBUG logs and above""")
137+
from knack.log import get_logger
138+
logger = get_logger(__name__)
139+
logger.debug("This is a debug log entry.")
140+
logger.info("This is a info log entry.")
141+
logger.warning("This is a warning log entry.")
142+
logger.error("This is a error log entry.")
143+
logger.critical("This is a critical log entry.")
144+
145+
128146
def hello_command_handler(greetings=None):
129147
"""
130148
Say "Hello World!" and my warm greetings
@@ -160,6 +178,7 @@ class MyCommandsLoader(CLICommandsLoader):
160178
with CommandGroup(self, '', '__main__#{}') as g:
161179
g.command('hello', 'hello_command_handler', confirmation=True)
162180
g.command('sample-json', 'sample_json_handler')
181+
g.command('sample-logger', 'sample_logger_handler')
163182
with CommandGroup(self, 'abc', '__main__#{}') as g:
164183
g.command('list', 'abc_list_command_handler')
165184
g.command('show', 'abc_show_command_handler')

knack/deprecation.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
# Licensed under the MIT License. See License.txt in the project root for license information.
44
# --------------------------------------------------------------------------------------------
55

6-
from .util import StatusTag
6+
from .util import StatusTag, color_map
77

88
DEFAULT_DEPRECATED_TAG = '[Deprecated]'
9+
_config_key = 'deprecation'
910

1011

1112
def resolve_deprecate_info(cli_ctx, name):
@@ -88,7 +89,7 @@ def _default_get_message(self):
8889
cli_ctx=cli_ctx,
8990
object_type=object_type,
9091
target=target,
91-
color='yellow',
92+
color=color_map[_config_key],
9293
tag_func=tag_func or (lambda _: DEFAULT_DEPRECATED_TAG),
9394
message_func=message_func or _default_get_message
9495
)

knack/experimental.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
# Licensed under the MIT License. See License.txt in the project root for license information.
44
# --------------------------------------------------------------------------------------------
55

6-
from .util import StatusTag, status_tag_messages
6+
from .util import StatusTag, status_tag_messages, color_map
77

88
_EXPERIMENTAL_TAG = '[Experimental]'
99
_experimental_kwarg = 'experimental_info'
10+
_config_key = 'experimental'
1011

1112

1213
def resolve_experimental_info(cli_ctx, name):
@@ -50,13 +51,13 @@ def __init__(self, cli_ctx, object_type='', target=None, tag_func=None, message_
5051
"""
5152

5253
def _default_get_message(self):
53-
return status_tag_messages['experimental'].format("This " + self.object_type)
54+
return status_tag_messages[_config_key].format("This " + self.object_type)
5455

5556
super().__init__(
5657
cli_ctx=cli_ctx,
5758
object_type=object_type,
5859
target=target,
59-
color='cyan',
60+
color=color_map[_config_key],
6061
tag_func=tag_func or (lambda _: _EXPERIMENTAL_TAG),
6162
message_func=message_func or _default_get_message
6263
)
@@ -67,7 +68,7 @@ class ImplicitExperimentalItem(ExperimentalItem):
6768
def __init__(self, **kwargs):
6869

6970
def get_implicit_experimental_message(self):
70-
return status_tag_messages['experimental'].format("Command group '{}'".format(self.target))
71+
return status_tag_messages[_config_key].format("Command group '{}'".format(self.target))
7172

7273
kwargs.update({
7374
'tag_func': lambda _: '',

knack/log.py

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import logging
88
from enum import IntEnum
99

10-
from .util import CtxTypeError, ensure_dir, CLIError
10+
from .util import CtxTypeError, ensure_dir, CLIError, color_map
1111
from .events import EVENT_PARSER_GLOBAL_CREATE
1212

1313

@@ -44,27 +44,11 @@ def get_logger(module_name=None):
4444

4545

4646
class _CustomStreamHandler(logging.StreamHandler):
47-
COLOR_MAP = None
4847

4948
@classmethod
50-
def get_color_wrapper(cls, level):
51-
if not cls.COLOR_MAP:
52-
import colorama
53-
54-
def _color_wrapper(color_marker):
55-
def wrap_msg_with_color(msg):
56-
return '{}{}{}'.format(color_marker, msg, colorama.Style.RESET_ALL)
57-
return wrap_msg_with_color
58-
59-
cls.COLOR_MAP = {
60-
logging.CRITICAL: _color_wrapper(colorama.Fore.LIGHTRED_EX),
61-
logging.ERROR: _color_wrapper(colorama.Fore.LIGHTRED_EX),
62-
logging.WARNING: _color_wrapper(colorama.Fore.YELLOW),
63-
logging.INFO: _color_wrapper(colorama.Fore.GREEN),
64-
logging.DEBUG: _color_wrapper(colorama.Fore.CYAN)
65-
}
66-
67-
return cls.COLOR_MAP.get(level, None)
49+
def wrap_with_color(cls, level_name, msg):
50+
color_marker = color_map[level_name.lower()]
51+
return '{}{}{}'.format(color_marker, msg, color_map['reset'])
6852

6953
def __init__(self, log_level_config, log_format, enable_color):
7054
logging.StreamHandler.__init__(self)
@@ -76,7 +60,7 @@ def format(self, record):
7660
msg = logging.StreamHandler.format(self, record)
7761
if self.enable_color:
7862
try:
79-
msg = self.get_color_wrapper(record.levelno)(msg)
63+
msg = self.wrap_with_color(record.levelname, msg)
8064
except KeyError:
8165
pass
8266
return msg

knack/preview.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
# Licensed under the MIT License. See License.txt in the project root for license information.
44
# --------------------------------------------------------------------------------------------
55

6-
from .util import StatusTag, status_tag_messages
6+
from .util import StatusTag, status_tag_messages, color_map
77

88
_PREVIEW_TAG = '[Preview]'
99
_preview_kwarg = 'preview_info'
10+
_config_key = 'preview'
1011

1112

1213
def resolve_preview_info(cli_ctx, name):
@@ -50,13 +51,13 @@ def __init__(self, cli_ctx, object_type='', target=None, tag_func=None, message_
5051
"""
5152

5253
def _default_get_message(self):
53-
return status_tag_messages['preview'].format("This " + self.object_type)
54+
return status_tag_messages[_config_key].format("This " + self.object_type)
5455

5556
super().__init__(
5657
cli_ctx=cli_ctx,
5758
object_type=object_type,
5859
target=target,
59-
color='cyan',
60+
color=color_map[_config_key],
6061
tag_func=tag_func or (lambda _: _PREVIEW_TAG),
6162
message_func=message_func or _default_get_message
6263
)
@@ -67,7 +68,7 @@ class ImplicitPreviewItem(PreviewItem):
6768
def __init__(self, **kwargs):
6869

6970
def get_implicit_preview_message(self):
70-
return status_tag_messages['preview'].format("Command group '{}'".format(self.target))
71+
return status_tag_messages[_config_key].format("Command group '{}'".format(self.target))
7172

7273
kwargs.update({
7374
'tag_func': lambda _: '',

knack/util.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,26 @@
1212
NO_COLOR_VARIABLE_NAME = 'KNACK_NO_COLOR'
1313

1414
# Override these values to customize the status message.
15-
# The message should contain a placeholder indicating the subject (like 'This command group', 'Commend group xxx').
15+
# The message should contain a placeholder indicating the subject (like 'This command group', 'Command group xxx').
1616
# (A dict is used to avoid the "from A import B" pitfall that creates a copy of the imported B.)
1717
status_tag_messages = {
1818
'preview': "{} is in preview. It may be changed/removed in a future release.",
1919
'experimental': "{} is experimental and under development."
2020
}
2121

22+
# https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences
23+
color_map = {
24+
'reset': '\x1b[0m', # Default
25+
'preview': '\x1b[36m', # Foreground Cyan
26+
'experimental': '\x1b[36m', # Foreground Cyan
27+
'deprecation': '\x1b[33m', # Foreground Yellow
28+
'critical': '\x1b[41m', # Background Red
29+
'error': '\x1b[91m', # Bright Foreground Red
30+
'warning': '\x1b[33m', # Foreground Yellow
31+
'info': '\x1b[32m', # Foreground Green
32+
'debug': '\x1b[36m', # Foreground Cyan
33+
}
34+
2235

2336
class CommandResultItem(object): # pylint: disable=too-few-public-methods
2437
def __init__(self, result, table_transformer=None, is_query_active=False,
@@ -48,18 +61,16 @@ def __init__(self, obj):
4861
class ColorizedString(object):
4962

5063
def __init__(self, message, color):
51-
import colorama
5264
self._message = message
53-
self._color = getattr(colorama.Fore, color.upper(), None)
65+
self._color = color
5466

5567
def __len__(self):
5668
return len(self._message)
5769

5870
def __str__(self):
59-
import colorama
6071
if not self._color:
6172
return self._message
62-
return self._color + self._message + colorama.Fore.RESET
73+
return self._color + self._message + color_map['reset']
6374

6475

6576
class StatusTag(object):

tests/test_log.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
except ImportError:
1010
from unittest import mock
1111
import logging
12-
import colorama
1312

1413
from knack.events import EVENT_PARSER_GLOBAL_CREATE, EVENT_INVOKER_PRE_CMD_TBL_CREATE
1514
from knack.log import CLILogging, get_logger, CLI_LOGGER_NAME, _CustomStreamHandler
@@ -165,15 +164,19 @@ def test_get_console_log_formats(self):
165164

166165

167166
class TestCustomStreamHandler(unittest.TestCase):
168-
expectation = {logging.CRITICAL: colorama.Fore.LIGHTRED_EX, logging.ERROR: colorama.Fore.LIGHTRED_EX,
169-
logging.WARNING: colorama.Fore.YELLOW, logging.INFO: colorama.Fore.GREEN,
170-
logging.DEBUG: colorama.Fore.CYAN}
167+
expectation = {
168+
'critical': '\x1b[41m', # Background Red
169+
'error': '\x1b[91m', # Bright Foreground Red
170+
'warning': '\x1b[33m', # Foreground Yellow
171+
'info': '\x1b[32m', # Foreground Green
172+
'debug': '\x1b[36m', # Foreground Cyan
173+
}
171174

172175
def test_get_color_wrapper(self):
173176
for level, prefix in self.expectation.items():
174-
message = _CustomStreamHandler.get_color_wrapper(level)('test')
177+
message = _CustomStreamHandler.wrap_with_color(level, 'test')
175178
self.assertTrue(message.startswith(prefix))
176-
self.assertTrue(message.endswith(colorama.Style.RESET_ALL))
179+
self.assertTrue(message.endswith('\x1b[0m'))
177180

178181

179182
if __name__ == '__main__':

0 commit comments

Comments
 (0)