Idiomatyczny sposób logowania w Kotlinie

164

Kotlin nie ma takiego samego pojęcia o polach statycznych, jak używane w Javie. W Javie ogólnie przyjęty sposób rejestrowania to:

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

Pytanie brzmi, jaki jest idiomatyczny sposób wykonywania logowania w Kotlinie?

mchlstckl
źródło
1
Nie publikowanie tego jako odpowiedzi, ponieważ jest to dalekie od języka Java, ale rozważałem napisanie funkcji rozszerzenia na Any do logowania. Oczywiście musisz buforować Loggery, ale myślę, że byłby to dobry sposób na zrobienie tego.
mhlz
1
@mhlz Czy ta funkcja rozszerzenia nie została rozwiązana statycznie? Tak jak w przypadku, nie zostałby zastosowany do wszystkich obiektów, tylko do tych typu Any(a więc wymagającego rzucenia)?
Jire,
1
@mhlz funkcja rozszerzająca nie ma sensu, ponieważ nie będzie miała stanu, aby zachować rejestrator. Może to być rozszerzenie zwracające rejestrator, ale po co to mieć na każdej znanej klasie w systemie? Umieszczanie rozszerzeń na Any zwykle staje się później niechlujnym szumem w IDE. @Jire rozszerzenie będzie miało zastosowanie do wszystkich potomków Any, nadal zwróci poprawne this.javaClassdla każdego. Ale nie polecam tego jako rozwiązania.
Jayson Minard

Odpowiedzi:

250

W większości dojrzałego kodu Kotlin poniżej znajdziesz jeden z tych wzorców. Podejście wykorzystujące delegatów właściwości wykorzystuje moc Kotlin do tworzenia najmniejszego kodu.

Uwaga: kod tutaj jest przeznaczony, java.util.Loggingale ta sama teoria dotyczy każdej biblioteki rejestrowania

Statyczne (typowe, odpowiednik twojego kodu Java w pytaniu)

Jeśli nie możesz ufać wydajności tego wyszukiwania skrótu w systemie rejestrowania, możesz uzyskać podobne zachowanie do kodu Java, używając obiektu towarzyszącego, który może przechowywać instancję i sprawiać wrażenie statycznego.

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

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

tworzenie wyników:

26 grudnia 2015 11:28:32 org.stackoverflow.kotlin.test.MyClassfoo INFO: Witam z MyClass

Więcej o obiektach towarzyszących tutaj: Obiekty towarzyszące ... Zauważ również, że w powyższym przykładzie MyClass::class.javapobiera instancję typu Class<MyClass>dla rejestrującego, podczas gdy this.javaClasspobiera instancję typu Class<MyClass.Companion>.

Na wystąpienie klasy (wspólne)

Ale naprawdę nie ma powodu, aby unikać wywoływania i pobierania programu rejestrującego na poziomie instancji. Wspomniany przez ciebie idiomatyczny sposób w Javie jest przestarzały i opiera się na strachu przed wydajnością, podczas gdy rejestrator dla każdej klasy jest już buforowany przez prawie każdy rozsądny system logowania na świecie. Po prostu utwórz członka do przechowywania obiektu rejestrującego.

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

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

tworzenie wyników:

26 grudnia 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Witam z MyClass

Możesz przetestować wydajność zarówno dla instancji, jak i odmian klas, i sprawdzić, czy istnieje realistyczna różnica w przypadku większości aplikacji.

Delegaci nieruchomości (zwykli, najbardziej eleganccy)

Innym podejściem, które sugeruje @Jire w innej odpowiedzi, jest utworzenie delegata właściwości, którego można następnie użyć do jednolitego wykonywania logiki w dowolnej innej klasie, którą chcesz. Jest na to prostszy sposób, ponieważ Kotlin już dostarcza Lazydelegata, możemy po prostu opakować go w funkcję. Jedna sztuczka polega na tym, że jeśli chcemy poznać typ klasy aktualnie używającej delegata, robimy z tego funkcję rozszerzającą dla dowolnej klasy:

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"

Ten kod zapewnia również, że jeśli użyjesz go w obiekcie towarzyszącym, nazwa programu rejestrującego będzie taka sama, jak w przypadku użycia jej w samej klasie. Teraz możesz po prostu:

