feat(analytics): Integrate _more_ Datadog (#2633)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2025-08-04 15:27:45 -05:00 committed by GitHub
parent cde871de91
commit 23c87fb48c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 60 additions and 22 deletions

View file

@ -12,14 +12,6 @@ concurrency:
cancel-in-progress: true
jobs:
test_secrets:
runs-on: ubuntu-latest
env:
TEST_SECRET: ${{ secrets.TEST_SECRET }}
steps:
- name: Test Secrets
run: |
echo "$TEST_SECRET"
build_and_detekt:
if: github.repository == 'meshtastic/Meshtastic-Android' && github.head_ref != 'scheduled-updates'

View file

@ -9,8 +9,6 @@ on:
required: false
DATADOG_CLIENT_TOKEN:
required: false
TEST_SECRET:
required: false
inputs:
upload_artifacts:
description: 'Whether to upload build and Detekt artifacts'
@ -27,10 +25,6 @@ jobs:
DATADOG_CLIENT_TOKEN: ${{ secrets.DATADOG_CLIENT_TOKEN }}
steps:
- name: Test Secrets
env:
TEST_SECRET: ${{ secrets.TEST_SECRET }}
run: echo "$TEST_SECRET"
- name: Checkout code
uses: actions/checkout@v4

View file

@ -126,7 +126,7 @@ android {
}
}
buildTypes {
named("release") {
release {
if (keystoreProperties["storeFile"] != null) {
signingConfig = signingConfigs.named("release").get()
}
@ -134,7 +134,10 @@ android {
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
named("debug") { isPseudoLocalesEnabled = true }
debug {
isDebuggable = true
isPseudoLocalesEnabled = true
}
}
bundle { language { enableSplit = false } }
buildFeatures {

View file

@ -22,10 +22,14 @@ import android.content.Context
import android.content.SharedPreferences
import android.provider.Settings
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
import androidx.core.content.edit
import androidx.navigation.NavHostController
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.analytics.AnalyticsProvider
import com.geeksville.mesh.analytics.NopAnalytics
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.BuildUtils.info
import com.geeksville.mesh.model.DeviceHardware
import timber.log.Timber
@ -80,4 +84,13 @@ val Context.isGooglePlayAvailable: Boolean
@Suppress("UnusedParameter")
fun setAttributes(deviceVersion: String, deviceHardware: DeviceHardware) {
// No-op for F-Droid version
info("Setting attributes: deviceVersion=$deviceVersion, deviceHardware=$deviceHardware")
}
@Composable
fun AddNavigationTracking(navController: NavHostController) {
// No-op for F-Droid version
navController.addOnDestinationChangedListener { _, destination, _ ->
debug("Navigation changed to: ${destination.route}")
}
}

View file

@ -21,11 +21,14 @@ 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.compose.runtime.Composable
import androidx.core.content.edit
import androidx.navigation.NavHostController
import com.datadog.android.Datadog
import com.datadog.android.DatadogSite
import com.datadog.android.compose.ExperimentalTrackingApi
import com.datadog.android.compose.NavigationViewTrackingEffect
import com.datadog.android.compose.enableComposeActionTracking
import com.datadog.android.core.configuration.Configuration
import com.datadog.android.log.Logger
@ -35,7 +38,11 @@ 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.rum.tracking.AcceptAllNavDestinations
import com.datadog.android.timber.DatadogTree
import com.datadog.android.trace.AndroidTracer
import com.datadog.android.trace.Trace
import com.datadog.android.trace.TraceConfiguration
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.analytics.AnalyticsProvider
import com.geeksville.mesh.analytics.FirebaseAnalytics
@ -44,6 +51,7 @@ 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 io.opentracing.util.GlobalTracer
import timber.log.Timber
/** Created by kevinh on 1/4/15. */
@ -135,10 +143,9 @@ open class GeeksvilleApplication :
val logger =
Logger.Builder()
.setNetworkInfoEnabled(true)
.setLogcatLogsEnabled(true)
.setRemoteSampleRate(sampleRate)
.setBundleWithTraceEnabled(true)
.setName("TimberLogger")
.setBundleWithRumEnabled(true)
.build()
val configuration =
Configuration.Builder(
@ -157,7 +164,6 @@ open class GeeksvilleApplication :
TrackingConsent.NOT_GRANTED
}
Datadog.initialize(this, configuration, consent)
Datadog.setVerbosity(Log.VERBOSE)
val rumConfiguration =
RumConfiguration.Builder(BuildConfig.datadogApplicationId)
@ -166,7 +172,9 @@ open class GeeksvilleApplication :
.trackFrustrations(true)
.trackLongTasks()
.trackNonFatalAnrs(true)
.trackUserInteractions()
// Re-enable tracking when auto instrumentation available. See note in `app/build.gradle`
.disableUserInteractionTracking()
// .trackUserInteractions()
.enableComposeActionTracking()
.build()
Rum.enable(rumConfiguration)
@ -174,6 +182,12 @@ open class GeeksvilleApplication :
val logsConfig = LogsConfiguration.Builder().build()
Logs.enable(logsConfig)
val traceConfig = TraceConfiguration.Builder().build()
Trace.enable(traceConfig)
val tracer = AndroidTracer.Builder().build()
GlobalTracer.registerIfAbsent(tracer)
Timber.plant(Timber.DebugTree(), DatadogTree(logger))
}
}
@ -188,3 +202,13 @@ val Context.isGooglePlayAvailable: Boolean
GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(this).let {
it != ConnectionResult.SERVICE_MISSING && it != ConnectionResult.SERVICE_INVALID
}
@OptIn(ExperimentalTrackingApi::class)
@Composable
fun AddNavigationTracking(navController: NavHostController) {
NavigationViewTrackingEffect(
navController = navController,
trackArguments = true,
destinationPredicate = AcceptAllNavDestinations(),
)
}

View file

@ -82,6 +82,7 @@ import androidx.navigation.compose.rememberNavController
import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.R
import com.geeksville.mesh.android.AddNavigationTracking
import com.geeksville.mesh.android.BuildUtils.debug
import com.geeksville.mesh.android.setAttributes
import com.geeksville.mesh.model.BluetoothViewModel
@ -159,6 +160,8 @@ fun MainScreen(
}
}
AddNavigationTracking(navController)
if (connectionState.isConnected()) {
requestChannelSet?.let { newChannelSet -> ScannedQrCodeDialog(uIViewModel, newChannelSet) }
}

View file

@ -90,8 +90,10 @@ datastore-preferences = { group = "androidx.datastore", name = "datastore-prefer
dd-sdk-android-compose = { group = "com.datadoghq", name = "dd-sdk-android-compose", version.ref = "dd-sdk-android" }
dd-sdk-android-gradle-plugin = { group = "com.datadoghq", name = "dd-sdk-android-gradle-plugin", version.ref = "dd-sdk-android-gradle-plugin" }
dd-sdk-android-logs = { group = "com.datadoghq", name = "dd-sdk-android-logs", version.ref = "dd-sdk-android" }
dd-sdk-android-okhttp = { group = "com.datadoghq", name = "dd-sdk-android-okhttp", version.ref = "dd-sdk-android" }
dd-sdk-android-rum = { group = "com.datadoghq", name = "dd-sdk-android-rum", version.ref = "dd-sdk-android" }
dd-sdk-android-timber = { group = "com.datadoghq", name = "dd-sdk-android-timber", version.ref = "dd-sdk-android" }
dd-sdk-android-trace = { group = "com.datadoghq", name = "dd-sdk-android-trace", version.ref = "dd-sdk-android" }
detekt-formatting = { group = "io.gitlab.arturbosch.detekt", name = "detekt-formatting", version.ref = "detekt" }
emoji2-emojipicker = { group = "androidx.emoji2", name = "emoji2-emojipicker", version.ref = "emoji2" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" }
@ -193,7 +195,7 @@ osm = ["osmdroid-android", "osmbonuspack", "mgrs"]
firebase = ["firebase-analytics", "firebase-crashlytics"]
# Datadog
datadog = ["dd-sdk-android-compose", "dd-sdk-android-logs", "dd-sdk-android-timber", "dd-sdk-android-rum"]
datadog = ["dd-sdk-android-compose", "dd-sdk-android-logs", "dd-sdk-android-okhttp", "dd-sdk-android-rum", "dd-sdk-android-timber", "dd-sdk-android-trace"]
# Protobuf
protobuf = ["protobuf-kotlin"]

View file

@ -55,6 +55,7 @@ dependencies {
implementation(libs.bundles.hilt)
implementation(libs.bundles.retrofit)
implementation(libs.bundles.coil)
"googleImplementation"(libs.bundles.datadog)
ksp(libs.hilt.compiler)
implementation(libs.kotlinx.serialization.json)
detektPlugins(libs.detekt.formatting)

View file

@ -26,6 +26,8 @@ import coil3.request.crossfade
import coil3.svg.SvgDecoder
import coil3.util.DebugLogger
import coil3.util.Logger
import com.datadog.android.okhttp.DatadogEventListener
import com.datadog.android.okhttp.DatadogInterceptor
import com.geeksville.mesh.network.BuildConfig
import com.geeksville.mesh.network.retrofit.ApiService
import dagger.Module
@ -49,13 +51,17 @@ class ApiModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor().apply {
if (BuildConfig.DEBUG) {
setLevel(HttpLoggingInterceptor.Level.BODY)
}
}
val tracedHosts = listOf("meshtastic.org")
return OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(DatadogInterceptor.Builder(tracedHosts).build())
.eventListenerFactory(DatadogEventListener.Factory())
.build()
}