-
-
Notifications
You must be signed in to change notification settings - Fork 60
Crash Reporting
Sucrose's multi-process design means a crash in one component should never take down the wallpaper or the tray. To make that work, every Sucrose executable installs a full set of global exception handlers, and when one fires the failure is routed to a dedicated crash handler process (Sucrose.Watchdog.exe) that writes a structured report, kills the faulting process, and optionally shows a themed error dialog. A separate uploader (Sucrose.Reportdog.exe) later sends those reports to the backend — but only if you have opted in. This page explains the whole crash-reporting pipeline, from the handlers compiled into each app to the on-disk report queue and the upload step. It is written for developers and administrators.
- Two things called "watchdog"
- The five global exception handlers
- How a crash reaches the Watchdog
- What the Watchdog writes
- The error dialog
- Reportdog: uploading reports
- Consent and privacy
- Native fallback
- See also
There are two distinct components that share the "watchdog" name — keep them separate:
| Name | What it is |
|---|---|
Sucrose.Shared.Watchdog.Extension.Watch |
A logger compiled into every executable. Its methods receive each unhandled exception and append a structured block to that process's log file. |
Sucrose.Watchdog.exe |
A WPF process that receives a Base64 exception payload, writes the structured report JSON, kills the faulting process, and (optionally) shows a crash dialog. |
The logger always runs locally and in-process; the Watchdog UI process is spawned only on demand through Commandog.
Every Sucrose app (Launcher, Portal, Watchdog, Property, and the rest) wires the same set of handlers in the application constructor (App()). Each is implemented in Sucrose.Shared.Watchdog.Extension.Watch:
| Handler | Source event |
|---|---|
Watch_CatchException |
not invoked by the other handlers; called from manual try/catch sites across the codebase (logs the CATCH block) |
Watch_ThreadException |
Application.ThreadException |
Watch_FirstChanceException |
AppDomain.FirstChanceException — intentionally a no-op (return;) to avoid log spam |
Watch_UnobservedTaskException |
TaskScheduler.UnobservedTaskException |
Watch_GlobalUnhandledException |
AppDomain.UnhandledException |
Watch_DispatcherUnhandledException |
Application.DispatcherUnhandledException |
Each handler writes a 5-line block to the per-app log manager:
<TYPE> EXCEPTION START
<message>
<inner exception>
<stack trace>
<TYPE> EXCEPTION FINISH
The log manager is selected at compile time by the app's preprocessor symbol (#if UNDO / PORTAL / UPDATE / LAUNCHER / PROPERTY / WATCHDOG / COMMANDOG / REPORTDOG / LIVE_* / BACKGROUNDOG …), so each process logs to its own file. See Preprocessor Symbols and Logs & Diagnostics.
Extra context can be attached before a crash via Sucrose.Shared.Watchdog.Helper.Dataset, a static Hashtable. Its known fields (Memory/Readonly/Watch.cs) are Text and Source — help/source strings merged into Exception.Data when the crash is reported.
After logging, the handler calls Sucrose.Shared.Space.Helper.Watchdog.Start(appName, exception, show, logPath). That helper:
- Runs only if both
Sucrose.Commandog.exeandSucrose.Watchdog.exeexist (Check()). - Merges any ambient
Datasetentries intoException.Data, then clears the dataset. - Builds the payload string
"<Application>✖<SerializedException>✖<Show>✖<LogPath>". -
Base64-encodes the whole payload (
CryptologyExtension.TextToBase). - Spawns the Watchdog through Commandog:
✔Watchdog✖<path-to-Sucrose.Watchdog.exe>✖<Base64 payload>
The ✔ (U+2714) start marker and ✖ (U+2716) separator are the standard command-bus wire format (see IPC and Command Reference).
flowchart LR
Throw["Any process throws"] --> Log["Watch.* handler<br/>logs 5-line block locally"]
Log --> Start["Watchdog.Start(app, ex, show, log)"]
Start --> Payload["build payload<br/>app✖exception✖show✖log<br/>then Base64-encode"]
Payload --> Cmd["Commandog<br/>✔Watchdog✖<exe>✖<Base64>"]
Cmd --> WD["Sucrose.Watchdog.exe"]
WD --> Report["write Cache/Report/<guid>.json"]
WD --> Kill["kill faulting process<br/>unless it is Watchdog"]
WD --> Dialog{"show == true?"}
Dialog -->|yes| Box["Dark / Light error dialog"]
Dialog -->|no| SilentNode["silent"]
Report -. later .-> RD["Reportdog uploads to Soferity v8<br/>if ExceptionData is on"]
Sucrose.Watchdog.App.Configure(args):
- Decodes the Base64 argument, splits on
✖, and requires exactly 4 parts:Application,RawException(JSON),Show(bool),Log(path). - Builds a
ThrowExceptionDatarecord (Sucrose.Shared.Space.Model.ThrowExceptionData) with:-
Id(Guid),AppId(deterministic from app name),Sid(hash of user + model + manufacturer). -
UserName, DeviceModel, ManufacturerBrand. -
AppName, AppVersion, AppFramework, AppArchitecture, ProcessArchitecture, ProcessorArchitecture. -
CultureName, CultureDisplay, CultureCode. -
OperatingSystem, OperatingSystemBuild, OperatingSystemArchitecture, IsServer, IsWorkstation. -
Exception(the exception as a JObject).
-
- Writes the record to:
%AppData%\Sucrose\Cache\Report\<Guid>.json
This is the queue that Reportdog later drains. See Data Locations for the full folder map.
- If
Application != Watchdog, kills the crashing process (the Watchdog never kills itself). - Always exits at the end (
Close()/Environment.Exit).
If Show == true, the Watchdog presents a crash dialog. It picks Dark or Light (DarkErrorMessageBox / LightErrorMessageBox) based on the current theme, showing the exception message plus the log/source/text/help fields. If WPF-UI initialization fails, it falls back to a native Windows MessageBox titled "Sucrose Watchdog - Critical Error" that hints at a missing Visual C++ Redistributable or .NET Desktop Runtime (see Native fallback).
📷 Screenshot needed: Watchdog crash dialog (dark theme) showing the exception message and the log/source/help fields.
Silent (Show == false) crashes still write the report JSON and kill the process — they just do not interrupt the user.
Sucrose.Reportdog.exe is a headless uploader with a long-running loop (do { Attempt.Start(); Initialize.Dispose(); delay AppTime = 1000 } while (Exit)). It is launched on Launcher start and from the tray, but only runs when single-instance and at least one consent flag (ExceptionData or TelemetryData) is enabled.
All uploads go to the Soferity backend (base Url.Soferity, API version Soferity.Version = "v8"). There are three upload streams:
| Stream | Trigger | Endpoint | Consent |
|---|---|---|---|
| Throw (crash) reports |
FileSystemWatcher on %AppData%\Sucrose\Cache\Report; drains existing files on startup, uploads on each Created event, deletes the file on success |
…/v8/Exception/Throw/<UserGuid> |
ExceptionData |
| Analytic telemetry |
AnalyticTimer every 10 minutes (AnalyticTime = TimeSpan.FromMinutes(10)); the timer is disposed after the first successful POST (one-shot per run) |
…/v8/Telemetry/Analytic/<UserGuid> |
TelemetryData |
| Online heartbeat |
OnlineTimer every ~2–2.5 minutes (OnlineTime = Random.Next(120, 150) seconds); body { AppVersion, Time } (Time = 1 on the first ping, else the interval in seconds) |
…/v8/Telemetry/Online/<UserGuid> |
TelemetryData |
The analytic payload (AnalyticTelemetryData) is a large snapshot of nearly every user setting (app exit/visible/startup state, all per-type engine choices, all *Performance modes, library/store/cycling settings) plus hardware facts (total memory, cores, processors, GPU/network/CPU lists, OS/arch/culture). The relevant Soferity path tokens (Memory/Readonly/Soferity.cs) include Version="v8", Telemetry, Analytic, Online, Exception, Throw, Encoding=UTF8, and ApplicationJson="application/json".
Crash reporting is enabled by default (opt-out) and controlled by two independent switches on the Settings → Other page:
-
ExceptionData— enables crash-report uploads (the Throw stream). -
TelemetryData— enables analytics and the online heartbeat.
If both are off, Reportdog does not run, and the Report/<guid>.json files simply remain on disk (the local crash logging in each process's log file is unaffected). For the full breakdown of what is collected and how to disable it, see Privacy & Telemetry.
The native-MessageBox fallback in the Watchdog dialog is a deliberate diagnostic: if the WPF-UI crash dialog itself cannot start, the most common root cause is a missing Visual C++ Redistributable or .NET Desktop Runtime on the machine. The fallback message names these explicitly so users know what to install. See Runtime Dependencies and Troubleshooting: Settings, Startup & GPU.
- Logs & Diagnostics — where each process logs and how to gather logs for a bug report.
- Privacy & Telemetry — the consent switches and exactly what is sent to Soferity.
-
Commandog Dispatcher — how the
✔Watchdog✖…✖…command spawns the crash handler. -
Data Locations — the
Cache\Reportqueue and other on-disk paths. - Architecture Overview — the Watchdog/Reportdog roles in the process roster.
Getting Started
- Installation
- System Requirements
- Quick Start
- Portal Interface Tour
- Updating Sucrose
- Uninstalling Sucrose
Wallpaper Types
Using Sucrose
- Managing Library
- Using Store
- Customizing Wallpaper
- Multi-Monitor
- Wallpaper Cycling
- Choosing Engines
- Performance Rules
- Theme, Tray & Startup
- Discord Rich Presence
Settings Reference
- Settings Overview
- Settings: General
- Settings: Personal
- Settings: Performance
- Settings: Wallpaper
- Settings: System
- Settings: Other
- Settings: All Keys
Creating Wallpapers
- Create Overview
- Create: Step By Step
- Create: Package Format
- Create: Customization Controls
- Create: JS Bridge
- Create: Audio API
- Create: System API
- Create: Property Listener & Filters
- Create: Web Architecture
- Create: Compatibility
- Create: Example Wallpapers
- Create: Sharing & Publishing
Engine Reference
- Engines Overview
- Engine: MpvPlayer
- Engine: VlcPlayer
- Engine: WebView
- Engine: CefSharp
- Engine: Nebula
- Engine: Vexana
- Engine: Xavier
- Engine: Aurora
- Engine Comparison
Automation & Command Line
Architecture & Internals
- Architecture Overview
- Lifecycle
- Commandog Dispatcher
- Single-Instance Mutexes
- IPC
- Backgroundog Service
- Crash Reporting
- Update Internals
- Property Service
- Undo Internals
Data, Files & Diagnostics
Building & Contributing
- Building From Source
- Repository Layout
- Shared Item Projects
- Code Conventions
- Preprocessor Symbols
- Publish Pipeline
- Bundle Installer Internals
- Extending Sucrose
- Contributing
- Translating with Localizer
- Localization Coverage
- Security Policy
- Privacy & Telemetry
Help & Support