Procházet zdrojové kódy

improved tty-less progress reporting (1.4-maint)

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!
Thomas Waldmann před 1 týdnem
rodič
revize
3e2f560a61
2 změnil soubory, kde provedl 40 přidání a 6 odebrání
  1. 13 2
      src/borg/archive.py
  2. 27 4
      src/borg/testsuite/archive.py

+ 13 - 2
src/borg/archive.py

@@ -141,9 +141,12 @@ class Statistics:
     def csize_fmt(self):
         return format_file_size(self.csize, iec=self.iec)
 
+    PROGRESS_FMT = '{0.osize_fmt} O {0.csize_fmt} C {0.usize_fmt} D {0.nfiles} N '
+
     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:
@@ -158,10 +161,18 @@ class Statistics:
                 })
                 msg = json.dumps(data)
                 end = '\n'
+            elif not stream.isatty():
+                # if we don't output to a terminal, use normal linefeeds and assume line length is unlimited
+                if not final:
+                    msg = self.PROGRESS_FMT.format(self)
+                    msg += remove_surrogates(item.path) if item else ''
+                else:
+                    msg = ''
+                end = '\n'
             else:
                 columns, lines = get_terminal_size()
                 if not final:
-                    msg = '{0.osize_fmt} O {0.csize_fmt} C {0.usize_fmt} D {0.nfiles} N '.format(self)
+                    msg = self.PROGRESS_FMT.format(self)
                     path = remove_surrogates(item.path) if item else ''
                     space = columns - swidth(msg)
                     if space < 12:
@@ -172,7 +183,7 @@ class Statistics:
                 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):

+ 27 - 4
src/borg/testsuite/archive.py

@@ -32,27 +32,50 @@ def test_stats_basic(stats):
     assert stats.usize == 10
 
 
-def tests_stats_progress(stats, monkeypatch, columns=80):
+def tests_stats_progress_tty(stats, monkeypatch, columns=80):
+    class TTYStringIO(StringIO):
+        def isatty(self):
+            return True
+
     monkeypatch.setenv('COLUMNS', str(columns))
-    out = StringIO()
+    out = TTYStringIO()
     stats.show_progress(stream=out)
     s = '20 B O 10 B C 10 B D 0 N '
     buf = ' ' * (columns - len(s))
     assert out.getvalue() == s + buf + "\r"
 
-    out = StringIO()
+    out = TTYStringIO()
     stats.update(10**3, 0, unique=False)
     stats.show_progress(item=Item(path='foo'), final=False, stream=out)
     s = '1.02 kB O 10 B C 10 B D 0 N foo'
     buf = ' ' * (columns - len(s))
     assert out.getvalue() == s + buf + "\r"
-    out = StringIO()
+    out = TTYStringIO()
     stats.show_progress(item=Item(path='foo'*40), final=False, stream=out)
     s = '1.02 kB O 10 B C 10 B D 0 N foofoofoofoofoofoofoofo...oofoofoofoofoofoofoofoofoo'
     buf = ' ' * (columns - len(s))
     assert out.getvalue() == s + buf + "\r"
 
 
+def tests_stats_progress_file(stats, monkeypatch):
+    out = StringIO()
+    stats.show_progress(stream=out)
+    s = '20 B O 10 B C 10 B D 0 N '
+    assert out.getvalue() == s + "\n"
+
+    out = StringIO()
+    stats.update(10**3, 0, unique=False)
+    path = 'foo'
+    stats.show_progress(item=Item(path=path), final=False, stream=out)
+    s = f'1.02 kB O 10 B C 10 B D 0 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 10 B C 10 B D 0 N {path}'
+    assert out.getvalue() == s + "\n"
+
+
 def test_stats_format(stats):
     assert str(stats) == """\
 This archive:                   20 B                 10 B                 10 B"""