mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-03-29 03:03:05 -04:00
feat(analytics): Integrate Datadog for RUM and Logging (#2578)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
@@ -21,11 +21,11 @@ import android.os.Debug
|
||||
import com.geeksville.mesh.android.AppPrefs
|
||||
import com.geeksville.mesh.android.BuildUtils.isEmulator
|
||||
import com.geeksville.mesh.android.GeeksvilleApplication
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.util.Exceptions
|
||||
import com.google.firebase.crashlytics.crashlytics
|
||||
import com.google.firebase.Firebase
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.google.firebase.crashlytics.setCustomKeys
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltAndroidApp
|
||||
class MeshUtilApplication : GeeksvilleApplication() {
|
||||
@@ -33,30 +33,15 @@ class MeshUtilApplication : GeeksvilleApplication() {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
Logging.showLogs = BuildConfig.DEBUG
|
||||
|
||||
// We default to off in the manifest - we turn on here if the user approves
|
||||
// leave off when running in the debugger
|
||||
if (!isEmulator && (!BuildConfig.DEBUG || !Debug.isDebuggerConnected())) {
|
||||
val crashlytics = Firebase.crashlytics
|
||||
crashlytics.setCrashlyticsCollectionEnabled(isAnalyticsAllowed)
|
||||
crashlytics.setCustomKey("debug_build", BuildConfig.DEBUG)
|
||||
|
||||
val crashlytics = FirebaseCrashlytics.getInstance()
|
||||
val pref = AppPrefs(this)
|
||||
crashlytics.setUserId(pref.getInstallId()) // be able to group all bugs per anonymous user
|
||||
|
||||
// We always send our log messages to the crashlytics lib, but they only get sent to the server if we report an exception
|
||||
// This makes log messages work properly if someone turns on analytics just before they click report bug.
|
||||
// send all log messages through crashyltics, so if we do crash we'll have those in the report
|
||||
val standardLogger = Logging.printlog
|
||||
Logging.printlog = { level, tag, message ->
|
||||
crashlytics.log("$tag: $message")
|
||||
standardLogger(level, tag, message)
|
||||
}
|
||||
|
||||
fun sendCrashReports() {
|
||||
if (isAnalyticsAllowed)
|
||||
crashlytics.sendUnsentReports()
|
||||
if (isAnalyticsAllowed) crashlytics.sendUnsentReports()
|
||||
}
|
||||
|
||||
// Send any old reports if user approves
|
||||
@@ -67,6 +52,30 @@ class MeshUtilApplication : GeeksvilleApplication() {
|
||||
crashlytics.recordException(exception)
|
||||
sendCrashReports() // Send the new report
|
||||
}
|
||||
Timber.plant(CrashlyticsTree())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CrashlyticsTree : Timber.Tree() {
|
||||
|
||||
companion object {
|
||||
private const val KEY_PRIORITY = "priority"
|
||||
private const val KEY_TAG = "tag"
|
||||
private const val KEY_MESSAGE = "message"
|
||||
}
|
||||
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
FirebaseCrashlytics.getInstance().setCustomKeys {
|
||||
key(KEY_PRIORITY, priority)
|
||||
key(KEY_TAG, tag ?: "No Tag")
|
||||
key(KEY_MESSAGE, message)
|
||||
}
|
||||
|
||||
if (t == null) {
|
||||
FirebaseCrashlytics.getInstance().recordException(Exception(message))
|
||||
} else {
|
||||
FirebaseCrashlytics.getInstance().recordException(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,44 +21,54 @@ import android.app.Application
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import com.datadog.android.Datadog
|
||||
import com.datadog.android.DatadogSite
|
||||
import com.datadog.android.compose.enableComposeActionTracking
|
||||
import com.datadog.android.core.configuration.Configuration
|
||||
import com.datadog.android.log.Logger
|
||||
import com.datadog.android.log.Logs
|
||||
import com.datadog.android.log.LogsConfiguration
|
||||
import com.datadog.android.privacy.TrackingConsent
|
||||
import com.datadog.android.rum.GlobalRumMonitor
|
||||
import com.datadog.android.rum.Rum
|
||||
import com.datadog.android.rum.RumConfiguration
|
||||
import com.datadog.android.timber.DatadogTree
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
import com.geeksville.mesh.analytics.AnalyticsProvider
|
||||
import com.geeksville.mesh.analytics.FirebaseAnalytics
|
||||
import com.geeksville.mesh.model.DeviceHardware
|
||||
import com.geeksville.mesh.util.exceptionReporter
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailabilityLight
|
||||
import com.suddenh4x.ratingdialog.AppRating
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Created by kevinh on 1/4/15.
|
||||
*/
|
||||
|
||||
open class GeeksvilleApplication : Application(), Logging {
|
||||
/** Created by kevinh on 1/4/15. */
|
||||
open class GeeksvilleApplication :
|
||||
Application(),
|
||||
Logging {
|
||||
|
||||
companion object {
|
||||
lateinit var analytics: AnalyticsProvider
|
||||
}
|
||||
|
||||
/// Are we running inside the testlab?
|
||||
// / Are we running inside the testlab?
|
||||
val isInTestLab: Boolean
|
||||
get() {
|
||||
val testLabSetting =
|
||||
Settings.System.getString(contentResolver, "firebase.test.lab") ?: null
|
||||
if(testLabSetting != null)
|
||||
info("Testlab is $testLabSetting")
|
||||
val testLabSetting = Settings.System.getString(contentResolver, "firebase.test.lab") ?: null
|
||||
if (testLabSetting != null) info("Testlab is $testLabSetting")
|
||||
return "true" == testLabSetting
|
||||
}
|
||||
|
||||
private val analyticsPrefs: SharedPreferences by lazy {
|
||||
getSharedPreferences("analytics-prefs", Context.MODE_PRIVATE)
|
||||
}
|
||||
private val analyticsPrefs: SharedPreferences by lazy { getSharedPreferences("analytics-prefs", MODE_PRIVATE) }
|
||||
|
||||
var isAnalyticsAllowed: Boolean
|
||||
get() = analyticsPrefs.getBoolean("allowed", true)
|
||||
set(value) {
|
||||
analyticsPrefs.edit {
|
||||
putBoolean("allowed", value)
|
||||
}
|
||||
analyticsPrefs.edit { putBoolean("allowed", value) }
|
||||
|
||||
// Change the flag with the providers
|
||||
analytics.setEnabled(value && !isInTestLab) // Never do analytics in the test lab
|
||||
@@ -68,12 +78,20 @@ open class GeeksvilleApplication : Application(), Logging {
|
||||
fun askToRate(activity: AppCompatActivity) {
|
||||
if (!isGooglePlayAvailable()) return
|
||||
|
||||
exceptionReporter { // we don't want to crash our app because of bugs in this optional feature
|
||||
exceptionReporter {
|
||||
// we don't want to crash our app because of bugs in this optional feature
|
||||
AppRating.Builder(activity)
|
||||
.setMinimumLaunchTimes(10) // default is 5, 3 means app is launched 3 or more times
|
||||
.setMinimumDays(10) // default is 5, 0 means install day, 10 means app is launched 10 or more days later than installation
|
||||
.setMinimumLaunchTimesToShowAgain(5) // default is 5, 1 means app is launched 1 or more times after neutral button clicked
|
||||
.setMinimumDaysToShowAgain(14) // default is 14, 1 means app is launched 1 or more days after neutral button clicked
|
||||
.setMinimumDays(10) // default is 5, 0 means install day, 10 means app is launched 10 or more days
|
||||
// later than installation
|
||||
.setMinimumLaunchTimesToShowAgain(
|
||||
5,
|
||||
) // default is 5, 1 means app is launched 1 or more times after neutral button
|
||||
// clicked
|
||||
.setMinimumDaysToShowAgain(
|
||||
14,
|
||||
) // default is 14, 1 means app is launched 1 or more days after neutral button
|
||||
// clicked
|
||||
.showIfMeetsConditions()
|
||||
}
|
||||
}
|
||||
@@ -81,19 +99,64 @@ open class GeeksvilleApplication : Application(), Logging {
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
val firebaseAnalytics = com.geeksville.mesh.analytics.FirebaseAnalytics(this)
|
||||
val logger =
|
||||
Logger.Builder()
|
||||
.setNetworkInfoEnabled(true)
|
||||
.setLogcatLogsEnabled(true)
|
||||
.setRemoteSampleRate(100f)
|
||||
.setBundleWithTraceEnabled(true)
|
||||
.setName("TimberLogger")
|
||||
.build()
|
||||
|
||||
val firebaseAnalytics = FirebaseAnalytics(this)
|
||||
analytics = firebaseAnalytics
|
||||
|
||||
// Set analytics per prefs
|
||||
isAnalyticsAllowed = isAnalyticsAllowed
|
||||
if (isAnalyticsAllowed || BuildConfig.DEBUG) {
|
||||
// datadog analytics
|
||||
val configuration =
|
||||
Configuration.Builder(
|
||||
clientToken = BuildConfig.datadogClientToken,
|
||||
env = if (BuildConfig.DEBUG || true) "debug" else "release",
|
||||
variant = BuildConfig.FLAVOR,
|
||||
)
|
||||
.useSite(DatadogSite.US5)
|
||||
.setCrashReportsEnabled(true)
|
||||
.setUseDeveloperModeWhenDebuggable(true)
|
||||
.build()
|
||||
val consent =
|
||||
if (isAnalyticsAllowed) {
|
||||
TrackingConsent.GRANTED
|
||||
} else {
|
||||
TrackingConsent.NOT_GRANTED
|
||||
}
|
||||
Datadog.initialize(this, configuration, consent)
|
||||
Datadog.setVerbosity(Log.VERBOSE)
|
||||
|
||||
val rumConfiguration =
|
||||
RumConfiguration.Builder(BuildConfig.datadogApplicationId)
|
||||
.trackUserInteractions()
|
||||
.trackLongTasks()
|
||||
.trackBackgroundEvents(true)
|
||||
.enableComposeActionTracking()
|
||||
.build()
|
||||
Rum.enable(rumConfiguration)
|
||||
|
||||
val logsConfig = LogsConfiguration.Builder().build()
|
||||
Logs.enable(logsConfig)
|
||||
|
||||
Timber.plant(Timber.DebugTree(), DatadogTree(logger))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.isGooglePlayAvailable(): Boolean {
|
||||
return GoogleApiAvailabilityLight.getInstance()
|
||||
.isGooglePlayServicesAvailable(this)
|
||||
.let {
|
||||
it != ConnectionResult.SERVICE_MISSING &&
|
||||
it != ConnectionResult.SERVICE_INVALID
|
||||
}
|
||||
}
|
||||
fun setAttributes(firmwareVersion: String, deviceHardware: DeviceHardware) {
|
||||
GlobalRumMonitor.get().addAttribute("firmware_version", firmwareVersion)
|
||||
GlobalRumMonitor.get().addAttribute("device_hardware", deviceHardware.hwModelSlug)
|
||||
}
|
||||
|
||||
fun Context.isGooglePlayAvailable(): Boolean =
|
||||
GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(this).let {
|
||||
it != ConnectionResult.SERVICE_MISSING && it != ConnectionResult.SERVICE_INVALID
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user