Add rotating session reports and log rotation

- on_exit() now generates a session report (txt + json) in log/runs/
- Log file rotates daily, keeps 7 days (TimedRotatingFileHandler)
- Added log/runs directory creation in main.py
This commit is contained in:
alex
2026-05-28 19:07:42 +02:00
parent 9491169549
commit e4c201bf2d
3 changed files with 129 additions and 6 deletions
+110
View File
@@ -507,6 +507,116 @@ class GameStats:
with open(file=f"log/stats/{self._mini_stats_filename}", mode="w+", encoding="utf-8") as f:
json.dump(payload, f, indent=2)
def _save_session_report(self):
"""Save a session report to log/runs/ with date-based rotation."""
os.makedirs("log/runs", exist_ok=True)
elapsed_time = time.time() - self._start_time
elapsed_time_str = hms(elapsed_time)
good_games_count = self._game_counter - self._runs_failed
good_games_time = elapsed_time - self._failed_game_time
if good_games_count == 0:
good_games_count = 1
avg_length = good_games_time / float(good_games_count)
avg_length_str = hms(avg_length)
curr_lvl = get_level(self._current_lvl) if self._current_lvl > 0 else {"lvl": 0, "exp": 0, "xp_to_next": 0}
lines = []
lines.append("=" * 50)
lines.append("BOTTY SESSION REPORT")
lines.append("=" * 50)
lines.append(f"Date: {time.strftime('%Y-%m-%d %H:%M:%S')}")
lines.append(f"Version: {__version__}")
lines.append(f"Session: {elapsed_time_str}")
lines.append(f"Games: {self._game_counter}")
lines.append(f"Failed: {self._runs_failed}")
lines.append(f"Avg Game: {avg_length_str}")
lines.append(f"Level: {curr_lvl['lvl'] if curr_lvl['lvl'] > 0 else 'n/a'}")
lines.append(f"Runes: {self._location_stats['totals']['runes']}")
lines.append(f"Est FG: {self._location_stats['totals']['fg_estimated']:.1f}")
lines.append(f"Gold Stashed (Session): {self._format_gold(self._location_stats['totals']['gold_stashed'])}")
lines.append(f"Gold In Stash: {self._format_gold(self._gold_in_stash)}")
lines.append(f"Deaths: {self._death_counter}")
lines.append(f"Chickens: {self._chicken_counter}")
lines.append(f"Merc Deaths:{self._merc_death_counter}")
if curr_lvl["lvl"] > 0 and curr_lvl["lvl"] < 99 and self._current_exp > 0:
gained_exp = self._current_exp - self._starting_exp
if good_games_time > 0:
exp_per_hour = round((gained_exp / good_games_time) * 3600, 1)
lines.append(f"XP Gained: {gained_exp:,}")
lines.append(f"XP/Hour: {exp_per_hour:,}")
lines.append("")
lines.append("Per-Run Stats:")
lines.append(f"{'Run':<20} {'Items':>6} {'Chick':>5} {'Death':>5} {'MercD':>5} {'Fail':>4} {'Gold':>14}")
lines.append("-" * 60)
for location in self._location_stats:
if location == "totals":
continue
stats = self._location_stats[location]
lines.append(f"{location:<20} {len(stats['items']):>6} {stats['chickens']:>5} {stats['deaths']:>5} {stats['merc_deaths']:>5} {stats['failed_runs']:>4} {self._format_gold(stats.get('gold_stashed', 0)):>14}")
totals = self._location_stats["totals"]
total_items = sum(len(self._location_stats[loc]["items"]) for loc in self._location_stats if loc != "totals")
lines.append("-" * 60)
lines.append(f"{'TOTAL':<20} {total_items:>6} {totals['chickens']:>5} {totals['deaths']:>5} {totals['merc_deaths']:>5} {totals['failed_runs']:>4} {self._format_gold(totals['gold_stashed']):>14}")
if len(self._fg_item_totals) > 0:
lines.append("")
lines.append("FG Tracker (Session Totals):")
sorted_fg = sorted(self._fg_item_totals.items(), key=lambda x: x[1], reverse=True)
for fg_name, fg_total in sorted_fg:
count = self._fg_item_counts.get(fg_name, 0)
lines.append(f" {fg_name}: {count}x, {fg_total:.1f} fg")
lines.append("")
lines.append("=" * 50)
report_content = "\n".join(lines)
# Save with date-based filename
report_filename = f"run_{time.strftime('%Y%m%d_%H%M%S')}.txt"
report_path = f"log/runs/{report_filename}"
with open(report_path, "w", encoding="utf-8") as f:
f.write(report_content)
Logger.info(f"Session report saved to {report_path}")
# Also save a JSON version for easy parsing
json_payload = {
"ts": time.strftime("%Y-%m-%dT%H:%M:%S"),
"version": __version__,
"session_seconds": round(elapsed_time, 2),
"games": self._game_counter,
"runs_failed": self._runs_failed,
"avg_game_length": round(avg_length, 2),
"current_level": self._current_lvl if self._current_lvl > 0 else None,
"runes_kept": self._location_stats["totals"]["runes"],
"estimated_fg": round(self._location_stats["totals"]["fg_estimated"], 1),
"gold_stashed_total": self._gold_stashed_total,
"gold_in_stash": self._gold_in_stash,
"deaths": self._death_counter,
"chickens": self._chicken_counter,
"merc_deaths": self._merc_death_counter,
"xp_gained": self._current_exp - self._starting_exp if self._current_exp > 0 else 0,
"locations": {}
}
for location in self._location_stats:
if location == "totals":
continue
stats = self._location_stats[location]
json_payload["locations"][location] = {
"items": len(stats["items"]),
"chickens": stats["chickens"],
"deaths": stats["deaths"],
"merc_deaths": stats["merc_deaths"],
"failed_runs": stats["failed_runs"],
"gold_stashed": stats.get("gold_stashed", 0)
}
json_path = f"log/runs/{report_filename.replace('.txt', '.json')}"
with open(json_path, "w", encoding="utf-8") as f:
json.dump(json_payload, f, indent=2)
if __name__ == "__main__":
game_stats = GameStats()
+9 -2
View File
@@ -5,6 +5,7 @@ import sys
import threading
import traceback
import warnings
from logging.handlers import TimedRotatingFileHandler
from version import __version__
from colorama import Fore, Back, Style, init
import time
@@ -118,8 +119,14 @@ class Logger:
Logger.console_handler = logging.StreamHandler(sys.stdout)
Logger.console_handler.setLevel(Logger._logger_level)
# Setup the file handler
Logger.file_handler = logging.FileHandler(Logger._current_log_file_path, 'a')
# Setup the file handler (rotating — daily, keeps 7 days)
Logger.file_handler = TimedRotatingFileHandler(
Logger._current_log_file_path,
when='midnight',
interval=1,
backupCount=7,
encoding='utf-8'
)
Logger.file_handler.setLevel(Logger._logger_level)
# Optionally add a formatter
+10 -4
View File
@@ -52,8 +52,14 @@ def start_or_stop_graphic_debugger(controllers: Controllers):
controllers.game.stop()
controllers.debugger.start()
def on_exit():
Logger.info(f'Force Exit')
def on_exit(controllers: Controllers):
Logger.info('Force Exit')
# Generate session report before exiting
if controllers.game.game_stats:
try:
controllers.game.game_stats._save_session_report()
except Exception as e:
Logger.warning(f"Failed to save session report: {e}")
screen.stop_detecting_window()
restore_d2r_window_visibility()
os._exit(1)
@@ -73,7 +79,7 @@ def startup_checks():
def main():
# Create folder for debug screenshots if they dont exist yet
for dir_name in ["log", "log/stats", "log/screenshots", "log/screenshots/info", "log/screenshots/items", "log/screenshots/pickit", "log/screenshots/generated"]:
for dir_name in ["log", "log/stats", "log/runs", "log/screenshots", "log/screenshots/info", "log/screenshots/items", "log/screenshots/pickit", "log/screenshots/generated"]:
os.makedirs(dir_name, exist_ok=True)
controllers = Controllers(
@@ -125,7 +131,7 @@ def main():
keyboard.add_hotkey(Config().advanced_options['restore_settings_from_backup_key'], lambda: restore_settings_from_backup())
keyboard.add_hotkey(Config().advanced_options['settings_backup_key'], lambda: backup_settings())
keyboard.add_hotkey(Config().advanced_options['resume_key'], lambda: start_or_pause_bot(controllers))
keyboard.add_hotkey(Config().advanced_options["exit_key"], lambda: on_exit())
keyboard.add_hotkey(Config().advanced_options["exit_key"], lambda: on_exit(controllers))
keyboard.wait()