Cara log in idiomatis di Kotlin

164

Kotlin tidak memiliki gagasan yang sama tentang bidang statis seperti yang digunakan di Jawa. Di Jawa, cara penebangan yang diterima secara umum adalah:

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

Pertanyaannya adalah apa cara idiomatis melakukan penebangan di Kotlin?

mchlstckl
sumber
1
Tidak memposting ini sebagai jawaban karena jauh dari cara Java, tapi saya telah mempertimbangkan untuk menulis fungsi ekstensi di Any untuk login. Anda perlu men-cache para Logger tentu saja, tetapi saya pikir ini akan menjadi cara yang bagus untuk melakukannya.
mhlz
1
@ mhlz Bukankah fungsi ekstensi itu diselesaikan secara statis? Seperti dalam, itu tidak akan diterapkan pada semua objek, hanya untuk yang bertipe Any(sehingga membutuhkan gips)?
Jire
1
@ mhlz fungsi ekstensi tidak masuk akal karena tidak memiliki status untuk menjaga logger. Ini bisa menjadi ekstensi untuk mengembalikan logger, tetapi mengapa memiliki itu pada setiap kelas yang dikenal dalam sistem? Menempatkan ekstensi pada Apa saja cenderung menjadi ceroboh dalam IDE nanti. @ Juli ekstensi akan berlaku untuk semua keturunan Any, masih akan mengembalikan yang benar this.javaClassuntuk masing-masing. Tapi saya tidak merekomendasikannya sebagai solusi.
Jayson Minard

Jawaban:

250

Di sebagian besar kode Kotlin dewasa, Anda akan menemukan salah satu pola ini di bawah. Pendekatan menggunakan Delegasi Properti mengambil keuntungan dari kekuatan Kotlin untuk menghasilkan kode terkecil.

Catatan: kode di sini adalah untuk java.util.Loggingtetapi teori yang sama berlaku untuk pustaka logging manapun

Seperti statis (umum, setara dengan kode Java Anda dalam pertanyaan)

Jika Anda tidak dapat mempercayai kinerja pencarian hash di dalam sistem logging, Anda bisa mendapatkan perilaku yang mirip dengan kode Java Anda dengan menggunakan objek pendamping yang dapat menyimpan instance dan terasa seperti statis untuk Anda.

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

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

menciptakan output:

26 Des 2015 11:28:32 org.stackoverflow.kotlin.test.MyClassINFO foo: Halo dari MyClass

Lebih lanjut tentang objek pengiring di sini: Objek Pengiring ... Juga perhatikan bahwa dalam sampel di atas MyClass::class.javamendapatkan instance tipe Class<MyClass>untuk logger, sedangkan this.javaClassakan mendapatkan instance tipe Class<MyClass.Companion>.

Per Instance of a Class (umum)

Tapi, sebenarnya tidak ada alasan untuk menghindari panggilan dan mendapatkan logger di tingkat instance. Cara Jawa idiomatis yang Anda sebutkan sudah usang dan didasarkan pada rasa takut akan kinerja, sedangkan penebang per kelas sudah di-cache oleh hampir semua sistem logging yang masuk akal di planet ini. Cukup buat anggota untuk memegang objek logger.

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

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

menciptakan output:

26 Des 2015 11:28:44 AM org.stackoverflow.kotlin.test.MyClass foo INFO: Halo dari MyClass

Anda dapat menguji kinerja per variasi instance dan per kelas dan melihat apakah ada perbedaan realistis untuk sebagian besar aplikasi.

Delegasi Properti (umum, paling elegan)

Pendekatan lain, yang disarankan oleh @Jire dalam jawaban lain, adalah membuat delegasi properti, yang kemudian dapat Anda gunakan untuk melakukan logika secara seragam di kelas lain yang Anda inginkan. Ada cara yang lebih sederhana untuk melakukan ini karena Kotlin sudah menyediakan Lazydelegasi, kita bisa membungkusnya dalam suatu fungsi. Satu trik di sini adalah jika kita ingin mengetahui tipe kelas yang saat ini menggunakan delegate, kita menjadikannya fungsi ekstensi pada kelas apa saja:

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"

