Kotlin'de deyimsel kayıt yöntemi


164

Kotlin, Java'da kullanılanla aynı statik alan kavramına sahip değildir. Java'da günlük kaydı yapmanın genel olarak kabul edilen yolu:

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

Soru Kotlin'de günlük kaydı yapmanın deyimsel yolu nedir?


1
Bunu bir cevap olarak yayınlamamak Java yolundan uzak olduğundan, ancak herhangi bir günlüğe kaydetme için bir uzantı işlevi yazmayı düşündüm. Loggers'ı elbette önbelleğe almanız gerekiyor, ancak bunun bunu yapmanın güzel bir yolu olacağını düşünüyorum.
mhlz

1
@mhlz Bu uzantı işlevi statik olarak çözülmez mi? İçinde olduğu gibi, tüm nesnelere uygulanmayacaktı, sadece tipte olanlara uygulanacaktı Any(böylece bir döküme ihtiyaç duyuyor)?
Jire

1
mhlz bir eklenti işlevi mantıklı değil çünkü kaydediciyi tutmak için durumu olmayacak. Bir kaydediciyi döndürmek için bir uzantı olabilir, ancak neden sistemdeki bilinen her sınıfta? Herhangi bir uzantıyı takma IDE'de daha sonra özensiz gürültü olma eğilimindedir. @Jire uzantısı Any'un tüm torunları için geçerli olacak, yine this.javaClassde her biri için doğru döndürecektir . Ama bunu bir çözüm olarak önermiyorum.
Jayson Minard

Yanıtlar:


251

Olgun Kotlin kodunun çoğunda, aşağıdaki modellerden birini bulacaksınız. Mülkiyet Delegelerini kullanan yaklaşım , Kotlin'in en küçük kodu üretme gücünden yararlanır.

Not: Buradaki kod içindir, java.util.Loggingancak aynı teori herhangi bir günlük kütüphanesi için geçerlidir

Statik benzeri (ortak, sorudaki Java kodunuzun eşdeğeri)

Günlük sistemi içindeki bu karma aramanın performansına güvenemiyorsanız, bir örneği tutabilen ve sizin için statik gibi hissedebileceğiniz bir tamamlayıcı nesne kullanarak Java kodunuza benzer davranışlar elde edebilirsiniz.

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

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

çıktı oluşturma:

26 Aralık 2015 11:28:32 AM org.stackoverflow.kotlin.test.MyClassfoo BİLGİ: Merhaba MyClass

Buradaki tamamlayıcı nesneler hakkında daha fazla bilgi: Tamamlayıcı Nesneler ... Ayrıca yukarıdaki MyClass::class.javaörnekte Class<MyClass>kaydedici için türün örneğini alırken this.javaClass, türün örneğini alacağınızı unutmayın Class<MyClass.Companion>.

Sınıf Örneği Başına (ortak)

Ancak, örnek düzeyinde bir günlükçü çağırmaktan ve almaktan kaçınmak için gerçekten hiçbir neden yoktur. Bahsettiğiniz deyimsel Java yolu modası geçmiş ve performans korkusuna dayanıyor, oysa sınıf başına kaydedici zaten gezegen üzerindeki neredeyse tüm makul kayıt sistemleri tarafından önbelleğe alınmış. Logger nesnesini tutmak için bir üye oluşturmanız yeterlidir.

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

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

çıktı oluşturma:

26 Aralık 2015 11:28:44 AM org.stackoverflow.kotlin.test.MyClass foo BİLGİ: Merhaba MyClass

Hem örnek başına hem de sınıf varyasyonları başına performans testi yapabilir ve çoğu uygulama için gerçekçi bir fark olup olmadığını görebilirsiniz.

Mülkiyet Delegeleri (ortak, en zarif)

@Jire tarafından başka bir cevapta önerilen başka bir yaklaşım, daha sonra istediğiniz herhangi bir sınıfta mantığı eşit bir şekilde yapmak için kullanabileceğiniz bir özellik temsilcisi oluşturmaktır. Kotlin Lazyzaten bir delege sağladığı için bunu yapmanın daha basit bir yolu var, sadece bir işlevde sarabiliriz. Buradaki bir hile, şu anda temsilci kullanan sınıfın türünü bilmek istiyorsak, onu herhangi bir sınıfta bir uzantı işlevi haline getiriyoruz:

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"

