Updated Bugsnag

This commit is contained in:
M66B 2021-06-01 07:34:31 +02:00
parent 21d8652947
commit 7202865777
14 changed files with 213 additions and 76 deletions

View File

@ -268,7 +268,7 @@ dependencies {
def dnsjava_version = "2.1.9"
def openpgp_version = "12.0"
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 relinker_version = "1.4.3"
def markwon_version = "4.6.2"

View File

@ -6,7 +6,6 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.os.Build
import android.os.SystemClock
import java.util.HashMap
/**
* Collects various data on the application state
@ -31,13 +30,19 @@ internal class AppDataCollector(
private val releaseStage = config.releaseStage
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(
config, binaryArch, packageName, releaseStage, versionName, codeBundleId,
getDurationMs(), calculateDurationInForeground(), sessionTracker.isInForeground,
launchCrashTracker.isLaunching()
)
fun generateAppWithState(): AppWithState {
val inForeground = sessionTracker.isInForeground
val durationInForeground = calculateDurationInForeground(inForeground)
return AppWithState(
config, binaryArch, packageName, releaseStage, versionName, codeBundleId,
getDurationMs(), durationInForeground, inForeground,
launchCrashTracker.isLaunching()
)
}
fun getAppDataMetadata(): MutableMap<String, Any?> {
val map = HashMap<String, Any?>()
@ -102,9 +107,21 @@ internal class AppDataCollector(
*
* @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()
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
}
/**

View File

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

View File

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

View File

@ -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)
}
}

View File

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

View File

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

View File

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

View File

@ -4,7 +4,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
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.
@ -21,6 +22,7 @@ class LibraryLoader {
if (!attemptedLoad.getAndSet(true)) {
try {
System.loadLibrary(name);
loaded = true;
return true;
} catch (UnsatisfiedLinkError error) {
client.notify(error, callback);
@ -28,4 +30,8 @@ class LibraryLoader {
}
return false;
}
boolean isLoaded() {
return loaded;
}
}

View File

@ -404,4 +404,24 @@ public class NativeInterface {
public static Logger 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);
}
}

View File

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

View File

@ -2,11 +2,21 @@ package com.bugsnag.android
internal class PluginClient(
userPlugins: Set<Plugin>,
immutableConfig: ImmutableConfig,
private val immutableConfig: ImmutableConfig,
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 {
val set = mutableSetOf<Plugin>()
@ -14,13 +24,9 @@ internal class PluginClient(
// instantiate ANR + NDK plugins by reflection as bugsnag-android-core has no
// direct dependency on the artefacts
if (immutableConfig.enabledErrorTypes.ndkCrashes) {
instantiatePlugin("com.bugsnag.android.NdkPlugin")?.let { set.add(it) }
}
if (immutableConfig.enabledErrorTypes.anrs) {
instantiatePlugin("com.bugsnag.android.AnrPlugin")?.let { set.add(it) }
}
instantiatePlugin("com.bugsnag.android.BugsnagReactNativePlugin")?.let { set.add(it) }
ndkPlugin?.let(set::add)
anrPlugin?.let(set::add)
rnPlugin?.let(set::add)
plugins = set.toSet()
}
@ -37,11 +43,51 @@ internal class PluginClient(
}
}
fun loadPlugins(client: Client) = plugins.forEach {
try {
it.load(client)
} catch (exc: Throwable) {
logger.e("Failed to load plugin $it, continuing with initialisation.", exc)
fun loadPlugins(client: Client) {
plugins.forEach { plugin ->
try {
loadPluginInternal(plugin, client)
} catch (exc: Throwable) {
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)
}
}
}

View File

@ -369,21 +369,8 @@ class SessionTracker extends BaseObservable {
return foregroundDetector.isInForeground();
}
//FUTURE:SM This shouldnt be here
@Nullable
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;
long getLastEnteredForegroundMs() {
return lastEnteredForegroundMs.get();
}
@Nullable

View File

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