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


164

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

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

Вопрос в том, каков идиоматический способ ведения логов в Kotlin?


1
Не публиковать это как ответ, потому что это далеко от способа Java, но я рассмотрел написание функции расширения для Any для регистрации. Вам, конечно, нужно кэшировать логгеры, но я думаю, что это был бы хороший способ сделать это.
Mhlz

1
@mhlz Разве эта функция расширения не будет разрешена статически? Как, например, он не будет применяться ко всем объектам, только к объектам типа Any(таким образом, нуждающимся в приведении)?
Jire

1
@mhlz функция расширения не имеет смысла, потому что у нее не будет состояния, чтобы держать регистратор. Это может быть расширение для возврата регистратора, но зачем это нужно для каждого известного класса в системе? Установка расширений Any, как правило, приводит к небрежному шуму в среде IDE. @Jire расширение будет применяться ко всем потомкам Any, по-прежнему будет возвращать правильное this.javaClassдля каждого. Но я не рекомендую это как решение.
Джейсон Минард

Ответы:


251

В большинстве зрелых кодов Kotlin вы найдете один из этих шаблонов ниже. Подход, использующий Property Delegates, использует возможности 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с помощью функции регистрации. Это может создавать шум в любое время, когда кто-либо использует завершение кода в своей среде 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()метод гарантирует , что мы не генерируют регистратор имени объекта - компаньона , а скорее объемлющего класса. В настоящее время это рекомендуемый способ найти класс, содержащий объект-компаньон. Удаление « $ Companion » из имени с помощью removeSuffix()не работает, поскольку объектам-компаньонам могут быть заданы собственные имена.


Некоторые структуры внедрения зависимостей используют делегатов, как вы видите в другом ответе здесь. Они выглядят как `val log: Logger by injectLogger ()` и позволяют внедрять систему журналирования и неизвестны используемому коду. (Моя платформа для инъекций, показывающая это, находится на github.com/kohesive/injekt )
Джейсон Минард,

10
Спасибо за исчерпывающий ответ. Очень информативно. Мне особенно нравится реализация Property Delegates (обычная, самая элегантная) .
mchlstckl

6
Я думаю, что было изменение в синтаксисе kotlin. и ofClass.enclosingClass.kotlin.objectInstance?.javaClassвместо этого должна быть ofClass.enclosingClass.kotlin.companionObject?.java
развернута

1
ах, неважно, как указано здесь kotlinlang.org/docs/reference/reflection.html отражающая банка поставляется отдельно от stdlib, для gradle нам нужно это:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran

1
Код для создания «делегатов свойств» и «функций расширения» выглядит одинаково, за исключением типа возвращаемого значения. Пример кода для Property Delegate ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}), по-видимому, создает такую ​​функцию расширения, которая "".logger()теперь является чем-то особенным. Должно ли это вести себя так?
Майк

32

Взгляните на библиотеку 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 vs kotlin-logging

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

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


Могу ли я предложить вам изменить свой ответ , чтобы показать выход из logger.info()вызовов, как это сделал Джейсон в его общепринятом ответ.
Пауло Мерсон

7

В качестве хорошего примера реализации журналирования я хотел бы упомянуть 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")
}

Этот ответ специфичен для Android, и в вопросе не упоминалось и нет тега Android.
Джейсон Минард

@JaysonMinard почему это? Этот подход является универсальным, поскольку, например, наличие уникального тега регистрации для каждого класса полезно и в проектах, не относящихся к Android.
горячая клавиша

1
Не ясно, что вы говорите «реализовать что-то похожее на то, что сделал Anko», и вместо этого вам больше нравится «использовать Anko» ... для чего требуется библиотека Android под названием Anko. Который имеет интерфейс, который имеет функции расширения, которые вызывают android.util.Logдля ведения журнала. Каковы были ваши намерения? использовать анко? Собрать нечто подобное, используя в качестве примера Anko (лучше просто вставить предложенный код в строку и исправить его для не Android, а вместо того, чтобы сказать «перенести это на не Android, вот ссылка». Вместо этого вы добавляете пример кода звонит Анко)
Джейсон Минард

1
@JaysonMinard, спасибо за ваши комментарии, я переписал пост так, что теперь он объясняет подход, а не ссылки на Анко.
горячая клавиша

6

KISS: для команд Java, мигрирующих в Котлин

Если вы не возражаете предоставить имя класса в каждом экземпляре логгера (так же, как в java), вы можете упростить его, определив его как функцию верхнего уровня где-то в вашем проекте:

import org.slf4j.LoggerFactory

inline fun <reified T:Any> logger() = LoggerFactory.getLogger(T::class.java)

