change crash reporter

fix crash on log polling
fix crash on udp write back
fix goroutine leak
fix default rule inject
This commit is contained in:
Kr328 committed 2020-02-26 13:32:29 +08:00
1 parent b39b3812d0
commit 977519b383
12 files changed
+162 -174

No files matched your search

@@ -31,9 +31,11 @@ import com.microsoft.appcenter.crashes.ingestion.models.ErrorAttachmentLog
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.selects.select
import java.io.DataInputStream
import java.io.FileWriter
import java.io.IOException
import java.util.*
import kotlin.concurrent.thread
import kotlin.math.max
class LogcatService : Service(), CoroutineScope by MainScope(), IInterface {
@@ -41,6 +43,7 @@ class LogcatService : Service(), CoroutineScope by MainScope(), IInterface {
private const val NOTIFICATION_CHANNEL_ID = "clash_logcat_channel"
private const val NOTIFICATION_ID = 256
private const val MAX_CACHE_COUNT = 200
private const val LOG_LISTENER_KEY = "logcat_service"
private const val LOG_CONTENT_FORMAT = "%d %s %s"
@@ -57,39 +60,23 @@ class LogcatService : Service(), CoroutineScope by MainScope(), IInterface {
private val entity = LogFile.generate()
private val connection = object : ServiceConnection {
private var manager: IClashManager? = null
override fun onServiceDisconnected(name: ComponentName?) {
val dump = LogcatDumper.dump()
logChannel.offer(LogEvent(LogEvent.Level.ERROR, "Clash Service Crashed"))
dump.forEach {
logChannel.offer(LogEvent(LogEvent.Level.ERROR, it))
}
val textLog = ErrorAttachmentLog
.attachmentWithText(dump.joinToString(separator = "\n"), "logcat.txt")
Crashes.trackError(RemoteException("Clash Service Crashed"),
null, listOf(textLog))
manager?.unregisterLogListener(LOG_LISTENER_KEY)
}
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val manager = IClashManager.Stub.asInterface(service) ?: return stopSelf()
manager.openLogEvent(object : IStreamCallback.Stub() {
override fun complete() {
stopSelf()
}
override fun completeExceptionally(reason: String?) {
stopSelf()
}
manager = IClashManager.Stub.asInterface(service) ?: return stopSelf()
manager?.registerLogListener(LOG_LISTENER_KEY, object: IStreamCallback.Stub() {
override fun complete() {}
override fun completeExceptionally(reason: String?) {}
override fun send(data: ParcelableContainer?) {
val logEvent = (data?.data as LogEvent?) ?: return
data ?: return
data.data ?: return
if (!logChannel.offer(logEvent))
Log.w("Drop log $logEvent")
logChannel.offer(data.data as LogEvent)
}
})
}
@@ -115,6 +102,8 @@ class LogcatService : Service(), CoroutineScope by MainScope(), IInterface {
cancel()
connection.onServiceDisconnected(null)
stopForeground(true)
super.onDestroy()
@@ -2,7 +2,9 @@ package com.github.kr328.clash
import android.app.Application
import android.content.Context
import android.os.DeadObjectException
import com.github.kr328.clash.core.Global
import com.github.kr328.clash.dump.LogcatDumper
import com.github.kr328.clash.remote.Broadcasts
import com.github.kr328.clash.remote.Remote
import com.microsoft.appcenter.AppCenter
@@ -32,6 +34,21 @@ class MainApplication : Application() {
BuildConfig.APP_CENTER_KEY,
Analytics::class.java, Crashes::class.java
)
Crashes.setListener(object: AbstractCrashesListener() {
override fun getErrorAttachments(report: ErrorReport?): MutableIterable<ErrorAttachmentLog> {
report ?: return mutableListOf()
if ( !report.stackTrace.contains("DeadObjectException") )
return mutableListOf()
val logcat = LogcatDumper.dump().joinToString(separator = "\n")
return mutableListOf(
ErrorAttachmentLog.attachmentWithText(logcat, "logcat.txt")
)
}
})
}
Remote.init(this)
@@ -12,50 +12,38 @@ import kotlinx.coroutines.withContext
class ClashClient(val service: IClashManager) {
suspend fun setSelectProxy(name: String, proxy: String): Boolean = withContext(Dispatchers.IO) {
Utils.callRemote {
service.setSelectProxy(name, proxy)
}
service.setSelectProxy(name, proxy)
}
suspend fun startHealthCheck(group: String) = withContext(Dispatchers.IO) {
Utils.callRemote {
CompletableDeferred<Unit>().apply {
service.startHealthCheck(group, object : IStreamCallback.Stub() {
override fun complete() {
this@apply.complete(Unit)
}
CompletableDeferred<Unit>().apply {
service.startHealthCheck(group, object : IStreamCallback.Stub() {
override fun complete() {
this@apply.complete(Unit)
}
override fun completeExceptionally(reason: String?) {
this@apply.completeExceptionally(RemoteException(reason))
}
override fun completeExceptionally(reason: String?) {
this@apply.completeExceptionally(RemoteException(reason))
}
override fun send(data: ParcelableContainer?) {}
})
}
override fun send(data: ParcelableContainer?) {}
})
}
}.await()
suspend fun queryAllProxyGroups(): List<ProxyGroup> = withContext(Dispatchers.IO) {
Utils.callRemote {
service.queryAllProxies().list
}
service.queryAllProxies().list
}
suspend fun queryGeneral(): General = withContext(Dispatchers.IO) {
Utils.callRemote {
service.queryGeneral()
}
service.queryGeneral()
}
suspend fun queryBandwidth(): Long = withContext(Dispatchers.IO) {
Utils.callRemote {
service.queryBandwidth()
}
}
service.queryBandwidth()
}
suspend fun setProxyMode(mode: General.Mode) = withContext(Dispatchers.IO) {
Utils.callRemote {
service.setProxyMode(mode.toString())
}
service.setProxyMode(mode.toString())
}
}
@@ -8,38 +8,26 @@ import kotlinx.coroutines.withContext
class ProfileClient(private val service: IProfileService) {
suspend fun queryProfiles(): Array<ClashProfileEntity> = withContext(Dispatchers.IO) {
Utils.callRemote {
service.queryProfiles()
}
service.queryProfiles()
}
suspend fun queryActiveProfile(): ClashProfileEntity? = withContext(Dispatchers.IO) {
Utils.callRemote {
service.queryActiveProfile()
}
service.queryActiveProfile()
}
suspend fun enqueueRequest(request: ProfileRequest) = withContext(Dispatchers.IO) {
Utils.callRemote {
service.enqueueRequest(request)
}
service.enqueueRequest(request)
}
suspend fun setActiveProfile(id: Long) = withContext(Dispatchers.IO) {
Utils.callRemote {
service.setActiveProfile(id)
}
service.setActiveProfile(id)
}
suspend fun requestProfileEditUri(id: Long): String? = withContext(Dispatchers.IO) {
Utils.callRemote {
service.requestProfileEditUri(id)
}
service.requestProfileEditUri(id)
}
suspend fun commitProfileEditUri(uri: String) = withContext(Dispatchers.IO) {
Utils.callRemote {
service.commitProfileEditUri(uri)
}
service.commitProfileEditUri(uri)
}
}
@@ -1,24 +0,0 @@
package com.github.kr328.clash.remote
import android.os.DeadObjectException
import com.github.kr328.clash.dump.LogcatDumper
import com.microsoft.appcenter.crashes.Crashes
import com.microsoft.appcenter.crashes.ingestion.models.ErrorAttachmentLog
internal object Utils {
fun <T>callRemote(block: () -> T): T {
return try {
block()
}
catch (e: DeadObjectException) {
val log = LogcatDumper.dump().joinToString(separator = "\n")
val attachmentLog = ErrorAttachmentLog
.attachmentWithText(log, "logcat.txt")
Crashes.trackError(e, null, listOf(attachmentLog))
throw e
}
}
}
+36
View File
@@ -1,12 +1,24 @@
package bridge
import (
"sync"
"github.com/Dreamacro/clash/component/mmdb"
C "github.com/Dreamacro/clash/constant"
"github.com/Dreamacro/clash/log"
"github.com/Dreamacro/clash/tunnel"
"github.com/kr328/cfa/profile"
)
var (
logCallback LogCallback
logSubscribe sync.Once
)
type LogCallback interface {
OnLogEvent(level, payload string)
}
func LoadMMDB(data []byte) {
dataClone := make([]byte, len(data))
copy(dataClone, data)
@@ -26,3 +38,27 @@ func Reset() {
func SetApplicationVersion(version string) {
profile.ApplicationVersion = version
}
func SetLogCallback(callback LogCallback) {
logSubscribe.Do(func() {
go func() {
sub := log.Subscribe()
defer log.UnSubscribe(sub)
for {
elm := <-sub
l := elm.(*log.Event)
if l.LogLevel < log.Level() {
continue
}
if cb := logCallback; cb != nil {
cb.OnLogEvent(l.LogLevel.String(), l.Payload)
}
}
}()
})
logCallback = callback
}
+7 -34
View File
@@ -1,16 +1,21 @@
package bridge
import (
"github.com/Dreamacro/clash/log"
"sync"
"github.com/Dreamacro/clash/tunnel"
)
type EventPoll struct {
stop sync.Once
onStop func()
}
func (e *EventPoll) Stop() {
e.onStop()
e.stop.Do(func() {
e.onStop()
})
}
type Traffic struct {
@@ -40,35 +45,3 @@ func QueryTraffic() *Traffic {
Download: down,
}
}
func PollLogs(logs Logs) *EventPoll {
stopChannel := make(chan int, 1)
sub := log.Subscribe()
go func() {
defer log.UnSubscribe(sub)
defer close(stopChannel)
defer log.Infoln("Logs Poll Stopped")
for {
select {
case <-stopChannel:
return
case elm := <-sub:
l := elm.(*log.Event)
if l.LogLevel < log.Level() {
break
}
logs.OnEvent(l.Type(), l.Payload)
}
}
}()
return &EventPoll{
onStop: func() {
stopChannel <- 0
},
}
}
+5 -1
View File
@@ -83,10 +83,14 @@ func ReadAndCheck(fd int, output, baseDir string) error {
}
func SaveAndCheck(data []byte, output, baseDir string) error {
_, err := parseConfig(data, baseDir)
cfg, err := parseConfig(data, baseDir)
if err != nil {
return err
}
for _, v := range cfg.Providers {
v.Destroy()
}
return ioutil.WriteFile(output, data, defaultFileMode)
}
+1 -1
View File
@@ -107,7 +107,7 @@ func parseConfig(data []byte, baseDir string) (*config.Config, error) {
raw.Experimental.Interface = ""
raw.ExternalUI = ""
raw.ExternalController = ""
raw.Rule = append([]string{fmt.Sprintf("IP-CIDR,%s,REJECT", tunAddress)}, raw.Rule...)
raw.Rule = append([]string{fmt.Sprintf("IP-CIDR,%s,REJECT,no-resolve", tunAddress)}, raw.Rule...)
patchRawConfig(raw)
@@ -1,6 +1,7 @@
package com.github.kr328.clash.core
import android.content.Context
import android.os.Messenger
import bridge.Bridge
import bridge.TunCallback
import com.github.kr328.clash.core.event.EventStream
@@ -18,6 +19,8 @@ import java.io.File
import java.io.InputStream
object Clash {
private val logReceivers = mutableMapOf<String, (LogEvent) -> Unit>()
private var initialized = false
@Synchronized
@@ -33,8 +36,6 @@ object Clash {
Bridge.setHome(context.cacheDir.absolutePath)
Bridge.setApplicationVersion(BuildConfig.VERSION_NAME)
Bridge.reset()
Log.d("MMDB loaded ${bytes.size}")
}
fun start() {
@@ -52,10 +53,11 @@ object Clash {
onNewSocket: (Int) -> Boolean,
onTunStop: () -> Unit
) {
Bridge.startTunDevice(fd.toLong(), mtu.toLong(), dns, object: TunCallback {
Bridge.startTunDevice(fd.toLong(), mtu.toLong(), dns, object : TunCallback {
override fun onCreateSocket(fd: Long) {
onNewSocket(fd.toInt())
}
override fun onStop() {
onTunStop()
}
@@ -143,14 +145,27 @@ object Clash {
return Traffic(data.upload, data.download)
}
fun openLogEvent(): EventStream<LogEvent> {
return object : EventStream<LogEvent>() {
val log = Bridge.pollLogs { level, payload ->
send(LogEvent(LogEvent.Level.fromString(level), payload))
}
fun registerLogReceiver(key: String, receiver: (LogEvent) -> Unit) {
synchronized(logReceivers) {
logReceivers[key] = receiver
override fun onClose() {
log.stop()
Bridge.setLogCallback(this::onLogEvent)
}
}
fun unregisterLogReceiver(key: String) {
synchronized(logReceivers) {
logReceivers.remove(key)
if ( logReceivers.isEmpty() )
Bridge.setLogCallback(null)
}
}
private fun onLogEvent(level: String, payload: String) {
synchronized(logReceivers) {
logReceivers.forEach {
it.value(LogEvent(LogEvent.Level.fromString(level), payload))
}
}
}
@@ -16,9 +16,6 @@ interface IClashManager {
long queryBandwidth();
// Events
void openLogEvent(IStreamCallback callback);
// Settings
boolean putSetting(String key, String value);
String getSetting(String key);
void registerLogListener(String key, IStreamCallback callback);
void unregisterLogListener(String key);
}
@@ -1,16 +1,26 @@
package com.github.kr328.clash.service
import android.content.Context
import android.os.ParcelFileDescriptor
import androidx.core.content.edit
import com.github.kr328.clash.core.Clash
import com.github.kr328.clash.core.event.LogEvent
import com.github.kr328.clash.core.model.General
import com.github.kr328.clash.core.model.ProxyGroupList
import com.github.kr328.clash.core.utils.Log
import com.github.kr328.clash.service.data.ClashDatabase
import com.github.kr328.clash.service.data.ClashProfileProxyEntity
import com.github.kr328.clash.service.ipc.IStreamCallback
import com.github.kr328.clash.service.ipc.ParcelableContainer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.toUtf8Bytes
import java.io.DataOutputStream
import java.lang.Exception
import java.nio.channels.FileChannel
import kotlin.concurrent.thread
class ClashManager(context: Context, parent: CoroutineScope) :
IClashManager.Stub(), CoroutineScope by parent {
@@ -42,37 +52,12 @@ class ClashManager(context: Context, parent: CoroutineScope) :
return Clash.setSelectedProxy(proxy, selected)
}
override fun putSetting(key: String?, value: String?): Boolean {
settings.edit(commit = false) {
putString(key, value)
}
return true
}
override fun queryBandwidth(): Long {
val data = Clash.queryBandwidth()
return data.download + data.upload
}
override fun openLogEvent(callback: IStreamCallback?) {
require(callback != null)
Clash.openLogEvent().apply {
callback.asBinder()?.linkToDeath({
close()
}, 0)
onEvent {
try {
callback.send(ParcelableContainer(it))
} catch (e: Exception) {
close()
}
}
}
}
override fun startHealthCheck(group: String?, callback: IStreamCallback?) {
require(group != null && callback != null)
@@ -84,7 +69,27 @@ class ClashManager(context: Context, parent: CoroutineScope) :
}
}
override fun getSetting(key: String?): String? {
return settings.getString(key, null)
override fun registerLogListener(key: String?, callback: IStreamCallback?) {
requireNotNull(key)
requireNotNull(callback)
callback.asBinder().linkToDeath({
Clash.unregisterLogReceiver(key)
}, 0)
Clash.registerLogReceiver(key) {
try {
callback.send(ParcelableContainer(it))
}
catch (e: Exception) {
Clash.unregisterLogReceiver(key)
}
}
}
override fun unregisterLogListener(key: String?) {
requireNotNull(key)
Clash.unregisterLogReceiver(key)
}
}