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, Custom1–Custom4, 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.Logwhich Unity handles thread-safely. - FileSink: Thread-safe via
ConcurrentQueueand 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:
- Shuts down all existing sinks (flushes FileSink)
- Re-initializes with fresh config and new sink instances
- 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
ILogSinkinterface — for creating custom sinksLoggerConfig— configuration properties