1. ホーム
  2. kotlin

[解決済み] Kotlinでログを取るための慣用的な方法

2022-04-20 07:13:45

質問

Kotlinには、Javaで使われているような静的フィールドの概念がありません。Javaでは、ロギングの方法として一般的に受け入れられています。

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

質問 は、Kotlinでロギングを実行するイディオム的な方法は何ですか?

どのように解決するのですか?

成熟したKotlinのコードの大部分には、以下のパターンのいずれかが見受けられます。 を使ったアプローチ プロパティ・デレゲート は、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 フー INFO MyClass からこんにちは

コンパニオン・オブジェクトの詳細はこちら コンパニオン・オブジェクト ... また、上記のサンプルでは MyClass::class.java 型のインスタンスを取得します。 Class<MyClass> をロガーに使用するのに対し this.javaClass のインスタンスを取得します。 Class<MyClass.Companion> .

クラスのインスタンスごと (共通)

しかし、インスタンスレベルでロガーを呼び出して取得することを避ける理由は、実は何もありません。 一方、クラスごとのロガーは、地球上のほとんどすべての合理的なロギング・システムによってすでにキャッシュされています。 ロガーオブジェクトを保持するメンバを作成するだけです。

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 INFOです。MyClass からこんにちは

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

プロパティ・デレゲート (一般的な、最もエレガントな)

もうひとつのアプローチは、別の回答で@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")
    }
}

をクラスごとのインスタンスに、あるいはクラスごとに1つのインスタンスでより静的なものにしたい場合。

class SomethingElse {
    companion object {
        val LOG by logger()

    }

    fun foo() {
        LOG.info("Hello from SomethingElse")
    }
}

を呼び出すと、その出力は foo() をこの両方のクラスで使用すると、次のようになります。

2015年12月26日 11:30:55 AM org.stackoverflow.kotlin.test.Something foo INFOです。サムシングからこんにちは

Dec 26, 2015 11:30:55 AM org.stackoverflow.kotlin.test.SomethingElse foo INFOです。SomethingElseからこんにちは

拡張機能 (この場合、名前空間が汚染されるため、一般的ではありません。)

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

出力を出すこと。

<ブロッククオート

Dec 26, 2015 11:29:12 AM org.stackoverflow.kotlin.test.SomethingDifferent foo INFOです。SomethingDifferentからこんにちは

基本的に、このコードは拡張機能の呼び出しと見なされている Something.logger() . 問題は、他のクラスで "pollution" を作成する次のようなこともあり得るということです。

val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()

マーカインターフェイスの拡張機能 (一般的かどうかは不明ですが、"trait"の共通モデル)

拡張機能の使用をよりクリーンにして "pollution" を減らすために、拡張にマーカー・インターフェースを使用することができます。

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.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を拡張して自分の好みに合うようにするのが一般的である。 拡張やトップレベルの関数で。 ここでは、ロガーの作成方法の選択肢と、すべてのバリエーションを示すサンプルを提供するために混在しています。

// 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.kotlin.test.MixedBagOfTricks foo INFOです。MixedBagOfTricks からこんにちは。

unwrapCompanionClass() メソッドを使用すると、コンパニオン・オブジェクトの名前ではなく、包含するクラスの名前を持つロガーを確実に生成することができます。 これは、コンパニオン・オブジェクトを含むクラスを見つけるために、現在推奨されている方法です。 ストリッピング "。 $Companion を使用して名前から "を削除します。 removeSuffix() というのは、コンパニオン・オブジェクトにはカスタム名を付けることができるからです。