Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6c7de19
refactored logging, and logging to file feature.
Sep 2, 2020
4184dce
adapted logs
Sep 2, 2020
2ae06c5
Merge branch 'master' into json-logs
Sep 2, 2020
cf9e47c
fixed merge issues
Sep 2, 2020
ad526a0
added comments
Sep 2, 2020
f004d86
fixed logs dir
Sep 2, 2020
e8aff12
shortened logging basi_scene
Sep 2, 2020
38aeeed
added logging tester
Sep 2, 2020
5fee24d
added logging test
Sep 2, 2020
667b530
cleaned up
Sep 2, 2020
8f3536c
cleaned up old tests
Sep 2, 2020
ace2d2f
updated changelog !
Sep 2, 2020
766096a
fixed tests
Sep 2, 2020
ebf1a84
removed commented out code
Sep 2, 2020
3461711
added comment
Sep 2, 2020
285f9f9
removed print statement
Sep 2, 2020
ba46ac0
Update tests/test_logging/test_logging.py
huguesdevimeux Sep 4, 2020
cf1cbf3
changed default value for show_timestamps back to False.
Sep 6, 2020
d4207cb
format fixes
Sep 6, 2020
d83dee4
added changelog entry for json logs.
Sep 6, 2020
867d00c
improved code structure
Sep 7, 2020
c208e40
improved logs mismatch display
Sep 7, 2020
ebfd921
nitpick
Sep 7, 2020
d65e9a3
Merge branch 'json-logs' of github.com:huguesdevimeux/manim into json…
Sep 7, 2020
4b0754a
used with for files
Sep 8, 2020
e4324f1
Apply suggestions from code review
huguesdevimeux Sep 8, 2020
3e2ee65
improved docs
Sep 8, 2020
88e0c51
fixed awful variable name and added an example
Sep 8, 2020
e958f27
Merge branch 'json-logs' of github.com:huguesdevimeux/manim into json…
Sep 8, 2020
8cace54
fixes n flag
Sep 8, 2020
3d1843b
added test for n flag
Sep 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ Of interest to developers
#. Added autogenerated documentation with sphinx and autodoc/autosummary [WIP]
#. Made manim internally use relative imports
#. Since the introduction of the :code:`TexTemplate` class, the files :code:`tex_template.tex` and :code:`ctex_template.tex` have been removed
#. Added logging tests tools.
#. Added ability to save logs in json



Other Changes
Expand Down
1 change: 1 addition & 0 deletions manim/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .scene.scene import Scene
from .utils.sounds import play_error_sound, play_finish_sound
from .utils.file_ops import open_file as open_media_file
from . import constants


def open_file_if_needed(file_writer):
Expand Down
15 changes: 14 additions & 1 deletion manim/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
from .. import constants
from .config_utils import _run_config, _init_dirs, _from_command_line

from .logger import logger
from .logger import set_rich_logger, set_file_logger, logger
from ..utils.tex import TexTemplate, TexTemplateFromFile

__all__ = ["file_writer_config", "config", "camera_config", "tempconfig"]


config = None

Expand Down Expand Up @@ -150,3 +152,14 @@ def _parse_config(config_parser, args):
_init_dirs(file_writer_config)
config = _parse_config(config_parser, args)
camera_config = config

# Set the different loggers
set_rich_logger(config_parser["logger"])
if file_writer_config["log_to_file"]:
# IMPORTANT note about file name : The log file name will be the scene_name get from the args (contained in file_writer_config). So it can differ from the real name of the scene.
log_file_path = os.path.join(
file_writer_config["log_dir"],
"".join(file_writer_config["scene_names"]) + ".log",
)
set_file_logger(log_file_path)
logger.info("Log file wil be saved in %(logpath)s", {"logpath": log_file_path})
12 changes: 8 additions & 4 deletions manim/config/config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,15 @@ def _parse_file_writer_config(config_parser, args):
default.getboolean(boolean_opt) if attr is None else attr
)
# for str_opt in ['media_dir', 'video_dir', 'tex_dir', 'text_dir']:
for str_opt in ["media_dir", "log_dir"]:
for str_opt in ["media_dir"]:
attr = getattr(args, str_opt)
fw_config[str_opt] = os.path.relpath(default[str_opt]) if attr is None else attr
attr = getattr(args, "log_dir")
fw_config["log_dir"] = (
os.path.join(fw_config["media_dir"], default["log_dir"])
if attr is None
else attr
)
dir_names = {
"video_dir": "videos",
"images_dir": "images",
Expand Down Expand Up @@ -147,9 +153,7 @@ def _parse_file_writer_config(config_parser, args):
}