class Something {
    val LOG by logger()

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

dla instancji klasy lub jeśli chcesz, aby była bardziej statyczna z jedną instancją na klasę:

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

    }

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

Wynik wywołania foo()obu tych klas byłby następujący:

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

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

Funkcje rozszerzające (rzadkie w tym przypadku z powodu „zanieczyszczenia” dowolnej przestrzeni nazw)

Kotlin ma kilka ukrytych sztuczek, które pozwalają jeszcze zmniejszyć część tego kodu. Możesz tworzyć funkcje rozszerzające na klasach i tym samym nadawać im dodatkową funkcjonalność. Jedna sugestia w komentarzach powyżej dotyczyła rozszerzenia Anyo funkcję rejestratora. Może to powodować szum za każdym razem, gdy ktoś używa uzupełniania kodu w swoim IDE w dowolnej klasie. Ale jest sekretna korzyść z rozszerzenia Anylub innego interfejsu znaczników: możesz zasugerować, że rozszerzasz swoją własną klasę, a tym samym wykryć klasę, w której się znajdujesz. Co? Aby być mniej zagmatwanym, oto kod:

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

Teraz w klasie (lub obiekcie towarzyszącym) mogę po prostu wywołać to rozszerzenie w mojej własnej klasie:

class SomethingDifferent {
    val LOG = logger()

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

Produkcja wyników:

26 grudnia 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Hello from SomethingDifferent

Zasadniczo kod jest postrzegany jako wezwanie do rozszerzenia Something.logger(). Problem polega na tym, że poniższe mogą również być prawdziwe, powodując „zanieczyszczenie” innych klas:

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

Funkcje rozszerzeń w interfejsie znacznika (nie wiem, jak często, ale wspólny model dla „cech”)

Aby ułatwić korzystanie z rozszerzeń i zmniejszyć „zanieczyszczenie”, możesz użyć interfejsu znaczników, aby rozszerzyć:

interface Loggable {} 

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

Lub nawet uczyń metodę częścią interfejsu z domyślną implementacją:

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

I użyj jednej z tych odmian w swojej klasie:

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

Produkcja wyników:

26 grudnia 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Witam z MarkedClass

Jeśli chciałbyś wymusić utworzenie jednolitego pola do przechowywania loggera, to korzystając z tego interfejsu możesz łatwo wymagać od realizatora posiadania pola takiego jak LOG:

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

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

Teraz implementator interfejsu musi wyglądać tak:

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

Oczywiście abstrakcyjna klasa bazowa może zrobić to samo, mając opcję zarówno interfejsu, jak i klasy abstrakcyjnej implementującej ten interfejs, co zapewnia elastyczność i jednolitość:

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

Kładąc wszystko razem (mała biblioteka pomocnicza)

Oto mała biblioteka pomocnicza, dzięki której każda z powyższych opcji jest łatwa w użyciu. Kotlin często rozszerza API, aby bardziej odpowiadały Twoim upodobaniom. Albo w rozszerzeniu, albo w funkcjach najwyższego poziomu. Oto zestawienie opcji tworzenia rejestratorów oraz próbka pokazująca wszystkie odmiany:

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

Wybierz jedną z tych, które chcesz zachować, a oto wszystkie używane opcje:

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

Wszystkie 13 wystąpień rejestratorów utworzonych w tym przykładzie będzie generować tę samą nazwę programu rejestrującego i dane wyjściowe:

26 grudnia 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Witam z MixedBagOfTricks

Uwaga: Do unwrapCompanionClass()metoda gwarantuje, że nie generują rejestratora po nazwie obiektu towarzyszącego ale raczej klasy okalającego. Jest to obecnie zalecany sposób znajdowania klasy zawierającej obiekt towarzyszący. Usunięcie „ $ Companion ” z nazwy za pomocą removeSuffix()nie działa, ponieważ obiektom towarzyszącym można nadawać niestandardowe nazwy.

Jayson Minard
źródło
Niektóre struktury iniekcji zależności używają delegatów, jak widać w innej odpowiedzi tutaj. Wyglądają jak `val log: Logger by injectLogger ()` i pozwalają na wstrzyknięcie systemu logowania nieznanego kodowi. (Mój framework iniekcyjny pokazujący to jest na github.com/kohesive/injekt )
Jayson Minard,
10
Dzięki za obszerną odpowiedź. Bardzo informujące. Szczególnie podoba mi się realizacja Property Delegates (powszechna, najbardziej elegancka) .
mchlstckl
6
Myślę, że nastąpiła zmiana w składni kotlin. a rozpakowanie powinno być ofClass.enclosingClass.kotlin.objectInstance?.javaClasszamiastofClass.enclosingClass.kotlin.companionObject?.java
oshai
1
ah, nieważne, jak stwierdzono tutaj kotlinlang.org/docs/reference/reflection.html słoik odblaskowy jest wysyłany oddzielnie od standardowego biblioteki, w przypadku gradle potrzebujemy tego:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran
1
Kod służący do tworzenia „Delegatów właściwości” i „Funkcji rozszerzających” wydaje się być taki sam, z wyjątkiem typu zwracanego. Wydaje się, że przykładowy kod dla Property Delegate ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) tworzy funkcję rozszerzającą, która "".logger()jest teraz rzeczą, czy to powinno zachowywać się w ten sposób?
Mike Rylander
32

