Logrocket fixes
This commit is contained in:
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35549bb5a76474e7c9927233413facfd
|
||||
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.darkmatter.logrocket">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<application />
|
||||
</manifest>
|
||||
</content>
|
||||
</invoke>
|
||||
@@ -1,4 +0,0 @@
|
||||
target=android-34
|
||||
android.library=true
|
||||
</content>
|
||||
</invoke>
|
||||
@@ -1,160 +0,0 @@
|
||||
package com.darkmatter.logrocket;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.util.Log;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Thin Unity-facing wrapper around the LogRocket Android SDK.
|
||||
*
|
||||
* The LogRocket native SDK API surface is reached via reflection so the Unity
|
||||
* project will compile even if the LogRocket Maven artifact has not been added
|
||||
* yet. To enable, drop the LogRocket AAR into Plugins/Android (or add the
|
||||
* Maven coordinate to mainTemplate.gradle) and the calls below resolve at
|
||||
* runtime.
|
||||
*
|
||||
* Expected LogRocket SDK class: com.logrocket.core.LogRocket
|
||||
* static void init(android.content.Context, String appId)
|
||||
* static void identify(String userId, java.util.Map traits)
|
||||
* static void track(String name, java.util.Map properties)
|
||||
* static void log(String severity, String message)
|
||||
* static String getSessionURL()
|
||||
*/
|
||||
public final class LogRocketUnityBridge {
|
||||
private static final String TAG = "LogRocketBridge";
|
||||
private static final String LR_CLASS = "com.logrocket.core.LogRocket";
|
||||
|
||||
private static LogRocketUnityBridge INSTANCE;
|
||||
|
||||
private Class<?> lrClass;
|
||||
private boolean replayActive;
|
||||
|
||||
public static synchronized LogRocketUnityBridge getInstance() {
|
||||
if (INSTANCE == null) INSTANCE = new LogRocketUnityBridge();
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void init(Activity activity, String appId) {
|
||||
try {
|
||||
lrClass = Class.forName(LR_CLASS);
|
||||
lrClass.getMethod("init", android.content.Context.class, String.class)
|
||||
.invoke(null, activity.getApplicationContext(), appId);
|
||||
Log.i(TAG, "LogRocket initialised: " + appId);
|
||||
} catch (ClassNotFoundException notFound) {
|
||||
Log.e(TAG, "LogRocket SDK class not found. Add the LogRocket Android dependency.");
|
||||
lrClass = null;
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "LogRocket init failed", t);
|
||||
lrClass = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void identify(String userId, String traitsJson) {
|
||||
if (lrClass == null) return;
|
||||
try {
|
||||
Map<String, Object> traits = parse(traitsJson);
|
||||
lrClass.getMethod("identify", String.class, Map.class).invoke(null, userId, traits);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "identify failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public void track(String name, String propsJson) {
|
||||
if (lrClass == null) return;
|
||||
try {
|
||||
Map<String, Object> props = parse(propsJson);
|
||||
lrClass.getMethod("track", String.class, Map.class).invoke(null, name, props);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "track failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public void log(String severity, String message) {
|
||||
if (lrClass == null) return;
|
||||
try {
|
||||
lrClass.getMethod("log", String.class, String.class).invoke(null, severity, message);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
Log.i(TAG, "[" + severity + "] " + message);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "log failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public void logException(String message, String stack) {
|
||||
if (lrClass == null) return;
|
||||
try {
|
||||
lrClass.getMethod("captureException", String.class, String.class).invoke(null, message, stack);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
log("error", message + "\n" + stack);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "logException failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public void captureFrame(byte[] jpeg, int width, int height) {
|
||||
if (lrClass == null || jpeg == null) return;
|
||||
try {
|
||||
lrClass.getMethod("captureFrame", byte[].class, int.class, int.class)
|
||||
.invoke(null, jpeg, width, height);
|
||||
} catch (NoSuchMethodException nsme) {
|
||||
// SDK does not expose frame ingest; ignore silently.
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "captureFrame failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public void startReplay() {
|
||||
replayActive = true;
|
||||
if (lrClass == null) return;
|
||||
try {
|
||||
lrClass.getMethod("startCapture").invoke(null);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "startReplay failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public void stopReplay() {
|
||||
replayActive = false;
|
||||
if (lrClass == null) return;
|
||||
try {
|
||||
lrClass.getMethod("stopCapture").invoke(null);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "stopReplay failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public String getSessionUrl() {
|
||||
if (lrClass == null) return null;
|
||||
try {
|
||||
Object url = lrClass.getMethod("getSessionURL").invoke(null);
|
||||
return url == null ? null : url.toString();
|
||||
} catch (Throwable t) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<String, Object> parse(String json) {
|
||||
Map<String, Object> out = new HashMap<>();
|
||||
if (json == null || json.isEmpty()) return out;
|
||||
try {
|
||||
JSONObject obj = new JSONObject(json);
|
||||
Iterator<String> it = obj.keys();
|
||||
while (it.hasNext()) {
|
||||
String k = it.next();
|
||||
out.put(k, obj.get(k));
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, "json parse failed: " + json);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
</content>
|
||||
</invoke>
|
||||
134
Assets/Plugins/Android/LogRocketUnityBridge.java
Normal file
134
Assets/Plugins/Android/LogRocketUnityBridge.java
Normal file
@@ -0,0 +1,134 @@
|
||||
package com.darkmatter.logrocket;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.util.Log;
|
||||
|
||||
import com.logrocket.core.CustomEventBuilder;
|
||||
import com.logrocket.core.LogRocket;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Unity to LogRocket Android bridge.
|
||||
*
|
||||
* This is a loose .java source file: Unity compiles it into the unityLibrary
|
||||
* Gradle module, which carries the LogRocket Maven dependency added by the
|
||||
* External Dependency Manager (see LogRocketDependencies.xml). That lets us call
|
||||
* the SDK directly - including the init/getSessionURL lambdas - instead of
|
||||
* reflecting, so there is no functional-interface name to guess.
|
||||
*
|
||||
* Because the calls are direct, Android builds REQUIRE the LogRocket AAR: keep
|
||||
* the androidPackage entry in LogRocketDependencies.xml enabled, or this file
|
||||
* will fail to compile.
|
||||
*
|
||||
* API verified against the LogRocket Android SDK 3.x
|
||||
* (docs.logrocket.com/reference/android): com.logrocket.core.LogRocket,
|
||||
* com.logrocket.core.CustomEventBuilder.
|
||||
*/
|
||||
public final class LogRocketUnityBridge {
|
||||
private static final String TAG = "LogRocketBridge";
|
||||
private static volatile String sessionUrl;
|
||||
|
||||
private LogRocketUnityBridge() {
|
||||
}
|
||||
|
||||
public static void init(Activity activity, String appId) {
|
||||
try {
|
||||
Application app = activity.getApplication();
|
||||
// Docs recommend init from a custom Application.attachBaseContext for
|
||||
// earliest-startup capture. Runtime init from the current activity
|
||||
// (as the React Native SDK does) works but misses the launch window.
|
||||
LogRocket.init(app, app.getBaseContext(), options -> options.setAppID(appId));
|
||||
LogRocket.getSessionURL(url -> sessionUrl = url);
|
||||
Log.i(TAG, "LogRocket initialized: " + appId);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "init failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void identify(String userId, String traitsJson) {
|
||||
try {
|
||||
Map<String, String> traits = parse(traitsJson);
|
||||
if (traits.isEmpty()) {
|
||||
LogRocket.identify(userId);
|
||||
} else {
|
||||
LogRocket.identify(userId, traits);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "identify failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void track(String name, String propsJson) {
|
||||
try {
|
||||
CustomEventBuilder builder = new CustomEventBuilder(name);
|
||||
for (Map.Entry<String, String> e : parse(propsJson).entrySet()) {
|
||||
builder.put(e.getKey(), e.getValue());
|
||||
}
|
||||
LogRocket.track(builder);
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "track failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void log(String severity, String message) {
|
||||
// LogRocket auto-captures logcat, so emitting here is enough to record it.
|
||||
Log.println(level(severity), TAG, message == null ? "" : message);
|
||||
}
|
||||
|
||||
public static void logException(String message, String stack) {
|
||||
Log.e(TAG, (message == null ? "" : message) + "\n" + (stack == null ? "" : stack));
|
||||
}
|
||||
|
||||
public static String getSessionUrl() {
|
||||
return sessionUrl;
|
||||
}
|
||||
|
||||
// Recording starts automatically at init. These provide manual control,
|
||||
// verified against the 3.1.0 AAR: endSession() halts the current recording
|
||||
// (SDK stays alive), startNewSession() begins a fresh one.
|
||||
public static void startReplay() {
|
||||
try {
|
||||
LogRocket.startNewSession();
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "startReplay failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
public static void stopReplay() {
|
||||
try {
|
||||
LogRocket.endSession();
|
||||
} catch (Throwable t) {
|
||||
Log.e(TAG, "stopReplay failed", t);
|
||||
}
|
||||
}
|
||||
|
||||
private static int level(String severity) {
|
||||
if ("error".equals(severity)) return Log.ERROR;
|
||||
if ("warn".equals(severity)) return Log.WARN;
|
||||
if ("debug".equals(severity)) return Log.DEBUG;
|
||||
return Log.INFO;
|
||||
}
|
||||
|
||||
private static Map<String, String> parse(String json) {
|
||||
Map<String, String> out = new HashMap<>();
|
||||
if (json == null || json.isEmpty()) return out;
|
||||
try {
|
||||
JSONObject obj = new JSONObject(json);
|
||||
Iterator<String> it = obj.keys();
|
||||
while (it.hasNext()) {
|
||||
String key = it.next();
|
||||
out.put(key, String.valueOf(obj.get(key)));
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.w(TAG, "bad json: " + json);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
}
|
||||
2
Assets/Plugins/Android/LogRocketUnityBridge.java.meta
Normal file
2
Assets/Plugins/Android/LogRocketUnityBridge.java.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75d114ce17de24caba8e420437ed31c9
|
||||
Reference in New Issue
Block a user