Skip to content

Commit fcc7c5c

Browse files
authored
Merge pull request freqtrade#11159 from xmatthias/rich_console
Add rich log handler
2 parents 4285602 + 10969b7 commit fcc7c5c

File tree

6 files changed

+93
-24
lines changed

6 files changed

+93
-24
lines changed

freqtrade/commands/arguments.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@
1111
from freqtrade.constants import DEFAULT_CONFIG
1212

1313

14-
ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_data_dir"]
14+
ARGS_COMMON = [
15+
"verbosity",
16+
"print_colorized",
17+
"logfile",
18+
"version",
19+
"config",
20+
"datadir",
21+
"user_data_dir",
22+
]
1523

1624
ARGS_STRATEGY = [
1725
"strategy",
@@ -58,7 +66,6 @@
5866
"epochs",
5967
"spaces",
6068
"print_all",
61-
"print_colorized",
6269
"print_json",
6370
"hyperopt_jobs",
6471
"hyperopt_random_state",
@@ -74,13 +81,12 @@
7481
ARGS_LIST_STRATEGIES = [
7582
"strategy_path",
7683
"print_one_column",
77-
"print_colorized",
7884
"recursive_strategy_search",
7985
]
8086

81-
ARGS_LIST_FREQAIMODELS = ["freqaimodel_path", "print_one_column", "print_colorized"]
87+
ARGS_LIST_FREQAIMODELS = ["freqaimodel_path", "print_one_column"]
8288

83-
ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"]
89+
ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column"]
8490

8591
ARGS_BACKTEST_SHOW = ["exportfilename", "backtest_show_pair_list", "backtest_breakdown"]
8692

@@ -202,7 +208,6 @@
202208
"hyperopt_list_max_total_profit",
203209
"hyperopt_list_min_objective",
204210
"hyperopt_list_max_objective",
205-
"print_colorized",
206211
"print_json",
207212
"hyperopt_list_no_details",
208213
"hyperoptexportfilename",

freqtrade/configuration/configuration.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,12 @@ def _process_logging_options(self, config: Config) -> None:
135135
if "logfile" in self.args and self.args["logfile"]:
136136
config.update({"logfile": self.args["logfile"]})
137137

138+
if "print_colorized" in self.args and not self.args["print_colorized"]:
139+
logger.info("Parameter --no-color detected ...")
140+
config.update({"print_colorized": False})
141+
else:
142+
config.update({"print_colorized": True})
143+
138144
setup_logging(config)
139145

140146
def _process_trading_options(self, config: Config) -> None:
@@ -326,12 +332,6 @@ def _process_optimize_options(self, config: Config) -> None:
326332
]
327333
self._args_to_config_loop(config, configurations)
328334

