Idiomatische Art, sich in Kotlin anzumelden


164

Kotlin hat nicht die gleiche Vorstellung von statischen Feldern wie in Java. In Java ist die allgemein akzeptierte Methode zur Protokollierung:

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

Die Frage ist, wie die Protokollierung in Kotlin idiomatisch durchgeführt wird.


1
Dies nicht als Antwort zu veröffentlichen, da es weit vom Java-Weg entfernt ist, aber ich habe überlegt, eine Erweiterungsfunktion für die Protokollierung auf Any zu schreiben. Sie müssen die Logger natürlich zwischenspeichern, aber ich denke, das wäre eine gute Möglichkeit, dies zu tun.
Mhlz

1
@mhlz Wäre diese Erweiterungsfunktion nicht statisch aufgelöst? Wie in, würde es nicht auf alle Objekte angewendet werden, nur auf solche vom Typ Any(also eine Besetzung erforderlich)?
Jire

1
@mhlz Eine Erweiterungsfunktion ist nicht sinnvoll, da sie keinen Status hat, um einen Logger in der Nähe zu halten. Es könnte eine Erweiterung sein, einen Logger zurückzugeben, aber warum sollte das bei jeder bekannten Klasse im System der Fall sein? Erweiterungen aktivieren Alle neigen dazu, später in der IDE zu schlampigem Rauschen zu werden. @Jire die Erweiterung gilt für alle Nachkommen von Any, gibt aber immer noch die richtige this.javaClassfür jeden zurück. Aber ich empfehle es nicht als Lösung.
Jayson Minard

Antworten:


250

In den meisten ausgereiften Kotlin-Codes finden Sie unten eines dieser Muster. Der Ansatz mit Property Delegates nutzt die Leistungsfähigkeit von Kotlin, um den kleinsten Code zu erstellen.

Hinweis: Der Code hier ist für, java.util.Loggingaber die gleiche Theorie gilt für jede Protokollierungsbibliothek

Statisch (häufig, entspricht Ihrem Java-Code in der Frage)

Wenn Sie der Leistung dieser Hash-Suche im Protokollierungssystem nicht vertrauen können, können Sie ein ähnliches Verhalten wie bei Ihrem Java-Code erzielen, indem Sie ein Begleitobjekt verwenden, das eine Instanz enthalten und sich für Sie statisch anfühlen kann.

class MyClass {
    companion object {
        val LOG = Logger.getLogger(MyClass::class.java.name) 
    }

    fun foo() {
        LOG.warning("Hello from MyClass")
    }
}  

Ausgabe erstellen:

26. Dezember 2015 11:28:32 org.stackoverflow.kotlin.test.MyClassfoo INFO: Hallo von MyClass

Mehr über Begleiter Objekte hier: Companion Objekte ... Auch , dass in der Probe beachten Sie über MyClass::class.javadie Instanz des Typs wird Class<MyClass>für den Logger, während this.javaClassdie Instanz des Typs erhalten würde Class<MyClass.Companion>.

Pro Instanz einer Klasse (gemeinsam)

Es gibt jedoch keinen Grund, das Aufrufen und Abrufen eines Loggers auf Instanzebene zu vermeiden. Die von Ihnen erwähnte idiomatische Java-Methode ist veraltet und basiert auf Leistungsangst, während der Logger pro Klasse bereits von fast jedem vernünftigen Protokollierungssystem auf dem Planeten zwischengespeichert wird. Erstellen Sie einfach ein Mitglied, das das Logger-Objekt enthält.

class MyClass {
  val LOG = Logger.getLogger(this.javaClass.name)

  fun foo() {
        LOG.warning("Hello from MyClass")
  }
} 

Ausgabe erstellen:

26. Dezember 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Hallo von MyClass

Sie können Leistungstests sowohl pro Instanz als auch pro Klasse durchführen und feststellen, ob für die meisten Apps ein realistischer Unterschied besteht.

Property Delegates (gewöhnlich, am elegantesten)