Kode ini juga memastikan bahwa jika Anda menggunakannya di objek pendamping, nama logger akan sama seperti jika Anda menggunakannya di kelas itu sendiri. Sekarang Anda cukup:

class Something {
    val LOG by logger()

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

untuk per instance kelas, atau jika Anda ingin agar lebih statis dengan satu instance per kelas:

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

    }

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

Dan hasil Anda dari memanggil foo()kedua kelas ini adalah:

26 Des 2015 11:30:55 AM org.stackoverflow.kotlin.test.Ada sesuatu INFO: Halo dari Sesuatu

26 Des 2015 11:30:55 AM org.stackoverflow.kotlin.test. Sesuatu yang Benar INFO: Halo dari SomethingElse

Fungsi Ekstensi (tidak umum dalam kasus ini karena "polusi" dari namespace Apa saja)

Kotlin memiliki beberapa trik tersembunyi yang memungkinkan Anda membuat beberapa kode ini menjadi lebih kecil. Anda dapat membuat fungsi ekstensi di kelas dan karenanya memberi mereka fungsi tambahan. Satu saran dalam komentar di atas adalah untuk memperluas Anydengan fungsi logger. Ini dapat membuat noise kapan saja seseorang menggunakan penyelesaian kode dalam IDE mereka di kelas mana pun. Tetapi ada manfaat rahasia untuk memperluas Anyatau antarmuka penanda lain: Anda dapat menyiratkan bahwa Anda memperluas kelas Anda sendiri dan karenanya mendeteksi kelas Anda berada di dalam. Hah? Agar tidak membingungkan, berikut ini kodenya:

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

Sekarang di dalam kelas (atau objek pendamping), saya cukup memanggil ekstensi ini di kelas saya sendiri:

class SomethingDifferent {
    val LOG = logger()

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

Memproduksi output:

26 Des 2015 11:29:12 pagi org.stackoverflow.kotlin.test. Sesuatu yang Berbeda INFO: Halo dari SomethingDifferent

Pada dasarnya, kode ini dilihat sebagai panggilan ke ekstensi Something.logger(). Masalahnya adalah bahwa hal-hal berikut ini juga bisa benar menciptakan "polusi" pada kelas lain:

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

Fungsi Ekstensi pada Antarmuka Penanda (tidak yakin seberapa umum, tetapi model umum untuk "ciri")

Untuk membuat penggunaan ekstensi lebih bersih dan mengurangi "polusi", Anda dapat menggunakan antarmuka penanda untuk memperluas:

interface Loggable {} 

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

Atau bahkan menjadikan metode bagian dari antarmuka dengan implementasi default:

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

Dan gunakan salah satu dari variasi ini di kelas Anda:

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

Memproduksi output:

26 Des 2015 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO: Halo dari MarkedClass

Jika Anda ingin memaksa pembuatan bidang yang seragam untuk menahan logger, maka saat menggunakan antarmuka ini Anda dapat dengan mudah meminta pelaksana memiliki bidang seperti LOG:

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

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

Sekarang pelaksana antarmuka harus terlihat seperti ini:

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

Tentu saja, kelas dasar abstrak dapat melakukan hal yang sama, memiliki opsi antarmuka dan kelas abstrak yang mengimplementasikan antarmuka memungkinkan fleksibilitas dan keseragaman:

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

Putting It All Together (Perpustakaan pembantu kecil)

Berikut adalah perpustakaan pembantu kecil untuk membuat salah satu opsi di atas mudah digunakan. Adalah umum di Kotlin untuk memperpanjang API agar lebih sesuai dengan keinginan Anda. Baik dalam fungsi ekstensi atau tingkat atas. Berikut adalah campuran untuk memberi Anda opsi cara membuat logger, dan sampel yang menunjukkan semua variasi:

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

Pilih yang mana yang ingin Anda simpan, dan berikut ini semua opsi yang digunakan:

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

Ke-13 instance dari logger yang dibuat dalam sampel ini akan menghasilkan nama logger yang sama, dan output:

26 Des 2015 11:39:00 pagi org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Halo dari MixedBagOfTricks

Catatan: The unwrapCompanionClass()Metode memastikan bahwa kita tidak menghasilkan logger dinamai objek pendamping melainkan kelas melampirkan. Ini adalah cara yang disarankan saat ini untuk menemukan kelas yang berisi objek pendamping. Menghapus " $ Companion " dari penggunaan nama removeSuffix()tidak berfungsi karena objek pendamping dapat diberikan nama khusus.

Jayson Minard
sumber
Beberapa kerangka kerja injeksi ketergantungan menggunakan delegasi seperti yang Anda lihat di jawaban lain di sini. Mereka terlihat seperti `val log: Logger by injectLogger ()` dan memungkinkan sistem logging untuk disuntikkan dan tidak diketahui oleh kode menggunakan. (Kerangka injeksi saya yang menunjukkan ini ada di github.com/kohesive/injekt )
Jayson Minard
10
Terima kasih atas jawaban yang luas. Sangat informatif. Saya terutama menyukai implementasi Delegasi Properti (umum, paling elegan) .
mchlstckl
6
Saya pikir ada perubahan sintaksis kotlin. dan bungkusan itu seharusnya ofClass.enclosingClass.kotlin.objectInstance?.javaClassbukanofClass.enclosingClass.kotlin.companionObject?.java
oshai
1
ah, sudahlah, seperti yang dinyatakan di sini kotlinlang.org/docs/reference/reflection.html toples dikirimkan secara terpisah dari stdlib, untuk gradle kita memerlukan ini:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran
1
Kode untuk membuat 'Delegasi Properti' dan 'Fungsi Ekstensi' tampaknya sama kecuali untuk tipe pengembalian. Contoh kode untuk Delegasi Properti ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) muncul untuk membuat fungsi ekstensi sehingga "".logger()sekarang menjadi sesuatu, apakah ini seharusnya berperilaku seperti ini?
Mike Rylander
32

Silahkan lihat di Kotlin-logging perpustakaan.
Itu memungkinkan logging seperti itu:

private val logger = KotlinLogging.logger {}

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

Atau seperti itu:

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

Saya juga menulis posting blog yang membandingkannya dengan AnkoLogger: Masuk di Kotlin & Android: AnkoLogger vs kotlin-logging

Penafian: Saya adalah pengelola perpustakaan itu.

Sunting: logging kotlin sekarang memiliki dukungan multi platform: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support

oshai
sumber
Mungkin saya sarankan Anda mengedit jawaban Anda untuk menunjukkan keluaran dari logger.info()panggilan, seperti Jayson lakukan dalam jawaban diterima-Nya.
Paulo Merson
7

Sebagai contoh implementasi logging yang baik, saya ingin menyebutkan Anko yang menggunakan antarmuka khusus AnkoLoggeryang harus diimplementasikan oleh kelas. Di dalam antarmuka ada kode yang menghasilkan tag logging untuk kelas. Logging kemudian dilakukan melalui fungsi ekstensi yang dapat dipanggil di dalam implementasi interace tanpa awalan atau bahkan pembuatan instance logger.

Saya tidak berpikir ini idiomatik , tetapi tampaknya pendekatan yang bagus karena memerlukan kode minimum, hanya menambahkan antarmuka ke deklarasi kelas, dan Anda mendapatkan logging dengan tag berbeda untuk kelas yang berbeda.


Kode di bawah ini pada dasarnya adalah AnkoLogger , disederhanakan dan ditulis ulang untuk penggunaan Android-agnostik.

Pertama, ada antarmuka yang berperilaku seperti antarmuka penanda:

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

Itu memungkinkan penerapannya menggunakan fungsi ekstensi untuk MyLoggerdi dalam kode mereka hanya memanggil mereka this. Dan itu juga berisi tag logging.

