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

Add ANSI colours to log output #2114

Merged
merged 2 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions novelwriter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
=======================

File History:
Created: 2018-09-22 [0.0.1]
Created: 2018-09-22 [0.0.1] main
Created: 2024-11-22 [2.6b2] ColorFormatter

This file is a part of novelWriter
Copyright 2018–2024, Veronica Berglyd Olsen
Expand Down Expand Up @@ -77,6 +78,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
"version",
"info",
"debug",
"color",
"style=",
"config=",
"data=",
Expand All @@ -98,6 +100,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
" -v, --version Print program version and exit.\n"
" --info Print additional runtime information.\n"
" --debug Print debug output. Includes --info.\n"
" --color Add ANSI colors to log output.\n"
" --meminfo Show memory usage information in the status bar.\n"
" --style= Sets Qt5 style flag. Defaults to 'Fusion'.\n"
" --config= Alternative config file.\n"
Expand All @@ -107,6 +110,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
# Defaults
logLevel = logging.WARN
logFormat = "{levelname:8} {message:}"
logFormatter = logging.Formatter
confPath = None
dataPath = None
testMode = False
Expand Down Expand Up @@ -137,6 +141,8 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
CONFIG.isDebug = True
logLevel = logging.DEBUG
logFormat = "[{asctime:}] {filename:>18}:{lineno:<4d} {levelname:8} {message:}"
elif inOpt == "--color":
logFormatter = ColorFormatter
elif inOpt == "--style":
qtStyle = inArg
elif inOpt == "--config":
Expand All @@ -154,7 +160,7 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
if len(pkgLogger.handlers) == 0:
# Make sure we only create one logger (mostly an issue with tests)
cHandle = logging.StreamHandler()
cHandle.setFormatter(logging.Formatter(fmt=logFormat, style="{"))
cHandle.setFormatter(logFormatter(fmt=logFormat, style="{"))
pkgLogger.addHandler(cHandle)

logger.info("Starting novelWriter %s (%s) %s", __version__, __hexversion__, __date__)
Expand Down Expand Up @@ -241,3 +247,22 @@ def main(sysArgs: list | None = None) -> GuiMain | None:
nwGUI.postLaunchTasks(cmdOpen)

sys.exit(app.exec())


class ColorFormatter(logging.Formatter):

def __init__(self, fmt: str, style: str) -> None:
super().__init__(fmt, style="{")
self._formats = {
logging.DEBUG: f"\033[1;34m{fmt}\033[0m",
logging.INFO: f"\033[1;32m{fmt}\033[0m",
logging.WARNING: f"\033[1;33m{fmt}\033[0m",
logging.ERROR: f"\033[1;31m{fmt}\033[0m",
logging.CRITICAL: f"\033[1;31m{fmt}\033[0m",
}
return

def format(self, record: logging.LogRecord) -> str:
"""Overload the format string for each record."""
self._style._fmt = self._formats.get(record.levelno, "ERR")
return super().format(record)
27 changes: 24 additions & 3 deletions tests/test_base/test_base_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

import pytest

from novelwriter import CONFIG, logger, main
from novelwriter import CONFIG, ColorFormatter, logger, main

from tests.mocked import MockGuiMain

Expand Down Expand Up @@ -99,14 +99,14 @@ def testBaseInit_Options(monkeypatch, fncPath):

# Log Levels
nwGUI = main(
["--testmode", "--info", f"--config={fncPath}", f"--data={fncPath}"]
["--testmode", "--info", "--color", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.INFO
assert nwGUI.closeMain() == "closeMain"

nwGUI = main(
["--testmode", "--debug", f"--config={fncPath}", f"--data={fncPath}"]
["--testmode", "--debug", "--color", f"--config={fncPath}", f"--data={fncPath}"]
)
assert nwGUI is not None
assert logger.getEffectiveLevel() == logging.DEBUG
Expand Down Expand Up @@ -171,3 +171,24 @@ def testBaseInit_Imports(caplog, monkeypatch, fncPath):
assert "At least Python" in caplog.messages[0]
assert "At least Qt5" in caplog.messages[1]
assert "At least PyQt5" in caplog.messages[2]


@pytest.mark.base
def testBaseInit_ColorFormatter(qtbot, monkeypatch, fncPath, tstPaths):
"""Check launching the main GUI."""
formatter = ColorFormatter(fmt="<{message:}>", style="{")

record = logging.LogRecord("", logging.INFO, "", 1, "Info", None, None)
assert formatter.format(record) == "\x1b[1;32m<Info>\x1b[0m"

record = logging.LogRecord("", logging.DEBUG, "", 1, "Debug", None, None)
assert formatter.format(record) == "\x1b[1;34m<Debug>\x1b[0m"

record = logging.LogRecord("", logging.WARNING, "", 1, "Warning", None, None)
assert formatter.format(record) == "\x1b[1;33m<Warning>\x1b[0m"

record = logging.LogRecord("", logging.ERROR, "", 1, "Error", None, None)
assert formatter.format(record) == "\x1b[1;31m<Error>\x1b[0m"

record = logging.LogRecord("", logging.CRITICAL, "", 1, "Critical", None, None)
assert formatter.format(record) == "\x1b[1;31m<Critical>\x1b[0m"