Improve logging
This commit is contained in:
27
mobile/src/main/java/be/mygod/librootkotlinx/Logger.kt
Normal file
27
mobile/src/main/java/be/mygod/librootkotlinx/Logger.kt
Normal file
@@ -0,0 +1,27 @@
|
||||
package be.mygod.librootkotlinx
|
||||
|
||||
import android.util.Log
|
||||
|
||||
interface Logger {
|
||||
companion object {
|
||||
/**
|
||||
* Override this variable to change default behavior,
|
||||
* which is to print to [android.util.Log] under tag "RootServer" except for [d].
|
||||
*/
|
||||
@JvmStatic
|
||||
var me = object : Logger { }
|
||||
|
||||
private const val TAG = "RootServer"
|
||||
}
|
||||
|
||||
fun d(m: String?, t: Throwable? = null) { }
|
||||
fun e(m: String?, t: Throwable? = null) {
|
||||
Log.e(TAG, m, t)
|
||||
}
|
||||
fun i(m: String?, t: Throwable? = null) {
|
||||
Log.i(TAG, m, t)
|
||||
}
|
||||
fun w(m: String?, t: Throwable? = null) {
|
||||
Log.w(TAG, m, t)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import android.os.Parcelable
|
||||
import android.os.RemoteException
|
||||
import android.system.Os
|
||||
import android.system.OsConstants
|
||||
import android.util.Log
|
||||
import androidx.collection.LongSparseArray
|
||||
import androidx.collection.set
|
||||
import androidx.collection.valueIterator
|
||||
@@ -23,7 +22,7 @@ import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> Unit = { Log.w(TAG, it) }) {
|
||||
class RootServer {
|
||||
private sealed class Callback(private val server: RootServer, private val index: Long,
|
||||
protected val classLoader: ClassLoader?) {
|
||||
var active = true
|
||||
@@ -107,12 +106,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
private val callbackLookup = LongSparseArray<Callback>()
|
||||
private val mutex = Mutex()
|
||||
|
||||
/**
|
||||
* If we encountered unexpected output from stderr during initialization, its content will be stored here.
|
||||
*
|
||||
* It is advised to read this after initializing the instance.
|
||||
*/
|
||||
fun readUnexpectedStderr(): String? {
|
||||
private fun readUnexpectedStderr(): String? {
|
||||
if (!this::process.isInitialized) return null
|
||||
var available = process.errorStream.available()
|
||||
return if (available <= 0) null else String(ByteArrayOutputStream().apply {
|
||||
@@ -131,10 +125,10 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
val line = readLine() ?: throw EOFException()
|
||||
if (line.endsWith(token)) {
|
||||
val extraLength = line.length - token.length
|
||||
if (extraLength > 0) warnLogger(line.substring(0, extraLength))
|
||||
if (extraLength > 0) Logger.me.w(line.substring(0, extraLength))
|
||||
break
|
||||
}
|
||||
warnLogger(line)
|
||||
Logger.me.w(line)
|
||||
}
|
||||
}
|
||||
private fun doInit(context: Context, niceName: String) {
|
||||
@@ -146,7 +140,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
writer.flush()
|
||||
val reader = process.inputStream.bufferedReader()
|
||||
reader.lookForToken(token1)
|
||||
if (isDebugEnabled) Log.d(TAG, "Root shell initialized")
|
||||
Logger.me.d("Root shell initialized")
|
||||
reader to writer
|
||||
} catch (e: Exception) {
|
||||
throw NoShellException(e)
|
||||
@@ -166,7 +160,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
output = writer
|
||||
require(!active)
|
||||
active = true
|
||||
if (isDebugEnabled) Log.d(TAG, "Root server initialized")
|
||||
Logger.me.d("Root server initialized")
|
||||
}
|
||||
|
||||
private fun callbackSpin() {
|
||||
@@ -187,7 +181,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
}
|
||||
} else null
|
||||
} ?: break
|
||||
if (isDebugEnabled) Log.d(TAG, "Received callback #$index: $result")
|
||||
Logger.me.d("Received callback #$index: $result")
|
||||
callback(input, result)
|
||||
}
|
||||
}
|
||||
@@ -198,7 +192,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
* @param context Any [Context] from the app.
|
||||
* @param niceName Name to call the rooted Java process.
|
||||
*/
|
||||
suspend fun init(context: Context, niceName: String = "${context.packageName}:root") {
|
||||
suspend fun init(context: Context, niceName: String = "${context.packageName}:root") = try {
|
||||
val future = CompletableDeferred<Unit>()
|
||||
callbackListenerExit = GlobalScope.async(Dispatchers.IO) {
|
||||
try {
|
||||
@@ -211,7 +205,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
val errorReader = async(Dispatchers.IO) {
|
||||
try {
|
||||
process.errorStream.bufferedReader().useLines { seq ->
|
||||
for (line in seq) warnLogger(line)
|
||||
for (line in seq) Logger.me.w(line)
|
||||
}
|
||||
} catch (_: IOException) { }
|
||||
}
|
||||
@@ -222,21 +216,15 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
process.destroy()
|
||||
throw e
|
||||
} finally {
|
||||
if (isDebugEnabled) Log.d(TAG, "Waiting for exit")
|
||||
Logger.me.d("Waiting for exit")
|
||||
errorReader.await()
|
||||
process.waitFor()
|
||||
withContext(NonCancellable) { closeInternal(true) }
|
||||
}
|
||||
}
|
||||
future.await()
|
||||
}
|
||||
/**
|
||||
* Convenience function that initializes and also logs warnings to [Log].
|
||||
*/
|
||||
suspend fun initAndroidLog(context: Context, niceName: String = "${context.packageName}:root") = try {
|
||||
init(context, niceName)
|
||||
} finally {
|
||||
readUnexpectedStderr()?.let { Log.e(TAG, it) }
|
||||
readUnexpectedStderr()?.let { Logger.me.e(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +233,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
private fun sendLocked(command: Parcelable) {
|
||||
output.writeParcelable(command)
|
||||
output.flush()
|
||||
if (isDebugEnabled) Log.d(TAG, "Sent #$counter: $command")
|
||||
Logger.me.d("Sent #$counter: $command")
|
||||
counter++
|
||||
}
|
||||
|
||||
@@ -307,15 +295,15 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
private suspend fun closeInternal(fromWorker: Boolean = false) = mutex.withLock {
|
||||
if (active) {
|
||||
active = false
|
||||
if (isDebugEnabled) Log.d(TAG, if (fromWorker) "Shutting down from worker" else "Shutting down from client")
|
||||
Logger.me.d(if (fromWorker) "Shutting down from worker" else "Shutting down from client")
|
||||
try {
|
||||
sendLocked(Shutdown())
|
||||
output.close()
|
||||
process.outputStream.close()
|
||||
} catch (e: IOException) {
|
||||
Log.i(TAG, "send Shutdown failed", e)
|
||||
Logger.me.i("send Shutdown failed", e)
|
||||
}
|
||||
if (isDebugEnabled) Log.d(TAG, "Client closed")
|
||||
Logger.me.d("Client closed")
|
||||
}
|
||||
if (fromWorker) {
|
||||
for (callback in callbackLookup.valueIterator()) callback.cancel()
|
||||
@@ -331,13 +319,6 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* If set to true, debug information will be printed to logcat.
|
||||
*/
|
||||
@JvmStatic
|
||||
var isDebugEnabled = false
|
||||
|
||||
private const val TAG = "RootServer"
|
||||
private const val SUCCESS = 0
|
||||
private const val EX_GENERIC = 1
|
||||
private const val EX_PARCELABLE = 2
|
||||
@@ -366,7 +347,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
|
||||
Log.e(TAG, "Uncaught exception from $thread", throwable)
|
||||
Logger.me.e("Uncaught exception from $thread", throwable)
|
||||
exitProcess(1)
|
||||
}
|
||||
rootMain(args)
|
||||
@@ -430,7 +411,7 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
// thread safety: usage of input should be in main thread
|
||||
val input = DataInputStream(System.`in`.buffered())
|
||||
var counter = 0L
|
||||
if (isDebugEnabled) Log.d(TAG, "Server entering main loop")
|
||||
Logger.me.d("Server entering main loop")
|
||||
loop@ while (true) {
|
||||
val command = try {
|
||||
input.readParcelable<Parcelable>(RootServer::class.java.classLoader)
|
||||
@@ -438,14 +419,14 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
break
|
||||
}
|
||||
val callback = counter
|
||||
if (isDebugEnabled) Log.d(TAG, "Received #$callback: $command")
|
||||
Logger.me.d("Received #$callback: $command")
|
||||
when (command) {
|
||||
is CancelCommand -> cancellables[command.index]?.invoke()
|
||||
is RootCommandOneWay -> defaultWorker.launch {
|
||||
try {
|
||||
command.execute()
|
||||
} catch (e: Throwable) {
|
||||
Log.e(command.javaClass.simpleName, "Unexpected exception in RootCommandOneWay", e)
|
||||
Logger.me.e("Unexpected exception in RootCommandOneWay ($command.javaClass.simpleName)", e)
|
||||
}
|
||||
}
|
||||
is RootCommand<*> -> {
|
||||
@@ -490,10 +471,10 @@ class RootServer @JvmOverloads constructor(private val warnLogger: (String) -> U
|
||||
counter++
|
||||
}
|
||||
job.cancel()
|
||||
if (isDebugEnabled) Log.d(TAG, "Clean up initiated before exit. Jobs: ${job.children.joinToString()}")
|
||||
Logger.me.d("Clean up initiated before exit. Jobs: ${job.children.joinToString()}")
|
||||
if (runBlocking { withTimeoutOrNull(5000) { job.join() } } == null) {
|
||||
Log.w(TAG, "Clean up timeout: ${job.children.joinToString()}")
|
||||
} else if (isDebugEnabled) Log.d(TAG, "Clean up finished, exiting")
|
||||
Logger.me.w("Clean up timeout: ${job.children.joinToString()}")
|
||||
} else Logger.me.d("Clean up finished, exiting")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import java.util.concurrent.TimeUnit
|
||||
* This object manages creation of [RootServer] and times them out automagically, with default timeout of 5 minutes.
|
||||
*/
|
||||
abstract class RootSession {
|
||||
protected open fun createServer() = RootServer()
|
||||
protected abstract suspend fun initServer(server: RootServer)
|
||||
/**
|
||||
* Timeout to close [RootServer] in milliseconds.
|
||||
@@ -30,7 +29,7 @@ abstract class RootSession {
|
||||
usersCount = 0
|
||||
}
|
||||
check(usersCount == 0L) { "Unexpected $server, $usersCount" }
|
||||
val server = createServer()
|
||||
val server = RootServer()
|
||||
try {
|
||||
initServer(server)
|
||||
this.server = server
|
||||
|
||||
@@ -2,24 +2,22 @@ package be.mygod.vpnhotspot.root
|
||||
|
||||
import android.os.Parcelable
|
||||
import android.util.Log
|
||||
import be.mygod.librootkotlinx.RootCommandNoResult
|
||||
import be.mygod.librootkotlinx.RootServer
|
||||
import be.mygod.librootkotlinx.RootSession
|
||||
import be.mygod.librootkotlinx.systemContext
|
||||
import be.mygod.librootkotlinx.*
|
||||
import be.mygod.vpnhotspot.App.Companion.app
|
||||
import be.mygod.vpnhotspot.BuildConfig
|
||||
import be.mygod.vpnhotspot.util.Services
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import timber.log.Timber
|
||||
|
||||
object RootManager : RootSession() {
|
||||
object RootManager : RootSession(), Logger {
|
||||
@Parcelize
|
||||
class RootInit : RootCommandNoResult {
|
||||
override suspend fun execute(): Parcelable? {
|
||||
RootServer.isDebugEnabled = BuildConfig.DEBUG
|
||||
Timber.plant(object : Timber.DebugTree() {
|
||||
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
|
||||
if (priority >= Log.WARN) System.err.println("$priority/$tag: $message")
|
||||
if (priority >= Log.WARN) {
|
||||
System.err.println("$priority/$tag: $message")
|
||||
t?.printStackTrace()
|
||||
}
|
||||
if (t == null) {
|
||||
Log.println(priority, tag, message)
|
||||
} else {
|
||||
@@ -29,19 +27,20 @@ object RootManager : RootSession() {
|
||||
}
|
||||
}
|
||||
})
|
||||
Logger.me = RootManager
|
||||
Services.init { systemContext }
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun createServer() = RootServer { Timber.w(it) }
|
||||
override fun d(m: String?, t: Throwable?) = Timber.d(t, m)
|
||||
override fun e(m: String?, t: Throwable?) = Timber.e(t, m)
|
||||
override fun i(m: String?, t: Throwable?) = Timber.i(t, m)
|
||||
override fun w(m: String?, t: Throwable?) = Timber.w(t, m)
|
||||
|
||||
override suspend fun initServer(server: RootServer) {
|
||||
RootServer.isDebugEnabled = BuildConfig.DEBUG
|
||||
try {
|
||||
server.init(app.deviceStorage)
|
||||
} finally {
|
||||
server.readUnexpectedStderr()?.let { Timber.e(it) }
|
||||
}
|
||||
Logger.me = this
|
||||
server.init(app.deviceStorage)
|
||||
server.execute(RootInit())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user