Berikutnya, ada titik masuk umum untuk berbagai metode logging:

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

Ini akan dipanggil dengan metode logging. Itu mendapat tag dari MyLoggerimplementasi, memeriksa pengaturan logging dan kemudian memanggil satu dari dua penangan, satu dengan Throwableargumen dan satu tanpa.

Kemudian Anda dapat mendefinisikan metode logging sebanyak yang Anda suka, dengan cara ini:

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

Ini didefinisikan satu kali untuk mencatat pesan dan mencatat Throwablejuga, ini dilakukan dengan throwableparameter opsional .

Fungsi yang dilewatkan sebagai handlerdan throwableHandlerdapat berbeda untuk metode logging yang berbeda, misalnya, mereka dapat menulis log untuk file atau mengunggahnya di suatu tempat. isLoggingEnableddan LoggingLevelsdihilangkan untuk singkatnya, tetapi menggunakannya memberikan lebih banyak fleksibilitas.


Ini memungkinkan untuk penggunaan berikut:

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

Ada kelemahan kecil: objek logger akan diperlukan untuk masuk dalam fungsi tingkat paket:

private object MyPackageLog : MyLogger

fun myFun() {
    MyPackageLog.info("Info message")
}
hotkey
sumber
Jawaban ini khusus untuk Android, dan pertanyaannya tidak menyebutkan atau memiliki tag Android.
Jayson Minard
@JaysonMinard kenapa begitu? Pendekatan ini adalah tujuan umum karena, misalnya, memiliki tag logging yang unik untuk setiap kelas juga berguna dalam proyek non-Android.
hotkey
1
Tidak jelas Anda mengatakan "menerapkan sesuatu yang mirip dengan apa yang dilakukan Anko" dan sebaliknya lebih seperti "gunakan Anko" ... yang kemudian membutuhkan perpustakaan Android yang disebut Anko. Yang memiliki antarmuka yang memiliki fungsi ekstensi yang memanggil android.util.Loguntuk melakukan pendataan. Apa niatmu? gunakan Anko? Dari membangun sesuatu yang mirip saat menggunakan Anko sebagai contoh (lebih baik jika Anda hanya memasukkan kode yang disarankan sebaris dan memperbaikinya untuk non-Android daripada mengatakan "port ini ke non-Android, inilah tautannya". Alih-alih Anda menambahkan kode sampel menelepon Anko)
Jayson Minard
1
@JaysonMinard, terima kasih atas komentar Anda, saya telah menulis ulang posting sehingga sekarang menjelaskan pendekatan daripada referensi Anko.
hotkey
6

KISS: Untuk Tim Java yang Bermigrasi ke Kotlin

Jika Anda tidak keberatan memberikan nama kelas pada setiap instance logger (seperti halnya java), Anda dapat membuatnya tetap sederhana dengan mendefinisikan ini sebagai fungsi tingkat atas di suatu tempat di proyek Anda:

import org.slf4j.LoggerFactory

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

Ini menggunakan parameter jenis yang direvisi Kotlin .

Sekarang, Anda dapat menggunakan ini sebagai berikut:

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

Pendekatan ini super-sederhana dan dekat dengan setara java, tetapi hanya menambahkan beberapa gula sintaksis.

Langkah Selanjutnya: Ekstensi atau Delegasi

Saya pribadi lebih suka melangkah lebih jauh dan menggunakan pendekatan ekstensi atau delegasi. Ini dirangkum dengan baik dalam jawaban @ JaysonMinard, tetapi di sini adalah TL; DR untuk pendekatan "Delegasi" dengan API log4j2 ( PEMBARUAN : tidak perlu lagi menulis kode ini secara manual, karena telah dirilis sebagai modul resmi dari proyek log4j2, lihat di bawah). Karena log4j2, tidak seperti slf4j, mendukung pencatatan dengan Supplier, saya juga menambahkan delegasi untuk menjadikan penggunaan metode ini lebih sederhana.