Spójrz na bibliotekę rejestrującą kotlin .
Umożliwia takie logowanie:

private val logger = KotlinLogging.logger {}

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

Lub tak:

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

Napisałem również wpis na blogu, porównując go z AnkoLogger: Logowanie się w Kotlin i Androidzie: AnkoLogger vs kotlin-logging

Zastrzeżenie: jestem opiekunem tej biblioteki.

Edycja: logowanie kotlin ma teraz obsługę wielu platform: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support

oshai
źródło
Mogę zaproponować edytować swoją odpowiedź pokazać wyjście z logger.info()połączeniami, jak Jayson zrobił w swoim przyjętym odpowiedź.
Paulo Merson
7

Jako dobry przykład implementacji logowania chciałbym wspomnieć o Anko, która używa specjalnego interfejsu, AnkoLoggerktóry klasa wymagająca logowania powinna zaimplementować. W interfejsie znajduje się kod, który generuje tag rejestrowania dla klasy. Rejestrowanie odbywa się następnie za pomocą funkcji rozszerzających, które można wywołać w ramach implementacji interfejsu bez prefiksów, a nawet tworzenia instancji rejestratora.

Nie sądzę, że jest to idiomatyczne , ale wydaje się dobrym podejściem, ponieważ wymaga minimalnego kodu, wystarczy dodać interfejs do deklaracji klasy, a otrzymasz rejestrowanie z różnymi tagami dla różnych klas.


Poniższy kod to w zasadzie AnkoLogger , uproszczony i przepisany do użytku niezależnego od Androida.

Po pierwsze, istnieje interfejs, który zachowuje się jak interfejs znaczników:

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

Pozwala jej implementacji na użycie funkcji rozszerzeń MyLoggerwewnątrz ich kodu, po prostu je wywołując this. Zawiera również tag logowania.

Następnie znajduje się ogólny punkt wejścia dla różnych metod logowania:

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

Zostanie wywołany przez metody logowania. Pobiera tag z MyLoggerimplementacji, sprawdza ustawienia rejestrowania, a następnie wywołuje jeden z dwóch programów obsługi, ten z Throwableargumentem, a drugi bez.

Następnie możesz zdefiniować dowolną liczbę metod logowania, w ten sposób:

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

Są one definiowane tylko raz w celu rejestrowania tylko wiadomości i logowania Throwable, jest to wykonywane z opcjąthrowable parametru.

Funkcje, które są przekazywane jako handleri throwableHandlermogą być różne dla różnych metod rejestrowania, na przykład mogą zapisać dziennik do pliku lub przesłać go gdzieś. isLoggingEnabledi LoggingLevelssą pomijane dla zwięzłości, ale ich używanie zapewnia jeszcze większą elastyczność.


Pozwala na następujące zastosowania:

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

Jest mała wada: do logowania do funkcji na poziomie pakietu potrzebny będzie obiekt rejestrujący:

private object MyPackageLog : MyLogger