Ein anderer Ansatz, der von @Jire in einer anderen Antwort vorgeschlagen wird, besteht darin, einen Eigenschaftendelegaten zu erstellen, mit dem Sie die Logik in jeder anderen gewünschten Klasse einheitlich ausführen können. Es gibt einen einfacheren Weg, dies zu tun, da Kotlin Lazybereits einen Delegaten bereitstellt . Wir können ihn einfach in eine Funktion einschließen. Ein Trick dabei ist, dass wir, wenn wir den Typ der Klasse kennen möchten, die derzeit den Delegaten verwendet, ihn zu einer Erweiterungsfunktion für jede Klasse machen:

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"

Dieser Code stellt außerdem sicher, dass bei Verwendung in einem Companion-Objekt der Loggername mit dem Namen übereinstimmt, den Sie für die Klasse selbst verwendet haben. Jetzt können Sie einfach:

class Something {
    val LOG by logger()

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

für pro Klasseninstanz oder wenn Sie möchten, dass es mit einer Instanz pro Klasse statischer ist:

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

    }

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

Und Ihre Ausgabe vom Aufrufen dieser foo()beiden Klassen wäre:

26. Dezember 2015 11:30:55 org.stackoverflow.kotlin.test.Something foo INFO: Hallo von etwas

26. Dezember 2015 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO: Hallo von SomethingElse

Erweiterungsfunktionen (in diesem Fall aufgrund der "Verschmutzung" eines beliebigen Namespace ungewöhnlich)

Kotlin hat ein paar versteckte Tricks, mit denen Sie einen Teil dieses Codes noch kleiner machen können. Sie können Erweiterungsfunktionen für Klassen erstellen und ihnen daher zusätzliche Funktionen geben. Ein Vorschlag in den obigen Kommentaren war, Anymit einer Logger-Funktion zu erweitern. Dies kann jedes Mal zu Rauschen führen, wenn jemand in einer ID eine Code-Vervollständigung in seiner IDE verwendet. Das Erweitern Anyoder eine andere Markierungsschnittstelle hat jedoch einen geheimen Vorteil : Sie können implizieren, dass Sie Ihre eigene Klasse erweitern und somit die Klasse erkennen, in der Sie sich befinden. Huh? Um weniger verwirrend zu sein, hier ist der Code:

// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
     return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}

Jetzt kann ich innerhalb einer Klasse (oder eines Begleitobjekts) diese Erweiterung einfach für meine eigene Klasse aufrufen:

class SomethingDifferent {
    val LOG = logger()

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

Ausgabe produzieren:

26. Dezember 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Hallo von SomethingDifferent

Grundsätzlich wird der Code als Aufruf zur Erweiterung angesehen Something.logger(). Das Problem ist, dass Folgendes auch zutreffen könnte, was zu einer "Verschmutzung" anderer Klassen führt:

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

Erweiterungsfunktionen auf der Marker-Schnittstelle (nicht sicher, wie häufig, aber gemeinsames Modell für "Merkmale")

Um die Verwendung von Erweiterungen sauberer zu gestalten und die "Verschmutzung" zu verringern, können Sie eine Markierungsschnittstelle verwenden, um Folgendes zu erweitern:

interface Loggable {} 

fun Loggable.logger(): Logger {
     return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}    

Oder machen Sie die Methode mit einer Standardimplementierung zu einem Teil der Schnittstelle:

interface Loggable {
    public fun logger(): Logger {
        return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
    }
}

Und verwenden Sie eine dieser Variationen in Ihrer Klasse:

class MarkedClass: Loggable {
    val LOG = logger()
}

Ausgabe produzieren:

26. Dezember 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Hallo von MarkedClass

Wenn Sie die Schaffung eines einheitlichen Feldes zwingen wollte , den Logger zu halten, dann , während Sie diese Schnittstelle können Sie bequem die Implementierer erfordern ein Feld zu haben , wie zum Beispiel LOG:

interface Loggable {
    val LOG: Logger  // abstract required field

