diff --git a/app/src/androidTest/java/com/mogo/functions/test/CrashTest.kt b/app/src/androidTest/java/com/mogo/functions/test/CrashTest.kt new file mode 100644 index 0000000000..e01690b088 --- /dev/null +++ b/app/src/androidTest/java/com/mogo/functions/test/CrashTest.kt @@ -0,0 +1,94 @@ +package com.mogo.functions.test + +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.test.core.app.ActivityScenario +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.LargeTest +import androidx.test.platform.app.InstrumentationRegistry +import com.mogo.eagle.core.function.call.devatools.CallerDevaToolsManager +import com.mogo.eagle.core.function.main.MainLauncherActivity +import com.mogo.eagle.core.utilcode.kotlin.onClick +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.util.concurrent.TimeUnit + +@RunWith(AndroidJUnit4::class) +@LargeTest +class CrashTest { + + lateinit var launch: ActivityScenario + + + @Before + fun before() { + launch = ActivityScenario.launch(MainLauncherActivity::class.java) + } + + @Test + fun testJavaCrash() = runBlocking { + val arguments = InstrumentationRegistry.getArguments() + val delay = arguments.getString("delay", "0") + val newThread = arguments.getString("new_thread", "false") + val delayLong = delay.toLong() + if (delayLong <= 0) { + throw AssertionError("illegal state ..") + } + delay(delayLong) + launch.onActivity { + CallerDevaToolsManager.logcat()?.testJavaCrash(newThread.toBoolean()) + } + delay(TimeUnit.MINUTES.toMillis(10)) + } + + @Test + fun testANRCrash() = runBlocking { + val arguments = InstrumentationRegistry.getArguments() + val delay = arguments.getString("delay", "0") + val delayLong = delay.toLong() + if (delayLong <= 0) { + throw AssertionError("illegal state ..") + } + delay(delayLong) + launch.onActivity { itx -> + + val content = (itx.findViewById(android.R.id.content) as ViewGroup) + content.postDelayed({ + content.addView(Button(itx).also { + it.background = ColorDrawable(Color.RED) + it.setPadding(20, 20, 20, 20) + it.textSize = 40.0f + it.text = "点我触发ANR" + it.setTextColor(Color.WHITE) + it.onClick { + CallerDevaToolsManager.logcat()?.testAnrCrash() + } + }) + }, 1000) + + } + delay(TimeUnit.MINUTES.toMillis(10)) + } + + @Test + fun testNativeCrash() = runBlocking { + val arguments = InstrumentationRegistry.getArguments() + val delay = arguments.getString("delay", "0") + val newThread = arguments.getString("new_thread", "false") + val delayLong = delay.toLong() + if (delayLong <= 0) { + throw AssertionError("illegal state ..") + } + delay(delayLong) + launch.onActivity { + CallerDevaToolsManager.logcat()?.testNativeCrash(newThread.toBoolean()) + } + delay(TimeUnit.MINUTES.toMillis(10)) + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/MoGoLogRecordProviderImpl.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/MoGoLogRecordProviderImpl.kt index 509435a7ba..2b52095da7 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/MoGoLogRecordProviderImpl.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/MoGoLogRecordProviderImpl.kt @@ -2,24 +2,39 @@ package com.zhjt.mogo_core_function_devatools.logcat import android.content.* import android.os.Process -import android.util.* +import android.os.SystemClock +import android.util.Log +import com.mogo.aicloud.services.socket.IMogoOnMessageListener +import com.mogo.aicloud.services.socket.MogoAiCloudSocketManager +import com.mogo.commons.AbsMogoApplication import com.mogo.core.log.record.* import com.mogo.core.log.record.config.* import com.mogo.core.log.record.config.crash.* import com.mogo.eagle.core.function.api.devatools.logcat.* -import com.mogo.eagle.core.utilcode.util.* +import com.zhidao.loglib.bean.RemoteLogPushContent +import com.zhjt.mogo_core_function_devatools.logcat.config.LogRecordConfig import com.zhjt.mogo_core_function_devatools.logcat.uploader.* import kotlinx.coroutines.* import java.io.* import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.atomic.AtomicBoolean +import java.util.concurrent.atomic.AtomicLong -internal class MoGoLogRecordProviderImpl: IMoGoLogRecordProvider { +internal class MoGoLogRecordProviderImpl: IMoGoLogRecordProvider, + IMogoOnMessageListener { + + companion object { + private const val TAG = "MoGoLogRecordProviderImpl" + } private val flag by lazy { AtomicBoolean(false) } private val scope by lazy { CoroutineScope(Dispatchers.IO + SupervisorJob()) } + private val uploadThreshold by lazy { MINUTES.toMillis(1) } //全量日志的上传的安全时间,在此时间内,上传任务只会触发一次 + + private val lastUploadTime by lazy { AtomicLong(0) } + override fun init(context: Context) { val zipDir = File(context.getExternalFilesDir(null), "logcat/zip") LogcatManager.init(LogcatConfig.Builder().context(context) @@ -33,27 +48,60 @@ internal class MoGoLogRecordProviderImpl: IMoGoLogRecordProvider { .crashDir(File(context.getExternalFilesDir(null), "logcat/crash")) .javaCrash(true) .anr(true) + .nativeCrash(true) .build()) .uploader(LogRecordUploader())) scope.launch { try { - if (zipDir.exists()) { - zipDir.listFiles()?.forEach { - it.delete() - } - } + zipDir.takeIf { it.exists() }?.deleteRecursively() } catch (t: Throwable) { t.printStackTrace() } } } + override fun target(): Class { + return RemoteLogPushContent::class.java + } + + override fun onMsgReceived(obj: RemoteLogPushContent?) { + Log.d(TAG, "--- onMsgReceived --: $obj") + if (obj == null) return + val endTime = System.currentTimeMillis() + val startTime = when(obj.type) { + LogRecordConfig.ALL_LOG_15_MINUTES -> endTime - MINUTES.toMillis(15) + LogRecordConfig.ALL_LOG_30_MINUTES -> endTime - MINUTES.toMillis(30) + LogRecordConfig.ALL_LOG_45_MINUTES -> endTime - MINUTES.toMillis(45) + LogRecordConfig.ALL_LOG_60_MINUTES -> endTime - MINUTES.toMillis(60) + LogRecordConfig.ALL_LOG_120_MINUTES -> endTime - MINUTES.toMillis(120) + LogRecordConfig.ALL_LOG_SAME_DAY -> endTime - (endTime % 86400000) + LogRecordConfig.ALL_LOG -> 0 + else -> endTime + } + Log.d(TAG, "--- onMsgReceived -- 1: $startTime, $endTime") + if (startTime < endTime) { + val now = System.currentTimeMillis() + val last = lastUploadTime.get() + if (last <= 0) { + lastUploadTime.set(System.currentTimeMillis()) + } + if (last > 0 && (now - last) <= uploadThreshold) { + return + } + lastUploadTime.set(System.currentTimeMillis()) + scope.launch { + LogcatManager.upload(startTime, endTime) + } + } + } override fun start() { if (flag.get()) { return } flag.set(true) + MogoAiCloudSocketManager.getInstance(AbsMogoApplication.getApp().applicationContext) + .registerOnMessageListener(LogRecordConfig.LOG_RECORD_TYPE, this) LogcatManager.start() } @@ -61,18 +109,25 @@ internal class MoGoLogRecordProviderImpl: IMoGoLogRecordProvider { if (!flag.get()) { return } + MogoAiCloudSocketManager.getInstance(AbsMogoApplication.getApp().applicationContext) + .unregisterOnMessageListener(LogRecordConfig.LOG_RECORD_TYPE, this) flag.set(false) LogcatManager.stop() } - override fun upload(startTime: Long, endTime: Long) { - scope.launch { - val result = LogcatManager.upload(startTime, endTime) - Log.d(TAG, "上传日志:[startTime:$startTime, endTime: $endTime], 结果: $result") - } - } - override fun export(): File? { return LogcatManager.export() } + + override fun testJavaCrash(runOnNewThread: Boolean) { + LogcatManager.testJavaCrash(runOnNewThread) + } + + override fun testNativeCrash(runOnNewThread: Boolean) { + LogcatManager.testNativeCrash(runOnNewThread) + } + + override fun testAnrCrash() { + SystemClock.sleep(15000) + } } \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/config/LogRecordConfig.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/config/LogRecordConfig.kt new file mode 100644 index 0000000000..2226b654b2 --- /dev/null +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/config/LogRecordConfig.kt @@ -0,0 +1,48 @@ +package com.zhjt.mogo_core_function_devatools.logcat.config + +internal class LogRecordConfig { + + companion object{ + + /** + * 长链下行数据的消息类型 + */ + const val LOG_RECORD_TYPE = 500002 + + + /** + * 全量日志抓取: 上传从收到抓取指令往前15分钟的日志 + */ + const val ALL_LOG_15_MINUTES = 1 + + /** + * 全量日志抓取: 上传从收到抓取指令往前30分钟的日志 + */ + const val ALL_LOG_30_MINUTES = 2 + + /** + * 全量日志抓取: 上传从收到抓取指令往前45分钟的日志 + */ + const val ALL_LOG_45_MINUTES = 3 + + /** + * 全量日志抓取: 上传从收到抓取指令往前60分钟的日志 + */ + const val ALL_LOG_60_MINUTES = 4 + + /** + * 全量日志抓取: 上传从收到抓取指令往前120分钟的日志 + */ + const val ALL_LOG_120_MINUTES = 5 + + /** + * 全量日志抓取: 上传从收到抓取指令往前当天的日志 + */ + const val ALL_LOG_SAME_DAY = 6 + + /** + * 全量日志抓取: 上传从收到抓取指令之前所有的日志 + */ + const val ALL_LOG = 7 + } +} \ No newline at end of file diff --git a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/uploader/LogRecordUploader.kt b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/uploader/LogRecordUploader.kt index 4f03953bf8..03f1145b63 100644 --- a/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/uploader/LogRecordUploader.kt +++ b/core/function-impl/mogo-core-function-devatools/src/main/java/com/zhjt/mogo_core_function_devatools/logcat/uploader/LogRecordUploader.kt @@ -1,11 +1,65 @@ package com.zhjt.mogo_core_function_devatools.logcat.uploader +import android.util.Log import com.mogo.core.log.record.config.uploader.* import com.mogo.core.log.record.model.* +import com.mogo.eagle.core.utilcode.util.ThreadUtils +import com.zhidao.cosupload.callback.CosStatusCallback +import com.zhidao.cosupload.callback.CosStatusCallbackManager +import com.zhidao.cosupload.model.CallbackData +import com.zhidao.loglib.upload.UploadManager +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.flow.channelFlow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.flowOn -internal class LogRecordUploader: ILogcatUploader { +internal class LogRecordUploader : ILogcatUploader { - override suspend fun upload(startTime: Long, endTime: Long, generatedZipPath: String): UploadState { - throw AssertionError("Not Implementation") + companion object { + private const val TAG = "LogRecordUploader" } + + @OptIn(ExperimentalCoroutinesApi::class) + override suspend fun upload(startTime: Long, endTime: Long, generatedZipPath: String): UploadState = + channelFlow { + Log.d(TAG, "---- 开始上传 --- 1 ---") + CosStatusCallbackManager.getInstance().register(object : CosStatusCallback { + + override fun onStartUpload(data: CallbackData?) { + if (data?.localPath == generatedZipPath) { + Log.d(TAG, "---- onStartUpload ---: $data") + } + } + + override fun uploadCosCompleted(data: CallbackData?) { + if (data?.localPath == generatedZipPath) { + Log.d(TAG, "---- uploadCosCompleted ---: $data") + trySend(UploadSuccess) + CosStatusCallbackManager.getInstance().unregister(this) + } + } + + override fun uploadCosFailed(data: CallbackData?) { + if (data?.localPath == generatedZipPath) { + Log.d(TAG, "---- uploadCosFailed ---: $data") + trySend(UploadError(Reason(data.exception))) + CosStatusCallbackManager.getInstance().unregister(this) + } + } + + override fun onProgress(data: CallbackData?) { + if (data?.localPath == generatedZipPath) { + Log.d(TAG, "---- onProgress ---: $data") + } + } + }) + try { + UploadManager.getInstance().uploadSingleFile(generatedZipPath) + } catch (t: Throwable) { + Log.d(TAG, "---- 上传失败 ---: ${t.message}") + trySend(UploadError(Reason(t.message ?: ""))) + } + }.flowOn(ThreadUtils.getIoPool().asCoroutineDispatcher()).firstOrNull() ?: UploadError(Reason("UnKnown reason")) + } \ No newline at end of file diff --git a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/logcat/IMoGoLogRecordProvider.kt b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/logcat/IMoGoLogRecordProvider.kt index 9fdd835c64..7d3a906462 100644 --- a/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/logcat/IMoGoLogRecordProvider.kt +++ b/core/mogo-core-function-api/src/main/java/com/mogo/eagle/core/function/api/devatools/logcat/IMoGoLogRecordProvider.kt @@ -11,7 +11,11 @@ interface IMoGoLogRecordProvider { fun stop() - fun upload(startTime: Long, endTime: Long) - fun export(): File? + + fun testJavaCrash(runOnNewThread: Boolean) + + fun testNativeCrash(runOnNewThread: Boolean) + + fun testAnrCrash() } \ No newline at end of file