fun myFun() {
    MyPackageLog.info("Info message")
}
Klawisz skrótu
źródło
Ta odpowiedź jest specyficzna dla Androida, a pytanie nie wspominało ani nie miało tagu Androida.
Jayson Minard
@JaysonMinard, dlaczego tak jest? To podejście ma charakter ogólny, ponieważ na przykład posiadanie unikalnego tagu rejestrowania dla każdej klasy jest przydatne również w projektach innych niż Android.
skrót klawiszowy
1
Nie jest jasne, czy mówisz „zaimplementuj coś podobnego do tego, co zrobiła Anko”, a zamiast tego bardziej przypomina „użyj Anko”… co wymaga biblioteki Android o nazwie Anko. Który ma interfejs z funkcjami rozszerzającymi, które wywołują android.util.Logrejestrowanie. Jaki był Twój zamiar? używać Anko? Zbudować coś podobnego, używając Anko jako przykładu (lepiej jest po prostu wstawić sugerowany kod w tekście i naprawić go dla systemu innego niż Android zamiast mówić „przenieś to na system inny niż Android, oto link”. Zamiast tego dodaj przykładowy kod dzwoniąc do Anko)
Jayson Minard
1
@JaysonMinard, dziękuję za komentarze, przepisałem post tak, że teraz wyjaśnia podejście, a nie odnosi się do Anko.
skrót klawiszowy
6

KISS: Dla zespołów Java przenoszących się do Kotlin

Jeśli nie masz nic przeciwko podawaniu nazwy klasy przy każdej instancji programu rejestrującego (tak jak java), możesz to uprościć, definiując to jako funkcję najwyższego poziomu gdzieś w swoim projekcie:

import org.slf4j.LoggerFactory

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

Używa parametru typu reified Kotlin .

Teraz możesz użyć tego w następujący sposób:

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

To podejście jest super proste i zbliżone do odpowiednika w Javie, ale dodaje tylko trochę cukru składniowego.

Następny krok: rozszerzenia lub pełnomocnicy

Osobiście wolę pójść o krok dalej i zastosować podejście rozszerzeń lub delegatów. Jest to ładnie podsumowane w odpowiedzi @ JaysonMinard, ale tutaj jest TL; DR dla podejścia „delegata” z log4j2 API ( AKTUALIZACJA : nie ma już potrzeby ręcznego pisania tego kodu, ponieważ został wydany jako oficjalny moduł projekt log4j2, patrz poniżej). Ponieważ log4j2, w przeciwieństwie do slf4j, obsługuje logowanie za pomocą Supplier, dodałem również delegata, aby ułatwić korzystanie z tych metod.

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

Większość poprzedniej sekcji została bezpośrednio przystosowana do tworzenia modułu Kotlin Logging API , który jest teraz oficjalną częścią Log4j2 (zastrzeżenie: jestem głównym autorem). Możesz pobrać to bezpośrednio z Apache lub przez Maven Central .

Użycie jest zasadniczo takie, jak opisano powyżej, ale moduł obsługuje zarówno dostęp do rejestratora oparty na interfejsie, funkcję loggerrozszerzającą Anydo użytku w miejscu, w którym thisjest zdefiniowana, jak i nazwaną funkcję rejestrującą do użytku, gdy nie thisjest zdefiniowane (na przykład funkcje najwyższego poziomu).

Ramana
źródło
1
Jeśli mam rację, możesz uniknąć wpisywania nazwy klasy w pierwszym rozwiązaniu, które dostarczyłeś, zmieniając sygnaturę metody na T.logger ()
IPat
1
@IPat tak, pierwsze rozwiązanie celowo nie robi tego, aby pozostać blisko „drogi java”. Druga część odpowiedzi dotyczy przypadku rozszerzenia T.logger()- patrz dolna część przykładowego kodu.
Raman
5

Anko

Możesz to zrobić za pomocą Ankobiblioteki. Miałbyś kod jak poniżej:

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

logowanie kotlin

Biblioteka kotlin-logging ( projekt Github - kotlin-logging ) umożliwia pisanie kodu do logowania, jak poniżej:

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

StaticLog

lub możesz również użyć tej małej napisanej w bibliotece Kotlin nazwanej StaticLogwtedy Twój kod będzie wyglądał następująco:

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

