Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 67b0c560 authored by Ashley Soucar's avatar Ashley Soucar Committed by GitHub
Browse files

Merge pull request #9235 from asoucar/sync-logging

Add Mail sync debug logging
parents 50226711 3f1d3e6a
Loading
Loading
Loading
Loading
+7 −1
Original line number Diff line number Diff line
@@ -4,5 +4,11 @@ import org.koin.dsl.module

val loggingModule = module {
    factory<ProcessExecutor> { RealProcessExecutor() }
    factory<LogFileWriter> { LogcatLogFileWriter(contentResolver = get(), processExecutor = get()) }
    factory<LogFileWriter> {
        MultiLogFileWriter(
            contentResolver = get(),
            processExecutor = get(),
            context = get(),
        )
    }
}
+42 −11
Original line number Diff line number Diff line
package net.thunderbird.core.android.logging

import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import java.io.OutputStream
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -12,23 +15,27 @@ interface LogFileWriter {
    suspend fun writeLogTo(contentUri: Uri)
}

class LogcatLogFileWriter(
class MultiLogFileWriter(
    private val contentResolver: ContentResolver,
    private val processExecutor: ProcessExecutor,
    private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
    private val context: Context?,
) : LogFileWriter {
    override suspend fun writeLogTo(contentUri: Uri) {
        return withContext(coroutineDispatcher) {
            writeLogBlocking(contentUri)
            Timber.v("Writing output to content URI: %s", contentUri)
            var uriString = ""
            contentResolver.query(contentUri, null, null, null, null)?.use { cursor ->
                val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                cursor.moveToFirst()
                uriString = cursor.getString(nameIndex)
            }
    }

    private fun writeLogBlocking(contentUri: Uri) {
        Timber.v("Writing logcat output to content URI: %s", contentUri)

            val outputStream = contentResolver.openOutputStream(contentUri, "wt")
                ?: error("Error opening contentUri for writing")

            if (uriString.contains(DEFAULT_SYNC_FILENAME)) {
                copyInternalFileToExternal(outputStream)
                clearInternalFile()
            } else {
                outputStream.use {
                    processExecutor.exec("logcat -d").use { inputStream ->
                        IOUtils.copy(inputStream, outputStream)
@@ -36,3 +43,27 @@ class LogcatLogFileWriter(
                }
            }
        }
    }
    private fun copyInternalFileToExternal(outputStream: OutputStream) {
        outputStream.use {
            try {
                context?.openFileInput("${DEFAULT_SYNC_FILENAME}.txt").use { inputStream ->
                    IOUtils.copy(inputStream, outputStream)
                }
            } catch (e: FileSystemException) {
                Timber.e(" Error while outputting into file: $e")
            }
        }
    }

    private fun clearInternalFile() {
        context?.openFileOutput(
            "${DEFAULT_SYNC_FILENAME}.txt",
            Context.MODE_PRIVATE,
        )?.bufferedWriter()?.write("")
    }

    companion object {
        const val DEFAULT_SYNC_FILENAME = "thunderbird-sync-logs"
    }
}
+6 −3
Original line number Diff line number Diff line
@@ -23,10 +23,11 @@ class LogcatLogFileWriterTest {
    @Test
    fun `write log to contentUri`() = runBlocking {
        val logData = "a".repeat(10_000)
        val logFileWriter = LogcatLogFileWriter(
        val logFileWriter = MultiLogFileWriter(
            contentResolver = createContentResolver(),
            processExecutor = createProcessExecutor(logData),
            coroutineDispatcher = Dispatchers.Unconfined,
            context = null,
        )

        logFileWriter.writeLogTo(contentUri)
@@ -36,10 +37,11 @@ class LogcatLogFileWriterTest {

    @Test(expected = FileNotFoundException::class)
    fun `contentResolver throws`() = runBlocking {
        val logFileWriter = LogcatLogFileWriter(
        val logFileWriter = MultiLogFileWriter(
            contentResolver = createThrowingContentResolver(FileNotFoundException()),
            processExecutor = createProcessExecutor("irrelevant"),
            coroutineDispatcher = Dispatchers.Unconfined,
            context = null,
        )

        logFileWriter.writeLogTo(contentUri)
@@ -47,10 +49,11 @@ class LogcatLogFileWriterTest {

    @Test(expected = IOException::class)
    fun `processExecutor throws`() = runBlocking {
        val logFileWriter = LogcatLogFileWriter(
        val logFileWriter = MultiLogFileWriter(
            contentResolver = createContentResolver(),
            processExecutor = ThrowingProcessExecutor(IOException()),
            coroutineDispatcher = Dispatchers.Unconfined,
            context = null,
        )

        logFileWriter.writeLogTo(contentUri)
+65 −0
Original line number Diff line number Diff line
package com.fsck.k9

import android.content.Context
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import timber.log.Timber

class FileLoggerTree(
    context: Context,
    coroutineContext: CoroutineContext = Dispatchers.IO,
) : Timber.Tree() {
    private val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())

    private val writeFile = context.createFile(fileName = "$DEFAULT_SYNC_FILENAME.txt")
    private val accumulatedLogs = ConcurrentHashMap<String, String>()

    override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
        if (message.contains("sync")) {
            try {
                accumulatedLogs[convertLongToTime(System.currentTimeMillis())] = "priority = $priority, $message"
                createLogFile()
            } catch (e: FileSystemException) {
                Timber.e(" Error while logging into file: $e")
            }
        }
    }

    private fun createLogFile() =
        coroutineScope.launch {
            writeToLogFile()
        }

    private fun writeToLogFile() {
        val result = runCatching {
            writeFile.bufferedWriter().use {
                it.write(accumulatedLogs.entries.joinToString("\n") { it2 -> it2.key + " " + it2.value })
            }
        }
        if (result.isFailure) {
            result.exceptionOrNull()?.printStackTrace()
        }
    }

    private fun convertLongToTime(long: Long): String {
        val date = Date(long)
        val format = SimpleDateFormat(ANDROID_LOG_TIME_FORMAT, Locale.US)
        return format.format(date)
    }
    companion object {
        private const val ANDROID_LOG_TIME_FORMAT = "MM-dd-yy hh:mm:ss.SSS"
        const val DEFAULT_SYNC_FILENAME = "thunderbird-sync-logs"
    }

    private fun Context.createFile(fileName: String): File {
        return File(filesDir, fileName)
    }
}
+19 −1
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ object K9 : KoinComponent {
    private val telemetryManager: TelemetryManager by inject()
    private val featureFlagProvider: FeatureFlagProvider by inject()
    private val logger: Logger by inject()
    private val context: Context by inject()

    /**
     * If this is `true`, various development settings will be enabled.
@@ -126,6 +127,13 @@ object K9 : KoinComponent {
            updateLoggingStatus()
        }

    @JvmStatic
    var isSyncLoggingEnabled: Boolean = false
        set(debug) {
            field = debug
            updateSyncLogging()
        }

    @JvmStatic
    var isSensitiveDebugLoggingEnabled: Boolean = false

@@ -288,7 +296,6 @@ object K9 : KoinComponent {
    var fundingReminderReferenceTimestamp: Long = 0
    var fundingReminderShownTimestamp: Long = 0
    var fundingActivityCounterInMillis: Long = 0

    val isQuietTime: Boolean
        get() {
            if (!isQuietTimeEnabled) {
@@ -334,6 +341,7 @@ object K9 : KoinComponent {
    @Suppress("LongMethod")
    fun loadPrefs(storage: Storage) {
        isDebugLoggingEnabled = storage.getBoolean("enableDebugLogging", DEVELOPER_MODE)
        isSyncLoggingEnabled = storage.getBoolean("enableSyncDebugLogging", false)
        isSensitiveDebugLoggingEnabled = storage.getBoolean("enableSensitiveLogging", false)
        isShowAnimations = storage.getBoolean("animations", true)
        isUseVolumeKeysForNavigation = storage.getBoolean("useVolumeKeysForNavigation", false)
@@ -425,6 +433,7 @@ object K9 : KoinComponent {
    @Suppress("LongMethod")
    internal fun save(editor: StorageEditor) {
        editor.putBoolean("enableDebugLogging", isDebugLoggingEnabled)
        editor.putBoolean("enableSyncDebugLogging", isSyncLoggingEnabled)
        editor.putBoolean("enableSensitiveLogging", isSensitiveDebugLoggingEnabled)
        editor.putEnum("backgroundOperations", backgroundOps)
        editor.putBoolean("animations", isShowAnimations)
@@ -504,6 +513,15 @@ object K9 : KoinComponent {
        }
    }

    private fun updateSyncLogging() {
        if (Timber.forest().contains(FileLoggerTree(context))) {
            Timber.uproot(FileLoggerTree(context))
        }
        if (isSyncLoggingEnabled) {
            Timber.plant(FileLoggerTree(context))
        }
    }

    @JvmStatic
    fun saveSettingsAsync() {
        generalSettingsManager.saveSettingsAsync()
Loading