Successfully convert Borg output to Python logging entries.

This commit is contained in:
Dan Helfman 2019-06-11 16:42:04 -07:00
parent 02eb6c7e09
commit 9a5e7a3abb
9 changed files with 71 additions and 21 deletions

3
NEWS
View file

@ -1,5 +1,6 @@
1.3.6.dev0
* #53: Log to syslog in addition to existing standard out logging.
* #53: Log to syslog in addition to existing console logging. Add --syslog-verbosity flag to
customize the log level.
* #178: Look for .yml configuration file extension in addition to .yaml.
* Remove Python cache files before each Tox run.
* Add #borgmatic Freenode IRC channel to documentation.

View file

@ -149,10 +149,14 @@ def create_archive(
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--umask', str(umask)) if umask else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--list', '--filter', 'AME-') if logger.isEnabledFor(logging.INFO) else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--stats',) if not dry_run and (logger.isEnabledFor(logging.INFO) or stats) else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--list', '--filter', 'AME-') if logger.isEnabledFor(logging.INFO) and not json else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
+ (
('--stats',)
if not dry_run and (logger.isEnabledFor(logging.INFO) or stats) and not json
else ()
)
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
+ (('--dry-run',) if dry_run else ())
+ (('--progress',) if progress else ())
+ (('--json',) if json else ())

View file

@ -5,6 +5,23 @@ from borgmatic.logger import get_logger
logger = get_logger(__name__)
def execute_and_log_output(full_command):
process = subprocess.Popen(full_command, stdout=None, stderr=subprocess.PIPE)
while process.poll() is None:
line = process.stderr.readline().rstrip().decode()
if line.startswith('borg: error:'):
logger.error(line)
else:
logger.info(line)
logger.info(process.stderr.read().rstrip().decode())
exit_code = process.poll()
if exit_code != 0:
raise subprocess.CalledProcessError(exit_code, full_command)
def execute_command(full_command, capture_output=False):
'''
Execute the given command (a sequence of command/argument strings). If capture output is True,
@ -16,4 +33,4 @@ def execute_command(full_command, capture_output=False):
output = subprocess.check_output(full_command)
return output.decode() if output is not None else None
else:
subprocess.check_call(full_command)
execute_and_log_output(full_command)

View file

@ -19,8 +19,8 @@ def display_archives_info(
(local_path, 'info', repository)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
+ (('--json',) if json else ())
)

View file

@ -20,8 +20,8 @@ def list_archives(
(local_path, 'list', '::'.join((repository, archive)) if archive else repository)
+ (('--remote-path', remote_path) if remote_path else ())
+ (('--lock-wait', str(lock_wait)) if lock_wait else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) else ())
+ (('--info',) if logger.getEffectiveLevel() == logging.INFO and not json else ())
+ (('--debug', '--show-rc') if logger.isEnabledFor(logging.DEBUG) and not json else ())
+ (('--json',) if json else ())
)

View file

@ -182,7 +182,7 @@ def parse_arguments(*arguments):
'--syslog-verbosity',
type=int,
choices=range(0, 3),
default=2,
default=1,
help='Display verbose progress to syslog (from none to lots: 0, 1, or 2)',
)
common_group.add_argument(

View file

@ -114,10 +114,18 @@ def configure_logging(console_log_level, syslog_log_level=None):
console_handler.setFormatter(logging.Formatter('%(message)s'))
console_handler.setLevel(console_log_level)
syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
syslog_handler.setFormatter(logging.Formatter('borgmatic: %(levelname)s %(message)s'))
syslog_handler.setLevel(syslog_log_level)
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'
logging.basicConfig(
level=min(console_log_level, syslog_log_level), handlers=(console_handler, syslog_handler)
)
if syslog_path:
syslog_handler = logging.handlers.SysLogHandler(address=syslog_path)
syslog_handler.setFormatter(logging.Formatter('borgmatic: %(levelname)s \ufeff%(message)s'))
syslog_handler.setLevel(syslog_log_level)
handlers = (console_handler, syslog_handler)
else:
handlers = (console_handler,)
logging.basicConfig(level=min(console_log_level, syslog_log_level), handlers=handlers)

View file

@ -33,12 +33,34 @@ borgmatic --list
borgmatic --info
```
## Logging
By default, borgmatic logs to a local syslog-compatible daemon if one is
present. You can customize the log level used for syslog logging with the
`--syslog-verbosity` flag, and this is independent from the console logging
`--verbosity` flag described above. For instance, to disable syslog logging
except for errors:
```bash
borgmatic --syslog-verbosity 0
```
Or to increase syslog logging to include debug spew:
```bash
borgmatic --syslog-verbosity 2
```
## Scripting borgmatic
To consume the output of borgmatic in other software, you can include an
optional `--json` flag with `--create`, `--list`, or `--info` to get the
output formatted as JSON.
Note that when you specify the `--json` flag, Borg's other non-JSON output is
suppressed so as not to interfere with the captured JSON. Also note that JSON
output only shows up at the console, and not in syslog.
## Related documentation

View file

@ -5,8 +5,7 @@ from borgmatic.borg import execute as module
def test_execute_command_calls_full_command():
full_command = ['foo', 'bar']
subprocess = flexmock(module.subprocess)
subprocess.should_receive('check_call').with_args(full_command).once()
flexmock(module).should_receive('execute_and_log_output').with_args(full_command).once()
output = module.execute_command(full_command)
@ -16,8 +15,7 @@ def test_execute_command_calls_full_command():
def test_execute_command_captures_output():
full_command = ['foo', 'bar']
expected_output = '[]'
subprocess = flexmock(module.subprocess)
subprocess.should_receive('check_output').with_args(full_command).and_return(
flexmock(module.subprocess).should_receive('check_output').with_args(full_command).and_return(
flexmock(decode=lambda: expected_output)
).once()