    public fun logger(): Logger {
        return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
    }
}

Jetzt muss der Implementierer der Schnittstelle folgendermaßen aussehen:

class MarkedClass: Loggable {
    override val LOG: Logger = logger()
}

Natürlich kann eine abstrakte Basisklasse dasselbe tun, wobei die Option, dass sowohl die Schnittstelle als auch eine abstrakte Klasse, die diese Schnittstelle implementiert, Flexibilität und Einheitlichkeit ermöglichen:

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

Alles zusammenfügen (Eine kleine Hilfsbibliothek)

Hier ist eine kleine Hilfsbibliothek, mit der Sie die oben genannten Optionen einfach verwenden können. In Kotlin ist es üblich, APIs zu erweitern, um sie Ihren Wünschen anzupassen. Entweder in Erweiterungs- oder Top-Level-Funktionen. Hier finden Sie eine Mischung mit Optionen zum Erstellen von Loggern sowie ein Beispiel mit allen Variationen:

// 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()
}

Wählen Sie die Optionen aus, die Sie behalten möchten, und hier sind alle verwendeten Optionen aufgeführt:

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

Alle 13 Instanzen der in diesem Beispiel erstellten Logger erzeugen denselben Loggernamen und dieselbe Ausgabe:

26. Dezember 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Hallo von MixedBagOfTricks

Hinweis: Die unwrapCompanionClass()Methode stellt sicher, dass kein Logger generiert wird, der nach dem Begleitobjekt benannt ist, sondern nach der einschließenden Klasse. Dies ist die derzeit empfohlene Methode, um die Klasse zu finden, die das Begleitobjekt enthält. Das Entfernen von " $ Companion " aus dem Namen mit removeSuffix()funktioniert nicht, da Companion-Objekte benutzerdefinierte Namen erhalten können.


Einige Frameworks für die Abhängigkeitsinjektion verwenden Delegaten, wie Sie in einer anderen Antwort hier sehen. Sie sehen aus wie "val log: Logger by injizLogger ()" und ermöglichen das Injizieren des Protokollierungssystems, das dem Verwendungscode unbekannt ist. (Mein Injektions-Framework zeigt dies unter github.com/kohesive/injekt )
Jayson Minard,

10
Danke für die ausführliche Antwort. Sehr informativ. Besonders gut gefällt mir die Implementierung der Property Delegates (allgemein, am elegantesten) .
mchlstckl

6
Ich denke, es gab eine Änderung in der Kotlin-Syntax. und das Auspacken sollte ofClass.enclosingClass.kotlin.objectInstance?.javaClassstattofClass.enclosingClass.kotlin.companionObject?.java
oshai

1
ah, egal , wie hier angegeben kotlinlang.org/docs/reference/reflection.html das reflektierglas wird separat von der stdlib geliefert, für gradle benötigen wir dies:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran

1
Der Code zum Erstellen der 'Property Delegates' und der 'Extension Functions' scheint bis auf den Rückgabetyp identisch zu sein. Das Codebeispiel für den Property Delegate ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) scheint eine Erweiterungsfunktion zu erstellen, "".logger()die nun eine Sache ist. Soll sich dies so verhalten?
Mike Rylander

32

Schauen Sie sich die Kotlin-Logging- Bibliothek an.
Es ermöglicht die Protokollierung wie folgt:

private val logger = KotlinLogging.logger {}

class Foo {
  logger.info{"wohoooo $wohoooo"}
}

Oder so:

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

Ich schrieb auch einen Beitrag Blog es zu vergleichen AnkoLogger: Logging in Kotlin & Android: AnkoLogger vs Kotlin-logging

Haftungsausschluss: Ich bin der Betreuer dieser Bibliothek.

Bearbeiten: kotlin-logging unterstützt jetzt mehrere Plattformen: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support


Darf ich vorschlagen, dass Sie Ihre Antwort bearbeiten, um die Ausgabe von logger.info()Anrufen anzuzeigen, wie es Jayson in seiner akzeptierten Antwort getan hat.
Paulo Merson

7

