From 9afd572948ef9c9e2e10fda6c23bbe7951e48e43 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Feb 2025 16:38:22 +0100 Subject: [PATCH 01/36] chore: add "log init from config" ... --- freqtrade/loggers/__init__.py | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 1d01d4177..6053069b7 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -53,18 +53,63 @@ def setup_logging_pre() -> None: ) +logging_config = { + "version": 1, + # "incremental": True, + # "disable_existing_loggers": False, + "formatters": { + "basic": {"format": "%(message)s"}, + "standard": { + "format": LOGFORMAT, + }, + }, + "handlers": { + "console": { + "class": "freqtrade.loggers.ft_rich_handler.FtRichHandler", + "console": error_console, + "formatter": "basic", + # "class": "logging.StreamHandler", + # "formatter": "standard", + # "stream": "ext://sys.stdout", + }, + "file": { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "standard", + "filename": "whatever.log", + "maxBytes": 1024 * 1024 * 10, # 10Mb + "backupCount": 10, + }, + }, + "loggers": { + "freqtrade": { + # "handlers": ["console", "file"], + "level": "INFO", + "propagate": True, + }, + }, + "root": { + "handlers": ["console", "file"], + "level": "INFO", + }, +} + + def setup_logging(config: Config) -> None: """ Process -v/--verbose, --logfile options """ # Log level verbosity = config["verbosity"] + + logging.config.dictConfig(logging_config) + logging.root.addHandler(bufferHandler) if config.get("print_colorized", True): logger.info("Enabling colorized output.") error_console._color_system = error_console._detect_color_system() logfile = config.get("logfile") + logging.info("Logfile configured") if logfile: s = logfile.split(":") From 41418784e36620bb0fc83a56631443d199c36a78 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 13:09:55 +0100 Subject: [PATCH 02/36] feat: add log_config generator --- freqtrade/loggers/__init__.py | 123 +++++++++++++++++++++------------- 1 file changed, 76 insertions(+), 47 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 6053069b7..49a9cf042 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -72,13 +72,13 @@ logging_config = { # "formatter": "standard", # "stream": "ext://sys.stdout", }, - "file": { - "class": "logging.handlers.RotatingFileHandler", - "formatter": "standard", - "filename": "whatever.log", - "maxBytes": 1024 * 1024 * 10, # 10Mb - "backupCount": 10, - }, + # "file": { + # "class": "logging.handlers.RotatingFileHandler", + # "formatter": "standard", + # "filename": "whatever.log", + # "maxBytes": 1024 * 1024 * 10, # 10Mb + # "backupCount": 10, + # }, }, "loggers": { "freqtrade": { @@ -88,7 +88,10 @@ logging_config = { }, }, "root": { - "handlers": ["console", "file"], + "handlers": [ + "console", + # "file", + ], "level": "INFO", }, } @@ -101,60 +104,75 @@ def setup_logging(config: Config) -> None: # Log level verbosity = config["verbosity"] - logging.config.dictConfig(logging_config) - - logging.root.addHandler(bufferHandler) - if config.get("print_colorized", True): - logger.info("Enabling colorized output.") - error_console._color_system = error_console._detect_color_system() + # Get log_config from user config or use default + log_config = config.get("log_config", logging_config.copy()) logfile = config.get("logfile") - logging.info("Logfile configured") if logfile: s = logfile.split(":") if s[0] == "syslog": - # Address can be either a string (socket filename) for Unix domain socket or - # a tuple (hostname, port) for UDP socket. - # Address can be omitted (i.e. simple 'syslog' used as the value of - # config['logfilename']), which defaults to '/dev/log', applicable for most - # of the systems. - address = (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else "/dev/log" - if handler_sl := get_existing_handlers(SysLogHandler): - logging.root.removeHandler(handler_sl) - handler_sl = SysLogHandler(address=address) - # No datetime field for logging into syslog, to allow syslog - # to perform reduction of repeating messages if this is set in the - # syslog config. The messages should be equal for this. - handler_sl.setFormatter(Formatter("%(name)s - %(levelname)s - %(message)s")) - logging.root.addHandler(handler_sl) + # Add syslog handler to the config + log_config["handlers"]["syslog"] = { + "class": "logging.handlers.SysLogHandler", + "formatter": "syslog_format", + "address": (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else "/dev/log", + } + # Add the syslog formatter if not already present + if "syslog_format" not in log_config["formatters"]: + log_config["formatters"]["syslog_format"] = { + "format": "%(name)s - %(levelname)s - %(message)s" + } + # Add handler to root + if "syslog" not in log_config["root"]["handlers"]: + log_config["root"]["handlers"].append("syslog") + elif s[0] == "journald": # pragma: no cover + # Check if we have the module available try: - from cysystemd.journal import JournaldLogHandler + from cysystemd.journal import JournaldLogHandler # noqa: F401 except ImportError: raise OperationalException( "You need the cysystemd python package be installed in " "order to use logging to journald." ) - if handler_jd := get_existing_handlers(JournaldLogHandler): - logging.root.removeHandler(handler_jd) - handler_jd = JournaldLogHandler() - # No datetime field for logging into journald, to allow syslog - # to perform reduction of repeating messages if this is set in the - # syslog config. The messages should be equal for this. - handler_jd.setFormatter(Formatter("%(name)s - %(levelname)s - %(message)s")) - logging.root.addHandler(handler_jd) + + # Add journald handler to the config + log_config["handlers"]["journald"] = { + "class": "cysystemd.journal.JournaldLogHandler", + "formatter": "journald_format", + } + # Add the journald formatter if not already present + if "journald_format" not in log_config["formatters"]: + log_config["formatters"]["journald_format"] = { + "format": "%(name)s - %(levelname)s - %(message)s" + } + # Add handler to root + if "journald" not in log_config["root"]["handlers"]: + log_config["root"]["handlers"].append("journald") + else: - if handler_rf := get_existing_handlers(RotatingFileHandler): - logging.root.removeHandler(handler_rf) + # Regular file logging try: logfile_path = Path(logfile) logfile_path.parent.mkdir(parents=True, exist_ok=True) - handler_rf = RotatingFileHandler( - logfile_path, - maxBytes=1024 * 1024 * 10, # 10Mb - backupCount=10, - ) + + # Update file handler configuration + if "file" in log_config["handlers"]: + log_config["handlers"]["file"]["filename"] = str(logfile_path) + else: + log_config["handlers"]["file"] = { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "standard", + "filename": str(logfile_path), + "maxBytes": 1024 * 1024 * 10, # 10Mb + "backupCount": 10, + } + + # Ensure file handler is in root handlers + if "file" not in log_config["root"]["handlers"]: + log_config["root"]["handlers"].append("file") + except PermissionError: raise OperationalException( f'Failed to create or access log file "{logfile_path.absolute()}". ' @@ -164,9 +182,20 @@ def setup_logging(config: Config) -> None: "non-root user, delete and recreate the directories you need, and then try " "again." ) - handler_rf.setFormatter(Formatter(LOGFORMAT)) - logging.root.addHandler(handler_rf) + # Apply the configuration + logging.config.dictConfig(log_config) + + # Add buffer handler to root logger + logging.root.addHandler(bufferHandler) + # Set color system for console output + if config.get("print_colorized", True): + logger.info("Enabling colorized output.") + error_console._color_system = error_console._detect_color_system() + + logging.info("Logfile configured") + + # Set verbosity levels logging.root.setLevel(logging.INFO if verbosity < 1 else logging.DEBUG) set_loggers(verbosity, config.get("api_server", {}).get("verbosity", "info")) From 4ca2a043b528274852cabe9d6c4f3d50bb5ef9cf Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 13:11:29 +0100 Subject: [PATCH 03/36] refactor: extract log_config creation --- freqtrade/loggers/__init__.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 49a9cf042..bae59f66a 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -1,7 +1,7 @@ import logging from logging import Formatter -from logging.handlers import RotatingFileHandler, SysLogHandler from pathlib import Path +from typing import Any from freqtrade.constants import Config from freqtrade.exceptions import OperationalException @@ -97,13 +97,7 @@ logging_config = { } -def setup_logging(config: Config) -> None: - """ - Process -v/--verbose, --logfile options - """ - # Log level - verbosity = config["verbosity"] - +def _create_log_config(config: Config) -> dict[str, Any]: # Get log_config from user config or use default log_config = config.get("log_config", logging_config.copy()) @@ -182,6 +176,17 @@ def setup_logging(config: Config) -> None: "non-root user, delete and recreate the directories you need, and then try " "again." ) + return log_config + + +def setup_logging(config: Config) -> None: + """ + Process -v/--verbose, --logfile options + """ + # Log level + verbosity = config["verbosity"] + + log_config = _create_log_config(config) # Apply the configuration logging.config.dictConfig(log_config) From 1c6c710696bd8d3caa349943c26f7de2f2f0aa36 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 13:11:52 +0100 Subject: [PATCH 04/36] chore: rename log_config naming --- freqtrade/loggers/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index bae59f66a..162cfcc62 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -53,7 +53,7 @@ def setup_logging_pre() -> None: ) -logging_config = { +FT_LOGGING_CONFIG = { "version": 1, # "incremental": True, # "disable_existing_loggers": False, @@ -99,7 +99,7 @@ logging_config = { def _create_log_config(config: Config) -> dict[str, Any]: # Get log_config from user config or use default - log_config = config.get("log_config", logging_config.copy()) + log_config = config.get("log_config", FT_LOGGING_CONFIG.copy()) logfile = config.get("logfile") From 70a81c86ba6c884c94dba78e3d2f2e17c06a414f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 13:16:25 +0100 Subject: [PATCH 05/36] feat: dynamically assign error console --- freqtrade/loggers/__init__.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 162cfcc62..c6294370d 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -66,7 +66,6 @@ FT_LOGGING_CONFIG = { "handlers": { "console": { "class": "freqtrade.loggers.ft_rich_handler.FtRichHandler", - "console": error_console, "formatter": "basic", # "class": "logging.StreamHandler", # "formatter": "standard", @@ -101,9 +100,12 @@ def _create_log_config(config: Config) -> dict[str, Any]: # Get log_config from user config or use default log_config = config.get("log_config", FT_LOGGING_CONFIG.copy()) - logfile = config.get("logfile") + # Dynamically update any FtRichHandler with the error_console + for handler_config in log_config.get("handlers", {}).values(): + if handler_config.get("class") == "freqtrade.loggers.ft_rich_handler.FtRichHandler": + handler_config["console"] = error_console - if logfile: + if logfile := config.get("logfile"): s = logfile.split(":") if s[0] == "syslog": # Add syslog handler to the config From 55d71cecdd6be29ee53ba421fa52b26cdc698ff1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 13:19:42 +0100 Subject: [PATCH 06/36] refactor: move root-handler adding to separate function --- freqtrade/loggers/__init__.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index c6294370d..eb814c2d9 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -96,11 +96,16 @@ FT_LOGGING_CONFIG = { } +def _add_root_handler(log_config: dict[str, Any], handler_name: str): + if handler_name not in log_config["root"]["handlers"]: + log_config["root"]["handlers"].append(handler_name) + + def _create_log_config(config: Config) -> dict[str, Any]: # Get log_config from user config or use default log_config = config.get("log_config", FT_LOGGING_CONFIG.copy()) - # Dynamically update any FtRichHandler with the error_console + # Dynamically update any FtRichHandler with the proper console object for handler_config in log_config.get("handlers", {}).values(): if handler_config.get("class") == "freqtrade.loggers.ft_rich_handler.FtRichHandler": handler_config["console"] = error_console @@ -119,9 +124,7 @@ def _create_log_config(config: Config) -> dict[str, Any]: log_config["formatters"]["syslog_format"] = { "format": "%(name)s - %(levelname)s - %(message)s" } - # Add handler to root - if "syslog" not in log_config["root"]["handlers"]: - log_config["root"]["handlers"].append("syslog") + _add_root_handler(log_config, "syslog") elif s[0] == "journald": # pragma: no cover # Check if we have the module available @@ -143,9 +146,7 @@ def _create_log_config(config: Config) -> dict[str, Any]: log_config["formatters"]["journald_format"] = { "format": "%(name)s - %(levelname)s - %(message)s" } - # Add handler to root - if "journald" not in log_config["root"]["handlers"]: - log_config["root"]["handlers"].append("journald") + _add_root_handler(log_config, "journald") else: # Regular file logging From 1aa6c2ad55ef26bba520e3f53450f9073a3047a5 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 13:23:17 +0100 Subject: [PATCH 07/36] feat: Improve filehandler config --- freqtrade/loggers/__init__.py | 44 +++++++++++++++++------------------ 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index eb814c2d9..cbe0c2795 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -105,11 +105,6 @@ def _create_log_config(config: Config) -> dict[str, Any]: # Get log_config from user config or use default log_config = config.get("log_config", FT_LOGGING_CONFIG.copy()) - # Dynamically update any FtRichHandler with the proper console object - for handler_config in log_config.get("handlers", {}).values(): - if handler_config.get("class") == "freqtrade.loggers.ft_rich_handler.FtRichHandler": - handler_config["console"] = error_console - if logfile := config.get("logfile"): s = logfile.split(":") if s[0] == "syslog": @@ -150,26 +145,28 @@ def _create_log_config(config: Config) -> dict[str, Any]: else: # Regular file logging + # Update existing file handler configuration + if "file" in log_config["handlers"]: + log_config["handlers"]["file"]["filename"] = logfile + else: + log_config["handlers"]["file"] = { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "standard", + "filename": logfile, + "maxBytes": 1024 * 1024 * 10, # 10Mb + "backupCount": 10, + } + _add_root_handler(log_config, "file") + + # Dynamically update some handlers + for handler_config in log_config.get("handlers", {}).values(): + if handler_config.get("class") == "freqtrade.loggers.ft_rich_handler.FtRichHandler": + handler_config["console"] = error_console + elif handler_config.get("class") == "logging.handlers.RotatingFileHandler": + logfile_path = Path(handler_config["filename"]) try: - logfile_path = Path(logfile) + # Create parent for filehandler logfile_path.parent.mkdir(parents=True, exist_ok=True) - - # Update file handler configuration - if "file" in log_config["handlers"]: - log_config["handlers"]["file"]["filename"] = str(logfile_path) - else: - log_config["handlers"]["file"] = { - "class": "logging.handlers.RotatingFileHandler", - "formatter": "standard", - "filename": str(logfile_path), - "maxBytes": 1024 * 1024 * 10, # 10Mb - "backupCount": 10, - } - - # Ensure file handler is in root handlers - if "file" not in log_config["root"]["handlers"]: - log_config["root"]["handlers"].append("file") - except PermissionError: raise OperationalException( f'Failed to create or access log file "{logfile_path.absolute()}". ' @@ -179,6 +176,7 @@ def _create_log_config(config: Config) -> dict[str, Any]: "non-root user, delete and recreate the directories you need, and then try " "again." ) + return log_config From cd77758852fb58d2f1abbefa65b36d0504dba2b7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 13:25:03 +0100 Subject: [PATCH 08/36] chore: remove some unnecessary comments --- freqtrade/loggers/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index cbe0c2795..cc78cc557 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -184,16 +184,15 @@ def setup_logging(config: Config) -> None: """ Process -v/--verbose, --logfile options """ - # Log level verbosity = config["verbosity"] log_config = _create_log_config(config) - # Apply the configuration logging.config.dictConfig(log_config) # Add buffer handler to root logger logging.root.addHandler(bufferHandler) + # Set color system for console output if config.get("print_colorized", True): logger.info("Enabling colorized output.") From 030dcfdd83e283f0834e150eb429b583f3bcd1c0 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 13:30:26 +0100 Subject: [PATCH 09/36] chore: extract log formatter addition --- freqtrade/loggers/__init__.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index cc78cc557..5f8581cf5 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -101,6 +101,11 @@ def _add_root_handler(log_config: dict[str, Any], handler_name: str): log_config["root"]["handlers"].append(handler_name) +def _add_formatter(log_config: dict[str, Any], format_name: str, format: str): + if format_name not in log_config["formatters"]: + log_config["formatters"][format_name] = {"format": format} + + def _create_log_config(config: Config) -> dict[str, Any]: # Get log_config from user config or use default log_config = config.get("log_config", FT_LOGGING_CONFIG.copy()) @@ -114,11 +119,8 @@ def _create_log_config(config: Config) -> dict[str, Any]: "formatter": "syslog_format", "address": (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else "/dev/log", } - # Add the syslog formatter if not already present - if "syslog_format" not in log_config["formatters"]: - log_config["formatters"]["syslog_format"] = { - "format": "%(name)s - %(levelname)s - %(message)s" - } + + _add_formatter(log_config, "syslog_format", "%(name)s - %(levelname)s - %(message)s") _add_root_handler(log_config, "syslog") elif s[0] == "journald": # pragma: no cover @@ -136,11 +138,8 @@ def _create_log_config(config: Config) -> dict[str, Any]: "class": "cysystemd.journal.JournaldLogHandler", "formatter": "journald_format", } - # Add the journald formatter if not already present - if "journald_format" not in log_config["formatters"]: - log_config["formatters"]["journald_format"] = { - "format": "%(name)s - %(levelname)s - %(message)s" - } + + _add_formatter(log_config, "journald_format", "%(name)s - %(levelname)s - %(message)s") _add_root_handler(log_config, "journald") else: From bf33f8b632d2c4ffe28c1aad16a66ecbefd8365f Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 13:33:43 +0100 Subject: [PATCH 10/36] chore: clean up log-levels --- freqtrade/loggers/__init__.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 5f8581cf5..7ecbbb58c 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -67,21 +67,10 @@ FT_LOGGING_CONFIG = { "console": { "class": "freqtrade.loggers.ft_rich_handler.FtRichHandler", "formatter": "basic", - # "class": "logging.StreamHandler", - # "formatter": "standard", - # "stream": "ext://sys.stdout", }, - # "file": { - # "class": "logging.handlers.RotatingFileHandler", - # "formatter": "standard", - # "filename": "whatever.log", - # "maxBytes": 1024 * 1024 * 10, # 10Mb - # "backupCount": 10, - # }, }, "loggers": { "freqtrade": { - # "handlers": ["console", "file"], "level": "INFO", "propagate": True, }, @@ -186,7 +175,7 @@ def setup_logging(config: Config) -> None: verbosity = config["verbosity"] log_config = _create_log_config(config) - + print(log_config) logging.config.dictConfig(log_config) # Add buffer handler to root logger From 390b1137766fa21ff38e913f479d0730ec9a264e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 14:08:53 +0100 Subject: [PATCH 11/36] feat: Ensure freqtrade-logger is always configured --- freqtrade/loggers/__init__.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 7ecbbb58c..1f41b8ea6 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -69,12 +69,6 @@ FT_LOGGING_CONFIG = { "formatter": "basic", }, }, - "loggers": { - "freqtrade": { - "level": "INFO", - "propagate": True, - }, - }, "root": { "handlers": [ "console", @@ -85,6 +79,14 @@ FT_LOGGING_CONFIG = { } +def _set_loggers(log_config: dict[str, Any]) -> None: + if "loggers" not in log_config: + log_config["loggers"] = {} + + if "freqtrade" not in log_config["loggers"]: + log_config["loggers"]["freqtrade"] = {"level": "INFO", "propagate": True} + + def _add_root_handler(log_config: dict[str, Any], handler_name: str): if handler_name not in log_config["root"]["handlers"]: log_config["root"]["handlers"].append(handler_name) @@ -164,7 +166,7 @@ def _create_log_config(config: Config) -> dict[str, Any]: "non-root user, delete and recreate the directories you need, and then try " "again." ) - + _set_loggers(log_config) return log_config @@ -172,10 +174,7 @@ def setup_logging(config: Config) -> None: """ Process -v/--verbose, --logfile options """ - verbosity = config["verbosity"] - log_config = _create_log_config(config) - print(log_config) logging.config.dictConfig(log_config) # Add buffer handler to root logger @@ -189,6 +188,7 @@ def setup_logging(config: Config) -> None: logging.info("Logfile configured") # Set verbosity levels + verbosity = config["verbosity"] logging.root.setLevel(logging.INFO if verbosity < 1 else logging.DEBUG) set_loggers(verbosity, config.get("api_server", {}).get("verbosity", "info")) From cc9c373a7636163e8d131dc53f43d9fef3f21657 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 14:09:13 +0100 Subject: [PATCH 12/36] feat: update config_schema for logging --- freqtrade/configuration/config_schema.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index f50334938..0dd158bcb 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -425,6 +425,10 @@ CONF_SCHEMA = { "description": "Edge configuration.", "$ref": "#/definitions/edge", }, + "log_config": { + "description": "Logging configuration.", + "$ref": "#/definitions/logging", + }, "freqai": { "description": "FreqAI configuration.", "$ref": "#/definitions/freqai", @@ -877,6 +881,16 @@ CONF_SCHEMA = { }, "required": ["process_throttle_secs", "allowed_risk"], }, + "logging": { + "type": "object", + "properties": { + "version": {"type": "number", "const": 1}, + "formatters": {"type": "object"}, + "handlers": {"type": "object"}, + "root": {"type": "object"}, + }, + "required": ["version", "formatters", "handlers", "root"], + }, "external_message_consumer": { "description": "Configuration for external message consumer.", "type": "object", From a98121ea267acbf8eb28a51e25d5d970fa9cd20d Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 14:09:29 +0100 Subject: [PATCH 13/36] chore: update json schema --- build_helpers/schema.json | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/build_helpers/schema.json b/build_helpers/schema.json index 5fb1772c6..752d13029 100644 --- a/build_helpers/schema.json +++ b/build_helpers/schema.json @@ -542,6 +542,10 @@ "description": "Edge configuration.", "$ref": "#/definitions/edge" }, + "log_config": { + "description": "Logging configuration.", + "$ref": "#/definitions/logging" + }, "freqai": { "description": "FreqAI configuration.", "$ref": "#/definitions/freqai" @@ -1273,6 +1277,30 @@ "allowed_risk" ] }, + "logging": { + "type": "object", + "properties": { + "version": { + "type": "number", + "const": 1 + }, + "formatters": { + "type": "object" + }, + "handlers": { + "type": "object" + }, + "root": { + "type": "object" + } + }, + "required": [ + "version", + "formatters", + "handlers", + "root" + ] + }, "external_message_consumer": { "description": "Configuration for external message consumer.", "type": "object", From 1eac77fe0bafac1fd92dcb5247ad6bc6cc4f67af Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 14:11:23 +0100 Subject: [PATCH 14/36] chore: don't shadow built-in functions --- freqtrade/loggers/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 1f41b8ea6..a0553ff45 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -92,9 +92,9 @@ def _add_root_handler(log_config: dict[str, Any], handler_name: str): log_config["root"]["handlers"].append(handler_name) -def _add_formatter(log_config: dict[str, Any], format_name: str, format: str): +def _add_formatter(log_config: dict[str, Any], format_name: str, format_: str): if format_name not in log_config["formatters"]: - log_config["formatters"][format_name] = {"format": format} + log_config["formatters"][format_name] = {"format": format_} def _create_log_config(config: Config) -> dict[str, Any]: From f9d978f16a8faed92782459b69d8969461408fed Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 14:21:05 +0100 Subject: [PATCH 15/36] chore: migrate set_log_levels to log_config --- freqtrade/loggers/__init__.py | 36 +++++++++++++++++++++++------ freqtrade/loggers/set_log_levels.py | 19 --------------- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index a0553ff45..f6f4985e6 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -8,7 +8,6 @@ from freqtrade.exceptions import OperationalException from freqtrade.loggers.buffering_handler import FTBufferingHandler from freqtrade.loggers.ft_rich_handler import FtRichHandler from freqtrade.loggers.rich_console import get_rich_console -from freqtrade.loggers.set_log_levels import set_loggers # from freqtrade.loggers.std_err_stream_handler import FTStdErrStreamHandler @@ -79,12 +78,34 @@ FT_LOGGING_CONFIG = { } -def _set_loggers(log_config: dict[str, Any]) -> None: +def _set_log_levels( + log_config: dict[str, Any], verbosity: int = 0, api_verbosity: str = "info" +) -> None: + """ + Set the logging level for the different loggers + """ if "loggers" not in log_config: log_config["loggers"] = {} - if "freqtrade" not in log_config["loggers"]: - log_config["loggers"]["freqtrade"] = {"level": "INFO", "propagate": True} + # Set default levels for third party libraries + third_party_loggers = { + "freqtrade": logging.INFO if verbosity <= 1 else logging.DEBUG, + "requests": logging.INFO if verbosity <= 1 else logging.DEBUG, + "urllib3": logging.INFO if verbosity <= 1 else logging.DEBUG, + "httpcore": logging.INFO if verbosity <= 1 else logging.DEBUG, + "ccxt.base.exchange": logging.INFO if verbosity <= 2 else logging.DEBUG, + "telegram": logging.INFO, + "httpx": logging.WARNING, + "werkzeug": logging.ERROR if api_verbosity == "error" else logging.INFO, + } + + # Add third party loggers to the configuration + for logger_name, level in third_party_loggers.items(): + if logger_name not in log_config["loggers"]: + log_config["loggers"][logger_name] = { + "level": logging.getLevelName(level), + "propagate": True, + } def _add_root_handler(log_config: dict[str, Any], handler_name: str): @@ -166,7 +187,6 @@ def _create_log_config(config: Config) -> dict[str, Any]: "non-root user, delete and recreate the directories you need, and then try " "again." ) - _set_loggers(log_config) return log_config @@ -174,7 +194,11 @@ def setup_logging(config: Config) -> None: """ Process -v/--verbose, --logfile options """ + verbosity = config["verbosity"] + log_config = _create_log_config(config) + _set_log_levels(log_config, verbosity, config.get("api_server", {}).get("verbosity", "info")) + logging.config.dictConfig(log_config) # Add buffer handler to root logger @@ -188,8 +212,6 @@ def setup_logging(config: Config) -> None: logging.info("Logfile configured") # Set verbosity levels - verbosity = config["verbosity"] logging.root.setLevel(logging.INFO if verbosity < 1 else logging.DEBUG) - set_loggers(verbosity, config.get("api_server", {}).get("verbosity", "info")) logger.info("Verbosity set to %s", verbosity) diff --git a/freqtrade/loggers/set_log_levels.py b/freqtrade/loggers/set_log_levels.py index 24f26ffd6..d794c5ebc 100644 --- a/freqtrade/loggers/set_log_levels.py +++ b/freqtrade/loggers/set_log_levels.py @@ -4,25 +4,6 @@ import logging logger = logging.getLogger(__name__) -def set_loggers(verbosity: int = 0, api_verbosity: str = "info") -> None: - """ - Set the logging level for third party libraries - :param verbosity: Verbosity level. amount of `-v` passed to the command line - :return: None - """ - for logger_name in ("requests", "urllib3", "httpcore"): - logging.getLogger(logger_name).setLevel(logging.INFO if verbosity <= 1 else logging.DEBUG) - logging.getLogger("ccxt.base.exchange").setLevel( - logging.INFO if verbosity <= 2 else logging.DEBUG - ) - logging.getLogger("telegram").setLevel(logging.INFO) - logging.getLogger("httpx").setLevel(logging.WARNING) - - logging.getLogger("werkzeug").setLevel( - logging.ERROR if api_verbosity == "error" else logging.INFO - ) - - __BIAS_TESTER_LOGGERS = [ "freqtrade.resolvers", "freqtrade.strategy.hyper", From 6edee269d873803688b4587701fbfebfb52c99c2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 14:31:03 +0100 Subject: [PATCH 16/36] chore: deepcopy log config --- freqtrade/loggers/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index f6f4985e6..673fb3ce3 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -1,4 +1,6 @@ import logging +import logging.config +from copy import deepcopy from logging import Formatter from pathlib import Path from typing import Any @@ -120,7 +122,7 @@ def _add_formatter(log_config: dict[str, Any], format_name: str, format_: str): def _create_log_config(config: Config) -> dict[str, Any]: # Get log_config from user config or use default - log_config = config.get("log_config", FT_LOGGING_CONFIG.copy()) + log_config = config.get("log_config", deepcopy(FT_LOGGING_CONFIG)) if logfile := config.get("logfile"): s = logfile.split(":") From 56c23f9dd81cd9cdb9432b20eeaed697d087d214 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 14:37:21 +0100 Subject: [PATCH 17/36] tests: remove unnecessary log-config --- tests/exchange_online/test_ccxt_ws_compat.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/exchange_online/test_ccxt_ws_compat.py b/tests/exchange_online/test_ccxt_ws_compat.py index 8083a926c..6ab7600f4 100644 --- a/tests/exchange_online/test_ccxt_ws_compat.py +++ b/tests/exchange_online/test_ccxt_ws_compat.py @@ -12,7 +12,6 @@ import pytest from freqtrade.enums import CandleType from freqtrade.exchange.exchange_utils import timeframe_to_prev_date -from freqtrade.loggers.set_log_levels import set_loggers from freqtrade.util.datetime_helpers import dt_now from tests.conftest import log_has_re from tests.exchange_online.conftest import EXCHANGE_WS_FIXTURE_TYPE @@ -50,7 +49,6 @@ class TestCCXTExchangeWs: assert res[pair_tf] is not None df1 = res[pair_tf] caplog.set_level(logging.DEBUG) - set_loggers(1) assert df1.iloc[-1]["date"] == curr_candle # Wait until the next candle (might be up to 1 minute). From 08c4f24bdf502686ab892cda7456e1ee615eb47b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 16:16:21 +0100 Subject: [PATCH 18/36] test: add log setup workaround for tests --- freqtrade/loggers/__init__.py | 13 ++++++++----- tests/conftest.py | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 673fb3ce3..be772dc65 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -197,14 +197,17 @@ def setup_logging(config: Config) -> None: Process -v/--verbose, --logfile options """ verbosity = config["verbosity"] + if not config.get("ft_tests_skip_logging"): + log_config = _create_log_config(config) + _set_log_levels( + log_config, verbosity, config.get("api_server", {}).get("verbosity", "info") + ) - log_config = _create_log_config(config) - _set_log_levels(log_config, verbosity, config.get("api_server", {}).get("verbosity", "info")) - - logging.config.dictConfig(log_config) + logging.config.dictConfig(log_config) # Add buffer handler to root logger - logging.root.addHandler(bufferHandler) + if bufferHandler not in logging.root.handlers: + logging.root.addHandler(bufferHandler) # Set color system for console output if config.get("print_colorized", True): diff --git a/tests/conftest.py b/tests/conftest.py index ca382f6ae..80c604162 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -644,6 +644,7 @@ def get_default_conf(testdatadir): "trading_mode": "spot", "margin_mode": "", "candle_type_def": CandleType.SPOT, + "ft_tests_skip_logging": True, } return configuration From e930221b78017d637cd46824123f7c31fceb60cc Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 8 Mar 2025 16:27:25 +0100 Subject: [PATCH 19/36] test: update logging tests --- freqtrade/loggers/__init__.py | 3 ++- tests/conftest.py | 1 - tests/test_configuration.py | 3 ++- tests/test_log_setup.py | 21 +++++++++++++++------ 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index be772dc65..4e04635ae 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -1,5 +1,6 @@ import logging import logging.config +import os from copy import deepcopy from logging import Formatter from pathlib import Path @@ -197,7 +198,7 @@ def setup_logging(config: Config) -> None: Process -v/--verbose, --logfile options """ verbosity = config["verbosity"] - if not config.get("ft_tests_skip_logging"): + if os.environ.get("PYTEST_VERSION") is None or config.get("ft_tests_force_logging"): log_config = _create_log_config(config) _set_log_levels( log_config, verbosity, config.get("api_server", {}).get("verbosity", "info") diff --git a/tests/conftest.py b/tests/conftest.py index 80c604162..ca382f6ae 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -644,7 +644,6 @@ def get_default_conf(testdatadir): "trading_mode": "spot", "margin_mode": "", "candle_type_def": CandleType.SPOT, - "ft_tests_skip_logging": True, } return configuration diff --git a/tests/test_configuration.py b/tests/test_configuration.py index a8ca72d86..7ee04e133 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -603,7 +603,7 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None: patched_configuration_load_config_file(mocker, default_conf) # Prevent setting loggers - mocker.patch("freqtrade.loggers.set_loggers", MagicMock) + mocker.patch("freqtrade.loggers.logging.config.dictConfig", MagicMock) arglist = ["trade", "-vvv"] args = Arguments(arglist).get_parsed_arg() @@ -615,6 +615,7 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None: def test_set_logfile(default_conf, mocker, tmp_path): + default_conf["ft_tests_force_logging"] = True patched_configuration_load_config_file(mocker, default_conf) f = tmp_path / "test_file.log" assert not f.is_file() diff --git a/tests/test_log_setup.py b/tests/test_log_setup.py index d4bc63193..c8ccf225e 100644 --- a/tests/test_log_setup.py +++ b/tests/test_log_setup.py @@ -7,7 +7,6 @@ from freqtrade.exceptions import OperationalException from freqtrade.loggers import ( FTBufferingHandler, FtRichHandler, - set_loggers, setup_logging, setup_logging_pre, ) @@ -27,8 +26,11 @@ def test_set_loggers() -> None: previous_value1 = logging.getLogger("requests").level previous_value2 = logging.getLogger("ccxt.base.exchange").level previous_value3 = logging.getLogger("telegram").level - - set_loggers() + config = { + "verbosity": 1, + "ft_tests_force_logging": True, + } + setup_logging(config) value1 = logging.getLogger("requests").level assert previous_value1 is not value1 @@ -41,15 +43,17 @@ def test_set_loggers() -> None: value3 = logging.getLogger("telegram").level assert previous_value3 is not value3 assert value3 is logging.INFO - - set_loggers(verbosity=2) + config["verbosity"] = 2 + setup_logging(config) assert logging.getLogger("requests").level is logging.DEBUG assert logging.getLogger("ccxt.base.exchange").level is logging.INFO assert logging.getLogger("telegram").level is logging.INFO assert logging.getLogger("werkzeug").level is logging.INFO - set_loggers(verbosity=3, api_verbosity="error") + config["verbosity"] = 3 + config["api_server"] = {"verbosity": "error"} + setup_logging(config) assert logging.getLogger("requests").level is logging.DEBUG assert logging.getLogger("ccxt.base.exchange").level is logging.DEBUG @@ -64,6 +68,7 @@ def test_set_loggers_syslog(): logger.handlers = [] config = { + "ft_tests_force_logging": True, "verbosity": 2, "logfile": "syslog:/dev/log", } @@ -88,6 +93,7 @@ def test_set_loggers_Filehandler(tmp_path): logger.handlers = [] logfile = tmp_path / "logs/ft_logfile.log" config = { + "ft_tests_force_logging": True, "verbosity": 2, "logfile": str(logfile), } @@ -117,6 +123,7 @@ def test_set_loggers_Filehandler_without_permission(tmp_path): tmp_path.chmod(0o400) logfile = tmp_path / "logs/ft_logfile.log" config = { + "ft_tests_force_logging": True, "verbosity": 2, "logfile": str(logfile), } @@ -137,6 +144,7 @@ def test_set_loggers_journald(mocker): logger.handlers = [] config = { + "ft_tests_force_logging": True, "verbosity": 2, "logfile": "journald", } @@ -156,6 +164,7 @@ def test_set_loggers_journald_importerror(import_fails): logger.handlers = [] config = { + "ft_tests_force_logging": True, "verbosity": 2, "logfile": "journald", } From eabcaa38e26d2a56140ac2905e9dc2e6a14c24f6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 19:25:18 +0100 Subject: [PATCH 20/36] feat: try improved config schema --- freqtrade/configuration/config_schema.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index 0dd158bcb..5376e693d 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -885,7 +885,16 @@ CONF_SCHEMA = { "type": "object", "properties": { "version": {"type": "number", "const": 1}, - "formatters": {"type": "object"}, + "formatters": { + "type": "object", + # In theory the below, but can be more flexible + # based on logging.config documentation + # "additionalProperties": { + # "type": "object", + # "properties": {"format": {"type": "string"}, "datefmt": {"type": "string"}}, + # "required": ["format"], + # }, + }, "handlers": {"type": "object"}, "root": {"type": "object"}, }, From 79ebc943a2870e4c3792849ba22fa74d02393625 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 19:33:09 +0100 Subject: [PATCH 21/36] docs: Document journald logging via configuration file --- docs/advanced-setup.md | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index ae2304538..434f0a3a2 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -232,9 +232,36 @@ $RepeatedMsgReduction on This needs the `cysystemd` python package installed as dependency (`pip install cysystemd`), which is not available on Windows. Hence, the whole journald logging functionality is not available for a bot running on Windows. -To send Freqtrade log messages to `journald` system service use the `--logfile` command line option with the value in the following format: +To send Freqtrade log messages to `journald` system service, add the following configuration snippet to your configuration. -* `--logfile journald` -- send log messages to `journald`. +``` json +{ + // ... + "log_config": { + "version": 1, + "formatters": { + "journald_fmt": { + "format": "%(name)s - %(levelname)s - %(message)s" + } + }, + "handlers": { + // Other handlers? + "journald": { + "class": "cysystemd.journal.JournaldLogHandler", + "formatter": "journald_fmt", + } + }, + "root": { + "handlers": [ + // .. + "journald", + + ] + } + + } +} +``` Log messages are send to `journald` with the `user` facility. So you can see them with the following commands: @@ -244,3 +271,8 @@ Log messages are send to `journald` with the `user` facility. So you can see the There are many other options in the `journalctl` utility to filter the messages, see manual pages for this utility. On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both `--logfile syslog` or `--logfile journald` can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better. + +??? Info "Deprecated - command line option" + To send Freqtrade log messages to `journald` system service use the `--logfile` command line option with the value in the following format: + + * `--logfile journald` -- send log messages to `journald`. From 7b1ee84b343799f7c80ec8d6f796b2351e5f5e0a Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 19:33:20 +0100 Subject: [PATCH 22/36] chore: deprecate --logfile=journald --- freqtrade/loggers/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 4e04635ae..9bc20ce78 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -140,6 +140,10 @@ def _create_log_config(config: Config) -> dict[str, Any]: elif s[0] == "journald": # pragma: no cover # Check if we have the module available + logger.warning( + "DEPRECATED: Configuring Journald logging via command line is deprecated." + "Please use the log_config option in the configuration file instead." + ) try: from cysystemd.journal import JournaldLogHandler # noqa: F401 except ImportError: From a85e044e99d34bfbb7f2e1e2a7754fb25746c829 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 19:33:49 +0100 Subject: [PATCH 23/36] docs: improve log formatting --- docs/advanced-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 434f0a3a2..2cc08ff51 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -275,4 +275,4 @@ On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice vers ??? Info "Deprecated - command line option" To send Freqtrade log messages to `journald` system service use the `--logfile` command line option with the value in the following format: - * `--logfile journald` -- send log messages to `journald`. + `--logfile journald` -- send log messages to `journald`. From 8832bae371f6579a6755bee24de236fbbab0e996 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 19:44:16 +0100 Subject: [PATCH 24/36] docs: document syslog logging via log_config --- docs/advanced-setup.md | 74 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 13 deletions(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 2cc08ff51..d149f934a 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -192,26 +192,47 @@ On many Linux systems the bot can be configured to send its log messages to `sys ### Logging to syslog -To send Freqtrade log messages to a local or remote `syslog` service use the `--logfile` command line option with the value in the following format: +To send Freqtrade log messages to a local or remote `syslog` service use the `"log_config"` setup option to configure logging. -* `--logfile syslog:` -- send log messages to `syslog` service using the `` as the syslog address. +``` json +{ + // ... + "log_config": { + "version": 1, + "formatters": { + "syslog_fmt": { + "format": "%(name)s - %(levelname)s - %(message)s" + } + }, + "handlers": { + // Other handlers? + "syslog": { + "class": "logging.handlers.SysLogHandler", + "formatter": "syslog_fmt", + // Use one of the other options above as adress instead? + "address": "/dev/log" + } + }, + "root": { + "handlers": [ + // other handlers + "syslog", + + ] + } -The syslog address can be either a Unix domain socket (socket filename) or a UDP socket specification, consisting of IP address and UDP port, separated by the `:` character. + } +} +``` -So, the following are the examples of possible usages: - -* `--logfile syslog:/dev/log` -- log to syslog (rsyslog) using the `/dev/log` socket, suitable for most systems. -* `--logfile syslog` -- same as above, the shortcut for `/dev/log`. -* `--logfile syslog:/var/run/syslog` -- log to syslog (rsyslog) using the `/var/run/syslog` socket. Use this on MacOS. -* `--logfile syslog:localhost:514` -- log to local syslog using UDP socket, if it listens on port 514. -* `--logfile syslog::514` -- log to remote syslog at IP address and port 514. This may be used on Windows for remote logging to an external syslog server. +#### Syslog usage Log messages are send to `syslog` with the `user` facility. So you can see them with the following commands: -* `tail -f /var/log/user`, or +* `tail -f /var/log/user`, or * install a comprehensive graphical viewer (for instance, 'Log File Viewer' for Ubuntu). -On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both `--logfile syslog` or `--logfile journald` can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better. +On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both syslog or journald can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better. For `rsyslog` the messages from the bot can be redirected into a separate dedicated log file. To achieve this, add @@ -228,6 +249,33 @@ For `syslog` (`rsyslog`), the reduction mode can be switched on. This will reduc $RepeatedMsgReduction on ``` +#### Syslog addressing + +The syslog address can be either a Unix domain socket (socket filename) or a UDP socket specification, consisting of IP address and UDP port, separated by the `:` character. + + +So, the following are the examples of possible addresses: + +* `"address": "/dev/log"` -- log to syslog (rsyslog) using the `/dev/log` socket, suitable for most systems. +* `"address": "/var/run/syslog"` -- log to syslog (rsyslog) using the `/var/run/syslog` socket. Use this on MacOS. +* `"address": "localhost:514"` -- log to local syslog using UDP socket, if it listens on port 514. +* `"address": ":514"` -- log to remote syslog at IP address and port 514. This may be used on Windows for remote logging to an external syslog server. + + +??? Info "Deprecated - configure syslog via command line" + + `--logfile syslog:` -- send log messages to `syslog` service using the `` as the syslog address. + + The syslog address can be either a Unix domain socket (socket filename) or a UDP socket specification, consisting of IP address and UDP port, separated by the `:` character. + + So, the following are the examples of possible usages: + + * `--logfile syslog:/dev/log` -- log to syslog (rsyslog) using the `/dev/log` socket, suitable for most systems. + * `--logfile syslog` -- same as above, the shortcut for `/dev/log`. + * `--logfile syslog:/var/run/syslog` -- log to syslog (rsyslog) using the `/var/run/syslog` socket. Use this on MacOS. + * `--logfile syslog:localhost:514` -- log to local syslog using UDP socket, if it listens on port 514. + * `--logfile syslog::514` -- log to remote syslog at IP address and port 514. This may be used on Windows for remote logging to an external syslog server. + ### Logging to journald This needs the `cysystemd` python package installed as dependency (`pip install cysystemd`), which is not available on Windows. Hence, the whole journald logging functionality is not available for a bot running on Windows. @@ -272,7 +320,7 @@ There are many other options in the `journalctl` utility to filter the messages, On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both `--logfile syslog` or `--logfile journald` can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better. -??? Info "Deprecated - command line option" +??? Info "Deprecated - configure journald via command line" To send Freqtrade log messages to `journald` system service use the `--logfile` command line option with the value in the following format: `--logfile journald` -- send log messages to `journald`. From 5b8752e6495b4bb789f3ed7b8d0bb99aaaeae655 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 19:44:44 +0100 Subject: [PATCH 25/36] chore: deprecate syslog from configuration --- freqtrade/loggers/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/loggers/__init__.py b/freqtrade/loggers/__init__.py index 9bc20ce78..e39dcdd45 100644 --- a/freqtrade/loggers/__init__.py +++ b/freqtrade/loggers/__init__.py @@ -128,6 +128,10 @@ def _create_log_config(config: Config) -> dict[str, Any]: if logfile := config.get("logfile"): s = logfile.split(":") if s[0] == "syslog": + logger.warning( + "DEPRECATED: Configuring syslog logging via command line is deprecated." + "Please use the log_config option in the configuration file instead." + ) # Add syslog handler to the config log_config["handlers"]["syslog"] = { "class": "logging.handlers.SysLogHandler", From 65d19c38b678f9092b49e4184fb3ef6e26a4fffb Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 19:46:18 +0100 Subject: [PATCH 26/36] docs: document deprecation of --logfile syslog --- docs/deprecated.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/deprecated.md b/docs/deprecated.md index 729db4915..2e76f2413 100644 --- a/docs/deprecated.md +++ b/docs/deprecated.md @@ -88,3 +88,8 @@ Setting protections from the configuration via `"protections": [],` has been rem Using hdf5 as data storage has been deprecated in 2024.12 and was removed in 2025.1. We recommend switching to the feather data format. Please use the [`convert-data` subcommand](data-download.md#sub-command-convert-data) to convert your existing data to one of the supported formats before updating. + +## Configuring advanced logging via config + +Configuring syslog and journald via `--logfile systemd` and `--logfile journald` respectively has been deprecated in 2025.3. +Please use configuration based [log setup](advanced-setup.md#advanced-logging) instead. From 85ccc31a638bbaaff0f22d91ddc25a7253b5d605 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 19:57:24 +0100 Subject: [PATCH 27/36] docs: document basic log_config setup --- docs/advanced-setup.md | 59 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index d149f934a..b9d87f6d4 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -188,8 +188,67 @@ as the watchdog. ## Advanced Logging +Freqtrade uses the default logging module provided by python. +Python allows for extensive [logging configuration](https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig) in this regards - way more than what can be covered here. + +Default logging (Colored terminal output) is setup by default if no `log_config` is provided. +Using `--logfile logfile.log` will enable the RotatingFileHandler. +If you're not content with the log format - or with the default settings provided for the RotatingFileHandler, you can customize logging to your liking. + +The default configuration looks roughly like the below - with the file handler being provided - but not enabled. + +``` json hl_lines="5-7 13-16 27" +{ + "log_config": { + "version": 1, + "formatters": { + "basic": { + "format": "%(message)s" + }, + "standard": { + "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + } + }, + "handlers": { + "console": { + "class": "freqtrade.loggers.ft_rich_handler.FtRichHandler", + "formatter": "basic" + }, + "file": { + "class": "logging.handlers.RotatingFileHandler", + "formatter": "standard", + // "filename": "someRandomLogFile.log", + "maxBytes": 10485760, + "backupCount": 10 + } + }, + "root": { + "handlers": [ + "console", + // "file" + ], + "level": "INFO", + } + } +} +``` + +!!! Note highlighted lines + Highlighted lines in the above code-block define the Rich handler and belong together. + The formatter "standard" and "file" will belong to the FileHandler. + +Each handler must use one of the defined formatters (by name) - and it's class must be available and a valid logging class. +To actually use a handler - it must be in the "handlers" section inside the "root" segment. +If this section is left out, freqtrade will provide no output (in the non-configured handler, anyway). + +!!! Tip "Explicit log configuration" + We recommend to extract the logging configuration from your main configuration, and provide it to your bot via [multiple configuration files](configuration.md#multiple-configuration-files) functionality. This will avoid unnecessary code duplication. + +--- + On many Linux systems the bot can be configured to send its log messages to `syslog` or `journald` system services. Logging to a remote `syslog` server is also available on Windows. The special values for the `--logfile` command line option can be used for this. + ### Logging to syslog To send Freqtrade log messages to a local or remote `syslog` service use the `"log_config"` setup option to configure logging. From 9d232c4949c22f59ab28c2e0cba1f490443bb6df Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 19:58:34 +0100 Subject: [PATCH 28/36] docs: add log_config to supported log options --- docs/configuration.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/configuration.md b/docs/configuration.md index fdddf7cda..f76b9360d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -282,6 +282,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi | `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data.
*Defaults to `feather`*.
**Datatype:** String | `dataformat_trades` | Data format to use to store historical trades data.
*Defaults to `feather`*.
**Datatype:** String | `reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage (and decreasing train/inference timing in FreqAI). (Currently only affects FreqAI use-cases)
**Datatype:** Boolean.
Default: `False`. +| `log_config` | Dictionary containing the log config for python logging. [more info](advanced-setup.md#advanced-logging)
**Datatype:** dict.
Default: `FtRichHandler` ### Parameters in the strategy From 385b39ff66f9bddce1d5f9bfe4c4ff74f2dfe0c2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 20:13:39 +0100 Subject: [PATCH 29/36] docs: enhance logging documentation with links --- docs/advanced-setup.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index b9d87f6d4..365cdfd0f 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -248,7 +248,6 @@ If this section is left out, freqtrade will provide no output (in the non-config On many Linux systems the bot can be configured to send its log messages to `syslog` or `journald` system services. Logging to a remote `syslog` server is also available on Windows. The special values for the `--logfile` command line option can be used for this. - ### Logging to syslog To send Freqtrade log messages to a local or remote `syslog` service use the `"log_config"` setup option to configure logging. @@ -268,7 +267,7 @@ To send Freqtrade log messages to a local or remote `syslog` service use the `"l "syslog": { "class": "logging.handlers.SysLogHandler", "formatter": "syslog_fmt", - // Use one of the other options above as adress instead? + // Use one of the other options above as address instead? "address": "/dev/log" } }, @@ -284,6 +283,8 @@ To send Freqtrade log messages to a local or remote `syslog` service use the `"l } ``` +[Additional log-handlers](#advanced-logging) may need to be configured to for example also have log output in the console. + #### Syslog usage Log messages are send to `syslog` with the `user` facility. So you can see them with the following commands: @@ -370,6 +371,8 @@ To send Freqtrade log messages to `journald` system service, add the following c } ``` +[Additional log-handlers](#advanced-logging) may need to be configured to for example also have log output in the console. + Log messages are send to `journald` with the `user` facility. So you can see them with the following commands: * `journalctl -f` -- shows Freqtrade log messages sent to `journald` along with other log messages fetched by `journald`. From 7e154c6fb67573e14637c14ac88d43df27828930 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 20:17:07 +0100 Subject: [PATCH 30/36] chore: fix too long comment --- freqtrade/configuration/config_schema.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/configuration/config_schema.py b/freqtrade/configuration/config_schema.py index 5376e693d..af48832b8 100644 --- a/freqtrade/configuration/config_schema.py +++ b/freqtrade/configuration/config_schema.py @@ -891,7 +891,10 @@ CONF_SCHEMA = { # based on logging.config documentation # "additionalProperties": { # "type": "object", - # "properties": {"format": {"type": "string"}, "datefmt": {"type": "string"}}, + # "properties": { + # "format": {"type": "string"}, + # "datefmt": {"type": "string"}, + # }, # "required": ["format"], # }, }, From 415e361c661d592baed7a0c65a709b50622d15b2 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Mar 2025 20:19:12 +0100 Subject: [PATCH 31/36] docs: fix note syntax --- docs/advanced-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index 365cdfd0f..af040008c 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -233,7 +233,7 @@ The default configuration looks roughly like the below - with the file handler b } ``` -!!! Note highlighted lines +!!! Note "highlighted lines" Highlighted lines in the above code-block define the Rich handler and belong together. The formatter "standard" and "file" will belong to the FileHandler. From 39288d2e539a62b809c8b5628c43fb002b26141e Mon Sep 17 00:00:00 2001 From: Matthias Date: Sat, 15 Mar 2025 08:30:32 +0100 Subject: [PATCH 32/36] test: Add fixture to prevent having disabled loggers --- tests/conftest.py | 8 ++++++++ tests/test_configuration.py | 1 + tests/test_log_setup.py | 8 +++++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index ca382f6ae..b7e766b3e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -549,6 +549,14 @@ def user_dir(mocker, tmp_path) -> Path: return user_dir +@pytest.fixture() +def keep_log_config_loggers(mocker): + # Mock the _handle_existing_loggers function to prevent it from disabling all loggers. + # This is necessary to keep all loggers active, and avoid random failures if + # this file is ran before the test_rest_client file. + mocker.patch("logging.config._handle_existing_loggers") + + @pytest.fixture(autouse=True) def patch_coingecko(mocker) -> None: """ diff --git a/tests/test_configuration.py b/tests/test_configuration.py index 7ee04e133..6c54ad350 100644 --- a/tests/test_configuration.py +++ b/tests/test_configuration.py @@ -614,6 +614,7 @@ def test_cli_verbose_with_params(default_conf, mocker, caplog) -> None: assert log_has("Verbosity set to 3", caplog) +@pytest.mark.usefixtures("keep_log_config_loggers") def test_set_logfile(default_conf, mocker, tmp_path): default_conf["ft_tests_force_logging"] = True patched_configuration_load_config_file(mocker, default_conf) diff --git a/tests/test_log_setup.py b/tests/test_log_setup.py index c8ccf225e..9c3b530bf 100644 --- a/tests/test_log_setup.py +++ b/tests/test_log_setup.py @@ -16,6 +16,7 @@ from freqtrade.loggers.set_log_levels import ( ) +@pytest.mark.usefixtures("keep_log_config_loggers") def test_set_loggers() -> None: # Reset Logging to Debug, otherwise this fails randomly as it's set globally logging.getLogger("requests").setLevel(logging.DEBUG) @@ -62,6 +63,7 @@ def test_set_loggers() -> None: @pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") +@pytest.mark.usefixtures("keep_log_config_loggers") def test_set_loggers_syslog(): logger = logging.getLogger() orig_handlers = logger.handlers @@ -87,6 +89,7 @@ def test_set_loggers_syslog(): @pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") +@pytest.mark.usefixtures("keep_log_config_loggers") def test_set_loggers_Filehandler(tmp_path): logger = logging.getLogger() orig_handlers = logger.handlers @@ -114,6 +117,7 @@ def test_set_loggers_Filehandler(tmp_path): @pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") +@pytest.mark.usefixtures("keep_log_config_loggers") def test_set_loggers_Filehandler_without_permission(tmp_path): logger = logging.getLogger() orig_handlers = logger.handlers @@ -138,7 +142,8 @@ def test_set_loggers_Filehandler_without_permission(tmp_path): @pytest.mark.skip(reason="systemd is not installed on every system, so we're not testing this.") -def test_set_loggers_journald(mocker): +@pytest.mark.usefixtures("keep_log_config_loggers") +def test_set_loggers_journald(): logger = logging.getLogger() orig_handlers = logger.handlers logger.handlers = [] @@ -158,6 +163,7 @@ def test_set_loggers_journald(mocker): logger.handlers = orig_handlers +@pytest.mark.usefixtures("keep_log_config_loggers") def test_set_loggers_journald_importerror(import_fails): logger = logging.getLogger() orig_handlers = logger.handlers From 03dfe4ec45e2b1c5a4c923c57dfaa87e6cecd468 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Mar 2025 09:37:57 +0100 Subject: [PATCH 33/36] feat: add jsonFormatter --- freqtrade/loggers/json_formatter.py | 63 +++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 freqtrade/loggers/json_formatter.py diff --git a/freqtrade/loggers/json_formatter.py b/freqtrade/loggers/json_formatter.py new file mode 100644 index 000000000..97fe67b7b --- /dev/null +++ b/freqtrade/loggers/json_formatter.py @@ -0,0 +1,63 @@ +import json +import logging + + +class JsonFormatter(logging.Formatter): + """ + Formatter that outputs JSON strings after parsing the LogRecord. + + @param dict fmt_dict: Key: logging format attribute pairs. Defaults to {"message": "message"}. + @param str time_format: time.strftime() format string. Default: "%Y-%m-%dT%H:%M:%S" + @param str msec_format: Microsecond formatting. Appended at the end. Default: "%s.%03dZ" + """ + + def __init__( + self, + fmt_dict: dict = None, + time_format: str = "%Y-%m-%dT%H:%M:%S", + msec_format: str = "%s.%03dZ", + ): + print(fmt_dict) + self.fmt_dict = fmt_dict if fmt_dict is not None else {"message": "message"} + self.default_time_format = time_format + self.default_msec_format = msec_format + self.datefmt = None + + def usesTime(self) -> bool: + """ + Look for the attribute in the format dict values instead of the fmt string. + """ + return "asctime" in self.fmt_dict.values() + + def formatMessage(self, record) -> dict: + """ + Return a dictionary of the relevant LogRecord attributes instead of a string. + KeyError is raised if an unknown attribute is provided in the fmt_dict. + """ + return {fmt_key: record.__dict__[fmt_val] for fmt_key, fmt_val in self.fmt_dict.items()} + + def format(self, record) -> str: + """ + Mostly the same as the parent's class method, the difference being that a dict is + manipulated and dumped as JSON instead of a string. + """ + record.message = record.getMessage() + + if self.usesTime(): + record.asctime = self.formatTime(record, self.datefmt) + + message_dict = self.formatMessage(record) + + if record.exc_info: + # Cache the traceback text to avoid converting it multiple times + # (it's constant anyway) + if not record.exc_text: + record.exc_text = self.formatException(record.exc_info) + + if record.exc_text: + message_dict["exc_info"] = record.exc_text + + if record.stack_info: + message_dict["stack_info"] = self.formatStack(record.stack_info) + + return json.dumps(message_dict, default=str) From da53b5d1159a7b56aa746a61fd13b6ef87221ea4 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Mar 2025 09:43:23 +0100 Subject: [PATCH 34/36] docs: document logging JSON format --- docs/advanced-setup.md | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/docs/advanced-setup.md b/docs/advanced-setup.md index af040008c..b7ab86eac 100644 --- a/docs/advanced-setup.md +++ b/docs/advanced-setup.md @@ -386,3 +386,46 @@ On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice vers To send Freqtrade log messages to `journald` system service use the `--logfile` command line option with the value in the following format: `--logfile journald` -- send log messages to `journald`. + +### Log format as JSON + +You can also configure the default output stream to use JSON format instead. +The "fmt_dict" attribute defines the keys for the json output - as well as the [python logging LogRecord attributes](https://docs.python.org/3/library/logging.html#logrecord-attributes). + +The below configuration will change the default output to JSON. The same formatter could however also be used in combination with the `RotatingFileHandler`. +We recommend to keep one format in human readable form. + +``` json +{ + // ... + "log_config": { + "version": 1, + "formatters": { + "json": { + "()": "freqtrade.loggers.json_formatter.JsonFormatter", + "fmt_dict": { + "timestamp": "asctime", + "level": "levelname", + "logger": "name", + "message": "message" + } + } + }, + "handlers": { + // Other handlers? + "jsonStream": { + "class": "logging.StreamHandler", + "formatter": "json" + } + }, + "root": { + "handlers": [ + // .. + "jsonStream", + + ] + } + + } +} +``` From 7db62689c41b874b931b52f5f1a6b7645c27396b Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Mar 2025 09:50:58 +0100 Subject: [PATCH 35/36] test: add test for json formatter --- freqtrade/loggers/json_formatter.py | 1 - tests/test_log_setup.py | 51 +++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/freqtrade/loggers/json_formatter.py b/freqtrade/loggers/json_formatter.py index 97fe67b7b..ac4e2fbb5 100644 --- a/freqtrade/loggers/json_formatter.py +++ b/freqtrade/loggers/json_formatter.py @@ -17,7 +17,6 @@ class JsonFormatter(logging.Formatter): time_format: str = "%Y-%m-%dT%H:%M:%S", msec_format: str = "%s.%03dZ", ): - print(fmt_dict) self.fmt_dict = fmt_dict if fmt_dict is not None else {"message": "message"} self.default_time_format = time_format self.default_msec_format = msec_format diff --git a/tests/test_log_setup.py b/tests/test_log_setup.py index 9c3b530bf..169a65005 100644 --- a/tests/test_log_setup.py +++ b/tests/test_log_setup.py @@ -1,4 +1,5 @@ import logging +import re import sys import pytest @@ -179,6 +180,56 @@ def test_set_loggers_journald_importerror(import_fails): logger.handlers = orig_handlers +@pytest.mark.usefixtures("keep_log_config_loggers") +def test_set_loggers_json_format(capsys): + logger = logging.getLogger() + orig_handlers = logger.handlers + logger.handlers = [] + + config = { + "ft_tests_force_logging": True, + "verbosity": 2, + "log_config": { + "version": 1, + "formatters": { + "json": { + "()": "freqtrade.loggers.json_formatter.JsonFormatter", + "fmt_dict": { + "timestamp": "asctime", + "level": "levelname", + "logger": "name", + "message": "message", + }, + } + }, + "handlers": { + "json": { + "class": "logging.StreamHandler", + "formatter": "json", + } + }, + "root": { + "handlers": ["json"], + "level": "DEBUG", + }, + }, + } + + setup_logging_pre() + setup_logging(config) + assert len(logger.handlers) == 2 + assert [x for x in logger.handlers if type(x).__name__ == "StreamHandler"] + assert [x for x in logger.handlers if isinstance(x, FTBufferingHandler)] + + logger.info("Test message") + + captured = capsys.readouterr() + assert re.search(r'{"timestamp": ".*"Test message".*', captured.err) + + # reset handlers to not break pytest + logger.handlers = orig_handlers + + def test_reduce_verbosity(): setup_logging_pre() reduce_verbosity_for_bias_tester() From 24e94cfaa7b762d20a8360237569a50c0da16a10 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 16 Mar 2025 09:54:21 +0100 Subject: [PATCH 36/36] feat: Improved default fmt_dict for json formatter --- freqtrade/loggers/json_formatter.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/freqtrade/loggers/json_formatter.py b/freqtrade/loggers/json_formatter.py index ac4e2fbb5..a74922a41 100644 --- a/freqtrade/loggers/json_formatter.py +++ b/freqtrade/loggers/json_formatter.py @@ -13,11 +13,20 @@ class JsonFormatter(logging.Formatter): def __init__( self, - fmt_dict: dict = None, + fmt_dict: dict | None = None, time_format: str = "%Y-%m-%dT%H:%M:%S", msec_format: str = "%s.%03dZ", ): - self.fmt_dict = fmt_dict if fmt_dict is not None else {"message": "message"} + self.fmt_dict = ( + fmt_dict + if fmt_dict is not None + else { + "timestamp": "asctime", + "level": "levelname", + "logger": "name", + "message": "message", + } + ) self.default_time_format = time_format self.default_msec_format = msec_format self.datefmt = None @@ -28,7 +37,10 @@ class JsonFormatter(logging.Formatter): """ return "asctime" in self.fmt_dict.values() - def formatMessage(self, record) -> dict: + def formatMessage(self, record) -> str: + raise NotImplementedError() + + def formatMessageDict(self, record) -> dict: """ Return a dictionary of the relevant LogRecord attributes instead of a string. KeyError is raised if an unknown attribute is provided in the fmt_dict. @@ -45,7 +57,7 @@ class JsonFormatter(logging.Formatter): if self.usesTime(): record.asctime = self.formatTime(record, self.datefmt) - message_dict = self.formatMessage(record) + message_dict = self.formatMessageDict(record) if record.exc_info: # Cache the traceback text to avoid converting it multiple times