Only log to syslog when run from a non-interactive console (e.g. a cron job). Related to #197.

This commit is contained in:
Dan Helfman 2019-06-27 14:41:21 -07:00
parent 032d4adee3
commit 90595e9c18
7 changed files with 62 additions and 14 deletions

3
NEWS
View file

@ -1,4 +1,5 @@
1.3.12.dev0 1.3.12
* Only log to syslog when run from a non-interactive console (e.g. a cron job).
* Remove unicode byte order mark from syslog output so it doesn't show up as a literal in rsyslog * Remove unicode byte order mark from syslog output so it doesn't show up as a literal in rsyslog
output. See discussion on #197. output. See discussion on #197.

View file

@ -108,7 +108,7 @@ def parse_arguments(*unparsed_arguments):
type=int, type=int,
choices=range(0, 3), choices=range(0, 3),
default=0, default=0,
help='Display verbose progress to syslog (from none to lots: 0, 1, or 2)', help='Display verbose progress to syslog (from none to lots: 0, 1, or 2). Ignored when console is interactive',
) )
global_group.add_argument( global_group.add_argument(
'--version', '--version',

View file

@ -21,6 +21,14 @@ def to_bool(arg):
return False return False
def interactive_console():
'''
Return whether the current console is "interactive". Meaning: Capable of
user input and not just something like a cron job.
'''
return sys.stdout.isatty() and os.environ.get('TERM') != 'dumb'
def should_do_markup(no_color, configs): def should_do_markup(no_color, configs):
''' '''
Given the value of the command-line no-color argument, and a dict of configuration filename to Given the value of the command-line no-color argument, and a dict of configuration filename to
@ -37,7 +45,7 @@ def should_do_markup(no_color, configs):
if py_colors is not None: if py_colors is not None:
return to_bool(py_colors) return to_bool(py_colors)
return sys.stdout.isatty() and os.environ.get('TERM') != 'dumb' return interactive_console()
LOG_LEVEL_TO_COLOR = { LOG_LEVEL_TO_COLOR = {
@ -82,7 +90,7 @@ def configure_logging(console_log_level, syslog_log_level=None):
elif os.path.exists('/var/run/syslog'): elif os.path.exists('/var/run/syslog'):
syslog_path = '/var/run/syslog' syslog_path = '/var/run/syslog'
if syslog_path: 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)

View file

@ -39,9 +39,10 @@ borgmatic info
## Logging ## Logging
By default, borgmatic logs to a local syslog-compatible daemon if one is By default, borgmatic logs to a local syslog-compatible daemon if one is
present. Where those logs show up depends on your particular system. If you're present and borgmatic is running in a non-interactive console. Where those
using systemd, try running `journalctl -xe`. Otherwise, try viewing logs show up depends on your particular system. If you're using systemd, try
`/var/log/syslog` or similiar. running `journalctl -xe`. Otherwise, try viewing `/var/log/syslog` or
similiar.
You can customize the log level used for syslog logging with the You can customize the log level used for syslog logging with the
`--syslog-verbosity` flag, and this is independent from the console logging `--syslog-verbosity` flag, and this is independent from the console logging

View file

@ -38,8 +38,8 @@ hooks:
## Hook output ## Hook output
Any output produced by your hooks shows up both at the console and in syslog. Any output produced by your hooks shows up both at the console and in syslog
For more information, read about <a (when run in a non-interactive console). For more information, read about <a
href="https://torsion.org/borgmatic/docs/how-to/inspect-your-backups.md">inspecting href="https://torsion.org/borgmatic/docs/how-to/inspect-your-backups.md">inspecting
your backups</a>. your backups</a>.

View file

@ -1,6 +1,6 @@
from setuptools import find_packages, setup from setuptools import find_packages, setup
VERSION = '1.3.12.dev0' VERSION = '1.3.12'
setup( setup(

View file

@ -20,6 +20,29 @@ def test_to_bool_passes_none_through():
assert module.to_bool(None) is None assert module.to_bool(None) is None
def test_interactive_console_false_when_not_isatty(capsys):
with capsys.disabled():
flexmock(module.sys.stdout).should_receive('isatty').and_return(False)
assert module.interactive_console() is False
def test_interactive_console_false_when_TERM_is_dumb(capsys):
with capsys.disabled():
flexmock(module.sys.stdout).should_receive('isatty').and_return(True)
flexmock(module.os.environ).should_receive('get').with_args('TERM').and_return('dumb')
assert module.interactive_console() is False
def test_interactive_console_true_when_isatty_and_TERM_is_not_dumb(capsys):
with capsys.disabled():
flexmock(module.sys.stdout).should_receive('isatty').and_return(True)
flexmock(module.os.environ).should_receive('get').with_args('TERM').and_return('smart')
assert module.interactive_console() is True
def test_should_do_markup_respects_no_color_value(): def test_should_do_markup_respects_no_color_value():
assert module.should_do_markup(no_color=True, configs={}) is False assert module.should_do_markup(no_color=True, configs={}) is False
@ -75,15 +98,17 @@ def test_should_do_markup_prefers_no_color_value_to_PY_COLORS():
assert module.should_do_markup(no_color=True, configs={}) is False assert module.should_do_markup(no_color=True, configs={}) is False
def test_should_do_markup_respects_stdout_tty_value(): def test_should_do_markup_respects_interactive_console_value():
flexmock(module.os.environ).should_receive('get').and_return(None) flexmock(module.os.environ).should_receive('get').and_return(None)
flexmock(module).should_receive('interactive_console').and_return(True)
assert module.should_do_markup(no_color=False, configs={}) is False assert module.should_do_markup(no_color=False, configs={}) is True
def test_should_do_markup_prefers_PY_COLORS_to_stdout_tty_value(): def test_should_do_markup_prefers_PY_COLORS_to_interactive_console_value():
flexmock(module.os.environ).should_receive('get').and_return('True') flexmock(module.os.environ).should_receive('get').and_return('True')
flexmock(module).should_receive('to_bool').and_return(True) flexmock(module).should_receive('to_bool').and_return(True)
flexmock(module).should_receive('interactive_console').and_return(False)
assert module.should_do_markup(no_color=False, configs={}) is True assert module.should_do_markup(no_color=False, configs={}) is True
@ -108,11 +133,11 @@ def test_color_text_without_color_does_not_raise():
def test_configure_logging_probes_for_log_socket_on_linux(): def test_configure_logging_probes_for_log_socket_on_linux():
flexmock(module).should_receive('Console_color_formatter') flexmock(module).should_receive('Console_color_formatter')
flexmock(module).should_receive('interactive_console').and_return(False)
flexmock(module.logging).should_receive('basicConfig').with_args( flexmock(module.logging).should_receive('basicConfig').with_args(
level=logging.INFO, handlers=tuple level=logging.INFO, handlers=tuple
) )
flexmock(module.os.path).should_receive('exists').with_args('/dev/log').and_return(True) flexmock(module.os.path).should_receive('exists').with_args('/dev/log').and_return(True)
flexmock(module.os.path).should_receive('exists').with_args('/var/run/syslog').and_return(False)
syslog_handler = logging.handlers.SysLogHandler() syslog_handler = logging.handlers.SysLogHandler()
flexmock(module.logging.handlers).should_receive('SysLogHandler').with_args( flexmock(module.logging.handlers).should_receive('SysLogHandler').with_args(
address='/dev/log' address='/dev/log'
@ -123,6 +148,7 @@ def test_configure_logging_probes_for_log_socket_on_linux():
def test_configure_logging_probes_for_log_socket_on_macos(): def test_configure_logging_probes_for_log_socket_on_macos():
flexmock(module).should_receive('Console_color_formatter') flexmock(module).should_receive('Console_color_formatter')
flexmock(module).should_receive('interactive_console').and_return(False)
flexmock(module.logging).should_receive('basicConfig').with_args( flexmock(module.logging).should_receive('basicConfig').with_args(
level=logging.INFO, handlers=tuple level=logging.INFO, handlers=tuple
) )
@ -155,3 +181,15 @@ def test_configure_logging_skips_syslog_if_not_found():
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_skips_syslog_if_interactive_console():
flexmock(module).should_receive('Console_color_formatter')
flexmock(module).should_receive('interactive_console').and_return(True)
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()
module.configure_logging(console_log_level=logging.INFO)