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.MyClass
foo 정보 : MyClass의 Hello
동반자 객체에 대한 자세한 내용은 여기를 참조하십시오. Companion Objects … 또한 위의 샘플 에서는 로거 의 유형 인스턴스를 얻는 반면 MyClass::class.java
type의 인스턴스를 얻는다는 점에 유의하십시오 .Class<MyClass>
this.javaClass
Class<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
매개 변수를 사용 하여 수행됩니다 .
로 전달하는 기능 handler
과 throwableHandler
다른 로깅 방법에 따라 다를 수 있습니다 예를 들어, 그들은 파일에 대한 로그를 작성하거나 어딘가에 업로드 할 수 있습니다. isLoggingEnabled
과 LoggingLevels
간결함을 위해 생략하지만,이를 사용하여 더 많은 유연성을 제공한다.
다음과 같은 사용법이 가능합니다.
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의 공식 부분입니다 (면책 조항 : 기본 저자입니다). Apache 나 Maven Central을 통해 직접 다운로드 할 수 있습니다 .
사용법 은 기본적으로 위에서 설명한대로이지만, 모듈은 인터페이스 기반 로거 액세스, 정의 된 경우 사용 logger
을 Any
위한 확장 기능 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이 다르게 나타나는 것처럼 “정신”(이디엄)과 비슷한 점이 비슷하다고 생각합니다.