removed logrocket and fixed Drawing UX

This commit is contained in:
Savya Bikram Shah
2026-06-01 16:04:27 +05:45
parent 28810235a5
commit 89011184d5
141 changed files with 27 additions and 1179741 deletions

View File

@@ -11,4 +11,11 @@ public interface IDrawingCatalogController
event Action ListChanged;
UniTask InitializeAsync(CancellationToken ct);
void OnTemplateSelected(string id);
/// <summary>
/// Signalled by the view layer once the catalog has finished populating after a
/// <see cref="ListChanged"/> refresh. Lets <see cref="InitializeAsync"/> keep the loading
/// screen up until items are on screen, instead of revealing an empty catalog that fills in later.
/// </summary>
void NotifyPopulated();
}

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: ddc06781290bc42b480134ac73d6a269
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,32 +0,0 @@
using System.Collections.Generic;
namespace Darkmatter.Core.Contracts.Services.LogRocket
{
public interface ILogRocketService
{
bool IsReady { get; }
void Identify(string userId);
void Identify(string userId, IReadOnlyDictionary<string, object> traits);
void Track(string eventName);
void Track(string eventName, string propName, string propValue);
void Track(string eventName, IReadOnlyDictionary<string, object> properties);
void Log(string message, LogRocketSeverity severity = LogRocketSeverity.Info);
void LogException(string message, string stackTrace);
void StartSessionReplay();
void StopSessionReplay();
string SessionUrl { get; }
}
public enum LogRocketSeverity
{
Debug,
Info,
Warn,
Error,
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5a44c903b7fed4e889c9fc91430e4b71

View File

@@ -17,6 +17,8 @@ public sealed class DrawingCatalogController : IDrawingCatalogController
public IReadOnlyList<string> VisibleIds => _visible;
public event Action ListChanged;
private UniTaskCompletionSource _firstPopulate;
public DrawingCatalogController(
IDrawingTemplateCatalog catalog,
IEventBus bus)
@@ -28,9 +30,25 @@ public sealed class DrawingCatalogController : IDrawingCatalogController
public async UniTask InitializeAsync(CancellationToken ct)
{
await _catalog.FetchAsync();
// No view listening (e.g. catalog view unassigned) — nothing will populate, so don't wait.
if (ListChanged == null)
{
Refresh();
return;
}
// Hold here until the presenter reports the catalog is on screen, so the caller can keep
// the loading screen up across the (async) thumbnail load + button spawn instead of
// revealing an empty catalog that fills in a few frames later.
_firstPopulate = new UniTaskCompletionSource();
Refresh();
using (ct.Register(() => _firstPopulate.TrySetResult()))
await _firstPopulate.Task;
}
public void NotifyPopulated() => _firstPopulate?.TrySetResult();
public void OnTemplateSelected(string id)
{
_bus.Publish(new DrawingSelectedSignal(id));

View File

@@ -120,6 +120,8 @@ namespace Darkmatter.Features.DrawingCatalog
_view.SetItems(vms);
_view.SetPagination(_currentPage, _totalPages);
// Unblock InitializeAsync: items are now on screen, so the loading screen can hide.
_controller.NotifyPopulated();
}
public void Dispose()

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: c6df39d33cdd84ce1ab71ee4e5026682
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 8eef8a073e91748a09d25cf48e413e05
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,32 +0,0 @@
using UnityEngine;
namespace Darkmatter.Services.LogRocket.Configuration
{
/// <summary>
/// Configuration for the LogRocket service.
///
/// Session replay is recorded natively and automatically by the LogRocket mobile SDK
/// from init - there is no per-frame upload from Unity. Use
/// <see cref="Core.Contracts.Services.LogRocket.ILogRocketService"/>'s
/// StartSessionReplay / StopSessionReplay for manual session control. NOTE: native
/// capture may not see Unity's Metal/GL surface - verify replay content on a device.
/// </summary>
[CreateAssetMenu(menuName = "Darkmatter/Services/LogRocket Config", fileName = "LogRocketConfig")]
public sealed class LogRocketConfig : ScriptableObject
{
[Header("Project")]
[Tooltip("LogRocket application ID, e.g. \"yourorg/yourapp\".")]
public string AppId;
[Header("Logs")]
[Tooltip("Forward Unity Debug.Log / exceptions to LogRocket.")]
public bool ForwardUnityLogs = true;
[Tooltip("Minimum Unity log type forwarded.")]
public LogType MinimumLogType = LogType.Warning;
[Header("Behaviour")]
[Tooltip("Disable LogRocket entirely in the editor.")]
public bool DisableInEditor = true;
}
}

View File

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

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: c7656001fcf7748289669ff7f1bba786
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
External Dependency Manager (EDM4U) dependency spec for the LogRocket SDK.
EDM injects these into the generated iOS Podfile / Android Gradle on build.
Android: VERIFIED against the actual com.logrocket:logrocket:3.1.0 AAR bytecode
(javap). The com.logrocket.core.LogRocket class exists only in the 3.x line - the
2.x line ships com.logrocket.core.SDK instead - so the range MUST stay >= 3.0,
or the direct-call bridge will not compile/link. Latest at time of writing: 3.1.0.
iOS: NOT a pod. The LogRocket.xcframework 3.1.0 (a dynamic Swift framework) is
vendored directly at Assets/Plugins/iOS/LogRocket.xcframework. The CocoaPods
integration linked it but did not embed it into the .app, causing a launch-time
dyld failure (@rpath/LogRocket.framework/LogRocket not loaded). Vendoring routes
it through Unity's plugin pipeline, which auto-embeds + signs dynamic frameworks.
The .m bridge (verified against this exact 3.1.0 ObjC header) is unchanged.
NOTE: the Android bridge (Plugins/Android/LogRocketUnityBridge.java) calls the
SDK directly, so the androidPackage below is REQUIRED - removing it will break
the Android compile.
-->
<dependencies>
<androidPackages>
<androidPackage spec="com.logrocket:logrocket:[3.0,4.0)">
<repositories>
<repository>https://storage.googleapis.com/logrocket-maven/</repository>
</repositories>
</androidPackage>
</androidPackages>
</dependencies>

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: e2806d88a0ebc4aaeb06d2fd098c7555
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 2d3c637029058429a8ea92a186cba06f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,28 +0,0 @@
using Darkmatter.Core.Contracts.Services.LogRocket;
using Darkmatter.Libs.Installers;
using Darkmatter.Services.LogRocket.Configuration;
using Darkmatter.Services.LogRocket.Systems;
using UnityEngine;
using VContainer;
using VContainer.Unity;
namespace Darkmatter.Services.LogRocket.Installers
{
public sealed class LogRocketModule : MonoBehaviour, IModule
{
[SerializeField] private LogRocketConfig _config;
public void Register(IContainerBuilder builder)
{
var config = _config;
if (config == null)
{
Debug.LogWarning("[LogRocket] LogRocketModule has no config assigned. Service will be disabled.");
config = ScriptableObject.CreateInstance<LogRocketConfig>();
}
builder.RegisterInstance(config);
builder.RegisterEntryPoint<LogRocketSystem>().As<ILogRocketService>();
}
}
}

View File

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

View File

@@ -1,19 +0,0 @@
{
"name": "Services.LogRocket",
"rootNamespace": "Darkmatter.Services.LogRocket",
"references": [
"GUID:6a0a834eb41764f12ba55c3fb04a40cb",
"GUID:c1c03c0e5b2f4412b9f2be1c20d6a9b1",
"GUID:b0214a6008ed146ff8f122a6a9c2f6cc",
"GUID:f51ebe6a0ceec4240a699833d6309b23"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

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

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 491835b65b47144db979541bb7e37499
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,17 +0,0 @@
using System.Collections.Generic;
namespace Darkmatter.Services.LogRocket.Systems
{
internal interface ILogRocketNative
{
bool IsAvailable { get; }
string SessionUrl { get; }
void Init(string appId);
void Identify(string userId, IReadOnlyDictionary<string, object> traits);
void Track(string eventName, IReadOnlyDictionary<string, object> properties);
void Log(string severity, string message);
void LogException(string message, string stackTrace);
void StartReplay();
void StopReplay();
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 14484983d15d0451ebe505bb8f93f447

View File

@@ -1,135 +0,0 @@
#if UNITY_ANDROID && !UNITY_EDITOR
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Darkmatter.Services.LogRocket.Systems
{
internal sealed class LogRocketNativeAndroid : ILogRocketNative
{
private const string BridgeClass = "com.darkmatter.logrocket.LogRocketUnityBridge";
// The Java bridge exposes static methods (Plugins/Android/LogRocketUnityBridge.java).
private AndroidJavaClass _bridge;
public bool IsAvailable => _bridge != null;
public string SessionUrl => SafeCall<string>("getSessionUrl");
public void Init(string appId)
{
try
{
using var unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
using var activity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
_bridge = new AndroidJavaClass(BridgeClass);
_bridge.CallStatic("init", activity, appId ?? string.Empty);
}
catch (Exception e)
{
Debug.LogError($"[LogRocket] Android init failed: {e}");
_bridge = null;
}
}
public void Identify(string userId, IReadOnlyDictionary<string, object> traits)
{
if (_bridge == null) return;
try { _bridge.CallStatic("identify", userId ?? string.Empty, MapToJson(traits)); }
catch (Exception e) { Debug.LogError($"[LogRocket] identify failed: {e}"); }
}
public void Track(string eventName, IReadOnlyDictionary<string, object> properties)
{
if (_bridge == null) return;
try { _bridge.CallStatic("track", eventName ?? string.Empty, MapToJson(properties)); }
catch (Exception e) { Debug.LogError($"[LogRocket] track failed: {e}"); }
}
public void Log(string severity, string message)
{
if (_bridge == null) return;
try { _bridge.CallStatic("log", severity ?? "info", message ?? string.Empty); }
catch (Exception e) { Debug.LogError($"[LogRocket] log failed: {e}"); }
}
public void LogException(string message, string stackTrace)
{
if (_bridge == null) return;
try { _bridge.CallStatic("logException", message ?? string.Empty, stackTrace ?? string.Empty); }
catch (Exception e) { Debug.LogError($"[LogRocket] logException failed: {e}"); }
}
public void StartReplay()
{
if (_bridge == null) return;
try { _bridge.CallStatic("startReplay"); }
catch (Exception e) { Debug.LogError($"[LogRocket] startReplay failed: {e}"); }
}
public void StopReplay()
{
if (_bridge == null) return;
try { _bridge.CallStatic("stopReplay"); }
catch (Exception e) { Debug.LogError($"[LogRocket] stopReplay failed: {e}"); }
}
private T SafeCall<T>(string method)
{
if (_bridge == null) return default;
try { return _bridge.CallStatic<T>(method); }
catch { return default; }
}
private static string MapToJson(IReadOnlyDictionary<string, object> map)
{
if (map == null || map.Count == 0) return "{}";
var sb = new System.Text.StringBuilder(64);
sb.Append('{');
bool first = true;
foreach (var kv in map)
{
if (!first) sb.Append(',');
first = false;
AppendJsonString(sb, kv.Key);
sb.Append(':');
AppendJsonValue(sb, kv.Value);
}
sb.Append('}');
return sb.ToString();
}
private static void AppendJsonValue(System.Text.StringBuilder sb, object v)
{
switch (v)
{
case null: sb.Append("null"); break;
case bool b: sb.Append(b ? "true" : "false"); break;
case int or long or float or double or decimal:
sb.Append(Convert.ToString(v, System.Globalization.CultureInfo.InvariantCulture));
break;
default: AppendJsonString(sb, v.ToString()); break;
}
}
private static void AppendJsonString(System.Text.StringBuilder sb, string s)
{
sb.Append('"');
foreach (var c in s ?? string.Empty)
{
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:
if (c < 0x20) sb.AppendFormat("\\u{0:X4}", (int)c);
else sb.Append(c);
break;
}
}
sb.Append('"');
}
}
}
#endif

View File

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

View File

@@ -1,135 +0,0 @@
#if UNITY_IOS && !UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
namespace Darkmatter.Services.LogRocket.Systems
{
internal sealed class LogRocketNativeIOS : ILogRocketNative
{
[DllImport("__Internal")] private static extern void _lr_init(string appId);
[DllImport("__Internal")] private static extern void _lr_identify(string userId, string traitsJson);
[DllImport("__Internal")] private static extern void _lr_track(string eventName, string propsJson);
[DllImport("__Internal")] private static extern void _lr_log(string severity, string message);
[DllImport("__Internal")] private static extern void _lr_logException(string message, string stack);
[DllImport("__Internal")] private static extern void _lr_startReplay();
[DllImport("__Internal")] private static extern void _lr_stopReplay();
[DllImport("__Internal")] private static extern IntPtr _lr_sessionUrl();
private bool _initialised;
public bool IsAvailable => _initialised;
public string SessionUrl
{
get
{
try
{
var ptr = _lr_sessionUrl();
return ptr == IntPtr.Zero ? null : Marshal.PtrToStringAuto(ptr);
}
catch { return null; }
}
}
public void Init(string appId)
{
try { _lr_init(appId ?? string.Empty); _initialised = true; }
catch (Exception e) { Debug.LogError($"[LogRocket] iOS init failed: {e}"); _initialised = false; }
}
public void Identify(string userId, IReadOnlyDictionary<string, object> traits)
{
if (!_initialised) return;
try { _lr_identify(userId ?? string.Empty, MapToJson(traits)); }
catch (Exception e) { Debug.LogError($"[LogRocket] identify failed: {e}"); }
}
public void Track(string eventName, IReadOnlyDictionary<string, object> properties)
{
if (!_initialised) return;
try { _lr_track(eventName ?? string.Empty, MapToJson(properties)); }
catch (Exception e) { Debug.LogError($"[LogRocket] track failed: {e}"); }
}
public void Log(string severity, string message)
{
if (!_initialised) return;
try { _lr_log(severity ?? "info", message ?? string.Empty); }
catch (Exception e) { Debug.LogError($"[LogRocket] log failed: {e}"); }
}
public void LogException(string message, string stack)
{
if (!_initialised) return;
try { _lr_logException(message ?? string.Empty, stack ?? string.Empty); }
catch (Exception e) { Debug.LogError($"[LogRocket] logException failed: {e}"); }
}
public void StartReplay()
{
if (!_initialised) return;
try { _lr_startReplay(); } catch (Exception e) { Debug.LogError($"[LogRocket] startReplay failed: {e}"); }
}
public void StopReplay()
{
if (!_initialised) return;
try { _lr_stopReplay(); } catch (Exception e) { Debug.LogError($"[LogRocket] stopReplay failed: {e}"); }
}
private static string MapToJson(IReadOnlyDictionary<string, object> map)
{
if (map == null || map.Count == 0) return "{}";
var sb = new System.Text.StringBuilder(64);
sb.Append('{');
bool first = true;
foreach (var kv in map)
{
if (!first) sb.Append(',');
first = false;
AppendJsonString(sb, kv.Key);
sb.Append(':');
AppendJsonValue(sb, kv.Value);
}
sb.Append('}');
return sb.ToString();
}
private static void AppendJsonValue(System.Text.StringBuilder sb, object v)
{
switch (v)
{
case null: sb.Append("null"); break;
case bool b: sb.Append(b ? "true" : "false"); break;
case int or long or float or double or decimal:
sb.Append(Convert.ToString(v, System.Globalization.CultureInfo.InvariantCulture));
break;
default: AppendJsonString(sb, v.ToString()); break;
}
}
private static void AppendJsonString(System.Text.StringBuilder sb, string s)
{
sb.Append('"');
foreach (var c in s ?? string.Empty)
{
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:
if (c < 0x20) sb.AppendFormat("\\u{0:X4}", (int)c);
else sb.Append(c);
break;
}
}
sb.Append('"');
}
}
}
#endif

View File

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

View File

@@ -1,33 +0,0 @@
#if UNITY_EDITOR || (!UNITY_IOS && !UNITY_ANDROID)
using System.Collections.Generic;
using UnityEngine;
namespace Darkmatter.Services.LogRocket.Systems
{
internal sealed class LogRocketNativeNoop : ILogRocketNative
{
public bool IsAvailable => true;
public string SessionUrl => null;
public void Init(string appId) =>
Debug.Log($"[LogRocket] Editor/standalone stub init (appId='{appId}'). No native upload.");
public void Identify(string userId, IReadOnlyDictionary<string, object> traits) =>
Debug.Log($"[LogRocket] Identify '{userId}' traits={Count(traits)}");
public void Track(string eventName, IReadOnlyDictionary<string, object> properties) =>
Debug.Log($"[LogRocket] Track '{eventName}' props={Count(properties)}");
public void Log(string severity, string message) =>
Debug.Log($"[LogRocket:{severity}] {message}");
public void LogException(string message, string stackTrace) =>
Debug.Log($"[LogRocket:exception] {message}\n{stackTrace}");
public void StartReplay() => Debug.Log("[LogRocket] StartReplay (noop)");
public void StopReplay() => Debug.Log("[LogRocket] StopReplay (noop)");
private static int Count(IReadOnlyDictionary<string, object> map) => map?.Count ?? 0;
}
}
#endif

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 545348daa325e4c809265a9f76584f19

View File

@@ -1,153 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Cysharp.Threading.Tasks;
using Darkmatter.Core.Contracts.Services.LogRocket;
using Darkmatter.Services.LogRocket.Configuration;
using UnityEngine;
using VContainer.Unity;
namespace Darkmatter.Services.LogRocket.Systems
{
public sealed class LogRocketSystem : ILogRocketService, IAsyncStartable, IDisposable
{
private readonly LogRocketConfig _config;
private readonly ILogRocketNative _native;
private readonly Queue<Action> _pending = new();
private UnityLogForwarder _logForwarder;
private bool _ready;
private bool _disabled;
public LogRocketSystem(LogRocketConfig config)
{
_config = config;
_native = CreateNative();
}
public bool IsReady => _ready;
public string SessionUrl => _native?.SessionUrl;
public async UniTask StartAsync(CancellationToken cancellation = default)
{
if (_config == null)
{
Debug.LogWarning("[LogRocket] No config assigned. Service disabled.");
_disabled = true;
return;
}
#if UNITY_EDITOR
if (_config.DisableInEditor) { _disabled = true; return; }
#endif
if (string.IsNullOrWhiteSpace(_config.AppId))
{
Debug.LogWarning("[LogRocket] AppId empty. Service disabled.");
_disabled = true;
return;
}
await UniTask.SwitchToMainThread(cancellation);
_native.Init(_config.AppId);
if (_config.ForwardUnityLogs)
{
_logForwarder = new UnityLogForwarder(_native, _config.MinimumLogType);
_logForwarder.Enable();
}
// Session replay records natively from init - no Unity-side start needed.
_ready = true;
FlushPending();
}
public void Identify(string userId) => Identify(userId, null);
public void Identify(string userId, IReadOnlyDictionary<string, object> traits)
{
Run(() => _native.Identify(userId, traits));
}
public void Track(string eventName)
{
Run(() => _native.Track(eventName, null));
}
public void Track(string eventName, string propName, string propValue)
{
var dict = new Dictionary<string, object>(1) { [propName] = propValue };
Run(() => _native.Track(eventName, dict));
}
public void Track(string eventName, IReadOnlyDictionary<string, object> properties)
{
Run(() => _native.Track(eventName, properties));
}
public void Log(string message, LogRocketSeverity severity = LogRocketSeverity.Info)
{
Run(() => _native.Log(SeverityString(severity), message));
}
public void LogException(string message, string stackTrace)
{
Run(() => _native.LogException(message, stackTrace));
}
// Manual session control. Recording is automatic from init; these map to the
// native SDK's session controls (e.g. iOS startNewSession / shutdown). Useful
// to rotate or halt a recording, e.g. around a sensitive screen.
public void StartSessionReplay() => Run(() => _native.StartReplay());
public void StopSessionReplay() => Run(() => _native.StopReplay());
public void Dispose()
{
_logForwarder?.Disable();
_logForwarder = null;
_pending.Clear();
}
private void Run(Action action)
{
if (_disabled) return;
if (_ready)
{
try { action(); }
catch (Exception e) { Debug.LogError($"[LogRocket] op failed: {e}"); }
}
else _pending.Enqueue(action);
}
private void FlushPending()
{
while (_pending.Count > 0)
{
try { _pending.Dequeue().Invoke(); }
catch (Exception e) { Debug.LogError($"[LogRocket] queued op failed: {e}"); }
}
}
private static string SeverityString(LogRocketSeverity s) => s switch
{
LogRocketSeverity.Debug => "debug",
LogRocketSeverity.Warn => "warn",
LogRocketSeverity.Error => "error",
_ => "info",
};
private static ILogRocketNative CreateNative()
{
#if UNITY_EDITOR
return new LogRocketNativeNoop();
#elif UNITY_ANDROID
return new LogRocketNativeAndroid();
#elif UNITY_IOS
return new LogRocketNativeIOS();
#else
return new LogRocketNativeNoop();
#endif
}
}
}

View File

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

View File

@@ -1,81 +0,0 @@
using System;
using UnityEngine;
namespace Darkmatter.Services.LogRocket.Systems
{
internal sealed class UnityLogForwarder
{
// Prefix every LogRocket diagnostic carries. Used to drop our own chatter so it
// never round-trips back into the session as user telemetry.
private const string SelfPrefix = "[LogRocket";
private readonly ILogRocketNative _native;
private readonly LogType _min;
private bool _enabled;
// Guards against a native Log/LogException call that itself emits a Unity log on the
// same thread, which would otherwise recurse through OnLog.
[ThreadStatic] private static bool _dispatching;
public UnityLogForwarder(ILogRocketNative native, LogType min)
{
_native = native;
_min = min;
}
public void Enable()
{
if (_enabled) return;
Application.logMessageReceivedThreaded += OnLog;
_enabled = true;
}
public void Disable()
{
if (!_enabled) return;
Application.logMessageReceivedThreaded -= OnLog;
_enabled = false;
}
private void OnLog(string condition, string stack, LogType type)
{
if (_dispatching) return;
if (!ShouldForward(type)) return;
if (condition != null && condition.StartsWith(SelfPrefix, StringComparison.Ordinal)) return;
_dispatching = true;
try
{
if (type == LogType.Exception)
_native.LogException(condition, stack);
else
_native.Log(Severity(type), condition);
}
finally
{
_dispatching = false;
}
}
private bool ShouldForward(LogType actual) => Rank(actual) >= Rank(_min);
private static int Rank(LogType t) => t switch
{
LogType.Log => 0,
LogType.Warning => 1,
LogType.Error => 2,
LogType.Assert => 2,
LogType.Exception => 3,
_ => 0,
};
private static string Severity(LogType t) => t switch
{
LogType.Warning => "warn",
LogType.Error => "error",
LogType.Assert => "error",
LogType.Exception => "error",
_ => "info",
};
}
}

View File

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