From 0d7bbafe9bf18ef9d1dda65bab5a5ae699d97d72 Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Mon, 6 Sep 2021 23:48:34 +0200 Subject: Move logging to a separate module --- bot/log.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 bot/log.py (limited to 'bot/log.py') diff --git a/bot/log.py b/bot/log.py new file mode 100644 index 00000000..1f4b9159 --- /dev/null +++ b/bot/log.py @@ -0,0 +1,63 @@ +import logging +import logging.handlers +import os +from pathlib import Path + +from bot.constants import Client + + +def setup() -> None: + """Set up loggers.""" + # Configure the "TRACE" logging level (e.g. "log.trace(message)") + logging.TRACE = 5 + logging.addLevelName(logging.TRACE, "TRACE") + logging.Logger.trace = _monkeypatch_trace + + # Set up file logging + log_dir = Path("bot/log") + log_file = log_dir / "hackbot.log" + os.makedirs(log_dir, exist_ok=True) + + # File handler rotates logs every 5 MB + file_handler = logging.handlers.RotatingFileHandler( + log_file, maxBytes=5 * (2 ** 20), backupCount=10, encoding="utf-8", + ) + file_handler.setLevel(logging.TRACE if Client.debug else logging.DEBUG) + + # Console handler prints to terminal + console_handler = logging.StreamHandler() + level = logging.TRACE if Client.debug else logging.INFO + console_handler.setLevel(level) + + # Remove old loggers, if any + root = logging.getLogger() + if root.handlers: + for handler in root.handlers: + root.removeHandler(handler) + + # Silence irrelevant loggers + logging.getLogger("discord").setLevel(logging.ERROR) + logging.getLogger("websockets").setLevel(logging.ERROR) + logging.getLogger("PIL").setLevel(logging.ERROR) + logging.getLogger("matplotlib").setLevel(logging.ERROR) + logging.getLogger("async_rediscache").setLevel(logging.WARNING) + + # Setup new logging configuration + logging.basicConfig( + format="%(asctime)s - %(name)s %(levelname)s: %(message)s", + datefmt="%D %H:%M:%S", + level=logging.TRACE if Client.debug else logging.DEBUG, + handlers=[console_handler, file_handler], + ) + logging.getLogger().info("Logging initialization complete") + + +def _monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None: + """ + Log 'msg % args' with severity 'TRACE'. + + To pass exception information, use the keyword argument exc_info with a true value, e.g. + logger.trace("Houston, we have an %s", "interesting problem", exc_info=1) + """ + if self.isEnabledFor(logging.TRACE): + self._log(logging.TRACE, msg, args, **kwargs) -- cgit v1.2.3 From c2c0aefae5d80f9daaecb4dcd2bcd776c14660cf Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Mon, 6 Sep 2021 23:52:22 +0200 Subject: Move logging to a top level logs directory and update file name --- bot/log.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'bot/log.py') diff --git a/bot/log.py b/bot/log.py index 1f4b9159..c8de957e 100644 --- a/bot/log.py +++ b/bot/log.py @@ -1,6 +1,5 @@ import logging import logging.handlers -import os from pathlib import Path from bot.constants import Client @@ -14,9 +13,8 @@ def setup() -> None: logging.Logger.trace = _monkeypatch_trace # Set up file logging - log_dir = Path("bot/log") - log_file = log_dir / "hackbot.log" - os.makedirs(log_dir, exist_ok=True) + log_file = Path("logs/sir-lancebot.log") + log_file.parent.mkdir(exist_ok=True) # File handler rotates logs every 5 MB file_handler = logging.handlers.RotatingFileHandler( -- cgit v1.2.3 From 9f2dcc30433d90739bd8a9bbc8f50fabbcd7d7fe Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Mon, 6 Sep 2021 23:53:49 +0200 Subject: Remove handler cleanup This doesn't seem to do anything as no handlers are set up beforehand --- bot/log.py | 6 ------ 1 file changed, 6 deletions(-) (limited to 'bot/log.py') diff --git a/bot/log.py b/bot/log.py index c8de957e..0e0c22f0 100644 --- a/bot/log.py +++ b/bot/log.py @@ -27,12 +27,6 @@ def setup() -> None: level = logging.TRACE if Client.debug else logging.INFO console_handler.setLevel(level) - # Remove old loggers, if any - root = logging.getLogger() - if root.handlers: - for handler in root.handlers: - root.removeHandler(handler) - # Silence irrelevant loggers logging.getLogger("discord").setLevel(logging.ERROR) logging.getLogger("websockets").setLevel(logging.ERROR) -- cgit v1.2.3 From e135189446e4a450376b9c694f1faadd57fef19a Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 7 Sep 2021 00:04:40 +0200 Subject: Control logging levels through root logger instead of handlers The difference in the logging levels without debug mode enabled was also removed --- bot/log.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'bot/log.py') diff --git a/bot/log.py b/bot/log.py index 0e0c22f0..9ae40984 100644 --- a/bot/log.py +++ b/bot/log.py @@ -20,12 +20,8 @@ def setup() -> None: file_handler = logging.handlers.RotatingFileHandler( log_file, maxBytes=5 * (2 ** 20), backupCount=10, encoding="utf-8", ) - file_handler.setLevel(logging.TRACE if Client.debug else logging.DEBUG) - # Console handler prints to terminal console_handler = logging.StreamHandler() - level = logging.TRACE if Client.debug else logging.INFO - console_handler.setLevel(level) # Silence irrelevant loggers logging.getLogger("discord").setLevel(logging.ERROR) @@ -38,7 +34,7 @@ def setup() -> None: logging.basicConfig( format="%(asctime)s - %(name)s %(levelname)s: %(message)s", datefmt="%D %H:%M:%S", - level=logging.TRACE if Client.debug else logging.DEBUG, + level=logging.TRACE if Client.debug else logging.INFO, handlers=[console_handler, file_handler], ) logging.getLogger().info("Logging initialization complete") -- cgit v1.2.3 From 6d7f46fabe530e4cafdffa25d7fced730965e481 Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 7 Sep 2021 00:15:36 +0200 Subject: Add coloredlogs --- bot/log.py | 18 +++++++++++++++++- poetry.lock | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++------- pyproject.toml | 2 ++ 3 files changed, 72 insertions(+), 8 deletions(-) (limited to 'bot/log.py') diff --git a/bot/log.py b/bot/log.py index 9ae40984..d44e6933 100644 --- a/bot/log.py +++ b/bot/log.py @@ -1,7 +1,11 @@ import logging import logging.handlers +import os +import sys from pathlib import Path +import coloredlogs + from bot.constants import Client @@ -23,6 +27,19 @@ def setup() -> None: # Console handler prints to terminal console_handler = logging.StreamHandler() + if "COLOREDLOGS_LEVEL_STYLES" not in os.environ: + coloredlogs.DEFAULT_LEVEL_STYLES = { + **coloredlogs.DEFAULT_LEVEL_STYLES, + "trace": {"color": 246}, + "critical": {"background": "red"}, + "debug": coloredlogs.DEFAULT_LEVEL_STYLES["info"], + } + + if "COLOREDLOGS_LOG_FORMAT" not in os.environ: + coloredlogs.DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s %(levelname)s: %(message)s" + + coloredlogs.install(stream=sys.stdout) + # Silence irrelevant loggers logging.getLogger("discord").setLevel(logging.ERROR) logging.getLogger("websockets").setLevel(logging.ERROR) @@ -32,7 +49,6 @@ def setup() -> None: # Setup new logging configuration logging.basicConfig( - format="%(asctime)s - %(name)s %(levelname)s: %(message)s", datefmt="%D %H:%M:%S", level=logging.TRACE if Client.debug else logging.INFO, handlers=[console_handler, file_handler], diff --git a/poetry.lock b/poetry.lock index 289f2039..328a474f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -139,10 +139,24 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" name = "colorama" version = "0.4.4" description = "Cross-platform colored terminal text." -category = "dev" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coloredlogs" +version = "15.0.1" +description = "Colored terminal output for Python's logging module" +category = "main" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +[package.dependencies] +humanfriendly = ">=9.1" + +[package.extras] +cron = ["capturer (>=2.4)"] + [[package]] name = "cycler" version = "0.10.0" @@ -328,6 +342,17 @@ category = "main" optional = false python-versions = ">=3.6" +[[package]] +name = "humanfriendly" +version = "9.2" +description = "Human friendly output for text interfaces using Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +pyreadline = {version = "*", markers = "sys_platform == \"win32\""} + [[package]] name = "identify" version = "2.2.13" @@ -552,6 +577,14 @@ category = "main" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +[[package]] +name = "pyreadline" +version = "2.1" +description = "A python implmementation of GNU readline." +category = "main" +optional = false +python-versions = "*" + [[package]] name = "python-dateutil" version = "2.8.2" @@ -565,11 +598,11 @@ six = ">=1.5" [[package]] name = "python-dotenv" -version = "0.15.0" -description = "Add .env support to your django/flask apps in development and deployments" +version = "0.19.0" +description = "Read key-value pairs from a .env file and set them as environment variables" category = "dev" optional = false -python-versions = "*" +python-versions = ">=3.5" [package.extras] cli = ["click (>=5.0)"] @@ -730,7 +763,7 @@ multidict = ">=4.0" [metadata] lock-version = "1.1" python-versions = "^3.9" -content-hash = "9efbf6be5298ab8ace2588e218be309e105987bfdfa8317453d584a1faac4934" +content-hash = "f519d64a4c9286d08a77e33c6776a7da280494a008826a069daad8bea3d11995" [metadata.files] aiodns = [ @@ -863,6 +896,10 @@ colorama = [ {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, ] +coloredlogs = [ + {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, + {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, +] cycler = [ {file = "cycler-0.10.0-py2.py3-none-any.whl", hash = "sha256:1d8a5ae1ff6c5cf9b93e8811e581232ad8920aeec647c37316ceac982b08cb2d"}, {file = "cycler-0.10.0.tar.gz", hash = "sha256:cd7b2d1018258d7247a71425e9f26463dfb444d411c39569972f4ce586b0c9d8"}, @@ -962,6 +999,10 @@ hiredis = [ {file = "hiredis-2.0.0-pp37-pypy37_pp73-win32.whl", hash = "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0"}, {file = "hiredis-2.0.0.tar.gz", hash = "sha256:81d6d8e39695f2c37954d1011c0480ef7cf444d4e3ae24bc5e89ee5de360139a"}, ] +humanfriendly = [ + {file = "humanfriendly-9.2-py2.py3-none-any.whl", hash = "sha256:332da98c24cc150efcc91b5508b19115209272bfdf4b0764a56795932f854271"}, + {file = "humanfriendly-9.2.tar.gz", hash = "sha256:f7dba53ac7935fd0b4a2fc9a29e316ddd9ea135fb3052d3d0279d10c18ff9c48"}, +] identify = [ {file = "identify-2.2.13-py2.py3-none-any.whl", hash = "sha256:7199679b5be13a6b40e6e19ea473e789b11b4e3b60986499b1f589ffb03c217c"}, {file = "identify-2.2.13.tar.gz", hash = "sha256:7bc6e829392bd017236531963d2d937d66fc27cadc643ac0aba2ce9f26157c79"}, @@ -1279,13 +1320,18 @@ pyparsing = [ {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"}, {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"}, ] +pyreadline = [ + {file = "pyreadline-2.1.win-amd64.exe", hash = "sha256:9ce5fa65b8992dfa373bddc5b6e0864ead8f291c94fbfec05fbd5c836162e67b"}, + {file = "pyreadline-2.1.win32.exe", hash = "sha256:65540c21bfe14405a3a77e4c085ecfce88724743a4ead47c66b84defcf82c32e"}, + {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, +] python-dateutil = [ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, ] python-dotenv = [ - {file = "python-dotenv-0.15.0.tar.gz", hash = "sha256:587825ed60b1711daea4832cf37524dfd404325b7db5e25ebe88c495c9f807a0"}, - {file = "python_dotenv-0.15.0-py2.py3-none-any.whl", hash = "sha256:0c8d1b80d1a1e91717ea7d526178e3882732420b03f08afea0406db6402e220e"}, + {file = "python-dotenv-0.19.0.tar.gz", hash = "sha256:f521bc2ac9a8e03c736f62911605c5d83970021e3fa95b37d769e2bbbe9b6172"}, + {file = "python_dotenv-0.19.0-py2.py3-none-any.whl", hash = "sha256:aae25dc1ebe97c420f50b81fb0e5c949659af713f31fdb63c749ca68748f34b1"}, ] pyyaml = [ {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, diff --git a/pyproject.toml b/pyproject.toml index 7848f593..31689468 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,6 +18,8 @@ PyYAML = "~=5.4" async-rediscache = {extras = ["fakeredis"], version = "~=0.1.4"} emojis = "~=0.6.0" matplotlib = "~=3.4.1" +coloredlogs = "~=15.0" +colorama = { version = "~=0.4.3", markers = "sys_platform == 'win32'" } [tool.poetry.dev-dependencies] flake8 = "~=3.8" -- cgit v1.2.3 From ac63915dd38c920cbe1d2d194ecd1bc2168d7570 Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 7 Sep 2021 00:20:30 +0200 Subject: Remove basicConfig configuration The basicConfig now only controlled the file handler which could've been confusing. The format string was also changed to use the same style as bot --- bot/log.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'bot/log.py') diff --git a/bot/log.py b/bot/log.py index d44e6933..c8437505 100644 --- a/bot/log.py +++ b/bot/log.py @@ -16,6 +16,9 @@ def setup() -> None: logging.addLevelName(logging.TRACE, "TRACE") logging.Logger.trace = _monkeypatch_trace + format_string = "%(asctime)s | %(name)s | %(levelname)s | %(message)s" + log_format = logging.Formatter(format_string) + # Set up file logging log_file = Path("logs/sir-lancebot.log") log_file.parent.mkdir(exist_ok=True) @@ -24,8 +27,11 @@ def setup() -> None: file_handler = logging.handlers.RotatingFileHandler( log_file, maxBytes=5 * (2 ** 20), backupCount=10, encoding="utf-8", ) - # Console handler prints to terminal - console_handler = logging.StreamHandler() + file_handler.setFormatter(log_format) + + root_logger = logging.getLogger() + root_logger.setLevel(logging.TRACE if Client.debug else logging.INFO) + root_logger.addHandler(file_handler) if "COLOREDLOGS_LEVEL_STYLES" not in os.environ: coloredlogs.DEFAULT_LEVEL_STYLES = { @@ -36,7 +42,7 @@ def setup() -> None: } if "COLOREDLOGS_LOG_FORMAT" not in os.environ: - coloredlogs.DEFAULT_LOG_FORMAT = "%(asctime)s - %(name)s %(levelname)s: %(message)s" + coloredlogs.DEFAULT_LOG_FORMAT = format_string coloredlogs.install(stream=sys.stdout) @@ -47,13 +53,7 @@ def setup() -> None: logging.getLogger("matplotlib").setLevel(logging.ERROR) logging.getLogger("async_rediscache").setLevel(logging.WARNING) - # Setup new logging configuration - logging.basicConfig( - datefmt="%D %H:%M:%S", - level=logging.TRACE if Client.debug else logging.INFO, - handlers=[console_handler, file_handler], - ) - logging.getLogger().info("Logging initialization complete") + root_logger.info("Logging initialization complete") def _monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None: -- cgit v1.2.3 From ecfe6bc1b89632c1161f5aad83a08a8459f8f13a Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Tue, 7 Sep 2021 00:32:12 +0200 Subject: Add selective trace loggers Add selective enabling of trace loggers as described in python-discord/bot#1529 --- bot/constants.py | 1 + bot/log.py | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 2 deletions(-) (limited to 'bot/log.py') diff --git a/bot/constants.py b/bot/constants.py index 2313bfdb..ae6e02c1 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -138,6 +138,7 @@ class Client(NamedTuple): github_bot_repo = "https://github.com/python-discord/sir-lancebot" # Override seasonal locks: 1 (January) to 12 (December) month_override = int(environ["MONTH_OVERRIDE"]) if "MONTH_OVERRIDE" in environ else None + trace_loggers = environ.get("BOT_TRACE_LOGGERS") class Colours: diff --git a/bot/log.py b/bot/log.py index c8437505..5e0e909d 100644 --- a/bot/log.py +++ b/bot/log.py @@ -30,7 +30,6 @@ def setup() -> None: file_handler.setFormatter(log_format) root_logger = logging.getLogger() - root_logger.setLevel(logging.TRACE if Client.debug else logging.INFO) root_logger.addHandler(file_handler) if "COLOREDLOGS_LEVEL_STYLES" not in os.environ: @@ -44,8 +43,9 @@ def setup() -> None: if "COLOREDLOGS_LOG_FORMAT" not in os.environ: coloredlogs.DEFAULT_LOG_FORMAT = format_string - coloredlogs.install(stream=sys.stdout) + coloredlogs.install(level=logging.TRACE, stream=sys.stdout) + root_logger.setLevel(logging.DEBUG if Client.debug else logging.INFO) # Silence irrelevant loggers logging.getLogger("discord").setLevel(logging.ERROR) logging.getLogger("websockets").setLevel(logging.ERROR) @@ -53,6 +53,8 @@ def setup() -> None: logging.getLogger("matplotlib").setLevel(logging.ERROR) logging.getLogger("async_rediscache").setLevel(logging.WARNING) + _set_trace_loggers() + root_logger.info("Logging initialization complete") @@ -65,3 +67,30 @@ def _monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None: """ if self.isEnabledFor(logging.TRACE): self._log(logging.TRACE, msg, args, **kwargs) + + +def _set_trace_loggers() -> None: + """ + Set loggers to the trace level according to the value from the BOT_TRACE_LOGGERS env var. + + When the env var is a list of logger names delimited by a comma, + each of the listed loggers will be set to the trace level. + + If this list is prefixed with a "!", all of the loggers except the listed ones will be set to the trace level. + + Otherwise if the env var begins with a "*", + the root logger is set to the trace level and other contents are ignored. + """ + level_filter = Client.trace_loggers + if level_filter: + if level_filter.startswith("*"): + logging.getLogger().setLevel(logging.TRACE) + + elif level_filter.startswith("!"): + logging.getLogger().setLevel(logging.TRACE) + for logger_name in level_filter.strip("!,").split(","): + logging.getLogger(logger_name).setLevel(logging.DEBUG) + + else: + for logger_name in level_filter.strip(",").split(","): + logging.getLogger(logger_name).setLevel(logging.TRACE) -- cgit v1.2.3 From 6a9f1886c70658636f36272ea8b8f4c3f5f4a7a9 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Fri, 12 Nov 2021 06:09:32 +0400 Subject: Disable File Logs In Production The most recent changes to our log setup had the loggers writing to a read-only location in prod. This would cause an error during startup. To get around this while keeping the change, I moved it to only be used if debug is True. Signed-off-by: Hassan Abouelela --- bot/log.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) (limited to 'bot/log.py') diff --git a/bot/log.py b/bot/log.py index 5e0e909d..97561be4 100644 --- a/bot/log.py +++ b/bot/log.py @@ -18,19 +18,22 @@ def setup() -> None: format_string = "%(asctime)s | %(name)s | %(levelname)s | %(message)s" log_format = logging.Formatter(format_string) + root_logger = logging.getLogger() - # Set up file logging - log_file = Path("logs/sir-lancebot.log") - log_file.parent.mkdir(exist_ok=True) + # Copied from constants file, which we can't import yet since loggers aren't instantiated + debug = os.environ.get("BOT_DEBUG", "true").lower() == "true" - # File handler rotates logs every 5 MB - file_handler = logging.handlers.RotatingFileHandler( - log_file, maxBytes=5 * (2 ** 20), backupCount=10, encoding="utf-8", - ) - file_handler.setFormatter(log_format) + if debug: + # Set up file logging + log_file = Path("logs/sir-lancebot.log") + log_file.parent.mkdir(exist_ok=True) - root_logger = logging.getLogger() - root_logger.addHandler(file_handler) + # File handler rotates logs every 5 MB + file_handler = logging.handlers.RotatingFileHandler( + log_file, maxBytes=5 * (2 ** 20), backupCount=10, encoding="utf-8", + ) + file_handler.setFormatter(log_format) + root_logger.addHandler(file_handler) if "COLOREDLOGS_LEVEL_STYLES" not in os.environ: coloredlogs.DEFAULT_LEVEL_STYLES = { -- cgit v1.2.3