Als gutes Beispiel für die Implementierung der Protokollierung möchte ich Anko erwähnen , das eine spezielle Schnittstelle verwendet, AnkoLoggerdie eine Klasse, die protokolliert werden muss, implementieren sollte. In der Schnittstelle befindet sich Code, der ein Protokollierungs-Tag für die Klasse generiert. Die Protokollierung erfolgt dann über Erweiterungsfunktionen, die innerhalb der Interace-Implementierung ohne Präfixe oder sogar die Erstellung von Logger-Instanzen aufgerufen werden können.

Ich denke nicht, dass dies idiomatisch ist , aber es scheint ein guter Ansatz zu sein, da es minimalen Code erfordert, nur die Schnittstelle zu einer Klassendeklaration hinzuzufügen und Sie mit verschiedenen Tags für verschiedene Klassen protokollieren.


Der folgende Code ist im Grunde AnkoLogger , vereinfacht und für die Android- unabhängige Verwendung neu geschrieben.

Erstens gibt es eine Schnittstelle, die sich wie eine Markierungsschnittstelle verhält:

interface MyLogger {
    val tag: String get() = javaClass.simpleName
}

Die Implementierung kann die Erweiterungsfunktionen für MyLoggerden Code verwenden, der sie nur aufruft this. Und es enthält auch Protokollierungs-Tag.

Als nächstes gibt es einen allgemeinen Einstiegspunkt für verschiedene Protokollierungsmethoden:

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

Es wird durch Protokollierungsmethoden aufgerufen. Es erhält ein Tag von der MyLoggerImplementierung, überprüft die Protokollierungseinstellungen und ruft dann einen von zwei Handlern auf, den mit Throwableund den ohne Argument.

Anschließend können Sie so viele Protokollierungsmethoden definieren, wie Sie möchten:

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

Diese werden sowohl für die Protokollierung nur einer Nachricht Throwableals auch für die Protokollierung einer Nachricht einmal definiert. Dies erfolgt mit einem optionalen throwableParameter.

Die Funktionen, die als übergeben werden handlerund throwableHandlerfür verschiedene Protokollierungsmethoden unterschiedlich sein können, können beispielsweise das Protokoll in eine Datei schreiben oder irgendwo hochladen. isLoggingEnabledund LoggingLevelswerden der Kürze halber weggelassen, aber ihre Verwendung bietet noch mehr Flexibilität.


Es ermöglicht die folgende Verwendung:

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

Es gibt einen kleinen Nachteil: Für die Anmeldung von Funktionen auf Paketebene wird ein Logger-Objekt benötigt:

private object MyPackageLog : MyLogger

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

Diese Antwort ist Android-spezifisch, und in der Frage wurde weder ein Android-Tag erwähnt noch erwähnt.
Jayson Minard

@ JaysonMinard warum ist es? Dieser Ansatz ist ein allgemeiner Zweck, da beispielsweise ein eindeutiges Protokollierungs-Tag für jede Klasse auch in Nicht-Android-Projekten nützlich ist.
Hotkey

1
Es ist nicht klar, dass Sie sagen "Implementieren Sie etwas Ähnliches wie Anko" und stattdessen eher "Anko verwenden" ... was dann eine Android-Bibliothek namens Anko erfordert. Welches hat eine Schnittstelle, die Erweiterungsfunktionen hat, die aufrufen, android.util.Logum die Protokollierung durchzuführen. Welches war deine Absicht? Anko benutzen? Erstellen Sie etwas Ähnliches, während Sie Anko als Beispiel verwenden (es ist besser, wenn Sie nur den vorgeschlagenen Code inline setzen und ihn für Nicht-Android korrigieren, anstatt zu sagen: "Portieren Sie dies auf Nicht-Android, hier ist der Link". Stattdessen fügen Sie Beispielcode hinzu Anko anrufen)
Jayson Minard

1
@JaysonMinard, danke für deine Kommentare. Ich habe den Beitrag so umgeschrieben, dass er nun den Ansatz erklärt und nicht auf Anko verweist.
Hotkey

6

KISS: Für Java-Teams, die nach Kotlin migrieren

