Maneira linguística de fazer logon no Kotlin

164

O Kotlin não possui a mesma noção de campos estáticos usada em Java. Em Java, a maneira geralmente aceita de fazer log é:

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

A questão é qual é a maneira idiomática de executar o log no Kotlin?

mchlstckl
fonte
1
Não postar isso como resposta, porque está muito longe do caminho Java, mas considerei escrever uma função de extensão em Qualquer para log. Você precisa armazenar em cache os loggers, é claro, mas acho que essa seria uma boa maneira de fazer isso.
mhlz
1
@mhlz Essa função de extensão não seria estaticamente resolvida? Por exemplo, não seria aplicado a todos os objetos, apenas àqueles do tipo Any(necessitando, portanto, de uma conversão)?
Jire
1
@mhlz uma função de extensão não faz sentido porque não terá estado para manter um logger por perto. Pode ser uma extensão para retornar um criador de logs, mas por que ter isso em todas as classes conhecidas no sistema? A colocação de extensões em Any tende a se tornar um ruído superficial no IDE posteriormente. @Jire a extensão será aplicada a todos os descendentes de Qualquer um, ainda retornará o correto this.javaClasspara cada um. Mas não estou recomendando isso como uma solução.
Jayson Minard

Respostas:

250

Na maioria dos códigos Kotlin maduros, você encontrará um desses padrões abaixo. A abordagem usando Delegados de Propriedade tira proveito do poder do Kotlin para produzir o menor código.

Nota: o código aqui é para, java.util.Loggingmas a mesma teoria se aplica a qualquer biblioteca de log

Estático (comum, equivalente ao seu código Java na pergunta)

Se você não puder confiar no desempenho dessa pesquisa de hash dentro do sistema de log, poderá obter um comportamento semelhante ao seu código Java usando um objeto complementar que pode conter uma instância e parecer estático para você.

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

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

criando saída:

26 de dezembro de 2015 11:28:32 org.stackoverflow.kotlin.test.MyClassfoo INFO: Olá, do MyClass

Mais sobre objetos complementares aqui: Objetos complementares ... Observe também que na amostra acima MyClass::class.javaobtém a instância do tipo Class<MyClass>para o criador de logs, enquanto this.javaClassque a instância do tipo Class<MyClass.Companion>.

Por instância de uma classe (comum)

Porém, não há realmente nenhuma razão para evitar ligar e obter um criador de logs no nível da instância. A maneira idiomática de Java que você mencionou está desatualizada e baseada no medo de desempenho, enquanto o criador de logs por classe já está armazenado em cache por quase qualquer sistema de criação de log razoável no planeta. Basta criar um membro para armazenar o objeto logger.

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

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

criando saída:

26 de dezembro de 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Olá, do MyClass

Você pode testar o desempenho por variações por instância e por classe e verificar se há uma diferença realista para a maioria dos aplicativos.

Delegados de propriedades (comuns, mais elegantes)

Outra abordagem, sugerida por @Jire em outra resposta, é criar um delegado de propriedade, que você pode usar para executar a lógica de maneira uniforme em qualquer outra classe que desejar. Existe uma maneira mais simples de fazer isso, já que o Kotlin já fornece um Lazydelegado, podemos apenas envolvê-lo em uma função. Um truque aqui é que, se queremos saber o tipo de classe atualmente usando o delegado, fazemos dela uma função de extensão em qualquer classe:

fun <R : Any> R.logger(): Lazy<Logger> {
    return lazy { Logger.getLogger(unwrapCompanionClass(this.javaClass).name) }
}
// see code for unwrapCompanionClass() below in "Putting it all Together section"

Esse código também garante que, se você o usar em um Objeto complementar, o nome do criador de logs será o mesmo que se você o utilizasse na própria classe. Agora você pode simplesmente:

class Something {
    val LOG by logger()

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

por instância de classe ou se você quiser que seja mais estático com uma instância por classe:

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

    }

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

E sua saída ao chamar foo()essas duas classes seria:

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

26 de dezembro de 2015 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO: Olá, de SomethingElse

Funções de extensão (incomum neste caso devido à "poluição" de qualquer espaço para nome)

O Kotlin possui alguns truques ocultos que permitem diminuir ainda mais esse código. Você pode criar funções de extensão nas classes e, portanto, fornecer funcionalidades adicionais. Uma sugestão nos comentários acima foi estender Anycom uma função de logger. Isso pode gerar ruído sempre que alguém usa o preenchimento de código no IDE em qualquer classe. Mas há um benefício secreto em estender Anyou em alguma outra interface de marcador: você pode sugerir que está estendendo sua própria classe e, portanto, detectar a classe em que está. Hã? Para ser menos confuso, aqui está o código:

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

