Skip to content

LoLLogger - Sink-Based Logging System

Overview

LoLLogger is a thin static facade that dispatches log events to pluggable sinks. The logger itself performs no formatting, no file I/O, and no history management — each sink handles its own responsibility independently.

Architecture

Caller  →  LoLLogger (static facade)  →  UnityConsoleSink  (rich-text → Debug.Log)
                                      →  FileSink           (plain-text → async file)
                                      →  HistorySink        (ring buffer → in-memory)

Quick Start

using LoLEngine.Runtime.Logger;

// Basic logging
LoLLogger.Log("Player spawned");
LoLLogger.Warning("Health is low");
LoLLogger.Error("Failed to load asset");

// Debug level (filtered out when LOGLevel > Debug)
LoLLogger.DebugLog("Cache miss for texture");

// Emphasis level (renders as warning with lilac color)
LoLLogger.LogEmphasis("=== INITIALIZATION COMPLETE ===");

Log Levels

Level When to Use Console Routing Color Release Build
Debug Verbose diagnostics during development Debug.Log #999DA0 Stripped
Info Normal operational messages Debug.Log #00FFFF Stripped
Emphasis Important milestones (init complete, etc.) Debug.LogWarning #E0B0FF Stripped
Warning Potential problems that don't stop execution Debug.LogWarning #ff7f50 Available
Error Failures that need attention Debug.LogError #ff0000 Available

Levels are ordered: Debug < Info < Warning < Emphasis < Error. Setting LOGLevel = LogLevel.Warning filters out Debug and Info messages.

Release Build Stripping

Log, DebugLog, and LogEmphasis methods are decorated with [Conditional("UNITY_EDITOR"), Conditional("DEVELOPMENT_BUILD")]. In release builds (where neither symbol is defined), the entire call site is stripped by the compiler — including argument evaluation. This means:

// In a release build, this line compiles to nothing — ExpensiveDebugInfo() is never called
LoLLogger.DebugLog($"State: {ExpensiveDebugInfo()}");

Warning and Error are always available in all build configurations.

Log Channels

Channels use a [Flags] enum for bitwise filtering:

// Enable only specific channels
LoLLogger.LOGChannels = LogChannel.Audio | LogChannel.Resource;

// Log with a channel (filtered if channel not enabled)
LoLLogger.Log(LogChannel.Audio, "Playing background music");
LoLLogger.Warning(LogChannel.Resource, "Asset not found, using fallback");

// Enable all channels
LoLLogger.LOGChannels = LogChannel.All;

Available channels: Default, Service, Logger, Boot, ObjectPool, Resource, Core, Event, Config, Data, Localization, GameState, Audio, Network, Physics, Editor, SaveLoad, Time, Notification, DependencyChecker, Custom1Custom4, All.

Configuration

Configuration is managed through LoggerConfig (created automatically):

// Enable/disable logging globally
LoLLogger.LOGEnabled = false;

// Set minimum log level
LoLLogger.LOGLevel = LogLevel.Warning;

// Set enabled channels
LoLLogger.LOGChannels = LogChannel.All;

// Check if a level/channel is enabled (useful for expensive message construction)
if (LoLLogger.IsEnabled(LogLevel.Debug))
{
    LoLLogger.DebugLog($"Complex debug info: {ExpensiveComputation()}");
}

Sinks

UnityConsoleSink

Routes formatted log events to Unity's Debug.Log, Debug.LogWarning, and Debug.LogError. Applies rich-text formatting:

<color=#82d3f9>[f1234]</color> <color=#f9a682>[01:23.456]</color> MyClass: <color=#00FFFF>Message here</color>

FileSink

Writes log events to disk asynchronously using a background thread with batched writes. File output is plain-text (no color tags):

[2026-02-06 14:30:00.123] [WARNING] [Audio] [T1] Asset loading slow

Configuration defaults: - Path: {Application.persistentDataPath}/Logs/game_log.txt - Minimum file level: Warning (Debug/Info not written to file by default) - Batch size: 32 events - Flush interval: 250ms - Max file size: 10 MB (with rotation) - Max backups: 1

HistorySink

Maintains a fixed-size ring buffer of recent log events in memory. O(1) insert, no allocations after initialization.

// Get a snapshot of recent log events
IReadOnlyList<LogEvent> history = LoLLogger.GetLogHistory();

foreach (var evt in history)
{
    Debug.Log($"[{evt.Level}] {evt.Message}");
}

// Clear the history
LoLLogger.ClearLogHistory();

Default capacity: 256 events.

Caller Information

All logging methods use [CallerMemberName], [CallerFilePath], and [CallerLineNumber] attributes to automatically capture call-site information at zero runtime cost (compiler-injected). This replaces the previous new StackTrace() approach which was expensive (~1ms per call).

The caller class name shown in console output is derived from the file path via Path.GetFileNameWithoutExtension() (convention: MyClass.cs = MyClass).

// Console output includes caller info automatically:
// [f1234] [01:23.456] MyClass.MyMethod: <color=#00FFFF>Player spawned</color>
LoLLogger.Log("Player spawned");

Caller information is also available on LogEvent objects from the history API:

var history = LoLLogger.GetLogHistory();
foreach (var evt in history)
{
    Debug.Log($"{evt.CallerClassName}.{evt.CallerMemberName}:{evt.CallerLineNumber} — {evt.Message}");
}

Thread Safety

  • LoLLogger facade: Safe to call from any thread. Main thread detection is correct (initialized via [RuntimeInitializeOnLoadMethod]).
  • UnityConsoleSink: Calls Debug.Log which Unity handles thread-safely.
  • FileSink: Thread-safe via ConcurrentQueue and a background worker thread.
  • HistorySink: Thread-safe via lock-based synchronization.
  • LoggerConfig: Not thread-safe for writes, but configuration is typically set once during startup.

Domain Reload

LoLLogger participates in the distributed domain reload pattern (see ADR-003 in Architecture.md). The [RuntimeInitializeOnLoadMethod(SubsystemRegistration)] callback:

  1. Shuts down all existing sinks (flushes FileSink)
  2. Re-initializes with fresh config and new sink instances
  3. Captures the correct main thread ID

Migration from Pre-Sink Logger

Before (v0.14.x) After (v0.15.x)
typeof(LoLLogger).GetField("_logHistory", ...) LoLLogger.GetLogHistory()
Reflection to call ClearLogHistory LoLLogger.ClearLogHistory()
color parameter controlled output color Colors determined by log level
timePrecision parameter Always MM:SS.mmm format
displayFrameCount parameter Frame count always shown
Synchronous file I/O per log call Async batched file writes
Channel filtering non-functional Full bitwise channel filtering
MonoBehaviour base class Static class
new StackTrace() per log call (~1ms) [CallerMemberName] (zero cost)
Debug logs present in release builds [Conditional] strips Debug/Info/Emphasis

See Also

  • Architecture.md — ADR-006: Sink-Based Logger Architecture
  • ILogSink interface — for creating custom sinks
  • LoggerConfig — configuration properties