Wenn es Ihnen nichts ausmacht, den Klassennamen bei jeder Instanziierung des Loggers anzugeben (genau wie bei Java), können Sie dies einfach halten, indem Sie dies als Funktion der obersten Ebene irgendwo in Ihrem Projekt definieren:

import org.slf4j.LoggerFactory

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

Dies verwendet einen Kotlin- Parameter vom Typ reified .

Jetzt können Sie dies wie folgt verwenden:

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

Dieser Ansatz ist sehr einfach und kommt dem Java-Äquivalent nahe, fügt jedoch nur etwas syntaktischen Zucker hinzu.

Nächster Schritt: Erweiterungen oder Delegaten

Ich persönlich gehe lieber einen Schritt weiter und verwende den Ansatz der Erweiterungen oder Delegierten. Dies ist in der Antwort von @ JaysonMinard gut zusammengefasst, aber hier ist der TL; DR für den "Delegate" -Ansatz mit der log4j2-API ( UPDATE : Sie müssen diesen Code nicht mehr manuell schreiben, da er als offizielles Modul des veröffentlicht wurde log4j2-Projekt, siehe unten). Da log4j2 im Gegensatz zu slf4j die Protokollierung mit Supplier's unterstützt, habe ich auch einen Delegaten hinzugefügt, um die Verwendung dieser Methoden zu vereinfachen.

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-Protokollierungs-API

Der größte Teil des vorherigen Abschnitts wurde direkt angepasst, um das Kotlin Logging API- Modul zu erstellen , das jetzt offizieller Bestandteil von Log4j2 ist (Haftungsausschluss: Ich bin der Hauptautor). Sie können dies direkt von Apache oder über Maven Central herunterladen .

Die Verwendung erfolgt im Wesentlichen wie oben beschrieben, das Modul unterstützt jedoch sowohl den schnittstellenbasierten Loggerzugriff als auch eine loggerErweiterungsfunktion Anyfür die Verwendung, bei der thisdefiniert ist, und eine benannte Loggerfunktion für die Verwendung, bei der no thisdefiniert ist (z. B. Funktionen der obersten Ebene).


1
Wenn ich recht habe, können Sie vermeiden, den Klassennamen in der ersten von Ihnen bereitgestellten Lösung einzugeben, indem Sie die Methodensignatur in T.logger ()
ändern

1
@IPat yup, die erste Lösung tut dies absichtlich nicht, um in der Nähe des "Java-Weges" zu bleiben. Der zweite Teil der Antwort behandelt den Erweiterungsfall T.logger()- siehe unten im Codebeispiel.
Raman

5

Anko

Sie können die AnkoBibliothek verwenden, um dies zu tun. Sie hätten folgenden Code:

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

Kotlin-Protokollierung

Mit der Bibliothek kotlin-logging ( Github-Projekt - kotlin-logging ) können Sie Protokollierungscode wie folgt schreiben:

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

StaticLog

oder Sie können auch diese kleine in Kotlin geschriebene Bibliothek verwenden, die heißt, StaticLogdann würde Ihr Code wie folgt aussehen:

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

Die zweite Lösung ist möglicherweise besser, wenn Sie ein Ausgabeformat für die Protokollierungsmethode definieren möchten, z.

Log.newFormat {
    line(date("yyyy-MM-dd HH:mm:ss"), space, level, text("/"), tag, space(2), message, space(2), occurrence)
}

oder verwenden Sie Filter, zum Beispiel:

Log.filterTag = "filterTag"
Log.info("This log will be filtered out", "otherTag")
Log.info("This log has the right tag", "filterTag")

timberkt

Wenn Sie bereits Jake Whartons TimberProtokollierungsbibliotheksprüfung verwendet haben timberkt.

Diese Bibliothek baut auf Timber mit einer API auf, die von Kotlin aus einfacher zu verwenden ist. Anstatt Formatierungsparameter zu verwenden, übergeben Sie ein Lambda, das nur ausgewertet wird, wenn die Nachricht protokolliert wird.

Codebeispiel:

// Standard timber
Timber.d("%d %s", intVar + 3, stringFun())