Drugie rozwiązanie może być lepsze, jeśli chcesz zdefiniować format wyjściowy dla metody logowania, taki jak:

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

lub użyj filtrów, na przykład:

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

timberkt

Jeśli korzystałeś już z Timberbiblioteki logowania Jake'a Whartona, sprawdź timberkt.

Ta biblioteka opiera się na Timber z interfejsem API, który jest łatwiejszy w użyciu od Kotlin. Zamiast używać parametrów formatowania, przekazujesz lambdę, która jest oceniana tylko wtedy, gdy komunikat jest rejestrowany.

Przykład kodu:

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

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

Sprawdź również: Logowanie w Kotlin i Android: AnkoLogger vs kotlin-logging

Mam nadzieję, że to pomoże

piotrek1543
źródło
4

Czy coś takiego zadziała dla Ciebie?

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
źródło
1
Ta odpowiedź wymaga więcej wyjaśnień, jeśli osoba pytająca nie rozumie obiektów towarzyszących, prawdopodobnie nie dotarła do delegatów i dlatego nie będzie wiedzieć, co to robi. Ponadto przy użyciu tego modelu uzyskuje się bardzo niewielkie oszczędności w kodzie. I wątpię, że buforowanie w obiekcie towarzyszącym jest naprawdę wzrostem wydajności innym niż w ograniczonym systemie z małym procesorem, takim jak Android.
Jayson Minard
1
Powyższy kod pokazuje utworzenie klasy działającej jako delegat (patrz kotlinlang.org/docs/reference/delegated-properties.html ), która jest pierwszą klasą, LoggerDelegate a następnie tworzy funkcję najwyższego poziomu, która łatwiej jest utworzyć instancję delegata (niewiele łatwiej, ale trochę). I tę funkcję należy zmienić na inline. Następnie używa delegata, aby zapewnić rejestrator, gdy tylko jest to pożądane. Ale zapewnia jeden dla towarzysza, Foo.Companiona nie dla klasy, Foowięc może nie jest zgodny z przeznaczeniem.
Jayson Minard
@JaysonMinard Zgadzam się, ale pozostawię odpowiedź przyszłym widzom, którzy chcą „szybkiego rozwiązania” lub przykładu, jak zastosować to do swoich własnych projektów. Nie rozumiem, dlaczego ta logger()funkcja ma działać, inlinejeśli nie ma żadnych lambd. IntelliJ sugeruje, że wstawianie w tym przypadku jest niepotrzebne: i.imgur.com/YQH3NB1.png
Jire
1
Włączyłem twoją odpowiedź do mojej i uprościłem ją, usuwając niestandardową klasę delegata i Lazyzamiast tego użyłem opakowania . Podstępem, aby dowiedzieć się, w jakiej klasie jest.
Jayson Minard
1

Nie słyszałem o żadnym idiomie w tym zakresie. Im prostsze, tym lepiej, więc użyłbym właściwości najwyższego poziomu

val logger = Logger.getLogger("package_name")

Ta praktyka dobrze sprawdza się w Pythonie i chociaż Kotlin i Python mogą się różnić, wydaje mi się, że są one dość podobne w swoim „duchu” (mówiąc o idiomach).

voddan
źródło
Najwyższy poziom jest również nazywany poziomem pakietu.
Caelum
Zmienna najwyższego poziomu to jak powiedzenie „użyj zmiennych globalnych” i myślę, że miałaby zastosowanie tylko wtedy, gdybyś miał inne funkcje najwyższego poziomu, które wymagały użycia rejestratora. Jednak w tym momencie lepiej byłoby przekazać program rejestrujący do dowolnej funkcji narzędzia, która chce rejestrować.
Jayson Minard
1
@JaysonMinard Myślę, że przekazanie loggera jako parametru byłoby anty-wzorcem, ponieważ twoje logowanie nigdy nie powinno wpływać na twoje API, zewnętrzne lub wewnętrzne
voddan
Wracając do mojego punktu, w przypadku rejestrowania na poziomie klasy należy umieścić program rejestrujący w klasie, a nie funkcję najwyższego poziomu.
Jayson Minard
1
@voddan przynajmniej podaj pełny przykład tego, jakiego typu rejestratora tworzysz. val log = what?!? ... tworzenie loggera według nazwy? Ignorując fakt, że pytanie wskazywało, że chce utworzyć rejestrator dla określonej klasyLoggerFactory.getLogger(Foo.class);
Jayson Minard
1

