[kotlin] 코 틀린에서의 관용적 로깅 방법

Kotlin은 Java에서 사용되는 것과 같은 정적 필드 개념을 가지고 있지 않습니다. Java에서 일반적으로 허용되는 로깅 방법은 다음과 같습니다.

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

질문 은 Kotlin에서 로깅을 수행하는 관용적 방법은 무엇입니까?



답변

성숙한 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")
    }
}  

출력 생성 :

2015 년 12 월 26 일 오전 11시 28 분 32 초 org.stackoverflow.kotlin.test.MyClassfoo 정보 : MyClass의 Hello

동반자 객체에 대한 자세한 내용은 여기를 참조하십시오. Companion Objects … 또한 위의 샘플 에서는 로거 의 유형 인스턴스를 얻는 반면 MyClass::class.javatype의 인스턴스를 얻는다는 점에 유의하십시오 .Class<MyClass>this.javaClassClass<MyClass.Companion>

클래스의 인스턴스 당 (공통)

그러나 인스턴스 레벨에서 로거를 호출하고 로거를 얻는 것을 피할 이유가 없습니다. 언급 한 관용적 Java 방식은 구식이며 성능에 대한 두려움을 기반으로하지만 클래스 당 로거는 이미 지구상의 거의 모든 합리적인 로깅 시스템에 의해 캐시됩니다. 로거 오브젝트를 보유 할 멤버를 작성하십시오.

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

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

출력 생성 :

2015 년 12 월 26 일 오전 11시 28 분 44 초 org.stackoverflow.kotlin.test.MyClass foo INFO : MyClass의 Hello

인스턴스 별 및 클래스 별 변형을 모두 성능 테스트하고 대부분의 앱에 현실적인 차이가 있는지 확인할 수 있습니다.

부동산 대표 (일반적이고 가장 우아함)

@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()두 클래스를 모두 호출 한 결과는 다음과 같습니다.

2015 년 12 월 26 일 오전 11:30:55 org.stackoverflow.kotlin.test. 무언가 foo 정보 : Hello from Something

2015 년 12 월 26 일 오전 11시 30 분 55 초 org.stackoverflow.kotlin.test.SomethingElse foo INFO : 안녕하세요

확장 함수 (이 경우 네임 스페이스의 “오염”으로 인해 드문 경우)

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

출력 생성 :

2015 년 12 월 26 일 오전 11시 29 분 12 초 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO : Hello from 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()
}

출력 생성 :

2015 년 12 월 26 일 오전 11시 41 분 01 초 org.stackoverflow.kotlin.test.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!
    }
}

종합 (작은 도우미 라이브러리)

다음은 위의 옵션을 쉽게 사용할 수 있도록 도와주는 작은 도우미 라이브러리입니다. Kotlin에서는 API를 원하는대로 확장하기 위해 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 개 인스턴스는 모두 동일한 로거 이름을 생성하고 출력합니다.

2015 년 12 월 26 일 오전 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo 정보 : MixedBagOfTricks의 Hello

참고 :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 vs kotlin-logging

면책 조항 : 나는 그 도서관의 관리자입니다.

편집 : kotlin-logging은 이제 다중 플랫폼을 지원합니다 : https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support


답변

로깅 구현의 좋은 예로 로깅 이 필요한 클래스가 구현 해야하는 특수 인터페이스 를 사용하는 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매개 변수를 사용 하여 수행됩니다 .

로 전달하는 기능 handlerthrowableHandler다른 로깅 방법에 따라 다를 수 있습니다 예를 들어, 그들은 파일에 대한 로그를 작성하거나 어딘가에 업로드 할 수 있습니다. isLoggingEnabledLoggingLevels간결함을 위해 생략하지만,이를 사용하여 더 많은 유연성을 제공한다.


다음과 같은 사용법이 가능합니다.

class MyClass : MyLogger {
    fun myFun() {
        info("Info message")
    }
}

패키지 레벨 기능에 로그인하려면 로거 오브젝트가 필요합니다.

private object MyPackageLog : MyLogger

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


답변

KISS : Kotlin으로 마이그레이션하는 Java 팀

로거의 각 인스턴스화에서 클래스 이름을 제공하는 데 신경 쓰지 않으면 (자바처럼) 프로젝트의 최상위 위치로 이것을 정의하여 간단하게 유지할 수 있습니다.

import org.slf4j.LoggerFactory

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

이것은 Kotlin reified type 매개 변수를 사용합니다 .

이제 다음과 같이 사용할 수 있습니다.

class SomeClass {
  // or within a companion object for one-instance-per-class
  val log = logger<SomeClass>()
  ...
}

이 접근 방식은 매우 간단하고 Java에 해당하지만 구문 설탕을 추가합니다.

다음 단계 : 확장 또는 대리인

개인적으로 한 단계 더 나아가 확장 또는 대리자 접근 방식을 선호합니다. 이것은 @JaysonMinard의 답변에 잘 요약되어 있지만 다음은 log4j2 API를 사용한 “Delegate”접근법에 대한 TL; DR입니다 ( 업데이트 :이 코드는 공식 모듈로 출시되었으므로 더 이상 수동으로 코드를 작성할 필요가 없습니다 log4j2 프로젝트, 아래 참조). slf4j와 달리 log4j2는로 로깅을 지원하므로 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 로깅 API

이전 섹션의 대부분은 Kotlin Logging API 모듈 을 생성하도록 직접 조정되었으며 , 현재 Log4j2의 공식 부분입니다 (면책 조항 : 기본 저자입니다). ApacheMaven Central을 통해 직접 다운로드 할 수 있습니다 .

사용법 은 기본적으로 위에서 설명한대로이지만, 모듈은 인터페이스 기반 로거 액세스, 정의 된 경우 사용 loggerAny위한 확장 기능 this및 no this가 정의 된 경우를위한 명명 된 로거 기능 (예 : 최상위 기능)을 모두 지원합니다.


답변

앙코

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 project-kotlin-logging ) 라이브러리를 사용하면 다음과 같은 로깅 코드를 작성할 수 있습니다.

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

정적 로그

또는 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")

목재

당신은 이미 제이크 와튼의 사용하려는 경우 Timber로깅 라이브러리 확인 timberkt.

이 라이브러리는 Kotlin에서 사용하기 쉬운 API로 Timber를 기반으로합니다. 서식 매개 변수를 사용하는 대신 메시지가 기록 된 경우에만 평가되는 람다를 전달합니다.

코드 예 :

// Standard timber
Timber.d("%d %s", intVar + 3, stringFun())

// Kotlin extensions
Timber.d { "${intVar + 3} ${stringFun()}" }
// or
d { "${intVar + 3} ${stringFun()}" }

또한 확인 : 로깅 코 틀린 및 안드로이드 : 코 틀린 로깅 대 AnkoLogger을

그것이 도움이되기를 바랍니다.


답변

이 같은 것이 당신을 위해 일할 것입니까?

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이 다르게 나타나는 것처럼 “정신”(이디엄)과 비슷한 점이 비슷하다고 생각합니다.