Browse Source

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

tags/1.3.12
Dan Helfman 1 year ago
parent
commit
90595e9c18
7 changed files with 62 additions and 14 deletions
  1. +2
    -1
      NEWS
  2. +1
    -1
      borgmatic/commands/arguments.py
  3. +10
    -2
      borgmatic/logger.py
  4. +4
    -3
      docs/how-to/inspect-your-backups.md
  5. +2
    -2
      docs/how-to/run-preparation-steps-before-backups.md
  6. +1
    -1
      setup.py
  7. +42
    -4
      tests/unit/test_logger.py

+ 2
- 1
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
output. See discussion on #197.



+ 1
- 1
borgmatic/commands/arguments.py View File

@@ -108,7 +108,7 @@ def parse_arguments(*unparsed_arguments):
type=int,
choices=range(0, 3),
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(
'--version',


+ 10
- 2
borgmatic/logger.py View File

@@ -21,6 +21,14 @@ def to_bool(arg):
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):
'''
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:
return to_bool(py_colors)

return sys.stdout.isatty() and os.environ.get('TERM') != 'dumb'
return interactive_console()


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'):
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.setFormatter(logging.Formatter('borgmatic: %(levelname)s %(message)s'))
syslog_handler.setLevel(syslog_log_level)


+ 4
- 3
docs/how-to/inspect-your-backups.md View File

@@ -39,9 +39,10 @@ borgmatic info
## Logging

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
using systemd, try running `journalctl -xe`. Otherwise, try viewing
`/var/log/syslog` or similiar.
present and borgmatic is running in a non-interactive console. Where those
logs show up depends on your particular system. If you're using systemd, try
running `journalctl -xe`. Otherwise, try viewing `/var/log/syslog` or
similiar.

You can customize the log level used for syslog logging with the
`--syslog-verbosity` flag, and this is independent from the console logging


+ 2
- 2
docs/how-to/run-preparation-steps-before-backups.md View File

@@ -38,8 +38,8 @@ hooks:

## Hook output

Any output produced by your hooks shows up both at the console and in syslog.
For more information, read about <a
Any output produced by your hooks shows up both at the console and in syslog
(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
your backups</a>.



+ 1
- 1
setup.py View File

@@ -1,6 +1,6 @@
from setuptools import find_packages, setup

VERSION = '1.3.12.dev0'
VERSION = '1.3.12'


setup(


+ 42
- 4
tests/unit/test_logger.py View File

@@ -20,6 +20,29 @@ def test_to_bool_passes_none_through():
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():
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


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

@@ -108,11 +133,11 @@ def test_color_text_without_color_does_not_raise():

def test_configure_logging_probes_for_log_socket_on_linux():
flexmock(module).should_receive('Console_color_formatter')
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.os.path).should_receive('exists').with_args('/var/run/syslog').and_return(False)
syslog_handler = logging.handlers.SysLogHandler()
flexmock(module.logging.handlers).should_receive('SysLogHandler').with_args(
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():
flexmock(module).should_receive('Console_color_formatter')
flexmock(module).should_receive('interactive_console').and_return(False)
flexmock(module.logging).should_receive('basicConfig').with_args(
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()

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)

Loading…
Cancel
Save