Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ kotlin {
iosSimulatorArm64()

cocoapods {
ios.deploymentTarget = "12.0"
ios.deploymentTarget = "15.3"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sentry is the only thing forcing us to jump versions, can we use an older version of sentry?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was me during bug fixing. But I just copied the minimum we had on the iOS config files. I think we had a lower minimum here than there, so I just matched it. Our minimum is 15.3 right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its 13.0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, then I have to fix it on a bunch of different places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in #133


version = "1.0"
summary = "Compose App"
Expand All @@ -100,6 +100,11 @@ kotlin {
binaryOption("bundleId", "composeApp")
}

pod("Sentry") {
version = "~> 8.25"
extraOpts += listOf("-compiler-option", "-fmodules")
}

podfile = project.file("../iosApp/Podfile")
}

Expand Down Expand Up @@ -178,7 +183,11 @@ android {
versionName = "1.0"
resValue("string", "app_name", config.appName)
resValue("string", "ooni_run_enabled", config.supportsOoniRun.toString())
resValue("string", "supported_languages", config.supportedLanguages.joinToString(separator = ","))
resValue(
"string",
"supported_languages",
config.supportedLanguages.joinToString(separator = ","),
)
resourceConfigurations += config.supportedLanguages
}
packaging {
Expand Down
4 changes: 2 additions & 2 deletions composeApp/composeApp.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Pod::Spec.new do |spec|
spec.summary = 'Compose App'
spec.vendored_frameworks = 'build/cocoapods/framework/composeApp.framework'
spec.libraries = 'c++'
spec.ios.deployment_target = '12.0'
spec.ios.deployment_target = '15.3'
spec.dependency 'Sentry', '~> 8.25'

if !Dir.exist?('build/cocoapods/framework/composeApp.framework') || Dir.empty?('build/cocoapods/framework/composeApp.framework')
raise "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@
<string name="Settings_Advanced_RecentLogs">See recent logs</string>
<string name="Settings_Advanced_LanguageSettings_Title">Language Setting</string>
<string name="Settings_Storage_Label">Storage usage</string>
<string name="Settings_Storage_Delete">Delete</string>
<string name="Settings_Storage_Clear">Clear</string>
<string name="Settings_WarmVPNInUse_Label">Warn when VPN is in use</string>

<string name="CategoryCode_ALDR_Name">Drugs &amp; Alcohol</string>
Expand Down Expand Up @@ -263,4 +265,7 @@
<string name="measurement_anomaly">Anomaly</string>
<string name="quiz_answer_correct">Correct answer</string>
<string name="quiz_answer_incorrect">Incorrect answer</string>
<string name="logs">Logs</string>
<string name="share_logs">Share Logs</string>
<string name="filter_logs">Filter Logs</string>
</resources>
14 changes: 10 additions & 4 deletions composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import co.touchlab.kermit.Logger
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.ooni.probe.data.models.DeepLink
import org.ooni.probe.di.Dependencies
import org.ooni.probe.shared.PlatformInfo
import org.ooni.probe.ui.navigation.BottomNavigationBar
import org.ooni.probe.ui.navigation.Navigation
import org.ooni.probe.ui.navigation.Screen
Expand Down Expand Up @@ -82,7 +83,12 @@ fun App(
}

LaunchedEffect(Unit) {
logAppStart(dependencies)
Logger.addLogWriter(dependencies.crashMonitoring.logWriter)
Logger.addLogWriter(dependencies.appLogger.logWriter)
logAppStart(dependencies.platformInfo)
}
LaunchedEffect(Unit) {
dependencies.crashMonitoring.setup()
}
LaunchedEffect(Unit) {
dependencies.bootstrapTestDescriptors()
Expand Down Expand Up @@ -110,9 +116,9 @@ fun App(
}
}

private fun logAppStart(dependencies: Dependencies) {
with(dependencies.platformInfo) {
Logger.i(
private fun logAppStart(platformInfo: PlatformInfo) {
with(platformInfo) {
Logger.v(
"""
---APP START---
Platform: $platform ($osVersion)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class ReadFileOkio(
readUtf8()
}
} catch (e: IOException) {
Logger.w("Could not read $path", e)
Logger.v("Could not read $path", e)
null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class WriteFileOkio(
try {
absolutePath.parent?.let { fileSystem.createDirectories(it) }
} catch (e: IOException) {
Logger.w("Could not create file $path", e)
Logger.v("Could not create file $path", e)
return
}

Expand Down
22 changes: 22 additions & 0 deletions composeApp/src/commonMain/kotlin/org/ooni/probe/di/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,13 @@ import org.ooni.probe.domain.ShouldShowVpnWarning
import org.ooni.probe.domain.TestRunStateManager
import org.ooni.probe.domain.UploadMissingMeasurements
import org.ooni.probe.shared.PlatformInfo
import org.ooni.probe.shared.monitoring.AppLogger
import org.ooni.probe.shared.monitoring.CrashMonitoring
import org.ooni.probe.ui.dashboard.DashboardViewModel
import org.ooni.probe.ui.descriptor.DescriptorViewModel
import org.ooni.probe.ui.descriptor.add.AddDescriptorViewModel
import org.ooni.probe.ui.descriptor.review.ReviewUpdatesViewModel
import org.ooni.probe.ui.log.LogViewModel
import org.ooni.probe.ui.onboarding.OnboardingViewModel
import org.ooni.probe.ui.result.ResultViewModel
import org.ooni.probe.ui.results.ResultsViewModel
Expand Down Expand Up @@ -125,6 +128,18 @@ class Dependencies(
private val writeFile: WriteFile by lazy { WriteFileOkio(FileSystem.SYSTEM, baseFileDir) }
private val deleteFiles: DeleteFiles by lazy { DeleteFilesOkio(FileSystem.SYSTEM, baseFileDir) }

// Monitoring

val crashMonitoring by lazy { CrashMonitoring(preferenceRepository) }
val appLogger by lazy {
AppLogger(
readFile = readFile,
writeFile = writeFile,
deleteFiles = deleteFiles,
backgroundDispatcher = backgroundDispatcher,
)
}

// Engine

private val taskEventMapper by lazy { TaskEventMapper(networkTypeFinder, json) }
Expand Down Expand Up @@ -346,6 +361,13 @@ class Dependencies(
descriptorUpdates = getDescriptorUpdate::observeAvailableUpdatesState,
)

fun logViewModel(onBack: () -> Unit) =
LogViewModel(
onBack = onBack,
readLog = appLogger::read,
clearLog = appLogger::clear,
)

fun onboardingViewModel(
goToDashboard: () -> Unit,
goToSettings: () -> Unit,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package org.ooni.probe.shared.monitoring

import co.touchlab.kermit.LogWriter
import co.touchlab.kermit.Severity
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.LocalDateTime
import okio.Path.Companion.toPath
import org.ooni.probe.data.disk.DeleteFiles
import org.ooni.probe.data.disk.ReadFile
import org.ooni.probe.data.disk.WriteFile
import org.ooni.probe.shared.now
import org.ooni.probe.ui.shared.logFormat

class AppLogger(
private val readFile: ReadFile,
private val writeFile: WriteFile,
private val deleteFiles: DeleteFiles,
private val backgroundDispatcher: CoroutineDispatcher,
) {
private val log = MutableStateFlow(emptyList<String>())

fun read(severity: Severity?): Flow<List<String>> =
log
.onStart {
if (log.value.isEmpty()) {
log.value = readFile(FILE_PATH).orEmpty().lines()
}
}
.map { lines ->
if (severity == null) {
lines
} else {
lines.filter { line ->
line.contains(": ${severity.name.uppercase()} :")
}
}
}

suspend fun clear() {
withContext(backgroundDispatcher) {
log.value = emptyList()
deleteFiles(FILE_PATH)
}
}

val logWriter = object : LogWriter() {
override fun isLoggable(
tag: String,
severity: Severity,
): Boolean = severity != Severity.Verbose

override fun log(
severity: Severity,
message: String,
tag: String,
throwable: Throwable?,
) {
CoroutineScope(backgroundDispatcher).launch {
val logMessage =
"${LocalDateTime.now().logFormat()} : ${severity.name.uppercase()} : $message"
log.update { lines ->
val newLines = (lines + logMessage).takeLast(MAX_LINES)
writeFile(FILE_PATH, newLines.joinToString("\n"), append = false)
newLines
}
}
}
}

companion object {
private val FILE_PATH = "Log/logger.txt".toPath()
private const val MAX_LINES = 1000
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.ooni.probe.shared.monitoring

import co.touchlab.kermit.LogWriter
import co.touchlab.kermit.Severity
import io.sentry.kotlin.multiplatform.Sentry
import io.sentry.kotlin.multiplatform.SentryLevel
import io.sentry.kotlin.multiplatform.protocol.Breadcrumb
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import org.ooni.probe.data.models.SettingsKey
import org.ooni.probe.data.repositories.PreferenceRepository

class CrashMonitoring(
private val preferencesRepository: PreferenceRepository,
) {
private var isEnabled = false

suspend fun setup() {
preferencesRepository.getValueByKey(SettingsKey.SEND_CRASH)
.onEach { sendCrash ->
if (sendCrash == true) {
Sentry.init {
it.dsn = SENTRY_DSN
}
isEnabled = true
} else {
isEnabled = false
Sentry.close()
}
}
.collect()
}

val logWriter = object : LogWriter() {
override fun isLoggable(
tag: String,
severity: Severity,
): Boolean = isEnabled && severity != Severity.Verbose

override fun log(
severity: Severity,
message: String,
tag: String,
throwable: Throwable?,
) {
if (!isEnabled) return

if (severity == Severity.Error) {
if (throwable != null) {
addBreadcrumb(severity, message, tag)
Sentry.captureException(throwable)
} else {
Sentry.captureMessage(message)
}
} else {
addBreadcrumb(severity, message, tag)
}
}

private fun addBreadcrumb(
severity: Severity,
message: String,
tag: String,
) {
Sentry.addBreadcrumb(
Breadcrumb(
level = when (severity) {
Severity.Verbose,
Severity.Debug,
-> SentryLevel.DEBUG

Severity.Info -> SentryLevel.INFO
Severity.Warn -> SentryLevel.WARNING
Severity.Error -> SentryLevel.ERROR
Severity.Assert -> SentryLevel.ERROR
},
type = when (severity) {
Severity.Debug -> "debug"
Severity.Error -> "error"
else -> "default"
},
message = message,
category = tag,
),
)
}
}

companion object {
private const val SENTRY_DSN =
"https://9dcd83d9519844188803aa817cdcd416@o155150.ingest.sentry.io/5619989"
}
}
Loading