Bu kod ayrıca bir Companion Object içinde kullanırsanız, günlükçü adının sınıfın kendisinde kullandığınız gibi olmasını sağlar. Şimdi şunları yapabilirsiniz:

class Something {
    val LOG by logger()

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

sınıf başına veya sınıf başına bir örnekle daha statik olmasını istiyorsanız:

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

    }

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

Ve foo()her iki sınıfı da çağırmaktan elde ettiğiniz sonuç:

26 Aralık 2015 11:30:55 org.stackoverflow.kotlin.test. Bir şey için BİLGİ: Merhaba bir şey

26 Aralık 2015 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo BİLGİ: Merhaba SomethingElse

Eklenti İşlevleri (Herhangi bir ad alanının "kirliliği" nedeniyle bu durumda nadirdir)

Kotlin, bu kodun bazılarını daha da küçük hale getirmenize izin veren birkaç gizli numaraya sahiptir. Sınıflar üzerinde uzantı işlevleri oluşturabilir ve böylece onlara ek işlevler verebilirsiniz. Yukarıdaki yorumlardan bir öneri, Anybir logger fonksiyonu ile genişletmekti . Bu, herhangi bir sınıftaki IDE'sinde kod tamamlamayı her kullandığında parazit oluşturabilir. Ancak, genişletmenin Anyveya başka bir işaretleyici arayüzünün gizli bir yararı vardır : kendi sınıfınızı genişlettiğinizi ve dolayısıyla içinde bulunduğunuz sınıfı tespit edebileceğinizi ima edebilirsiniz. Ha? Daha az kafa karıştırıcı olmak için kod İşte:

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

Şimdi bir sınıf (veya tamamlayıcı nesne) içinde, bu uzantıyı kendi sınıfımda çağırabilirim:

