Modo idiomatico di accedere a Kotlin


164

Kotlin non ha la stessa nozione di campi statici utilizzata in Java. In Java, il modo generalmente accettato di effettuare la registrazione è:

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

La domanda è qual è il modo idiomatico di eseguire il logging in Kotlin?


1
Non pubblicarlo come una risposta perché è lontano dal modo Java, ma ho considerato di scrivere una funzione di estensione su Any per la registrazione. Ovviamente devi memorizzare nella cache i logger, ma penso che sarebbe un bel modo per farlo.
mhlz,

1
@mhlz La funzione di estensione non verrebbe risolta staticamente? Come in, non sarebbe applicato a tutti gli oggetti, solo a quelli di tipo Any(che necessitano quindi di un cast)?
Jire,

1
@mhlz una funzione di estensione non ha senso perché non avrà lo stato per tenere un logger in giro. Potrebbe essere un'estensione per restituire un logger, ma perché farlo su ogni classe nota nel sistema? Mettere le estensioni su Qualsiasi tende a diventare un rumore sciatto nell'IDE in seguito. @Jire l'estensione si applicherà a tutti i discendenti di Any, restituirà comunque il corretto this.javaClassper ciascuno. Ma non lo sto raccomandando come soluzione.
Jayson Minard,

Risposte:


251

Nella maggior parte del codice Kotlin maturo, troverai uno di questi schemi qui sotto. L'approccio che utilizza i delegati di proprietà sfrutta la potenza di Kotlin per produrre il codice più piccolo.

Nota: il codice qui è per java.util.Loggingma la stessa teoria si applica a qualsiasi libreria di registrazione

Static-like (comune, equivalente del codice Java nella domanda)

Se non puoi fidarti delle prestazioni di quella ricerca di hash all'interno del sistema di registrazione, puoi ottenere un comportamento simile al tuo codice Java usando un oggetto associato che può contenere un'istanza e sembrare statico per te.

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

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

creazione di output:

26 dic 2015 11:28:32 org.stackoverflow.kotlin.test.MyClassfoo INFO: Ciao da MyClass

Maggiori informazioni sugli oggetti associati qui: Oggetti Companion ... Si noti inoltre che nell'esempio precedente MyClass::class.javaottiene l'istanza di tipo Class<MyClass>per il logger, mentre this.javaClassotterrebbe l'istanza di tipo Class<MyClass.Companion>.

Per istanza di una classe (comune)

Ma non c'è davvero alcun motivo per evitare di chiamare e ottenere un logger a livello di istanza. Il modo idiomatico di Java che hai citato è obsoleto e basato sulla paura delle prestazioni, mentre il logger per classe è già memorizzato nella cache da quasi ogni ragionevole sistema di registrazione sul pianeta. Basta creare un membro per contenere l'oggetto logger.

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

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

creazione di output:

26 dic 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Hello from MyClass

Puoi testare le prestazioni sia per istanza che per variazioni di classe e vedere se esiste una differenza realistica per la maggior parte delle app.

Delegati di proprietà (comuni, i più eleganti)

Un altro approccio, suggerito da @Jire in un'altra risposta, è quello di creare un delegato di proprietà, che è quindi possibile utilizzare per eseguire la logica in modo uniforme in qualsiasi altra classe desiderata. C'è un modo più semplice per farlo poiché Kotlin fornisce Lazygià un delegato, possiamo semplicemente inserirlo in una funzione. Un trucco qui è che se vogliamo conoscere il tipo di classe che attualmente utilizza il delegato, ne facciamo una funzione di estensione su qualsiasi classe:

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"

Questo codice assicura inoltre che se lo si utilizza in un oggetto associato, il nome del logger sarà lo stesso di quello utilizzato per la classe stessa. Ora puoi semplicemente:

class Something {
    val LOG by logger()

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

per istanza per classe o se si desidera che sia più statico con un'istanza per classe:

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

    }

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

E il tuo output dal chiamare foo()entrambe queste classi sarebbe:

26 dic 2015 11:30:55 org.stackoverflow.kotlin.test.Qualcosa di foo INFO: Hello from Something

26 dic 2015 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO: Hello from SomethingElse

