Skip to content

Crash Reporting

Taiizor edited this page Jun 5, 2026 · 3 revisions

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.

Contents

Two things called "watchdog"

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.

The five global exception handlers

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.FirstChanceExceptionintentionally 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.

How a crash reaches the Watchdog

After logging, the handler calls Sucrose.Shared.Space.Helper.Watchdog.Start(appName, exception, show, logPath). That helper:

  1. Runs only if both Sucrose.Commandog.exe and Sucrose.Watchdog.exe exist (Check()).
  2. Merges any ambient Dataset entries into Exception.Data, then clears the dataset.
  3. Builds the payload string "<Application>✖<SerializedException>✖<Show>✖<LogPath>".
  4. Base64-encodes the whole payload (CryptologyExtension.TextToBase).
  5. 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✖&lt;exe&gt;✖&lt;Base64&gt;"]
    Cmd --> WD["Sucrose.Watchdog.exe"]
    WD --> Report["write Cache/Report/&lt;guid&gt;.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"]
Loading

What the Watchdog writes

Sucrose.Watchdog.App.Configure(args):

  1. Decodes the Base64 argument, splits on , and requires exactly 4 parts: Application, RawException (JSON), Show (bool), Log (path).
  2. Builds a ThrowExceptionData record (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).
  3. 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.

  1. If Application != Watchdog, kills the crashing process (the Watchdog never kills itself).
  2. Always exits at the end (Close() / Environment.Exit).

The error dialog

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.

Reportdog: uploading reports

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".

Consent and privacy

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.

Native fallback

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.

See also

Home

Getting Started

Wallpaper Types

Using Sucrose

Settings Reference

Creating Wallpapers

Engine Reference

Automation & Command Line

Architecture & Internals

Data, Files & Diagnostics

Building & Contributing

Help & Support

Clone this wiki locally