CryptikLemur.RimLogging 1.1.0

dotnet add package CryptikLemur.RimLogging --version 1.1.0
                    
NuGet\Install-Package CryptikLemur.RimLogging -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="CryptikLemur.RimLogging" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CryptikLemur.RimLogging" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="CryptikLemur.RimLogging" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add CryptikLemur.RimLogging --version 1.1.0
                    
#r "nuget: CryptikLemur.RimLogging, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package CryptikLemur.RimLogging@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=CryptikLemur.RimLogging&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=CryptikLemur.RimLogging&version=1.1.0
                    
Install as a Cake Tool

RimLogging

Maintainability Rating Reliability Rating

A public, structured logging framework for RimWorld 1.6+ mods.

What it is

Replaces vanilla Verse.Log and UnityEngine.Debug.Log with a single structured pipeline:

  • Hierarchical channels (XML defs or transient) with prefix-based resolution.
  • Serilog-style templated messages: Log.Info("player {Name} died at {Hp}hp", "Bob", 5).
  • Anonymous-object structured context: Log.Info("died", new { pawn, hp }).
  • Six levels: Trace, Debug, Info, Warn, Error, Fatal.
  • Multi-sink output: Verse log writeback, rolling text file, rolling NDJSON file, in-memory (tests), plus a plugin sink API.
  • Expression-based filter DSL for the in-game viewer (level >= Warn OR channel = "Cosmere.*").
  • Three-pane in-game log viewer, shipped with this mod and activated when Lightweave is installed.
  • Lock-free MPSC queue + background drain. Synchronous bypass for Error / Fatal.

Modules

Assembly Depends on Purpose
CryptikLemur.RimLogging Harmony, RimWorld 1.6 Core pipeline, sinks, channels, DSL parser, Verse.Log + Unity hijack.
CryptikLemur.RimLogging.LightweaveViewer Core, Lightweave The three-pane in-game log viewer. Ships in this mod under LightweaveViewer/ but is only loaded when Lightweave is active.

The three-pane in-game log viewer ships with this mod as a companion assembly built on the Lightweave UI framework. Because the viewer references Lightweave types, it is loaded late (after all mods are present) and only when Cosmere.Lightweave is active — so RimLogging works fine on its own, and the viewer lights up automatically once you also install Lightweave.

Install

End-users install the Workshop mod once and every consumer mod shares a single dll. Declare it in your About/About.xml:

<modDependencies>
    <li>
        <packageId>CryptikLemur.RimLogging</packageId>
        <displayName>RimLogging</displayName>
        <steamWorkshopUrl>steam://url/CommunityFilePage/REPLACE_WITH_WORKSHOP_ID</steamWorkshopUrl>
    </li>
</modDependencies>
<loadAfter>
    <li>CryptikLemur.RimLogging</li>
</loadAfter>

As a NuGet package (bundled)

dotnet add package CryptikLemur.RimLogging

The dll (and its System.Text.Json runtime dependencies) are copied into your mod's Assemblies/ directory at build time. No Workshop dependency required.

The three-pane in-game log viewer is built on the Lightweave UI framework. RimLogging ships the viewer but only activates it when Lightweave is present, so install Lightweave alongside RimLogging to get the viewer. Without it, logging still works fully — you just use the vanilla log window instead of the three-pane viewer.

Quick start

using CryptikLemur.RimLogging;

// Default channel, templated message with positional args.
Log.Info("colony {Name} founded at {Tile}", colony.Name, colony.Tile);

// Structured context from an anonymous object.
Log.Warn("low food", new { pawn, days_left = 2 });

// Exceptions: pass the exception first, or fold it into the context.
Log.Error(ex, "save failed");
Log.Error("save failed", new { ex, path });

// Explicit channel + structured context.
Log.Info("Cosmere.Roshar.Surgebinding", "bond formed", new { spren = spren.Label });

// Explicit channel + templated args: pass the args as an explicit array so the
// channel overload is selected (a bare trailing value binds as structured context).
Log.Info("Cosmere.Roshar.Surgebinding", "bond formed with {Spren}", new object?[] { spren.Label });

// Lower-severity levels.
Log.Trace("tick {N}", ticks);
Log.Debug("pathfinding cache miss");
Log.Fatal("unrecoverable: {Reason}", reason);

The first string argument is the channel only when a later argument disambiguates the overload; Log.Info("text", arg) treats "text" as the message. Formatting is lazy: if no registered sink accepts the entry's level, the template is never rendered and the context object is never reflected.

Channels

Channels are dotted, hierarchical names. Define them in XML to set defaults, or just pass any string at call time for a transient channel.

<?xml version="1.0" encoding="utf-8"?>
<Defs>
    <CryptikLemur.RimLogging.Channels.ChannelDef>
        <defName>Cosmere.Roshar.Surgebinding</defName>
        <label>Surgebinding</label>
        <description>Stormlight bonding, surge investiture, oath progression.</description>
        <defaultLevel>Debug</defaultLevel>
        <color>(0.7, 0.85, 1.0)</color>
        <captureStackAt>Error</captureStackAt>
        <destinations>
            <li>RollingText</li>
        </destinations>
        <format>[{Channel}] {Message}</format>
    </CryptikLemur.RimLogging.Channels.ChannelDef>
</Defs>

The XML element name is the fully namespace-qualified type, CryptikLemur.RimLogging.Channels.ChannelDef, not RimLogging.ChannelDef.

ChannelDef fields:

Field Default Meaning
defaultLevel Info Minimum level emitted on this channel.
color none RGB tuple for the viewer, e.g. (0.7, 0.85, 1.0).
captureStackAt Error Level at/above which a stack trace is captured.
destinations all sinks Sink defNames this channel routes to (empty = every registered sink).
format default Per-channel format template override.

Transient fallback / prefix resolution: when you log to a channel name with no exact ChannelDef, resolution walks up the dotted prefix to the nearest registered ancestor, then falls back to the built-in default channel. So Cosmere.Roshar.Surgebinding.Windrunner uses the Cosmere.Roshar.Surgebinding def if that is the closest registered ancestor.

Built-in channels: default (catch-all), Vanilla (captured Verse.Log calls), Unity (captured UnityEngine.Debug.Log calls).

Filter DSL

Used by the in-game viewer to filter the live log. Grammar:

expr    := orExpr
orExpr  := andExpr ( "OR" andExpr )*
andExpr := notExpr ( "AND" notExpr )*
notExpr := "NOT" notExpr | "(" expr ")" | term
term    := "level" levelOp LEVEL | "channel" strOp STRING
levelOp := "=" | "!=" | "<" | "<=" | ">" | ">="
strOp   := "=" | "!="
LEVEL   := Trace | Debug | Info | Warn | Error | Fatal
STRING  := "double-quoted, supports * wildcards"

Channel string matching supports * wildcards; a trailing .* matches the channel itself or any dotted descendant.

Examples:

level >= Warn
level >= Warn OR channel = "Cosmere.*"
channel = "Cosmere.Roshar.*" AND level >= Debug
NOT (channel = "Unity")
level != Trace AND NOT channel = "Vanilla"

Custom sinks

Implement ILogSink and register it, either from code or via a SinkDef.

public sealed class MySink : ILogSink
{
    public string Name => "MySink";
    public LogLevel MinLevel => LogLevel.Info;

    public void Write(LogEntry entry) { /* render or store entry */ }
    public void Flush() { /* flush buffers */ }
    public void Dispose() { /* close handles */ }
}

// Register from a StaticConstructorOnStartup or your mod ctor:
Logging.RegisterSink(new MySink());

Or load it from XML so the bootstrap phase instantiates it:

<?xml version="1.0" encoding="utf-8"?>
<Defs>
    <CryptikLemur.RimLogging.Sinks.SinkDef>
        <defName>MySink</defName>
        <label>My Sink</label>
        <sinkClass>MyMod.MySink, MyMod</sinkClass>
        <minLevel>Info</minLevel>
        <enabledByDefault>true</enabledByDefault>
    </CryptikLemur.RimLogging.Sinks.SinkDef>
</Defs>

sinkClass is an assembly-qualified type name. The implementation needs a public parameterless constructor for XML loading. Built-in sinks: VerseLog, RollingText (enabled by default), RollingJson (NDJSON, off by default).

Settings

The in-game mod settings page exposes:

  • Global minimum level (globalMinLevel) - drops every entry below this level before any sink sees it.
  • Log directory (logDirectory) - where rolling files are written; normalized to a default under the game's persistent data path when left blank.
  • Retention count (retentionCount) - number of rotated log files kept.
  • Bundle proxy URL (proxyUrl) - upload endpoint for bug-report bundles.
  • Combine message and stack trace (logViewerCombinedDetail) - when Lightweave's viewer is active, shows the message and stack trace together in the detail pane.
  • Filter presets - saved name/expression pairs for the viewer's filter DSL.

All settings persist across restarts via RimWorld's Scribe system.

Bug bundle

The viewer's "share bundle" button serializes a JSON payload and uploads it through the configured proxy, then copies the returned URL to your clipboard (with a toast confirmation). The payload contains:

  • RimWorld version and framework version.
  • The active mod list (name, packageId, version, active flag).
  • Recent log entries (timestamp, level, channel, source, message, structured context, stack trace).

Override the upload endpoint with the proxyUrl setting if you self-host the proxy.

Versioning

Versions are derived automatically from Conventional Commits via semantic-release:

  • fix: / perf: → patch
  • feat: → minor
  • BREAKING CHANGE: / ! → major

Releases are git tags. Stable releases are cut from main; the beta branch publishes prereleases.

Building

make build      # whole solution
make test       # xunit suites
make format     # dotnet format

Translations

English is the source language. The other bundled translations (Chinese Simplified, French, Spanish, German) may be inaccurate. Corrections are welcome via pull request.

Contributing

  • Run make build and make test green before opening a PR.

License

MIT.

Product Compatible and additional computed target framework versions.
.NET Framework net48 is compatible.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on CryptikLemur.RimLogging:

Package Downloads
Cosmere.Lightweave

A composable IMGUI framework for RimWorld mods. Provides nodes, layout, theming, typography, navigation, and adapters that bridge nodes into vanilla surfaces (gizmos, ITabs, MainTabs, FloatMenuOptions, ChoiceLetters, tooltips).

Cosmere.Lightweave.Redesign

Reimagined main menu, mod manager, load colony, and options screens, built on the Lightweave framework.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 150 7/5/2026
1.0.10 130 5/30/2026
1.0.9 117 5/30/2026
1.0.8 120 5/30/2026
1.0.7 135 5/29/2026
1.0.6 122 5/29/2026
1.0.5 123 5/29/2026
1.0.4 118 5/29/2026
1.0.3 123 5/29/2026
1.0.2 164 5/26/2026
1.0.1 127 5/26/2026
1.0.0 122 5/26/2026
1.0.0-beta.10 77 5/26/2026