Agora, dentro de uma classe (ou objeto complementar), posso simplesmente chamar essa extensão em minha própria classe:

class SomethingDifferent {
    val LOG = logger()

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

Produção de saída:

26 de dezembro de 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Olá, de SomethingDifferent

Basicamente, o código é visto como uma chamada para extensão Something.logger(). O problema é que o seguinte também pode ser verdadeiro, criando "poluição" em outras classes:

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

Funções de extensão na interface do marcador (não tenho certeza de quão comum, mas modelo comum para "características")

Para tornar o uso de extensões mais limpo e reduzir a "poluição", você pode usar uma interface de marcador para estender:

interface Loggable {} 

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

Ou até mesmo torne o método parte da interface com uma implementação padrão:

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

E use uma destas variações em sua classe:

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

Produção de saída:

26 de dezembro de 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Olá, de MarkedClass

Se você deseja forçar a criação de um campo uniforme para reter o criador de logs, ao usar essa interface, você pode facilmente exigir que o implementador tenha um campo como LOG:

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

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

Agora o implementador da interface deve ficar assim:

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

Obviamente, uma classe base abstrata pode fazer o mesmo, tendo a opção da interface e de uma classe abstrata implementando essa interface, permitindo flexibilidade e uniformidade:

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

Juntando tudo (uma pequena biblioteca auxiliar)

Aqui está uma pequena biblioteca auxiliar para facilitar a utilização de qualquer uma das opções acima. É comum no Kotlin estender APIs para torná-las mais ao seu gosto. Nas funções de extensão ou de nível superior. Aqui está uma combinação para fornecer opções de como criar loggers e uma amostra mostrando todas as variações:

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

Escolha o que você deseja manter e aqui estão todas as opções em uso:

class MixedBagOfTricks {
    companion object {
        val LOG1 by lazyLogger()          // lazy delegate, 1 instance per class
        val LOG2 by injectLogger()        // immediate, 1 instance per class
        val LOG3 = logger()               // immediate, 1 instance per class
        val LOG4 = logger(this.javaClass) // immediate, 1 instance per class
    }

