Skip to content

Commit

Permalink
Sentry integration (#1755)
Browse files Browse the repository at this point in the history
* Configure Cocoapods for ios target

* 贡献文章中增加构建 iOS

* Add sentry for automatic crash report

* configure CI

* install cocoapods on macos ci

* Add ios deployment target

* fix compile
  • Loading branch information
Him188 authored Mar 3, 2025
1 parent c22a044 commit 0ff6311
Show file tree
Hide file tree
Showing 23 changed files with 328 additions and 77 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.

3 changes: 3 additions & 0 deletions .github/workflows/src.main.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import Secrets.SIGNING_RELEASE_STOREFILE
import Secrets.SIGNING_RELEASE_STOREPASSWORD
import Secrets.DANDANPLAY_APP_ID
import Secrets.DANDANPLAY_APP_SECRET
import Secrets.SENTRY_DSN
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 @@ -218,6 +219,7 @@ data class MatrixInstance(
add(quote("-Dkotlin.daemon.jvm.options=-Xmx${kotlinCompilerHeap}"))
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 }}"))

if (gradleParallel) {
add(quote("--parallel"))
Expand Down Expand Up @@ -1496,6 +1498,7 @@ object Secrets {
val SecretsContext.AWS_BUCKET by SecretsContext.propertyToExprPath
val SecretsContext.DANDANPLAY_APP_ID by SecretsContext.propertyToExprPath
val SecretsContext.DANDANPLAY_APP_SECRET by SecretsContext.propertyToExprPath
val SecretsContext.SENTRY_DSN by SecretsContext.propertyToExprPath
}
/// EXTENSIONS
Expand Down
2 changes: 2 additions & 0 deletions app/android/src/main/kotlin/AniApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ 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
8 changes: 5 additions & 3 deletions app/desktop/src/main/kotlin/AniDesktop.kt
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ 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 @@ -300,11 +302,11 @@ object AniDesktop {
}
TestTasks.handleTestTask(taskName, args, context)
}

val loadAnitorrentJob = coroutineScope.launch {
try {
AnitorrentLibraryLoader.loadLibraries()
} catch (e: Throwable){
} catch (e: Throwable) {
logger.error(e) { "Failed to load anitorrent libraries" }
}
}
Expand All @@ -317,7 +319,7 @@ object AniDesktop {
}
// Load anitorrent libraries before JCEF, so they won't load at the same time.
// We suspect concurrent loading of native libraries may cause some issues #1121.

val proxySettings = koin.koin.get<ProxyProvider>()
.proxy.first()

Expand Down
7 changes: 7 additions & 0 deletions app/shared/app-platform/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
* https://github.com/open-ani/ani/blob/main/LICENSE
*/

import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType
import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool

plugins {
Expand Down Expand Up @@ -42,6 +44,7 @@ kotlin {
api(libs.compose.material3.adaptive.navigation0)

api(libs.koin.core)
api(libs.sentry.kotlin.multiplatform)
}
sourceSets.commonTest.dependencies {
api(projects.utils.uiTesting)
Expand All @@ -66,6 +69,7 @@ val aniAuthServerUrlDebug =
val aniAuthServerUrlRelease = getPropertyOrNull("ani.auth.server.url.release") ?: "https://auth.myani.org"
val dandanplayAppId = getPropertyOrNull("ani.dandanplay.app.id") ?: ""
val dandanplayAppSecret = getPropertyOrNull("ani.dandanplay.app.secret") ?: ""
val sentryDsn = getPropertyOrNull("ani.sentry.dsn") ?: ""

//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 @@ -76,6 +80,7 @@ android {
buildConfigField("String", "VERSION_NAME", "\"${getProperty("version.name")}\"")
buildConfigField("String", "DANDANPLAY_APP_ID", "\"$dandanplayAppId\"")
buildConfigField("String", "DANDANPLAY_APP_SECRET", "\"$dandanplayAppSecret\"")
buildConfigField("String", "SENTRY_DSN", "\"$sentryDsn\"")
}
buildTypes.getByName("release") {
isMinifyEnabled = false
Expand Down Expand Up @@ -130,6 +135,7 @@ val generateAniBuildConfigDesktop = tasks.register("generateAniBuildConfigDeskto
override val aniAuthServerUrl = if (isDebug) "$aniAuthServerUrlDebug" else "$aniAuthServerUrlRelease"
override val dandanplayAppId = "$dandanplayAppId"
override val dandanplayAppSecret = "$dandanplayAppSecret"
override val sentryDsn = "$sentryDsn"
}
""".trimIndent()

Expand Down Expand Up @@ -164,6 +170,7 @@ if (enableIos) {
override val aniAuthServerUrl = if (isDebug) "$aniAuthServerUrlDebug" else "$aniAuthServerUrlRelease"
override val dandanplayAppId = "$dandanplayAppId"
override val dandanplayAppSecret = "$dandanplayAppSecret"
override val sentryDsn = "$sentryDsn"
}
""".trimIndent()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ private object AniBuildConfigAndroid : AniBuildConfig {
get() = BuildConfig.DANDANPLAY_APP_ID
override val dandanplayAppSecret: String
get() = BuildConfig.DANDANPLAY_APP_SECRET
override val sentryDsn: String
get() = BuildConfig.SENTRY_DSN
}

@Stable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ interface AniBuildConfig {
val aniAuthServerUrl: String
val dandanplayAppId: String
val dandanplayAppSecret: String
val sentryDsn: String

companion object {
@Stable
Expand Down
53 changes: 53 additions & 0 deletions app/shared/app-platform/src/commonMain/kotlin/trace/ErrorReport.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.trace

import io.sentry.kotlin.multiplatform.Scope
import io.sentry.kotlin.multiplatform.Sentry
import io.sentry.kotlin.multiplatform.SentryLevel
import io.sentry.kotlin.multiplatform.protocol.Breadcrumb
import io.sentry.kotlin.multiplatform.protocol.SentryId

object ErrorReport {
inline fun captureMessage(
message: String,
level: SentryLevel = SentryLevel.INFO,
crossinline config: Scope.() -> Unit = {}
): SentryId {
return if (Sentry.isEnabled()) {
Sentry.captureMessage(message) {
it.level = level
config(it)
}
} else {
SentryId.EMPTY_ID
}
}

inline fun breadcrumb(
breadcrumb: Breadcrumb,
config: Breadcrumb.() -> Unit = {}
) = Sentry.addBreadcrumb(breadcrumb.apply(config))

inline fun captureException(
throwable: Throwable,
level: SentryLevel = SentryLevel.ERROR,
crossinline config: Scope.() -> Unit = {}
): SentryId {
return if (Sentry.isEnabled()) {
Sentry.captureException(throwable) {
it.level = level
config(it)
}
} else {
SentryId.EMPTY_ID
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.platform

import io.sentry.kotlin.multiplatform.Sentry

internal actual fun initializeErrorReport() {
Sentry.init { options ->
CommonTracingInitializer.configureSentryOptions(options)
}
Sentry.configureScope {
CommonTracingInitializer.configureGlobalScope(it)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import me.him188.ani.utils.platform.currentPlatform
import kotlin.coroutines.cancellation.CancellationException

object AppStartupTasks {
fun initializeSentry() {
initializeErrorReport()
}

fun printVersions() {
logger.info { "Ani started. platform: ${currentPlatform()}, version: ${currentAniBuildConfig.versionName}, isDebug: ${currentAniBuildConfig.isDebug}" }
}
Expand Down Expand Up @@ -51,4 +55,4 @@ object AppStartupTasks {
}

private val logger = logger<AppStartupTasks>()
}
}
40 changes: 40 additions & 0 deletions app/shared/application/src/commonMain/kotlin/platform/Tracing.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* 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.platform

import io.sentry.kotlin.multiplatform.Scope
import io.sentry.kotlin.multiplatform.SentryOptions
import me.him188.ani.app.trace.ErrorReport
import me.him188.ani.utils.platform.currentPlatform


/**
* Shares initialization logic across platforms. Only used by [initializeErrorReport].
*/
internal object CommonTracingInitializer {
fun configureSentryOptions(options: SentryOptions) {
val buildConfig = currentAniBuildConfig
options.dsn = buildConfig.sentryDsn
options.debug = buildConfig.isDebug
options.release = "me.him188.ani@${buildConfig.versionName}"
}

fun configureGlobalScope(scope: Scope) {
val platform = currentPlatform()
scope.setContext("os", platform.name)
scope.setContext("arch", platform.arch.name)
scope.setContext("version", currentAniBuildConfig.versionName)
}
}

/**
* Initializes [ErrorReport] for the platform.
*/
internal expect fun initializeErrorReport()
4 changes: 2 additions & 2 deletions app/shared/application/src/iosMain/kotlin/ios/AniIos.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import androidx.compose.ui.window.ComposeUIViewController
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.CoroutineScope
import me.him188.ani.app.data.repository.user.SettingsRepository
import me.him188.ani.app.domain.foundation.HttpClientProvider
import me.him188.ani.app.domain.foundation.get
import me.him188.ani.app.domain.media.resolver.HttpStreamingMediaResolver
Expand All @@ -38,7 +37,6 @@ import me.him188.ani.app.domain.torrent.TorrentManager
import me.him188.ani.app.navigation.AniNavigator
import me.him188.ani.app.navigation.BrowserNavigator
import me.him188.ani.app.navigation.LocalNavigator
import me.him188.ani.app.navigation.NavRoutes
import me.him188.ani.app.navigation.NoopBrowserNavigator
import me.him188.ani.app.platform.AppStartupTasks
import me.him188.ani.app.platform.GrantedPermissionManager
Expand Down Expand Up @@ -88,6 +86,8 @@ fun MainViewController(): UIViewController {
)
AppStartupTasks.printVersions()

AppStartupTasks.initializeSentry()

val koin = startKoin {
modules(getCommonKoinModule({ context }, scope))
modules(getIosModules(SystemDocumentDir.resolve("torrent"), scope))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.platform

import io.sentry.kotlin.multiplatform.Sentry

internal actual fun initializeErrorReport() {
Sentry.init { options ->
CommonTracingInitializer.configureSentryOptions(options)
}
Sentry.configureScope {
CommonTracingInitializer.configureGlobalScope(it)
}
}
47 changes: 28 additions & 19 deletions app/shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@

@file:Suppress("UnstableApiUsage")

import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType


plugins {
kotlin("multiplatform")
Expand All @@ -21,6 +24,8 @@ plugins {

kotlin("plugin.serialization")
id("org.jetbrains.kotlinx.atomicfu")
kotlin("native.cocoapods")
id("io.sentry.kotlin.multiplatform.gradle")
idea
}

Expand All @@ -36,14 +41,18 @@ atomicfu {
val enableIosFramework = enableIos && getPropertyOrNull("ani.build.framework") != "false"

kotlin {
if (enableIosFramework) {
listOf(
iosArm64(),
iosSimulatorArm64(),
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "ComposeApp"
if (enableIos) {
// Sentry requires cocoapods for its dependencies
cocoapods {
// https://kotlinlang.org/docs/native-cocoapods.html#configure-existing-project
framework {
baseName = "AnimekoFramework"
isStatic = false
// Dependency export
// Uncomment and specify another project module if you have one:
// export(project(":<your other KMP module>"))
@OptIn(ExperimentalKotlinGradlePluginApi::class)
transitiveExport = false // This is default.
}
}
}
Expand Down Expand Up @@ -273,16 +282,16 @@ dependencies {

if (enableIosFramework) {
// 太耗内存了, 只能一次跑一个
tasks.named("linkDebugFrameworkIosArm64") {
mustRunAfter("linkReleaseFrameworkIosArm64")
mustRunAfter("linkDebugFrameworkIosSimulatorArm64")
mustRunAfter("linkReleaseFrameworkIosSimulatorArm64")
}
tasks.named("linkReleaseFrameworkIosArm64") {
mustRunAfter("linkDebugFrameworkIosSimulatorArm64")
mustRunAfter("linkReleaseFrameworkIosSimulatorArm64")
}
tasks.named("linkDebugFrameworkIosSimulatorArm64") {
mustRunAfter("linkReleaseFrameworkIosSimulatorArm64")
}
// tasks.named("linkDebugFrameworkIosArm64") {
// mustRunAfter("linkReleaseFrameworkIosArm64")
// mustRunAfter("linkDebugFrameworkIosSimulatorArm64")
// mustRunAfter("linkReleaseFrameworkIosSimulatorArm64")
// }
// tasks.named("linkReleaseFrameworkIosArm64") {
// mustRunAfter("linkDebugFrameworkIosSimulatorArm64")
// mustRunAfter("linkReleaseFrameworkIosSimulatorArm64")
// }
// tasks.named("linkDebugFrameworkIosSimulatorArm64") {
// mustRunAfter("linkReleaseFrameworkIosSimulatorArm64")
// }
}
Loading

0 comments on commit 0ff6311

Please sign in to comment.