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

This commit is contained in:
Dan Helfman 2023-04-02 23:06:36 -07:00
parent 01811e03ba
commit 7e6bee84b0
6 changed files with 85 additions and 9 deletions

3
NEWS
View file

@ -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.

View file

@ -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='Log format string used for log messages written to the log file',
help='Write log messages to this file instead of syslog',
) )
global_group.add_argument( global_group.add_argument(
'--override', '--override',

View file

@ -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)

View file

@ -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:

View file

@ -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 [logrotate](https://wiki.archlinux.org/index.php/Logrotate).
`--log-file-verbosity` flag to customize the log file's log level.
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.

View file

@ -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(