329-
if "print_colorized" in self.args and not self.args["print_colorized"]:
330-
logger.info("Parameter --no-color detected ...")
331-
config.update({"print_colorized": False})
332-
else:
333-
config.update({"print_colorized": True})
334-
335335
configurations = [
336336
("print_json", "Parameter --print-json detected ..."),
337337
("export_csv", "Parameter --export-csv detected: {}"),

freqtrade/loggers/__init__.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,16 @@
33
from logging.handlers import RotatingFileHandler, SysLogHandler
44
from pathlib import Path
55

6+
from rich.console import Console
7+
68
from freqtrade.constants import Config
79
from freqtrade.exceptions import OperationalException
810
from freqtrade.loggers.buffering_handler import FTBufferingHandler
11+
from freqtrade.loggers.ft_rich_handler import FtRichHandler
912
from freqtrade.loggers.set_log_levels import set_loggers
10-
from freqtrade.loggers.std_err_stream_handler import FTStdErrStreamHandler
13+
14+
15+
# from freqtrade.loggers.std_err_stream_handler import FTStdErrStreamHandler
1116

1217

1318
logger = logging.getLogger(__name__)
@@ -17,6 +22,8 @@
1722
bufferHandler = FTBufferingHandler(1000)
1823
bufferHandler.setFormatter(Formatter(LOGFORMAT))
1924

25+
error_console = Console(stderr=True, color_system=None)
26+
2027

2128
def get_existing_handlers(handlertype):
2229
"""
@@ -33,8 +40,16 @@ def setup_logging_pre() -> None:
3340
logging handlers after the real initialization, because we don't know which
3441
ones the user desires beforehand.
3542
"""
43+
rh = FtRichHandler(console=error_console)
44+
rh.setFormatter(Formatter("%(message)s"))
3645
logging.basicConfig(
37-
level=logging.INFO, format=LOGFORMAT, handlers=[FTStdErrStreamHandler(), bufferHandler]
46+
level=logging.INFO,
47+
format=LOGFORMAT,
48+
handlers=[
49+
# FTStdErrStreamHandler(),
50+
rh,
51+
bufferHandler,
52+
],
3853
)
3954

4055

@@ -45,6 +60,9 @@ def setup_logging(config: Config) -> None:
4560
# Log level
4661
verbosity = config["verbosity"]
4762
logging.root.addHandler(bufferHandler)
63+
if config.get("print_colorized", True):
64+
logger.info("Enabling colorized output.")
65+
error_console._color_system = error_console._detect_color_system()
4866

4967
logfile = config.get("logfile")
5068

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from datetime import datetime
2+
from logging import Handler
3+
4+
from rich._null_file import NullFile
5+
from rich.console import Console
6+
from rich.text import Text
7+
from rich.traceback import Traceback
8+
9+
10+
class FtRichHandler(Handler):
11+
"""
12+
Basic colorized logging handler using Rich.
13+
Does not support all features of the standard logging handler, and uses a hard-coded log format
14+
"""
15+
16+
def __init__(self, console: Console, *args, **kwargs) -> None:
17+
super().__init__(*args, **kwargs)
18+
self._console = console
19+
20+
def emit(self, record):
21+
try:
22+
msg = self.format(record)
23+
# Format log message
24+
log_time = Text(
25+
datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S,%f")[:-3],
26+
)
27+
name = Text(record.name, style="violet")
28+
log_level = Text(record.levelname, style=f"logging.level.{record.levelname.lower()}")
29+
gray_sep = Text(" - ", style="gray46")
30+
31+
if isinstance(self._console.file, NullFile):
32+
# Handles pythonw, where stdout/stderr are null, and we return NullFile
33+
# instance from Console.file. In this case, we still want to make a log record
34+
# even though we won't be writing anything to a file.
35+
self.handleError(record)
36+
return
37+
38+
self._console.print(
39+
Text() + log_time + gray_sep + name + gray_sep + log_level + gray_sep + msg
40+
)
41+
tb = None
42+
if record.exc_info:
43+
exc_type, exc_value, exc_traceback = record.exc_info
44+
tb = Traceback.from_exception(exc_type, exc_value, exc_traceback, extra_lines=1)
45+
self._console.print(tb)
46+
47+
except RecursionError:
48+
raise
49+
except Exception:
50+
self.handleError(record)

freqtrade/optimize/hyperopt/hyperopt.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
import rapidjson
1717
from joblib import Parallel, cpu_count, delayed, wrap_non_picklable_objects
1818
from joblib.externals import cloudpickle
19-
from rich.console import Console
2019

2120
from freqtrade.constants import FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
2221
from freqtrade.enums import HyperoptState
2322
from freqtrade.exceptions import OperationalException
23+
from freqtrade.loggers import error_console
2424
from freqtrade.misc import file_dump_json, plural
2525
from freqtrade.optimize.hyperopt.hyperopt_logger import logging_mp_handle, logging_mp_setup
2626
from freqtrade.optimize.hyperopt.hyperopt_optimizer import HyperOptimizer
@@ -93,7 +93,6 @@ def __init__(self, config: Config) -> None:
9393

9494
self.print_all = self.config.get("print_all", False)
9595
self.hyperopt_table_header = 0
96-
self.print_colorized = self.config.get("print_colorized", False)
9796
self.print_json = self.config.get("print_json", False)
9897

9998
self.hyperopter = HyperOptimizer(self.config)
@@ -281,13 +280,10 @@ def start(self) -> None:
281280
with Parallel(n_jobs=config_jobs) as parallel:
282281
jobs = parallel._effective_n_jobs()
283282
logger.info(f"Effective number of parallel workers used: {jobs}")
284-
console = Console(
285-
color_system="auto" if self.print_colorized else None,
286-
)
287283

288284
# Define progressbar
289285
with get_progress_tracker(
290-
console=console,
286+
console=error_console,
291287
cust_callables=[self._hyper_out],
292288
) as pbar:
293289
task = pbar.add_task("Epochs", total=self.total_epochs)

tests/test_log_setup.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from freqtrade.exceptions import OperationalException
77
from freqtrade.loggers import (
88
FTBufferingHandler,
9-
FTStdErrStreamHandler,
9+
FtRichHandler,
1010
set_loggers,
1111
setup_logging,
1212
setup_logging_pre,
@@ -72,7 +72,7 @@ def test_set_loggers_syslog():
7272
setup_logging(config)
7373
assert len(logger.handlers) == 3
7474
assert [x for x in logger.handlers if isinstance(x, logging.handlers.SysLogHandler)]
75-
assert [x for x in logger.handlers if isinstance(x, FTStdErrStreamHandler)]
75+
assert [x for x in logger.handlers if isinstance(x, FtRichHandler)]
7676
assert [x for x in logger.handlers if isinstance(x, FTBufferingHandler)]
7777
# setting up logging again should NOT cause the loggers to be added a second time.
7878
setup_logging(config)
@@ -96,7 +96,7 @@ def test_set_loggers_Filehandler(tmp_path):
9696
setup_logging(config)
9797
assert len(logger.handlers) == 3
9898
assert [x for x in logger.handlers if isinstance(x, logging.handlers.RotatingFileHandler)]
99-
assert [x for x in logger.handlers if isinstance(x, FTStdErrStreamHandler)]
99+
assert [x for x in logger.handlers if isinstance(x, FtRichHandler)]
100100
assert [x for x in logger.handlers if isinstance(x, FTBufferingHandler)]
101101
# setting up logging again should NOT cause the loggers to be added a second time.
102102
setup_logging(config)
@@ -145,7 +145,7 @@ def test_set_loggers_journald(mocker):
145145
setup_logging(config)
146146
assert len(logger.handlers) == 3
147147
assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"]
148-
assert [x for x in logger.handlers if isinstance(x, FTStdErrStreamHandler)]
148+
assert [x for x in logger.handlers if isinstance(x, FtRichHandler)]
149149
# reset handlers to not break pytest
150150
logger.handlers = orig_handlers
151151

0 commit comments

Comments
 (0)