forked from hush/SilentDragonAndroid
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
200 lines
6.4 KiB
200 lines
6.4 KiB
@file:Suppress("NOTHING_TO_INLINE")
|
|
|
|
package cash.z.ecc.android.util
|
|
|
|
import java.util.concurrent.CopyOnWriteArraySet
|
|
import kotlin.math.roundToLong
|
|
|
|
internal typealias Leaf = String
|
|
|
|
/**
|
|
* A tiny log.
|
|
*/
|
|
interface Twig {
|
|
|
|
/**
|
|
* Log the message. Simple.
|
|
*/
|
|
fun twig(logMessage: String = "", priority: Int = 0)
|
|
|
|
/**
|
|
* Bundles twigs together.
|
|
*/
|
|
operator fun plus(twig: Twig): Twig {
|
|
// if the other twig is a composite twig, let it handle the addition
|
|
return if (twig is CompositeTwig) twig.plus(this) else CompositeTwig(mutableListOf(this, twig))
|
|
}
|
|
companion object {
|
|
|
|
/**
|
|
* Access the trunk corresponding to this twig.
|
|
*/
|
|
val trunk get() = Bush.trunk
|
|
|
|
/**
|
|
* Convenience function to just turn this thing on. Twigs are silent by default so this is
|
|
* most useful to enable developer logging at the right time.
|
|
*/
|
|
fun enabled(isEnabled: Boolean) {
|
|
if (isEnabled) plant(TroubleshootingTwig()) else plant(SilentTwig())
|
|
}
|
|
|
|
/**
|
|
* Plants the twig, making it the one and only bush. Twigs can be bundled together to create
|
|
* the appearance of multiple bushes (i.e `Twig.plant(twigA + twigB + twigC)`) even though
|
|
* there's only ever one bush.
|
|
*/
|
|
fun plant(rootTwig: Twig) {
|
|
Bush.trunk = rootTwig
|
|
}
|
|
|
|
/**
|
|
* Generate a leaf on the bush. Leaves show up in every log message as tags until they are
|
|
* clipped.
|
|
*/
|
|
fun sprout(leaf: Leaf) = Bush.leaves.add(leaf)
|
|
|
|
/**
|
|
* Clip a leaf from the bush. Clipped leaves no longer appear in logs.
|
|
*/
|
|
fun clip(leaf: Leaf) = Bush.leaves.remove(leaf)
|
|
|
|
/**
|
|
* Clip all leaves from the bush.
|
|
*/
|
|
fun prune() = Bush.leaves.clear()
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A collection of tiny logs (twigs) consisting of one trunk and maybe some leaves. There can only
|
|
* ever be one trunk. Trunks are created by planting a twig. Whenever a leaf sprouts, it will appear
|
|
* as a tag on every log message until clipped.
|
|
*
|
|
* @see [Twig.plant]
|
|
* @see [Twig.sprout]
|
|
* @see [Twig.clip]
|
|
*/
|
|
object Bush {
|
|
var trunk: Twig = SilentTwig()
|
|
val leaves: MutableSet<Leaf> = CopyOnWriteArraySet<Leaf>()
|
|
}
|
|
|
|
/**
|
|
* Makes a tiny log.
|
|
*/
|
|
inline fun twig(message: String, priority: Int = 0) = Bush.trunk.twig(message, priority)
|
|
|
|
/**
|
|
* Makes an exception.
|
|
*/
|
|
inline fun twig(t: Throwable) = t.stackTraceToString().lines().forEach {
|
|
twig(it)
|
|
}
|
|
|
|
/**
|
|
* Times a tiny log.
|
|
*/
|
|
inline fun <R> twig(logMessage: String, priority: Int = 0, block: () -> R): R = Bush.trunk.twig(logMessage, priority, block)
|
|
|
|
/**
|
|
* Meticulously times a tiny task.
|
|
*/
|
|
inline fun <R> twigTask(logMessage: String, priority: Int = 0, block: () -> R): R = Bush.trunk.twigTask(logMessage, priority, block)
|
|
|
|
/**
|
|
* A tiny log that does nothing. No one hears this twig fall in the woods.
|
|
*/
|
|
class SilentTwig : Twig {
|
|
|
|
/**
|
|
* Shh.
|
|
*/
|
|
override fun twig(logMessage: String, priority: Int) {
|
|
// shh
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A tiny log for detecting troubles. Aim at your troubles and pull the twigger.
|
|
*
|
|
* @param formatter a formatter for the twigs. The default one is pretty spiffy.
|
|
* @param printer a printer for the twigs. The default is System.err.println.
|
|
*/
|
|
open class TroubleshootingTwig(
|
|
val formatter: (String) -> String = spiffy(6),
|
|
val printer: (String) -> Any = System.err::println,
|
|
val minPriority: Int = 0
|
|
) : Twig {
|
|
|
|
/**
|
|
* Actually print and format the log message, unlike the SilentTwig, which does nothing.
|
|
*/
|
|
override fun twig(logMessage: String, priority: Int) {
|
|
if (priority >= minPriority) printer(formatter(logMessage))
|
|
}
|
|
|
|
companion object {
|
|
|
|
/**
|
|
* A tiny log formatter that makes twigs pretty spiffy.
|
|
*
|
|
* @param stackFrame the stack frame from which we try to derive the class. This can vary depending
|
|
* on how the code is called so we expose it for flexibility. Jiggle the handle on this whenever the
|
|
* line numbers appear incorrect.
|
|
*/
|
|
fun spiffy(stackFrame: Int = 4, tag: String = "@TWIG"): (String) -> String = { logMessage: String ->
|
|
val stack = Thread.currentThread().stackTrace[stackFrame]
|
|
val time = String.format("$tag %1\$tD %1\$tI:%1\$tM:%1\$tS.%1\$tN", System.currentTimeMillis())
|
|
val className = stack.className.split(".").lastOrNull()?.split("\$")?.firstOrNull()
|
|
val tags = Bush.leaves.joinToString(" #", "#")
|
|
"$time[$className:${stack.lineNumber}]($tags) $logMessage"
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Since there can only ever be one trunk on the bush of twigs, this class lets
|
|
* you cheat and make that trunk be a bundle of twigs.
|
|
*/
|
|
open class CompositeTwig(open val twigBundle: MutableList<Twig>) :
|
|
Twig {
|
|
override operator fun plus(twig: Twig): Twig {
|
|
if (twig is CompositeTwig) twigBundle.addAll(twig.twigBundle) else twigBundle.add(twig)
|
|
return this
|
|
}
|
|
|
|
override fun twig(logMessage: String, priority: Int) {
|
|
for (twig in twigBundle) {
|
|
twig.twig(logMessage, priority)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Times a tiny log. Execute the block of code on the clock.
|
|
*/
|
|
inline fun <R> Twig.twig(logMessage: String, priority: Int = 0, block: () -> R): R {
|
|
val start = System.currentTimeMillis()
|
|
val result = block()
|
|
val elapsed = (System.currentTimeMillis() - start)
|
|
twig("$logMessage | ${elapsed}ms", priority)
|
|
return result
|
|
}
|
|
|
|
/**
|
|
* A tiny log task. Execute the block of code with some twigging around the outside. For silent
|
|
* twigs, this adds a small amount of overhead at the call site but still avoids logging.
|
|
*
|
|
* note: being an extension function (i.e. static rather than a member of the Twig interface) allows
|
|
* this function to be inlined and simplifies its use with suspend functions
|
|
* (otherwise the function and its "block" param would have to suspend)
|
|
*/
|
|
inline fun <R> Twig.twigTask(logMessage: String, priority: Int = 0, block: () -> R): R {
|
|
twig("$logMessage - started | on thread ${Thread.currentThread().name}", priority)
|
|
val start = System.nanoTime()
|
|
val result = block()
|
|
val elapsed = ((System.nanoTime() - start) / 1e5).roundToLong() / 10L
|
|
twig("$logMessage - completed | in $elapsed ms" + " on thread ${Thread.currentThread().name}", priority)
|
|
return result
|
|
}
|
|
|