При этом используется параметр типа Kotlin reified .

Теперь вы можете использовать это следующим образом:

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, поддерживает ведение журнала с помощью Suppliers, я также добавил делегата, чтобы упростить использование этих методов.

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расширения Anyдля использования там, где thisона определена, так и именованную функцию регистратора для использования там, где не thisопределено (например, функции верхнего уровня).


1
Если я прав, вы можете избежать ввода имени класса в первом предложенном вами решении, изменив сигнатуру метода на T.logger ()
IPat

1
@ IPat Да, первое решение намеренно не делает это, чтобы оставаться близко к «пути Java». Вторая часть ответа охватывает случай расширения T.logger()- см. Нижнюю часть примера кода.
Раман

5

Анко

Вы можете использовать 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

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


4

Будет ли что-то подобное для вас?

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

1
Этот ответ требует более подробного объяснения: если спрашивающий не понимает сопутствующие объекты, он, вероятно, не дошел до делегатов и поэтому не будет знать, что он делает. Кроме того, при использовании этой модели в коде очень мало сбережений. И я сомневаюсь, что кеширование в объекте-компаньоне - это действительно выигрыш в производительности, кроме как в ограниченной системе с небольшим процессором, например Android.
Джейсон Минард

1
Этот код, приведенный выше, показывает создание класса, который действует как делегат (см. Kotlinlang.org/docs/reference/delegated-properties.html ), который является первым классом, LoggerDelegate а затем он создает функцию верхнего уровня, которая создает проще создать экземпляр делегата (не намного проще, но немного). И эта функция должна быть изменена, чтобы быть inline. Затем он использует делегата для предоставления регистратора всякий раз, когда он требуется. Но он предоставляет один для компаньона, Foo.Companionа не для класса, Fooтак что, возможно, не так, как задумано.
Джейсон Минард

@JaysonMinard Я согласен, но я оставлю ответ для будущих зрителей, которые хотят «быстрое решение» или пример того, как применить это к своим собственным проектам. Я не понимаю, почему logger()функция должна быть, inlineесли нет лямбды. IntelliJ предлагает встраивание в этом случае не требуется: i.imgur.com/YQH3NB1.png
Jire

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

1

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

val logger = Logger.getLogger("package_name")

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


Верхний уровень также известен как пакетный уровень.
Caelum

Переменная верхнего уровня похожа на фразу «использовать глобальные переменные», и я думаю, что она будет применима, только если у вас есть другие функции верхнего уровня, которые должны были использовать регистратор. Однако в этот момент может быть лучше передать регистратор любой служебной функции, которая хочет регистрироваться.
Джейсон Минард

1
@JaysonMinard Я думаю, что передача логгера в качестве параметра была бы анти-паттерном, потому что логирование никогда не должно влиять на ваш API, внешний или внутренний
voddan

Хорошо, тогда вернемся к моему вопросу, для ведения журнала на уровне класса поместите регистратор в класс, а не функцию верхнего уровня.
Джейсон Минард

1
@voddan, по крайней мере, предоставьте полный пример того, какой тип регистратора вы создаете. val log = what?!? ... создать регистратор по имени? Игнорируя тот факт, что вопрос показал, что он хотел создать регистратор для определенного классаLoggerFactory.getLogger(Foo.class);
Джейсон Минард

1

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

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

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

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


1

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

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


1

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

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

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

0

В общем, для этого нужны сопутствующие объекты: замена статического материала.


Сопутствующий объект не является статическим, это одиночный объект, который может содержать элементы, которые могут стать статическими, если вы используете JvmStaticаннотацию. И в будущем их может быть больше одного. Плюс этот ответ не очень полезен без дополнительной информации или образца.
Джейсон Минард

Я не говорил, что это было статично. Я сказал, что это для замены статики. И почему их должно быть больше одного? Это не имеет смысла. Наконец, я спешил, и я подумал, что указание в правильном направлении будет достаточно полезным.
Джейкоб Циммерман

1
Сопутствующий объект не предназначен для замены статики, но он также может сделать элементы статичными. Котлин какое-то время поддерживал больше, чем на спутнике, и позволяет им иметь другие имена. Как только вы начинаете называть их, они действуют не так, как статика. И это остается открытым в будущем, чтобы иметь больше чем одного названного компаньона. Например, один может быть, Factoryа другойHelpers
Джейсон Минард

0

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

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

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

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

val logger = getLogger { }

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

0

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

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

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

}

0

Вы можете просто создать свою собственную «библиотеку» утилит. Для этой задачи вам не нужна большая библиотека, которая сделает ваш проект более тяжелым и сложным.

Например, вы можете использовать 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)
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.