    val LOG5 by lazyLogger()              // lazy delegate, 1 per instance of class
    val LOG6 by injectLogger()            // immediate, 1 per instance of class
    val LOG7 = logger()                   // immediate, 1 per instance of class
    val LOG8 = logger(this.javaClass)     // immediate, 1 instance per class
}

val LOG9 = logger(MixedBagOfTricks::class)  // top level variable in package

// or alternative for marker interface in class
class MixedBagOfTricks : Loggable {
    val LOG10 = logger()
}

// or alternative for marker interface in companion object of class
class MixedBagOfTricks {
    companion object : Loggable {
        val LOG11 = logger()
    }
}

// or alternative for abstract base class for companion object of class
class MixedBagOfTricks {
    companion object: WithLogging() {} // instance 12

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

// or alternative for abstract base class for our actual class
class MixedBagOfTricks : WithLogging() { // instance 13
    fun foo() {
       LOG.info("Hello from MixedBagOfTricks")
    }
}

Todas as 13 instâncias dos criadores de logs criados nesta amostra produzirão o mesmo nome e a saída:

26 de dezembro de 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Olá, de MixedBagOfTricks

Nota: O unwrapCompanionClass()método garante que não geremos um criador de logs com o nome do objeto complementar, mas a classe envolvente. Esta é a maneira atualmente recomendada de encontrar a classe que contém o objeto complementar. Retirar " $ Companion " do nome usando removeSuffix()não funciona, pois os objetos complementares podem receber nomes personalizados.

Jayson Minard
fonte
Algumas estruturas de injeção de dependência usam delegados como você vê em outra resposta aqui. Eles se parecem com `val log: Logger por injectLogger ()` e permitem que o sistema de log seja injetado e desconhecido do código em uso. (My estrutura de injeção mostrando esta é a github.com/kohesive/injekt )
Jayson Minard
10
Obrigado pela resposta extensa. Muito informativo. Eu particularmente gosto da implementação de Delegados de Propriedade (comum, mais elegante) .
Mchlstckl
6
Eu acho que houve uma mudança na sintaxe do kotlin. eo unwrap deve ser ofClass.enclosingClass.kotlin.objectInstance?.javaClassem vez deofClass.enclosingClass.kotlin.companionObject?.java
oshai
1
ah, não importa, como afirmou aqui kotlinlang.org/docs/reference/reflection.html a refletir frasco é enviado separadamente do stdlib, para Gradle precisamos isto:compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
Hoang Tran
1
O código para criar os 'Delegados de propriedades' e as 'Funções de extensão' parece ser o mesmo, exceto para o tipo de retorno. O exemplo de código do Property Delegate ( public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}) parece criar uma função de extensão que "".logger()agora é uma coisa, isso deveria se comportar dessa maneira?
Mike Rylander
32

Dê uma olhada na biblioteca de registro kotlin .
Permite registrar assim:

private val logger = KotlinLogging.logger {}

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

Ou assim:

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

Também escrevi uma postagem no blog comparando-a com AnkoLogger: Logon no Kotlin e Android: AnkoLogger vs kotlin-logging

Disclaimer: Eu sou o mantenedor dessa biblioteca.

Edit: o kotlin-logging agora tem suporte a multiplataforma: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support

oshai
fonte
Posso sugerir que você edite sua resposta para mostrar a saída de logger.info()chamadas, como Jayson fez em sua resposta aceita.
Paulo Merson
7

Como um bom exemplo de implementação de log, eu gostaria de mencionar o Anko, que usa uma interface especial AnkoLoggerque uma classe que precisa de log deve implementar. Dentro da interface, há um código que gera uma marca de log para a classe. O log é feito por meio de funções de extensão que podem ser chamadas dentro da implementação de interace sem prefixos ou mesmo criação da instância do criador de logs.

Eu não acho que isso seja idiomático , mas parece uma boa abordagem, pois requer código mínimo, apenas adicionando a interface a uma declaração de classe e você obtém log com tags diferentes para diferentes classes.


O código abaixo é basicamente o AnkoLogger , simplificado e reescrito para uso independente do Android.

Primeiro, há uma interface que se comporta como uma interface de marcador:

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

Ele permite que sua implementação use as funções de extensões para MyLoggerdentro de seu código, apenas chamando-as this. E também contém marca de log.

Em seguida, há um ponto de entrada geral para diferentes métodos de log:

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

Será chamado pelos métodos de log. Ele obtém uma tag da MyLoggerimplementação, verifica as configurações de log e depois chama um dos dois manipuladores, aquele com Throwableargumento e o sem.

Em seguida, você pode definir quantos métodos de log desejar, desta maneira:

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

Eles são definidos uma vez para registrar apenas uma mensagem e registrar Throwabletambém, isso é feito com o throwableparâmetro opcional .

As funções que são passadas handlere throwableHandlerpodem ser diferentes para diferentes métodos de log, por exemplo, elas podem gravar o log em um arquivo ou carregá-lo em algum lugar. isLoggingEnablede LoggingLevelssão omitidos por questões de concisão, mas usá-los fornece ainda mais flexibilidade.


Permite o seguinte uso:

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

Há uma pequena desvantagem: um objeto de logger será necessário para efetuar logon nas funções no nível do pacote:

private object MyPackageLog : MyLogger

fun myFun() {
    MyPackageLog.info("Info message")
}
tecla de atalho
fonte
Esta resposta é específica para Android, e a pergunta não mencionou nem possui uma tag Android.
Jayson Minard
@JaysonMinard, por que é isso? Essa abordagem é de uso geral, pois, por exemplo, ter uma tag de log exclusiva para cada classe também é útil em projetos que não são Android.
hotkey
1
Não está claro que você está dizendo "implemente algo semelhante ao que Anko fez" e, em vez disso, parece mais "use Anko" ... que, em seguida, requer uma biblioteca Android chamada Anko. Que possui uma interface que possui funções de extensão que chamam android.util.Logpara fazer o log. Qual foi a sua intenção? usar Anko? De criar algo semelhante ao usar o Anko como exemplo (é melhor colocar o código sugerido em linha e corrigi-lo para não Android, em vez de dizer "port isto para não Android, aqui está o link". Em vez disso, adicione código de exemplo (Anko)
Jayson Minard
1
@JaysonMinard, obrigado por seus comentários, reescrevi a postagem para que agora explique a abordagem em vez de fazer referência à Anko.
hotkey
6

BEIJO: Para equipes Java migrando para o Kotlin

Se você não se importa de fornecer o nome da classe em cada instanciação do criador de logs (como java), você pode simplificar, definindo isso como uma função de nível superior em algum lugar do seu projeto:

import org.slf4j.LoggerFactory

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

Isso usa um parâmetro do tipo reificado Kotlin .

Agora, você pode usar isso da seguinte maneira:

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

Essa abordagem é super simples e próxima do equivalente a java, mas apenas adiciona um pouco de açúcar sintático.

Próxima etapa: extensões ou delegados

Pessoalmente, prefiro dar um passo adiante e usar a abordagem de extensões ou delegados. Isso está bem resumido na resposta da @ JaysonMinard, mas aqui está o TL; DR para a abordagem "Delegate" com a API log4j2 ( UPDATE : não é mais necessário escrever esse código manualmente, pois foi lançado como um módulo oficial do projeto log4j2, veja abaixo). Como o log4j2, diferentemente do slf4j, suporta o log com Supplier's, também adicionei um delegado para simplificar o uso desses métodos.

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

API de log Kotlin do Log4j2

A maior parte da seção anterior foi diretamente adaptada para produzir o módulo da API Kotlin Logging , que agora é uma parte oficial do Log4j2 (isenção de responsabilidade: eu sou o autor principal). Você pode fazer o download diretamente do Apache ou via Maven Central .

O uso é basicamente como descrito acima, mas o módulo suporta o acesso ao criador de logs com base na interface, uma loggerfunção de extensão ativada Anypara uso onde thisé definido e uma função de criador de logs nomeado para uso onde não thisé definido (como funções de nível superior).

Raman
fonte
1
Se eu estiver certo, você é capaz de evitar digitar o nome da classe na primeira solução que você forneceu, alterando a assinatura do método para T.logger ()
IPAT
1
@IPat yup, a primeira solução intencionalmente não faz isso para permanecer próxima à "maneira java". A segunda parte da resposta cobre o caso de extensão T.logger()- veja a parte inferior do exemplo de código.
Raman
5

Anko

Você pode usar a Ankobiblioteca para fazer isso. Você teria código como abaixo:

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

kotlin-log

A biblioteca kotlin-logging ( projeto Github - kotlin-logging ) permite escrever o código de registro como abaixo:

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

StaticLog

ou você também pode usar este pequeno escrito na biblioteca Kotlin chamado, StaticLogentão seu código se parecerá com:

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

A segunda solução pode ser melhor se você desejar definir um formato de saída para o método de log como:

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

ou use filtros, por exemplo:

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

timberkt

Se você já usou a Timberbiblioteca de registros de Jake Wharton, verifique timberkt.

Esta biblioteca baseia-se no Timber com uma API mais fácil de usar do Kotlin. Em vez de usar parâmetros de formatação, você passa um lambda que é avaliado apenas se a mensagem estiver registrada.

Exemplo de código:

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

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

Verifique também: Logon no Kotlin e Android: AnkoLogger vs kotlin-logging

Espero que ajude

piotrek1543
fonte
4

Algo assim funcionaria para você?

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
fonte
1
Essa resposta precisa de mais explicações: se a pessoa que perguntar não entender os objetos complementares, provavelmente não terá conseguido delegar e, portanto, não saberá o que está fazendo. Além disso, há pouquíssima economia de código usando esse modelo. E duvido que o armazenamento em cache no objeto complementar seja realmente um ganho de desempenho que não seja em um sistema restrito com CPU pequena como o Android.
Jayson Minard
1
O que este código acima está mostrando é a criação de uma classe que atua como um Delegado (consulte kotlinlang.org/docs/reference/delegated-properties.html ), que é a primeira classe LoggerDelegate e, em seguida, está criando uma função de nível superior que está criando é mais fácil criar uma instância do delegado (não muito mais fácil, mas um pouco). E essa função deve ser alterada para ser inline. Em seguida, ele usa o delegado para fornecer um criador de logs sempre que desejado. Mas ele fornece um para o acompanhante Foo.Companione não para a turma, por Fooisso talvez não seja o pretendido.
Jayson Minard
@JaysonMinard Concordo, mas deixarei a resposta para os futuros espectadores que desejam uma "solução rápida" ou um exemplo de como aplicar isso a seus próprios projetos. Eu não entendo por que a logger()função deve ser inlinese não houver lambdas presentes. IntelliJ sugere que inlining neste caso é desnecessário: i.imgur.com/YQH3NB1.png
Jire
1
Incorporei sua resposta à minha e a simplifiquei removendo a classe delegada personalizada e, em Lazyvez disso, usei um wrapper . Com um truque para saber em que classe está.
Jayson Minard
1

Não ouvi falar em nenhum idioma a esse respeito. Quanto mais simples, melhor, então eu usaria uma propriedade de nível superior

val logger = Logger.getLogger("package_name")

Essa prática serve bem em Python e, por mais diferente que Kotlin e Python possa parecer, acredito que sejam bastante semelhantes no "espírito" (falando de expressões idiomáticas).

voddan
fonte
O nível superior também é conhecido como nível do pacote.
Caelum
Uma variável de nível superior é como dizer "usar variáveis ​​globais" e acho que só seria aplicável se você tivesse outras funções de nível superior que precisassem usar um criador de logs. Nesse ponto, porém, pode ser melhor passar um registrador para qualquer função de utilitário que deseja registrar.
Jayson Minard
1
@JaysonMinard Eu acho que passar logger como parâmetro seria um anti-padrão, porque o seu logging nunca deve afetar sua API, externo ou interno
voddan
Ok, voltando ao meu ponto, para o log no nível da classe, coloque o logger na classe, não uma função de nível superior.
Jayson Minard
1
O @voddan pelo menos fornece um exemplo completo de que tipo de criador de logs você está criando. val log = what?!? ... criando um logger por nome? Ignorando o fato de que a pergunta mostrava que ele estava querendo criar um logger para uma classe específicaLoggerFactory.getLogger(Foo.class);
Jayson Minard
1

Que tal uma função de extensão na Classe? Dessa forma, você acaba com:

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

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

Nota - Eu não testei isso, então pode não estar certo.

Graham
fonte
1

Primeiro, você pode adicionar funções de extensão para a criação do criador de logs.

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

Em seguida, você poderá criar um logger usando o código a seguir.

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

Segundo, você pode definir uma interface que forneça um criador de logs e sua implementação mixin.

interface LoggerAware {
  val logger: Logger
}

class LoggerAwareMixin(containerClass: Class<*>) : LoggerAware {
  override val logger: Logger = LoggerFactory.getLogger(containerClass)
}

inline fun <reified T : Any> loggerAware() = LoggerAwareMixin(T::class.java)

Essa interface pode ser usada da seguinte maneira.

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

crie um objeto complementar e marque os campos apropriados com a anotação @JvmStatic

agente de limpeza
fonte
1

Já existem muitas ótimas respostas aqui, mas todas dizem respeito à adição de um criador de logs a uma classe, mas como você faria isso para fazer o log de funções de nível superior?

Essa abordagem é genérica e simples o suficiente para funcionar bem em ambas as classes, objetos complementares e funções de nível superior:

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
fonte
0

É para isso que servem os objetos complementares, em geral: substituir coisas estáticas.

Jacob Zimmerman
fonte
Um objeto complementar não é estático, é um singleton que pode conter membros que podem se tornar estáticos se você usar JvmStaticanotação. E no futuro pode haver mais de um permitido. Além disso, esta resposta não é muito útil sem mais informações ou uma amostra.
Jayson Minard
Eu não disse que era estático. Eu disse que era para substituir estática. E por que haveria mais de um permitido? Isso não faz sentido. Por fim, eu estava com pressa e pensei que apontar na direção certa seria útil o suficiente.
27515 Jacob Zimmerman
1
Um objeto complementar não é para substituir estática, mas também pode tornar seus elementos estáticos. Kotlin apoiou mais do que o companheiro por um tempo e permite que eles tenham outros nomes. Quando você começa a nomeá-los, eles agem menos como estática. E fica em aberto no futuro ter mais de um companheiro nomeado. Por exemplo, um pode serFactory e outraHelpers
Jayson Minard
0

Exemplo Slf4j, o mesmo para os outros. Isso funciona mesmo para criar criadores de logs no nível do pacote

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

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

Uso:

val logger = getLogger { }
Liu Dong
fonte
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
fonte
0

Isso ainda é WIP (quase concluído), então eu gostaria de compartilhá-lo: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14

O principal objetivo desta biblioteca é aplicar um certo estilo de log em um projeto. Ao gerar o código Kotlin, estou tentando resolver alguns dos problemas mencionados nesta pergunta. Com relação à pergunta original, o que costumo fazer é simplesmente:

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

}
Leandro
fonte
0

Você pode simplesmente criar sua própria "biblioteca" de utilitários. Você não precisa de uma grande biblioteca para esta tarefa, que tornará seu projeto mais pesado e complexo.

Por exemplo, você pode usar o Kotlin Reflection para obter o nome, o tipo e o valor de qualquer propriedade de classe.

Primeiro de tudo, verifique se a meta-dependência foi definida em seu build.gradle:

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

Depois, você pode simplesmente copiar e colar este código no seu projeto:

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

Exemplo de uso:

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