Идиоматический способ входа в Котлин

У 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()
    }
}
Другие вопросы по тегам