Skip to content
Snippets Groups Projects
Unverified Commit ccae06a7 authored by Matthias Urhahn's avatar Matthias Urhahn Committed by GitHub
Browse files

Remove legacy FileLogger in favor of new DebugLogger (DEV) #2050

parent d9a965f5
No related branches found
No related tags found
No related merge requests found
Showing
with 40 additions and 215 deletions
......@@ -2,20 +2,19 @@ package de.rki.coronawarnapp.test.debugoptions.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.format.Formatter
import android.view.View
import android.widget.RadioButton
import android.widget.RadioGroup
import androidx.core.view.ViewCompat
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import de.rki.coronawarnapp.R
import de.rki.coronawarnapp.databinding.FragmentTestDebugoptionsBinding
import de.rki.coronawarnapp.test.menu.ui.TestMenuItem
import de.rki.coronawarnapp.util.di.AutoInject
import de.rki.coronawarnapp.util.ui.observe2
import de.rki.coronawarnapp.util.ui.setGone
import de.rki.coronawarnapp.util.ui.viewBindingLazy
import de.rki.coronawarnapp.util.viewmodel.CWAViewModelFactoryProvider
import de.rki.coronawarnapp.util.viewmodel.cwaViewModels
......@@ -32,19 +31,9 @@ class DebugOptionsFragment : Fragment(R.layout.fragment_test_debugoptions), Auto
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.testLogfileToggle.apply {
setOnClickListener { vm.setLoggerEnabled(isChecked) }
binding.showDebugLogScreen.setOnClickListener {
findNavController().navigate(R.id.debuglogFragment)
}
vm.loggerState.observe2(this) { state ->
binding.apply {
testLogfileToggle.isChecked = state.isLogging
val logSize = Formatter.formatShortFileSize(requireContext(), state.logsize)
testLogfileToggle.text = "Logfile enabled ($logSize)"
testLogfileShare.setGone(!state.isLogging)
}
}
binding.testLogfileShare.setOnClickListener { vm.shareLogFile() }
vm.logShareEvent.observe2(this) { showSnackBar("Logfile copied to $it") }
// Server environment card
binding.environmentToggleGroup.apply {
......
package de.rki.coronawarnapp.test.debugoptions.ui
import android.content.Context
import com.squareup.inject.assisted.AssistedInject
import de.rki.coronawarnapp.environment.EnvironmentSetup
import de.rki.coronawarnapp.environment.EnvironmentSetup.Type.Companion.toEnvironmentType
import de.rki.coronawarnapp.test.debugoptions.ui.EnvironmentState.Companion.toEnvironmentState
import de.rki.coronawarnapp.util.CWADebug
import de.rki.coronawarnapp.util.coroutine.DispatcherProvider
import de.rki.coronawarnapp.util.debug.LoggerState.Companion.toLoggerState
import de.rki.coronawarnapp.util.di.AppContext
import de.rki.coronawarnapp.util.ui.SingleLiveEvent
import de.rki.coronawarnapp.util.ui.smartLiveData
import de.rki.coronawarnapp.util.viewmodel.CWAViewModel
import de.rki.coronawarnapp.util.viewmodel.SimpleCWAViewModelFactory
import java.io.File
class DebugOptionsFragmentViewModel @AssistedInject constructor(
@AppContext private val context: Context,
private val envSetup: EnvironmentSetup,
dispatcherProvider: DispatcherProvider
) : CWAViewModel(dispatcherProvider = dispatcherProvider) {
......@@ -34,36 +28,6 @@ class DebugOptionsFragmentViewModel @AssistedInject constructor(
}
}
val loggerState by smartLiveData {
CWADebug.toLoggerState()
}
fun setLoggerEnabled(enable: Boolean) {
CWADebug.fileLogger?.let {
if (enable) it.start() else it.stop()
}
loggerState.update { CWADebug.toLoggerState() }
}
val logShareEvent = SingleLiveEvent<File?>()
fun shareLogFile() {
CWADebug.fileLogger?.let {
launch {
if (!it.logFile.exists()) return@launch
val externalPath = File(
context.getExternalFilesDir(null),
"LogFile-${System.currentTimeMillis()}.log"
)
it.logFile.copyTo(externalPath)
logShareEvent.postValue(externalPath)
}
}
}
@AssistedInject.Factory
interface Factory : SimpleCWAViewModelFactory<DebugOptionsFragmentViewModel>
}
......@@ -32,27 +32,26 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Switch
android:id="@+id/test_logfile_toggle"
style="@style/body1"
<TextView
android:id="@+id/new_debuglog_screen_explanation"
style="@style/body2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:layout_marginTop="@dimen/spacing_small"
android:layout_weight="1"
android:text="Logfile enabled"
android:theme="@style/switchBase"
android:text="The debuglog option has moved. It's now available for all builds. For test builds it's started automatically on each app start."
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/debug_container_title" />
<Button
android:id="@+id/test_logfile_share"
android:id="@+id/show_debug_log_screen"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_tiny"
android:text="Share log"
android:text="Open new debug log screen"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/test_logfile_toggle" />
app:layout_constraintTop_toBottomOf="@+id/new_debuglog_screen_explanation" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
......
......@@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.util.Log
import de.rki.coronawarnapp.util.CWADebug
import de.rki.coronawarnapp.util.di.ApplicationComponent
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
......@@ -44,8 +45,19 @@ object DebugLogger : DebugLoggerBase() {
context = application
try {
if (triggerFile.exists()) {
Timber.tag(TAG).i("Trigger file exists, starting debug log.")
val startLogger = when {
triggerFile.exists() -> {
Timber.tag(TAG).i("Trigger file exists, starting debug log.")
true
}
CWADebug.isDeviceForTestersBuild -> {
Timber.tag(TAG).i("Trigger file does not exist, but it's a tester build, starting debug log.")
true
}
else -> false
}
if (startLogger) {
runBlocking { start() }
}
} catch (e: Exception) {
......
......@@ -5,13 +5,11 @@ import android.os.Build
import androidx.annotation.VisibleForTesting
import de.rki.coronawarnapp.BuildConfig
import de.rki.coronawarnapp.bugreporting.debuglog.DebugLogger
import de.rki.coronawarnapp.util.debug.FileLogger
import de.rki.coronawarnapp.util.debug.UncaughtExceptionLogger
import de.rki.coronawarnapp.util.di.ApplicationComponent
import timber.log.Timber
object CWADebug {
var fileLogger: FileLogger? = null
fun init(application: Application) {
if (isDebugBuildOrMode) System.setProperty("kotlinx.coroutines.debug", "on")
......@@ -19,9 +17,6 @@ object CWADebug {
if (isDeviceForTestersBuild) {
Timber.plant(Timber.DebugTree())
}
if (isDeviceForTestersBuild) {
fileLogger = FileLogger(application)
}
setupExceptionHandler()
......
package de.rki.coronawarnapp.util.debug
import android.content.Context
import de.rki.coronawarnapp.util.CWADebug
import timber.log.Timber
import java.io.File
class FileLogger constructor(context: Context) {
val logFile = File(context.cacheDir, "FileLoggerTree.log")
private val blockerFile = File(context.filesDir, "FileLoggerTree.blocker")
private var loggerTree: FileLoggerTree? = null
val isLogging: Boolean
get() = loggerTree != null
init {
if (!blockerFile.exists()) {
start()
}
}
fun start() {
if (!CWADebug.isDeviceForTestersBuild) return
if (loggerTree != null) return
loggerTree = FileLoggerTree(logFile).also {
Timber.plant(it)
it.start()
blockerFile.delete()
}
}
fun stop() {
if (!CWADebug.isDeviceForTestersBuild) return
loggerTree?.let {
it.stop()
logFile.delete()
blockerFile.createNewFile()
loggerTree = null
}
}
}
package de.rki.coronawarnapp.util.debug
import android.annotation.SuppressLint
import android.util.Log
import org.joda.time.Instant
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStreamWriter
@SuppressLint("LogNotTimber")
class FileLoggerTree(private val logFile: File) : Timber.DebugTree() {
private var logWriter: OutputStreamWriter? = null
@SuppressLint("SetWorldReadable")
@Synchronized
fun start() {
if (logWriter != null) return
logFile.parentFile.mkdirs()
if (logFile.createNewFile()) {
Log.i(TAG, "File logger writing to " + logFile.path)
}
if (logFile.setReadable(true, false)) {
Log.i(TAG, "Debug run log read permission set")
}
try {
logWriter = OutputStreamWriter(FileOutputStream(logFile, true))
logWriter!!.write("=== BEGIN ===\n")
logWriter!!.write("Logfile: $logFile\n")
logWriter!!.flush()
Log.i(TAG, "File logger started.")
} catch (e: IOException) {
e.printStackTrace()
logFile.delete()
if (logWriter != null) logWriter!!.close()
}
}
@Synchronized
fun stop() {
logWriter?.let {
logWriter = null
try {
it.write("=== END ===\n")
it.close()
} catch (ignore: IOException) {
}
Log.i(TAG, "File logger stopped.")
}
}
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
logWriter?.let {
try {
it.write("${Instant.now()} ${priorityToString(priority)}/$tag: $message\n")
it.flush()
} catch (e: IOException) {
Timber.tag(TAG).e(e)
try {
it.close()
} catch (ignore: Exception) {
}
logWriter = null
}
}
}
override fun toString(): String {
return "FileLoggerTree(file=$logFile)"
}
companion object {
private const val TAG = "FileLoggerTree"
private fun priorityToString(priority: Int): String = when (priority) {
Log.ERROR -> "E"
Log.WARN -> "W"
Log.INFO -> "I"
Log.DEBUG -> "D"
Log.VERBOSE -> "V"
else -> priority.toString()
}
}
}
package de.rki.coronawarnapp.util.debug
import de.rki.coronawarnapp.util.CWADebug
data class LoggerState(
val isLogging: Boolean,
val logsize: Long
) {
companion object {
internal fun CWADebug.toLoggerState() = LoggerState(
isLogging = fileLogger?.isLogging ?: false,
logsize = fileLogger?.logFile?.length() ?: 0L
)
}
}
......@@ -3,6 +3,7 @@ package de.rki.coronawarnapp.bugreporting.debuglog
import android.app.Application
import dagger.Lazy
import de.rki.coronawarnapp.bugreporting.censors.RegistrationTokenCensor
import de.rki.coronawarnapp.util.CWADebug
import de.rki.coronawarnapp.util.di.ApplicationComponent
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
......@@ -10,6 +11,7 @@ import io.mockk.MockKAnnotations
import io.mockk.clearAllMocks
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockkObject
import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
......@@ -36,6 +38,9 @@ class DebugLoggerTest : BaseIOTest() {
@BeforeEach
fun setup() {
MockKAnnotations.init(this)
mockkObject(CWADebug)
every { CWADebug.isDeviceForTestersBuild } returns false
testDir.mkdirs()
testDir.exists() shouldBe true
......@@ -80,6 +85,16 @@ class DebugLoggerTest : BaseIOTest() {
runningLog.exists() shouldBe true
}
@Test
fun `init calls start if it is a tester build`() {
every { CWADebug.isDeviceForTestersBuild } returns true
createInstance().apply {
init(application)
isLogging shouldBe true
}
runningLog.exists() shouldBe true
}
@Test
fun `start plants a tree and starts a logging coroutine`() {
val instance = createInstance().apply {
......
......@@ -55,7 +55,6 @@ class DebugOptionsFragmentViewModelTest : BaseTest() {
}
private fun createViewModel(): DebugOptionsFragmentViewModel = DebugOptionsFragmentViewModel(
context = context,
envSetup = environmentSetup,
dispatcherProvider = TestDispatcherProvider
)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment