Skip to content

Commit 6646610

Browse files
Aathish04Aathish Sivasubrahmanian
andauthored
Implement Logging of Terminal Output to a .log file. (#180)
* Implemented logging of terminal output to file. * Set default config values for log_to_file, log_dir * Change default log_dir to ./logs [skip ci] * Bring log-file up to date with Master. * Re-implement logging of Terminal output to log file. * Move log _writing_ statements to separate function. * Add a test for logging. * Added test to check if logs generated are same. * Added model log file. * Test on this branch * Change Regex expression for windows. * Print log on failure * Print log windows. * Edit regex expression for both unix and windows. * Edit regex expression. * Edit expected log. * Try stripping whitespace. * Move test_logging to a separate file. * Print log for all OS's * Make rules for passing looser. * Fix a typo * Split string before cleaning. * Remove duplicate test_logging file. * Move default log directory into media directory. * Ignored Line Numbers during test. Added some documentation for why word-for-word comparisons are not used. * Allow changing of Max width and height of logs via config files. * Implemented word-for-word tests for log file. (Except for data that _will_ change between tests) * Formatted as per black's wishes. * Add cfg subcmd case for log_width and height. Modified regex to only ignore numbers and paths. * Make regex remove list indicators as well. * Make regex remove timestamps. * Added a comment about the monstrous regex. Co-authored-by: Aathish Sivasubrahmanian <[email protected]>
1 parent 1f85f85 commit 6646610

File tree

10 files changed

+143
-19
lines changed

10 files changed

+143
-19
lines changed

manim/default.cfg

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ output_file =
4848
# --leave_progress_bars
4949
leave_progress_bars = False
5050

51+
# --log_to_file
52+
log_to_file = False
53+
5154
# -c, --color
5255
background_color = BLACK
5356

@@ -67,6 +70,9 @@ upto_animation_number = -1
6770
# --media_dir
6871
media_dir = ./media
6972

73+
# --log_dir
74+
log_dir = %(media_dir)s/logs
75+
7076
# # --video_dir
7177
# video_dir = %(MEDIA_DIR)s/videos
7278

@@ -153,3 +159,5 @@ log_level =
153159
log_time = cyan dim
154160
log_message =
155161
log_path = dim
162+
log_width = -1
163+
log_height = -1

manim/logger.py

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,28 +21,39 @@ def parse_theme(fp):
2121
config_parser.read(fp)
2222
theme = dict(config_parser["logger"])
2323
# replaces `_` by `.` as rich understands it
24-
for key in theme:
25-
temp = theme[key]
26-
del theme[key]
27-
key = key.replace("_", ".")
28-
theme[key] = temp
24+
theme = dict(
25+
zip([key.replace("_", ".") for key in theme.keys()], list(theme.values()))
26+
)
27+
28+
theme["log.width"] = None if theme["log.width"] == "-1" else int(theme["log.width"])
29+
30+
theme["log.height"] = (
31+
None if theme["log.height"] == "-1" else int(theme["log.height"])
32+
)
2933
try:
30-
customTheme = Theme(theme)
34+
customTheme = Theme(
35+
{k: v for k, v in theme.items() if k not in ["log.width", "log.height"]}
36+
)
3137
except (color.ColorParseError, errors.StyleSyntaxError):
3238
customTheme = None
3339
printf(
3440
"[logging.level.error]It seems your colour configuration couldn't be parsed. Loading the default color configuration...[/logging.level.error]"
3541
)
36-
return customTheme
42+
return customTheme, theme
3743

3844

3945
config_items = _run_config()
4046
config_parser, successfully_read_files = config_items[1], config_items[-1]
4147
try:
42-
customTheme = parse_theme(successfully_read_files)
43-
console = Console(theme=customTheme)
48+
customTheme, themedict = parse_theme(successfully_read_files)
49+
console = Console(
50+
theme=customTheme,
51+
record=True,
52+
height=themedict["log.height"],
53+
width=themedict["log.width"],
54+
)
4455
except KeyError:
45-
console = Console()
56+
console = Console(record=True)
4657
printf(
4758
"[logging.level.warning]No cfg file found, creating one in "
4859
+ successfully_read_files[0]

manim/scene/scene_file_writer.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
from ..constants import FFMPEG_BIN, GIF_FILE_EXTENSION
1212
from ..config import file_writer_config
13-
from ..logger import logger
13+
from ..logger import logger, console
1414
from ..utils.config_ops import digest_config
1515
from ..utils.file_ops import guarantee_existence
1616
from ..utils.file_ops import add_extension_if_not_present
@@ -533,3 +533,13 @@ def print_file_ready_message(self, file_path):
533533
Prints the "File Ready" message to STDOUT.
534534
"""
535535
logger.info("\nFile ready at {}\n".format(file_path))
536+
537+
if file_writer_config["log_to_file"]:
538+
self.write_log()
539+
540+
def write_log(self):
541+
log_file_path = os.path.join(
542+
file_writer_config["log_dir"], f"{self.get_default_scene_name()}.log"
543+
)
544+
console.save_text(log_file_path)
545+
logger.info("Log written to {}\n".format(log_file_path))

manim/utils/cfg_subcmds.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,24 @@ def write(level=None, openfile=False):
9393
console.print(RICH_COLOUR_INSTRUCTIONS)
9494
default = replace_keys(default)
9595
for key in default:
96-
console.print(f"Enter the style for {key}:", style=key, end="")
96+
desc = (
97+
"style" if key not in ["log.width", "log.height"] else "value"
98+
)
99+
style = key if key not in ["log.width", "log.height"] else None
100+
cond = (
101+
is_valid_style
102+
if key not in ["log.width", "log.height"]
103+
else lambda m: m.isdigit()
104+
)
105+
console.print(f"Enter the {desc} for {key}:", style=style, end="")
97106
temp = input()
98107
if temp:
99-
while not is_valid_style(temp):
108+
while not cond(temp):
100109
console.print(
101-
"[red bold]Invalid style. Try again.[/red bold]"
110+
f"[red bold]Invalid {desc}. Try again.[/red bold]"
102111
)
103112
console.print(
104-
f"Enter the style for {key}:", style=key, end=""
113+
f"Enter the {desc} for {key}:", style=style, end=""
105114
)
106115
temp = input()
107116
else:
@@ -162,7 +171,7 @@ def show():
162171
for category in current_config:
163172
console.print(f"{category}", style="bold green underline")
164173
for entry in current_config[category]:
165-
if category == "logger":
174+
if category == "logger" and entry not in ["log_width", "log_height"]:
166175
console.print(f"{entry} :", end="")
167176
console.print(
168177
f" {current_config[category][entry]}",

manim/utils/config_utils.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,14 @@ def _parse_file_writer_config(config_parser, args):
6060
"save_pngs",
6161
"save_as_gif",
6262
"write_all",
63+
"log_to_file",
6364
]:
6465
attr = getattr(args, boolean_opt)
6566
fw_config[boolean_opt] = (
6667
default.getboolean(boolean_opt) if attr is None else attr
6768
)
6869
# for str_opt in ['media_dir', 'video_dir', 'tex_dir', 'text_dir']:
69-
for str_opt in ["media_dir"]:
70+
for str_opt in ["media_dir", "log_dir"]:
7071
attr = getattr(args, str_opt)
7172
fw_config[str_opt] = os.path.relpath(default[str_opt]) if attr is None else attr
7273
dir_names = {"video_dir": "videos", "tex_dir": "Tex", "text_dir": "texts"}
@@ -255,6 +256,13 @@ def _parse_cli(arg_list, input=True):
255256
help="Save the video as gif",
256257
)
257258

259+
parser.add_argument(
260+
"--log_to_file",
261+
action="store_const",
262+
const=True,
263+
help="Log terminal output to file.",
264+
)
265+
258266
# The default value of the following is set in manim.cfg
259267
parser.add_argument(
260268
"-c", "--color", help="Background color",
@@ -265,6 +273,11 @@ def _parse_cli(arg_list, input=True):
265273
parser.add_argument(
266274
"--media_dir", help="directory to write media",
267275
)
276+
277+
parser.add_argument(
278+
"--log_dir", help="directory to write log files to",
279+
)
280+
268281
# video_group = parser.add_mutually_exclusive_group()
269282
# video_group.add_argument(
270283
# "--video_dir",
@@ -370,9 +383,14 @@ def _init_dirs(config):
370383
config["video_dir"],
371384
config["tex_dir"],
372385
config["text_dir"],
386+
config["log_dir"],
373387
]:
374388
if not os.path.exists(folder):
375-
os.makedirs(folder)
389+
# If log_to_file is False, ignore log_dir
390+
if folder is config["log_dir"] and (not config["log_to_file"]):
391+
pass
392+
else:
393+
os.makedirs(folder)
376394

377395

378396
def _from_command_line():

tests/test_cli/test_cfg_subcmd.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def test_cfg_show(python_version):
2020
"""Test if the `manim cfg show` command works as intended."""
2121
command = f"cd {this_folder} && {python_version} -m manim cfg show"
2222
out, err, exitcode = capture(command, use_shell=True)
23-
assert exitcode == 0
23+
assert exitcode == 0, err
2424
assert f"{os.path.sep}tests{os.path.sep}".encode("utf-8") in out, err
2525

2626

tests/test_cli/write_cfg_sbcmd_input.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ False
2424

2525

2626

27+
28+
29+

tests/test_logging/expected.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
INFO Read configuration files: config.py:
2+
INFO scene_file_writer.py:
3+
File ready at
4+

tests/test_logging/test_logging.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import subprocess
2+
import os
3+
import sys
4+
from shutil import rmtree
5+
import pytest
6+
import re
7+
8+
9+
def capture(command, instream=None):
10+
proc = subprocess.Popen(
11+
command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=instream
12+
)
13+
out, err = proc.communicate()
14+
return out, err, proc.returncode
15+
16+
17+
def test_logging_to_file(python_version):
18+
"""Test logging Terminal output to a log file.
19+
As some data will differ with each log (the timestamps, file paths, line nums etc)
20+
a regex substitution has been employed to replace the strings that may change with
21+
whitespace.
22+
"""
23+
path_basic_scene = os.path.join("tests", "tests_data", "basic_scenes.py")
24+
path_output = os.path.join("tests_cache", "media_temp")
25+
command = [
26+
python_version,
27+
"-m",
28+
"manim",
29+
path_basic_scene,
30+
"SquareToCircle",
31+
"-l",
32+
"--log_to_file",
33+
"--log_dir",
34+
os.path.join(path_output, "logs"),
35+
"--media_dir",
36+
path_output,
37+
]
38+
out, err, exitcode = capture(command)
39+
log_file_path = os.path.join(path_output, "logs", "SquareToCircle.log")
40+
assert exitcode == 0, err
41+
assert os.path.exists(log_file_path), err
42+
if sys.platform.startswith("win32") or sys.platform.startswith("cygwin"):
43+
enc = "Windows-1252"
44+
else:
45+
enc = "utf-8"
46+
with open(log_file_path, encoding=enc) as logfile:
47+
logs = logfile.read()
48+
# The following regex pattern selects timestamps, file paths and all numbers..
49+
pattern = r"(\[?\d+:?]?)|(\['[A-Z]?:?[\/\\].*cfg'])|([A-Z]?:?[\/\\].*mp4)"
50+
51+
logs = re.sub(pattern, lambda m: " " * len((m.group(0))), logs)
52+
with open(
53+
os.path.join(os.path.dirname(__file__), "expected.txt"), "r"
54+
) as expectedfile:
55+
expected = re.sub(
56+
pattern, lambda m: " " * len((m.group(0))), expectedfile.read()
57+
)
58+
assert logs == expected, logs

tests/tests_data/manim.cfg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ write_to_movie = True
44
# write_all = False
55
save_last_frame = False
66
# save_pngs = False
7+
8+
[logger]
9+
log_width = 256

0 commit comments

Comments
 (0)