Browse Source

Add "--log-file-format" flag for customizing the log message format (#658).

Dan Helfman 2 years ago
parent
commit
7e6bee84b0

+ 3 - 0
NEWS

@@ -7,6 +7,9 @@
    https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#archive-naming
    https://torsion.org/borgmatic/docs/how-to/make-per-application-backups/#archive-naming
  * #479, #588: The "prefix" options have been deprecated in favor of the new "archive_name_format"
  * #479, #588: The "prefix" options have been deprecated in favor of the new "archive_name_format"
    auto-matching behavior and the "match_archives" option.
    auto-matching behavior and the "match_archives" option.
+ * #658: Add "--log-file-format" flag for customizing the log message format. See the documentation
+   for more information:
+   https://torsion.org/borgmatic/docs/how-to/inspect-your-backups/#logging-to-file
  * #662: Fix regression in which the "check_repositories" option failed to match repositories.
  * #662: Fix regression in which the "check_repositories" option failed to match repositories.
  * #663: Fix regression in which the "transfer" action produced a traceback.
  * #663: Fix regression in which the "transfer" action produced a traceback.
  * Add spellchecking of source code during test runs.
  * Add spellchecking of source code during test runs.

+ 5 - 3
borgmatic/commands/arguments.py

@@ -178,10 +178,12 @@ def make_parsers():
         help='Log verbose progress to monitoring integrations that support logging (from only errors to very verbose: -1, 0, 1, or 2)',
         help='Log verbose progress to monitoring integrations that support logging (from only errors to very verbose: -1, 0, 1, or 2)',
     )
     )
     global_group.add_argument(
     global_group.add_argument(
-        '--log-file',
+        '--log-file', type=str, help='Write log messages to this file instead of syslog',
+    )
+    global_group.add_argument(
+        '--log-file-format',
         type=str,
         type=str,
-        default=None,
-        help='Write log messages to this file instead of syslog',
+        help='Log format string used for log messages written to the log file',
     )
     )
     global_group.add_argument(
     global_group.add_argument(
         '--override',
         '--override',

+ 1 - 0
borgmatic/commands/borgmatic.py

@@ -700,6 +700,7 @@ def main():  # pragma: no cover
             verbosity_to_log_level(global_arguments.log_file_verbosity),
             verbosity_to_log_level(global_arguments.log_file_verbosity),
             verbosity_to_log_level(global_arguments.monitoring_verbosity),
             verbosity_to_log_level(global_arguments.monitoring_verbosity),
             global_arguments.log_file,
             global_arguments.log_file,
+            global_arguments.log_file_format,
         )
         )
     except (FileNotFoundError, PermissionError) as error:
     except (FileNotFoundError, PermissionError) as error:
         configure_logging(logging.CRITICAL)
         configure_logging(logging.CRITICAL)

+ 9 - 2
borgmatic/logger.py

@@ -156,6 +156,7 @@ def configure_logging(
     log_file_log_level=None,
     log_file_log_level=None,
     monitoring_log_level=None,
     monitoring_log_level=None,
     log_file=None,
     log_file=None,
+    log_file_format=None,
 ):
 ):
     '''
     '''
     Configure logging to go to both the console and (syslog or log file). Use the given log levels,
     Configure logging to go to both the console and (syslog or log file). Use the given log levels,
@@ -200,12 +201,18 @@ def configure_logging(
 
 
     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} {message}', style='{')  # noqa: FS003
+        )
         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:
     elif log_file:
         file_handler = logging.handlers.WatchedFileHandler(log_file)
         file_handler = logging.handlers.WatchedFileHandler(log_file)
-        file_handler.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s: %(message)s'))
+        file_handler.setFormatter(
+            logging.Formatter(
+                log_file_format or '[{asctime}] {levelname}: {message}', style='{'  # noqa: FS003
+            )
+        )
         file_handler.setLevel(log_file_log_level)
         file_handler.setLevel(log_file_log_level)
         handlers = (console_handler, file_handler)
         handlers = (console_handler, file_handler)
     else:
     else:

+ 36 - 2
docs/how-to/inspect-your-backups.md

@@ -154,5 +154,39 @@ borgmatic --log-file /path/to/file.log
 
 
 Note that if you use the `--log-file` flag, you are responsible for rotating
 Note that if you use the `--log-file` flag, you are responsible for rotating
 the log file so it doesn't grow too large, for example with
 the log file so it doesn't grow too large, for example with
