From baadbef820d4f6598764e80d108735229ca3317c Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Wed, 13 Nov 2024 00:10:58 -0600 Subject: [PATCH] Update file logging * Add ability to send logs * Log app exit info * Redact sensitive info from logs --- app/proguard.pro | 7 -- app/src/debug/java/org/tasks/BuildSetup.kt | 10 +-- app/src/main/java/org/tasks/Tasks.kt | 7 ++ .../org/tasks/caldav/CaldavSynchronizer.kt | 3 +- .../main/java/org/tasks/logging/FileLogger.kt | 66 +++++++++++++++++-- .../preferences/fragments/HelpAndFeedback.kt | 26 ++++++++ app/src/main/res/values/strings.xml | 1 + app/src/main/res/xml/help_and_feedback.xml | 5 ++ app/src/release/java/org/tasks/BuildSetup.kt | 3 +- build.gradle.kts | 1 + data/build.gradle.kts | 6 ++ .../kotlin/org/tasks/data/Redacted.kt | 5 ++ .../org/tasks/data/entity/CaldavAccount.kt | 8 ++- .../org/tasks/data/entity/CaldavCalendar.kt | 3 + .../kotlin/org/tasks/data/entity/Filter.kt | 2 + .../kotlin/org/tasks/data/entity/Place.kt | 7 ++ .../kotlin/org/tasks/data/entity/Principal.kt | 5 +- .../kotlin/org/tasks/data/entity/Tag.kt | 2 + .../kotlin/org/tasks/data/entity/TagData.kt | 2 + .../kotlin/org/tasks/data/entity/Task.kt | 3 + .../org/tasks/data/entity/TaskAttachment.kt | 2 + .../org/tasks/data/entity/UserActivity.kt | 2 + gradle/libs.versions.toml | 1 + 23 files changed, 150 insertions(+), 27 deletions(-) create mode 100644 data/src/commonMain/kotlin/org/tasks/data/Redacted.kt diff --git a/app/proguard.pro b/app/proguard.pro index d37b4fb38..7fed68313 100644 --- a/app/proguard.pro +++ b/app/proguard.pro @@ -2,13 +2,6 @@ -keep class org.tasks.** { *; } -# remove logging statements --assumenosideeffects class timber.log.Timber* { - public static *** v(...); - public static *** d(...); - public static *** i(...); -} - # guava -dontwarn sun.misc.Unsafe -dontwarn java.lang.ClassValue diff --git a/app/src/debug/java/org/tasks/BuildSetup.kt b/app/src/debug/java/org/tasks/BuildSetup.kt index f413c3b6c..dd72c69c1 100644 --- a/app/src/debug/java/org/tasks/BuildSetup.kt +++ b/app/src/debug/java/org/tasks/BuildSetup.kt @@ -17,16 +17,16 @@ import leakcanary.AppWatcher import org.tasks.logging.FileLogger import org.tasks.preferences.Preferences import timber.log.Timber -import timber.log.Timber.DebugTree import javax.inject.Inject class BuildSetup @Inject constructor( - private val context: Application, - private val preferences: Preferences + private val context: Application, + private val preferences: Preferences, + private val fileLogger: FileLogger, ) { fun setup() { - Timber.plant(DebugTree()) - Timber.plant(FileLogger(context)) + Timber.plant(Timber.DebugTree()) + Timber.plant(fileLogger) SoLoader.init(context, false) if (preferences.getBoolean(R.string.p_leakcanary, false)) { AppWatcher.manualInstall(context) diff --git a/app/src/main/java/org/tasks/Tasks.kt b/app/src/main/java/org/tasks/Tasks.kt index 06923049b..80c0178fd 100644 --- a/app/src/main/java/org/tasks/Tasks.kt +++ b/app/src/main/java/org/tasks/Tasks.kt @@ -1,5 +1,6 @@ package org.tasks +import android.app.ActivityManager import android.app.Application import android.content.BroadcastReceiver import android.content.Context @@ -13,6 +14,7 @@ import androidx.lifecycle.ProcessLifecycleOwner import androidx.lifecycle.coroutineScope import androidx.work.Configuration import com.mikepenz.iconics.Iconics +import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.astrid.service.Upgrader import dagger.Lazy import dagger.hilt.android.HiltAndroidApp @@ -85,6 +87,11 @@ class Tasks : Application(), Configuration.Provider { val lastVersion = preferences.lastSetVersion val currentVersion = BuildConfig.VERSION_CODE Timber.i("Astrid Startup. %s => %s", lastVersion, currentVersion) + if (AndroidUtilities.atLeastR()) { + val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val exitReasons = activityManager.getHistoricalProcessExitReasons(null, 0, 5) + Timber.i(exitReasons.joinToString("\n")) + } // invoke upgrade service if (lastVersion != currentVersion) { diff --git a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt index 9bcc995bd..4c854cb41 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt +++ b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt @@ -161,7 +161,6 @@ class CaldavSynchronizer @Inject constructor( caldavDao.update(account) } val urls = resources.map { it.href.toString() }.toHashSet() - Timber.d("Found calendars: %s", urls) for (calendar in caldavDao.findDeletedCalendars(account.uuid!!, ArrayList(urls))) { taskDeleter.delete(calendar) } @@ -239,7 +238,7 @@ class CaldavSynchronizer @Inject constructor( val httpUrl = resource.href val remoteCtag = resource.ctag if (caldavCalendar.ctag?.equals(remoteCtag) == true) { - Timber.d("%s up to date", caldavCalendar.name) + Timber.d("up to date: $caldavCalendar") return } Timber.d("updating $caldavCalendar") diff --git a/app/src/main/java/org/tasks/logging/FileLogger.kt b/app/src/main/java/org/tasks/logging/FileLogger.kt index a7bbacda8..e77b556a7 100644 --- a/app/src/main/java/org/tasks/logging/FileLogger.kt +++ b/app/src/main/java/org/tasks/logging/FileLogger.kt @@ -1,29 +1,46 @@ package org.tasks.logging -import android.content.Context +import android.annotation.SuppressLint +import android.app.Application import android.os.Process import android.util.Log import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.tasks.logging.LogFormatter.Companion.LINE_SEPARATOR import timber.log.Timber import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException import java.util.logging.FileHandler import java.util.logging.Logger +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import javax.inject.Inject +import javax.inject.Singleton -class FileLogger( - context: Context, +@Singleton +class FileLogger @Inject constructor( + private val context: Application, ) : Timber.DebugTree() { - val logDirectory = File(context.cacheDir, "logs").apply { mkdirs() } + private val logDirectory = File(context.cacheDir, "logs").apply { mkdirs() } private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val fileHandler: FileHandler = FileHandler( "${logDirectory.absolutePath}/log.%g.txt", 20 * 1024 * 1024, 10 ) - private val logger = Logger.getLogger(context.packageName) + @SuppressLint("LogNotTimber") + private val logger = Logger.getLogger(context.packageName).apply { + try { + useParentHandlers = false + } catch (e: SecurityException) { + Log.e("FileLogger", "Failed to disable parent handlers", e) + } + } init { fileHandler.formatter = LogFormatter() @@ -41,8 +58,43 @@ class FileLogger( } } - fun flush() { - fileHandler.flush() + suspend fun getZipFile(): File = withContext(Dispatchers.IO) { + val zipFile = File(context.cacheDir, "logs.zip") + val buffer = ByteArray(1024) + FileOutputStream(zipFile).use { fos -> + ZipOutputStream(fos).use { zos -> + try { + Runtime + .getRuntime() + .exec(arrayOf("logcat", "-d", "-v", "threadtime", "*:*")) + ?.inputStream + ?.use { logcat -> + zos.putNextEntry(ZipEntry("logcat.txt")) + var len: Int + while (logcat.read(buffer).also { len = it } > 0) { + zos.write(buffer, 0, len) + } + zos.closeEntry() + } + } catch (e: IOException) { + Timber.e(e, "Failed to save logcat") + } + fileHandler.flush() + logDirectory + .listFiles { _, name -> name?.endsWith(".txt") == true } + ?.forEach { file -> + FileInputStream(file).use { fis -> + zos.putNextEntry(ZipEntry(file.name)) + var len: Int + while (fis.read(buffer).also { len = it } > 0) { + zos.write(buffer, 0, len) + } + zos.closeEntry() + } + } + } + } + zipFile } companion object { diff --git a/app/src/main/java/org/tasks/preferences/fragments/HelpAndFeedback.kt b/app/src/main/java/org/tasks/preferences/fragments/HelpAndFeedback.kt index a67a9a6db..cfd2a7e06 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/HelpAndFeedback.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/HelpAndFeedback.kt @@ -3,14 +3,19 @@ package org.tasks.preferences.fragments import android.content.Intent import android.net.Uri import android.os.Bundle +import androidx.core.content.FileProvider import androidx.fragment.app.FragmentManager +import androidx.lifecycle.lifecycleScope +import com.todoroo.astrid.utility.Constants import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import org.tasks.BuildConfig import org.tasks.R import org.tasks.Tasks.Companion.IS_GENERIC import org.tasks.analytics.Firebase import org.tasks.dialogs.WhatsNewDialog import org.tasks.injection.InjectingPreferenceFragment +import org.tasks.logging.FileLogger import javax.inject.Inject private const val FRAG_TAG_WHATS_NEW = "frag_tag_whats_new" @@ -19,6 +24,7 @@ private const val FRAG_TAG_WHATS_NEW = "frag_tag_whats_new" class HelpAndFeedback : InjectingPreferenceFragment() { @Inject lateinit var firebase: Firebase + @Inject lateinit var fileLogger: FileLogger override fun getPreferenceXml() = R.xml.help_and_feedback @@ -47,6 +53,26 @@ class HelpAndFeedback : InjectingPreferenceFragment() { false } + findPreference(R.string.send_application_logs) + .setOnPreferenceClickListener { + lifecycleScope.launch { + val file = FileProvider.getUriForFile( + requireContext(), + Constants.FILE_PROVIDER_AUTHORITY, + fileLogger.getZipFile() + ) + val intent = Intent(Intent.ACTION_SEND) + .setType("message/rfc822") + .putExtra(Intent.EXTRA_EMAIL, arrayOf(getString(R.string.support_email))) + .putExtra(Intent.EXTRA_SUBJECT, "Tasks logs") + .putExtra(Intent.EXTRA_TEXT, device.debugInfo) + .putExtra(Intent.EXTRA_STREAM, file) + .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + startActivity(intent) + } + false + } + findPreference(R.string.p_collect_statistics) .setOnPreferenceClickListener { showRestartDialog() diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f25648272..2f67ad185 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -320,6 +320,7 @@ File %1$s contained %2$s.\n\n Source code Contribute translations Contact developer + Send application logs Rate Tasks No reminders during quiet hours Donate diff --git a/app/src/main/res/xml/help_and_feedback.xml b/app/src/main/res/xml/help_and_feedback.xml index 2f97efc03..02db4361f 100644 --- a/app/src/main/res/xml/help_and_feedback.xml +++ b/app/src/main/res/xml/help_and_feedback.xml @@ -30,6 +30,11 @@ android:title="@string/contact_developer" app:icon="@drawable/ic_outline_email_24px" /> + +