class SomethingDifferent {
    val LOG = logger()

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

Üretim üreten:

26 Aralık 2015 11:29:12 AM org.stackoverflow.kotlin.test.SomethingDifferent foo BİLGİ: Merhaba SomethingDifferent

Temel olarak, kod bir dahili numaraya çağrı olarak görülür Something.logger(). Sorun şu ki, diğer sınıflarda "kirlilik" yaratarak aşağıdakiler de doğru olabilir:

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

İşaretleyici Arabiriminde Uzantı İşlevleri (ne kadar yaygın olduğundan emin değil, ancak "özellikler" için ortak model)

Uzantıların kullanımını daha temiz hale getirmek ve "kirliliği" azaltmak için, aşağıdakileri genişletmek üzere bir işaretleyici arabirimi kullanabilirsiniz:

interface Loggable {} 

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

Veya yöntemi, varsayılan bir uygulamayla arabirimin bir parçası haline getirin:

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

Ve sınıfınızda şu varyasyonlardan birini kullanın:

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

Üretim üreten:

26 Aralık 2015 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foFO BİLGİ: Merhaba MarkedClass'tan

Kaydediciyi tutmak için tek tip bir alan oluşturulmasını zorlamak istiyorsanız, bu arabirimi kullanırken, uygulayıcının kolayca aşağıdaki gibi bir alana sahip olmasını isteyebilirsiniz LOG:

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

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

Şimdi arayüzün uygulayıcısı şöyle görünmelidir:

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

Tabii ki, soyut bir temel sınıf aynı şeyi yapabilir, hem arayüz hem de bu arayüzü uygulayan bir soyut sınıf seçeneği esneklik ve bütünlüğü sağlar:

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

Hepsini Bir Araya Getirmek (Küçük bir yardımcı kütüphane)

Yukarıdaki seçeneklerden herhangi birinin kullanımını kolaylaştıracak küçük bir yardımcı kütüphane. Kotlin'de API'leri beğeninize göre daha fazla genişletmek yaygındır. Uzatma veya üst seviye işlevlerde. Aşağıda, günlüklerin nasıl oluşturulacağı konusunda seçenekler sunan bir karışım ve tüm varyasyonları gösteren bir örnek verilmiştir:

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

Saklamak istediklerinizi seçin ve kullanılan tüm seçenekler şunlardır:

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

Bu örnekte oluşturulan günlükçünün 13 örneğinin tümü aynı günlükçü adını ve çıktısını üretecektir:

26 Aralık 2015 11:39:00 AM org.stackoverflow.kotlin.test.MixedBagOfTricks foo BİLGİ: Hello from MixedBagOfTricks

Not:unwrapCompanionClass() yöntem olmasını sağlar biz refakatçi nesne ziyade kapsayan sınıfının adıyla anılan bir logger üretemeyen. Tamamlayıcı nesneyi içeren sınıfı bulmak için önerilen geçerli yöntem budur. Tamamlayıcı nesnelere özel adlar verilebildiğinden , " $ Companion " ifadesini kullanarak adından removeSuffix()çıkarmak işe yaramaz.


Bazı bağımlılık enjeksiyon çerçeveleri, başka bir cevapta gördüğünüz gibi delegeleri kullanır. `Val log: injectLogger () 'ile Logger'a benziyorlar ve log sisteminin enjekte edilmesine ve kullanılan kod tarafından bilinmemesine izin veriyorlar. (Bunu gösteren enjeksiyon çerçevem github.com/kohesive/injekt adresinde )
Jayson Minard

10
Kapsamlı cevap için teşekkürler. Çok bilgilendirici. Özellikle Mülkiyet Delegelerinin (ortak, en zarif) uygulamasını seviyorum .
mchlstckl

6
Bence kotlin sözdiziminde bir değişiklik oldu. ve paketini açmak ofClass.enclosingClass.kotlin.objectInstance?.javaClassyerineofClass.enclosingClass.kotlin.companionObject?.java
oshai

1
ah, boşver, burada belirtildiği gibi kotlinlang.org/docs/reference/reflection.html yansıma kavanoz stdlib'den ayrı olarak gönderilir, kepçe için buna ihtiyacımız var:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran

1
'Özellik Temsilcileri' ve 'Uzantı İşlevleri' oluşturma kodu, dönüş türü dışında aynı görünüyor. Özellik Temsilcisi ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) için kod örneği "".logger(), şimdi bir şey gibi bir uzantı işlevi oluşturuyor gibi görünüyor , bu sözde böyle davranıyor mu?
Mike Rylander

32

Kotlin-logging kütüphanesine bir göz atın .
Bu şekilde günlüğe kaydetmeye izin verir:

private val logger = KotlinLogging.logger {}

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

Veya bunun gibi:

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

Ayrıca şöyle bir blog yazısı yazdım AnkoLogger: Kotlin ve Android'de oturum açma: AnkoLogger vs kotlin-logging

Feragatname: Bu kütüphanenin koruyucusuyum.

Düzenleme: kotlin-logging artık çoklu platform desteğine sahip: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support


Sana göstermek için cevabınızı düzenlemek önerebilir miyim çıkışı arasında logger.info()Jayson onun kabul cevap olduğu gibi, aramalar.
Paulo Merson

7

Günlüğe kaydetme uygulamasına iyi bir örnek olarak, günlüğe kaydetme gerektiren bir sınıfın uygulaması gereken özel bir arabirim kullanan Anko'dan bahsetmek istiyorum AnkoLogger. Arayüzün içinde, sınıf için bir günlük kaydı etiketi oluşturan kod vardır. Günlüğe kaydetme daha sonra interace uygulaması içinde önekler veya hatta logger örneği oluşturma olmadan çağrılabilen uzantı işlevleri aracılığıyla yapılır.

Bunun deyimsel olduğunu düşünmüyorum , ancak sadece bir sınıf bildirimine arabirim ekleyerek minimum kod gerektirdiği için iyi bir yaklaşım gibi görünüyor ve farklı sınıflar için farklı etiketlerle günlük kaydı alıyorsunuz.


Aşağıdaki kod temel olarak Android agnostik kullanım için basitleştirilmiş ve yeniden yazılan AnkoLogger'dır .

İlk olarak, bir işaretçi arayüzü gibi davranan bir arayüz var:

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

Uygulamasının MyLoggersadece onları çağıran kodlarının içindeki uzantı işlevlerini kullanmasına izin verir this. Ayrıca günlük etiketi içerir.

Ardından, farklı kayıt yöntemleri için genel bir giriş noktası vardır:

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

Kayıt yöntemleri ile çağrılır. Uygulamadan bir etiket alır MyLogger, günlük ayarlarını kontrol eder ve sonra iki işleyiciden birini çağırır, biri Throwablebağımsız değişkenli ve olmayan işleyiciyi .

Daha sonra bu şekilde istediğiniz kadar günlük yöntemi tanımlayabilirsiniz:

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

Bunlar sadece bir iletinin günlüğe kaydedilmesi ve a'nın günlüğe kaydedilmesi için bir kez tanımlanır Throwable, bu isteğe bağlı throwableparametre ile yapılır .

Farklı günlüğe kaydetme yöntemleri için iletilen handlerve throwableHandlerfarklı olabilen işlevler, örneğin, günlüğü dosyaya yazabilir veya bir yere yükleyebilir. isLoggingEnabledve LoggingLevelskısalık için kullanılmaz, ancak bunları kullanmak daha da esneklik sağlar.


Aşağıdaki kullanıma izin verir:

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

Küçük bir dezavantaj var: paket düzeyinde işlevlerde oturum açmak için bir günlükçü nesnesi gerekli olacaktır:

private object MyPackageLog : MyLogger

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

Bu cevap Android'e özgüdür ve soru Android etiketinden bahsetmedi.
Jayson Minard

@JaysonMinard neden bu? Bu yaklaşım genel bir amaçtır, örneğin, her sınıf için benzersiz bir kayıt etiketine sahip olmak Android dışı projelerde de yararlıdır.
hotkey

1
"Anko'nun yaptıklarına benzer bir şey uygula" dediğiniz açık değil ve daha çok "Anko kullan" ... Hangi android.util.Loggünlüğü yapmak için çağrı uzantısı işlevleri olan bir arabirimi vardır . Amacın hangisiydi? Anko kullanın? Örnek olarak Anko'yu kullanırken benzer bir şey inşa etmek (önerilen kodu satır içine koymak ve "Android için bu bağlantı noktası, bağlantı burada" demek yerine Android olmayan için düzeltmek daha iyidir. Bunun yerine örnek kod eklemek Anko'yu arıyor)
Jayson Minard

1
@JaysonMinard, yorumlarınız için teşekkürler, şimdi Anko referansları yerine yaklaşımı açıklayacak şekilde yazıyı yeniden yazdım.
hotkey

6

ÖPÜCÜ: Kotlin'e Göç Eden Java Takımları İçin

Kaydedicinin her örneğinde (tıpkı java gibi) sınıf adını sağlamanın sakıncası yoksa, bunu projenizde bir yerde üst düzey işlev olarak tanımlayarak basit tutabilirsiniz:

import org.slf4j.LoggerFactory

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

Bu bir Kotlin reified type parametresi kullanır .

Şimdi, bunu aşağıdaki gibi kullanabilirsiniz:

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

Bu yaklaşım süper basittir ve java eşdeğerine yakındır, ancak sadece sözdizimsel şeker ekler.

Sonraki Adım: Uzantılar veya Delegeler

Şahsen bir adım daha ileri gitmeyi ve uzantılar veya delegeler yaklaşımını kullanmayı tercih ediyorum. Bu, @ JaysonMinard'ın cevabında güzel bir şekilde özetlenmiştir, ancak işte TL; log4j2 API ile "Delege" yaklaşımı için TL ( GÜNCELLEME : bu kodu artık manuel olarak yazmanıza gerek yok, çünkü resmi bir modül olarak yayınlandı) log4j2 projesi, aşağıya bakınız). Log4j2, slf4j'den farklı olarak, Supplier's ile günlüğe kaydetmeyi desteklediğinden , bu yöntemleri kullanmayı kolaylaştırmak için bir temsilci ekledim.

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 Günlük API'sı

Önceki bölümün çoğu , şimdi Log4j2'nin resmi bir parçası olan Kotlin Logging API modülünü üretmek için doğrudan uyarlanmıştır (sorumluluk reddi: Ben birincil yazarım). Bunu doğrudan Apache'den veya Maven Central'dan indirebilirsiniz .

Kullanım temel olarak yukarıda açıklandığı gibidir, ancak modül hem arabirim tabanlı günlükçü erişimini, tanımlanmış yerlerde kullanım için bir loggeruzantı işlevini , hem de tanımlanmamış Anyyerlerde kullanım thisiçin adlandırılmış bir günlükçü işlevini this(üst düzey işlevler gibi) destekler.


1
Haklıysam, yöntem imzasını T.logger ()
IPat

1
@IPat yup, ilk çözüm kasıtlı olarak bunu "java yolu" yakın kalmak için yapmaz. Cevabın ikinci kısmı uzatma durumunu kapsar T.logger()- kod örneğinin altına bakın.
Raman

5

Anko

Bunu Ankoyapmak için kitaplığı kullanabilirsiniz . Aşağıdaki gibi bir kod olurdu:

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

KOTLIN-günlüğü

kotlin-logging ( Github project - kotlin-logging ) kütüphanesi, aşağıdaki gibi günlük kodu yazmanıza olanak sağlar:

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

StaticLog

ya da Kotlin kütüphanesinde yazılmış bu küçük StaticLogkodu kullanabilirsiniz, o zaman kodunuz şöyle görünür:

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

Günlüğe kaydetme yöntemi için bir çıktı biçimi tanımlamak isterseniz ikinci çözüm daha iyi olabilir:

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

veya filtreleri kullanın, örneğin:

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

timberkt

Jake Wharton'un Timbergünlük kitaplığı denetimini daha önce kullandıysanız timberkt.

Bu kütüphane, Kotlin'den kullanımı daha kolay bir API ile Kereste üzerine inşa edilmiştir. Biçimlendirme parametrelerini kullanmak yerine, yalnızca mesaj günlüğe kaydedildiğinde değerlendirilen bir lambda iletirsiniz.

Kod örneği:

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

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

Ayrıca kontrol et: Kotlin ve Android'de oturum açma: AnkoLogger vs kotlin-logging

Umarım yardımcı olur


4

Bunun gibi bir şey sizin için işe yarar mı?

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
Bu cevabın daha fazla açıklanması gerekiyor, eğer soran kişi eşlik eden nesneleri anlamıyorsa, muhtemelen delegelere ulaşamamışlardır ve bu yüzden bunun ne yaptığını bilemezler. Ayrıca bu modeli kullanarak kodda çok az tasarruf var. Eşlik nesnesindeki önbelleklemenin, Android gibi küçük CPU'lu kısıtlı bir sistemden başka bir performans kazancı olduğundan şüpheliyim.
Jayson Minard

1
Yukarıda bu kodun gösterdiği şey, birinci sınıf olan bir Delege olarak hareket eden bir sınıfın oluşturulmasıdır (bkz. Kotlinlang.org/docs/reference/delegated-properties.html ) LoggerDelegate Ve daha sonra, temsilcinin bir örneğini oluşturmak daha kolaydır (çok daha kolay değil, biraz). Ve bu işlev olarak değiştirilmelidir inline. Daha sonra, istendiğinde bir günlükçü sağlamak için delege kullanır. Ancak Foo.Companionsınıf için değil , refakatçi için bir tane sağlar, Foobu yüzden belki de amaçlandığı gibi değildir.
Jayson Minard

@JaysonMinard Kabul ediyorum, ancak gelecekteki "hızlı bir düzeltme" veya bunu kendi projelerine nasıl uygulayacağına dair bir örnek isteyen izleyiciler için cevap bırakacağım. Lamda yoksa logger()işlevin neden olması gerektiğini anlamıyorum inline. IntelliJ, bu durumda satır içi işlemin
Jire

1
Cevabınızı benimkine dahil ettim ve özel delege sınıfını kaldırarak basitleştirdim ve Lazybunun yerine bir sargı kullandım. İçinde hangi sınıf olduğunu bilmek için bir hile ile.
Jayson Minard

1

Bu konuda hiçbir deyim duymadım. Daha basit, daha iyi, bu yüzden üst düzey bir özellik kullanırdım

val logger = Logger.getLogger("package_name")

Bu uygulama Python'da iyi hizmet eder ve Kotlin ve Python'un göründüğü kadar farklıdır, orada "ruh" da (deyimden bahsetmek) oldukça benzer olduklarına inanıyorum.


Üst düzey, paket düzeyi olarak da bilinir.
Caelum

Üst düzey bir değişken "global değişkenleri kullan" demek gibidir ve bence sadece bir günlükçü kullanmak için gereken diğer üst düzey işlevleriniz varsa uygulanabilir. Ancak bu noktada, bir günlükçüyü, günlüğe kaydetmek isteyen herhangi bir yardımcı program işlevine geçirmek daha iyi olabilir.
Jayson Minard

1
@JaysonMinard Logger'ı parametre olarak geçirmenin bir anti-desen olacağını düşünüyorum, çünkü günlük
kaydınız API'nizi

Tamam, sonra noktama geri dön, sınıf düzeyinde günlük kaydı için logger'ı bir üst düzey fonksiyona değil, sınıfa koyun.
Jayson Minard

1
@voddan en azından ne tür bir kayıt cihazı oluşturduğunuza dair tam bir örnek sunar. val log = what?!? ... adıyla bir kayıt cihazı mı oluşturuyorsunuz? Sorunun belirli bir sınıf için bir kaydedici oluşturmak istediğini gösterdiğini görmezden gelmekLoggerFactory.getLogger(Foo.class);
Jayson Minard

1

Bunun yerine Class'taki bir genişletme işlevi ne olacak? Bu şekilde şunları elde edersiniz:

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

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

Not - Bunu hiç test etmedim, bu yüzden doğru olmayabilir.


1

İlk olarak, günlükçü oluşturmak için uzantı işlevleri ekleyebilirsiniz.

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

Ardından, aşağıdaki kodu kullanarak bir günlükçü oluşturabilirsiniz.

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

İkincisi, bir logger ve onun mixin uygulamasını sağlayan bir arayüz tanımlayabilirsiniz.

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)

Bu arayüz aşağıdaki şekilde kullanılabilir.

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

1

tamamlayıcı nesne oluşturun ve uygun alanları @JvmStatic ek açıklama ile işaretleyin


1

Burada zaten birçok harika cevap var, ancak hepsi bir sınıfa bir günlükçü eklemeyle ilgili, ancak Üst Düzey İşlevlerde günlük kaydı yapmak için bunu nasıl yapardınız?

Bu yaklaşım genel ve her iki sınıfta, yardımcı nesnelerde ve Üst Düzey İşlevlerde iyi çalışacak kadar basittir:

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

Tamamlayıcı nesneler genel olarak budur: statik şeylerin değiştirilmesi.


Tamamlayıcı nesne statik değildir, JvmStaticek açıklama kullanırsanız statik olabilecek üyeleri tutabilen tek bir alandır . Ve gelecekte birden fazla izin olabilir. Ayrıca bu cevap daha fazla bilgi veya örnek olmadan çok yardımcı olmaz.
Jayson Minard

Statik olduğunu söylemedim. Statiği değiştirmek için olduğunu söyledim. Ve neden birden fazla izin verilsin ki? Bu mantıklı değil. Son olarak, acelem vardı ve doğru yöne işaret etmenin yeterince yardımcı olacağını düşündüm.
Jacob Zimmerman

1
Tamamlayıcı bir nesne, statikin değiştirilmesi için değil, aynı zamanda öğesinin statik olmasını da sağlayabilir. Kotlin bir süre refakatçiden daha fazlasını destekledi ve başka isimlere sahip olmalarına izin verdi. Onları adlandırmaya başladığınızda, daha az statik gibi davranırlar. Ve gelecekte birden fazla arkadaşın olması açık bırakıldı. Örneğin, biri olabilir Factoryve diğeriHelpers
Jayson Minard

0

Slf4j örneği, diğerleri için aynı. Bu, paket seviyesi günlükçüsü oluşturmak için bile çalışır

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

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

Kullanımı:

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

Bu hala WIP (neredeyse bitti) bu yüzden paylaşmak istiyorum: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

Bu kütüphanenin temel amacı, bir proje boyunca belirli bir günlük stilini zorlamaktır. Kotlin kodunu üreterek bu soruda bahsedilen sorunların bazılarını ele almaya çalışıyorum. Orijinal soru ile ilgili olarak genellikle yapmak eğiliminde olduğum şey:

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

}

0

Basitçe kendi "kütüphane" programı oluşturabilirsiniz. Bu görev için projenizi daha ağır ve karmaşık hale getirecek geniş bir kütüphaneye ihtiyacınız yok.

Örneğin, herhangi bir sınıf özelliğinin adını, türünü ve değerini almak için Kotlin Reflection'ı kullanabilirsiniz.

Her şeyden önce, meta bağımlılığın build.gradle dosyasına yerleştiğinden emin olun:

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

Daha sonra, bu kodu kopyalayıp projenize yapıştırabilirsiniz:

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

Kullanım örneği:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.