feat(logging): add diagnostic crash log subsystem#5
Open
f1rep0wr wants to merge 1 commit into
Open
Conversation
08fd622 to
faf43c7
Compare
The launcher ships as a PyInstaller --windowed --onefile exe, so every
existing print(...) writes to a stdout that doesn't exist and uncaught
exceptions on Qt threads or in discord_game_sdk.dll die silently. This
adds a stdlib logging-based subsystem so end-user bug reports become
actionable.
What's new:
- logger.py — RotatingFileHandler at data/logs/launcher.log (1 MB x 5),
sys.excepthook + threading.excepthook + faulthandler.enable, session
preamble with version/platform/Python/argv/CWD, 3-tier path fallback
(cwd -> %LOCALAPPDATA% -> %TEMP%), atexit cleanup, RLock-guarded
idempotency. A username-redaction filter runs on every record so log
files are shareable publicly (Windows username never appears, even
inside exception tracebacks).
Refactors:
- functions.py / mech_editor.py / discord_rpc.py / run.py — every
print(...) replaced with structured log.* calls. log.exception()
inside except blocks so the full traceback lands on disk.
Filesystem-sourced strings (filenames, paths) formatted with %r to
prevent log-injection via planted filenames.
- show_error_box (functions.py) and prompt_for_new_game_folder — the
_error_count increment is now only bumped after the Tk dialog
actually shows, preserving the invariant InjectionThread relies on
even when logging or Tk itself fails.
- run.py — adds an "Open Log File" IconButton that opens launcher.log
in Notepad (not Explorer; spawning Explorer from an elevated process
is a documented UAC escalation surface). Adds a "Session end" log
line in closeEvent and removes a pre-existing duplicate
rpc_manager.stop() call.
- constants.py — adds LAUNCHER_VERSION constant ("1.0.8") used by the
log preamble; must be kept in sync with version.txt's filevers tuple
when bumping.
faf43c7 to
d179c0a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The launcher ships as a PyInstaller
--windowed --onefileexe, so every existingprint(...)writes to a stdout that doesn't exist and uncaught exceptions on Qt threads or indiscord_game_sdk.dlldie silently. This PR adds a stdlib-logging-based diagnostic subsystem so end-user bug reports become actionable, and migrates every existingprintsite to it.logger.py—RotatingFileHandleratdata/logs/launcher.log(1 MB × 5),sys.excepthook+threading.excepthook+faulthandler.enable, session preamble (version/Python/platform/CWD/argv), 3-tier path fallback (<cwd>/data/logs→%LOCALAPPDATA%→%TEMP%),atexitcleanup of the faulthandler FD,RLock-guarded idempotency. A username-redaction filter runs on every log record so the file is shareable publicly.print → loggingmigration acrossfunctions.py/mech_editor.py/discord_rpc.py/run.py.log.exception(...)insideexceptblocks so the full traceback lands on disk. Filesystem-sourced strings (filenames, paths) formatted with%rto defeat log injection via planted filenames.show_error_boxandprompt_for_new_game_folder—_error_countincrement moved adjacent to the dialog so a logging or Tk failure can never bump the counter without the user actually seeing the dialog (preserves the invariantInjectionThreadatrun.py:29relies on).run.py— adds an "Open Log File"IconButtonthat openslauncher.login Notepad (deliberately not Explorer — spawning Explorer from a--uac-adminprocess is a documented UAC escalation surface). Adds aSession end (close).log line incloseEventand removes a pre-existing duplicateself.rpc_manager.stop()call at the oldrun.py:307.constants.py— newLAUNCHER_VERSION = "1.0.8"constant consumed by the log preamble; must stay in sync withversion.txt'sfileverstuple when bumping.What this looks like for end users
1. A new icon under the Discord and Info icons. Same
IconButtonwidget already used for the Info button. Placed at(10, 95)directly below it. Ifdata/assets/log.pngexists it's used; otherwise the widget renders its existing fallback (a primary-green bordered circle with a bold "L" letter, matching how the Info button falls back to "i" today). Tooltip on hover: "Open diagnostic log".2. Clicking it opens
launcher.login Notepad. Two branches in_open_log_file(run.py:158-179):subprocess.Popen(["notepad.exe", log_path]). The user sees Notepad open with the full log.QMessageBox.informationsaying "The diagnostic log has not been created yet, or the launcher failed to initialize logging. Check%TEMP%\e710-launcher-boot.logfor clues if this is unexpected." This is deliberatelyQMessageBox.information, notshow_error_box, because using the latter would bump_error_countfrom a benign UI affordance and taint the next CONNECT cycle.3. Every existing error dialog now ends with a "Diagnostic logs folder:" footer. This goes through the modified
show_error_box(functions.py). The footer is appended via a lazyfrom logger import get_log_dirso the dialog still renders even if logging is uninitialized (in which case no footer is appended — graceful degradation). End users filing a bug now have a path to copy + paste from without being told where to look.4. Log file content is publicly shareable. The username-redaction filter rewrites every record (message, args, and materialized exception tracebacks) so paths like
C:\Users\<actual-name>\AppData\Local\...becomeC:\Users\<user>\AppData\Local\...before they hit disk. Users can attachlauncher.logdirectly to a Discord message or GitHub issue without scrubbing it first.faulthandler.log(native crash file) only contains the "armed at" timestamp markers under normal operation — no memory addresses or DLL paths leak.Test plan
python -m py_compileon every touched filesetup_logging()called twice in one process keepslen(root.handlers) == 1; concurrent calls from 4 threads also produce 1 handler with noPermissionErrordata/logs/launcher.logread-only triggers fallback to%LOCALAPPDATA%\E-710 Launcher\logslauncher.logafter a session + an artificially raisedPermissionErrorwith a home-directory path — zero occurrences of the actual Windows username_error_countinvariant: monkey-patchedtk.Tkto raise, calledprompt_for_new_game_folder, confirmed counter stays at 0 when no dialog could be shownlog.exception(...)writes a traceback; raising in a worker thread triggersthreading.excepthook; preamble + all log levels land in the fileshow_antivirus_warningon a missingmsvcp140.dll, the error dialog now ends with "Diagnostic logs folder:\nC:\projects\cgw-launcher\data\logs" — confirming the new footer is wired correctly through the lazylogger.get_log_dirimport