Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Colorize live-log levelnames #3142

Merged
merged 1 commit into from
Jan 30, 2018

Conversation

twmr
Copy link
Contributor

@twmr twmr commented Jan 23, 2018

I propose to prettify the default output of the live logs a bit by colorizing the levelnames.

A screenshot paints a thousand words:
image

@twmr twmr changed the base branch from master to features January 23, 2018 00:27
@coveralls
Copy link

coveralls commented Jan 23, 2018

Coverage Status

Coverage decreased (-0.02%) to 92.628% when pulling ebab1b6 on thisch:only_colorize_levelname into 49773b5 on pytest-dev:features.

@twmr twmr force-pushed the only_colorize_levelname branch 2 times, most recently from 87013c4 to d797e3f Compare January 26, 2018 11:08
@twmr twmr changed the title WIP: Colorize live-log levelnames Colorize live-log levelnames Jan 26, 2018
@twmr
Copy link
Contributor Author

twmr commented Jan 26, 2018

What do you think about this?

Are there tests that test the text colors of the pytest output, where I can take a look at?

@nicoddemus
Copy link
Member

Are there tests that test the text colors of the pytest output, where I can take a look at?

Unfortunately no AFAIK. 😕

@twmr
Copy link
Contributor Author

twmr commented Jan 26, 2018

Note that I successfully tested this PR in cpython2.7 and in cpython3.6.

@nicoddemus
Copy link
Member

This is how it looks on Windows:

colored-log

👍

Copy link
Member

@nicoddemus nicoddemus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great @thisch, please take a look at my comments! 👍

DEFAULT_LOG_FORMAT = '%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s'
DEFAULT_LOG_DATE_FORMAT = '%H:%M:%S'

# move to __init__ of ColoredLevelFormatter?
LEVELNAME_FMT_REGEX = re.compile(r'%\(levelname\)([+-]?\d*s)')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably both LEVELNAME_FMT_REGEX and LOGLEVEL_COLOROPTS would be better off being class attributes of ColoredLevelFormatter.

def __init__(self, *args, **kwargs):
super(ColoredLevelFormatter, self).__init__(
*args, **kwargs)
if py.std.sys.version_info[0] >= 3:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use six.PY3 instead (this will make it easier later to find python2-specific code)

fmt = self._level_to_fmt_mapping.get(
record.levelno, self._original_fmt)

if py.std.sys.version_info[0] >= 3:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here for six.PY3 👍

LEVELNAME_FMT_REGEX = re.compile(r'%\(levelname\)([+-]?\d*s)')


class ColoredLevelFormatter(logging.Formatter):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To test this, probably the way to go is to unittest the ColoredLevelFormatter class directly: create an instance and pass different LogRecord instances and ensure they have the correct markup applied. Also interesting to use custom log levels and ensure they don't have any markup.

Copy link
Contributor Author

@twmr twmr Jan 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately this does not work, since tw.markup() does not add ANSI escape sequences around the string in the unit tests:

(Pdb++) tw.markup('abc', red=True)
'abc'
(Pdb++) tw.hasmarkup
False

Any ideas?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll do some mocking and then it should work.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@twmr
Copy link
Contributor Author

twmr commented Jan 30, 2018

I guess that this is already too late for pytest 3.4, right?

@twmr
Copy link
Contributor Author

twmr commented Jan 30, 2018

BTW, I don't understand why the tests fail.

@nicoddemus
Copy link
Member

I guess that this is already too late for pytest 3.4, right?

I plan to release 3.4 when I get home later, but if we merge this until then I will be happy to generate the package again.

BTW, I don't understand why the tests fail.

No idea either, seems to be some bad interaction with mock... I suggest to try to use monkeypatch in your test instead of mock to see if the problem goes away (plus it will work on Python 2 as well).

@twmr
Copy link
Contributor Author

twmr commented Jan 30, 2018

Let's hope that the tests now pass.

@nicoddemus
Copy link
Member

Just remembered this now and would like to confirm, passing --color=no disables the colors?

@twmr
Copy link
Contributor Author

twmr commented Jan 30, 2018

Now the color config option should be taken into account. This made the monkeypatching code obsolete.

@@ -376,7 +432,10 @@ def _setup_cli_logging(self):
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
log_cli_format = get_option_ini(self._config, 'log_cli_format', 'log_format')
log_cli_date_format = get_option_ini(self._config, 'log_cli_date_format', 'log_date_format')
log_cli_formatter = logging.Formatter(log_cli_format, datefmt=log_cli_date_format)
if ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format):
log_cli_formatter = ColoredLevelFormatter(self._config, log_cli_format, datefmt=log_cli_date_format)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could also perform the color check in the condition in line 435. Then I don't need to pass self._config to the __init__ method of ColoredLevelFormatter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't know that color is a ternary type ('yes', 'no' and 'auto'). So I can't add self._config.option.color == 'yes' and to the if condition. If the value of the color option is 'auto' the TerminalWriter.init determines if the terminal supports markup or not.

An alternative would be to pass a terminalwriter object (_pytest.config.create_terminal_writer(self._config)) as the first arg of ColoredLevelFormatter. Don't know if this makes it better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative would be to pass a terminalwriter object (_pytest.config.create_terminal_writer(self._config)) as the first arg of ColoredLevelFormatter. Don't know if this makes it better.

This reduces the number of dependencies at least (ColoredLevelFormatter will then depend only on TerminalWriter, instead of Config and TerminalWriter with the current code), so feel free to go with this route.

@twmr
Copy link
Contributor Author

twmr commented Jan 30, 2018

If all tests pass, this is ready for merging.

@nicoddemus nicoddemus merged commit 71a7b3c into pytest-dev:features Jan 30, 2018
@nicoddemus
Copy link
Member

Thanks @thisch!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants