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

Kotlin не имеет того же понятия статических полей, что и в Java. В Java общепринятым способом ведения журнала является:

public class Foo {
    private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}

Вопрос - что такое идиоматический способ ведения журнала в Котлин?

Ответ 1

В большинстве зрелых кодов 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.stackoverflow.kotlin.test.MyClass foo ИНФОРМАЦИЯ: Привет от MyClass

Подробнее о сопутствующих объектах здесь: Companion Objects... Также обратите внимание, что в приведенном выше примере MyClass::class.java получает экземпляр типа Class<MyClass> для регистратора, тогда как this.javaClass получит экземпляр типа Class<MyClass.Companion>.

На экземпляр класса (общий)

Но на самом деле нет причин избегать вызова и получения регистратора на уровне экземпляра. Упомянутый вами идиоматический способ Java устарел и основан на страхе перед производительностью, в то время как средство ведения журнала для каждого класса уже кэшируется практически любой разумной системой ведения журнала на планете. Просто создайте член для хранения объекта регистратора.

class MyClass {
  val LOG = Logger.getLogger(this.javaClass.name)

  fun foo() {
        LOG.warning("Hello from MyClass")
  }
} 

создание вывода:

26 декабря 2015 11:28:44 org.stackoverflow.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.stackoverflow.kotlin.test.Something foo ИНФОРМАЦИЯ: Привет от чего-то

26 декабря 2015 г. 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo ИНФОРМАЦИЯ: Привет от SomethingElse

Функции расширения (необычны в этом случае из-за "загрязнения" любого пространства имен)

У Kotlin есть несколько скрытых трюков, которые позволяют сделать этот код еще меньше. Вы можете создавать функции расширения для классов и, следовательно, предоставлять им дополнительную функциональность. Одним из предложений в комментариях выше было расширение Any с помощью функции logger. Это может создавать шум в любое время, когда кто-либо использует завершение кода в своей среде 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.stackoverflow.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.stackoverflow.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.stackoverflow.kotlin.test.MixedBagOfTricks foo ИНФОРМАЦИЯ: Привет от MixedBagOfTricks

Примечание. Метод unwrapCompanionClass() гарантирует, что мы не unwrapCompanionClass() регистратор, названный в честь объекта-компаньона, а включающий класс. Это текущий рекомендуемый способ найти класс, содержащий объект-компаньон. Удаление " $ Companion " из имени с помощью removeSuffix() не работает, поскольку объектам-компаньонам могут быть присвоены собственные имена.

Ответ 2

Посмотрите библиотеку 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: AnkoLogger в Kotlin и Android: AnkoLogger против kotlin-logging

Отказ от ответственности: я хранитель этой библиотеки.

Изменение: kotlin-logging теперь имеет многоплатформенную поддержку: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support

Ответ 3

В качестве хорошего примера реализации регистрации я хотел бы упомянуть Anko, который использует специальный интерфейс AnkoLogger, который класс, необходимо выполнить ведение журнала. Внутри интерфейса есть код, который генерирует тег регистрации для класса. Ведение журнала выполняется через функции расширения, которые можно вызывать внутри реализации interace без префиксов или даже создания экземпляра журнала.

Я не думаю, что это идиоматично, но он кажется хорошим подходом, поскольку он требует минимального кода, просто добавляя интерфейс к объявлению класса и вы регистрируетесь с разными тегами для разных классов.


Код ниже в основном 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")
}

Ответ 4

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, см. ниже). Поскольку 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
  }
}

Log4j2 Kotlin Logging API

Большая часть предыдущего раздела была напрямую адаптирована для создания модуля API Kotlin Logging, который теперь является официальной частью Log4j2 (отказ от ответственности: я являюсь основным автором). Вы можете скачать его прямо из Apache или через Maven Central.

Использование в основном такое, как описано выше, но модуль поддерживает как интерфейсный доступ к logger функцию расширения logger для Any для использования там, где this определено, так и именованную функцию регистратора для использования там, где this не определено (например, функции верхнего уровня).).

Ответ 5

Что-то вроде этой работы для вас?

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() }
}

Ответ 6

Анко

Вы можете использовать библиотеку Anko чтобы сделать это. У вас будет код, как показано ниже:

class MyActivity : Activity(), AnkoLogger {
    private fun someMethod() {
        info("This is my first app and it 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

Если вы уже пользовались библиотекой журналов Jake Wharton 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

Надеюсь, это поможет

Ответ 7

Я не слышал ни слова об этом. Чем проще, тем лучше, поэтому я бы использовал свойство верхнего уровня

val logger = Logger.getLogger("package_name")

Эта практика хорошо работает на Python, и, как может показаться Kotlin и Python, я думаю, что они очень похожи на "дух" (говоря о идиомах).

Ответ 8

Как насчет функции расширения вместо класса? Таким образом, вы получите:

public fun KClass.logger(): Logger = LoggerFactory.getLogger(this.java)

class SomeClass {
    val LOG = SomeClass::class.logger()
}

Примечание. Я не тестировал это вообще, так что это может быть не совсем правильно.

Ответ 9

Во-первых, вы можете добавить функции расширения для создания журнала.

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()

Во-вторых, вы можете определить интерфейс, который предоставляет регистратор и его реализацию mixin.

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.
}

Ответ 10

создать сопутствующий объект и пометить соответствующие поля аннотацией @JvmStatic

Ответ 11

То, для чего предназначены вспомогательные объекты, в общем: замена статического материала.

Ответ 12

Пример Slf4j, тот же для других. Это даже работает для создания регистратора уровня пакетов

/**  
  * Get logger by current class name.  
  */ 

fun getLogger(c: () -> Unit): Logger = 
        LoggerFactory.getLogger(c.javaClass.enclosingClass)

Использование:

val logger = getLogger { }

Ответ 13

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()
    }
}

Ответ 14

Здесь уже есть много хороших ответов, но все они касаются добавления регистратора в класс, но как бы вы это сделали, чтобы войти в функции верхнего уровня?

Этот подход является достаточно общим и простым, чтобы хорошо работать в обоих классах, сопутствующих объектах и функциях верхнего уровня:

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")
    }
}

Ответ 15

Это все еще WIP (почти закончен), поэтому я хотел бы поделиться им: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

Основная цель этой библиотеки - обеспечить определенный стиль журнала для всего проекта. Получив код Kotlin, я пытаюсь решить некоторые из проблем, упомянутых в этом вопросе. Что касается первоначального вопроса, то я обычно стремлюсь просто:

private val LOG = LogFormatEnforcer.loggerFor<Foo>()
class Foo {

}