First commit
This commit is contained in:
8
Packages/com.jovian.logger/Editor.meta
Normal file
8
Packages/com.jovian.logger/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61591a804f18a0c4684be901a22095d9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
2224
Packages/com.jovian.logger/Editor/CustomConsole.cs
Normal file
2224
Packages/com.jovian.logger/Editor/CustomConsole.cs
Normal file
File diff suppressed because it is too large
Load Diff
2
Packages/com.jovian.logger/Editor/CustomConsole.cs.meta
Normal file
2
Packages/com.jovian.logger/Editor/CustomConsole.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd8ce1f3fd630a147b25c4578865941e
|
||||
18
Packages/com.jovian.logger/Editor/Jovian.LoggerEditor.asmdef
Normal file
18
Packages/com.jovian.logger/Editor/Jovian.LoggerEditor.asmdef
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "Jovian.LoggerEditor",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:9e11523c9d4d45445a0938098559d830"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 206337e0b2cdd1b448d7d752a7ca77e8
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
67
Packages/com.jovian.logger/Editor/JovianProjectSettings.cs
Normal file
67
Packages/com.jovian.logger/Editor/JovianProjectSettings.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.IO;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.ProjectSettings {
|
||||
/// <summary>
|
||||
/// Reads and writes settings to ProjectSettings/CustomLoggerSettings.json.
|
||||
/// Flat key-value store. Use prefixed keys by convention (e.g. "logger.myKey").
|
||||
/// </summary>
|
||||
internal static class JovianProjectSettings {
|
||||
private const string FileName = "CustomLoggerSettings.json";
|
||||
|
||||
private static string FilePath {
|
||||
get {
|
||||
var fullName = Directory.GetParent(Application.dataPath)?.FullName;
|
||||
return Path.Combine(fullName ?? Application.dataPath, "ProjectSettings", FileName);
|
||||
}
|
||||
}
|
||||
|
||||
internal static T Get<T>(string packagePrefix, string setting, T defaultValue) {
|
||||
var root = LoadRoot();
|
||||
var key = $"{packagePrefix}.{setting}";
|
||||
if (root.TryGetValue(key, out var token)) {
|
||||
try {
|
||||
return token.ToObject<T>();
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
internal static void Set<T>(string packagePrefix, string setting, T value) {
|
||||
var root = LoadRoot();
|
||||
var key = $"{packagePrefix}.{setting}";
|
||||
root[key] = value != null ? JToken.FromObject(value) : JValue.CreateNull();
|
||||
SaveRoot(root);
|
||||
}
|
||||
|
||||
private static JObject LoadRoot() {
|
||||
string path = FilePath;
|
||||
if (!File.Exists(path)) {
|
||||
return new JObject();
|
||||
}
|
||||
|
||||
try {
|
||||
string json = File.ReadAllText(path);
|
||||
return JObject.Parse(json);
|
||||
} catch {
|
||||
return new JObject();
|
||||
}
|
||||
}
|
||||
|
||||
private static void SaveRoot(JObject root) {
|
||||
string path = FilePath;
|
||||
string dir = Path.GetDirectoryName(path);
|
||||
if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) {
|
||||
Directory.CreateDirectory(dir);
|
||||
}
|
||||
|
||||
string json = root.ToString(Formatting.Indented);
|
||||
File.WriteAllText(path, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eff2fe1736b6efc40b7e52153c3b1010
|
||||
271
Packages/com.jovian.logger/Editor/LoggerSettingsEditor.cs
Normal file
271
Packages/com.jovian.logger/Editor/LoggerSettingsEditor.cs
Normal file
@@ -0,0 +1,271 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Jovian.Logger {
|
||||
[CustomEditor(typeof(LoggerSettings))]
|
||||
internal class LoggerSettingsEditor : Editor {
|
||||
private static SerializedObject LoggerSettingsObj;
|
||||
|
||||
private SerializedProperty enableGlobalLoggingProp;
|
||||
private SerializedProperty loggerColorsProp;
|
||||
|
||||
|
||||
// For loggerColors sub-fields
|
||||
private SerializedProperty infoColorProp;
|
||||
private SerializedProperty warningColorProp;
|
||||
private SerializedProperty errorColorProp;
|
||||
private SerializedProperty assertColorProp;
|
||||
private SerializedProperty exceptionColorProp;
|
||||
private SerializedProperty spamColorProp;
|
||||
|
||||
private static Editor editor;
|
||||
private bool showLocalFilters = true;
|
||||
private bool showAllFilters = true;
|
||||
private bool showGlobalFilters = true;
|
||||
private string globalCallerNames = "";
|
||||
private string localCallerNames = "";
|
||||
private bool hasGlobalSavedFilter = true;
|
||||
private bool hasLocalSavedFilter = true;
|
||||
|
||||
private class ListOfFilters {
|
||||
public List<Filters> filters;
|
||||
}
|
||||
|
||||
private void LoadLocalFilters() {
|
||||
var loggerSettings = (LoggerSettings)target;
|
||||
var filters = EditorPrefs.GetString("LoggerFilters");
|
||||
if(UnityIsHeadless()) {
|
||||
filters = null;
|
||||
}
|
||||
if(!string.IsNullOrEmpty(filters)) {
|
||||
loggerSettings.LocalFilters = JsonConvert.DeserializeObject<ListOfFilters>(filters).filters;
|
||||
}
|
||||
|
||||
if(loggerSettings.LocalFilters == null) {
|
||||
loggerSettings.LocalFilters = new List<Filters>(0);
|
||||
}
|
||||
}
|
||||
|
||||
private bool UnityIsHeadless() {
|
||||
if(Environment.CommandLine.Contains("-batchmode")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void OnEnable() {
|
||||
enableGlobalLoggingProp = serializedObject.FindProperty("enableGlobalLogging");
|
||||
loggerColorsProp = serializedObject.FindProperty("loggerColors");
|
||||
|
||||
infoColorProp = loggerColorsProp.FindPropertyRelative("infoColor");
|
||||
warningColorProp = loggerColorsProp.FindPropertyRelative("warningColor");
|
||||
errorColorProp = loggerColorsProp.FindPropertyRelative("errorColor");
|
||||
assertColorProp = loggerColorsProp.FindPropertyRelative("assertColor");
|
||||
exceptionColorProp = loggerColorsProp.FindPropertyRelative("exceptionColor");
|
||||
spamColorProp = loggerColorsProp.FindPropertyRelative("spamColor");
|
||||
|
||||
LoadLocalFilters();
|
||||
}
|
||||
|
||||
public static void ShowSettings() {
|
||||
var loggerSettings = new AssetSettingsLoader<LoggerSettings>().GetSettings(LoggerSettingsProvider.SETTINGS_FILE);
|
||||
LoggerSettingsObj = new SerializedObject(loggerSettings);
|
||||
LoggerSettingsObj.Update();
|
||||
editor ??= CreateEditor(loggerSettings);
|
||||
editor.OnInspectorGUI();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI() {
|
||||
serializedObject.Update();
|
||||
var loggerSettings = (LoggerSettings)target;
|
||||
|
||||
EditorGUILayout.Space(15);
|
||||
EditorGUILayout.LabelField("Custom Logger Settings", EditorStyles.whiteLargeLabel);
|
||||
EditorGUILayout.Space(15);
|
||||
EditorGUILayout.HelpBox("This will enable/disable logging for the entire project. It has a code level method as well.", MessageType.Warning);
|
||||
EditorGUILayout.Space(3);
|
||||
EditorGUILayout.PropertyField(enableGlobalLoggingProp, new GUIContent("Enable Global Logging"));
|
||||
|
||||
EditorGUILayout.Space(15);
|
||||
EditorGUILayout.LabelField("Log Message Colors", EditorStyles.whiteLargeLabel);
|
||||
EditorGUILayout.Space(3);
|
||||
EditorGUILayout.PropertyField(infoColorProp, new GUIContent("Info Color"));
|
||||
EditorGUILayout.PropertyField(warningColorProp, new GUIContent("Warning Color"));
|
||||
EditorGUILayout.PropertyField(errorColorProp, new GUIContent("Error Color"));
|
||||
EditorGUILayout.PropertyField(assertColorProp, new GUIContent("Assert Color"));
|
||||
EditorGUILayout.PropertyField(exceptionColorProp, new GUIContent("Exception Color"));
|
||||
EditorGUILayout.PropertyField(spamColorProp, new GUIContent("Spam Color"));
|
||||
|
||||
EditorGUILayout.Space(10);
|
||||
if(GUILayout.Button("Reset To Default Colors")) {
|
||||
loggerSettings.ResetColorsToDefault();
|
||||
EditorUtility.SetDirty(loggerSettings);
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(20);
|
||||
EditorGUILayout.LabelField("Log Uploader Settings", EditorStyles.whiteLargeLabel);
|
||||
EditorGUILayout.Space(3);
|
||||
|
||||
EditorGUILayout.Space(30);
|
||||
showAllFilters = EditorGUILayout.Foldout(showAllFilters, "Logger Filters");
|
||||
if(showAllFilters) {
|
||||
EditorGUILayout.Space(20);
|
||||
EditorGUILayout.LabelField("Global Project Level Filters", EditorStyles.whiteLargeLabel);
|
||||
EditorGUILayout.Space(3);
|
||||
EditorGUILayout.HelpBox("Global filters will be saved with the asset and, if commited, applied to everyone using the project", MessageType.Warning);
|
||||
EditorGUILayout.Space(3);
|
||||
showGlobalFilters = EditorGUILayout.Foldout(showGlobalFilters, "Active Filters");
|
||||
if(showGlobalFilters) {
|
||||
for(int i = 0; i < loggerSettings.globalFilters.Length; i++) {
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
loggerSettings.globalFilters[i].logCategory = (LogCategory)EditorGUILayout.EnumFlagsField("Log Category", loggerSettings.globalFilters[i].logCategory);
|
||||
loggerSettings.globalFilters[i].jovianLogType = (JovianLogType)EditorGUILayout.EnumPopup("Log Type", loggerSettings.globalFilters[i].jovianLogType);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
globalCallerNames = GetCallersAsString(loggerSettings.globalFilters[i].callerNames);
|
||||
globalCallerNames = EditorGUILayout.TextField("Caller Name", globalCallerNames);
|
||||
loggerSettings.globalFilters[i].callerListingType = (CallerListingType)EditorGUILayout.EnumPopup("", loggerSettings.globalFilters[i].callerListingType, GUILayout.Width(120));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
loggerSettings.globalFilters[i].callerNames = globalCallerNames.Split(",").ToList();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if(GUILayout.Button("Remove This Filter")) {
|
||||
var temp = loggerSettings.globalFilters.ToList();
|
||||
temp.RemoveAt(i);
|
||||
loggerSettings.globalFilters = temp.ToArray();
|
||||
EditorUtility.SetDirty(loggerSettings);
|
||||
AssetDatabase.SaveAssets();
|
||||
EditorUtility.RequestScriptReload();
|
||||
}
|
||||
|
||||
if(GUILayout.Button("Save Changes")) {
|
||||
hasGlobalSavedFilter = true;
|
||||
EditorUtility.SetDirty(loggerSettings);
|
||||
AssetDatabase.SaveAssets();
|
||||
EditorUtility.RequestScriptReload();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (!hasGlobalSavedFilter) {
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.HelpBox("Filter Not Saved...", MessageType.Error);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if(GUILayout.Button("Add Global Filter")) {
|
||||
var temp = loggerSettings.globalFilters.ToList();
|
||||
temp.Add(new Filters());
|
||||
loggerSettings.globalFilters = temp.ToArray();
|
||||
hasGlobalSavedFilter = false;
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
EditorGUILayout.Space(20);
|
||||
EditorGUILayout.LabelField("Local Editor Level Filters (Not Serialized)", EditorStyles.whiteLargeLabel);
|
||||
EditorGUILayout.Space(3);
|
||||
EditorGUILayout.HelpBox("Local filters are local and will not be saved with the asset but rather with the editor settings. Use these to set personal filters.", MessageType.Info);
|
||||
EditorGUILayout.Space(3);
|
||||
showLocalFilters = EditorGUILayout.Foldout(showLocalFilters, "Active Filters");
|
||||
if(showLocalFilters) {
|
||||
for(int i = 0; i < loggerSettings.LocalFilters.Count; i++) {
|
||||
EditorGUILayout.BeginVertical("box");
|
||||
EditorGUI.BeginChangeCheck();
|
||||
loggerSettings.LocalFilters[i].logCategory = (LogCategory)EditorGUILayout.EnumFlagsField("Log Category", loggerSettings.LocalFilters[i].logCategory);
|
||||
loggerSettings.LocalFilters[i].jovianLogType = (JovianLogType)EditorGUILayout.EnumPopup("Log Type", loggerSettings.LocalFilters[i].jovianLogType);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
localCallerNames = GetCallersAsString(loggerSettings.LocalFilters[i].callerNames);
|
||||
localCallerNames = EditorGUILayout.TextField("Caller Name", localCallerNames, GUILayout.MinWidth(400));
|
||||
loggerSettings.LocalFilters[i].callerListingType = (CallerListingType)EditorGUILayout.EnumPopup("", loggerSettings.LocalFilters[i].callerListingType, GUILayout.Width(120));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
loggerSettings.LocalFilters[i].callerNames = localCallerNames.Split(",").ToList();
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if(GUILayout.Button("Remove This Filter")) {
|
||||
var temp = new List<Filters>(loggerSettings.LocalFilters);
|
||||
temp.RemoveAt(i);
|
||||
loggerSettings.LocalFilters = temp;
|
||||
var filters = JsonConvert.SerializeObject(new ListOfFilters() { filters = loggerSettings.LocalFilters });
|
||||
EditorPrefs.SetString("LoggerFilters", filters);
|
||||
EditorUtility.RequestScriptReload();
|
||||
}
|
||||
|
||||
if(GUILayout.Button("Save Changes")) {
|
||||
var filters = JsonConvert.SerializeObject(new ListOfFilters() { filters = loggerSettings.LocalFilters });
|
||||
EditorPrefs.SetString("LoggerFilters", filters);
|
||||
EditorUtility.RequestScriptReload();
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
if (!hasLocalSavedFilter) {
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.HelpBox("Filter Not Saved...", MessageType.Error);
|
||||
EditorGUILayout.EndHorizontal();
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndVertical();
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if(GUILayout.Button("Add Local Filter")) {
|
||||
loggerSettings.LocalFilters.Add(new Filters());
|
||||
hasLocalSavedFilter = false;
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
private string GetCallersAsString(List<string> callerNames) {
|
||||
if(callerNames == null || callerNames.Count == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
string result = "";
|
||||
for(int i = 0; i < callerNames.Count; i++) {
|
||||
result += callerNames[i];
|
||||
if(i != callerNames.Count - 1) {
|
||||
result += ",";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void OnDisable() {
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
private void OnDestroy() {
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e97c6cb58b8e0f4f990c51f841842af
|
||||
60
Packages/com.jovian.logger/Editor/LoggerSettingsProvider.cs
Normal file
60
Packages/com.jovian.logger/Editor/LoggerSettingsProvider.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.Logger {
|
||||
internal sealed class LoggerSettingsProvider : SettingsProvider {
|
||||
public LoggerSettingsProvider(string path, SettingsScope scopes, IEnumerable<string> keywords = null) : base(path, scopes, keywords) { }
|
||||
|
||||
public const string SETTINGS_FILE = "Assets/Settings/Resources/logger-settings.asset";
|
||||
|
||||
[SettingsProvider]
|
||||
public static SettingsProvider CreateSettingsProvider() {
|
||||
var provider = new SettingsProvider("Project/Jovian/Logger", SettingsScope.Project) {
|
||||
guiHandler = (searchContext) => { LoggerSettingsEditor.ShowSettings(); }
|
||||
};
|
||||
return provider;
|
||||
}
|
||||
|
||||
private class ConfigLoader : AssetPostprocessor {
|
||||
private static AssetSettingsLoader<LoggerSettings> loader;
|
||||
|
||||
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
|
||||
loader ??= new AssetSettingsLoader<LoggerSettings>();
|
||||
loader.GetSettings(SETTINGS_FILE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AssetSettingsLoader<T> where T : ScriptableObject {
|
||||
private T settings;
|
||||
|
||||
/// <summary>
|
||||
/// Get the settings file if cached/existent. If it doesn't exist, it will create one.
|
||||
/// </summary>
|
||||
/// <param name="settingsFilePath"></param>
|
||||
/// <returns></returns>
|
||||
public T GetSettings(string settingsFilePath) {
|
||||
return settings ?? SetSettings(settingsFilePath);
|
||||
}
|
||||
|
||||
private T SetSettings(string settingsFilePath) {
|
||||
if(!Directory.Exists(Path.GetDirectoryName(settingsFilePath))) {
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(settingsFilePath));
|
||||
}
|
||||
|
||||
settings = AssetDatabase.LoadAssetAtPath<T>(settingsFilePath);
|
||||
if(settings != null) {
|
||||
return settings;
|
||||
}
|
||||
|
||||
settings = ScriptableObject.CreateInstance<T>();
|
||||
AssetDatabase.CreateAsset(settings, settingsFilePath);
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
settings = AssetDatabase.LoadAssetAtPath<T>(settingsFilePath);
|
||||
return settings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 360fda9a575ab7b4eaf523256ac39663
|
||||
245
Packages/com.jovian.logger/Editor/RemoteLogReceiver.cs
Normal file
245
Packages/com.jovian.logger/Editor/RemoteLogReceiver.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Jovian.Logger {
|
||||
/// <summary>
|
||||
/// TCP server that receives log messages from RemoteLogSender on device builds.
|
||||
/// Managed by Custom Console — not intended for standalone use.
|
||||
/// </summary>
|
||||
internal static class RemoteLogReceiver {
|
||||
public const int DefaultPort = 9876;
|
||||
|
||||
public struct RemoteLogEntry {
|
||||
public string message;
|
||||
public string stackTrace;
|
||||
public string timestamp;
|
||||
public int frameCount;
|
||||
public JovianLogType type;
|
||||
public LogCategory logCategory;
|
||||
public bool isCustomLog;
|
||||
}
|
||||
|
||||
public struct RemoteWatchEntry {
|
||||
public string key;
|
||||
public string value;
|
||||
public LogCategory logCategory;
|
||||
public string timestamp;
|
||||
public int frameCount;
|
||||
}
|
||||
|
||||
public static event Action<RemoteLogEntry> OnRemoteLog;
|
||||
public static event Action<RemoteWatchEntry> OnRemoteWatch;
|
||||
public static event Action<string> OnRemoteUnwatch;
|
||||
public static event Action<string> OnClientConnected;
|
||||
public static event Action OnClientDisconnected;
|
||||
|
||||
private static TcpListener listener;
|
||||
private static Thread listenThread;
|
||||
private static volatile bool running;
|
||||
private static int port = DefaultPort;
|
||||
|
||||
public static bool IsRunning => running;
|
||||
public static int Port => port;
|
||||
public static string ConnectedClientName { get; private set; } = "";
|
||||
public static bool HasClient { get; private set; }
|
||||
|
||||
private static readonly ConcurrentQueue<Action> mainThreadQueue = new ConcurrentQueue<Action>();
|
||||
|
||||
public static void Start(int listenPort = DefaultPort) {
|
||||
if (running) return;
|
||||
port = listenPort;
|
||||
|
||||
try {
|
||||
listener = new TcpListener(IPAddress.Any, port);
|
||||
listener.Start();
|
||||
running = true;
|
||||
|
||||
listenThread = new Thread(ListenLoop) {
|
||||
IsBackground = true,
|
||||
Name = "RemoteLogReceiver"
|
||||
};
|
||||
listenThread.Start();
|
||||
|
||||
EditorApplication.update += ProcessMainThreadQueue;
|
||||
Debug.Log($"[RemoteLog] Listening on port {port}");
|
||||
} catch (Exception e) {
|
||||
Debug.LogError($"[RemoteLog] Failed to start listener: {e.Message}");
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop() {
|
||||
running = false;
|
||||
HasClient = false;
|
||||
ConnectedClientName = "";
|
||||
|
||||
try { listener?.Stop(); } catch { }
|
||||
listener = null;
|
||||
|
||||
EditorApplication.update -= ProcessMainThreadQueue;
|
||||
}
|
||||
|
||||
private static void ProcessMainThreadQueue() {
|
||||
while (mainThreadQueue.TryDequeue(out var action)) {
|
||||
action?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ListenLoop() {
|
||||
while (running) {
|
||||
try {
|
||||
if (!listener.Pending()) {
|
||||
Thread.Sleep(100);
|
||||
continue;
|
||||
}
|
||||
|
||||
var client = listener.AcceptTcpClient();
|
||||
client.NoDelay = true;
|
||||
client.ReceiveTimeout = 0;
|
||||
HandleClient(client);
|
||||
} catch (SocketException) {
|
||||
if (!running) break;
|
||||
} catch (ObjectDisposedException) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleClient(TcpClient client) {
|
||||
var thread = new Thread(() => ClientReadLoop(client)) {
|
||||
IsBackground = true,
|
||||
Name = "RemoteLogClient"
|
||||
};
|
||||
thread.Start();
|
||||
}
|
||||
|
||||
private static void ClientReadLoop(TcpClient client) {
|
||||
try {
|
||||
using var stream = client.GetStream();
|
||||
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||
|
||||
HasClient = true;
|
||||
|
||||
while (running && client.Connected) {
|
||||
string line = reader.ReadLine();
|
||||
if (line == null) break;
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
ParseAndDispatch(line);
|
||||
}
|
||||
} catch (IOException) {
|
||||
// Connection lost
|
||||
} catch (ObjectDisposedException) {
|
||||
// Shutting down
|
||||
} finally {
|
||||
try { client.Close(); } catch { }
|
||||
HasClient = false;
|
||||
ConnectedClientName = "";
|
||||
mainThreadQueue.Enqueue(() => OnClientDisconnected?.Invoke());
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParseAndDispatch(string json) {
|
||||
try {
|
||||
// Check for handshake
|
||||
if (json.Contains("\"handshake\"")) {
|
||||
string appName = ExtractJsonString(json, "app");
|
||||
string platform = ExtractJsonString(json, "platform");
|
||||
ConnectedClientName = $"{appName} ({platform})";
|
||||
mainThreadQueue.Enqueue(() => OnClientConnected?.Invoke(ConnectedClientName));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for watch
|
||||
if (json.Contains("\"watch\"")) {
|
||||
var watchEntry = new RemoteWatchEntry {
|
||||
key = ExtractJsonString(json, "wk"),
|
||||
value = ExtractJsonString(json, "wv"),
|
||||
logCategory = (LogCategory)ExtractJsonInt(json, "c"),
|
||||
timestamp = ExtractJsonString(json, "ts"),
|
||||
frameCount = ExtractJsonInt(json, "fc"),
|
||||
};
|
||||
mainThreadQueue.Enqueue(() => OnRemoteWatch?.Invoke(watchEntry));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for unwatch
|
||||
if (json.Contains("\"unwatch\"")) {
|
||||
string key = ExtractJsonString(json, "wk");
|
||||
mainThreadQueue.Enqueue(() => OnRemoteUnwatch?.Invoke(key));
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = new RemoteLogEntry {
|
||||
message = ExtractJsonString(json, "m"),
|
||||
stackTrace = ExtractJsonString(json, "s"),
|
||||
timestamp = ExtractJsonString(json, "ts"),
|
||||
frameCount = ExtractJsonInt(json, "fc"),
|
||||
type = (JovianLogType)ExtractJsonInt(json, "t"),
|
||||
logCategory = (LogCategory)ExtractJsonInt(json, "c"),
|
||||
isCustomLog = ExtractJsonBool(json, "f"),
|
||||
};
|
||||
|
||||
mainThreadQueue.Enqueue(() => OnRemoteLog?.Invoke(entry));
|
||||
} catch (Exception e) {
|
||||
Debug.LogWarning($"[RemoteLog] Failed to parse: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Lightweight JSON field extractors (avoids dependency on full JSON parser for simple protocol)
|
||||
private static string ExtractJsonString(string json, string key) {
|
||||
string pattern = "\"" + key + "\":\"";
|
||||
int start = json.IndexOf(pattern, StringComparison.Ordinal);
|
||||
if (start < 0) return "";
|
||||
start += pattern.Length;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (int i = start; i < json.Length; i++) {
|
||||
char c = json[i];
|
||||
if (c == '\\' && i + 1 < json.Length) {
|
||||
char next = json[i + 1];
|
||||
switch (next) {
|
||||
case '"': sb.Append('"'); i++; break;
|
||||
case '\\': sb.Append('\\'); i++; break;
|
||||
case 'n': sb.Append('\n'); i++; break;
|
||||
case 'r': sb.Append('\r'); i++; break;
|
||||
case 't': sb.Append('\t'); i++; break;
|
||||
default: sb.Append(c); break;
|
||||
}
|
||||
} else if (c == '"') {
|
||||
break;
|
||||
} else {
|
||||
sb.Append(c);
|
||||
}
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static int ExtractJsonInt(string json, string key) {
|
||||
string pattern = "\"" + key + "\":";
|
||||
int start = json.IndexOf(pattern, StringComparison.Ordinal);
|
||||
if (start < 0) return 0;
|
||||
start += pattern.Length;
|
||||
|
||||
int end = start;
|
||||
while (end < json.Length && (char.IsDigit(json[end]) || json[end] == '-')) end++;
|
||||
if (end == start) return 0;
|
||||
return int.TryParse(json.AsSpan(start, end - start), out int val) ? val : 0;
|
||||
}
|
||||
|
||||
private static bool ExtractJsonBool(string json, string key) {
|
||||
string pattern = "\"" + key + "\":";
|
||||
int start = json.IndexOf(pattern, StringComparison.Ordinal);
|
||||
if (start < 0) return false;
|
||||
start += pattern.Length;
|
||||
return start < json.Length && json[start] == 't';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13353efce044337428c052b7e37f0445
|
||||
3
Packages/com.jovian.logger/README.md
Normal file
3
Packages/com.jovian.logger/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# jovian-unity-logger
|
||||
|
||||
A Unity engine custom logger package based logger that I created some time in the past.
|
||||
7
Packages/com.jovian.logger/README.md.meta
Normal file
7
Packages/com.jovian.logger/README.md.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa77be11bdb554d4c9b1ab265a0a0b4a
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Packages/com.jovian.logger/Runtime.meta
Normal file
8
Packages/com.jovian.logger/Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68a0fd172189d17458408cca0bc439a9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
181
Packages/com.jovian.logger/Runtime/GlobalLogger.cs
Normal file
181
Packages/com.jovian.logger/Runtime/GlobalLogger.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Packages/com.jovian.logger/Runtime/GlobalLogger.cs.meta
Normal file
2
Packages/com.jovian.logger/Runtime/GlobalLogger.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 351a08a96ffb9654b94e50b31c1058ba
|
||||
193
Packages/com.jovian.logger/Runtime/InternalLogger.cs
Normal file
193
Packages/com.jovian.logger/Runtime/InternalLogger.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1463245ed08381f4688ab74cc4296ba1
|
||||
14
Packages/com.jovian.logger/Runtime/Jovian.Logger.asmdef
Normal file
14
Packages/com.jovian.logger/Runtime/Jovian.Logger.asmdef
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "Jovian.Logger",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e11523c9d4d45445a0938098559d830
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
36
Packages/com.jovian.logger/Runtime/LogCategory.cs
Normal file
36
Packages/com.jovian.logger/Runtime/LogCategory.cs
Normal 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
|
||||
}
|
||||
}
|
||||
2
Packages/com.jovian.logger/Runtime/LogCategory.cs.meta
Normal file
2
Packages/com.jovian.logger/Runtime/LogCategory.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: edd66ef15598e84448ef22155f418e66
|
||||
280
Packages/com.jovian.logger/Runtime/Logger.cs
Normal file
280
Packages/com.jovian.logger/Runtime/Logger.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Packages/com.jovian.logger/Runtime/Logger.cs.meta
Normal file
2
Packages/com.jovian.logger/Runtime/Logger.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb1cfb4712ed8a649a051a11d0aa17f5
|
||||
36
Packages/com.jovian.logger/Runtime/LoggerSettings.cs
Normal file
36
Packages/com.jovian.logger/Runtime/LoggerSettings.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 67fe3f48aa2b3b349a7b99381bebb12d
|
||||
152
Packages/com.jovian.logger/Runtime/LoggerUtility.cs
Normal file
152
Packages/com.jovian.logger/Runtime/LoggerUtility.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Packages/com.jovian.logger/Runtime/LoggerUtility.cs.meta
Normal file
2
Packages/com.jovian.logger/Runtime/LoggerUtility.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89f12f254c1d11e418ec326092b0b049
|
||||
270
Packages/com.jovian.logger/Runtime/RemoteLogSender.cs
Normal file
270
Packages/com.jovian.logger/Runtime/RemoteLogSender.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 690a449164ff86c49b1c0c9c3b4ef3b1
|
||||
9
Packages/com.jovian.logger/package.json
Normal file
9
Packages/com.jovian.logger/package.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "com.jovian.logger",
|
||||
"displayName": "Jovian Logger",
|
||||
"version": "1.0.0",
|
||||
"description": "A Unity engine custom logger package based logger that I created some time in the past.",
|
||||
"dependencies": {
|
||||
"com.unity.nuget.newtonsoft-json": "3.2.1"
|
||||
}
|
||||
}
|
||||
7
Packages/com.jovian.logger/package.json.meta
Normal file
7
Packages/com.jovian.logger/package.json.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 701d375fdedcd4741aed611b3054cbc9
|
||||
PackageManifestImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user