// Kotlin extensions
Timber.d { "${intVar + 3} ${stringFun()}" }
// or
d { "${intVar + 3} ${stringFun()}" }

Überprüfen Sie auch: Anmelden in Kotlin & Android: AnkoLogger vs Kotlin-Protokollierung

Hoffe es wird helfen


4

Würde so etwas für Sie funktionieren?

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
Diese Antwort bedarf weiterer Erläuterungen. Wenn die fragende Person Begleitobjekte nicht versteht, hat sie wahrscheinlich keine Delegierten erreicht und weiß daher nicht, was dies tut. Außerdem werden mit diesem Modell nur sehr wenig Code eingespart. Und ich bezweifle, dass das Caching im Begleitobjekt wirklich ein Leistungsgewinn ist, außer in einem eingeschränkten System mit kleiner CPU wie Android.
Jayson Minard

1
Was dieser obige Code zeigt, ist die Erstellung einer Klasse, die als Delegat fungiert (siehe kotlinlang.org/docs/reference/delegated-properties.html ). Dies ist die erste Klasse LoggerDelegate . Anschließend wird eine Funktion der obersten Ebene erstellt Es ist einfacher, eine Instanz des Delegaten zu erstellen (nicht viel einfacher, aber ein wenig). Und diese Funktion sollte geändert werden, um zu sein inline. Anschließend wird der Delegat verwendet, um einen Logger bereitzustellen, wann immer dies gewünscht wird. Aber es bietet eine für den Begleiter Foo.Companionund nicht für die Klasse, Fooso ist es vielleicht nicht wie beabsichtigt.
Jayson Minard

@JaysonMinard Ich stimme zu, aber ich überlasse die Antwort zukünftigen Zuschauern, die eine "schnelle Lösung" oder ein Beispiel dafür wünschen, wie dies auf ihre eigenen Projekte angewendet werden kann. Ich verstehe nicht, warum die logger()Funktion sein sollte, inlinewenn keine Lambdas vorhanden sind. IntelliJ schlägt vor, dass Inlining in diesem Fall nicht erforderlich
Jire

1
Ich habe Ihre Antwort in meine aufgenommen und sie vereinfacht, indem ich die benutzerdefinierte Delegatenklasse entfernt und Lazystattdessen einen Wrapper verwendet habe. Mit einem Trick, um herauszufinden, in welcher Klasse es sich befindet.
Jayson Minard

1

Ich habe diesbezüglich keine Redewendung gehört. Je einfacher desto besser, daher würde ich eine Eigenschaft der obersten Ebene verwenden

val logger = Logger.getLogger("package_name")

Diese Praxis eignet sich gut für Python, und so unterschiedlich Kotlin und Python auch erscheinen mögen, ich glaube, sie sind sich in ihrem "Geist" (wenn man von Redewendungen spricht) ziemlich ähnlich.


Die oberste Ebene wird auch als Paketebene bezeichnet.
Caelum

Eine Variable der obersten Ebene ist wie "globale Variablen verwenden" und ich denke, sie wäre nur anwendbar, wenn Sie andere Funktionen der obersten Ebene hätten, die einen Logger verwenden müssten. Zu diesem Zeitpunkt ist es möglicherweise besser, einen Logger an eine Dienstprogrammfunktion zu übergeben, die protokollieren möchte.
Jayson Minard

1
@JaysonMinard Ich denke, Logger als Parameter zu übergeben wäre ein Anti-Pattern, da Ihre Protokollierung niemals Ihre externe oder interne API beeinflussen sollte
voddan

Ok, dann zurück zu meinem Punkt, für die Protokollierung auf Klassenebene setzen Sie den Logger in die Klasse, nicht in eine Funktion der obersten Ebene.
Jayson Minard

1
@voddan liefert mindestens ein vollständiges Beispiel dafür, welche Art von Logger Sie erstellen. val log = what?!? ... einen Logger mit Namen erstellen? Ohne die Tatsache zu beachten, dass die Frage zeigte, dass er einen Logger für eine bestimmte Klasse erstellen wollteLoggerFactory.getLogger(Foo.class);
Jayson Minard

