Updated Bugsnag

pull/199/head
M66B 3 years ago
parent 21d8652947
commit 7202865777

@ -268,7 +268,7 @@ dependencies {
def dnsjava_version = "2.1.9" def dnsjava_version = "2.1.9"
def openpgp_version = "12.0" def openpgp_version = "12.0"
def badge_version = "1.1.22" def badge_version = "1.1.22"
def bugsnag_version = "5.9.2" def bugsnag_version = "5.9.4"
def biweekly_version = "0.6.6" def biweekly_version = "0.6.6"
def relinker_version = "1.4.3" def relinker_version = "1.4.3"
def markwon_version = "4.6.2" def markwon_version = "4.6.2"

@ -6,7 +6,6 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.os.SystemClock import android.os.SystemClock
import java.util.HashMap
/** /**
* Collects various data on the application state * Collects various data on the application state
@ -31,13 +30,19 @@ internal class AppDataCollector(
private val releaseStage = config.releaseStage private val releaseStage = config.releaseStage
private val versionName = config.appVersion ?: packageInfo?.versionName private val versionName = config.appVersion ?: packageInfo?.versionName
fun generateApp(): App = App(config, binaryArch, packageName, releaseStage, versionName, codeBundleId) fun generateApp(): App =
App(config, binaryArch, packageName, releaseStage, versionName, codeBundleId)
fun generateAppWithState(): AppWithState = AppWithState( fun generateAppWithState(): AppWithState {
val inForeground = sessionTracker.isInForeground
val durationInForeground = calculateDurationInForeground(inForeground)
return AppWithState(
config, binaryArch, packageName, releaseStage, versionName, codeBundleId, config, binaryArch, packageName, releaseStage, versionName, codeBundleId,
getDurationMs(), calculateDurationInForeground(), sessionTracker.isInForeground, getDurationMs(), durationInForeground, inForeground,
launchCrashTracker.isLaunching() launchCrashTracker.isLaunching()
) )
}
fun getAppDataMetadata(): MutableMap<String, Any?> { fun getAppDataMetadata(): MutableMap<String, Any?> {
val map = HashMap<String, Any?>() val map = HashMap<String, Any?>()
@ -102,9 +107,21 @@ internal class AppDataCollector(
* *
* @return the duration in ms * @return the duration in ms
*/ */
internal fun calculateDurationInForeground(): Long? { internal fun calculateDurationInForeground(inForeground: Boolean? = sessionTracker.isInForeground): Long? {
if (inForeground == null) {
return null
}
val nowMs = System.currentTimeMillis() val nowMs = System.currentTimeMillis()
return sessionTracker.getDurationInForegroundMs(nowMs) var durationMs: Long = 0
val sessionStartTimeMs: Long = sessionTracker.lastEnteredForegroundMs
if (inForeground && sessionStartTimeMs != 0L) {
durationMs = nowMs - sessionStartTimeMs
}
return if (durationMs > 0) durationMs else 0
} }
/** /**

@ -94,6 +94,11 @@ public class Breadcrumb implements JsonStream.Streamable {
return impl.getTimestamp(); return impl.getTimestamp();
} }
@NonNull
String getStringTimestamp() {
return DateUtils.toIso8601(impl.getTimestamp());
}
@Override @Override
public void toStream(@NonNull JsonStream stream) throws IOException { public void toStream(@NonNull JsonStream stream) throws IOException {
impl.toStream(stream); impl.toStream(stream);

@ -19,6 +19,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import kotlin.Unit; import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.functions.Function2; import kotlin.jvm.functions.Function2;
import java.util.ArrayList; import java.util.ArrayList;
@ -83,7 +84,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
final Logger logger; final Logger logger;
final DeliveryDelegate deliveryDelegate; final DeliveryDelegate deliveryDelegate;
final ClientObservable clientObservable = new ClientObservable(); final ClientObservable clientObservable;
private PluginClient pluginClient; private PluginClient pluginClient;
final Notifier notifier = new Notifier(); final Notifier notifier = new Notifier();
@ -93,6 +94,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
final LastRunInfoStore lastRunInfoStore; final LastRunInfoStore lastRunInfoStore;
final LaunchCrashTracker launchCrashTracker; final LaunchCrashTracker launchCrashTracker;
final BackgroundTaskService bgTaskService = new BackgroundTaskService(); final BackgroundTaskService bgTaskService = new BackgroundTaskService();
private final ExceptionHandler exceptionHandler;
/** /**
* Initialize a Bugsnag client * Initialize a Bugsnag client
@ -142,6 +144,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
immutableConfig = sanitiseConfiguration(appContext, configuration, connectivity); immutableConfig = sanitiseConfiguration(appContext, configuration, connectivity);
logger = immutableConfig.getLogger(); logger = immutableConfig.getLogger();
warnIfNotAppContext(androidContext); warnIfNotAppContext(androidContext);
clientObservable = new ClientObservable();
// Set up breadcrumbs // Set up breadcrumbs
callbackState = configuration.impl.callbackState.copy(); callbackState = configuration.impl.callbackState.copy();
@ -213,14 +216,16 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
immutableConfig, breadcrumbState, notifier, bgTaskService); immutableConfig, breadcrumbState, notifier, bgTaskService);
// Install a default exception handler with this client // Install a default exception handler with this client
exceptionHandler = new ExceptionHandler(this, logger);
if (immutableConfig.getEnabledErrorTypes().getUnhandledExceptions()) { if (immutableConfig.getEnabledErrorTypes().getUnhandledExceptions()) {
new ExceptionHandler(this, logger); exceptionHandler.install();
} }
// register a receiver for automatic breadcrumbs // register a receiver for automatic breadcrumbs
systemBroadcastReceiver = SystemBroadcastReceiver.register(this, logger, bgTaskService); systemBroadcastReceiver = SystemBroadcastReceiver.register(this, logger, bgTaskService);
registerOrientationChangeListener(); registerOrientationChangeListener();
registerMemoryTrimListener();
// load last run info // load last run info
lastRunInfoStore = new LastRunInfoStore(immutableConfig); lastRunInfoStore = new LastRunInfoStore(immutableConfig);
@ -249,6 +254,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
ContextState contextState, ContextState contextState,
CallbackState callbackState, CallbackState callbackState,
UserState userState, UserState userState,
ClientObservable clientObservable,
Context appContext, Context appContext,
@NonNull DeviceDataCollector deviceDataCollector, @NonNull DeviceDataCollector deviceDataCollector,
@NonNull AppDataCollector appDataCollector, @NonNull AppDataCollector appDataCollector,
@ -264,13 +270,15 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
Logger logger, Logger logger,
DeliveryDelegate deliveryDelegate, DeliveryDelegate deliveryDelegate,
LastRunInfoStore lastRunInfoStore, LastRunInfoStore lastRunInfoStore,
LaunchCrashTracker launchCrashTracker LaunchCrashTracker launchCrashTracker,
ExceptionHandler exceptionHandler
) { ) {
this.immutableConfig = immutableConfig; this.immutableConfig = immutableConfig;
this.metadataState = metadataState; this.metadataState = metadataState;
this.contextState = contextState; this.contextState = contextState;
this.callbackState = callbackState; this.callbackState = callbackState;
this.userState = userState; this.userState = userState;
this.clientObservable = clientObservable;
this.appContext = appContext; this.appContext = appContext;
this.deviceDataCollector = deviceDataCollector; this.deviceDataCollector = deviceDataCollector;
this.appDataCollector = appDataCollector; this.appDataCollector = appDataCollector;
@ -288,6 +296,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
this.lastRunInfoStore = lastRunInfoStore; this.lastRunInfoStore = lastRunInfoStore;
this.launchCrashTracker = launchCrashTracker; this.launchCrashTracker = launchCrashTracker;
this.lastRunInfo = null; this.lastRunInfo = null;
this.exceptionHandler = exceptionHandler;
} }
private LastRunInfo loadLastRunInfo() { private LastRunInfo loadLastRunInfo() {
@ -350,6 +359,18 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
ContextExtensionsKt.registerReceiverSafe(appContext, receiver, configFilter, logger); ContextExtensionsKt.registerReceiverSafe(appContext, receiver, configFilter, logger);
} }
private void registerMemoryTrimListener() {
appContext.registerComponentCallbacks(new ClientComponentCallbacks(
new Function1<Boolean, Unit>() {
@Override
public Unit invoke(Boolean isLowMemory) {
clientObservable.postMemoryTrimEvent(isLowMemory);
return null;
}
}
));
}
void setupNdkPlugin() { void setupNdkPlugin() {
String lastRunInfoPath = lastRunInfoStore.getFile().getAbsolutePath(); String lastRunInfoPath = lastRunInfoStore.getFile().getAbsolutePath();
int crashes = (lastRunInfo != null) ? lastRunInfo.getConsecutiveLaunchCrashes() : 0; int crashes = (lastRunInfo != null) ? lastRunInfo.getConsecutiveLaunchCrashes() : 0;
@ -369,6 +390,17 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
launchCrashTracker.addObserver(observer); launchCrashTracker.addObserver(observer);
} }
void unregisterObserver(Observer observer) {
metadataState.deleteObserver(observer);
breadcrumbState.deleteObserver(observer);
sessionTracker.deleteObserver(observer);
clientObservable.deleteObserver(observer);
userState.deleteObserver(observer);
contextState.deleteObserver(observer);
deliveryDelegate.deleteObserver(observer);
launchCrashTracker.deleteObserver(observer);
}
/** /**
* Sends initial state values for Metadata/User/Context to any registered observers. * Sends initial state values for Metadata/User/Context to any registered observers.
*/ */
@ -990,13 +1022,7 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Nullable @Nullable
Plugin getPlugin(@NonNull Class clz) { Plugin getPlugin(@NonNull Class clz) {
Set<Plugin> plugins = pluginClient.getPlugins(); return pluginClient.findPlugin(clz);
for (Plugin plugin : plugins) {
if (plugin.getClass().equals(clz)) {
return plugin;
}
}
return null;
} }
Notifier getNotifier() { Notifier getNotifier() {
@ -1006,4 +1032,18 @@ public class Client implements MetadataAware, CallbackAware, UserAware {
MetadataState getMetadataState() { MetadataState getMetadataState() {
return metadataState; return metadataState;
} }
void setAutoNotify(boolean autoNotify) {
pluginClient.setAutoNotify(this, autoNotify);
if (autoNotify) {
exceptionHandler.install();
} else {
exceptionHandler.uninstall();
}
}
void setAutoDetectAnrs(boolean autoDetectAnrs) {
pluginClient.setAutoDetectAnrs(this, autoDetectAnrs);
}
} }

@ -0,0 +1,14 @@
package com.bugsnag.android
import android.content.ComponentCallbacks
import android.content.res.Configuration
internal class ClientComponentCallbacks(
val callback: (Boolean) -> Unit
) : ComponentCallbacks {
override fun onConfigurationChanged(newConfig: Configuration) {}
override fun onLowMemory() {
callback(true)
}
}

@ -6,6 +6,10 @@ internal class ClientObservable : BaseObservable() {
notifyObservers(StateEvent.UpdateOrientation(orientation)) notifyObservers(StateEvent.UpdateOrientation(orientation))
} }
fun postMemoryTrimEvent(isLowMemory: Boolean) {
notifyObservers(StateEvent.UpdateMemoryTrimEvent(isLowMemory))
}
fun postNdkInstall(conf: ImmutableConfig, lastRunInfoPath: String, consecutiveLaunchCrashes: Int) { fun postNdkInstall(conf: ImmutableConfig, lastRunInfoPath: String, consecutiveLaunchCrashes: Int) {
notifyObservers( notifyObservers(
StateEvent.Install( StateEvent.Install(

@ -85,8 +85,7 @@ internal class DeviceDataCollector(
fun getDeviceMetadata(): Map<String, Any?> { fun getDeviceMetadata(): Map<String, Any?> {
val map = HashMap<String, Any?>() val map = HashMap<String, Any?>()
map["batteryLevel"] = getBatteryLevel() populateBatteryInfo(into = map)
map["charging"] = isCharging()
map["locationStatus"] = getLocationStatus() map["locationStatus"] = getLocationStatus()
map["networkAccess"] = getNetworkAccess() map["networkAccess"] = getNetworkAccess()
map["brand"] = buildInfo.brand map["brand"] = buildInfo.brand
@ -126,41 +125,31 @@ internal class DeviceDataCollector(
private fun getScreenDensityDpi(): Int? = displayMetrics?.densityDpi private fun getScreenDensityDpi(): Int? = displayMetrics?.densityDpi
/** /**
* Get the current battery charge level, eg 0.3 * Populate the current Battery Info into the specified MutableMap
*/ */
private fun getBatteryLevel(): Float? { private fun populateBatteryInfo(into: MutableMap<String, Any?>) {
try { try {
val ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED) val ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
val batteryStatus = appContext.registerReceiverSafe(null, ifilter, logger) val batteryStatus = appContext.registerReceiverSafe(null, ifilter, logger)
if (batteryStatus != null) { if (batteryStatus != null) {
return batteryStatus.getIntExtra( val level = batteryStatus.getIntExtra("level", -1)
"level", val scale = batteryStatus.getIntExtra("scale", -1)
-1
) / batteryStatus.getIntExtra("scale", -1).toFloat()
}
} catch (exception: Exception) {
logger.w("Could not get batteryLevel")
}
return null
}
/** if (level != -1 || scale != -1) {
* Is the device currently charging/full battery? val batteryLevel: Float = level.toFloat() / scale.toFloat()
*/ into["batteryLevel"] = batteryLevel
private fun isCharging(): Boolean? { }
try {
val ifilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)
val batteryStatus = appContext.registerReceiverSafe(null, ifilter, logger)
if (batteryStatus != null) {
val status = batteryStatus.getIntExtra("status", -1) val status = batteryStatus.getIntExtra("status", -1)
return status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL val charging =
status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL
into["charging"] = charging
} }
} catch (exception: Exception) { } catch (exception: Exception) {
logger.w("Could not get charging status") logger.w("Could not get battery status")
} }
return null
} }
/** /**

@ -23,9 +23,16 @@ class ExceptionHandler implements UncaughtExceptionHandler {
this.client = client; this.client = client;
this.logger = logger; this.logger = logger;
this.originalHandler = Thread.getDefaultUncaughtExceptionHandler(); this.originalHandler = Thread.getDefaultUncaughtExceptionHandler();
}
void install() {
Thread.setDefaultUncaughtExceptionHandler(this); Thread.setDefaultUncaughtExceptionHandler(this);
} }
void uninstall() {
Thread.setDefaultUncaughtExceptionHandler(originalHandler);
}
@Override @Override
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) { public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
boolean strictModeThrowable = strictModeHandler.isStrictModeThrowable(throwable); boolean strictModeThrowable = strictModeHandler.isStrictModeThrowable(throwable);

@ -4,7 +4,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
class LibraryLoader { class LibraryLoader {
private AtomicBoolean attemptedLoad = new AtomicBoolean(); private final AtomicBoolean attemptedLoad = new AtomicBoolean();
private boolean loaded = false;
/** /**
* Attempts to load a native library, returning false if the load was unsuccessful. * Attempts to load a native library, returning false if the load was unsuccessful.
@ -21,6 +22,7 @@ class LibraryLoader {
if (!attemptedLoad.getAndSet(true)) { if (!attemptedLoad.getAndSet(true)) {
try { try {
System.loadLibrary(name); System.loadLibrary(name);
loaded = true;
return true; return true;
} catch (UnsatisfiedLinkError error) { } catch (UnsatisfiedLinkError error) {
client.notify(error, callback); client.notify(error, callback);
@ -28,4 +30,8 @@ class LibraryLoader {
} }
return false; return false;
} }
boolean isLoaded() {
return loaded;
}
} }

@ -404,4 +404,24 @@ public class NativeInterface {
public static Logger getLogger() { public static Logger getLogger() {
return getClient().getConfig().getLogger(); return getClient().getConfig().getLogger();
} }
/**
* Switches automatic error detection on/off after Bugsnag has initialized.
* This is required to support legacy functionality in Unity.
*
* @param autoNotify whether errors should be automatically detected.
*/
public static void setAutoNotify(boolean autoNotify) {
getClient().setAutoNotify(autoNotify);
}
/**
* Switches automatic ANR detection on/off after Bugsnag has initialized.
* This is required to support legacy functionality in Unity.
*
* @param autoDetectAnrs whether ANRs should be automatically detected.
*/
public static void setAutoDetectAnrs(boolean autoDetectAnrs) {
getClient().setAutoDetectAnrs(autoDetectAnrs);
}
} }

@ -7,7 +7,7 @@ import java.io.IOException
*/ */
class Notifier @JvmOverloads constructor( class Notifier @JvmOverloads constructor(
var name: String = "Android Bugsnag Notifier", var name: String = "Android Bugsnag Notifier",
var version: String = "5.9.3", var version: String = "5.9.4",
var url: String = "https://bugsnag.com" var url: String = "https://bugsnag.com"
) : JsonStream.Streamable { ) : JsonStream.Streamable {

@ -2,11 +2,21 @@ package com.bugsnag.android
internal class PluginClient( internal class PluginClient(
userPlugins: Set<Plugin>, userPlugins: Set<Plugin>,
immutableConfig: ImmutableConfig, private val immutableConfig: ImmutableConfig,
private val logger: Logger private val logger: Logger
) { ) {
protected val plugins: Set<Plugin> companion object {
private const val NDK_PLUGIN = "com.bugsnag.android.NdkPlugin"
private const val ANR_PLUGIN = "com.bugsnag.android.AnrPlugin"
private const val RN_PLUGIN = "com.bugsnag.android.BugsnagReactNativePlugin"
}
private val plugins: Set<Plugin>
private val ndkPlugin = instantiatePlugin(NDK_PLUGIN)
private val anrPlugin = instantiatePlugin(ANR_PLUGIN)
private val rnPlugin = instantiatePlugin(RN_PLUGIN)
init { init {
val set = mutableSetOf<Plugin>() val set = mutableSetOf<Plugin>()
@ -14,13 +24,9 @@ internal class PluginClient(
// instantiate ANR + NDK plugins by reflection as bugsnag-android-core has no // instantiate ANR + NDK plugins by reflection as bugsnag-android-core has no
// direct dependency on the artefacts // direct dependency on the artefacts
if (immutableConfig.enabledErrorTypes.ndkCrashes) { ndkPlugin?.let(set::add)
instantiatePlugin("com.bugsnag.android.NdkPlugin")?.let { set.add(it) } anrPlugin?.let(set::add)
} rnPlugin?.let(set::add)
if (immutableConfig.enabledErrorTypes.anrs) {
instantiatePlugin("com.bugsnag.android.AnrPlugin")?.let { set.add(it) }
}
instantiatePlugin("com.bugsnag.android.BugsnagReactNativePlugin")?.let { set.add(it) }
plugins = set.toSet() plugins = set.toSet()
} }
@ -37,11 +43,51 @@ internal class PluginClient(
} }
} }
fun loadPlugins(client: Client) = plugins.forEach { fun loadPlugins(client: Client) {
plugins.forEach { plugin ->
try { try {
it.load(client) loadPluginInternal(plugin, client)
} catch (exc: Throwable) { } catch (exc: Throwable) {
logger.e("Failed to load plugin $it, continuing with initialisation.", exc) logger.e("Failed to load plugin $plugin, continuing with initialisation.", exc)
}
}
}
fun setAutoNotify(client: Client, autoNotify: Boolean) {
setAutoDetectAnrs(client, autoNotify)
if (autoNotify) {
ndkPlugin?.load(client)
} else {
ndkPlugin?.unload()
}
}
fun setAutoDetectAnrs(client: Client, autoDetectAnrs: Boolean) {
if (autoDetectAnrs) {
anrPlugin?.load(client)
} else {
anrPlugin?.unload()
}
}
fun findPlugin(clz: Class<*>): Plugin? = plugins.find { it.javaClass == clz }
private fun loadPluginInternal(plugin: Plugin, client: Client) {
val name = plugin.javaClass.name
val errorTypes = immutableConfig.enabledErrorTypes
// only initialize NDK/ANR plugins if automatic detection enabled
if (name == NDK_PLUGIN) {
if (errorTypes.ndkCrashes) {
plugin.load(client)
}
} else if (name == ANR_PLUGIN) {
if (errorTypes.anrs) {
plugin.load(client)
}
} else {
plugin.load(client)
} }
} }
} }

@ -369,21 +369,8 @@ class SessionTracker extends BaseObservable {
return foregroundDetector.isInForeground(); return foregroundDetector.isInForeground();
} }
//FUTURE:SM This shouldnt be here long getLastEnteredForegroundMs() {
@Nullable return lastEnteredForegroundMs.get();
Long getDurationInForegroundMs(long nowMs) {
long durationMs = 0;
long sessionStartTimeMs = lastEnteredForegroundMs.get();
Boolean inForeground = isInForeground();
if (inForeground == null) {
return null;
}
if (inForeground && sessionStartTimeMs != 0) {
durationMs = nowMs - sessionStartTimeMs;
}
return durationMs > 0 ? durationMs : 0;
} }
@Nullable @Nullable

@ -42,4 +42,6 @@ sealed class StateEvent {
class UpdateOrientation(val orientation: String?) : StateEvent() class UpdateOrientation(val orientation: String?) : StateEvent()
class UpdateUser(val user: User) : StateEvent() class UpdateUser(val user: User) : StateEvent()
class UpdateMemoryTrimEvent(val isLowMemory: Boolean) : StateEvent()
} }

Loading…
Cancel
Save