From df2ee94613fbc998793984eb0ce826d4bcb9ae15 Mon Sep 17 00:00:00 2001 From: mm Date: Mon, 14 Sep 2026 12:08:48 +0200 Subject: [PATCH] =?UTF-8?q?fix(survey):=20Fortschritt=20sichtbar=20?= =?UTF-8?q?=E2=80=94=20der=20leere=20bestand.txt=20war=20kein=20Fehler,=20?= =?UTF-8?q?sondern=20ein=20laufender=20Scan?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BEFUND (x22, gemessen und reproduziert): Der Aufruf war 'python3 apache-log-survey.py 2>&1 | tee bestand.txt'. Das Skript gab ALLES erst nach der Scan-Schleife aus (alle print() standen dahinter), und Python puffert stdout in eine PIPE blockweise (4-8 KB). Die Gesamtausgabe ist aber nur ~1 KB -> sie landete erst beim Prozessende in der Datei. Messung mit 2 Mio Zeilen (140 MB): mit tee: nach 5 s = 0 Bytes, fertig nach 41 s -> dann 1028 Bytes ohne Pipe: nach 1 s = 0 Bytes python3 -u: nach 1 s = 0 Bytes => Die Datei war leer, weil der Scan LIEF. Auf einem KeyHelp-Host mit tausenden Logdateien dauert das Minuten und liest sich wie ein Haenger oder Absturz. FIX (drei Massnahmen, damit das nie wieder passiert): 1. sys.stdout.reconfigure(line_buffering=True) — jede Zeile geht sofort raus, auch durch eine Pipe. 2. Startbanner VOR dem Scan: Anzahl und Gesamtgroesse der Dateien. 3. Fortschrittszeile pro Datei (Nummer/Gesamtzahl, Zeilen, Groesse, Pfad) plus eine Zeile fuer nicht lesbare Dateien. Verifiziert mit 1,4 Mio Zeilen durch eine Pipe: nach 2 s steht das Banner, drei Fortschrittszeilen erscheinen waehrend des Scans. Gegenprobe mit dem alten Code: nach 3 s 16 Bytes, alles erst am Ende. --- scripts/apache-log-survey.py | 71 +++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/scripts/apache-log-survey.py b/scripts/apache-log-survey.py index 15056de..9bc83ad 100644 --- a/scripts/apache-log-survey.py +++ b/scripts/apache-log-survey.py @@ -17,6 +17,7 @@ import glob import os import re import sys +import time from collections import Counter, defaultdict from datetime import datetime, timezone @@ -68,11 +69,54 @@ def parse_ts(raw): def main(): + # ---------------------------------------------------------------------- + # FORTSCHRITT SICHTBAR MACHEN — sonst sieht der Scan wie ein Haenger aus. + # + # Real passiert (x22): der Aufruf war `python3 survey.py 2>&1 | tee bestand.txt`. + # Das Skript gibt ALLES erst am Ende aus (alle print() stehen hinter der + # Scan-Schleife), und Python puffert stdout in eine PIPE blockweise (4-8 KB, + # nicht zeilenweise). Die Gesamtausgabe ist aber nur ~1 KB — sie landet also + # erst beim Prozessende in der Datei. Bei 2 Mio Zeilen gemessen: 41 s lang + # 0 Bytes, dann alles auf einmal. Auf einem KeyHelp-Host mit tausenden + # Logdateien dauert das Minuten und liest sich wie ein Absturz. + # + # Drei Gegenmaßnahmen: + # 1. Zeilenpufferung erzwingen (reconfigure), damit jede Zeile sofort raus ist + # 2. einen Startbanner VOR dem Scan ausgeben + # 3. pro Datei eine Fortschrittszeile (mit flush) + # ---------------------------------------------------------------------- + try: + sys.stdout.reconfigure(line_buffering=True) + sys.stderr.reconfigure(line_buffering=True) + except (AttributeError, ValueError): + pass # aeltere Python-Version: dann eben gepuffert + files = sorted({f for pat in PATTERNS for f in glob.glob(pat)}) if not files: print("Keine Apache-Logs gefunden. Laeuft Apache? (apache2ctl -v)") return 1 + total_bytes = 0 + for _f in files: + try: + total_bytes += os.path.getsize(_f) + except OSError: + pass + + def human(n): + for u in ("B", "KB", "MB", "GB"): + if n < 1024: + return f"{n:.1f} {u}" + n /= 1024 + return f"{n:.1f} TB" + + print("=" * 72) + print("APACHE-LOG BESTANDSAUFNAHME") + print("=" * 72) + print(f"Scanne {len(files)} Dateien ({human(total_bytes)} auf Platte) …") + print("(grosse Bestaende dauern; dieser Fortschritt laeuft mit)") + print() + total_lines = 0 total_parsed = 0 total_err = 0 @@ -88,14 +132,17 @@ def main(): day_domain = set() per_file = [] - for path in files: + t0 = time.time() + for _idx, path in enumerate(files, 1): fh = open_maybe_gz(path) if fh is None: + print(f" [{_idx}/{len(files)}] NICHT LESBAR: {path}", flush=True) continue try: - bytes_on_disk += os.path.getsize(path) + _sz = os.path.getsize(path) + bytes_on_disk += _sz except OSError: - pass + _sz = 0 domain = os.path.basename(os.path.dirname(path)) or os.path.basename(path) n_lines = n_ok = n_err = 0 f_first = f_last = None @@ -137,18 +184,14 @@ def main(): total_parsed += n_ok total_err += n_err per_file.append((path, n_lines, n_ok, n_err, f_first, f_last)) + # Fortschritt pro Datei — und bei grossen Dateien auch bei jeder Zeile + # die Marke, damit lange Einzeldateien nicht stumm wirken. + print(f" [{_idx}/{len(files)}] {n_lines:>10,} Zeilen " + f"{human(_sz):>9} {path}", flush=True) - def human(n): - for u in ("B", "KB", "MB", "GB"): - if n < 1024: - return f"{n:.1f} {u}" - n /= 1024 - return f"{n:.1f} TB" - - print("=" * 72) - print("APACHE-LOG BESTANDSAUFNAHME") - print("=" * 72) - print(f"Dateien: {len(files)} ({human(bytes_on_disk)} auf Platte)") + print() + print(f"Scan fertig in {time.time() - t0:.1f} s.") + print() print(f"Zeilen total: {total_lines:,}") print(f" Zugriffe: {total_parsed:,}") print(f" Fehlerlog: {total_err:,}")