Skip to content

Commit edfb29b

Browse files
committed
Send logs to datadog
1 parent 65b76fe commit edfb29b

File tree

18 files changed

+228
-21
lines changed

18 files changed

+228
-21
lines changed

.github/workflows/android.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ jobs:
4444
UNES_KEYSTORE_PASSWORD: ${{ secrets.UNES_KEYSTORE_PASSWORD }}
4545
UNES_KEYSTORE_PRIVATE_KEY_PASSWORD: ${{ secrets.UNES_KEYSTORE_PRIVATE_KEY_PASSWORD }}
4646
UNES_MAPS_KEY: ${{ secrets.UNES_MAPS_KEY }}
47+
UNES_DATADOG_CLIENT_KEY: ${{ secrets.UNES_DATADOG_CLIENT_KEY }}
4748

4849
- name: Upload Check Results
4950
uses: actions/upload-artifact@v2

app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ android {
6262
buildConfigField("String", "SIECOMP_DAY5_START", "\"2019-10-22T08:00:00-03:00\"")
6363
buildConfigField("String", "SIECOMP_DAY5_END", "\"2019-10-22T17:30:00-03:00\"")
6464
buildConfigField("String", "UEFS_DEFAULT_PROXY", "\"10.65.16.2:3128\"")
65+
buildConfigField("String", "DATADOG_PUBLIC_KEY", "\"${System.getenv("UNES_DATADOG_CLIENT_KEY")}\"")
6566

6667
ndk {
6768
abiFilters += listOf("arm64-v8a", "armeabi", "armeabi-v7a", "mips", "mips64", "x86", "x86_64")
@@ -231,6 +232,7 @@ dependencies {
231232
debugImplementation(libs.chucker)
232233
releaseImplementation(libs.chucker.no.op)
233234
implementation(libs.timber)
235+
implementation(libs.datadog)
234236
implementation(libs.play.services.games.v2)
235237
implementation(libs.play.services.auth)
236238
implementation(libs.play.services.location)

app/proguard-rules.pro

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
-assumenosideeffects class timber.log.Timber {
99
public static *** v(...);
1010
public static *** d(...);
11-
public static *** i(...);
1211
}
1312

1413
-if class androidx.credentials.CredentialManager

app/src/main/java/com/forcetower/uefs/UApplication.kt

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,25 @@ package com.forcetower.uefs
2323
import android.app.Application
2424
import android.content.Context
2525
import android.content.SharedPreferences
26+
import androidx.core.content.edit
2627
import androidx.hilt.work.HiltWorkerFactory
2728
import androidx.work.Configuration
29+
import com.datadog.android.Datadog
30+
import com.datadog.android.DatadogSite
31+
import com.datadog.android.log.Logger
32+
import com.datadog.android.log.Logs
33+
import com.datadog.android.log.LogsConfiguration
34+
import com.datadog.android.privacy.TrackingConsent
2835
import com.forcetower.sagres.SagresNavigator
2936
import com.forcetower.uefs.core.constants.Constants
3037
import com.forcetower.uefs.core.storage.cookies.CachedCookiePersistor
3138
import com.forcetower.uefs.core.storage.cookies.PrefsCookiePersistor
3239
import com.forcetower.uefs.core.work.sync.SyncMainWorker
40+
import com.forcetower.uefs.domain.usecase.device.GetDeviceInfoUseCase
3341
import com.forcetower.uefs.feature.themeswitcher.ThemePreferencesManager
3442
import com.forcetower.uefs.impl.AndroidBase64Encoder
3543
import com.forcetower.uefs.impl.CrashlyticsTree
44+
import com.forcetower.uefs.impl.DatadogTree
3645
import com.forcetower.uefs.impl.SharedPrefsCachePersistence
3746
import com.forcetower.uefs.service.NotificationHelper
3847
import com.google.android.gms.games.PlayGamesSdk
@@ -41,12 +50,15 @@ import dagger.hilt.android.HiltAndroidApp
4150
import javax.inject.Inject
4251
import okhttp3.OkHttpClient
4352
import timber.log.Timber
53+
import java.util.UUID
54+
import kotlin.uuid.Uuid
4455

4556
@HiltAndroidApp
4657
class UApplication : Application(), Configuration.Provider {
4758
@Inject lateinit var preferences: SharedPreferences
48-
59+
@Inject lateinit var datadogTree: DatadogTree
4960
@Inject lateinit var workerFactory: HiltWorkerFactory
61+
@Inject lateinit var deviceInfoUseCase: GetDeviceInfoUseCase
5062

5163
var disciplineToolbarDevClickCount = 0
5264
var messageToolbarDevClickCount = 0
@@ -56,13 +68,34 @@ class UApplication : Application(), Configuration.Provider {
5668
SplitCompat.install(this)
5769
}
5870

71+
private fun initializeDatadog() {
72+
if (BuildConfig.DEBUG) return
73+
val configuration = com.datadog.android.core.configuration.Configuration.Builder(
74+
clientToken = BuildConfig.DATADOG_PUBLIC_KEY,
75+
env = "production",
76+
).apply {
77+
setCrashReportsEnabled(false)
78+
useSite(DatadogSite.US1)
79+
}.build()
80+
Datadog.initialize(this, configuration, TrackingConsent.GRANTED)
81+
82+
83+
val logsConfig = LogsConfiguration.Builder().build()
84+
Logs.enable(logsConfig)
85+
}
86+
5987
override fun onCreate() {
88+
initializeDatadog()
89+
super.onCreate()
90+
Logs.addAttribute("machineId", deviceInfoUseCase.machineIdDirect())
91+
6092
if (BuildConfig.DEBUG) {
6193
Timber.plant(Timber.DebugTree())
6294
} else {
6395
Timber.plant(CrashlyticsTree())
96+
Timber.plant(datadogTree)
6497
}
65-
super.onCreate()
98+
6699

67100
if (preferences.getBoolean("google_play_games_enabled_v2", false)) {
68101
PlayGamesSdk.initialize(this)

app/src/main/java/com/forcetower/uefs/core/injection/module/AppModule.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import androidx.datastore.preferences.core.Preferences
2929
import androidx.datastore.preferences.preferencesDataStoreFile
3030
import androidx.preference.PreferenceManager
3131
import androidx.room.Room
32+
import com.datadog.android.log.Logger
3233
import com.forcetower.uefs.core.storage.apidatabase.APIDatabase
3334
import com.forcetower.uefs.core.storage.database.M50TO51
3435
import com.forcetower.uefs.core.storage.database.M51TO52
@@ -92,6 +93,15 @@ object AppModule {
9293
}
9394
}
9495

96+
@Provides
97+
@Singleton
98+
@Named("settings")
99+
fun provideSettings(context: Context): DataStore<Preferences> {
100+
return PreferenceDataStoreFactory.create {
101+
context.preferencesDataStoreFile("settings")
102+
}
103+
}
104+
95105
@Provides
96106
@Singleton
97107
fun provideThemeSwitcherResourceProvider() = ThemeSwitcherResourceProvider()
@@ -117,6 +127,19 @@ object AppModule {
117127
return agents.random()
118128
}
119129

130+
@Provides
131+
@Singleton
132+
fun datadogLogs(): Logger {
133+
return Logger.Builder()
134+
.setNetworkInfoEnabled(true)
135+
.setLogcatLogsEnabled(false)
136+
.setRemoteSampleRate(100f)
137+
.setBundleWithTraceEnabled(true)
138+
.setService("Android")
139+
.setName("Android")
140+
.build()
141+
}
142+
120143
@Provides
121144
@Reusable
122145
@Named("unesUserAgent")

app/src/main/java/com/forcetower/uefs/core/injection/module/NetworkModule.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,17 @@ object NetworkModule {
9191
.cookieJar(cookieJar)
9292
.callTimeout(2, TimeUnit.MINUTES)
9393
.addInterceptor(interceptor)
94-
.addInterceptor(
95-
HttpLoggingInterceptor {
96-
Timber.tag("ok-http").d(it)
97-
}.apply {
98-
level = if (BuildConfig.DEBUG) {
99-
HttpLoggingInterceptor.Level.BASIC
100-
} else {
101-
HttpLoggingInterceptor.Level.NONE
102-
}
94+
.apply {
95+
if (!BuildConfig.DEBUG) {
96+
addInterceptor(
97+
HttpLoggingInterceptor {
98+
Timber.tag("ok-http").d(it)
99+
}.apply {
100+
level = HttpLoggingInterceptor.Level.BASIC
101+
}
102+
)
103103
}
104-
)
104+
}
105105
.addInterceptor(chuckerInterceptor)
106106
.build()
107107
}

app/src/main/java/com/forcetower/uefs/core/storage/repository/cloud/EdgeAccountRepository.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ class EdgeAccountRepository @Inject constructor(
1717
fun getAccount() = database.edgeServiceAccount.me()
1818

1919
suspend fun fetchAccountIfNeeded() {
20-
val token = database.edgeAccessToken.require() ?: return
21-
Timber.d("Has edge token $token")
20+
database.edgeAccessToken.require() ?: return
21+
Timber.d("Has edge token")
2222

2323
val me = service.me().data
2424
val value = EdgeServiceAccount(
@@ -32,8 +32,8 @@ class EdgeAccountRepository @Inject constructor(
3232
}
3333

3434
suspend fun startSession() {
35-
val token = database.edgeAccessToken.require() ?: return
36-
Timber.d("Has edge token $token")
35+
database.edgeAccessToken.require() ?: return
36+
Timber.d("Has edge token")
3737
val access = database.accessDao().getAccessDirectSuspend() ?: return
3838
Timber.d("No credentials")
3939
runCatching {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.forcetower.uefs.domain.usecase.device
2+
3+
import androidx.datastore.core.DataStore
4+
import androidx.datastore.preferences.core.Preferences
5+
import androidx.datastore.preferences.core.edit
6+
import androidx.datastore.preferences.core.stringPreferencesKey
7+
import dagger.Reusable
8+
import kotlinx.coroutines.flow.Flow
9+
import kotlinx.coroutines.flow.first
10+
import kotlinx.coroutines.flow.map
11+
import kotlinx.coroutines.runBlocking
12+
import java.security.MessageDigest
13+
import java.util.UUID
14+
import javax.inject.Inject
15+
import javax.inject.Named
16+
17+
@Reusable
18+
class GetDeviceInfoUseCase @Inject constructor(
19+
@Named("settings") private val settings: DataStore<Preferences>
20+
) {
21+
private val deviceId = stringPreferencesKey("device_id")
22+
23+
@Inject
24+
fun generateMachineId(): String {
25+
return runBlocking {
26+
settings.data.first()[deviceId] ?: run {
27+
val generated = UUID.randomUUID().toString()
28+
settings.edit { it[deviceId] = generated }
29+
generated
30+
}
31+
}
32+
}
33+
34+
fun machineIdDirect(): String {
35+
return runBlocking {
36+
settings.data.first()[deviceId]?.let { str ->
37+
val bytes = MessageDigest.getInstance("MD5").digest(str.toByteArray(Charsets.UTF_8))
38+
bytes.joinToString(separator = "") { byte -> "%02x".format(byte) }
39+
} ?: UNINITIALIZED
40+
}
41+
}
42+
43+
fun machineId(): Flow<String> {
44+
return settings.data.map {
45+
it[deviceId]?.let { str ->
46+
val bytes = MessageDigest.getInstance("MD5").digest(str.toByteArray(Charsets.UTF_8))
47+
bytes.joinToString(separator = "") { byte -> "%02x".format(byte) }
48+
} ?: UNINITIALIZED
49+
}
50+
}
51+
52+
private companion object {
53+
const val UNINITIALIZED = "unavailable"
54+
}
55+
}

app/src/main/java/com/forcetower/uefs/feature/home/HomeActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import androidx.lifecycle.Lifecycle
3737
import androidx.lifecycle.lifecycleScope
3838
import androidx.navigation.findNavController
3939
import androidx.navigation.ui.NavigationUI
40+
import com.datadog.android.log.Logs
4041
import com.forcetower.core.lifecycle.EventObserver
4142
import com.forcetower.sagres.SagresNavigator
4243
import com.forcetower.sagres.database.model.SagresCredential
@@ -297,6 +298,10 @@ class HomeActivity : UGameActivity() {
297298
private fun onAccessUpdate(access: Access?) {
298299
if (access == null) {
299300
Timber.d("Access Invalidated")
301+
Logs.removeAttribute("username")
302+
Logs.removeAttribute("institution")
303+
Logs.removeAttribute("accessValid")
304+
300305
val intent = Intent(this, LoginActivity::class.java)
301306
intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
302307
intent.addFlags(FLAG_ACTIVITY_CLEAR_TOP)
@@ -309,6 +314,9 @@ class HomeActivity : UGameActivity() {
309314
analytics.setUserId(access.username)
310315
analytics.setUserProperty("institution", SagresNavigator.instance.getSelectedInstitution())
311316
analytics.setUserProperty("access_valid", "${access.valid}")
317+
Logs.addAttribute("username", access.username)
318+
Logs.addAttribute("institution", SagresNavigator.instance.getSelectedInstitution())
319+
Logs.addAttribute("accessValid", access.valid)
312320
SagresNavigator.instance.putCredentials(SagresCredential(access.username, access.password, SagresNavigator.instance.getSelectedInstitution()))
313321

314322
if (!access.valid) {

app/src/main/java/com/forcetower/uefs/feature/home/HomeBottomFragment.kt

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package com.forcetower.uefs.feature.home
2222

23+
import android.content.Intent
2324
import android.content.SharedPreferences
2425
import android.net.Uri
2526
import android.os.Bundle
@@ -41,6 +42,7 @@ import com.canhub.cropper.CropImageView
4142
import com.forcetower.core.utils.ColorUtils
4243
import com.forcetower.uefs.BuildConfig
4344
import com.forcetower.uefs.R
45+
import com.forcetower.uefs.core.constants.Constants
4446
import com.forcetower.uefs.core.model.unes.Course
4547
import com.forcetower.uefs.core.model.unes.EdgeServiceAccount
4648
import com.forcetower.uefs.core.util.isStudentFromUEFS
@@ -171,8 +173,7 @@ class HomeBottomFragment : UFragment() {
171173
true
172174
}
173175
R.id.bug_report -> {
174-
val fragment = SendFeedbackFragment()
175-
fragment.show(childFragmentManager, "feedback_modal")
176+
sendEmail()
176177
true
177178
}
178179
R.id.campus_map -> {
@@ -186,6 +187,15 @@ class HomeBottomFragment : UFragment() {
186187
}
187188
}
188189

190+
private fun sendEmail() {
191+
val text = "\n\nVersion: ${BuildConfig.VERSION_NAME}\nCode: ${BuildConfig.VERSION_CODE}\nDevice ID: ${viewModel.requireDeviceId()}"
192+
val intent = Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto", Constants.DEVELOPER_EMAIL, null)).apply {
193+
putExtra(Intent.EXTRA_SUBJECT, "[UNES] App Feedback")
194+
putExtra(Intent.EXTRA_TEXT, text)
195+
}
196+
startActivity(Intent.createChooser(intent, getString(R.string.send_email)))
197+
}
198+
189199
private fun onImagePicked(uri: Uri) {
190200
viewModel.setSelectedImage(uri)
191201
Glide.with(requireContext())

app/src/main/java/com/forcetower/uefs/feature/home/HomeViewModel.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import com.forcetower.uefs.core.storage.resource.Status
5252
import com.forcetower.uefs.core.task.FetchMissingSemestersUseCase
5353
import com.forcetower.uefs.core.work.image.UploadImageToStorage
5454
import com.forcetower.uefs.domain.usecase.auth.EdgeAnonymousLoginUseCase
55+
import com.forcetower.uefs.domain.usecase.device.GetDeviceInfoUseCase
5556
import com.forcetower.uefs.easter.darktheme.DarkThemeRepository
5657
import com.google.android.play.core.install.model.AppUpdateType
5758
import com.google.android.play.core.install.model.InstallStatus
@@ -78,7 +79,8 @@ class HomeViewModel @Inject constructor(
7879
@Named("flagSnowpiercerEnabled")
7980
private val snowpiercerEnabled: Boolean,
8081
private val anonymousLoginUseCase: EdgeAnonymousLoginUseCase,
81-
private val edgeSyncRepository: EdgeSyncRepository
82+
private val edgeSyncRepository: EdgeSyncRepository,
83+
private val deviceInfoUseCase: GetDeviceInfoUseCase
8284
) : AndroidViewModel(application) {
8385
private var selectImageUri: Uri? = null
8486

@@ -231,4 +233,8 @@ class HomeViewModel @Inject constructor(
231233
fetchMissingSemesters(Unit)
232234
}
233235
}
236+
237+
fun requireDeviceId(): String {
238+
return deviceInfoUseCase.machineIdDirect()
239+
}
234240
}

0 commit comments

Comments
 (0)