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


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 확장 기능은 로거를 유지할 상태가 없으므로 의미가 없습니다. 로거를 반환하는 것이 확장 일 수는 있지만 시스템의 알려진 모든 클래스에 왜 있습니까? IDE에서 확장 기능을 사용하는 것은 나중에 IDE에서 조잡한 노이즈가되는 경향이 있습니다. @Jire 확장은 Any의 모든 자손에게 적용되며 여전히 올바른 것을 반환합니다 this.javaClass. 그러나 솔루션으로 권장하지 않습니다.
Jayson Minard

답변:


250

성숙한 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()작동하지 않습니다.


일부 의존성 주입 프레임 워크는 다른 답변에서 볼 수 있듯이 위임을 사용합니다. 그것들은`val log : injectLogger ()에 의한 로거`처럼 보이며 로깅 시스템이 주입 코드에 주입되고 알려지지 않도록합니다. (내 주입 틀이 도시에있을 github.com/kohesive/injekt )
제이슨 Minard

10
광범위한 답변에 감사드립니다. 매우 유익합니다. 나는 특히 부동산 대표 (일반적이고 가장 우아한) 구현을 좋아합니다 .
mchlstckl

6
나는 kotlin 구문에 변화가 있다고 생각합니다. unwrap ofClass.enclosingClass.kotlin.objectInstance?.javaClass대신ofClass.enclosingClass.kotlin.companionObject?.java
oshai

1
아, 신경 쓰지, 여기에 명시된 kotlinlang.org/docs/reference/reflection.html (가) Gradle을 위해 우리가이 필요 항아리는 다음 stdlib 별도로 제공됩니다 반영 :compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
호앙 트란

1
'Property Delegates'및 'Extension Functions'를 작성하는 코드는 리턴 유형을 제외하고 동일한 것으로 보입니다. Property Delegate ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) 의 코드 샘플 "".logger()이 이제 확장 기능을 생성하는 것으로 보이며 이제는 이런 방식으로 작동합니까?
Mike Rylander

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()제이슨 그의 허용 대답에 그랬던 것처럼 전화를.
Paulo Merson

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매개 변수를 사용 하여 수행됩니다 .

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


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

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

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

private object MyPackageLog : MyLogger

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

이 답변은 Android 전용이며 질문에 Android 태그가 언급되어 있지 않습니다.
Jayson Minard

@JaysonMinard 왜 그렇습니까? 예를 들어 모든 클래스에 고유 한 로깅 태그를 갖는 것이 Android 이외의 프로젝트에서도 유용하기 때문에이 방법은 일반적인 용도입니다.
핫키

1
"Anko와 비슷한 기능을 구현하고있다"는 말이 아니라 "Anko 사용"과 비슷해 보인다. Anko라는 안드로이드 라이브러리가 필요하다. android.util.Log로깅을 수행하기 위해 호출 하는 확장 기능이있는 인터페이스 가 있습니다. 당신의 의도는 무엇입니까? Anko를 사용합니까? Anko를 예제로 사용하는 동안 비슷한 것을 빌드하십시오 (제안 된 코드를 인라인으로 넣고 "Android가 아닌 포트로 포팅, 링크가 있습니다"라고 말하는 대신 Android 이외의 코드로 수정하는 것이 좋습니다. 대신 샘플 코드를 추가하십시오. Call Anko)
Jayson Minard

1
@JaysonMinard, 귀하의 의견에 감사드립니다. 게시물을 다시 작성하여 Anko를 참조하는 대신 접근 방식을 설명했습니다.
핫키

6

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가 정의 된 경우를위한 명명 된 로거 기능 (예 : 최상위 기능)을 모두 지원합니다.


1
내가 옳다 경우, 당신은 당신이 T.logger ()에 메소드 서명을 변경하여 제공하는 최초의 솔루션에서 클래스 이름 알아 피하기 입력 할 수있어
IPAT

1
@IPat yup, 첫 번째 솔루션은 의도적으로 "자바 방식"에 가깝게 유지하지 않습니다. 답변의 두 번째 부분은 확장 사례를 다루고 T.logger()있습니다. 코드 샘플의 하단을 참조하십시오.
Raman

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

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


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와 같은 작은 CPU가있는 제한된 시스템 이외의 성능 향상이 아닌지 의심합니다.
Jayson Minard

