Skip to content

Commit

Permalink
See recent logs
Browse files Browse the repository at this point in the history
  • Loading branch information
sdsantos committed Sep 25, 2024
1 parent e329aa4 commit 24d9997
Show file tree
Hide file tree
Showing 18 changed files with 431 additions and 58 deletions.
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"

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 @@ -253,4 +255,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>
13 changes: 7 additions & 6 deletions composeApp/src/commonMain/kotlin/org/ooni/probe/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,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 @@ -78,10 +79,10 @@ fun App(
}
}

Logger.addLogWriter(dependencies.crashMonitoring.logWriter)

LaunchedEffect(Unit) {
logAppStart(dependencies)
Logger.addLogWriter(dependencies.crashMonitoring.logWriter)
Logger.addLogWriter(dependencies.appLogger.logWriter)
logAppStart(dependencies.platformInfo)
}
LaunchedEffect(Unit) {
dependencies.crashMonitoring.setup()
Expand All @@ -108,9 +109,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
17 changes: 17 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 @@ -65,10 +65,12 @@ 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.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 @@ -122,6 +124,14 @@ class Dependencies(
// Monitoring

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

// Engine

Expand Down Expand Up @@ -322,6 +332,13 @@ class Dependencies(
deleteTestDescriptor = deleteTestDescriptor::invoke,
)

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
}
}
158 changes: 158 additions & 0 deletions composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package org.ooni.probe.ui.log

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import co.touchlab.kermit.Severity
import ooniprobe.composeapp.generated.resources.Res
import ooniprobe.composeapp.generated.resources.Settings_Storage_Delete
import ooniprobe.composeapp.generated.resources.back
import ooniprobe.composeapp.generated.resources.filter_logs
import ooniprobe.composeapp.generated.resources.ic_delete_all
import ooniprobe.composeapp.generated.resources.logs
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
import org.ooni.probe.ui.shared.CustomFilterChip
import org.ooni.probe.ui.theme.LocalCustomColors

@Composable
fun LogScreen(
state: LogViewModel.State,
onEvent: (LogViewModel.Event) -> Unit,
) {
Column {
TopAppBar(
title = { Text(stringResource(Res.string.logs)) },
navigationIcon = {
IconButton(onClick = { onEvent(LogViewModel.Event.BackClicked) }) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(Res.string.back),
)
}
},
actions = {
IconButton(onClick = { onEvent(LogViewModel.Event.ClearClicked) }) {
Icon(
painterResource(Res.drawable.ic_delete_all),
contentDescription = stringResource(Res.string.Settings_Storage_Delete),
)
}
},
)

Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp),
) {
Text(
stringResource(Res.string.filter_logs),
modifier = Modifier.weight(2f),
)
SeverityFilter(
current = state.filter,
onFilterChanged = { onEvent(LogViewModel.Event.FilterChanged(it)) },
modifier = Modifier.weight(1f),
)
}

LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(
start = 16.dp,
end = 16.dp,
bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding(),
),
) {
items(state.log) { line ->
Text(
line,
style = MaterialTheme.typography.labelMedium,
color = when {
line.contains(": WARN : ") -> LocalCustomColors.current.logWarn
line.contains(": ERROR : ") -> LocalCustomColors.current.logError
line.contains(": INFO : ") -> LocalCustomColors.current.logInfo
else -> LocalCustomColors.current.logDebug
},
)
}
}
}
}

@Composable
fun SeverityFilter(
current: Severity?,
onFilterChanged: (Severity?) -> Unit,
modifier: Modifier = Modifier,
) {
var expanded by remember { mutableStateOf(false) }

ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
modifier = modifier.width(IntrinsicSize.Min),
) {
CustomFilterChip(
text = current.label(),
selected = current != null,
onClick = { expanded = true },
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
SEVERITY_OPTIONS.forEach { option ->
DropdownMenuItem(
text = { Text(option.label()) },
onClick = {
onFilterChanged(option)
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
)
}
}
}
}

private fun Severity?.label() = this?.name?.uppercase() ?: "ALL"

private val SEVERITY_OPTIONS = listOf(
null,
Severity.Error,
Severity.Warn,
Severity.Info,
Severity.Debug,
)
Loading

0 comments on commit 24d9997

Please sign in to comment.