import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.util.Supplier
import kotlin.reflect.companionObject

/**
 * An adapter to allow cleaner syntax when calling a logger with a Kotlin lambda. Otherwise calling the
 * method with a lambda logs the lambda itself, and not its evaluation. We specify the Lambda SAM type as a log4j2 `Supplier`
 * to avoid this. Since we are using the log4j2 api here, this does not evaluate the lambda if the level
 * is not enabled.
 */
class FunctionalLogger(val log: Logger): Logger by log {
  inline fun debug(crossinline supplier: () -> String) {
    log.debug(Supplier { supplier.invoke() })
  }

  inline fun debug(t: Throwable, crossinline supplier: () -> String) {
    log.debug(Supplier { supplier.invoke() }, t)
  }

  inline fun info(crossinline supplier: () -> String) {
    log.info(Supplier { supplier.invoke() })
  }

  inline fun info(t: Throwable, crossinline supplier: () -> String) {
    log.info(Supplier { supplier.invoke() }, t)
  }

  inline fun warn(crossinline supplier: () -> String) {
    log.warn(Supplier { supplier.invoke() })
  }

  inline fun warn(t: Throwable, crossinline supplier: () -> String) {
    log.warn(Supplier { supplier.invoke() }, t)
  }

  inline fun error(crossinline supplier: () -> String) {
    log.error(Supplier { supplier.invoke() })
  }

  inline fun error(t: Throwable, crossinline supplier: () -> String) {
    log.error(Supplier { supplier.invoke() }, t)
  }
}

/**
 * A delegate-based lazy logger instantiation. Use: `val log by logger()`.
 */
@Suppress("unused")
inline fun <reified T : Any> T.logger(): Lazy<FunctionalLogger> =
  lazy { FunctionalLogger(LogManager.getLogger(unwrapCompanionClass(T::class.java))) }

// unwrap companion class to enclosing class given a Java Class
fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> {
  return if (ofClass.enclosingClass != null && ofClass.enclosingClass.kotlin.companionObject?.java == ofClass) {
    ofClass.enclosingClass
  } else {
    ofClass
  }
}

Log4j2 Kotlin Logging API

Sebagian besar bagian sebelumnya telah disesuaikan secara langsung untuk menghasilkan modul API Logging Kotlin , yang sekarang merupakan bagian resmi dari Log4j2 (penafian: Saya adalah penulis utama). Anda dapat mengunduh ini langsung dari Apache , atau melalui Maven Central .

Penggunaan pada dasarnya seperti yang dijelaskan di atas, tetapi modul mendukung akses logger berbasis antarmuka, loggerfungsi ekstensi aktif Anyuntuk digunakan di mana thisdidefinisikan, dan fungsi logger bernama untuk digunakan di mana tidak ada thisyang didefinisikan (seperti fungsi tingkat atas).

Raman
sumber
1
Jika saya benar, Anda dapat menghindari mengetikkan nama kelas dalam solusi pertama yang Anda berikan dengan mengubah tanda tangan metode menjadi T.logger ()
IPat
1
@IPat yup, solusi pertama sengaja tidak melakukan itu untuk tetap dekat dengan "cara java". Bagian kedua dari jawabannya mencakup kasus ekstensi T.logger()- lihat bagian bawah contoh kode.
Raman
5

Anko

Anda dapat menggunakan Ankoperpustakaan untuk melakukannya. Anda akan memiliki kode seperti di bawah ini:

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

kotlin-logging

perpustakaan kotlin-logging ( proyek Github - kotlin-logging ) memungkinkan Anda untuk menulis kode logging seperti di bawah ini:

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

StaticLog

atau Anda juga bisa menggunakan tulisan kecil ini di perpustakaan Kotlin bernama StaticLog maka kode Anda akan terlihat seperti:

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

Solusi kedua mungkin lebih baik jika Anda ingin menentukan format output untuk metode logging seperti:

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

atau gunakan filter, misalnya:

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

timberkt