A co z funkcją rozszerzającą w Class? W ten sposób otrzymasz:

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

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

Uwaga - w ogóle tego nie testowałem, więc może nie być całkiem poprawne.

Graham
źródło
1

Po pierwsze, możesz dodać funkcje rozszerzające do tworzenia rejestratora.

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

Wtedy będziesz mógł stworzyć rejestrator używając poniższego kodu.

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

Po drugie, możesz zdefiniować interfejs, który zapewnia rejestrator i jego implementację mieszaną.

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)

Ten interfejs może być używany w następujący sposób.

class SomeClass : LoggerAware by loggerAware<SomeClass>() {
  // Now you can use a logger here.
}
Michael
źródło
1

utwórz obiekt towarzyszący i oznacz odpowiednie pola adnotacją @JvmStatic

środek czyszczący
źródło
1

Jest tu już wiele świetnych odpowiedzi, ale wszystkie dotyczą dodawania loggera do klasy, ale jak byś to zrobił, aby zarejestrować się w funkcjach najwyższego poziomu?

To podejście jest ogólne i na tyle proste, że działa dobrze w obu klasach, obiektach towarzyszących i funkcjach najwyższego poziomu:

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
źródło
0

Generalnie do tego służą obiekty towarzyszące: zastępowanie statycznych elementów.

Jacob Zimmerman
źródło
Obiekt towarzyszący nie jest statyczny, to singleton, który może zawierać elementy członkowskie, które mogą stać się statyczne, jeśli użyjesz JvmStaticadnotacji. W przyszłości może być więcej niż jedna dozwolona. Poza tym ta odpowiedź nie jest zbyt pomocna bez dodatkowych informacji lub próbki.
Jayson Minard
Nie powiedziałem, że to zakłócenie. Powiedziałem, że to dla wymiany statyki. A dlaczego miałoby być więcej niż jeden? To nie ma sensu. Wreszcie spieszyłem się i pomyślałem, że wskazanie we właściwym kierunku byłoby wystarczająco pomocne.
Jacob Zimmerman
1
Obiekt towarzyszący nie służy do zastępowania elementów statycznych, ale może również sprawiać, że jego elementy są statyczne. Kotlin przez jakiś czas wspierał więcej niż towarzysza i pozwala im mieć inne imiona. Gdy zaczniesz nazywać je, zachowują się mniej jak statyki. W przyszłości pozostaje otwarta, aby mieć więcej niż jednego nazwanego towarzysza. Na przykład jeden może być, Factorya drugiHelpers
Jayson Minard
0

Przykład Slf4j, to samo dla innych. Działa to nawet przy tworzeniu rejestratora na poziomie pakietu

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

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

Stosowanie:

val logger = getLogger { }
Liu Dong
źródło
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()
    }
}
trytot
źródło
0

To wciąż WIP (prawie ukończone), więc chciałbym się nim podzielić: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

Głównym celem tej biblioteki jest wymuszenie określonego stylu dziennika w całym projekcie. Poprzez wygenerowanie kodu Kotlin próbuję rozwiązać niektóre z problemów wymienionych w tym pytaniu. Jeśli chodzi o pierwotne pytanie, zwykle robię po prostu:

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

}
Leandro
źródło
0

Możesz po prostu zbudować własną „bibliotekę” narzędzi. Nie potrzebujesz dużej biblioteki do tego zadania, co sprawi, że Twój projekt będzie cięższy i złożony.

Na przykład, możesz użyć Kotlin Reflection, aby uzyskać nazwę, typ i wartość dowolnej właściwości klasy.

Przede wszystkim upewnij się, że meta-zależność została ustalona w pliku build.gradle:

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

Następnie możesz po prostu skopiować i wkleić ten kod do swojego projektu:

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

Przykład użycia:

data class Actor(val id: Int, val name: String) {
    override fun toString(): String {
        return classToString(this)
    }
}
rokammo
źródło