1212import argparse
1313import logging
1414import sys
15- from logging .handlers import RotatingFileHandler
16- from pathlib import Path
1715from subprocess import CalledProcessError
1816
19- from platformdirs import user_log_path
2017from scenedetect import VideoOpenFailure
2118from scenedetect .platform import FakeTqdmLoggingRedirect , logging_redirect_tqdm
2219
2320import dvr_scan
2421from dvr_scan import get_license_info
2522from dvr_scan .app .application import Application
2623from dvr_scan .config import CHOICE_MAP , USER_CONFIG_FILE_PATH , ConfigLoadFailure , ConfigRegistry
27- from dvr_scan .platform import LOG_FORMAT_ROLLING_LOGS , attach_log_handler
28- from dvr_scan .shared import ScanSettings , init_logging
24+ from dvr_scan .shared import ScanSettings , init_logging , logfile_path , setup_logger
2925from dvr_scan .shared .cli import VERSION_STRING , LicenseAction , VersionAction , string_type_check
3026
3127logger = logging .getLogger ("dvr_scan" )
3430EXIT_SUCCESS : int = 0
3531EXIT_ERROR : int = 1
3632
37- # Keep the last 16 KiB of logfiles automatically
38- MAX_LOGFILE_SIZE_KB = 16384
39- MAX_LOGFILE_BACKUPS = 4
33+ LOGFILE_PATH = logfile_path (logfile_name = "dvr-scan-app.log" )
4034
4135
4236def get_cli_parser ():
@@ -71,7 +65,7 @@ def get_cli_parser():
7165 metavar = "type" ,
7266 type = string_type_check (CHOICE_MAP ["verbosity" ], False , "type" ),
7367 help = (
74- "Amount of verbosity to use for log output. Must be one of: %s."
68+ "Verbosity type to use for log output. Must be one of: %s."
7569 % (", " .join (CHOICE_MAP ["verbosity" ]),)
7670 ),
7771 )
@@ -81,8 +75,8 @@ def get_cli_parser():
8175 metavar = "file" ,
8276 type = str ,
8377 help = (
84- "Path to log file for writing application output. If FILE already exists, the program "
85- " output will be appended to the existing contents. "
78+ "Appends application output to file. If file does not exist it will be created. "
79+ f"Debug log path: { LOGFILE_PATH } "
8680 ),
8781 )
8882
@@ -109,45 +103,28 @@ def get_cli_parser():
109103 return parser
110104
111105
112- def init_debug_logger () -> logging .Handler :
113- """Initialize rolling debug logger."""
114- folder = user_log_path ("DVR-Scan" , False )
115- folder .mkdir (parents = True , exist_ok = True )
116- handler = RotatingFileHandler (
117- folder / Path ("dvr-scan.app.log" ),
118- maxBytes = MAX_LOGFILE_SIZE_KB * 1024 ,
119- backupCount = MAX_LOGFILE_BACKUPS ,
120- )
121- handler .setLevel (logging .DEBUG )
122- handler .setFormatter (logging .Formatter (fmt = LOG_FORMAT_ROLLING_LOGS ))
123- return handler
124-
125-
126106# TODO: There's a lot of duplicated code here between the CLI and GUI. See if we can combine some
127107# of the handling of config file loading and exceptions to be consistent between the two.
128108#
129109# It would also be nice if both commands took the same set of arguments. Can probably re-use the
130110# existing CLI parser.
131111def main ():
132112 """Parse command line options and load config file settings."""
113+ # We defer printing the debug log until we know where to put it.
133114 init_log = []
134- debug_log_handler = init_debug_logger ()
135- config_load_error = None
136- failed_to_load_config = False
115+ failed_to_load_config = True
137116 config = ConfigRegistry ()
117+ config_load_error = None
138118 # Create debug log handler and try to load the user config file.
139119 try :
140120 user_config = ConfigRegistry ()
141121 user_config .load ()
142122 config = user_config
143123 except ConfigLoadFailure as ex :
144124 config_load_error = ex
145-
146125 # Parse CLI args, override config if an override was specified on the command line.
147126 try :
148127 args = get_cli_parser ().parse_args ()
149- # TODO: Add a UI element somewhere (e.g. in the about window) that indicates to the user
150- # where the log files are being written.
151128 init_logging (args , config )
152129 init_log += [(logging .INFO , "DVR-Scan Application %s" % dvr_scan .__version__ )]
153130 if config_load_error and not hasattr (args , "config" ):
@@ -158,28 +135,38 @@ def main():
158135 init_logging (args , config_setting )
159136 config = config_setting
160137 init_log += config .consume_init_log ()
138+ if config .config_dict :
139+ logger .debug ("Loaded configuration:\n %s" , str (config .config_dict ))
140+ logger .debug ("Program arguments:\n %s" , str (args ))
141+ settings = ScanSettings (args = args , config = config )
142+ if settings .get ("save-log" ):
143+ setup_logger (
144+ logfile_path = LOGFILE_PATH ,
145+ max_size_bytes = settings .get ("max-log-size" ),
146+ max_files = settings .get ("max-log-files" ),
147+ )
148+ failed_to_load_config = False
161149 except ConfigLoadFailure as ex :
162150 init_log += ex .init_log
163151 if ex .reason is not None :
164152 init_log += [(logging .ERROR , "Error: %s" % str (ex .reason ).replace ("\t " , " " ))]
165153 failed_to_load_config = True
166154 config_load_error = ex
167155 finally :
168- attach_log_handler (debug_log_handler )
169156 for log_level , log_str in init_log :
170157 logger .log (log_level , log_str )
171- if failed_to_load_config :
172- logger . critical ( "Failed to load config file." )
173- logger .debug ( "Error loading config file:" , exc_info = config_load_error )
174- # Intentionally suppress the exception in release mode since we've already logged the
175- # failure reason to the user above. We can now exit with an error code.
176- raise SystemExit ( 1 )
177-
178- if config . config_dict :
179- logger . debug ( "Loaded configuration: \n %s" , str ( config . config_dict ))
180-
181- logger . debug ( "Program arguments: \n %s" , str ( args ))
182- settings = ScanSettings ( args = args , config = config )
158+
159+ if failed_to_load_config :
160+ logger .critical ( "Failed to load config file." )
161+ logger . debug ( "Error loading config file:" , exc_info = config_load_error )
162+ # Intentionally suppress the exception in release mode since we've already logged the
163+ # failure reason to the user above. We can now exit with an error code.
164+ raise SystemExit ( 1 )
165+
166+ # TODO(1.7): The logging redirect does not respect the original log level, which is now set to
167+ # DEBUG mode for rolling log files. https://github.com/tqdm/tqdm/issues/1272
168+ # We can just remove the use of a context manager here and install our own hooks into the
169+ # loggers instead as a follow-up action.
183170 redirect = FakeTqdmLoggingRedirect if settings .get ("quiet-mode" ) else logging_redirect_tqdm
184171 show_traceback = getattr (logging , settings .get ("verbosity" ).upper ()) == logging .DEBUG
185172 # TODO: Use Python __debug__ mode instead of hard-coding as config option.
0 commit comments