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


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拡張機能は、ロガーを保持する状態がないため、意味がありません。これはロガーを返す拡張機能である可能性がありますが、なぜシステム内のすべての既知のクラスにそれがあるのでしょうか。Anyに拡張機能を配置すると、後でIDEでずさんなノイズになる傾向があります。@Jire拡張はAnyのすべての子孫に適用されthis.javaClass、それぞれの正しい子を返します。しかし、私はそれを解決策としてお勧めしません。
Jayson Minard、2015

回答:


250

成熟したKotlinコードの大部分では、これらのパターンの1つを以下に示します。プロパティデリゲートを使用するアプローチは、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 AM org.stackoverflow.kotlin.test.MyClassfoo INFO:MyClassからこんにちは

ここでコンパニオンオブジェクトの詳細:コンパニオンオブジェクト ...また、上記のサンプルでは、ロガーのタイプのインスタンスを取得するのに対し、MyClass::class.javaタイプのインスタンスを取得することに注意してください。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:Hello from MyClass

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

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

@Jireが別の回答で提案している別のアプローチは、プロパティデリゲートを作成することです。これを使用して、必要な他のクラスでロジックを均一に実行できます。KotlinはLazy既にデリゲートを提供しているため、これを行う簡単な方法があります。これを関数にラップするだけです。ここでの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 INFO:Hello from 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

基本的に、コードはextensionの呼び出しと見なされ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 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO:Hello from 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 INFO:Hello from MixedBagOfTricks

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


一部の依存関係注入フレームワークは、ここで別の回答にあるようにデリゲートを使用します。それらは `val log:Logger by injectLogger()`のように見え、ロギングシステムが挿入され、使用中のコードに認識されないようにします。(これを示す私のインジェクションフレームワークはgithub.com/kohesive/injektにあります
Jayson Minard

10
幅広い回答ありがとうございます。とても有益です。私は特にプロパティデリゲート(一般的で最もエレガントな)実装が好き です。
mchlstckl 2015

6
kotlin構文に変更があったと思います。アンラップのofClass.enclosingClass.kotlin.objectInstance?.javaClass代わりにofClass.enclosingClass.kotlin.companionObject?.java
oshai 2016

1
ああ、気にしないで、ここに述べたようにkotlinlang.org/docs/reference/reflection.html Gradleのために我々はこれを必要とする、jarファイルがSTDLIBとは別に出荷され反映:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
ホアントラン

1
「プロパティデリゲート」と「拡張関数」を作成するためのコードは、戻り値の型を除いて同じように見えます。プロパティデリゲート(public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}})のコードサンプルは拡張機能を作成しているように見えますが、"".logger()これはこのように動作するはずですか?
マイク・ライランダー2017

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

私はそれと比較するブログ投稿も書きましたAnkoLoggerKotlinとAndroidでのロギング:AnkoLoggerと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引数のあるハンドラと引数のないハンドラの2つのうちの1つを呼び出します。

次に、次のように、好きなだけロギングメソッドを定義できます。

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さまざまなロギング方法で異なる可能性がある関数。たとえば、ログをファイルに書き込んだり、どこかにアップロードしたりできます。isLoggingEnabledそしてLoggingLevels簡潔にするため省略しますが、それらを使用すると、より柔軟性を提供しています。


次の使用法が可能です。

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が行ったのと同じようなものを実装する」と言っているのは明らかではありませんが、代わりに「Use Anko」のように見えます。その場合、AnkoというAndroidライブラリが必要です。android.util.Logロギングを行うために呼び出す拡張関数を持つインターフェースを持っています。あなたの意図はどちらですか?アンコを使う?Ankoを例として使用して同様のビルドを行います(「これをAndroid以外に移植してください、ここにリンクがあります」と言うのではなく、提案されたコードをインラインにしてAndroid以外で修正するとよいでしょう。代わりにサンプルコードを追加します
Ankoに

1
@JaysonMinard、コメントありがとうございます。Ankoを参照するのではなく、アプローチを説明するように投稿を書き直しました。
ホットキー2015

6

KISS:Kotlinに移行するJavaチーム向け

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

import org.slf4j.LoggerFactory

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

これはKotlinの具体化された型パラメーターを使用します

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

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

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

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

私は個人的に、さらに一歩進んで、拡張機能またはデリゲートアプローチを使用することを好みます。これは@JaysonMinardの回答にうまくまとめられていますが、log4j2 APIを使用した「デリゲート」アプローチのTL; DRです(更新:このコードは、 log4j2プロジェクト、下記参照)。slf4jとは異なり、log4j2はSuppliers によるロギングをサポートしているため、これらのメソッドをより簡単に使用できるようにデリゲートも追加しました。

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経由でダウンロードできます。

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


1
私が正しい場合は、メソッドのシグネチャをT.logger()に変更することで、最初のソリューションでクラス名を入力しなくても済むようになります
IPat

1
@IPat yup、最初の解決策は、意図的に「Javaの方法」に近づくことを意図的に行わない。回答の2番目の部分は拡張のケースをカバーしていますT.logger()-コードサンプルの下部を参照してください。
ラマン

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プロジェクト-kotlin-logging)ライブラリを使用すると、以下のようなロギングコードを記述できます。

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

StaticLog

または、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")\

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

Timberkt

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とkotlin-logging

それが役に立てば幸い


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
上記のコードが示しているのは、最初のクラスであるデリゲート(kotlinlang.org/docs/reference/delegated-properties.htmlを参照)として機能するクラスの作成であり、LoggerDelegate 次に、デリゲートのインスタンスを作成する方が簡単です(それほど簡単ではありませんが、少しです)。そして、その関数はに変更する必要がありますinline。次に、デリゲートを使用して、必要なときにロガーを提供します。ただしFoo.Companion、クラスでFooはなくコンパニオンに1つを提供するため、意図したとおりではない可能性があります。
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
@JaysonMinardロガーをパラメーターとして渡すことはアンチパターンになると思います。ロギングがAPIの外部または内部に影響を与えることは決してないからです
voddan

OK、それから私のポイントに戻ります。クラスレベルのロギングでは、ロガーをクラスに配置します。トップレベルの関数ではありません。
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リフレクションを使用して、任意のクラスプロパティの名前、タイプ、値を取得できます。

まず、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.