Răsfoiți Sursa

Log to file instead of syslog via command-line "--log-file" flag (#233).

Dan Helfman 5 ani în urmă
părinte
comite
06f134cc71

+ 6 - 0
borgmatic/commands/arguments.py

@@ -149,6 +149,12 @@ def parse_arguments(*unparsed_arguments):
         default=0,
         default=0,
         help='Display verbose progress to syslog (from none to lots: 0, 1, or 2). Ignored when console is interactive',
         help='Display verbose progress to syslog (from none to lots: 0, 1, or 2). Ignored when console is interactive',
     )
     )
+    global_group.add_argument(
+        '--log-file',
+        type=str,
+        default=None,
+        help='Write log messages to this file instead of concole and syslog',
+    )
     global_group.add_argument(
     global_group.add_argument(
         '--version',
         '--version',
         dest='version',
         dest='version',

+ 1 - 0
borgmatic/commands/borgmatic.py

@@ -487,6 +487,7 @@ def main():  # pragma: no cover
     configure_logging(
     configure_logging(
         verbosity_to_log_level(global_arguments.verbosity),
         verbosity_to_log_level(global_arguments.verbosity),
         verbosity_to_log_level(global_arguments.syslog_verbosity),
         verbosity_to_log_level(global_arguments.syslog_verbosity),
+        global_arguments.log_file,
     )
     )
 
 
     logger.debug('Ensuring legacy configuration is upgraded')
     logger.debug('Ensuring legacy configuration is upgraded')

+ 18 - 5
borgmatic/logger.py

@@ -73,7 +73,7 @@ def color_text(color, message):
     return '{}{}{}'.format(color, message, colorama.Style.RESET_ALL)
     return '{}{}{}'.format(color, message, colorama.Style.RESET_ALL)
 
 
 
 
-def configure_logging(console_log_level, syslog_log_level=None):
+def configure_logging(console_log_level, syslog_log_level=None, log_file=None):
     '''
     '''
     Configure logging to go to both the console and syslog. Use the given log levels, respectively.
     Configure logging to go to both the console and syslog. Use the given log levels, respectively.
     '''
     '''
@@ -85,16 +85,29 @@ def configure_logging(console_log_level, syslog_log_level=None):
     console_handler.setLevel(console_log_level)
     console_handler.setLevel(console_log_level)
 
 
     syslog_path = None
     syslog_path = None
-    if os.path.exists('/dev/log'):
-        syslog_path = '/dev/log'
-    elif os.path.exists('/var/run/syslog'):
-        syslog_path = '/var/run/syslog'
+    if log_file is None:
+        if os.path.exists('/dev/log'):
+            syslog_path = '/dev/log'
+        elif os.path.exists('/var/run/syslog'):
+            syslog_path = '/var/run/syslog'
 
 
     if syslog_path and not interactive_console():
     if syslog_path and not interactive_console():
         syslog_handler = logging.handlers.SysLogHandler(address=syslog_path)
         syslog_handler = logging.handlers.SysLogHandler(address=syslog_path)
         syslog_handler.setFormatter(logging.Formatter('borgmatic: %(levelname)s %(message)s'))
         syslog_handler.setFormatter(logging.Formatter('borgmatic: %(levelname)s %(message)s'))
         syslog_handler.setLevel(syslog_log_level)
         syslog_handler.setLevel(syslog_log_level)
         handlers = (console_handler, syslog_handler)
         handlers = (console_handler, syslog_handler)
+    elif log_file:
+        try:
+            file_handler = logging.FileHandler(log_file)
+        except FileNotFoundError:
+            print("ERROR: Path to log-file doesn't exist: {}".format(log_file))
+            sys.exit(1)
+        except PermissionError:
+            print("ERROR: No write access to log-file: {}".format(log_file))
+            sys.exit(1)
+        file_handler.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s'))
+        file_handler.setLevel(syslog_log_level)
+        handlers = (console_handler, file_handler)
     else:
     else:
         handlers = (console_handler,)
         handlers = (console_handler,)
 
 

+ 28 - 0
tests/unit/test_logger.py

@@ -193,3 +193,31 @@ def test_configure_logging_skips_syslog_if_interactive_console():
     flexmock(module.logging.handlers).should_receive('SysLogHandler').never()
     flexmock(module.logging.handlers).should_receive('SysLogHandler').never()
 
 
     module.configure_logging(console_log_level=logging.INFO)
     module.configure_logging(console_log_level=logging.INFO)
+
+
+def test_configure_logging_to_logfile_instead_syslog():
+    # syslog skipped in non-interactive console if --log-file argument provided
+    flexmock(module).should_receive('interactive_console').and_return(False)
+    flexmock(module.logging).should_receive('basicConfig').with_args(
+        level=logging.INFO, handlers=tuple
+    )
+    flexmock(module.os.path).should_receive('exists').with_args('/dev/log').and_return(True)
+    flexmock(module.logging.handlers).should_receive('SysLogHandler').never()
+    file_handler = logging.FileHandler('/tmp/logfile')
+    flexmock(module.logging).should_receive('FileHandler').with_args('/tmp/logfile').and_return(
+        file_handler
+    ).once()
+
+    module.configure_logging(console_log_level=logging.INFO, log_file='/tmp/logfile')
+
+
+def test_configure_logging_skips_logfile_if_argument_is_none():
+    # No FileHandler added if argument --log-file is None
+    flexmock(module).should_receive('interactive_console').and_return(False)
+    flexmock(module.logging).should_receive('basicConfig').with_args(
+        level=logging.INFO, handlers=tuple
+    )
+    flexmock(module.os.path).should_receive('exists').and_return(False)
+    flexmock(module.logging).should_receive('FileHandler').never()
+
+    module.configure_logging(console_log_level=logging.INFO, log_file=None)