-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
431 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
composeApp/src/commonMain/kotlin/org/ooni/probe/shared/monitoring/AppLogger.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
158
composeApp/src/commonMain/kotlin/org/ooni/probe/ui/log/LogScreen.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
) |
Oops, something went wrong.