Mark
12/21/2023, 8:59 AMJoffrey
12/21/2023, 9:06 AMMark
12/21/2023, 9:09 AMTgo1014
12/21/2023, 9:12 AMMark
12/21/2023, 9:19 AMelectrolobzik
12/21/2023, 9:26 AMMark
12/21/2023, 9:27 AMelectrolobzik
12/21/2023, 9:30 AMJoffrey
12/21/2023, 9:36 AMinline fun <reified T> T.log(...) {
// use T::class here
}
And for top-level you could define one that requires passing an explicit type parameter:
inline fun <reified T> log(...) {
// use T::class here
}
This second one needs to be called like log<MyClass>(...)
Mark
12/21/2023, 10:54 AMMark
12/21/2023, 11:36 AMCoroutineScope
.Joffrey
12/21/2023, 11:59 AMlogger
property for the class, and then I used the logger
property to actually log messageselectrolobzik
12/21/2023, 12:22 PMMark
12/21/2023, 12:52 PMelectrolobzik
12/21/2023, 1:00 PMlog(text)
and nothing more. Easy to write and easy to read (in some classes I used to have a lot of them). But if you call log
function only 1-2 times in a class or you don’t mind having something like log<MyClass>(text)
the top functions may be better. In my particular case I also have another parameters besides the class/tag (I am logging also a feature) and I didn’t like to add a lot of duplicated boilerplate to each actual call.electrolobzik
12/21/2023, 1:03 PMMark
12/21/2023, 1:21 PMlog<MyClass>(text)
type.Joffrey
12/21/2023, 1:59 PMJoffrey
12/21/2023, 1:59 PMLogger
, and that you use once in the class, and then every logging call is made through the logger (which also solves the copy-paste problem mentioned by Roman)Mark
12/21/2023, 2:01 PMJoffrey
12/21/2023, 2:02 PMMark
12/21/2023, 2:05 PMpublic static final String TAG = Foo.class.getSimpleName();
It’s better than that, yes, but I’m going to have to add a few hundred logger properties to my codebase.Joffrey
12/21/2023, 2:06 PMlogger
property behind the scenes with the proper class. So every class would just do:
class MyThing {
companion object : KLogging()
fun doStuff() {
logger.info("I can use the logger property from companion here")
}
}
Where KLogging
is defined something like this:
abstract class KLogging(target: KClass<*>? = null) {
val logger: KLogger by lazy { YourLoggerFactory.logger(target ?: this::class) }
}
Mark
12/21/2023, 2:07 PMMark
12/21/2023, 2:08 PMcompanion object
or always keep it separate? (I think it’s possible to have multiple companion objects?)Joffrey
12/21/2023, 2:10 PMcompanion object : KLogging() {
val otherCustomProp = 42
fun otherCustomFun() { ... }
}
Mark
12/21/2023, 2:11 PMJoffrey
12/21/2023, 2:13 PMMark
12/21/2023, 2:16 PMelectrolobzik
12/21/2023, 2:23 PMelectrolobzik
12/21/2023, 2:25 PMelectrolobzik
12/21/2023, 2:25 PMMark
12/21/2023, 2:39 PMKLogging
(String
& KClass
) KISS
abstract class KLogging(private val name: String) {
constructor(target: KClass<*>) : this(target::class.java.simpleName)
val logger: KLogger by lazy { createLogger(name) }
}
Mark
12/21/2023, 3:06 PMFoo
companion object, would you prefer to do Foo.log("bar")
or log<Foo>("bar")
? The advantage of the former is reuse of the lazy logger. Also it means the Foo
companion object is the single place that determines the tag name.Mark
12/21/2023, 3:32 PMComposable
functions? Surely a different strategy is needed to prevent recompositions? I guess you shouldn’t be logging outside of a side effect anyway.Mark
12/22/2023, 8:03 AMinternal
logger for each module (though you can of course change this strategy on a per module basis). Note - this obviously only applies to apps with lots of modules (mine has over 30).
internal object ModuleLogger: MyLogger by MyLogger("featureFoo")
where:
interface MyLogger {
val logger: KLogger
fun logd(message: String) {
logger.debug(message)
}
fun logd(lazyMessage: () -> String) {
logger.debug(lazyMessage)
}
fun logi(message: String) {
logger.info(message)
}
...
companion object {
operator fun invoke(name: String): MyLogger = object : MyLogger {
override val logger: KLogger by lazy { createLogger(name) }
}
// for class-level loggers
// use like:
// Foo {
// companion object : MyLogger by MyLogger<Foo>()
// }
inline operator fun <reified T> invoke(): MyLogger = invoke(T::class.java.simpleName)
}
}
This means, I can just leave the existing logd
, `logi`calls as they are by simply adding imports like:
import com.bar.myapp.feature.foo.ModuleLogger.logd
If, for certain classes, you want to do a class-level tag strategy, then you can just do the companion object thing there.Mark
12/22/2023, 9:21 AMinternal
visibility loggers cannot be used in public inline
functions. In this case, you can use, for example, class-level loggers from which public companion objects extend.