Funzioni di estensione (non comuni in questo caso a causa dell '"inquinamento" di qualsiasi spazio dei nomi)

Kotlin ha alcuni trucchi nascosti che ti consentono di ridurre ulteriormente questo codice. È possibile creare funzioni di estensione sulle classi e quindi fornire loro funzionalità aggiuntive. Un suggerimento nei commenti sopra era di estendere Anycon una funzione logger. Questo può creare rumore ogni volta che qualcuno utilizza il completamento del codice nel proprio IDE in qualsiasi classe. Ma c'è un vantaggio segreto nell'estensione Anyo in qualche altra interfaccia marker: puoi implicare che stai estendendo la tua classe e quindi rilevare la classe in cui ti trovi. Eh? Per essere meno confuso, ecco il codice:

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

Ora all'interno di una classe (o oggetto associato), posso semplicemente chiamare questa estensione sulla mia classe:

class SomethingDifferent {
    val LOG = logger()

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

Produzione di output:

26 dic 2015 11:29:12 org.stackoverflow.kotlin.test.Qualcosa di diverso foo INFO: Hello from SomethingDifferent

Fondamentalmente, il codice è visto come una chiamata all'estensione Something.logger(). Il problema è che ciò che segue potrebbe anche essere vero creando "inquinamento" su altre classi:

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

Funzioni di estensione su Marker Interface (non sono sicuro di quanto comune, ma modello comune per "tratti")

Per rendere più pulito l'uso delle estensioni e ridurre "l'inquinamento", è possibile utilizzare un'interfaccia marcatore per estendere:

interface Loggable {} 

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

O anche rendere il metodo parte dell'interfaccia con un'implementazione predefinita:

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

E usa una di queste variazioni nella tua classe:

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

Produzione di output:

26 dic 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Hello from MarkedClass

Se si desidera forzare la creazione di un campo uniforme per contenere il logger, durante l'utilizzo di questa interfaccia è possibile richiedere facilmente all'implementatore un campo come LOG:

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

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

Ora l'implementatore dell'interfaccia deve assomigliare a questo:

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

Naturalmente, una classe base astratta può fare lo stesso, avendo l'opzione sia dell'interfaccia sia di una classe astratta che implementa tale interfaccia consente flessibilità e uniformità:

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

Mettere tutto insieme (una piccola libreria di supporto)

Ecco una piccola libreria di helper per rendere facile usare una delle opzioni sopra. È comune in Kotlin estendere le API per renderle più di tuo gradimento. Nelle funzioni di estensione o di livello superiore. Ecco un mix per darti opzioni su come creare logger e un esempio che mostra tutte le varianti:

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

Scegli quello che vuoi conservare, e qui ci sono tutte le opzioni in uso:

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

Tutte le 13 istanze dei logger creati in questo esempio produrranno lo stesso nome di logger e produrranno:

26 dic 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Hello from MixedBagOfTricks

Nota: il unwrapCompanionClass()metodo assicura che non generiamo un logger che prende il nome dall'oggetto compagno ma piuttosto dalla classe che lo racchiude. Questo è l'attuale metodo consigliato per trovare la classe contenente l'oggetto associato. L'eliminazione di " $ Companion " dal nome utilizzando removeSuffix()non funziona poiché agli oggetti companion possono essere assegnati nomi personalizzati.


Alcuni framework di iniezione delle dipendenze usano delegati come vedi in un'altra risposta qui. Sembrano `val log: Logger di injectLogger ()` e permettono al sistema di logging di essere iniettato e sconosciuto al codice usando. (Il mio framework di iniezione che mostra questo è su github.com/kohesive/injekt )
Jayson Minard,

10
Grazie per la risposta estesa. Molto informativo. Mi piace particolarmente l' implementazione dei delegati di proprietà (comune, più elegante) .
mchlstckl,

6
Penso che ci sia stato un cambiamento nella sintassi di Kotlin. e lo scartato dovrebbe essere ofClass.enclosingClass.kotlin.objectInstance?.javaClassinvece diofClass.enclosingClass.kotlin.companionObject?.java
oshai

1
ah, non importa, come indicato qui kotlinlang.org/docs/reference/reflection.html il barattolo di riflessione viene spedito separatamente dallo stdlib, per il gradle abbiamo bisogno di questo:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran,

1
Il codice per la creazione di "Delegati proprietà" e "Funzioni di estensione" sembrano essere gli stessi, tranne per il tipo restituito. L'esempio di codice per il delegato proprietà ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) sembra creare una funzione di estensione tale che "".logger()ora è una cosa, si suppone che si comporti in questo modo?
Mike Rylander,

32

Dai un'occhiata alla libreria di registrazione di kotlin .
Consente la registrazione in questo modo:

private val logger = KotlinLogging.logger {}

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

O così:

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

Ho anche scritto un post sul blog confrontandolo con AnkoLogger: Accesso a Kotlin e Android: AnkoLogger vs kotlin-logging

Disclaimer: sono il manutentore di quella biblioteca.

Modifica: kotlin-logging ora ha il supporto multipiattaforma: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support


Vorrei suggerire di modificare la risposta per mostrare l' output delle logger.info()chiamate, come ha fatto Jayson nella sua risposta accettata.
Paulo Merson,

7

Come buon esempio dell'implementazione della registrazione, vorrei citare Anko che utilizza un'interfaccia speciale AnkoLoggerche dovrebbe implementare una classe che necessita della registrazione. All'interno dell'interfaccia c'è un codice che genera un tag di registrazione per la classe. La registrazione viene quindi eseguita tramite funzioni di estensione che possono essere richiamate all'interno dell'implementazione dell'interfaccia senza prefissi o persino creazione dell'istanza del logger.

Non penso che sia idiomatico , ma sembra un buon approccio in quanto richiede un codice minimo, basta aggiungere l'interfaccia a una dichiarazione di classe e si ottiene la registrazione con tag diversi per classi diverse.


Il codice seguente è fondamentalmente AnkoLogger , semplificato e riscritto per l'utilizzo agnostico su Android.

Innanzitutto, c'è un'interfaccia che si comporta come un'interfaccia marcatore:

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

Permette alla sua implementazione di utilizzare le funzioni delle estensioni MyLoggerall'interno del proprio codice semplicemente richiamandole this. E contiene anche tag di registrazione.

Successivamente, c'è un punto di ingresso generale per diversi metodi di registrazione:

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

Verrà chiamato dai metodi di registrazione. Ottiene un tag dall'implementazione MyLogger, controlla le impostazioni di registrazione e quindi chiama uno dei due gestori, quello con Throwableargomento e quello senza.

Quindi puoi definire quanti metodi di registrazione vuoi, in questo modo:

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

Questi sono definiti una volta sia per la registrazione di un solo messaggio sia per la registrazione di un Throwable, questo viene fatto con un throwableparametro opzionale .

Le funzioni che vengono passate come handlere throwableHandlerpossono essere diverse per i diversi metodi di registrazione, ad esempio, possono scrivere il registro su file o caricarlo da qualche parte. isLoggingEnablede LoggingLevelssono omessi per brevità, ma il loro utilizzo offre una flessibilità ancora maggiore.


Consente il seguente utilizzo:

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

C'è un piccolo inconveniente: sarà necessario un oggetto logger per accedere alle funzioni a livello di pacchetto:

private object MyPackageLog : MyLogger

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

Questa risposta è specifica per Android e la domanda non ha menzionato né un tag Android.
Jayson Minard,

@JaysonMinard perché è? Questo approccio è di uso generale poiché, ad esempio, avere un tag di registrazione univoco per ogni classe è utile anche in progetti non Android.
tasto di scelta rapida

1
Non è chiaro che stai dicendo "implementa qualcosa di simile a quello che ha fatto Anko" e invece sembra più "usa Anko" ... che quindi richiede una libreria Android chiamata Anko. Che ha un'interfaccia che ha funzioni di estensione che chiamano android.util.Logper fare la registrazione. Qual era il tuo intento? usare Anko? Di costruire qualcosa di simile usando Anko come esempio (è meglio se metti in linea il codice suggerito e lo aggiusti per non-Android invece di dire "port questo su non-Android, ecco il link". Invece aggiungi il codice di esempio chiamando Anko)
Jayson Minard il

1
@JaysonMinard, grazie per i tuoi commenti, ho riscritto il post in modo che ora spieghi l'approccio piuttosto che i riferimenti ad Anko.
tasto di scelta rapida

6

KISS: per i team Java che migrano verso Kotlin

Se non ti dispiace fornire il nome della classe ad ogni istanza del logger (proprio come java), puoi mantenerlo semplice definendolo come una funzione di alto livello da qualche parte nel tuo progetto:

import org.slf4j.LoggerFactory

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

Questo utilizza un parametro di tipo reificato di Kotlin .

Ora puoi usarlo come segue:

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

Questo approccio è semplicissimo e vicino all'equivalente Java, ma aggiunge solo un po 'di zucchero sintattico.

Passaggio successivo: estensioni o delegati

Personalmente preferisco fare un passo avanti e usare l'approccio di estensioni o delegati. Questo è ben riassunto nella risposta di @ JaysonMinard, ma ecco il TL; DR per l'approccio "Delegate" con l'API log4j2 ( AGGIORNAMENTO : non è più necessario scrivere manualmente questo codice, poiché è stato rilasciato come modulo ufficiale del progetto log4j2, vedi sotto). Dato che log4j2, a differenza di slf4j, supporta la registrazione con Supplier"s", ho anche aggiunto un delegato per semplificare l'utilizzo di questi metodi.

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 API di registrazione di Kotlin

La maggior parte della sezione precedente è stata adattata direttamente per produrre il modulo API di registrazione di Kotlin , che è ora una parte ufficiale di Log4j2 (dichiarazione di non responsabilità: sono l'autore principale). Puoi scaricarlo direttamente da Apache o tramite Maven Central .

L'utilizzo è sostanzialmente come descritto sopra, ma il modulo supporta sia l'accesso al logger basato sull'interfaccia, una loggerfunzione di estensione attiva Anyper l'uso dove thisè definita, e una funzione di registrazione denominata per l'uso dove non thisè definita (come le funzioni di livello superiore).


1
Se ho ragione, puoi evitare di digitare il nome della classe nella prima soluzione che hai fornito cambiando la firma del metodo in T.logger ()
IPat

1
@IPat yup, la prima soluzione intenzionalmente non lo fa per rimanere vicino alla "via java". La seconda parte della risposta copre il caso di estensione T.logger()- vedere la parte inferiore dell'esempio di codice.
Raman,

5

Anko

È possibile utilizzare la Ankolibreria per farlo. Avresti codice come di seguito:

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

Kotlin-logging

La libreria kotlin-logging ( progetto Github - kotlin-logging ) consente di scrivere codice di registrazione come di seguito:

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

StaticLog

oppure puoi anche usare questo piccolo scritto nella libreria di Kotlin chiamato StaticLogquindi il tuo codice sarebbe simile a:

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

La seconda soluzione potrebbe essere migliore se si desidera definire un formato di output per il metodo di registrazione come:

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

o utilizzare i filtri, ad esempio:

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

timberkt

Se avessi già usato il Timbercontrollo della libreria di registrazione di Jake Wharton timberkt.

Questa libreria si basa su Timber con un'API che è più facile da usare da Kotlin. Invece di utilizzare i parametri di formattazione, si passa un lambda che viene valutato solo se il messaggio è registrato.

Esempio di codice:

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

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

Controlla anche: Accesso a Kotlin e Android: AnkoLogger vs kotlin-logging

Spero che possa essere d'aiuto


4

Qualcosa del genere funzionerebbe per te?

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
Questa risposta ha bisogno di maggiori spiegazioni, se la persona che chiede non capisce gli oggetti associati, probabilmente non è arrivata ai delegati e quindi non saprà cosa sta facendo. Inoltre, questo modello consente di risparmiare molto poco nel codice. E dubito che la memorizzazione nella cache dell'oggetto associato sia in realtà un miglioramento delle prestazioni diverso da quello di un sistema limitato con CPU di piccole dimensioni come Android.
Jayson Minard,

1
Ciò che questo codice sopra mostra è la creazione di una classe che funge da delegato (vedi kotlinlang.org/docs/reference/delegated-properties.html ) che è la prima classe LoggerDelegate E quindi sta creando una funzione di livello superiore che sta facendo è più facile creare un'istanza del delegato (non molto più semplice, ma un po '). E quella funzione dovrebbe essere cambiata per essere inline. Quindi utilizza il delegato per fornire un logger ogni volta che lo si desidera. Ma fornisce uno per il compagno Foo.Companione non per la classe, Fooquindi forse non è come previsto.
Jayson Minard,

@JaysonMinard Sono d'accordo, ma lascerò la risposta per i futuri spettatori che desiderano una "soluzione rapida" o un esempio di come applicare questo ai propri progetti. Non capisco perché la logger()funzione dovrebbe essere inlinese non sono presenti lambda. IntelliJ suggerisce che in questo caso non è necessario allineare
Jire il

1
Ho incorporato la tua risposta nella mia e l'ho semplificata rimuovendo la classe delegata personalizzata e Lazyinvece ho usato un wrapper . Con un trucco per fargli sapere in che classe è.
Jayson Minard,

1

Non ho sentito parlare di un idioma al riguardo. Più semplice è, meglio userei una proprietà di alto livello

val logger = Logger.getLogger("package_name")

Questa pratica funziona bene in Python, e per quanto differenti possano apparire Kotlin e Python, credo che siano abbastanza simili nel loro "spirito" (parlando di idiomi).


Il livello principale è anche noto come livello del pacchetto.
Caelum,

Una variabile di livello superiore è come dire "usa variabili globali" e penso che sarebbe applicabile solo se avessi altre funzioni di livello superiore che avevano bisogno di usare un logger. A quel punto, tuttavia, potrebbe essere meglio passare un logger a qualsiasi funzione di utilità che desideri accedere.
Jayson Minard,

1
@JaysonMinard Penso che passare il logger come parametro sarebbe un anti-pattern, perché la tua registrazione non dovrebbe mai influenzare la tua API, esterna o interna
voddan,

Ok, torniamo al mio punto, per la registrazione a livello di classe inserisci il logger nella classe, non una funzione di livello superiore.
Jayson Minard,

1
@voddan fornisce almeno un esempio completo del tipo di logger che stai creando. val log = what?!? ... creando un logger per nome? Ignorando il fatto che la domanda mostrava che voleva creare un logger per una classe specificaLoggerFactory.getLogger(Foo.class);
Jayson Minard,

1

Che dire invece di una funzione di estensione su Class? In questo modo si finisce con:

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

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

Nota: non l'ho provato affatto, quindi potrebbe non essere del tutto corretto.


1

Innanzitutto, è possibile aggiungere funzioni di estensione per la creazione del logger.

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

Quindi sarai in grado di creare un logger usando il seguente codice.

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

In secondo luogo, è possibile definire un'interfaccia che fornisce un logger e la sua implementazione 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)

Questa interfaccia può essere utilizzata nel modo seguente.

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

1

creare un oggetto associato e contrassegnare i campi appropriati con l'annotazione @JvmStatic


1

Ci sono già molte ottime risposte qui, ma tutte riguardano l'aggiunta di un logger a una classe, ma come faresti per fare il logging nelle funzioni di primo livello?

Questo approccio è generico e abbastanza semplice da funzionare bene in entrambe le classi, oggetti associati e funzioni di livello superiore:

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

Ecco a cosa servono gli oggetti associati, in generale: la sostituzione di oggetti statici.


Un oggetto associato non è statico, è un singleton che può contenere membri che possono diventare statici se si utilizza l' JvmStaticannotazione. E in futuro potrebbe essercene più di uno consentito. Inoltre questa risposta non è molto utile senza ulteriori informazioni o un campione.
Jayson Minard,

Non ho detto che fosse statico. Ho detto che era per sostituire la statica. E perché dovrebbe essercene più di uno permesso? Non ha senso. Infine, avevo fretta e pensavo che indicare nella giusta direzione sarebbe stato abbastanza utile.
Jacob Zimmerman,

1
Un oggetto associato non serve a sostituire la statica, ma può anche renderne statici gli elementi. Kotlin ha supportato più di un compagno per un certo periodo e consente loro di avere altri nomi. Una volta che inizi a nominarli, si comportano meno come statici. Ed è lasciato aperto in futuro avere più di un compagno di nome. Ad esempio, uno potrebbe essere Factorye un altroHelpers
Jayson Minard il

0

Esempio Slf4j, lo stesso per gli altri. Questo funziona anche per la creazione di logger a livello di pacchetto

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

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

Uso:

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

Questo è ancora WIP (quasi finito), quindi vorrei condividerlo: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

L'obiettivo principale di questa libreria è applicare un determinato stile di registro in un progetto. Con la sua generazione di codice Kotlin sto cercando di affrontare alcuni dei problemi menzionati in questa domanda. Per quanto riguarda la domanda originale, ciò che di solito tendo a fare è semplicemente:

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

}

0

Puoi semplicemente creare la tua "libreria" di utility. Non hai bisogno di una grande libreria per questo compito che renderà il tuo progetto più pesante e complesso.

Ad esempio, è possibile utilizzare Kotlin Reflection per ottenere il nome, il tipo e il valore di qualsiasi proprietà della classe.

Prima di tutto, assicurati di avere la meta-dipendenza stabilita nel tuo build.gradle:

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

Successivamente, puoi semplicemente copiare e incollare questo codice nel tuo progetto:

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

Esempio di utilizzo:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.