Added debug log viewing function

This commit is contained in:
2022-10-05 04:36:55 +08:00
parent a44d29e102
commit 92f6837c30
15 changed files with 515 additions and 2 deletions

View File

@@ -63,6 +63,11 @@
android:exported="false"
android:screenOrientation="behind" />
<activity
android:name=".ui.activity.debug.LoggerActivity"
android:exported="false"
android:screenOrientation="behind" />
<activity
android:name=".ui.activity.errors.AppErrorsRecordActivity"
android:exported="true"

View File

@@ -33,7 +33,10 @@ import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
class HookEntry : IYukiHookXposedInit {
override fun onInit() = configs {
debugLog { tag = "AppErrorsTracking" }
debugLog {
tag = "AppErrorsTracking"
isRecord = true
}
isDebug = false
isEnableModulePrefsCache = false
}

View File

@@ -0,0 +1,207 @@
/*
* AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer.
* Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com)
* https://github.com/KitsunePie/AppErrorsTracking
*
* This software is non-free but opensource software: you can redistribute it
* and/or modify it under the terms of the GNU Affero General Public License
* as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This software 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* and eula along with this software. If not, see
* <https://www.gnu.org/licenses/>
*
* This file is Created by fankes on 2022/10/4.
*/
@file:Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
package com.fankes.apperrorstracking.ui.activity.debug
import android.app.Activity
import android.content.Intent
import android.view.ContextMenu
import android.view.MenuItem
import android.view.View
import android.widget.AdapterView
import androidx.core.view.isVisible
import com.fankes.apperrorstracking.R
import com.fankes.apperrorstracking.databinding.ActivitiyLoggerBinding
import com.fankes.apperrorstracking.databinding.AdapterLoggerBinding
import com.fankes.apperrorstracking.databinding.DiaLoggerFilterBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.utils.factory.*
import com.fankes.apperrorstracking.utils.tool.FrameworkTool
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.dataChannel
import com.highcapable.yukihookapi.hook.log.YukiHookLogger
import com.highcapable.yukihookapi.hook.log.YukiLoggerData
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.text.SimpleDateFormat
import java.util.*
class LoggerActivity : BaseActivity<ActivitiyLoggerBinding>() {
companion object {
/** 请求保存文件回调标识 */
private const val WRITE_REQUEST_CODE = 0
}
/** 回调适配器改变 */
private var onChanged: (() -> Unit)? = null
/** 过滤条件 */
private var filters = arrayListOf("D", "I", "W", "E")
/** 全部的调试日志数据 */
private val listData = ArrayList<YukiLoggerData>()
override fun onCreate() {
binding.titleBackIcon.setOnClickListener { finish() }
binding.refreshIcon.setOnClickListener { refreshData() }
binding.filterIcon.setOnClickListener {
showDialog<DiaLoggerFilterBinding> {
title = LocaleString.filterByCondition
binding.configCheck0.isChecked = filters.any { it == "D" }
binding.configCheck1.isChecked = filters.any { it == "I" }
binding.configCheck2.isChecked = filters.any { it == "W" }
binding.configCheck3.isChecked = filters.any { it == "E" }
confirmButton {
filters.clear()
if (binding.configCheck0.isChecked) filters.add("D")
if (binding.configCheck1.isChecked) filters.add("I")
if (binding.configCheck2.isChecked) filters.add("W")
if (binding.configCheck3.isChecked) filters.add("E")
refreshData()
}
cancelButton()
}
}
binding.exportAllIcon.setOnClickListener {
runCatching {
startActivityForResult(Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "*/application"
putExtra(Intent.EXTRA_TITLE, "app_errors_tracking_${System.currentTimeMillis().toUtcTime()}.log")
}, WRITE_REQUEST_CODE)
}.onFailure { toast(msg = "Start Android SAF failed") }
}
/** 设置列表元素和 Adapter */
binding.listView.apply {
bindAdapter {
onBindDatas { listData }
onBindViews<AdapterLoggerBinding> { binding, position ->
listData[position].also { bean ->
binding.priorityText.apply {
text = bean.priority
setBackgroundResource(
when (bean.priority) {
"D" -> R.drawable.bg_logger_d_round
"I" -> R.drawable.bg_logger_i_round
"W" -> R.drawable.bg_logger_w_round
"E" -> R.drawable.bg_logger_e_round
else -> R.drawable.bg_logger_d_round
}
)
}
binding.messageText.text = bean.msg.format()
binding.timeText.text = bean.timestamp.format()
binding.throwableText.isVisible = bean.throwable != null
binding.throwableText.text = bean.throwable?.toStackTrace() ?: ""
}
}
}.apply { onChanged = { notifyDataSetChanged() } }
registerForContextMenu(this)
}
}
/** 更新列表数据 */
private fun refreshData() {
dataChannel(FrameworkTool.SYSTEM_FRAMEWORK_NAME).obtainLoggerInMemoryData {
listData.clear()
it.takeIf { e -> e.isNotEmpty() }?.reversed()?.filter { filters.any { e -> it.priority == e } }?.forEach { e -> listData.add(e) }
onChanged?.invoke()
binding.listView.post { binding.listView.setSelection(0) }
binding.exportAllIcon.isVisible = listData.isNotEmpty()
binding.listView.isVisible = listData.isNotEmpty()
binding.listNoDataView.isVisible = listData.isEmpty()
binding.listNoDataView.text = if (filters.size < 4) LocaleString.noListResult else LocaleString.noListData
}
}
/**
* 获取当前所有日志写出文件的格式
*
* 复制自 [YukiHookLogger.contents]
* @return [String]
*/
private val loggerContents: String
get() {
var content = ""
listData.takeIf { it.isNotEmpty() }?.forEach {
val head = "${it.time} ------ "
content += "$head$it\n"
it.throwable?.also { e ->
content += "${head}Dump stack trace for \"${e.current().name}\":\n"
content += e.toStackTrace()
}
}
return content
}
/**
* 格式化为本地时间格式
* @return [String]
*/
private fun Long.format() = SimpleDateFormat.getDateTimeInstance().format(Date(this))
/**
* 格式化消息字符串样式
* @return [String]
*/
private fun String.format() = replace(oldValue = "--", newValue = "\n--")
/**
* 获取完整的异常堆栈内容
* @return [String]
*/
private fun Throwable.toStackTrace() = ByteArrayOutputStream().also { printStackTrace(PrintStream(it)) }.toString().trim()
override fun onCreateContextMenu(menu: ContextMenu?, v: View?, menuInfo: ContextMenu.ContextMenuInfo?) {
menuInflater.inflate(R.menu.menu_logger_action, menu)
super.onCreateContextMenu(menu, v, menuInfo)
}
override fun onContextItemSelected(item: MenuItem): Boolean {
if (item.menuInfo is AdapterView.AdapterContextMenuInfo)
(item.menuInfo as? AdapterView.AdapterContextMenuInfo?)?.also {
if (item.itemId == R.id.logger_copy)
copyToClipboard(listData[it.position].let { e -> e.toString() + (e.throwable?.toStackTrace()?.let { t -> "\n$t" } ?: "") })
}
return super.onContextItemSelected(item)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == WRITE_REQUEST_CODE && resultCode == Activity.RESULT_OK) runCatching {
data?.data?.let {
contentResolver?.openOutputStream(it)?.apply { write(loggerContents.toByteArray()) }?.close()
toast(LocaleString.exportAllLogsSuccess)
} ?: toast(LocaleString.exportAllLogsFail)
}.onFailure { toast(LocaleString.exportAllLogsFail) }
}
override fun onResume() {
super.onResume()
/** 执行更新 */
refreshData()
}
}