# For internal use (no CLI flag)
fw_config["skip_animations"] = any(
[fw_config["save_last_frame"], fw_config["from_animation_number"]]
)
fw_config["skip_animations"] = fw_config["save_last_frame"]
fw_config["max_files_cached"] = default.getint("max_files_cached")
if fw_config["max_files_cached"] == -1:
fw_config["max_files_cached"] = float("inf")
Expand Down
4 changes: 2 additions & 2 deletions manim/config/default.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ upto_animation_number = -1
# as well as Tex and texts.
media_dir = ./media

# --log_dir
log_dir = %(media_dir)s/logs
# --log_dir (by default "/logs", that will be put inside the media dir)
log_dir = logs

# If the -t (--transparent) flag is used, these will be replaced with the
# values specified in the [TRANSPARENT] section later in this file.
Expand Down
121 changes: 70 additions & 51 deletions manim/config/logger.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"""


__all__ = ['logger', 'console']
__all__ = ["logger", "console"]


import configparser
Expand All @@ -18,24 +18,39 @@
from rich.theme import Theme
from rich import print as printf
from rich import errors, color
import json
import copy

from .config_utils import _run_config

class JSONFormatter(logging.Formatter):
"""Subclass of `:class:`logging.Formatter`, to build our own format of the logs (JSON)."""

def parse_theme(fp):
config_parser.read(fp)
theme = dict(config_parser["logger"])
# replaces `_` by `.` as rich understands it
def format(self, record):
record_c = copy.deepcopy(record)
if record_c.args:
for arg in record_c.args:
record_c.args[arg] = "<>"
return json.dumps({
"levelname": record_c.levelname,
"module": record_c.module,
"message": super().format(record_c),
})


def _parse_theme(config_logger):
theme = dict(
zip([key.replace("_", ".") for key in theme.keys()], list(theme.values()))
zip(
[key.replace("_", ".") for key in config_logger.keys()],
list(config_logger.values()),
)
)

theme["log.width"] = None if theme["log.width"] == "-1" else int(theme["log.width"])

theme["log.height"] = (
None if theme["log.height"] == "-1" else int(theme["log.height"])
)
theme["log.timestamps"] = config_parser["logger"].getboolean("log.timestamps")
theme["log.timestamps"] = False
try:
customTheme = Theme(
{
Expand All @@ -49,51 +64,55 @@ def parse_theme(fp):
printf(
"[logging.level.error]It seems your colour configuration couldn't be parsed. Loading the default color configuration...[/logging.level.error]"
)
return customTheme, theme


config_items = _run_config()
config_parser, successfully_read_files = config_items[1], config_items[-1]
try:
customTheme, themedict = parse_theme(successfully_read_files)
console = Console(
theme=customTheme,
record=True,
height=themedict["log.height"],
width=themedict["log.width"],
)
except KeyError:
console = Console(record=True)
printf(
"[logging.level.warning]No cfg file found, creating one in "
+ successfully_read_files[0]
+ " [/logging.level.warning]"
return customTheme


def set_rich_logger(config_logger):
"""Will set the RichHandler of the logger.

Parameter
----------
config_logger :class:
Config object of the logger.
"""
theme = _parse_theme(config_logger)
global console
console = Console(theme=theme)
# These keywords Are Highlighted specially.
RichHandler.KEYWORDS = [
"Played",
"animations",
"scene",
"Reading",
"Writing",
"script",
"arguments",
"Invalid",
"Aborting",
"module",
"File",
"Rendering",
"Rendered",
]
rich_handler = RichHandler(
console=console, show_time=config_logger.getboolean("log_timestamps")
)
global logger
logger.addHandler(rich_handler)


def set_file_logger(log_file_path):
file_handler = logging.FileHandler(log_file_path, mode="w")
file_handler.setFormatter(JSONFormatter())
# We set the level to DEBUG to it will be able to catch all logs.
file_handler.setLevel(logging.DEBUG)
global logger
logger.addHandler(file_handler)


# These keywords Are Highlighted specially.
RichHandler.KEYWORDS = [
"Played",
"animations",
"scene",
"Reading",
"Writing",
"script",
"arguments",
"Invalid",
"Aborting",
"module",
"File",
"Rendering",
"Rendered",
]
logging.basicConfig(
level="NOTSET",
format="%(message)s",
datefmt="[%X]",
handlers=[RichHandler(console=console, show_time=themedict["log.timestamps"])],
)

logger = logging.getLogger("rich")
logger = logging.getLogger("manim")
# The console is set to None as it will be changed by set_rich_logger.
console = None

# TODO : This is only temporary to keep the terminal output clean when working with ImageMobject and matplotlib plots
logging.getLogger("PIL").setLevel(logging.INFO)
Expand Down
23 changes: 15 additions & 8 deletions manim/scene/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,10 @@ def construct(self):
def __init__(self, **kwargs):
Container.__init__(self, **kwargs)
self.camera = self.camera_class(**camera_config)
self.file_writer = SceneFileWriter(self, **file_writer_config,)
self.file_writer = SceneFileWriter(self, **file_writer_config)
self.play_hashes_list = []
self.mobjects = []
self.original_skipping_status = file_writer_config["skip_animations"]
# TODO, remove need for foreground mobjects
self.foreground_mobjects = []
self.num_plays = 0
Expand All @@ -85,7 +86,7 @@ def __init__(self, **kwargs):
self.tear_down()
# We have to reset these settings in case of multiple renders.
file_writer_config["skip_animations"] = False
self.original_skipping_status = file_writer_config["skip_animations"]

self.file_writer.finish()
self.print_end_message()

Expand Down Expand Up @@ -195,7 +196,7 @@ def update_frame( # TODO Description in Docstring
if file_writer_config["skip_animations"] and not ignore_skipping:
return
if mobjects is None:
mobjects = list_update(self.mobjects, self.foreground_mobjects,)
mobjects = list_update(self.mobjects, self.foreground_mobjects)
if background is not None:
self.camera.set_pixel_array(background)
else:
Expand Down Expand Up @@ -779,10 +780,10 @@ def update_skipping_status(self):
"""

if file_writer_config["from_animation_number"]:
if self.num_plays == file_writer_config["from_animation_number"]:
file_writer_config["skip_animations"] = False
if self.num_plays < file_writer_config["from_animation_number"]:
file_writer_config["skip_animations"] = True
if file_writer_config["upto_animation_number"]:
if self.num_plays >= file_writer_config["upto_animation_number"]:
if self.num_plays > file_writer_config["upto_animation_number"]:
file_writer_config["skip_animations"] = True
raise EndSceneEarlyException()

Expand All @@ -800,8 +801,13 @@ def handle_caching_play(func):

def wrapper(self, *args, **kwargs):
self.revert_to_original_skipping_status()
self.update_skipping_status()
animations = self.compile_play_args_to_animation_list(*args, **kwargs)
self.add_mobjects_from_animations(animations)
if file_writer_config["skip_animations"]:
logger.debug(f"Skipping animation {self.num_plays}")
func(self, *args, **kwargs)
return
if not file_writer_config["disable_caching"]:
mobjects_on_scene = self.get_mobjects()
hash_play = get_hash_from_play_call(
Expand All @@ -810,7 +816,8 @@ def wrapper(self, *args, **kwargs):
self.play_hashes_list.append(hash_play)
if self.file_writer.is_already_cached(hash_play):
logger.info(
f"Animation {self.num_plays} : Using cached data (hash : {hash_play})"
f"Animation {self.num_plays} : Using cached data (hash : %(hash_play)s)",
{"hash_play": hash_play},
)
file_writer_config["skip_animations"] = True
else:
Expand All @@ -834,6 +841,7 @@ def handle_caching_wait(func):

def wrapper(self, duration=DEFAULT_WAIT_TIME, stop_condition=None):
self.revert_to_original_skipping_status()
self.update_skipping_status()
if not file_writer_config["disable_caching"]:
hash_wait = get_hash_from_wait_call(
self.camera, duration, stop_condition, self.get_mobjects()
Expand Down Expand Up @@ -873,7 +881,6 @@ def handle_play_like_call(func):
"""

def wrapper(self, *args, **kwargs):
self.update_skipping_status()
allow_write = not file_writer_config["skip_animations"]
self.file_writer.begin_animation(allow_write)
func(self, *args, **kwargs)
Expand Down
Loading