-[logrotate](https://wiki.archlinux.org/index.php/Logrotate). Also, there is a
-`--log-file-verbosity` flag to customize the log file's log level.
+[logrotate](https://wiki.archlinux.org/index.php/Logrotate).
+
+You can the `--log-file-verbosity` flag to customize the log file's log level:
+
+```bash
+borgmatic --log-file /path/to/file.log --log-file-verbosity 2
+```
+
+<span class="minilink minilink-addedin">New in borgmatic version 1.7.11</span>
+Use the `--log-file-format` flag to override the default log message format.
+This format string can contain a series of named placeholders wrapped in curly
+brackets. For instance, the default log format is: `[{asctime}] {levelname}:
+{message}`. This means each log message is recorded as the log time (in square
+brackets), a logging level name, a colon, and the actual log message.
+
+So if you just want each log message to get logged *without* a timestamp or a
+logging level name:
+
+```bash
+borgmatic --log-file /path/to/file.log --log-file-format "{message}"
+```
+
+Here is a list of available placeholders:
+
+ * `{asctime}`: time the log message was created
+ * `{levelname}`: level of the log message (`INFO`, `DEBUG`, etc.)
+ * `{lineno}`: line number in the source file where the log message originated
+ * `{message}`: actual log message
+ * `{pathname}`: path of the source file where the log message originated
+
+See the [Python logging
+documentation](https://docs.python.org/3/library/logging.html#logrecord-attributes)
+for additional placeholders.
+
+Note that this `--log-file-format` flg only applies to the specified
+`--log-file` and not to syslog or other logging.

+ 31 - 2
tests/unit/test_logger.py

@@ -285,7 +285,7 @@ def test_configure_logging_skips_syslog_if_interactive_console():
     module.configure_logging(console_log_level=logging.INFO)
     module.configure_logging(console_log_level=logging.INFO)
 
 
 
 
-def test_configure_logging_to_logfile_instead_of_syslog():
+def test_configure_logging_to_log_file_instead_of_syslog():
     flexmock(module).should_receive('add_custom_log_levels')
     flexmock(module).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.ANSWER
     flexmock(module.logging).ANSWER = module.ANSWER
     flexmock(module).should_receive('Multi_stream_handler').and_return(
     flexmock(module).should_receive('Multi_stream_handler').and_return(
@@ -309,7 +309,36 @@ def test_configure_logging_to_logfile_instead_of_syslog():
     )
     )
 
 
 
 
-def test_configure_logging_skips_logfile_if_argument_is_none():
+def test_configure_logging_to_log_file_formats_with_custom_log_format():
+    flexmock(module).should_receive('add_custom_log_levels')
+    flexmock(module.logging).ANSWER = module.ANSWER
+    flexmock(module.logging).should_receive('Formatter').with_args(
+        '{message}', style='{'  # noqa: FS003
+    ).once()
+    flexmock(module).should_receive('Multi_stream_handler').and_return(
+        flexmock(setFormatter=lambda formatter: None, setLevel=lambda level: None)
+    )
+
+    flexmock(module).should_receive('interactive_console').and_return(False)
+    flexmock(module.logging).should_receive('basicConfig').with_args(
+        level=logging.DEBUG, 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.handlers.WatchedFileHandler('/tmp/logfile')
+    flexmock(module.logging.handlers).should_receive('WatchedFileHandler').with_args(
+        '/tmp/logfile'
+    ).and_return(file_handler).once()
+
+    module.configure_logging(
+        console_log_level=logging.INFO,
+        log_file_log_level=logging.DEBUG,
+        log_file='/tmp/logfile',
+        log_file_format='{message}',  # noqa: FS003
+    )
+
+
+def test_configure_logging_skips_log_file_if_argument_is_none():
     flexmock(module).should_receive('add_custom_log_levels')
     flexmock(module).should_receive('add_custom_log_levels')
     flexmock(module.logging).ANSWER = module.ANSWER
     flexmock(module.logging).ANSWER = module.ANSWER
     flexmock(module).should_receive('Multi_stream_handler').and_return(
     flexmock(module).should_receive('Multi_stream_handler').and_return(