diff --git a/src/ytdl_sub/config/subscription.py b/src/ytdl_sub/config/subscription.py index dcc9d20d..6d046a00 100644 --- a/src/ytdl_sub/config/subscription.py +++ b/src/ytdl_sub/config/subscription.py @@ -69,6 +69,15 @@ class SubscriptionValidator(StrictDictValidator): preset_options=self.preset, ) + @property + def name(self) -> str: + """ + Returns + ------- + Name of the subscription + """ + return self._name + @classmethod def from_dict( cls, config: ConfigFile, subscription_name: str, subscription_dict: Dict diff --git a/src/ytdl_sub/downloaders/youtube_downloader.py b/src/ytdl_sub/downloaders/youtube_downloader.py index f82cbdbf..45bb057c 100644 --- a/src/ytdl_sub/downloaders/youtube_downloader.py +++ b/src/ytdl_sub/downloaders/youtube_downloader.py @@ -17,10 +17,13 @@ from ytdl_sub.config.preset_options import Overrides from ytdl_sub.downloaders.downloader import Downloader from ytdl_sub.downloaders.downloader import DownloaderValidator from ytdl_sub.entries.youtube import YoutubeVideo +from ytdl_sub.utils.logger import Logger from ytdl_sub.validators.date_range_validator import DateRangeValidator from ytdl_sub.validators.string_formatter_validators import OverridesStringFormatterValidator from ytdl_sub.validators.validators import StringValidator +logger = Logger.get() + ############################################################################### # Abstract Youtube downloader + options @@ -223,7 +226,7 @@ class YoutubeChannelDownloader(YoutubeDownloader[YoutubeChannelDownloaderOptions break if not thumbnail_url: - # TODO: add logger with warn here + logger.warning("Could not find a thumbnail for %s", entry_dict.get("id")) return with urlopen(thumbnail_url) as file: diff --git a/src/ytdl_sub/main.py b/src/ytdl_sub/main.py index 84a22574..7226f480 100644 --- a/src/ytdl_sub/main.py +++ b/src/ytdl_sub/main.py @@ -1,6 +1,5 @@ import argparse import sys -import traceback from typing import List from ytdl_sub.cli.download_args_parser import DownloadArgsParser @@ -8,8 +7,9 @@ from ytdl_sub.cli.main_args_parser import parser from ytdl_sub.config.config_file import ConfigFile from ytdl_sub.config.subscription import SubscriptionValidator from ytdl_sub.utils.exceptions import ValidationException +from ytdl_sub.utils.logger import Logger -DEBUGGER_MODE = True +logger = Logger.get() def _download_subscriptions_from_yaml_files(config: ConfigFile, args: argparse.Namespace) -> None: @@ -28,6 +28,7 @@ def _download_subscriptions_from_yaml_files(config: ConfigFile, args: argparse.N ) for subscription in subscriptions: + logger.info("Beginning subscription download for %s", subscription.name) subscription.to_subscription().download() @@ -56,27 +57,22 @@ def main(): config: ConfigFile = ConfigFile.from_file_path(args.config) if args.subparser == "sub": _download_subscriptions_from_yaml_files(config=config, args=args) - print("Subscription download complete!") + logger.info("Subscription download complete!") # One-off download if args.subparser == "dl": _download_subscription_from_cli(config=config, extra_args=extra_args) - print("Download complete!") + logger.info("Download complete!") if __name__ == "__main__": try: main() except ValidationException as validation_exception: - if DEBUGGER_MODE: - raise - print(validation_exception) + logger.error(validation_exception) sys.exit(1) except Exception as exc: # pylint: disable=broad-except - if DEBUGGER_MODE: - raise - print(traceback.format_exc()) - print( + logger.exception( "A fatal error occurred. Please copy and paste the stacktrace above and make a Github " "issue at https://github.com/jmbannon/ytdl-subscribe/issues with your config and " "command/subscription yaml file to reproduce. Thanks for trying ytdl-subscribe!" diff --git a/src/ytdl_sub/plugins/music_tags.py b/src/ytdl_sub/plugins/music_tags.py index 26b22d6c..9d61ca3e 100644 --- a/src/ytdl_sub/plugins/music_tags.py +++ b/src/ytdl_sub/plugins/music_tags.py @@ -31,10 +31,11 @@ class MusicTagsPlugin(Plugin[MusicTagsOptions]): audio_file = mediafile.MediaFile(entry.get_download_file_path()) for tag, tag_formatter in self.plugin_options.tags.dict.items(): if tag not in audio_file.fields(): - # TODO: Add proper logger and warn here - print( - f"[ytld-sub: WARN] tag {tag} is not supported for {entry.ext} files. Supported " - f"tags: {', '.join(audio_file.sorted_fields())}" + self._logger.warning( + "tag '%s' is not supported for %s files. Supported tags: %s", + tag, + entry.ext, + ", ".join(audio_file.sorted_fields()), ) tag_value = self.overrides.apply_formatter(formatter=tag_formatter, entry=entry) diff --git a/src/ytdl_sub/plugins/plugin.py b/src/ytdl_sub/plugins/plugin.py index f70103b1..4d2ad76a 100644 --- a/src/ytdl_sub/plugins/plugin.py +++ b/src/ytdl_sub/plugins/plugin.py @@ -6,6 +6,7 @@ from typing import final from ytdl_sub.config.preset_options import Overrides from ytdl_sub.entries.entry import Entry +from ytdl_sub.utils.logger import Logger from ytdl_sub.validators.strict_dict_validator import StrictDictValidator from ytdl_sub.ytdl_additions.enhanced_download_archive import EnhancedDownloadArchive @@ -38,6 +39,8 @@ class Plugin(Generic[PluginOptionsT], ABC): self.output_directory = output_directory self.overrides = overrides self.__enhanced_download_archive = enhanced_download_archive + # TODO pass yaml snake case name in the class somewhere, and use it for the logger + self._logger = Logger.get(self.__class__.__name__) @final def archive_entry_file_name(self, entry: Entry, relative_file_path: str) -> None: diff --git a/src/ytdl_sub/utils/logger.py b/src/ytdl_sub/utils/logger.py new file mode 100644 index 00000000..24d8dc27 --- /dev/null +++ b/src/ytdl_sub/utils/logger.py @@ -0,0 +1,47 @@ +import logging +import sys +from typing import Optional + + +class Logger: + @classmethod + def _get_formatter(cls) -> logging.Formatter: + """ + Returns + ------- + Formatter for all ytdl-sub loggers + """ + return logging.Formatter("[%(name)s] %(message)s") + + @classmethod + def _get_handler(cls) -> logging.StreamHandler: + """ + Returns + ------- + Logger handler + """ + handler = logging.StreamHandler(sys.stdout) + handler.setLevel(logging.INFO) + handler.setFormatter(cls._get_formatter()) + return handler + + @classmethod + def get(cls, name: Optional[str] = None) -> logging.Logger: + """ + Parameters + ---------- + name + Optional. Name of the logger which is included in the prefix like [ytdl-sub:]. + If None, the prefix is just [ytdl-sub] + + Returns + ------- + A configured logger + """ + logger_name = "ytdl-sub" + if name: + logger_name += f":{name}" + + logger = logging.Logger(name=logger_name) + logger.addHandler(cls._get_handler()) + return logger diff --git a/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py b/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py index 7db054c7..50786b89 100644 --- a/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py +++ b/src/ytdl_sub/ytdl_additions/enhanced_download_archive.py @@ -13,6 +13,7 @@ from typing import Set from yt_dlp import DateRange from ytdl_sub.entries.entry import Entry +from ytdl_sub.utils.logger import Logger @dataclass @@ -336,6 +337,8 @@ class EnhancedDownloadArchive: self._download_archive: Optional[DownloadArchive] = None self._download_mapping: Optional[DownloadMappings] = None + self._logger = Logger.get(name=subscription_name) + @property def archive_file_name(self) -> str: """ @@ -460,10 +463,10 @@ class EnhancedDownloadArchive: ) for uid, mapping in stale_mappings.items(): - print(f"[{uid}] Removing the following stale file(s):") + self._logger.info("[%s] Removing the following stale file(s):", uid) for file_name in mapping.file_names: file_path = Path(self.output_directory) / Path(file_name) - print(f" - {file_path}") + self._logger.info(" - %s", file_path) if os.path.exists(file_path): os.remove(file_path)