Category-based logging for the whole stack #
System Log is Game Framework’s category-based logging system. A call names a category (a dot-path such as Steam.App.Client), a severity, and a message. Project Settings decides, per category and per environment, where each one goes. Every Heathen Foundation and Toolkit package logs through it, so this one page controls logging for the whole stack.
Three tiers per category: Message (an in-memory display buffer), Log (a file or API stream), and Console (Unity’s own Console). Each has a threshold — None, Error, Warning, Info (Info includes Verbose) — and the tiers clamp: Log can never be more permissive than Message, and Console can never be more permissive than Log.
Project Settings › Subsystems › System Log #
- Editor, Client, Dedicated Server tabs — each configures its own environment independently. “Copy from” buttons copy a whole configuration between tabs. The Client and Dedicated Server tabs’ settings currently live in the Editor machine’s PlayerPrefs rather than being baked into a build, so treat them as editor-side configuration surfaces for now rather than a way to configure a shipped player’s logging remotely.
- General block — Unity Console (capture Unity’s own log traffic into the same system), Stack Trace, Max Entries (default 200), and an Enabled toggle for the runtime recorder.
- Log section — one Destination dropdown, FileStream or APIStream, with its own options and a Fields list controlling what gets written.
- Category Settings — a System Log root row plus one banded, indented row per category, three dropdowns each (Message / Log / Console). Choosing a value on a row applies it to that category and every descendant; a parent row shows the value its children share, or
-when they differ (a real dropdown entry that leaves children as they are). Categories from every installed Heathen package appear automatically — each package ships its own Editor registrar.
Console output #
Entries show their category as a hyperlink to the call site, formatted Category: message, in the detail pane. Select an entry in the Console list, then click the link in the detail pane — it opens the file at the real call site. This needs Unity 2022.2 or later; older editors show the raw markup instead of a link.
The System Log window #
Open it from Window › Heathen › Game Framework › System Log — it docks next to Unity’s own Console by default, and is the live viewer for the SystemLog stream.
Toolbar: Clear, Load (opens a saved SystemLogRecorder JSON export), a Categories dropdown (a cascading multi-select tree matching the Category Settings tree), a Severity dropdown (Error / Warning / Info / Verbose), a search field, a gear (Settings: Auto-scroll to newest, Word wrap messages, Clear on entering Play, Collapse same-category messages), and a ? link back to this page.
List behavior: consecutive entries sharing the same category and severity collapse under one shared header (a severity icon and the category name) instead of repeating it per entry — toggle this from the Settings gear. The header has its own … action control, opening a menu with Copy (the whole group as Markdown), Hide Category, and Show Only This Category (both acting on the same category filter state as the toolbar’s Categories dropdown). Each entry renders as one line: a millisecond-precision timestamp, a clickable Line # that opens the real call site in your code editor, a T/M toggle that swaps a stack trace in for the message when one exists (T shows the trace, M goes back — hidden when there is no trace), then the message text.
Selection and copy: click an entry to select it, Ctrl/Cmd-click to add to the selection, Shift-click to select a range, and Ctrl/Cmd+C to copy the whole selection as Markdown — the same template the group Copy action uses (# Severity : Category, timestamp and line, ## Message:, and ## Stack Trace: when present). Handy for pasting a repro straight into Discord or a ticket.
Categories by package #
Every installed Heathen package registers its own category tree automatically. A summary:
- Steamworks —
Steam.App.Client,Steam.App.Server,Steam.Achievements.General,Steam.Leaderboards,Steam.GameServer,Steam.Lobby,Steam.Tools,Steam.Editor - Ogham —
Ogham.Storyteller,Ogham.Reader - Lexicon —
Lexicon.AssetLoader,Lexicon.Source,Lexicon.Bindings - Gameplay Tags —
GameplayTags.Events,GameplayTags.Conditions,GameplayTags.UI,GameplayTags.CodeGen(Editor only)
Steam, Steam.App, Ogham, Lexicon, and GameplayTags are structural rows — they control their children in Category Settings, but nothing logs directly to them.
Steamworks code generation #
Both Steamworks code generators emit SteamTools.Log.Achievements.<id> gates into SteamTools.Game.cs when Game Framework is installed and at least one achievement exists. If you’re upgrading an existing project, its generated wrapper will show as out of date until you regenerate it once. Per-stat and per-leaderboard gates are not generated yet.
Adding your own logging #
Heathen code (generated code included) never calls Debug.Log/LogWarning/LogError directly — every call goes through the shared gate, and every call site is guarded, even for a cheap literal message, so every call site reads the same way:
if (Log.IsEnabled("Steam.Tools", LogSeverity.Verbose))
Log.Verbose("Steam.Tools", $"...");
The guard exists because skipping it still evaluates the message’s arguments first (string interpolation, boxing) — the category filter alone doesn’t save that cost. Guard every severity, including Error and Warning, not just the visibly expensive ones: a category tree can mute any severity, so consistency is what keeps the pattern legible later, not case-by-case judgement about which calls were “worth” guarding. A hot path that can’t afford a guard’s Bus round trip uses a generated Heathen.LogGates.<Product>.<Area>.Value.Info field read instead of Log.IsEnabled — a plain static field, refreshed live whenever settings change.
A few other rules: only log to leaf categories (use a General leaf for the umbrella case), never pass the compiler-filled callerFile/callerLine parameters yourself, there is no Log.Exception — use Log.Error(category, e.ToString()) — and logging calls are main-thread only. HeathenDebug remains as an older, narrower type-keyed convenience wrapper over Log (mapping a subsystem Type to its Subsystems.<Name> category) for a subsystem’s own internal housekeeping, where a plain Type is more convenient than composing a category string by hand; its LogWarning/LogError/LogException stay always-on and unguarded, the one place it still diverges from Log’s fully-guarded convention.