View File

@@ -32,6 +32,7 @@ import com.fankes.apperrorstracking.data.ConfigData.bind
import com.fankes.apperrorstracking.databinding.ActivityMainBinding
import com.fankes.apperrorstracking.locale.LocaleString
import com.fankes.apperrorstracking.ui.activity.base.BaseActivity
import com.fankes.apperrorstracking.ui.activity.debug.LoggerActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsMutedActivity
import com.fankes.apperrorstracking.ui.activity.errors.AppErrorsRecordActivity
import com.fankes.apperrorstracking.utils.factory.*
@@ -78,6 +79,8 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
/** 功能管理按钮点击事件 */
binding.viewErrorsRecordButton.setOnClickListener { whenActivated { navigate<AppErrorsRecordActivity>() } }
binding.viewMutedErrorsAppsButton.setOnClickListener { whenActivated { navigate<AppErrorsMutedActivity>() } }
/** 调试日志按钮点击事件 */
binding.titleLoggerIcon.setOnClickListener { navigate<LoggerActivity>() }
/** 重启按钮点击事件 */
binding.titleRestartIcon.setOnClickListener { FrameworkTool.restartSystem(context = this) }
/** 项目地址按钮点击事件 */

View File

@@ -43,7 +43,7 @@ import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
object FrameworkTool {
/** 系统框架包名 */
private const val SYSTEM_FRAMEWORK_NAME = "android"
const val SYSTEM_FRAMEWORK_NAME = "android"
private const val CALL_APP_ERRORS_DATA_GET = "call_app_errors_data_get"
private const val CALL_MUTED_ERRORS_APP_DATA_GET = "call_muted_app_errors_data_get"

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#565656" />
<corners
android:bottomLeftRadius="10dp"
android:topLeftRadius="10dp" />
</shape>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#E53935" />
<corners
android:bottomLeftRadius="10dp"
android:topLeftRadius="10dp" />
</shape>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#3974CB" />
<corners
android:bottomLeftRadius="10dp"
android:topLeftRadius="10dp" />
</shape>

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#FB8C00" />
<corners
android:bottomLeftRadius="10dp"
android:topLeftRadius="10dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="150dp"
android:height="150dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M712.9,295.1c-120.7,-110.7 -308.3,-102.6 -419,18.1 -87.4,95.3 -103,236.3 -38.5,348.4 82,141.7 263.2,190.3 405,108.4 66.8,-38.6 116,-101.7 137,-176 8.9,-31.5 41.7,-49.8 73.2,-40.9 31.5,8.9 49.8,41.7 40.9,73.2C849.1,846.8 619.8,975 399.2,912.6 178.7,850.2 50.5,620.9 112.9,400.3S404.6,51.6 625.2,114c64.2,18.2 123.1,51.5 171.6,97.3l79.7,-79.7c11.6,-11.6 30.3,-11.6 41.9,-0.1 5.6,5.6 8.7,13.1 8.7,21V407c0,16.4 -13.3,29.6 -29.6,29.6H642.9c-16.4,0 -29.6,-13.3 -29.6,-29.7 0,-7.8 3.1,-15.3 8.6,-20.9l91,-90.9z"
android:fillColor="#ffffff"/>
</vector>

View File

@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorThemeBackground"
android:orientation="vertical"
tools:context=".ui.activity.debug.LoggerActivity"
tools:ignore="ContentDescription,UseCompoundDrawables">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:elevation="0dp"
android:gravity="center|start"
android:paddingLeft="15dp"
android:paddingTop="13dp"
android:paddingRight="15dp"
android:paddingBottom="5dp">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/title_back_icon"
style="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="20dp"
android:src="@drawable/ic_back"
android:tint="@color/colorTextGray"
android:tooltipText="@string/back" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="2.5dp"
android:layout_weight="1"
android:gravity="center|start"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:singleLine="true"
android:text="@string/debug_log"
android:textColor="@color/colorTextGray"
android:textSize="19sp"
android:textStyle="bold" />
<TextView
android:id="@+id/title_count_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/this_contents_clear_when_restarts_tip"
android:textColor="@color/colorTextDark"
android:textSize="11.5sp"
android:tooltipText="@string/this_contents_clear_when_restarts_tip" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:gravity="center|end"
android:orientation="horizontal">
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/refresh_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="15dp"
android:src="@drawable/ic_refresh"
android:tint="@color/colorTextGray"
android:tooltipText="@string/refresh" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/filter_icon"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_marginEnd="12dp"
android:src="@drawable/ic_filter"
android:tint="@color/colorTextGray"
android:tooltipText="@string/filter_by_condition" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/export_all_icon"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="10dp"
android:src="@drawable/ic_export"
android:tint="@color/colorTextGray"
android:tooltipText="@string/export_all" />
</LinearLayout>
</LinearLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp">
<TextView
android:id="@+id/list_no_data_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:lineSpacingExtra="6dp"
android:text="@string/no_list_data"
android:textColor="@color/colorTextDark"
android:textSize="17sp" />
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="@color/trans"
android:dividerHeight="5dp"
android:fadingEdgeLength="10dp"
android:listSelector="@color/trans"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingBottom="5dp"
android:requiresFadingEdge="vertical"
android:scrollbars="none"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>

View File

@@ -29,6 +29,18 @@
android:textSize="25sp"
android:textStyle="bold" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/title_logger_icon"
style="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="25dp"
android:layout_height="25dp"
android:layout_marginEnd="15dp"
android:alpha="0.85"
android:padding="0.5dp"
android:src="@drawable/ic_debug"
android:tint="@color/colorTextGray"
android:tooltipText="@string/debug_log" />
<androidx.constraintlayout.utils.widget.ImageFilterView
android:id="@+id/title_restart_icon"
style="?android:attr/selectableItemBackgroundBorderless"

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_permotion_round"
android:orientation="horizontal">
<TextView
android:id="@+id/priority_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:padding="5dp"
android:textColor="@color/white"
android:textSize="13sp"
android:typeface="monospace" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center|start"
android:orientation="vertical"
android:padding="10dp">
<TextView
android:id="@+id/message_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lineSpacingExtra="3dp"
android:textColor="@color/colorTextGray"
android:textSize="13sp" />
<TextView
android:id="@+id/time_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:ellipsize="end"
android:singleLine="true"
android:textColor="@color/colorTextDark"
android:textSize="11sp" />
<TextView
android:id="@+id/throwable_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:lineSpacingExtra="3dp"
android:textColor="#EF5350"
android:textSize="11sp"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="15dp"
android:paddingTop="15dp"
android:paddingRight="15dp"
tools:ignore="HardcodedText">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="8.5dp"
android:layout_marginRight="8.5dp"
android:layout_marginBottom="10dp"
android:lineSpacingExtra="6dp"
android:text="@string/when_logger_how_to_show_tip"
android:textSize="13sp" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/config_check_0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="DEBUG"
app:buttonTint="@color/colorPrimaryAccent" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/config_check_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="INFO"
app:buttonTint="@color/colorPrimaryAccent" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/config_check_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="WARN"
app:buttonTint="@color/colorPrimaryAccent" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/config_check_3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:checked="true"
android:text="ERROR"
app:buttonTint="@color/colorPrimaryAccent" />
</LinearLayout>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/logger_copy"
android:title="@string/copy" />
</menu>