Advanced Unity logging system with channel filtering, rate limiting, contexts, assertions, and conditional logging.
Select "Add package from git URL" in Unity Package Manager and paste:
https://github.com/sathyarajshetigar/IJSLogger.git#upm
Organize logs into channels (Audio, Network, Physics, AI, UI, Gameplay, Performance, etc.) and control them independently. Configure channels to log only in Editor, only in Builds, or Both.
Prevent log spam with automatic rate limiting. Suppressed logs are counted and reported.
Add automatic context to your logs using the disposable pattern:
using (new LogContext("Level Loading"))
{
_logger.PrintLog("Loading assets"); // [Level Loading] Loading assets
}Fluent assertion API with automatic logging:
_logger.Assert(health > 0, "Health must be positive!")
.OnFailure(() => health = 0)
.PauseEditor();Log only when conditions are met, with lazy evaluation support:
_logger.LogIf(() => debugMode, () => $"Debug: {expensiveOperation()}");- Settings Window (
Window → IJS Logger → Settings): Configure channels and scopes - Log Viewer (
Window → IJS Logger → Log Viewer): View all Unity logs with filtering
graph TB
subgraph "User Code"
UC[Your MonoBehaviour]
end
subgraph "IJSLogger Core"
Logger[IJSLogger Instance]
Static[Static Log Methods]
end
subgraph "Advanced Features"
Context[LogContext<br/>Disposable Pattern]
Assert[LogAssert<br/>Fluent API]
RateLimit[LogRateLimiter<br/>Spam Prevention]
end
subgraph "Configuration"
Settings[IJSLoggerSettings<br/>ScriptableObject]
Channels[Channel Config<br/>12 Channels]
end
subgraph "Editor Tools"
SettingsWin[Settings Window]
ViewerWin[Log Viewer Window]
end
subgraph "Unity"
Console[Unity Console]
DebugLog[Debug.Log API]
end
UC -->|Create Instance| Logger
UC -->|Use Static| Static
UC -->|using Statement| Context
Logger -->|Check Settings| Settings
Logger -->|Apply Context| Context
Logger -->|Check Rate| RateLimit
Logger -->|Returns| Assert
Settings -->|Loads| Channels
Logger -->|Outputs to| DebugLog
Static -->|Outputs to| DebugLog
DebugLog -->|Displays in| Console
DebugLog -->|Captured by| ViewerWin
SettingsWin -->|Configures| Settings
style Logger fill:#4a9eff
style Settings fill:#ffa94a
style Context fill:#9eff4a
style Assert fill:#ff4a9e
style RateLimit fill:#4affe9
flowchart TD
Start([Log Message]) --> Check1{USE_LOGS<br/>Scripting Define?}
Check1 -->|Not Defined| Strip[Code Stripped<br/>at Compile Time]
Check1 -->|Defined| Check2{Channel<br/>Enabled?}
Check2 -->|No| Blocked1[❌ Blocked]
Check2 -->|Yes| Check3{Channel Scope<br/>Matches Environment?}
Check3 -->|No| Blocked2[❌ Blocked<br/>Wrong Scope]
Check3 -->|Yes| Check4{Instance<br/>Enabled?}
Check4 -->|No| Blocked3[❌ Blocked]
Check4 -->|Yes| Check5{Rate Limit<br/>Passed?}
Check5 -->|No| Suppressed[⏸️ Suppressed<br/>Count Incremented]
Check5 -->|Yes| Output[✅ Log Output<br/>to Unity Console]
Strip -.->|Zero Runtime Cost| End([End])
Blocked1 --> End
Blocked2 --> End
Blocked3 --> End
Suppressed --> End
Output --> End
style Check1 fill:#ff6b6b
style Check2 fill:#ffd93d
style Check3 fill:#6bcf7f
style Check4 fill:#4d96ff
style Check5 fill:#a78bfa
style Output fill:#51cf66
style Strip fill:#ff8787
style Blocked1 fill:#ff6b6b
style Blocked2 fill:#ff6b6b
style Blocked3 fill:#ff6b6b
style Suppressed fill:#ffd93d
graph LR
subgraph "12 Log Channels"
Default[Default<br/>Always On]
Audio[Audio]
Network[Network]
Physics[Physics]
AI[AI]
UI[UI]
Gameplay[Gameplay]
Perf[Performance]
Anim[Animation]
Input[Input]
Render[Rendering]
System[System]
end
subgraph "Channel Scopes"
EditorOnly[Editor Only<br/>🖥️]
BuildOnly[Build Only<br/>📦]
Both[Both<br/>🖥️ + 📦]
end
subgraph "Environments"
Editor[Unity Editor]
Build[Unity Build]
end
Default --> Both
Audio --> Both
Network --> Both
Physics --> Both
AI --> Both
UI --> Both
Gameplay --> Both
Perf --> EditorOnly
Anim --> Both
Input --> Both
Render --> Both
System --> Both
EditorOnly -.->|Logs in| Editor
BuildOnly -.->|Logs in| Build
Both -.->|Logs in| Editor
Both -.->|Logs in| Build
style Default fill:#51cf66
style Perf fill:#ffd93d
style EditorOnly fill:#4d96ff
style BuildOnly fill:#ff6b6b
style Both fill:#a78bfa
sequenceDiagram
participant User as Your Code
participant Logger as IJSLogger
participant Context as LogContext
participant Settings as Settings
participant RateLimit as RateLimiter
participant Unity as Unity Console
User->>Logger: IJSLogger.Create("MyClass", Color.cyan, true, LogChannel.Gameplay)
Logger->>Settings: Check if Gameplay channel enabled
Settings-->>Logger: Enabled ✓
User->>Context: using (new LogContext("Init"))
Context->>Context: Push "Init" to stack
User->>Logger: PrintLog("Starting")
Logger->>Settings: Is channel enabled + scope valid?
Settings-->>Logger: Yes ✓
Logger->>Context: Get current context
Context-->>Logger: "[Init]"
Logger->>RateLimit: Should log?
RateLimit-->>Logger: Yes ✓
Logger->>Unity: Debug.Log("[Init] MyClass:: Starting")
User->>Logger: LogThrottled("Update", 1.0f)
Logger->>RateLimit: Should log? (within 1s)
RateLimit-->>Logger: No, suppressed
Note over User,Context: Wait 1 second...
User->>Logger: LogThrottled("Update", 1.0f)
Logger->>RateLimit: Should log? (after 1s)
RateLimit-->>Logger: Yes ✓ (suppressed 59x)
Logger->>Unity: Debug.Log("Update (suppressed 59x)")
User->>Logger: Assert(health > 0, "Invalid!")
Logger->>Unity: Debug.LogError("ASSERTION FAILED: Invalid!")
Logger-->>User: LogAssert fluent API
User->>Logger: .OnFailure(() => health = 0)
User->>Context: Dispose (end of using block)
Context->>Context: Pop "Init" from stack
graph TD
subgraph "v1.0.11 - Basic Logging"
V1[Colored Logs]
V2[Prefixes]
V3[Instance Enable/Disable]
V4[Global USE_LOGS]
end
subgraph "v1.1.0 - Advanced Features"
V5[12 Channel System]
V6[Channel Scopes<br/>Editor/Build/Both]
V7[Smart Rate Limiting]
V8[Log Contexts]
V9[Assertion System]
V10[Conditional Logging]
V11[Settings Window]
V12[Log Viewer Window]
end
V1 --> V5
V2 --> V5
V3 --> V6
V4 --> V6
V5 --> V7
V6 --> V8
V7 --> V9
V8 --> V10
V9 --> V11
V10 --> V12
style V1 fill:#e0e0e0
style V2 fill:#e0e0e0
style V3 fill:#e0e0e0
style V4 fill:#e0e0e0
style V5 fill:#4a9eff
style V6 fill:#ffa94a
style V7 fill:#9eff4a
style V8 fill:#ff4a9e
style V9 fill:#4affe9
style V10 fill:#a78bfa
style V11 fill:#51cf66
style V12 fill:#ffd93d
using UnityEngine;
using com.ijs.logger;
public class MyGame : MonoBehaviour
{
// Create logger with channel
private readonly IJSLogger _logger = IJSLogger.Create("MyGame", Color.cyan, true, LogChannel.Gameplay);
void Start()
{
// Simple logging
_logger.PrintLog("Game started!");
// Different log types
_logger.PrintLog("Warning!", LogType.Warning);
_logger.PrintLog("Error!", LogType.Error);
}
}Use IJSLogger.Create(...) as the preferred creation path when you want to skip allocating a real logger while instance logging is disabled or USE_LOGS is not defined.
public class AdvancedExample : MonoBehaviour
{
private readonly IJSLogger _gameplay = IJSLogger.Create("Gameplay", Color.cyan, true, LogChannel.Gameplay);
private readonly IJSLogger _perf = IJSLogger.Create("Perf", Color.magenta, true, LogChannel.Performance);
[SerializeField] private float health = 100f;
void Start()
{
// Context-based logging
using (new LogContext("Initialization"))
{
_gameplay.PrintLog("Loading level");
_gameplay.PrintLog("Spawning player");
}
// Assertions
_gameplay.Assert(health > 0, "Health must be positive!");
_gameplay.ValidateRange(health, 0, 100, nameof(health));
// Conditional logging with lazy evaluation
_gameplay.LogIf(
() => health < 20,
() => $"Low health: {health}",
LogType.Warning
);
}
void Update()
{
// Rate-limited logging (prevents spam)
_perf.LogThrottled($"FPS: {1f / Time.deltaTime:F1}", 1.0f);
}
void TakeDamage(float damage)
{
using (new LogContext("Combat"))
{
health -= damage;
_gameplay.PrintLog($"Took {damage} damage. Health: {health}");
// Assert with callback
_gameplay.Assert(health >= 0, "Health cannot be negative!")
.OnFailure(() => health = 0);
}
}
}| Channel | Description | Default Scope |
|---|---|---|
Default |
Always enabled | Both |
Audio |
Audio system logs | Both |
Network |
Network/multiplayer logs | Both |
Physics |
Physics system logs | Both |
AI |
AI and pathfinding logs | Both |
UI |
User interface logs | Both |
Gameplay |
Core gameplay logs | Both |
Performance |
Performance metrics | Editor Only |
Animation |
Animation system logs | Both |
Input |
Input handling logs | Both |
Rendering |
Rendering system logs | Both |
System |
General system logs | Both |
- Editor Only: Logs only in Unity Editor (not in builds)
- Build Only: Logs only in builds (not in editor)
- Both: Logs in both editor and builds
In Editor:
- Open
Window → IJS Logger → Settings - Enable/disable channels
- Set channel scope for each
- Use quick actions (Enable All, Disable All, Reset)
In Code:
var settings = IJSLoggerSettings.Instance;
settings.SetChannelEnabled(LogChannel.Performance, true);
settings.SetChannelScope(LogChannel.Performance, ChannelScope.EditorOnly);Prevent console spam from tight loops:
void Update()
{
// Only logs once per second, shows suppression count
_logger.LogThrottled($"Player position: {transform.position}", 1.0f);
// Output after 1 second: "Player position: (1,2,3) (suppressed 59x)"
}Add hierarchical context to logs:
using (new LogContext("Level Loading"))
{
_logger.PrintLog("Loading assets");
using (new LogContext("Spawning"))
{
// Output: [Level Loading > Spawning] Spawned 10 enemies
_logger.PrintLog("Spawned 10 enemies");
}
}_logger.LogIf(debugMode, "Debug information");
_logger.LogIf(health < 20, "Low health!", LogType.Warning);Avoid expensive operations when logs are disabled:
_logger.LogIf(
() => debugMode && isInCombat,
() => $"Stats: Health={health}, Enemies={enemies.Count}, Pos={transform.position}",
LogType.Log
);_logger.Assert(health > 0, "Health must be positive!");_logger.Assert(enemyCount <= 100, "Too many enemies!")
.OnFailure(() => enemyCount = 100);_logger.ValidateNotNull(player, nameof(player));
_logger.ValidateRange(health, 0, 100, nameof(health));_logger.Assert(isInitialized, "Not initialized!")
.PauseEditor() // Pauses Unity Editor (Editor only)
.BreakDebugger(); // Breaks if debugger attachedWindow → IJS Logger → Settings
Features:
- Visual channel configuration with color-coded status
- Enable/disable individual channels
- Set channel scope (Editor/Build/Both)
- Quick actions (Enable All, Disable All, Reset to Defaults)
- About tab with quick start guide
Window → IJS Logger → Log Viewer
Features:
- Captures all Unity logs (not just IJSLogger)
- IJSLogger logs highlighted with special background
- Filter by type (Log, Warning, Error)
- Filter to show only IJSLogger logs
- Search functionality
- Timestamps for all logs
- Auto-scroll option
- Export logs to file
- Adjustable max logs limit (100-5000)
Menu: IJS → Logger → Enable Logs / Disable Logs
When disabled, the USE_LOGS scripting define is removed, and all logging code is completely stripped from builds using [Conditional("USE_LOGS")] attributes.
Logs must pass all these checks to be displayed:
- ✅ Global USE_LOGS (build-time via scripting defines)
- ✅ Channel Enabled (runtime via settings)
- ✅ Channel Scope (Editor/Build/Both)
- ✅ Instance Enabled (runtime via constructor or ToggleLogs)
- ✅ Rate Limiting (runtime per-message throttling)
This hierarchy allows fine-grained control:
- Ship builds with only Error/Warning channels enabled
- Enable Performance logging only in Editor
- Disable specific logger instances at runtime
- Prevent spam with rate limiting
IJSLogger(
string prefix = "",
Color? color = null,
bool logsEnabled = true,
LogChannel channel = LogChannel.Default
)| Method | Description |
|---|---|
PrintLog(message, logType, gameObject) |
Log a message with instance settings |
LogIf(condition, message, logType, gameObject) |
Log only if condition is true |
LogIf(conditionFunc, messageFunc, logType, gameObject) |
Lazy evaluation conditional logging |
LogThrottled(message, minIntervalSeconds, logType, gameObject) |
Rate-limited logging |
Assert(condition, message) |
Assert condition, returns fluent LogAssert |
ValidateNotNull(obj, paramName) |
Validate object is not null |
ValidateRange(value, min, max, paramName) |
Validate value is in range |
EnableLogs() |
Attempts to enable this logger. Returns false when the logger was already disabled and must be recreated |
DisableLogs() |
Disable this logger instance; returns true when it changed state |
ModifyPrefix(prefix) |
Change the log prefix |
ModifyColor(color) |
Change the log color |
| Method | Description |
|---|---|
Log(message, type, gameObject, color) |
Static logging method |
using (new LogContext("ContextName"))
{
// All logs here will include [ContextName] prefix
}logger.Assert(condition, message)
.OnFailure(() => { /* callback */ })
.BreakDebugger()
.PauseEditor(); // Editor only// Usually used internally, but available for custom use
LogRateLimiter.ShouldLog(key, intervalSeconds);
LogRateLimiter.GetSuppressedCount(key);
LogRateLimiter.Clear();Option 1: Via Editor Window
- Open
Window → IJS Logger → Settings - Click "Create Settings Asset"
- Configure channels as needed
Option 2: Via Asset Menu
- Create folder:
Assets/_PackageRoot/Runtime/Resources/ - Right-click →
Create → IJS → Logger Settings - Name it
IJSLoggerSettings - Configure channels
If no settings asset exists:
- All channels are enabled by default
Performancechannel defaults to Editor Only- All other channels default to Both
-
Use Channels: Organize logs by system for better filtering
var audioLogger = IJSLogger.Create("Audio", Color.yellow, true, LogChannel.Audio); var networkLogger = IJSLogger.Create("Network", Color.green, true, LogChannel.Network);
-
Use Contexts: Group related logs together
using (new LogContext("Player Spawning")) { _logger.PrintLog("Creating player"); _logger.PrintLog("Setting position"); }
-
Rate Limit in Loops: Always throttle logs in Update/FixedUpdate
void Update() { _logger.LogThrottled("Update info", 1.0f); }
-
Use Lazy Evaluation: For expensive string operations
_logger.LogIf(() => condition, () => $"Expensive: {CalculateStats()}");
-
Configure Scopes: Set Performance/Debug channels to Editor-only
// In Settings Window or: settings.SetChannelScope(LogChannel.Performance, ChannelScope.EditorOnly);
-
Use Assertions: Validate assumptions during development
_logger.ValidateNotNull(player, nameof(player)); _logger.ValidateRange(health, 0, 100, nameof(health));
-
Color Code Systems: Use consistent colors for each system
var audioLogger = IJSLogger.Create("Audio", Color.yellow, true, LogChannel.Audio); var aiLogger = IJSLogger.Create("AI", Color.magenta, true, LogChannel.AI);
- Zero Cost in Production: When
USE_LOGSis not defined, all logging code is stripped via[Conditional] - Minimal Runtime Cost: Channel checks are simple boolean comparisons
- Smart Rate Limiting: Only tracks messages that are actually rate-limited
- Lazy Evaluation: Expensive operations only execute when logs will be displayed
- No Allocations: Efficient string handling and caching
See Assets/_PackageRoot/Samples~/IJSLoggerExamples.cs for comprehensive examples including:
- Basic logging with channels
- Channel filtering
- Rate limiting
- Log contexts
- Assertions
- Conditional logging
- Complete gameplay scenarios
- Check if
USE_LOGSscripting define is enabled:IJS → Logger → Enable Logs - Verify channel is enabled:
Window → IJS Logger → Settings - Check channel scope matches current environment (Editor vs Build)
- If the logger was created disabled, create a new logger with logging enabled
- For throttled logs, check if rate limit interval has passed
- Ensure settings asset exists in a Resources folder
- Check asset is named
IJSLoggerSettings - Try creating new settings via
Window → IJS Logger → Settings
- Check
Window → IJS Logger → SettingsorLog Viewer - Verify editor assembly is properly referenced
- Reimport package if necessary
The new version is backwards compatible. Existing code will work without changes:
// Existing code still works
var logger = new IJSLogger("MyClass", Color.cyan);
logger.PrintLog("Hello");To avoid allocating unnecessary logger instances when logging is disabled or USE_LOGS is not defined, prefer IJSLogger.Create(...):
// Recommended creation path
var logger = IJSLogger.Create("MyClass", Color.cyan, true, LogChannel.Gameplay);- ✨ Added channel-based filtering system with 12 predefined channels
- ✨ Added ChannelScope support (EditorOnly, BuildOnly, Both)
- ✨ Added smart rate limiting with suppression counting
- ✨ Added log contexts with disposable pattern
- ✨ Added assertion system with fluent API
- ✨ Added conditional logging with lazy evaluation
- ✨ Added Settings window for channel configuration
- ✨ Added Log Viewer window for all Unity logs
- 🔧 Updated constructor to accept LogChannel parameter
- 🔧 Updated PrintLog to support contexts automatically
- 📚 Comprehensive README with examples and API documentation
- 📚 Added example script demonstrating all features
- Initial release with basic logging functionality
- Color-coded logs
- Prefix support
- Instance-level enable/disable
- Global USE_LOGS scripting define control
For issues, questions, or feature requests:
- GitHub: https://github.com/sathyarajshetigar/IJSLogger
- Create an issue with details and example code
See LICENSE file in the repository.
Happy Logging! 🎉
Built with ❤️ by Ironjaw Studios