feat: add option to dump AndroidIDE logs to file

This commit is contained in:
Akash Yadav
2023-06-26 20:01:32 +05:30
parent 9e9f2787de
commit 7b52e70231
7 changed files with 184 additions and 8 deletions
@@ -28,6 +28,7 @@ import com.itsaky.androidide.databinding.ActivityPreferencesBinding;
import com.itsaky.androidide.fragments.IDEPreferencesFragment;
import com.itsaky.androidide.preferences.AboutPreferences;
import com.itsaky.androidide.preferences.ConfigurationPreferences;
import com.itsaky.androidide.preferences.DeveloperOptionsPreferences;
import com.itsaky.androidide.preferences.IDEPreferences;
import com.itsaky.androidide.preferences.PrivacyPreferences;
import java.util.ArrayList;
@@ -59,6 +60,7 @@ public class PreferencesActivity extends LimitlessIDEActivity {
prefs.getChildren().clear();
prefs.addPreference(new ConfigurationPreferences());
prefs.addPreference(new PrivacyPreferences());
prefs.addPreference(new DeveloperOptionsPreferences());
prefs.addPreference(new AboutPreferences());
final var args = new Bundle();
@@ -215,12 +215,6 @@ abstract class BaseEditorActivity :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy()).detectLeakedClosableObjects().build()
)
}
registerLanguageServers()
if (savedInstanceState != null && savedInstanceState.containsKey(KEY_PROJECT_PATH)) {
@@ -0,0 +1,62 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.activities.editor
import android.os.Process
import com.itsaky.androidide.utils.Environment
import com.itsaky.androidide.utils.ILogger
import java.io.File
import java.text.SimpleDateFormat
import java.util.Date
/**
* Reads the logs from AndroidIDE and saves it to a file in the projects directory.
*
* @author Akash Yadav
*/
class IDELogcatReader : Thread("IDELogcatReader") {
private val log = ILogger.newInstance("IDELogcatReader")
override fun run() {
val date = Date()
val dateFormat = SimpleDateFormat("yyyy-MM-dd_HH:mm:ss.SSS")
val outputFile = File(Environment.ANDROIDIDE_HOME,
"logs/AndroidIDE-LOG-${dateFormat.format(date)}.txt")
log.debug("Creating output file: $outputFile")
outputFile.parentFile!!.mkdirs()
outputFile.createNewFile()
outputFile.outputStream().buffered().use {
try {
val process = ProcessBuilder("logcat", "--pid=${Process.myPid()}", "-v",
"threadtime").let { builder ->
builder.redirectErrorStream(true)
builder.start()
}
process.inputStream.bufferedReader().forEachLine { line ->
it.write("$line\n".toByteArray())
it.flush()
}
log.info("Process ended with exit code :", process.waitFor())
} catch (err: Throwable) {
log.error("Failed to read logs", err)
}
}
}
}
@@ -20,6 +20,7 @@ package com.itsaky.androidide.app
import android.content.Intent
import android.net.Uri
import android.os.StrictMode
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.Observer
import androidx.work.Constraints
@@ -32,6 +33,7 @@ import com.blankj.utilcode.util.ThrowableUtils.getFullStackTrace
import com.google.android.material.color.DynamicColors
import com.itsaky.androidide.BuildConfig
import com.itsaky.androidide.activities.CrashHandlerActivity
import com.itsaky.androidide.activities.editor.IDELogcatReader
import com.itsaky.androidide.buildinfo.BuildInfo
import com.itsaky.androidide.editor.schemes.IDEColorSchemeProvider
import com.itsaky.androidide.eventbus.events.preferences.PreferenceChangeEvent
@@ -40,6 +42,8 @@ import com.itsaky.androidide.events.EditorEventsIndex
import com.itsaky.androidide.events.LspApiEventsIndex
import com.itsaky.androidide.events.LspJavaEventsIndex
import com.itsaky.androidide.events.ProjectsApiEventsIndex
import com.itsaky.androidide.preferences.KEY_DEVOPTS_DEBUGGING_DUMPLOGS
import com.itsaky.androidide.preferences.dumpLogs
import com.itsaky.androidide.preferences.internal.STAT_OPT_IN
import com.itsaky.androidide.preferences.internal.enableMaterialYou
import com.itsaky.androidide.preferences.internal.statOptIn
@@ -63,6 +67,7 @@ import kotlin.system.exitProcess
class IDEApplication : BaseApplication() {
private var uncaughtExceptionHandler: UncaughtExceptionHandler? = null
private var ideLogcatReader: IDELogcatReader? = null
private val log = ILogger.newInstance("IDEApplication")
init {
@@ -78,6 +83,16 @@ class IDEApplication : BaseApplication() {
Thread.setDefaultUncaughtExceptionHandler { thread, th -> handleCrash(thread, th) }
super.onCreate()
if (BuildConfig.DEBUG) {
StrictMode.setVmPolicy(
StrictMode.VmPolicy.Builder(StrictMode.getVmPolicy()).penaltyLog().detectAll().build()
)
if (dumpLogs) {
startLogcatReader()
}
}
EventBus.builder()
.addIndex(AppEventsIndex())
.addIndex(EditorEventsIndex())
@@ -164,15 +179,37 @@ class IDEApplication : BaseApplication() {
})
}
private fun startLogcatReader() {
if (ideLogcatReader != null) {
// already started
return
}
log.info("Starting logcat reader...")
ideLogcatReader = IDELogcatReader().also { it.start() }
}
private fun stopLogcatReader() {
log.info("Stopping logcat reader...")
ideLogcatReader?.interrupt()
ideLogcatReader = null
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onPrefChanged(event: PreferenceChangeEvent) {
val enabled = event.value as? Boolean? ?: return
if (event.key == STAT_OPT_IN) {
val enabled = event.value as Boolean
if (enabled) {
reportStatsIfNecessary()
} else {
cancelStatUploadWorker()
}
} else if (event.key == KEY_DEVOPTS_DEBUGGING_DUMPLOGS) {
if (enabled) {
startLogcatReader()
} else {
stopLogcatReader()
}
}
}
@@ -0,0 +1,61 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.preferences
import com.itsaky.androidide.R
import com.itsaky.androidide.preferences.internal.prefManager
import kotlinx.parcelize.Parcelize
internal const val KEY_DEVOPTS = "ide.prefs.developerOptions"
internal const val KEY_DEVOPTS_DEBUGGING = "${KEY_DEVOPTS}.debugging"
internal const val KEY_DEVOPTS_DEBUGGING_DUMPLOGS = "${KEY_DEVOPTS_DEBUGGING}.dumpLogs"
@Parcelize
internal class DeveloperOptionsScreen(override val key: String = KEY_DEVOPTS,
override val title: Int = R.string.title_developer_options,
override val summary: Int? = R.string.idepref_devOptions_summary,
override val children: List<IPreference> = mutableListOf()) : IPreferenceScreen() {
init {
addPreference(DebuggingPreferences())
}
}
@Parcelize
internal class DebuggingPreferences(
override val key: String = KEY_DEVOPTS_DEBUGGING,
override val title: Int = R.string.idepref_group_debugging,
override val children: List<IPreference> = mutableListOf()) : IPreferenceGroup() {
init {
addPreference(DumpLogsPreference())
}
}
internal var dumpLogs: Boolean
get() = prefManager.getBoolean(KEY_DEVOPTS_DEBUGGING_DUMPLOGS, false)
set(value) {
prefManager.putBoolean(KEY_DEVOPTS_DEBUGGING_DUMPLOGS, value)
}
@Parcelize
internal class DumpLogsPreference(
override val key: String = KEY_DEVOPTS_DEBUGGING_DUMPLOGS,
override val title: Int = R.string.idepref_devOptions_dumpLogs_title,
override val summary: Int? = R.string.idepref_devOptions_dumpLogs_summary) :
SwitchPreference(setValue = ::dumpLogs::set, getValue = ::dumpLogs::get)
@@ -19,9 +19,9 @@ package com.itsaky.androidide.preferences
import android.content.Intent
import com.itsaky.androidide.activities.AboutActivity
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.app.BaseApplication
import com.itsaky.androidide.app.IDEApplication
import com.itsaky.androidide.resources.R.string
import kotlinx.parcelize.Parcelize
private const val KEY_GITHUB = "idepref_gh"
@@ -82,6 +82,7 @@ class ConfigurationPreferences(
override val title: Int = string.configure,
override val children: List<IPreference> = mutableListOf()
) : IPreferenceGroup() {
init {
addPreference(GeneralPreferences())
addPreference(EditorPreferences())
@@ -95,17 +96,31 @@ class PrivacyPreferences(
override val title: Int = string.title_privacy,
override val children: List<IPreference> = mutableListOf()
) : IPreferenceGroup() {
init {
addPreference(StatPreferences())
}
}
@Parcelize
class DeveloperOptionsPreferences(
override val key: String = "idepref_devOpts",
override val title: Int = string.title_developer_options,
override val children: List<IPreference> = mutableListOf()
) : IPreferenceGroup() {
init {
addPreference(DeveloperOptionsScreen())
}
}
@Parcelize
class AboutPreferences(
override val key: String = "idepref_category_about",
override val title: Int = string.about,
override val children: List<IPreference> = mutableListOf()
) : IPreferenceGroup() {
init {
addPreference(github)
addPreference(channel)
@@ -581,4 +581,9 @@
<string name="title_android_veresion">Android veresion</string>
<string name="btn_opt_in">Opt-in</string>
<string name="btn_no_thanks">No, thanks</string>
<string name="title_developer_options">Developer options</string>
<string name="idepref_devOptions_summary">Experimental/debugging options for AndroidIDE</string>
<string name="idepref_group_debugging">Debugging</string>
<string name="idepref_devOptions_dumpLogs_title">Dump logs</string>
<string name="idepref_devOptions_dumpLogs_summary">Dump AndroidIDE logs to $HOME/.androidide/logs</string>
</resources>