Идиоматический способ входа в Котлин
У Kotlin нет такого же понятия статических полей, как в Java. В Java общепринятым способом ведения журнала является:
public class Foo {
private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}
Вопрос в том, каков идиоматический способ ведения логов в Kotlin?
18 ответов
В большинстве зрелых кодов Kotlin вы найдете один из этих шаблонов ниже. Подход с использованием делегатов свойств использует возможности Kotlin для создания наименьшего кода.
Примечание: код здесь для java.util.Logging
но та же теория применима к любой библиотеке журналов
Статический (обычный, эквивалент вашего Java-кода в вопросе)
Если вы не можете доверять производительности этого поиска хеша в системе ведения журналов, вы можете получить поведение, подобное вашему Java-коду, используя объект-компаньон, который может содержать экземпляр и чувствовать себя статичным для вас.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
создание вывода:
26 декабря 2015 11:28:32
org.stackru.kotlin.test.MyClass
foo INFO: Привет от MyClass
Подробнее о сопутствующих объектах здесь: Сопутствующие объекты... Также обратите внимание, что в приведенном выше примере MyClass::class.java
получает экземпляр типа Class<MyClass>
для регистратора, тогда как this.javaClass
получил бы экземпляр типа Class<MyClass.Companion>
,
На экземпляр класса (общий)
Но на самом деле нет причин избегать звонков и получения регистратора на уровне экземпляра. Упомянутый вами идиоматический способ Java устарел и основан на страхе перед производительностью, в то время как средство ведения журнала для каждого класса уже кэшируется практически любой разумной системой ведения журнала на планете. Просто создайте член для хранения объекта logger.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
создание вывода:
26 декабря 2015 11:28:44 org.stackru.kotlin.test.MyClass foo ИНФОРМАЦИЯ: Привет из MyClass
Вы можете проверить производительность как для каждого экземпляра, так и для каждого класса, и посмотреть, есть ли реальная разница для большинства приложений.
Имущественные делегаты (обычные, самые элегантные)
Другой подход, предложенный @Jire в другом ответе, заключается в создании делегата свойства, который затем можно использовать для равномерного выполнения логики в любом другом классе, который вы хотите. Есть более простой способ сделать это, так как Kotlin предоставляет Lazy
делегировать уже, мы можем просто обернуть его в функцию. Здесь есть одна хитрость: если мы хотим узнать тип класса, использующего в настоящее время делегат, мы сделаем его функцией расширения для любого класса:
fun <R : Any> R.logger(): Lazy<Logger> {
return lazy { Logger.getLogger(unwrapCompanionClass(this.javaClass).name) }
}
// see code for unwrapCompanionClass() below in "Putting it all Together section"
Этот код также гарантирует, что если вы используете его в объекте-компаньоне, имя регистратора будет таким же, как если бы вы использовали его в самом классе. Теперь вы можете просто:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
для каждого экземпляра класса или если вы хотите, чтобы он был более статичным с одним экземпляром для каждого класса:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
И ваш выход из звонка foo()
на обоих этих классах будет:
26 декабря 2015 г. 11:30:55 org.stackru.kotlin.test.Something foo ИНФОРМАЦИЯ: Привет от чего-то
26 декабря 2015 г. 11:30:55 org.stackru.kotlin.test.SomethingElse foo ИНФОРМАЦИЯ: Привет от SomethingElse
Функции расширения (необычные в этом случае из-за "загрязнения" любого пространства имен)
У Kotlin есть несколько скрытых трюков, которые позволяют сделать этот код еще меньше. Вы можете создавать функции расширения для классов и, следовательно, предоставлять им дополнительную функциональность. Одним из предложений в комментариях выше было продлить Any
с функцией регистрации. Это может создавать шум в любое время, когда кто-либо использует завершение кода в своей среде IDE для любого класса. Но есть секретная выгода для расширения Any
или некоторый другой интерфейс маркера: вы можете подразумевать, что вы расширяете свой собственный класс и, следовательно, обнаруживаете класс, в котором находитесь А? Чтобы быть менее запутанным, вот код:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Теперь внутри класса (или объекта-компаньона) я могу просто вызвать это расширение в своем собственном классе:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Производить продукцию:
26 декабря 2015 11:29:12 org.stackru.kotlin.test.SomethingDifferent foo ИНФОРМАЦИЯ: Привет от SomethingDifferent
По сути, код рассматривается как призыв к расширению Something.logger()
, Проблема заключается в том, что следующее может также быть правдой, создавая "загрязнение" для других классов:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Функции расширения на интерфейсе маркера (не уверен, насколько распространена, но распространена модель для "черт")
Чтобы сделать использование расширений более понятным и уменьшить "загрязнение", вы можете использовать интерфейс маркера для расширения:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Или даже сделать метод частью интерфейса с реализацией по умолчанию:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
И используйте любой из этих вариантов в вашем классе:
class MarkedClass: Loggable {
val LOG = logger()
}
Производить продукцию:
26 декабря 2015 11:41:01 org.stackru.kotlin.test.MarkedClass foo ИНФОРМАЦИЯ: Привет от MarkedClass
Если вы хотите принудительно создать единое поле для хранения регистратора, то при использовании этого интерфейса вы можете легко потребовать, чтобы у разработчика было поле, такое как LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Теперь разработчик интерфейса должен выглядеть так:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Конечно, абстрактный базовый класс может делать то же самое, имея опцию как интерфейса, так и абстрактного класса, реализующего этот интерфейс, что обеспечивает гибкость и единообразие:
abstract class WithLogging: Loggable {
override val LOG: Logger = logger()
}
// using the logging from the base class
class MyClass1: WithLogging() {
// ... already has logging!
}
// providing own logging compatible with marker interface
class MyClass2: ImportantBaseClass(), Loggable {
// ... has logging that we can understand, but doesn't change my hierarchy
override val LOG: Logger = logger()
}
// providing logging from the base class via a companion object so our class hierarchy is not affected
class MyClass3: ImportantBaseClass() {
companion object : WithLogging() {
// we have the LOG property now!
}
}
Собираем все вместе (небольшая вспомогательная библиотека)
Вот небольшая вспомогательная библиотека, позволяющая легко использовать любой из перечисленных выше вариантов. В Kotlin принято расширять API-интерфейсы, чтобы сделать их по своему вкусу. Либо в расширении, либо в функциях верхнего уровня. Вот микс, чтобы дать вам варианты создания регистраторов, и пример, показывающий все варианты:
// Return logger for Java class, if companion object fix the name
fun <T: Any> logger(forClass: Class<T>): Logger {
return Logger.getLogger(unwrapCompanionClass(forClass).name)
}
// unwrap companion class to enclosing class given a Java Class
fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> {
return ofClass.enclosingClass?.takeIf {
ofClass.enclosingClass.kotlin.companionObject?.java == ofClass
} ?: ofClass
}
// unwrap companion class to enclosing class given a Kotlin Class
fun <T: Any> unwrapCompanionClass(ofClass: KClass<T>): KClass<*> {
return unwrapCompanionClass(ofClass.java).kotlin
}
// Return logger for Kotlin class
fun <T: Any> logger(forClass: KClass<T>): Logger {
return logger(forClass.java)
}
// return logger from extended class (or the enclosing class)
fun <T: Any> T.logger(): Logger {
return logger(this.javaClass)
}
// return a lazy logger property delegate for enclosing class
fun <R : Any> R.lazyLogger(): Lazy<Logger> {
return lazy { logger(this.javaClass) }
}
// return a logger property delegate for enclosing class
fun <R : Any> R.injectLogger(): Lazy<Logger> {
return lazyOf(logger(this.javaClass))
}
// marker interface and related extension (remove extension for Any.logger() in favour of this)
interface Loggable {}
fun Loggable.logger(): Logger = logger(this.javaClass)
// abstract base class to provide logging, intended for companion objects more than classes but works for either
abstract class WithLogging: Loggable {
val LOG = logger()
}
Выберите любой из тех, которые вы хотите сохранить, и вот все варианты:
class MixedBagOfTricks {
companion object {
val LOG1 by lazyLogger() // lazy delegate, 1 instance per class
val LOG2 by injectLogger() // immediate, 1 instance per class
val LOG3 = logger() // immediate, 1 instance per class
val LOG4 = logger(this.javaClass) // immediate, 1 instance per class
}
val LOG5 by lazyLogger() // lazy delegate, 1 per instance of class
val LOG6 by injectLogger() // immediate, 1 per instance of class
val LOG7 = logger() // immediate, 1 per instance of class
val LOG8 = logger(this.javaClass) // immediate, 1 instance per class
}
val LOG9 = logger(MixedBagOfTricks::class) // top level variable in package
// or alternative for marker interface in class
class MixedBagOfTricks : Loggable {
val LOG10 = logger()
}
// or alternative for marker interface in companion object of class
class MixedBagOfTricks {
companion object : Loggable {
val LOG11 = logger()
}
}
// or alternative for abstract base class for companion object of class
class MixedBagOfTricks {
companion object: WithLogging() {} // instance 12
fun foo() {
LOG.info("Hello from MixedBagOfTricks")
}
}
// or alternative for abstract base class for our actual class
class MixedBagOfTricks : WithLogging() { // instance 13
fun foo() {
LOG.info("Hello from MixedBagOfTricks")
}
}
Все 13 экземпляров регистраторов, созданных в этом образце, будут производить одно и то же имя регистратора и выводить:
26 декабря 2015 г. 11:39:00 org.stackru.kotlin.test.MixedBagOfTricks foo ИНФОРМАЦИЯ: Привет от MixedBagOfTricks
Примечание: unwrapCompanionClass()
Метод гарантирует, что мы не генерируем логгер, названный в честь объекта-компаньона, а включающий класс. Это текущий рекомендуемый способ найти класс, содержащий объект-компаньон. Снятие " $ Companion " с названия с помощью removeSuffix()
не работает, поскольку объектам-компаньонам могут быть заданы собственные имена.
Посмотрите библиотеку kotlin-logging.
Это позволяет регистрироваться так:
private val logger = KotlinLogging.logger {}
class Foo {
logger.info{"wohoooo $wohoooo"}
}
Или вот так:
class FooWithLogging {
companion object: KLogging()
fun bar() {
logger.info{"wohoooo $wohoooo"}
}
}
Я также написал пост в блоге, сравнивая его с AnkoLogger
: Вход в Kotlin & Android: AnkoLogger против котлин-логов
Отказ от ответственности: я хранитель этой библиотеки.
Изменить: kotlin-logging теперь имеет многоплатформенную поддержку: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support
KISS: для команд Java, мигрирующих в Котлин
Если вы не возражаете предоставить имя класса в каждом экземпляре логгера (как в java), вы можете упростить его, определив это как функцию верхнего уровня где-то в вашем проекте:
import org.slf4j.LoggerFactory
inline fun <reified T:Any> logger() = LoggerFactory.getLogger(T::class.java)
При этом используется параметр уточненного типа Kotlin.
Теперь вы можете использовать это следующим образом:
class SomeClass {
// or within a companion object for one-instance-per-class
val log = logger<SomeClass>()
...
}
Этот подход очень прост и близок к Java-эквиваленту, но добавляет немного синтаксического сахара.
Следующий шаг: расширения или делегаты
Лично я предпочитаю идти дальше и использовать подход расширений или делегатов. Это хорошо суммировано в ответе @JaysonMinard, но вот TL;DR для подхода "Делегировать" с API log4j2. Поскольку log4j2, в отличие от slf4j, поддерживает ведение журнала с Supplier
Я также добавил делегата, чтобы упростить использование этих методов.
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.util.Supplier
import kotlin.reflect.companionObject
/**
* An adapter to allow cleaner syntax when calling a logger with a Kotlin lambda. Otherwise calling the
* method with a lambda logs the lambda itself, and not its evaluation. We specify the Lambda SAM type as a log4j2 `Supplier`
* to avoid this. Since we are using the log4j2 api here, this does not evaluate the lambda if the level
* is not enabled.
*/
class FunctionalLogger(val log: Logger): Logger by log {
inline fun debug(crossinline supplier: () -> String) {
log.debug(Supplier { supplier.invoke() })
}
inline fun debug(t: Throwable, crossinline supplier: () -> String) {
log.debug(Supplier { supplier.invoke() }, t)
}
inline fun info(crossinline supplier: () -> String) {
log.info(Supplier { supplier.invoke() })
}
inline fun info(t: Throwable, crossinline supplier: () -> String) {
log.info(Supplier { supplier.invoke() }, t)
}
inline fun warn(crossinline supplier: () -> String) {
log.warn(Supplier { supplier.invoke() })
}
inline fun warn(t: Throwable, crossinline supplier: () -> String) {
log.warn(Supplier { supplier.invoke() }, t)
}
inline fun error(crossinline supplier: () -> String) {
log.error(Supplier { supplier.invoke() })
}
inline fun error(t: Throwable, crossinline supplier: () -> String) {
log.error(Supplier { supplier.invoke() }, t)
}
}
/**
* A delegate-based lazy logger instantiation. Use: `val log by logger()`.
*/
@Suppress("unused")
inline fun <reified T : Any> T.logger(): Lazy<FunctionalLogger> =
lazy { FunctionalLogger(LogManager.getLogger(unwrapCompanionClass(T::class.java))) }
// unwrap companion class to enclosing class given a Java Class
fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> {
return if (ofClass.enclosingClass != null && ofClass.enclosingClass.kotlin.companionObject?.java == ofClass) {
ofClass.enclosingClass
} else {
ofClass
}
}
В качестве хорошего примера реализации журналирования я хотел бы упомянуть Anko, который использует специальный интерфейс AnkoLogger
который должен реализовать класс, который нуждается в регистрации. Внутри интерфейса есть код, который генерирует тег регистрации для класса. Ведение журнала затем осуществляется с помощью функций расширения, которые могут вызываться внутри реализации взаимодействия, без префиксов или даже создания экземпляра регистратора.
Я не думаю, что это идиоматично, но это кажется хорошим подходом, так как требует минимального кода, просто добавив интерфейс к объявлению класса, и вы получите логирование с разными тегами для разных классов.
Приведенный ниже код в основном AnkoLogger, упрощенный и переписанный для использования без Android.
Во-первых, есть интерфейс, который ведет себя как интерфейс маркера:
interface MyLogger {
val tag: String get() = javaClass.simpleName
}
Это позволяет его реализации использовать функции расширений для MyLogger
внутри их кода просто вызывая их this
, И он также содержит тег регистрации.
Далее, есть общая точка входа для разных методов регистрации:
private inline fun log(logger: MyLogger,
message: Any?,
throwable: Throwable?,
level: Int,
handler: (String, String) -> Unit,
throwableHandler: (String, String, Throwable) -> Unit
) {
val tag = logger.tag
if (isLoggingEnabled(tag, level)) {
val messageString = message?.toString() ?: "null"
if (throwable != null)
throwableHandler(tag, messageString, throwable)
else
handler(tag, messageString)
}
}
Это будет вызвано методами регистрации. Получает тег от MyLogger
реализации, проверяет параметры ведения журнала и затем вызывает один из двух обработчиков, один с Throwable
аргумент и тот, без.
Затем вы можете определить столько методов ведения журнала, сколько захотите, следующим образом:
fun MyLogger.info(message: Any?, throwable: Throwable? = null) =
log(this, message, throwable, LoggingLevels.INFO,
{ tag, message -> println("INFO: $tag # $message") },
{ tag, message, thr ->
println("INFO: $tag # $message # $throwable");
thr.printStackTrace()
})
Они определены один раз для регистрации только сообщения и регистрации Throwable
а это делается с помощью throwable
параметр.
Функции, которые передаются как handler
а также throwableHandler
могут отличаться для разных методов ведения журнала, например, они могут записать журнал в файл или загрузить его куда-нибудь. isLoggingEnabled
а также LoggingLevels
опущены для краткости, но их использование обеспечивает еще большую гибкость.
Это позволяет для следующего использования:
class MyClass : MyLogger {
fun myFun() {
info("Info message")
}
}
Есть небольшой недостаток: для входа в функции уровня пакета потребуется объект регистратора:
private object MyPackageLog : MyLogger
fun myFun() {
MyPackageLog.info("Info message")
}
Анко
Ты можешь использовать Anko
библиотека, чтобы сделать это. У вас будет код, как показано ниже:
class MyActivity : Activity(), AnkoLogger {
private fun someMethod() {
info("This is my first app and it's awesome")
debug(1234)
warn("Warning")
}
}
Котлин-каротаж
Библиотека kotlin-logging ( проект Github - kotlin-logging) позволяет писать код регистрации, как показано ниже:
class FooWithLogging {
companion object: KLogging()
fun bar() {
logger.info{"Item $item"}
}
}
StaticLog
или вы также можете использовать эту небольшую написанную в Kotlin библиотеке под названием StaticLog
тогда ваш код будет выглядеть так:
Log.info("This is an info message")
Log.debug("This is a debug message")
Log.warn("This is a warning message","WithACustomTag")
Log.error("This is an error message with an additional Exception for output", "AndACustomTag", exception )
Log.logLevel = LogLevel.WARN
Log.info("This message will not be shown")\
Второе решение может быть лучше, если вы хотите определить выходной формат для метода ведения журнала, например:
Log.newFormat {
line(date("yyyy-MM-dd HH:mm:ss"), space, level, text("/"), tag, space(2), message, space(2), occurrence)
}
или используйте фильтры, например:
Log.filterTag = "filterTag"
Log.info("This log will be filtered out", "otherTag")
Log.info("This log has the right tag", "filterTag")
timberkt
Если вы уже использовали Джейка Уортона Timber
проверка библиотеки журналов timberkt
,
Эта библиотека основана на Timber с API, который проще в использовании от Kotlin. Вместо использования параметров форматирования вы передаете лямбду, которая оценивается только в том случае, если сообщение зарегистрировано.
Пример кода:
// Standard timber
Timber.d("%d %s", intVar + 3, stringFun())
// Kotlin extensions
Timber.d { "${intVar + 3} ${stringFun()}" }
// or
d { "${intVar + 3} ${stringFun()}" }
Проверьте также: Вход в Kotlin & Android: AnkoLogger vs kotlin-logging
Надеюсь, это поможет
Будет ли что-то подобное для вас?
class LoggerDelegate {
private var logger: Logger? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): Logger {
if (logger == null) logger = Logger.getLogger(thisRef!!.javaClass.name)
return logger!!
}
}
fun logger() = LoggerDelegate()
class Foo { // (by the way, everything in Kotlin is public by default)
companion object { val logger by logger() }
}
Я не слышал об идиомах в этом отношении. Чем проще, тем лучше, поэтому я бы использовал свойство верхнего уровня
val logger = Logger.getLogger("package_name")
Эта практика хорошо работает в Python, и, как бы ни выглядели Kotlin и Python, я считаю, что они очень похожи в этом "духе" (говоря об идиомах).
Здесь уже есть много хороших ответов, но все они касаются добавления регистратора в класс, но как бы вы сделали это, чтобы войти в функции верхнего уровня?
Этот подход является достаточно общим и простым, чтобы хорошо работать в обоих классах, сопутствующих объектах и функциях верхнего уровня:
package nieldw.test
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.junit.jupiter.api.Test
fun logger(lambda: () -> Unit): Lazy<Logger> = lazy { LogManager.getLogger(getClassName(lambda.javaClass)) }
private fun <T : Any> getClassName(clazz: Class<T>): String = clazz.name.replace(Regex("""\$.*$"""), "")
val topLog by logger { }
class TopLevelLoggingTest {
val classLog by logger { }
@Test
fun `What is the javaClass?`() {
topLog.info("THIS IS IT")
classLog.info("THIS IS IT")
}
}
А как насчет функции расширения вместо Class? Таким образом, вы в конечном итоге с:
public fun KClass.logger(): Logger = LoggerFactory.getLogger(this.java)
class SomeClass {
val LOG = SomeClass::class.logger()
}
Примечание. Я вообще не проверял это, так что это может быть не совсем правильно.
Создать сопутствующий объект и пометить соответствующие поля аннотацией @JvmStatic
В общем, для этого нужны сопутствующие объекты: замена статического материала.
Во-первых, вы можете добавить функции расширения для создания регистратора.
inline fun <reified T : Any> getLogger() = LoggerFactory.getLogger(T::class.java)
fun <T : Any> T.getLogger() = LoggerFactory.getLogger(javaClass)
Тогда вы сможете создать регистратор, используя следующий код.
private val logger1 = getLogger<SomeClass>()
private val logger2 = getLogger()
Во-вторых, вы можете определить интерфейс, который обеспечивает регистратор и его смешанную реализацию.
interface LoggerAware {
val logger: Logger
}
class LoggerAwareMixin(containerClass: Class<*>) : LoggerAware {
override val logger: Logger = LoggerFactory.getLogger(containerClass)
}
inline fun <reified T : Any> loggerAware() = LoggerAwareMixin(T::class.java)
Этот интерфейс может быть использован следующим образом.
class SomeClass : LoggerAware by loggerAware<SomeClass>() {
// Now you can use a logger here.
}
Для ведения журнала Kotlin Multiplaform я не смог найти библиотеку, в которой были бы все необходимые мне функции, поэтому в итоге я написал ее. Пожалуйста, проверьте KmLogging. Функции, которые он реализует:
- Использует ведение журнала для конкретной платформы на каждой платформе: вход в Android, os_log в iOS и консоль в JavaScript.
- Высокая производительность. Только 1 логическая проверка при отключении. Мне нравится вводить большое количество журналов, и я хочу, чтобы все это отключалось при выпуске, и я не хочу платить много накладных расходов за большое количество журналов. Кроме того, когда ведение журнала включено, оно должно быть действительно производительным.
- Расширяемый. Необходимо иметь возможность добавлять другие регистраторы, такие как ведение журнала в Crashlytics и т. д.
- Каждый регистратор может вести журнал на другом уровне. Например, вы можете захотеть, чтобы информация и выше направлялись в Crashlytics, а все остальные регистраторы были отключены в рабочей среде.
Использовать:
val log = logging()
log.i { "some message" }
Пример Slf4j, то же самое для других. Это даже работает для создания регистратора уровня пакета
/**
* Get logger by current class name.
*/
fun getLogger(c: () -> Unit): Logger =
LoggerFactory.getLogger(c.javaClass.enclosingClass)
Использование:
val logger = getLogger { }
Вы можете просто создать свою собственную "библиотеку" утилит. Для этой задачи вам не нужна большая библиотека, которая сделает ваш проект более тяжелым и сложным.
Например, вы можете использовать Kotlin Reflection, чтобы получить имя, тип и значение любого свойства класса.
Прежде всего, убедитесь, что в вашем build.gradle установлена мета-зависимость:
dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
После этого вы можете просто скопировать и вставить этот код в свой проект:
import kotlin.reflect.full.declaredMemberProperties
class LogUtil {
companion object {
/**
* Receives an [instance] of a class.
* @return the name and value of any member property.
*/
fun classToString(instance: Any): String {
val sb = StringBuilder()
val clazz = instance.javaClass.kotlin
clazz.declaredMemberProperties.forEach {
sb.append("${it.name}: (${it.returnType}) ${it.get(instance)}, ")
}
return marshalObj(sb)
}
private fun marshalObj(sb: StringBuilder): String {
sb.insert(0, "{ ")
sb.setLength(sb.length - 2)
sb.append(" }")
return sb.toString()
}
}
}
Пример использования:
data class Actor(val id: Int, val name: String) {
override fun toString(): String {
return classToString(this)
}
}
Вот мое решение, использующее расширение свойства, чтобы обеспечить чистую работу журнала.
interface Loggable {
}
val Loggable.log: Logger
get() {
return LoggerFactory.getLogger(this::class.java);
}
class LogTest : Loggable {
}
fun main() {
val test = LogTest()
test.log.info("the log work!")
}
Это все еще WIP (почти закончен), поэтому я хотел бы поделиться им: https://github.com/leandronunes85/log-format-enforcer
Основная цель этой библиотеки - обеспечить определенный стиль журнала для всего проекта. Получив код Kotlin, я пытаюсь решить некоторые из проблем, упомянутых в этом вопросе. Что касается первоначального вопроса, то я обычно стремлюсь просто:
private val LOG = LogFormatEnforcer.loggerFor<Foo>()
class Foo {
}
fun <R : Any> R.logger(): Lazy<Logger> = lazy {
LoggerFactory.getLogger((if (javaClass.kotlin.isCompanion) javaClass.enclosingClass else javaClass).name)
}
class Foo {
val logger by logger()
}
class Foo {
companion object {
val logger by logger()
}
}