1
이 코드가 보여주는 것은 첫 번째 클래스 인 Delegate ( kotlinlang.org/docs/reference/delegated-properties.html 참조 ) 역할을하는 클래스를 LoggerDelegate 만드는 것입니다. 대리자의 인스턴스를 만드는 것이 더 쉽습니다 (훨씬 쉽지는 않지만 조금). 그리고 그 기능은로 변경되어야합니다 inline. 그런 다음 대리자를 사용하여 원할 때마다 로거를 제공합니다. 그러나 그것은 Foo.Companion반원 Foo이 아닌 동반자를 위해 하나를 제공 하므로 의도 된 것과는 다릅니다.
Jayson Minard

@JaysonMinard 동의하지만 "빠른 해결"을 원하거나 향후 자신의 프로젝트에 적용하는 방법에 대한 예를 원하는 미래 시청자에게 답을 남겨 드리겠습니다. 람다가 없으면 logger()함수가 왜 필요한지 이해하지 못합니다 inline. IntelliJ는이 경우 인라인이 불필요하다고 제안합니다. i.imgur.com/YQH3NB1.png
Jire

1
귀하의 답변을 광산에 통합하고 사용자 지정 대리자 클래스를 제거하고 Lazy대신 래퍼를 사용하여 단순화했습니다 . 그것이 어떤 클래스에 있는지 알 수있는 트릭.
Jayson Minard

1

나는 이와 관련하여 관용구가 없다고 들었습니다. 더 단순할수록 최상위 속성을 사용합니다.

val logger = Logger.getLogger("package_name")

이 연습은 Python에서 잘 작동하며 Kotlin과 Python이 다르게 나타나는 것처럼 "정신"(이디엄)과 비슷한 점이 비슷하다고 생각합니다.


최상위는 패키지 수준이라고도합니다.
Caelum

최상위 변수는 "전역 변수 사용"이라고 말하는 것과 같으며 로거를 사용해야하는 다른 최상위 함수가있는 경우에만 적용 할 수 있다고 생각합니다. 이 시점에서 로거를 로그하려는 유틸리티 함수에 전달하는 것이 좋습니다.
Jayson Minard

1
나는 당신의 기록은 외부 또는 내부 사용자의 API에 영향을해서는 안하기 때문에, 안티 패턴 것 매개 변수로 로거를 통과 생각 @JaysonMinard
voddan

클래스 레벨 로깅을 위해 로거를 최상위 함수가 아닌 클래스에 넣습니다.
Jayson Minard

1
@voddan은 최소한 어떤 로거 유형을 작성하는지에 대한 완전한 예를 제공합니다. val log = what?!? ... 이름으로 로거를 만드는가? 질문이 특정 클래스에 대한 로거를 만들고 싶어한다는 사실을 무시하고LoggerFactory.getLogger(Foo.class);
Jayson Minard

1

대신 클래스의 확장 함수는 어떻습니까? 그런 식으로 당신은 결국 :

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

컴패니언 객체를 만들고 @JvmStatic 주석으로 적절한 필드를 표시하십시오.


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주석 을 사용하는 경우 정적이 될 수있는 멤버를 보유 할 수있는 싱글 톤입니다 . 그리고 앞으로는 둘 이상이 허용 될 수 있습니다. 또한이 답변은 자세한 정보 나 샘플이 없으면별로 도움이되지 않습니다.
Jayson Minard

나는 그것이 정적이라고 말하지 않았다. 나는 정적을 대체하기위한 것이라고 말했다. 왜 둘 이상이 허용됩니까? 말이되지 않습니다. 마지막으로 서두르고 올바른 방향을 가리키는 것이 도움이 될 것이라고 생각했습니다.
Jacob Zimmerman

1
컴패니언 객체는 스태틱을 대체하기위한 것이 아니라 요소를 정적으로 만들 수도 있습니다. Kotlin은 동료보다 한동안 더 많은 것을 지원했으며 다른 이름을 가질 수있었습니다. 일단 이름을 지정하면 정적처럼 작동하지 않습니다. 그리고 앞으로는 둘 이상의 지명 된 동반자가있을 수 있습니다. 예를 들어, 하나는 Factory다른 것일 수 있습니다Helpers
Jayson Minard

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)
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.