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

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

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

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

13 ответов


в большинстве зрелого кода Котлина вы найдете один из этих шаблонов ниже. Подход с использованием Делегаты Собственность использует силу Котлина для создания наименьшего кода.

Примечание: вот код 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 AM org.stackoverflow.kotlin.test.MyClass foo INFO: привет от MyClass

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

на экземпляр класса (common)

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

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

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

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

Dec 26, 2015 11:28: 44 am org.сайте StackOverflow.Котлин.тест.Класса MyClass ФОО INFO: привет от MyClass

вы можете проверить производительность как на экземпляр, так и на класс и посмотреть, есть ли реалистичная разница для большинства приложений.

Делегаты Собственность (общей, самый элегантный)

другой подход, который предлагается @Jire в другом ответе-создать делегат свойства, который затем можно использовать для равномерного выполнения логики в любом другом классе. Существует более простой способ сделать это, так как Котлин предоставляет 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() на обоих этих классах будет:

Dec 26, 2015 11:30: 55 am org.сайте StackOverflow.Котлин.тест.Что-то foo INFO: привет от чего-то

Дек 26, 2015 11: 30: 55 орг.сайте StackOverflow.Котлин.тест.SomethingElse фу INFO: привет от SomethingElse

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

Котлин имеет несколько скрытых трюков, которые позволяют сделать некоторые из этого кода еще меньше. Вы можете создавать функции расширения для классов и, следовательно, предоставлять им дополнительные функции. Одно из предложений в комментариях выше заключалось в расширении 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")
    }
}

производить вывод:

Dec 26, 2015 11:29: 12 am org.сайте StackOverflow.Котлин.тест.Что-то другое фу INFO: привет от 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()
}

производить выход:

Dec 26, 2015 11:41: 01 am org.сайте StackOverflow.Котлин.тест.MarkedClass foo INFO: привет от 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!
    }
}

все это вместе (небольшая вспомогательная библиотека)

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

Dec 26, 2015 11:39: 00 am org.сайте StackOverflow.Котлин.тест.MixedBagOfTricks foo INFO: привет от MixedBagOfTricks

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


посмотреть Котлин-ведение журнала библиотека.
Это позволяет регистрировать так:

private val logger = KotlinLogging.logger {}

class Foo {
  logger.info{"wohoooo $wohoooo"}
}

или так:

class FooWithLogging {
  companion object: KLogging()
  fun bar() {
    logger.info{"wohoooo $wohoooo"}
  }
}

Я также написал сообщение в блоге, сравнивая его с AnkoLogger: вход в Котлин и Android: AnkoLogger vs Котлин-вход

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

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


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

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


Код ниже в основном 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 также, это сделано с опционным .

функции, которые передаются как handler и throwableHandler может отличаться для разных методов ведения журнала, например, они могут записать журнал в файл или загрузить его куда-нибудь. isLoggingEnabled и LoggingLevels опущены для краткости, но использование их обеспечивает даже больше гибкость.


Это позволяет для следующего использования:
class MyClass : MyLogger {
    fun myFun() {
        info("Info message")
    }
}

есть небольшой недостаток: объект logger будет необходим для входа в функции уровня пакета:

private object MyPackageLog : MyLogger

fun myFun() {
    MyPackageLog.info("Info message")
}

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

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

Анко

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

class MyActivity : Activity(), AnkoLogger {
    private fun someMethod() {
        info("This is my first app and it's awesome")
        debug(1234) 
        warn("Warning")
    }
}

Котлин-ведение журнала

Котлин-лесозаготовки(проект Github-Котлин-ведение журнала ) библиотека позволяет писать код журнала, как показано ниже:

class FooWithLogging {
  companion object: KLogging()
  fun bar() {
    logger.info{"Item $item"}
  }
}

StaticLog

или вы также можете использовать этот небольшой написанный в библиотеке Котлина под названием 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()}" }

Смотрите также: вход в Котлин и Android: AnkoLogger vs Котлин-вход

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


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

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

import org.slf4j.LoggerFactory

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

это использует Котлин параметр типа reified.

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

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

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

val logger = Logger.getLogger("package_name")

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


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

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

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

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


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

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

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


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


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

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

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

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

val logger = getLogger { }

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