1

Was ist stattdessen mit einer Erweiterungsfunktion für Class? Auf diese Weise erhalten Sie:

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

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

Hinweis - Ich habe dies überhaupt nicht getestet, daher ist es möglicherweise nicht ganz richtig.


1

Zunächst können Sie Erweiterungsfunktionen für die Loggererstellung hinzufügen.

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

Anschließend können Sie mit dem folgenden Code einen Logger erstellen.

private val logger1 = getLogger<SomeClass>()
private val logger2 = getLogger()

Zweitens können Sie eine Schnittstelle definieren, die einen Logger und dessen Mixin-Implementierung bereitstellt.

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)

Diese Schnittstelle kann folgendermaßen verwendet werden.

class SomeClass : LoggerAware by loggerAware<SomeClass>() {
  // Now you can use a logger here.
}

1

Erstellen Sie ein Begleitobjekt und markieren Sie die entsprechenden Felder mit der Annotation @JvmStatic


1

Hier gibt es bereits viele gute Antworten, aber alle betreffen das Hinzufügen eines Loggers zu einer Klasse. Wie würden Sie dies tun, um sich in Funktionen der obersten Ebene anzumelden?

Dieser Ansatz ist allgemein und einfach genug, um sowohl in Klassen als auch in Begleitobjekten und Funktionen der obersten Ebene gut zu funktionieren:

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

Dafür sind Begleitobjekte im Allgemeinen gedacht: statisches Material ersetzen.


Ein Begleitobjekt ist kein statisches Objekt, sondern ein Singleton, das Elemente enthalten kann, die statisch werden können, wenn Sie JvmStaticAnmerkungen verwenden. Und in Zukunft ist möglicherweise mehr als eine zulässig. Außerdem ist diese Antwort ohne weitere Informationen oder ein Beispiel nicht sehr hilfreich.
Jayson Minard

Ich habe nicht gesagt, dass es statisch ist. Ich sagte, es sei zum Ersetzen der Statik. Und warum sollte es mehr als eine geben? Das macht keinen Sinn. Schließlich hatte ich es eilig und dachte, dass es hilfreich genug wäre, in die richtige Richtung zu zeigen.
Jacob Zimmerman

1
Ein Begleitobjekt dient nicht zum Ersetzen der Statik, kann jedoch auch Elemente davon statisch machen. Kotlin unterstützte eine Zeit lang mehr als nur Begleiter und erlaubte ihnen, andere Namen zu haben. Sobald Sie sie benennen, verhalten sie sich weniger statisch. Und es bleibt in Zukunft offen, mehr als einen benannten Begleiter zu haben. Zum Beispiel könnte einer sein Factoryund ein andererHelpers
Jayson Minard

0

Slf4j Beispiel, dasselbe für andere. Dies funktioniert sogar zum Erstellen eines Loggers auf Paketebene

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

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

Verwendung:

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

Dies ist noch WIP (fast fertig), daher möchte ich es teilen: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

Das Hauptziel dieser Bibliothek besteht darin, einen bestimmten Protokollstil in einem Projekt durchzusetzen. Indem ich Kotlin-Code generieren lasse, versuche ich, einige der in dieser Frage genannten Probleme zu lösen. In Bezug auf die ursprüngliche Frage neige ich normalerweise dazu, einfach:

private val LOG = LogFormatEnforcer.loggerFor<Foo>()
class Foo {

}

0

Sie können einfach Ihre eigene "Bibliothek" von Dienstprogrammen erstellen. Für diese Aufgabe benötigen Sie keine große Bibliothek, wodurch Ihr Projekt schwerer und komplexer wird.

Sie können beispielsweise Kotlin Reflection verwenden, um den Namen, den Typ und den Wert einer Klasseneigenschaft abzurufen.

Stellen Sie zunächst sicher, dass die Meta-Abhängigkeit in Ihrem build.gradle festgelegt ist:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}

Anschließend können Sie diesen Code einfach kopieren und in Ihr Projekt einfügen:

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

Anwendungsbeispiel:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.