web-dev-qa-db-ja.com

Kotlinでのロギングの慣用的な方法

Kotlinには、Javaで使用される静的フィールドと同じ概念はありません。 Javaでは、一般的に受け入れられているロギングの方法は次のとおりです。

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

質問 Kotlinでロギングを実行する慣用的な方法は何ですか?

140
mchlstckl

成熟したKotlinコードの大部分では、これらのパターンの1つが以下にあります。 Property Delegatesを使用するアプローチは、Kotlinのパワーを利用して最小のコードを生成します。

注:ここのコードはJava.util.Logging用ですが、同じ理論がすべてのロギングライブラリに適用されます

Static-like(共通、質問内の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 AM org.stackoverflow.kotlin.test.MyClass foo情報:MyClassからこんにちは

ここでコンパニオンオブジェクトの詳細: コンパニオンオブジェクト ...上記のサンプルでは、​​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")
  }
} 

出力の作成:

2015年12月26日11:28:44 AM org.stackoverflow.kotlin.test.MyClass foo情報:MyClassからこんにちは

インスタンスごとおよびクラスごとのバリエーションの両方でパフォーマンステストを行い、ほとんどのアプリで現実的な違いがあるかどうかを確認できます。

プロパティデリゲート(共通、最もエレガント)

別の回答で@Jireによって提案されている別のアプローチは、プロパティデリゲートを作成することです。これを使用して、必要な他のクラスでロジックを均一に実行できます。 KotlinはすでにLazyデリゲートを提供しているため、これを行う簡単な方法があります。関数にラップするだけです。ここでの秘Oneの1つは、デリゲートを現在使用しているクラスのタイプを知りたい場合、それを任意のクラスの拡張関数にすることです。

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

クラスごとのインスタンス、またはクラスごとに1つのインスタンスでより静的にしたい場合:

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.Something foo INFO:Hello from Something

2015年12月26日11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo情報:SomethingElseからこんにちは

拡張機能(任意のネームスペースの「汚染」のため、この場合はまれです)

Kotlinには、このコードの一部をさらに小さくできる隠されたトリックがいくつかあります。クラスに拡張関数を作成して、追加機能を付与できます。上記のコメントの1つの提案は、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情報: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!
    }
}

Putting it All Together(小さなヘルパーライブラリ)

上記のオプションを使いやすくするための小さなヘルパーライブラリを次に示します。 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個のインスタンスはすべて、同じロガー名と出力を生成します。

2015年12月26日11:39:00 AM org.stackoverflow.kotlin.test.MixedBagOfTricks foo情報:MixedBagOfTricksからこんにちは

注:unwrapCompanionClass()メソッドにより、コンパニオンオブジェクトにちなんで名付けられたロガーが生成されるのではなく、クラスを囲むクラスが生成されます。これは、コンパニオンオブジェクトを含むクラスを見つけるための現在推奨されている方法です。 removeSuffix()を使用して名前から「$ Companion」を除去しても、コンパニオンオブジェクトにカスタム名を付けることができるため機能しません。

221
Jayson Minard

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

26
oshai

KISS:JavaチームのKotlinへの移行

ロガーの各インスタンス化でクラス名を提供することを気にしない場合(Javaのように)、これをプロジェクトのどこかでトップレベル関数として定義することにより、単純に保つことができます。

import org.slf4j.LoggerFactory

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

これは、Kotlin reified type parameter を使用します。

これで、これを次のように使用できます。

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

このアプローチは非常にシンプルでJavaに近いものですが、構文的な砂糖を追加するだけです。

次のステップ:拡張機能またはデリゲート

個人的には、さらに一歩進んで、拡張機能またはデリゲートのアプローチを使用することを好みます。これは@JaysonMinardの答えにうまくまとめられていますが、log4j2 APIを使用した「デリゲート」アプローチのTL; DR(UPDATE:no 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
  }
}

Log4j2 Kotlin Logging API

前のセクションのほとんどは、 Kotlin Logging API モジュールを生成するように直接調整されており、Log4j2の公式部分になりました(免責事項:私は主著者)。これは Apacheから直接 または Maven Central からダウンロードできます。

使用法 は基本的に上記のとおりですが、モジュールはインターフェイスベースのロガーアクセス、loggerが定義されている場所で使用するAnythis拡張機能の両方をサポートします。 thisが定義されていない場合に使用する名前付きロガー関数(トップレベル関数など)。

6
Raman

あんこ

Anko libraryを使用して実行できます。次のようなコードがあります:

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

kotlin-logging

kotlin-logging( Githubプロジェクト-kotlin-logging )ライブラリを使用すると、次のようなロギングコードを記述できます。

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

StaticLog

または、 StaticLog と呼ばれるKotlinライブラリで書かれたこの小さなコードを使用することもできます。コードは次のようになります。

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

2番目の解決策は、次のようなロギングメソッドの出力形式を定義する場合に適しています。

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

ティンバークト

すでにJake Whartonの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()}" }

次も確認してください: KotlinとAndroidでのログ:AnkoLogger vs kotlin-logging

それが役立つことを願っています

4
piotrek1543

このような何かがあなたのために働くだろうか?

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

代わりにクラスの拡張機能はどうですか?そのようにして、次のようになります。

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

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

注-私はこれをまったくテストしていないので、まったく正しくない可能性があります。

1
Graham

この点でイディオムは聞いていません。シンプルなほど良いので、トップレベルのプロパティを使用します

val logger = Logger.getLogger("package_name")

このプラクティスはPythonでうまく機能し、KotlinとPythonが異なるように見えますが、「スピリット」(イディオムと言えば)でもかなり似ていると思います。

1
voddan

コンパニオンオブジェクトを作成し、@ JvmStaticアノテーションで適切なフィールドをマークします

1
cleaning agent

最初に、ロガーを作成するための拡張機能を追加できます。

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

ここには多くの素晴らしい回答がありますが、それらはすべてロガーをクラスに追加することに関するものですが、トップレベル関数でログを記録するにはどうしますか?

このアプローチは、クラス、コンパニオンオブジェクト、およびトップレベル関数の両方で適切に機能するための汎用的でシンプルなものです。

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
Niel de Wet

これはまだ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
Leandro

他のSlf4jの例も同じです。これは、パッケージレベルのロガーを作成する場合にも機能します。

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

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

使用法:

val logger = getLogger { }
0
Liu Dong

一般に、これがコンパニオンオブジェクトの目的です。静的なものを置き換えることです。

0
Jacob Zimmerman
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
tritot