mirror of
https://github.com/borgbackup/borg.git
synced 2026-04-27 17:18:52 -04:00
improved tty-less progress reporting
Previously when running borg in a systemd service (and similar when piping to a file and co.), these problems occurred: - The carriage return both made it so that journald interpreted the output as binary, therefore not printing the text, while also not buffering correctly, so that log output was only available every once in a while in the form [40k blob data]. This can partially be worked around by using `journalctl -a` to view the logs, which at least prints the text, though only sporadically. - The path was getting truncated to a short length, since the default get_terminal_size returns a column width of 80, which isn't relevant when printing to e.g. journald. This commit fixes this by introducing a new code path for when stream is not a tty, which always prints the full paths and ends lines with a linefeed. This is based on unfinished PR #8939 by @infinisil, thanks for your suggestion! Forward port of PR #9055 to master.
This commit is contained in:
parent
160a966044
commit
8cbe4b8d48
3 changed files with 53 additions and 19 deletions
|
|
@ -149,6 +149,7 @@ Bytes sent to remote: {stats.tx_bytes}
|
|||
def show_progress(self, item=None, final=False, stream=None, dt=None):
|
||||
now = time.monotonic()
|
||||
if dt is None or now - self.last_progress > dt:
|
||||
stream = stream or sys.stderr
|
||||
self.last_progress = now
|
||||
if self.output_json:
|
||||
if not final:
|
||||
|
|
@ -160,6 +161,14 @@ Bytes sent to remote: {stats.tx_bytes}
|
|||
data.update({"time": time.time(), "type": "archive_progress", "finished": final})
|
||||
msg = json.dumps(data)
|
||||
end = "\n"
|
||||
elif not stream.isatty():
|
||||
# Non-TTY output: use normal linefeeds and do not truncate the path.
|
||||
if not final:
|
||||
msg = "{0.osize_fmt} O {0.usize_fmt} U {0.nfiles} N ".format(self)
|
||||
msg += remove_surrogates(item.path) if item else ""
|
||||
else:
|
||||
msg = ""
|
||||
end = "\n"
|
||||
else:
|
||||
columns, lines = get_terminal_size()
|
||||
if not final:
|
||||
|
|
@ -174,7 +183,7 @@ Bytes sent to remote: {stats.tx_bytes}
|
|||
else:
|
||||
msg = " " * columns
|
||||
end = "\r"
|
||||
print(msg, end=end, file=stream or sys.stderr, flush=True)
|
||||
print(msg, end=end, file=stream, flush=True)
|
||||
|
||||
|
||||
def is_special(mode):
|
||||
|
|
|
|||
|
|
@ -33,26 +33,51 @@ def test_stats_basic(stats):
|
|||
assert stats.usize == 20
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"item_path, update_size, expected_output",
|
||||
[
|
||||
("", 0, "20 B O 20 B U 1 N "), # test unchanged 'stats' fixture
|
||||
("foo", 10**3, "1.02 kB O 20 B U 1 N foo"), # test updated original size and set item path
|
||||
# test long item path which exceeds 80 characters
|
||||
("foo" * 40, 10**3, "1.02 kB O 20 B U 1 N foofoofoofoofoofoofoofoofo...foofoofoofoofoofoofoofoofoofoo"),
|
||||
],
|
||||
)
|
||||
def test_stats_progress(item_path, update_size, expected_output, stats, monkeypatch, columns=80):
|
||||
monkeypatch.setenv("COLUMNS", str(columns))
|
||||
out = StringIO()
|
||||
item = Item(path=item_path) if item_path else None
|
||||
s = expected_output
|
||||
def test_stats_progress_tty(stats, monkeypatch, columns=80):
|
||||
class TTYStringIO(StringIO):
|
||||
def isatty(self):
|
||||
return True
|
||||
|
||||
stats.update(update_size, unique=False)
|
||||
stats.show_progress(item=item, stream=out)
|
||||
monkeypatch.setenv("COLUMNS", str(columns))
|
||||
out = TTYStringIO()
|
||||
stats.show_progress(stream=out)
|
||||
s = "20 B O 20 B U 1 N "
|
||||
buf = " " * (columns - len(s))
|
||||
assert out.getvalue() == s + buf + "\r"
|
||||
|
||||
out = TTYStringIO()
|
||||
stats.update(10**3, unique=False)
|
||||
stats.show_progress(item=Item(path="foo"), final=False, stream=out)
|
||||
s = "1.02 kB O 20 B U 1 N foo"
|
||||
buf = " " * (columns - len(s))
|
||||
assert out.getvalue() == s + buf + "\r"
|
||||
|
||||
out = TTYStringIO()
|
||||
stats.show_progress(item=Item(path="foo" * 40), final=False, stream=out)
|
||||
s = "1.02 kB O 20 B U 1 N foofoofoofoofoofoofoofoofo...foofoofoofoofoofoofoofoofoofoo"
|
||||
buf = " " * (columns - len(s))
|
||||
assert out.getvalue() == s + buf + "\r"
|
||||
|
||||
|
||||
def test_stats_progress_file(stats, monkeypatch):
|
||||
out = StringIO()
|
||||
stats.show_progress(stream=out)
|
||||
s = "20 B O 20 B U 1 N "
|
||||
assert out.getvalue() == s + "\n"
|
||||
|
||||
out = StringIO()
|
||||
stats.update(10**3, unique=False)
|
||||
path = "foo"
|
||||
stats.show_progress(item=Item(path=path), final=False, stream=out)
|
||||
s = f"1.02 kB O 20 B U 1 N {path}"
|
||||
assert out.getvalue() == s + "\n"
|
||||
|
||||
out = StringIO()
|
||||
path = "foo" * 40
|
||||
stats.show_progress(item=Item(path=path), final=False, stream=out)
|
||||
s = f"1.02 kB O 20 B U 1 N {path}"
|
||||
assert out.getvalue() == s + "\n"
|
||||
|
||||
|
||||
def test_stats_format(stats):
|
||||
assert (
|
||||
|
|
|
|||
|
|
@ -634,7 +634,7 @@ def test_progress_on(archivers, request):
|
|||
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
||||
cmd(archiver, "repo-create", RK_ENCRYPTION)
|
||||
output = cmd(archiver, "create", "test4", "input", "--progress")
|
||||
assert "\r" in output
|
||||
assert "0 B O 0 B U 0 N" in output
|
||||
|
||||
|
||||
def test_progress_off(archivers, request):
|
||||
|
|
@ -642,7 +642,7 @@ def test_progress_off(archivers, request):
|
|||
create_regular_file(archiver.input_path, "file1", size=1024 * 80)
|
||||
cmd(archiver, "repo-create", RK_ENCRYPTION)
|
||||
output = cmd(archiver, "create", "test5", "input")
|
||||
assert "\r" not in output
|
||||
assert "0 B O 0 B U 0 N" not in output
|
||||
|
||||
|
||||
def test_file_status(archivers, request):
|
||||
|
|
|
|||
Loading…
Reference in a new issue