Jika Anda sudah menggunakan Timbercek perpustakaan logging Jake Whartontimberkt .

Perpustakaan ini dibangun di atas Kayu dengan API yang lebih mudah digunakan dari Kotlin. Alih-alih menggunakan parameter pemformatan, Anda meneruskan lambda yang hanya dievaluasi jika pesan dicatat.

Contoh kode:

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

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

Periksa juga: Masuk ke Kotlin & Android: AnkoLogger vs kotlin-logging

Semoga ini bisa membantu

piotrek1543
sumber
4

Apakah sesuatu seperti ini cocok untuk Anda?

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() }
}
Jire
sumber
1
Jawaban ini perlu penjelasan lebih lanjut, jika orang yang ditanya tidak memahami objek pendamping, mereka mungkin belum mendapatkan delegasi, dan karena itu tidak akan tahu apa yang sedang dilakukan. Plus ada sedikit penghematan dalam kode menggunakan model ini. Dan saya ragu caching di objek pendamping benar-benar merupakan peningkatan kinerja selain dalam sistem terbatas dengan CPU kecil seperti Android.
Jayson Minard
1
Apa yang ditunjukkan oleh kode ini di atas adalah penciptaan kelas yang bertindak sebagai Delegasi (lihat kotlinlang.org/docs/reference/delegated-properties.html ) yang merupakan kelas pertama LoggerDelegate Dan kemudian ia menciptakan fungsi tingkat atas yang membuat lebih mudah untuk membuat instance dari delegasi (tidak jauh lebih mudah, tetapi sedikit). Dan fungsi itu harus diubah menjadi inline. Kemudian ia menggunakan delegasi untuk menyediakan logger kapan pun diinginkan. Tetapi menyediakan satu untuk teman Foo.Companiondan bukan untuk kelas Foojadi mungkin tidak seperti yang dimaksudkan.
Jayson Minard
@JaysonMinard Saya setuju tetapi saya akan menyerahkan jawaban untuk pemirsa di masa depan yang menginginkan "perbaikan cepat" atau contoh bagaimana menerapkan ini pada proyek mereka sendiri. Saya tidak mengerti mengapa logger()fungsinya inlinejika tidak ada lambda. IntelliJ menyarankan inlining dalam kasus ini tidak perlu: i.imgur.com/YQH3NB1.png
Jire
1
Saya memasukkan jawaban Anda ke dalam milik saya, dan menyederhanakannya dengan menghapus kelas delegasi khusus dan menggunakan pembungkus Lazy. Dengan trik untuk mengetahui kelas apa yang ada di dalamnya.
Jayson Minard
1

Saya belum pernah mendengar ungkapan dalam hal ini. Semakin sederhana semakin baik, jadi saya akan menggunakan properti tingkat atas

val logger = Logger.getLogger("package_name")

Praktek ini berfungsi baik dalam Python, dan berbeda seperti Kotlin dan Python mungkin muncul, saya percaya mereka sangat mirip di sana "roh" (berbicara tentang idiom).

voddan
sumber
Tingkat atas juga dikenal sebagai tingkat paket.
Caelum
Variabel tingkat atas seperti mengatakan "gunakan variabel global" dan saya pikir hanya akan berlaku jika Anda memiliki fungsi tingkat atas lainnya yang diperlukan untuk menggunakan logger. Pada titik itu, mungkin lebih baik untuk mengirimkan logger ke fungsi utilitas apa pun yang ingin login.
Jayson Minard
1
@JaysonMinard Saya pikir meloloskan logger sebagai parameter akan menjadi anti-pola, karena pencatatan Anda tidak boleh memengaruhi API, eksternal atau internal Anda
voddan
Ok, lalu kembali ke poin saya, untuk logging level kelas menempatkan logger di kelas, bukan fungsi level atas.
Jayson Minard
1
@voddan setidaknya memberikan contoh lengkap tentang jenis logger apa yang Anda buat. val log = what?!? ... membuat logger dengan nama? Mengabaikan fakta, pertanyaan itu menunjukkan ia ingin membuat logger untuk kelas tertentuLoggerFactory.getLogger(Foo.class);
Jayson Minard
1

