Added a bunch of utilities and modfief the character data structue

This commit is contained in:
Sebastian Bularca
2026-03-29 18:31:03 +02:00
parent 4a9c00212a
commit ee97b2fec3
110 changed files with 6752 additions and 169 deletions

View File

@@ -0,0 +1,181 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Jovian.Logger {
/// <summary>
/// Default logger for Custom Logger in case you don't want to create a new instance of CustomLogger
/// Recommended is to create a new instance of CustomLogger
/// </summary>
public static class GlobalLogger {
/// <summary>
/// Logs messages that are meant to only be seen in the editor
/// </summary>
/// <param name="msg">Log message</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[Conditional("UNITY_EDITOR")] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogSpam(string msg, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogSpam($"{GetCaller()?.Name}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs messages that are meant to only be seen in the editor with a context object
/// </summary>
/// <param name="msg">Log message</param>
/// <param name="context">Object to select in scene/project</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[Conditional("UNITY_EDITOR")] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogSpam(string msg, UnityEngine.Object context, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogSpam($"{GetCaller()?.Name}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Info/Debug messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogInfo(string msg, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogInfo($"{GetCaller()?.Name}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs standard Info/Debug messagesc with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="context">Object to select in scene/project</param>
/// <param name="logCat"></param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogInfo(string msg, UnityEngine.Object context, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogInfo($"{GetCaller()?.Name}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Warning messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogWarning(string msg, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogWarning($"{GetCaller()?.Name}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs standard Warning messages with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="context">Object to select in scene/project</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogWarning(string msg, UnityEngine.Object context, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogWarning($"{GetCaller()?.Name}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Error messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogError(string msg, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogError($"{GetCaller()?.Name}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs standard Error messages with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="context">Object to select in scene/project</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogError(string msg, UnityEngine.Object context, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogError($"{GetCaller()?.Name}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Assert messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogAssert(string msg, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogAssert($"{GetCaller()?.Name}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs standard Assert messages with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="context">Object to select in scene/project</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogAssert(string msg, UnityEngine.Object context, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogAssert($"{GetCaller()?.Name}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs string Exception messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogException(string msg, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogException($"{GetCaller()?.Name}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs string Exception messages with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="context">Object to select in scene/project</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogException(string msg, UnityEngine.Object context, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogException($"{GetCaller()?.Name}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Exception messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogException(Exception e, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogException($"{GetCaller()?.Name}.{callerMethod}", e.Message, logCat);
}
/// <summary>
/// Logs standard Exception messages with a context object
/// </summary>
/// <param name="e"></param>
/// <param name="context">Object to select in scene/project</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void LogException(Exception e, UnityEngine.Object context, LogCategory logCat, [CallerMemberName] string callerMethod = "") {
InternalLogger.LogException($"{GetCaller()?.Name}.{callerMethod}", e.Message, logCat, context);
}
private static Type GetCaller() {
var stackTrace = new StackTrace();
var stackFrame = stackTrace.GetFrame(2);
var caller = stackFrame?.GetMethod()?.DeclaringType;
return caller;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 351a08a96ffb9654b94e50b31c1058ba

View File

@@ -0,0 +1,193 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using UnityEngine;
using Debug = UnityEngine.Debug;
namespace Jovian.Logger {
internal class LoggerSettingsData {
public bool enableGlobalLogging = true;
public Filters[] globalFilters = Array.Empty<Filters>();
public LoggerColors loggerColors = new();
public bool isLoaded = false;
#if UNITY_EDITOR
public List<Filters> LocalFilters { get; set; } = new();
#endif
}
internal static class InternalLogger {
private static readonly LoggerSettingsData loggerSettingsData = new();
private static bool enableGlobalLogging = true;
internal static bool IsMainThread => LoggerUtility.IsMainThread;
internal static void LoadSettings() {
var loggerSettings = LoggerUtility.LoadCustomLoggerSettings();
if(!loggerSettings) {
return;
}
var colors = loggerSettings.loggerColors;
loggerSettingsData.globalFilters = loggerSettings.globalFilters;
enableGlobalLogging = loggerSettings.enableGlobalLogging;
loggerSettingsData.loggerColors.infoColor = colors.infoColor;
loggerSettingsData.loggerColors.warningColor = colors.warningColor;
loggerSettingsData.loggerColors.errorColor = colors.errorColor;
loggerSettingsData.loggerColors.assertColor = colors.assertColor;
loggerSettingsData.loggerColors.exceptionColor = colors.exceptionColor;
loggerSettingsData.loggerColors.spamColor = colors.spamColor;
#if UNITY_EDITOR
if(!Environment.CommandLine.Contains("-batchmode")) {
loggerSettingsData.LocalFilters = loggerSettings.LocalFilters;
} else {
loggerSettings.LocalFilters.Clear();
}
Debug.Log("[CustomLogger] Local Filters: " + loggerSettingsData.LocalFilters.Count);
#endif
loggerSettingsData.isLoaded = true;
Debug.Log("[CustomLogger] Global Filters: " + loggerSettingsData.globalFilters.Length);
Debug.Log("[CustomLogger] Settings loaded");
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LogInternal(JovianLogType jovianLogType, LogCategory logcat, string classType, string msg, Color color, string args = "", UnityEngine.Object reference = null) {
if(!enableGlobalLogging) {
return;
}
if(!loggerSettingsData.isLoaded && IsMainThread) {
LoadSettings();
}
// Check if any filters are present and apply filters
foreach(var filter in loggerSettingsData.globalFilters) {
if(filter == null) {
continue;
}
if((filter.jovianLogType < jovianLogType || !filter.logCategory.HasFlag(logcat)) || !FilterCaller(filter.callerNames, filter.callerListingType)) {
return;
}
}
#if UNITY_EDITOR
if(!Environment.CommandLine.Contains("-batchmode")) {
foreach(var filter in loggerSettingsData.LocalFilters) {
if(filter == null) {
continue;
}
if((filter.jovianLogType < jovianLogType || !filter.logCategory.HasFlag(logcat)) || !FilterCaller(filter.callerNames, filter.callerListingType)) {
return;
}
}
} else {
if(jovianLogType == JovianLogType.Spam) {
return;
}
}
#endif
StringBuilder sb = new(500);
switch(jovianLogType) {
case JovianLogType.Spam:
sb.Append("SPAM -> ");
break;
case JovianLogType.Info:
sb.Append("INFO -> ");
break;
case JovianLogType.Warning:
sb.Append("WARNING -> ");
break;
case JovianLogType.Error:
sb.Append("ERROR -> ");
break;
case JovianLogType.Assert:
sb.Append("ASSERT -> ");
break;
case JovianLogType.Exception:
sb.Append("EXCEPTION -> ");
break;
default:
return;
}
var isFrameCountEnabled = LoggerUtility.IsFrameCountEnabled;
// As exceptions can be reported to Unity Exception tracking, we do not want frame numbers in the message. It will prevent Unity from grouping reports
var logTypeShouldIncludeFrameCount = jovianLogType != JovianLogType.Exception;
if(isFrameCountEnabled && logTypeShouldIncludeFrameCount) {
sb.Append("F:");
sb.Append(LoggerUtility.FrameCount);
sb.Append(" |");
}
sb.Append(" [");
sb.Append(logcat);
sb.Append("] ");
sb.Append("[");
sb.Append(classType);
sb.Append("] ");
sb.Append(msg);
var message = sb.ToString();
LoggerUtility.FormattedLogCallback?.Invoke((jovianLogType, logcat, message));
#if UNITY_EDITOR
//remove the color when not in the editor to avoid cluttering the log files
if(!Environment.CommandLine.Contains("-batchmode") && IsMainThread) {
message = $"<color=#{ColorUtility.ToHtmlStringRGB(color)}>{message}</color>";
}
#endif
Debug.unityLogger.Log(LoggerUtility.GetLogType(jovianLogType), (object)message, reference);
return;
bool FilterCaller(List<string> filterCallerNames, CallerListingType filterCallerListingType) {
foreach(var caller in filterCallerNames) {
if(!string.IsNullOrEmpty(caller)) {
switch(filterCallerListingType) {
case CallerListingType.Blacklist_Caller:
return !classType.Contains(caller);
case CallerListingType.Whitelist_Caller:
return classType.Contains(caller);
}
}
}
return true;
}
}
[Conditional("UNITY_EDITOR")] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void LogSpam(string caller, string msg, LogCategory logcat, UnityEngine.Object reference = null) {
LogInternal(JovianLogType.Spam, logcat, caller, msg, loggerSettingsData.loggerColors.spamColor, "spam", reference);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void LogInfo(string caller, string msg, LogCategory logcat, UnityEngine.Object reference = null) {
LogInternal(JovianLogType.Info, logcat, caller, msg, loggerSettingsData.loggerColors.infoColor, "", reference);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void LogWarning(string classType, string msg, LogCategory logcat, UnityEngine.Object reference = null) {
LogInternal(JovianLogType.Warning, logcat, classType, msg, loggerSettingsData.loggerColors.warningColor, "", reference);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void LogError(string caller, string msg, LogCategory logcat, UnityEngine.Object reference = null) {
LogInternal(JovianLogType.Error, logcat, caller, msg, loggerSettingsData.loggerColors.errorColor, "", reference);
}
internal static void LogException(string caller, string msg, LogCategory logcat, UnityEngine.Object reference = null) {
LogInternal(JovianLogType.Exception, logcat, caller, msg, loggerSettingsData.loggerColors.exceptionColor, "", reference);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void LogAssert(string caller, string msg, LogCategory logcat, UnityEngine.Object reference = null) {
LogInternal(JovianLogType.Assert, logcat, caller, msg, loggerSettingsData.loggerColors.assertColor, "", reference);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1463245ed08381f4688ab74cc4296ba1

View File

@@ -0,0 +1,14 @@
{
"name": "Jovian.Logger",
"rootNamespace": "",
"references": [],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9e11523c9d4d45445a0938098559d830
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
using System;
namespace Jovian.Logger {
[Flags]
public enum LogCategory {
None = 0,
General = 1,
Editor = 2,
Core = 4,
GameLogic = 8,
UI = 16,
Input = 32,
Network = 64,
Analytics = 128,
Audio = 256,
Graphics = 512,
Physics = 1024,
AI = 2048,
Internal = 4096,
Testing = 8192
}
public enum CallerListingType {
Blacklist_Caller,
Whitelist_Caller
}
public enum JovianLogType {
Exception,
Assert,
Error,
Warning,
Info,
Spam
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: edd66ef15598e84448ef22155f418e66

View File

@@ -0,0 +1,280 @@
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Jovian.Logger {
/// <summary>
/// The recommended way to log messages in Jovian Logger.
/// Create a new instance of this struct in the class you want to log messages.
/// </summary>
public struct Logger {
private string caller;
private LogCategory logCategory;
public Logger(Type caller, LogCategory logCategory) : this() {
this.logCategory = logCategory;
this.caller = caller.Name;
LoggerUtility.PreloadLoggerSettings();
}
private string Caller {
get {
if(string.IsNullOrEmpty(caller)) {
caller = "Unspecified";
}
return caller;
}
}
private LogCategory LogCategory {
get {
if(logCategory == LogCategory.None) {
logCategory = LogCategory.General;
}
return logCategory;
}
}
/// <summary>
/// Logs messages that are meant to only be seen in the editor
/// </summary>
/// <param name="msg">Log message</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[Conditional("UNITY_EDITOR")] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogSpam(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogSpam($"{Caller}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs messages that are meant to only be seen in the editor with a context object
/// </summary>
/// <param name="msg">Log message</param>
/// <param name="context">Object to focus on in scene/project window</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[Conditional("UNITY_EDITOR")] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogSpam(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogSpam($"{Caller}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Info/Debug messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogInfo(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogInfo($"{Caller}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs standard Info/Debug messages with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="context">Object to focus on in scene/project window</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogInfo(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogInfo($"{Caller}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Warning messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogWarning(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogWarning($"{Caller}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs standard Warning messages with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="context">Object to focus on in scene/project window</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogWarning(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogWarning($"{Caller}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Error messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogError(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogError($"{Caller}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs standard Error messages with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="context">Object to focus on in scene/project window</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogError(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogError($"{Caller}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Assert messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogAssert(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogAssert($"{Caller}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs standard Assert messages with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="context">Object to focus on in scene/project window</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogAssert(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogAssert($"{Caller}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs string Exception messages
/// </summary>
/// <param name="msg"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogException(string msg, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogException($"{Caller}.{callerMethod}", msg, logCat);
}
/// <summary>
/// Logs string Exception messages with a context object
/// </summary>
/// <param name="msg"></param>
/// <param name="context">Object to focus on in scene/project window</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogException(string msg, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogException($"{Caller}.{callerMethod}", msg, logCat, context);
}
/// <summary>
/// Logs standard Exception messages
/// </summary>
/// <param name="e"></param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogException(Exception e, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogException($"{Caller}.{callerMethod}", e.Message, logCat);
}
/// <summary>
/// Logs standard Exception messages with a context object
/// </summary>
/// <param name="e"></param>
/// <param name="context">Object to focus on in scene/project window</param>
/// <param name="logCat">Optional Log Category</param>
/// <param name="callerMethod">Implicit, do not provide</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogException(Exception e, UnityEngine.Object context, LogCategory logCat = LogCategory.None, [CallerMemberName] string callerMethod = "") {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
InternalLogger.LogException($"{Caller}.{callerMethod}", e.Message, logCat, context);
}
/// <summary>
/// Logs a watch variable that updates in place in the Custom Console instead of creating new entries.
/// Ideal for values that change every frame (positions, FPS, state names, etc.).
/// </summary>
/// <param name="key">Unique identifier for this watch (e.g. "playerPos", "fps")</param>
/// <param name="value">The current value to display</param>
/// <param name="logCat">Optional Log Category</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Watch(string key, object value, LogCategory logCat = LogCategory.None) {
if(logCat == LogCategory.None) {
logCat = LogCategory;
}
LoggerUtility.WatchCallback?.Invoke((key, value?.ToString() ?? "null", logCat));
}
/// <summary>
/// Removes a watch variable from the Custom Console.
/// </summary>
/// <param name="key">The watch key to remove</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Unwatch(string key) {
LoggerUtility.UnwatchCallback?.Invoke(key);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fb1cfb4712ed8a649a051a11d0aa17f5

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Jovian.Logger {
public class LoggerSettings : ScriptableObject {
public bool enableGlobalLogging = true;
public Filters[] globalFilters = Array.Empty<Filters>();
public LoggerColors loggerColors = new();
#if UNITY_EDITOR
public List<Filters> LocalFilters { get; set; } = new();
#endif
public void ResetColorsToDefault() {
loggerColors = new LoggerColors();
}
}
[Serializable]
public class LoggerColors {
public Color infoColor = Color.white;
public Color warningColor = Color.yellow;
public Color errorColor = Color.red;
public Color assertColor = new(1f, 0.3f, 0.2f);
public Color exceptionColor = new(1f, 0.0f, 0.7f);
public Color spamColor = Color.grey;
}
[Serializable]
public class Filters {
public LogCategory logCategory = (LogCategory)~0;
public JovianLogType jovianLogType = JovianLogType.Spam;
public List<string> callerNames = new();
public CallerListingType callerListingType = CallerListingType.Blacklist_Caller;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 67fe3f48aa2b3b349a7b99381bebb12d

View File

@@ -0,0 +1,152 @@
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
namespace Jovian.Logger {
public static class LoggerUtility {
private static LoggerSettings loggerSettings;
private static bool setByFrameCount = true;
private static int frameCount;
private static bool isLoaded;
private static readonly int mainThreadId = Thread.CurrentThread.ManagedThreadId;
/// <summary>
/// Returns true if the current code is executing on the main Unity thread.
/// </summary>
public static bool IsMainThread => Thread.CurrentThread.ManagedThreadId == mainThreadId;
/// <summary>
/// Callback to post formatted log messages.
/// </summary>
public static Action<(JovianLogType, LogCategory, string)> FormattedLogCallback { get; set; }
/// <summary>
/// Callback for watch-mode logs that update in place instead of creating new entries.
/// Parameters: (watchKey, value, logCategory)
/// </summary>
public static Action<(string key, string value, LogCategory category)> WatchCallback { get; set; }
/// <summary>
/// Callback when a watch key is removed.
/// </summary>
public static Action<string> UnwatchCallback { get; set; }
/// <summary>
/// If enabled it will show either the Unity's Time.frameCount or the custom frame count set by the user. This is a global setting.
/// </summary>
public static int FrameCount {
get {
if(setByFrameCount) {
try {
frameCount = Time.frameCount;
} catch(UnityException) {
// Time.frameCount can only be called from the main thread.
// Return last known value when called from a background thread.
}
}
return frameCount;
}
set {
frameCount = value;
setByFrameCount = false;
}
}
public static LoggerSettings LoadCustomLoggerSettings() {
try {
if(isLoaded) {
return loggerSettings;
}
loggerSettings = Resources.Load<LoggerSettings>("logger-settings");
isLoaded = true;
return loggerSettings;
} catch {
//Debug.Log($"[Exception] LoggerSettings could not be loaded.");
}
return null;
}
/// <summary>
/// Toggle the frame counting feature to be included with the logs on/off
/// </summary>
/// <param name="enable"></param>
public static void ToggleFrameCount(bool enable) {
IsFrameCountEnabled = enable;
}
/// <summary>
/// Enable/Disable logging globally
/// </summary>
/// <param name="enable"></param>
public static void ToggleLogging(bool enable) {
loggerSettings.enableGlobalLogging = enable;
}
/// <summary>
/// Check if the frame count feature is enabled
/// </summary>
public static bool IsFrameCountEnabled { get; private set; }
/// <summary>
/// Load settings preemptively to avoid asynchronous loading for unity assets.
/// </summary>
public static void PreloadLoggerSettings() {
try {
LoadCustomLoggerSettings();
} catch(Exception e) {
Debug.Log($"[Exception] LoggerSettings could not be loaded. {e}");
}
}
/// <summary>
/// Adds a custom filter programmatically. Useful for setting up remote filters
/// </summary>
/// <param name="logCategory">Required set of log categories</param>
/// <param name="jovianLogLevel">The log level, default being level 4, Exception(in unity it is the base log level)</param>
/// <param name="callerNames">A list of classes that should be watched, default null</param>
/// <param name="clearAll">If all preexisting filters should be first removed</param>
public static void AddFilter(LogCategory logCategory, JovianLogType jovianLogLevel = JovianLogType.Spam, List<string> callerNames = null, bool clearAll = false) {
var filter = new Filters() {
jovianLogType = jovianLogLevel,
logCategory = logCategory,
callerNames = callerNames
};
var loggerSettings = LoadCustomLoggerSettings();
if(clearAll) {
loggerSettings.globalFilters = new Filters[1];
loggerSettings.globalFilters[0] = filter;
InternalLogger.LoadSettings();
return;
}
var filters = new Filters[loggerSettings.globalFilters.Length + 1];
for(var i = 0; i < loggerSettings.globalFilters.Length; i++) {
filters[i] = loggerSettings.globalFilters[i];
}
filters[^1] = filter;
loggerSettings.globalFilters = filters;
InternalLogger.LoadSettings();
}
public static void ReloadLoggerSettings() {
InternalLogger.LoadSettings();
}
public static UnityEngine.LogType GetLogType(JovianLogType jovianLogType) {
return jovianLogType switch {
JovianLogType.Info => UnityEngine.LogType.Log,
JovianLogType.Warning => UnityEngine.LogType.Warning,
JovianLogType.Error => UnityEngine.LogType.Error,
JovianLogType.Assert => UnityEngine.LogType.Assert,
JovianLogType.Exception => UnityEngine.LogType.Exception,
JovianLogType.Spam => UnityEngine.LogType.Log,
_ => UnityEngine.LogType.Log
};
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 89f12f254c1d11e418ec326092b0b049

View File

@@ -0,0 +1,270 @@
using System;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
namespace Jovian.Logger {
/// <summary>
/// Sends log messages to the Custom Console editor window over TCP.
/// Add this component to a GameObject in your scene (or create one at runtime)
/// to enable remote logging from device builds.
/// </summary>
public class RemoteLogSender : MonoBehaviour {
[Tooltip("IP address of the machine running the Unity Editor")]
[SerializeField] private string editorHost = "127.0.0.1";
[Tooltip("Port the Custom Console is listening on")]
[SerializeField] private int editorPort = 9876;
[Tooltip("Automatically connect on start")]
[SerializeField] private bool autoConnect = true;
[Tooltip("Retry connection every N seconds when disconnected")]
[SerializeField] private float reconnectInterval = 5f;
private TcpClient client;
private NetworkStream stream;
private readonly ConcurrentQueue<string> sendQueue = new ConcurrentQueue<string>();
private Thread sendThread;
private volatile bool running;
private volatile bool connected;
private float reconnectTimer;
private static RemoteLogSender instance;
/// <summary>Whether the sender is currently connected to the editor.</summary>
public bool IsConnected => connected;
/// <summary>The editor host address.</summary>
public string EditorHost {
get => editorHost;
set => editorHost = value;
}
/// <summary>The editor port.</summary>
public int EditorPort {
get => editorPort;
set => editorPort = value;
}
private void Awake() {
if (instance != null && instance != this) {
Destroy(gameObject);
return;
}
instance = this;
DontDestroyOnLoad(gameObject);
}
private void OnEnable() {
Application.logMessageReceivedThreaded += OnUnityLogReceived;
LoggerUtility.FormattedLogCallback += OnJovianLogReceived;
LoggerUtility.WatchCallback += OnWatch;
LoggerUtility.UnwatchCallback += OnUnwatch;
if (autoConnect) {
Connect();
}
}
private void OnDisable() {
Application.logMessageReceivedThreaded -= OnUnityLogReceived;
LoggerUtility.FormattedLogCallback -= OnJovianLogReceived;
LoggerUtility.WatchCallback -= OnWatch;
LoggerUtility.UnwatchCallback -= OnUnwatch;
Disconnect();
}
private void Update() {
if (!connected && autoConnect) {
reconnectTimer += Time.unscaledDeltaTime;
if (reconnectTimer >= reconnectInterval) {
reconnectTimer = 0f;
Connect();
}
}
}
/// <summary>Connect to the Custom Console editor.</summary>
public void Connect() {
if (connected) return;
try {
client = new TcpClient();
client.NoDelay = true;
client.SendTimeout = 2000;
var result = client.BeginConnect(editorHost, editorPort, null, null);
bool success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(2));
if (!success || !client.Connected) {
client.Close();
client = null;
return;
}
client.EndConnect(result);
stream = client.GetStream();
connected = true;
running = true;
sendThread = new Thread(SendLoop) {
IsBackground = true,
Name = "RemoteLogSender"
};
sendThread.Start();
// Send handshake
EnqueueMessage("{\"handshake\":true,\"app\":\"" + Application.productName + "\",\"platform\":\"" + Application.platform + "\"}");
Debug.Log($"[RemoteLog] Connected to editor at {editorHost}:{editorPort}");
} catch (Exception e) {
Debug.LogWarning($"[RemoteLog] Failed to connect: {e.Message}");
CleanupConnection();
}
}
/// <summary>Disconnect from the editor.</summary>
public void Disconnect() {
running = false;
CleanupConnection();
}
private void CleanupConnection() {
connected = false;
try { stream?.Close(); } catch { }
try { client?.Close(); } catch { }
stream = null;
client = null;
}
private void OnJovianLogReceived((JovianLogType logType, LogCategory logCategory, string message) log) {
if (!connected) return;
var json = BuildLogJson(log.message, "", (int)log.logType, (int)log.logCategory, true,
DateTime.Now.ToString("HH:mm:ss.fff"), Time.frameCount);
EnqueueMessage(json);
}
private static readonly string[] LoggerPrefixes = {
"INFO -> ", "ERROR -> ", "WARNING -> ",
"EXCEPTION -> ", "ASSERT -> ", "SPAM -> "
};
private static bool IsLoggerFormattedMessage(string condition) {
foreach (var prefix in LoggerPrefixes) {
if (condition.StartsWith(prefix, StringComparison.Ordinal)) return true;
}
if (condition.StartsWith("<color=#", StringComparison.Ordinal) && condition.Length > 22) {
var afterTag = condition.AsSpan(15);
foreach (var prefix in LoggerPrefixes) {
if (afterTag.StartsWith(prefix.AsSpan(), StringComparison.Ordinal)) return true;
}
}
return false;
}
private void OnUnityLogReceived(string condition, string stackTrace, UnityEngine.LogType type) {
if (!connected) return;
// Skip logger-formatted messages — those come via OnCustomLog
if (IsLoggerFormattedMessage(condition)) return;
int loggerTyper = type switch {
LogType.Error => (int)JovianLogType.Error,
LogType.Assert => (int)JovianLogType.Assert,
LogType.Warning => (int)JovianLogType.Warning,
LogType.Exception => (int)JovianLogType.Exception,
_ => (int)JovianLogType.Info,
};
string message = condition;
if (type is LogType.Error or LogType.Assert or LogType.Exception && !string.IsNullOrEmpty(stackTrace)) {
message = $"{condition}\n{stackTrace}";
}
var json = BuildLogJson(message, stackTrace ?? "", loggerTyper, (int)LogCategory.General, false,
DateTime.Now.ToString("HH:mm:ss.fff"), Time.frameCount);
EnqueueMessage(json);
}
private void OnWatch((string key, string value, LogCategory category) watch) {
if (!connected) return;
var sb = new StringBuilder(128);
sb.Append("{\"watch\":true,\"wk\":");
AppendJsonString(sb, watch.key);
sb.Append(",\"wv\":");
AppendJsonString(sb, watch.value);
sb.Append(",\"c\":").Append((int)watch.category);
sb.Append(",\"ts\":");
AppendJsonString(sb, DateTime.Now.ToString("HH:mm:ss.fff"));
sb.Append(",\"fc\":").Append(Time.frameCount);
sb.Append('}');
EnqueueMessage(sb.ToString());
}
private void OnUnwatch(string key) {
if (!connected) return;
var sb = new StringBuilder(64);
sb.Append("{\"unwatch\":true,\"wk\":");
AppendJsonString(sb, key);
sb.Append('}');
EnqueueMessage(sb.ToString());
}
private static string BuildLogJson(string message, string stackTrace, int type, int category, bool isCustom, string timestamp, int frame) {
// Manual JSON building to avoid allocations from JsonUtility
var sb = new StringBuilder(256);
sb.Append("{\"m\":");
AppendJsonString(sb, message);
sb.Append(",\"s\":");
AppendJsonString(sb, stackTrace);
sb.Append(",\"t\":").Append(type);
sb.Append(",\"c\":").Append(category);
sb.Append(",\"f\":").Append(isCustom ? "true" : "false");
sb.Append(",\"ts\":");
AppendJsonString(sb, timestamp);
sb.Append(",\"fc\":").Append(frame);
sb.Append('}');
return sb.ToString();
}
private static void AppendJsonString(StringBuilder sb, string value) {
if (value == null) { sb.Append("\"\""); return; }
sb.Append('"');
foreach (char c in value) {
switch (c) {
case '"': sb.Append("\\\""); break;
case '\\': sb.Append("\\\\"); break;
case '\n': sb.Append("\\n"); break;
case '\r': sb.Append("\\r"); break;
case '\t': sb.Append("\\t"); break;
default: sb.Append(c); break;
}
}
sb.Append('"');
}
private void EnqueueMessage(string json) {
// Cap queue size to prevent memory issues if sending is slow
if (sendQueue.Count < 10000) {
sendQueue.Enqueue(json);
}
}
private void SendLoop() {
while (running) {
try {
if (sendQueue.TryDequeue(out string json)) {
byte[] data = Encoding.UTF8.GetBytes(json + "\n");
stream.Write(data, 0, data.Length);
} else {
Thread.Sleep(5);
}
} catch (Exception) {
running = false;
connected = false;
break;
}
}
}
private void OnDestroy() {
if (instance == this) instance = null;
Disconnect();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 690a449164ff86c49b1c0c9c3b4ef3b1