Skip to content

Commit ce59f37

Browse files
committed
move the actual implementation of Logger to debugMain so dependencies can be debugImplementation
1 parent e706d62 commit ce59f37

File tree

3 files changed

+289
-284
lines changed

3 files changed

+289
-284
lines changed

logger/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ android {
1212
}
1313

1414
dependencies {
15-
implementation("androidx.collection:collection-ktx:1.4.0")
16-
implementation("androidx.core:core-ktx:1.12.0")
15+
debugImplementation("androidx.collection:collection-ktx:1.4.0")
16+
debugImplementation("androidx.core:core-ktx:1.12.0")
1717
}
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
2+
3+
package de.binarynoise.logger
4+
5+
import java.lang.ref.Reference
6+
import java.lang.reflect.Field
7+
import java.lang.reflect.Method
8+
import java.lang.reflect.Modifier
9+
import java.text.SimpleDateFormat
10+
import java.util.*
11+
import kotlin.reflect.KClass
12+
import android.os.BaseBundle
13+
import android.os.Build
14+
import android.util.Log
15+
import android.util.Log.getStackTraceString
16+
import android.util.SparseArray
17+
import android.util.SparseBooleanArray
18+
import android.util.SparseIntArray
19+
import android.util.SparseLongArray
20+
import android.view.View
21+
import android.view.ViewGroup
22+
import androidx.collection.SparseArrayCompat
23+
import androidx.collection.forEach
24+
import androidx.core.util.forEach
25+
import androidx.core.util.isEmpty
26+
import androidx.core.view.children
27+
28+
object Logger {
29+
30+
fun log(message: CharSequence) {
31+
val callingClassTag = callingClassTag
32+
message.toString().lines().forEach {
33+
Log.v("Logger", "$callingClassTag: $it")
34+
}
35+
}
36+
37+
fun log(message: CharSequence, t: Throwable?) {
38+
val callingClassTag = callingClassTag
39+
val stackTraceString = getStackTraceString(t)
40+
message.lines().forEach {
41+
Log.e("Logger", "$callingClassTag: $it:")
42+
}
43+
stackTraceString.lines().forEach {
44+
Log.e("Logger", it)
45+
}
46+
}
47+
48+
private val currentDateTimeString get() = SimpleDateFormat("yyyy-MM-dd HH:mm:ss,SSS", Locale.GERMAN).format(Date()).toString()
49+
50+
fun Any?.dump(name: String, forceInclude: Set<Any> = emptySet(), forceIncludeClasses: Set<Class<*>> = emptySet()) {
51+
log("dumping $name")
52+
dump(name, 0, mutableSetOf(), forceInclude, forceIncludeClasses)
53+
System.out.flush()
54+
}
55+
56+
private fun Any?.dump(name: String, indent: Int, processed: MutableSet<Any>, forceInclude: Set<Any>, forceIncludeClasses: Set<Class<*>>) {
57+
//<editor-fold defaultstate="collapsed" desc="...">
58+
59+
val tabs = " ".repeat(indent * 2)
60+
val nextIndent = indent + 1
61+
print("$tabs$name ")
62+
if (this == null || this is Nothing? || this::class.qualifiedName == "null") {
63+
println("-> null")
64+
return
65+
}
66+
if (this::class.javaPrimitiveType != null || this is CharSequence) {
67+
println(this.toString())
68+
return
69+
}
70+
print("(${this::class.qualifiedName}@${hashCode()}) -> ")
71+
if (processed.contains(this)) {
72+
println("already dumped")
73+
return
74+
}
75+
processed.add(this)
76+
when {
77+
indent > 5 -> {
78+
println("[...]")
79+
}
80+
this::class.java.isArray -> {
81+
if (this is Array<*>) { // Object Arrays
82+
if (this.isEmpty()) {
83+
println("[]")
84+
} else {
85+
println()
86+
this.forEachIndexed { index, value -> value.dump(index.toString(), nextIndent, processed, forceInclude, forceIncludeClasses) }
87+
}
88+
} else { // primitive Array like int[]
89+
println(Arrays::class.java.getMethod("toString", this::class.java).invoke(null, this))
90+
}
91+
}
92+
//region SparseArrays
93+
this is SparseArray<*> -> {
94+
if (this.isEmpty()) {
95+
println("[]")
96+
} else {
97+
println()
98+
this.forEach { k, v -> v.dump(k.toString(), nextIndent, processed, forceInclude, forceIncludeClasses) }
99+
}
100+
}
101+
this is SparseIntArray -> {
102+
if (this.isEmpty()) {
103+
println("[]")
104+
} else {
105+
println()
106+
this.forEach { k, v -> v.dump(k.toString(), nextIndent, processed, forceInclude, forceIncludeClasses) }
107+
}
108+
}
109+
this is SparseLongArray -> {
110+
if (this.isEmpty()) {
111+
println("[]")
112+
} else {
113+
println()
114+
this.forEach { k, v -> v.dump(k.toString(), nextIndent, processed, forceInclude, forceIncludeClasses) }
115+
}
116+
}
117+
this is SparseBooleanArray -> {
118+
if (this.isEmpty()) {
119+
println("[]")
120+
} else {
121+
println()
122+
this.forEach { k, v -> v.dump(k.toString(), nextIndent, processed, forceInclude, forceIncludeClasses) }
123+
}
124+
}
125+
this is SparseArrayCompat<*> -> {
126+
if (this.isEmpty) {
127+
println("[]")
128+
} else {
129+
println()
130+
this.forEach { k, v -> v.dump(k.toString(), nextIndent, processed, forceInclude, forceIncludeClasses) }
131+
}
132+
}
133+
//endregion
134+
this is Collection<*> -> {
135+
if (this.isEmpty()) {
136+
println("[]")
137+
} else {
138+
println()
139+
this.forEachIndexed { index, value -> value.dump(index.toString(), nextIndent, processed, forceInclude, forceIncludeClasses) }
140+
}
141+
}
142+
this is Map<*, *> -> {
143+
if (this.isEmpty()) {
144+
println("[]")
145+
} else {
146+
println()
147+
this.forEach { (k, v) -> v.dump(k.toString(), nextIndent, processed, forceInclude, forceIncludeClasses) }
148+
}
149+
}
150+
Build.VERSION.SDK_INT >= 21 && this is BaseBundle -> {
151+
val keys = keySet()
152+
if (keys.isNullOrEmpty()) {
153+
println("[]")
154+
} else {
155+
println()
156+
keys.forEach {
157+
@Suppress("DEPRECATION") get(it).dump(it, nextIndent, processed, forceInclude, forceIncludeClasses)
158+
}
159+
}
160+
}
161+
this is View.BaseSavedState -> {
162+
println(this.toString())
163+
}
164+
this is ViewGroup -> {
165+
println()
166+
children.forEachIndexed { view, i -> view.dump(i.toString(), nextIndent, processed, forceInclude, forceIncludeClasses) }
167+
}
168+
forceInclude.none { it == this } && forceIncludeClasses.none { it.isInstance(this) } && listOf(
169+
"android.content.Context",
170+
"android.view.View",
171+
"androidx.fragment.app.Fragment",
172+
"android.os.Handler",
173+
"android.content.res.Resources",
174+
"java.lang.Thread",
175+
"java.lang.ThreadGroup",
176+
"java.lang.ClassLoader",
177+
"android.content.res.ResourcesImpl",
178+
"android.content.res.ApkAssets",
179+
"kotlin.Function",
180+
"android.app.ActivityManager",
181+
"androidx.appcompat.app.AppCompatDelegate",
182+
"android.view.accessibility.AccessibilityManager",
183+
"android.view.View.AttachInfo",
184+
"java.lang.reflect.Member",
185+
).any { this::class.qualifiedName == it } -> {
186+
println("i: $this")
187+
}
188+
this is Reference<*> -> {
189+
// println(get().toString())
190+
println()
191+
get().dump("referenced", nextIndent, processed, forceInclude, forceIncludeClasses)
192+
}
193+
this is Class<*> -> {
194+
println(this.canonicalName)
195+
}
196+
this is KClass<*> -> {
197+
println(this.java.canonicalName)
198+
}
199+
this::class.java.declaredFields.find { it.name.equals("INSTANCE", true) } != null -> {
200+
println("kotlin object")
201+
return
202+
}
203+
else -> {
204+
println()
205+
val fields = mutableSetOf<Field>()
206+
val methods = mutableSetOf<Method>()
207+
var cls: Class<*>? = this::class.java
208+
while (cls != null && cls != Any::class.java && cls != Object::class.java) {
209+
fields.addAll(cls.declaredFields.filterNot { Modifier.isStatic(it.modifiers) })
210+
methods.addAll(cls.declaredMethods.filter { !Modifier.isStatic(it.modifiers) && it.name.startsWith("get") && it.parameterCount == 0 })
211+
cls = cls.superclass
212+
}
213+
214+
fields.sortedBy { it.name }.forEach {
215+
it.isAccessible = true
216+
try {
217+
it.get(this).dump(it.name, nextIndent, processed, forceInclude, forceIncludeClasses)
218+
} catch (e: ReflectiveOperationException) {
219+
e.printStackTrace()
220+
}
221+
}
222+
223+
methods.sortedBy { it.name }.forEach {
224+
it.isAccessible = true
225+
try {
226+
it.invoke(this).dump(it.name, nextIndent, processed, forceInclude, forceIncludeClasses)
227+
} catch (e: ReflectiveOperationException) {
228+
e.printStackTrace()
229+
}
230+
}
231+
}
232+
}
233+
// processed.remove(this)
234+
//</editor-fold>
235+
}
236+
237+
fun View.dump(indent: Int = 0) {
238+
println(" ".repeat(indent * 2) + this)
239+
if (this is ViewGroup) {
240+
children.forEach {
241+
it.dump(indent + 1)
242+
}
243+
}
244+
}
245+
246+
private val callingClassTag: String
247+
get() {
248+
val stackTraceElement = callingClassStackTraceElement
249+
250+
val simpleClassName = stackTraceElement.simpleClassName
251+
return run {
252+
val lineNumber = stackTraceElement.lineNumber
253+
val file = stackTraceElement.fileName ?: "unknown"
254+
// val substringAfterLast = proc.substringAfterLast(":", missingDelimiterValue = "x")
255+
// val proc = if (substringAfterLast != "x") "$substringAfterLast:" else ""
256+
// val thread = Thread.currentThread().name
257+
simpleClassName.padEnd(30) + "." + (" ($file:$lineNumber)").padStart(40)
258+
}
259+
}
260+
261+
private val StackTraceElement.simpleClassName: String
262+
get() = className.split("$").first().split(".").last()
263+
264+
private val callingClassStackTraceElement: StackTraceElement
265+
get() {
266+
val stackTrace = Thread.currentThread().stackTrace
267+
268+
var foundOwn = false
269+
stackTrace.forEach { ste ->
270+
val isLogger = ste.className == Logger::class.qualifiedName
271+
if (isLogger) {
272+
foundOwn = true
273+
} else if (foundOwn) {
274+
return ste
275+
}
276+
}
277+
278+
Log.w("Logger", stackTrace.joinToString("\t\n"))
279+
throw IllegalStateException("invalid stack")
280+
}
281+
}

0 commit comments

Comments
 (0)