Bagaimana dengan fungsi ekstensi di Kelas saja? Dengan begitu Anda berakhir dengan:

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

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

Catatan - Saya belum pernah menguji ini sama sekali, jadi mungkin tidak tepat.

Graham
sumber
1

Pertama, Anda dapat menambahkan fungsi ekstensi untuk pembuatan logger.

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

Maka Anda akan dapat membuat logger menggunakan kode berikut.

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

Kedua, Anda bisa mendefinisikan antarmuka yang menyediakan logger dan implementasinya.

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)

Antarmuka ini dapat digunakan dengan cara berikut.

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

buat objek pengiring dan tandai bidang yang sesuai dengan anotasi @JvmStatic

agen pembersih
sumber
1

Sudah ada banyak jawaban bagus di sini, tetapi semuanya berkenaan dengan menambahkan logger ke kelas, tetapi bagaimana Anda melakukan itu untuk melakukan login di Fungsi Level Top?

Pendekatan ini generik dan cukup sederhana untuk bekerja dengan baik di kedua kelas, objek pendamping dan Fungsi Level Top:

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")
    }
}
Niel de Wet
sumber
0

Itulah tujuan objek pendamping, secara umum: mengganti barang statis.

Jacob Zimmerman
sumber
Objek pendamping bukan statis, ini adalah singleton yang dapat menampung anggota yang mungkin menjadi statis jika Anda menggunakan JvmStaticanotasi. Dan di masa depan mungkin ada lebih dari satu yang diizinkan. Plus jawaban ini sangat tidak membantu tanpa lebih banyak informasi atau sampel.
Jayson Minard
Saya tidak mengatakan itu statis. Saya bilang itu untuk mengganti statika. Dan mengapa ada lebih dari satu yang diizinkan? Itu tidak masuk akal. Terakhir, saya sedang terburu-buru, dan saya berpikir bahwa menunjuk ke arah yang benar akan cukup membantu.
Jacob Zimmerman
1
Objek pendamping bukan untuk mengganti statika, tetapi juga dapat membuat elemen statik. Kotlin mendukung lebih dari pada pendamping untuk sementara waktu, dan memungkinkan mereka untuk memiliki nama lain. Begitu Anda mulai menamai mereka, mereka bertindak kurang seperti statika. Dan dibiarkan terbuka di masa depan untuk memiliki lebih dari satu teman bernama. Misalnya, yang satu mungkin Factorydan yang lainHelpers
Jayson Minard
0

Contoh Slf4j, sama untuk orang lain. Ini bahkan berfungsi untuk membuat logger tingkat paket

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

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

Pemakaian:

val logger = getLogger { }
Liu Dong
sumber
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()
    }
}
tritot
sumber
0

Ini masih WIP (hampir selesai) jadi saya ingin membagikannya: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

Tujuan utama perpustakaan ini adalah untuk menegakkan gaya log tertentu di seluruh proyek. Dengan membuatnya menghasilkan kode Kotlin saya mencoba untuk mengatasi beberapa masalah yang disebutkan dalam pertanyaan ini. Sehubungan dengan pertanyaan awal yang biasanya saya cenderung lakukan adalah hanya:

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

}
Leandro
sumber
0

Anda cukup membangun "perpustakaan" utilitas Anda sendiri. Anda tidak memerlukan perpustakaan besar untuk tugas ini yang akan membuat proyek Anda lebih berat dan kompleks.

Misalnya, Anda bisa menggunakan Refleksi Kotlin untuk mendapatkan nama, jenis, dan nilai properti kelas apa pun.

Pertama-tama, pastikan meta-dependensi Anda sudah ada di build.gradle Anda:

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

Setelah itu, Anda cukup menyalin dan menempelkan kode ini ke proyek Anda:

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

Contoh penggunaan:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
rocammo
sumber