Skip to content

Commit

Permalink
数据埋点 (#1756)
Browse files Browse the repository at this point in the history
Add analytics
  • Loading branch information
Him188 authored Mar 3, 2025
1 parent 0ff6311 commit 2dab675
Show file tree
Hide file tree
Showing 19 changed files with 613 additions and 55 deletions.
60 changes: 30 additions & 30 deletions .github/workflows/build.yml

Large diffs are not rendered by default.

34 changes: 17 additions & 17 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions .github/workflows/src.main.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ import Secrets.SIGNING_RELEASE_STOREPASSWORD
import Secrets.DANDANPLAY_APP_ID
import Secrets.DANDANPLAY_APP_SECRET
import Secrets.SENTRY_DSN
import Secrets.ANALYTICS_SERVER
import Secrets.ANALYTICS_KEY
import io.github.typesafegithub.workflows.actions.actions.Checkout
import io.github.typesafegithub.workflows.actions.actions.DownloadArtifact
import io.github.typesafegithub.workflows.actions.actions.GithubScript
Expand Down Expand Up @@ -220,6 +222,8 @@ data class MatrixInstance(
add(quote("-Pani.dandanplay.app.id=${expr { secrets.DANDANPLAY_APP_ID }}"))
add(quote("-Pani.dandanplay.app.secret=${expr { secrets.DANDANPLAY_APP_SECRET }}"))
add(quote("-Pani.sentry.dsn=${expr { secrets.SENTRY_DSN }}"))
add(quote("-Pani.analytics.server=${expr { secrets.ANALYTICS_SERVER }}"))
add(quote("-Pani.analytics.key=${expr { secrets.ANALYTICS_KEY }}"))

if (gradleParallel) {
add(quote("--parallel"))
Expand Down Expand Up @@ -1499,6 +1503,8 @@ object Secrets {
val SecretsContext.DANDANPLAY_APP_ID by SecretsContext.propertyToExprPath
val SecretsContext.DANDANPLAY_APP_SECRET by SecretsContext.propertyToExprPath
val SecretsContext.SENTRY_DSN by SecretsContext.propertyToExprPath
val SecretsContext.ANALYTICS_SERVER by SecretsContext.propertyToExprPath
val SecretsContext.ANALYTICS_KEY by SecretsContext.propertyToExprPath
}
/// EXTENSIONS
Expand Down
38 changes: 36 additions & 2 deletions app/android/src/main/kotlin/AniApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,27 @@ import androidx.lifecycle.ProcessLifecycleOwner
import io.ktor.client.engine.okhttp.OkHttp
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import me.him188.ani.android.activity.MainActivity
import me.him188.ani.app.data.repository.user.SettingsRepository
import me.him188.ani.app.domain.torrent.TorrentManager
import me.him188.ani.app.domain.torrent.service.AniTorrentService
import me.him188.ani.app.domain.torrent.service.ServiceConnectionManager
import me.him188.ani.app.platform.AndroidLoggingConfigurator
import me.him188.ani.app.platform.AppStartupTasks
import me.him188.ani.app.platform.JvmLogHelper
import me.him188.ani.app.platform.create
import me.him188.ani.app.platform.createAppRootCoroutineScope
import me.him188.ani.app.platform.currentAniBuildConfig
import me.him188.ani.app.platform.getCommonKoinModule
import me.him188.ani.app.platform.startCommonKoinModule
import me.him188.ani.app.ui.settings.tabs.getLogsDir
import me.him188.ani.app.ui.settings.tabs.media.DEFAULT_TORRENT_CACHE_DIR_NAME
import me.him188.ani.utils.analytics.AnalyticsConfig
import me.him188.ani.utils.analytics.AnalyticsHolder
import me.him188.ani.utils.analytics.AnalyticsImpl
import me.him188.ani.utils.coroutines.IO_
import me.him188.ani.utils.logging.error
import me.him188.ani.utils.logging.logger
Expand Down Expand Up @@ -73,8 +81,6 @@ class AniApplication : Application() {
AndroidLoggingConfigurator.configure(logsDir)
AppStartupTasks.printVersions()

AppStartupTasks.initializeSentry()

val defaultUEH = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
logger<AniApplication>().error(e) { "!!!ANI FATAL EXCEPTION!!! ($e)" }
Expand Down Expand Up @@ -122,9 +128,37 @@ class AniApplication : Application() {
}.startCommonKoinModule(scope)

val koin = getKoin()
val analyticsInitializer = scope.launch {
val settingsRepository = koin.get<SettingsRepository>()
val settings = settingsRepository.analyticsSettings.flow.first()
if (settings.allowAnonymousBugReport) {
AppStartupTasks.initializeSentry()
}
if (settings.isInit) {
settingsRepository.analyticsSettings.update {
copy(isInit = false) // save user id
}
}
if (settings.allowAnonymousAnalytics) {
AnalyticsHolder.init(
AnalyticsImpl(
AnalyticsConfig.create(),
).apply {
init(
this@AniApplication,
apiKey = currentAniBuildConfig.analyticsKey,
host = currentAniBuildConfig.analyticsServer,
)
},
)
}
}

scope.launch(CoroutineName("TorrentManager initializer")) {
koin.get<TorrentManager>() // start sharing, connect to DHT now
}

runBlocking { analyticsInitializer.join() }
}

@SuppressLint("DiscouragedPrivateApi")
Expand Down
37 changes: 35 additions & 2 deletions app/desktop/src/main/kotlin/AniDesktop.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ import me.him188.ani.app.platform.JvmLogHelper
import me.him188.ani.app.platform.LocalContext
import me.him188.ani.app.platform.PermissionManager
import me.him188.ani.app.platform.PlatformWindow
import me.him188.ani.app.platform.create
import me.him188.ani.app.platform.createAppRootCoroutineScope
import me.him188.ani.app.platform.currentAniBuildConfig
import me.him188.ani.app.platform.getCommonKoinModule
import me.him188.ani.app.platform.startCommonKoinModule
import me.him188.ani.app.platform.window.HandleWindowsWindowProc
Expand Down Expand Up @@ -111,6 +113,9 @@ import me.him188.ani.app.ui.main.AniApp
import me.him188.ani.app.ui.main.AniAppContent
import me.him188.ani.desktop.generated.resources.Res
import me.him188.ani.desktop.generated.resources.a_round
import me.him188.ani.utils.analytics.AnalyticsConfig
import me.him188.ani.utils.analytics.AnalyticsHolder
import me.him188.ani.utils.analytics.AnalyticsImpl
import me.him188.ani.utils.io.inSystem
import me.him188.ani.utils.io.toKtPath
import me.him188.ani.utils.logging.error
Expand Down Expand Up @@ -189,8 +194,6 @@ object AniDesktop {
}
AppStartupTasks.printVersions()

AppStartupTasks.initializeSentry()

logger.info { "dataDir: file://${dataDir.absolutePathString().replace(" ", "%20")}" }
logger.info { "cacheDir: file://${cacheDir.absolutePathString().replace(" ", "%20")}" }
logger.info { "logsDir: file://${logsDir.absolutePath.replace(" ", "%20")}" }
Expand Down Expand Up @@ -302,6 +305,32 @@ object AniDesktop {
}
TestTasks.handleTestTask(taskName, args, context)
}
val settingsRepository = koin.koin.get<SettingsRepository>()

val analyticsInitializer = coroutineScope.launch {
val settings = settingsRepository.analyticsSettings.flow.first()
if (settings.allowAnonymousBugReport) {
AppStartupTasks.initializeSentry()
}
if (settings.isInit) {
settingsRepository.analyticsSettings.update {
copy(isInit = false) // save user id
}
}
if (settings.allowAnonymousAnalytics) {
AnalyticsHolder.init(
AnalyticsImpl(
AnalyticsConfig.create(),
settings.userId,
).apply {
init(
apiKey = currentAniBuildConfig.analyticsKey,
host = currentAniBuildConfig.analyticsServer,
)
},
)
}
}

val loadAnitorrentJob = coroutineScope.launch {
try {
Expand Down Expand Up @@ -369,6 +398,10 @@ object AniDesktop {

val systemThemeDetector = SystemThemeDetector()

if (analyticsInitializer.isActive) {
runBlocking { analyticsInitializer.join() }
}

application {
WindowStateRecorder(
windowState = windowState,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (C) 2024-2025 OpenAni and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license, which can be found at the following link.
*
* https://github.com/open-ani/ani/blob/main/LICENSE
*/

package me.him188.ani.app.data.models.preference

import kotlinx.serialization.Serializable
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid

@Serializable
data class AnalyticsSettings(
val allowAnonymousBugReport: Boolean,
val allowAnonymousAnalytics: Boolean,
val userId: String,
val isInit: Boolean,
) {
companion object {
@OptIn(ExperimentalUuidApi::class)
fun default() = AnalyticsSettings(
allowAnonymousBugReport = true,
allowAnonymousAnalytics = true,
userId = Uuid.random().toString(),
isInit = true,
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (C) 2024 OpenAni and contributors.
* Copyright (C) 2024-2025 OpenAni and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license, which can be found at the following link.
Expand All @@ -23,6 +23,7 @@ import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import me.him188.ani.app.data.models.danmaku.DanmakuConfigSerializer
import me.him188.ani.app.data.models.danmaku.DanmakuFilterConfig
import me.him188.ani.app.data.models.preference.AnalyticsSettings
import me.him188.ani.app.data.models.preference.AnitorrentConfig
import me.him188.ani.app.data.models.preference.DanmakuSettings
import me.him188.ani.app.data.models.preference.DebugSettings
Expand Down Expand Up @@ -78,6 +79,7 @@ interface SettingsRepository {

val oneshotActionConfig: Settings<OneshotActionConfig>

val analyticsSettings: Settings<AnalyticsSettings>
val debugSettings: Settings<DebugSettings>
}

Expand Down Expand Up @@ -227,7 +229,7 @@ class PreferencesRepositoryImpl(
override val torrentPeerConfig: Settings<TorrentPeerConfig> = SerializablePreference(
"torrentPeerConfig",
TorrentPeerConfig.serializer(),
default = { TorrentPeerConfig.Default }
default = { TorrentPeerConfig.Default },
)

override val oneshotActionConfig: Settings<OneshotActionConfig> = SerializablePreference(
Expand All @@ -236,6 +238,12 @@ class PreferencesRepositoryImpl(
default = { OneshotActionConfig.Default },
)

override val analyticsSettings: Settings<AnalyticsSettings> = SerializablePreference(
"analyticsSettings",
AnalyticsSettings.serializer(),
default = { AnalyticsSettings.default() },
)

override val debugSettings: Settings<DebugSettings> = SerializablePreference(
"debugSettings",
DebugSettings.serializer(),
Expand Down
9 changes: 9 additions & 0 deletions app/shared/app-platform/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ kotlin {

api(libs.koin.core)
api(libs.sentry.kotlin.multiplatform)
api(projects.utils.analytics)
}
sourceSets.commonTest.dependencies {
api(projects.utils.uiTesting)
Expand All @@ -70,6 +71,8 @@ val aniAuthServerUrlRelease = getPropertyOrNull("ani.auth.server.url.release") ?
val dandanplayAppId = getPropertyOrNull("ani.dandanplay.app.id") ?: ""
val dandanplayAppSecret = getPropertyOrNull("ani.dandanplay.app.secret") ?: ""
val sentryDsn = getPropertyOrNull("ani.sentry.dsn") ?: ""
val analyticsServer = getPropertyOrNull("ani.analytics.server") ?: ""
val analyticsKey = getPropertyOrNull("ani.analytics.key") ?: ""

//if (bangumiClientDesktopAppId == null || bangumiClientDesktopSecret == null) {
// logger.warn("bangumi.oauth.client.desktop.appId or bangumi.oauth.client.desktop.secret is not set. Bangumi authorization will not work. Get a token from https://bgm.tv/dev/app and set them in local.properties.")
Expand All @@ -81,6 +84,8 @@ android {
buildConfigField("String", "DANDANPLAY_APP_ID", "\"$dandanplayAppId\"")
buildConfigField("String", "DANDANPLAY_APP_SECRET", "\"$dandanplayAppSecret\"")
buildConfigField("String", "SENTRY_DSN", "\"$sentryDsn\"")
buildConfigField("String", "ANALYTICS_KEY", "\"$analyticsKey\"")
buildConfigField("String", "ANALYTICS_SERVER", "\"$analyticsServer\"")
}
buildTypes.getByName("release") {
isMinifyEnabled = false
Expand Down Expand Up @@ -136,6 +141,8 @@ val generateAniBuildConfigDesktop = tasks.register("generateAniBuildConfigDeskto
override val dandanplayAppId = "$dandanplayAppId"
override val dandanplayAppSecret = "$dandanplayAppSecret"
override val sentryDsn = "$sentryDsn"
override val analyticsKey = "$analyticsKey"
override val analyticsServer = "$analyticsServer"
}
""".trimIndent()

Expand Down Expand Up @@ -171,6 +178,8 @@ if (enableIos) {
override val dandanplayAppId = "$dandanplayAppId"
override val dandanplayAppSecret = "$dandanplayAppSecret"
override val sentryDsn = "$sentryDsn"
override val analyticsKey = "$analyticsKey"
override val analyticsServer = "$analyticsServer"
}
""".trimIndent()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ private object AniBuildConfigAndroid : AniBuildConfig {
get() = BuildConfig.DANDANPLAY_APP_SECRET
override val sentryDsn: String
get() = BuildConfig.SENTRY_DSN
override val analyticsServer: String
get() = BuildConfig.ANALYTICS_SERVER
override val analyticsKey: String
get() = BuildConfig.ANALYTICS_KEY
}

@Stable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ interface AniBuildConfig {
val dandanplayAppId: String
val dandanplayAppSecret: String
val sentryDsn: String
val analyticsServer: String
val analyticsKey: String

companion object {
@Stable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import me.him188.ani.app.domain.session.AuthorizationCancelledException
import me.him188.ani.app.domain.session.AuthorizationFailedException
import me.him188.ani.app.domain.session.SessionManager
import me.him188.ani.app.domain.session.SessionStatus
import me.him188.ani.utils.analytics.AnalyticsConfig
import me.him188.ani.utils.logging.info
import me.him188.ani.utils.logging.logger
import me.him188.ani.utils.logging.warn
Expand Down Expand Up @@ -56,3 +57,11 @@ object AppStartupTasks {

private val logger = logger<AppStartupTasks>()
}

fun AnalyticsConfig.Companion.create(): AnalyticsConfig {
return AnalyticsConfig(
currentAniBuildConfig.versionName,
currentAniBuildConfig.isDebug,
enabled = currentAniBuildConfig.isDebug, // todo: ask for consent then enable for production
)
}
Loading

0 comments on commit 2dab675

Please sign in to comment.