Kotlin no tiene la misma noción de campos estáticos que la utilizada en Java. En Java, la forma generalmente aceptada de hacer registros es:
public class Foo {
private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}
La pregunta es ¿cuál es la forma idiomática de realizar el inicio de sesión en Kotlin?
kotlin
kotlin-logging
mchlstckl
fuente
fuente
Any
(por lo que necesita un yeso)?this.javaClass
para cada uno. Pero no lo recomiendo como solución.Respuestas:
En la mayoría de los códigos Kotlin maduros, encontrará uno de estos patrones a continuación. El enfoque que usa Delegados de propiedades aprovecha el poder de Kotlin para producir el código más pequeño.
Nota: el código aquí es para
java.util.Logging
pero la misma teoría se aplica a cualquier biblioteca de registroEstática (común, equivalente a su código Java en la pregunta)
Si no puede confiar en el rendimiento de esa búsqueda hash dentro del sistema de registro, puede obtener un comportamiento similar a su código Java mediante el uso de un objeto complementario que puede contener una instancia y sentirse como estático para usted.
creando salida:
Más información sobre los objetos complementarios aquí: Objetos complementarios ... También tenga en cuenta que en el ejemplo anterior
MyClass::class.java
obtiene la instancia de tipoClass<MyClass>
para el registrador, mientrasthis.javaClass
que obtendría la instancia de tipoClass<MyClass.Companion>
.Por instancia de una clase (común)
Pero, realmente no hay razón para evitar llamar y obtener un registrador a nivel de instancia. La forma idiomática de Java que mencionó está desactualizada y se basa en el miedo al rendimiento, mientras que el registrador por clase ya está almacenado en caché por casi cualquier sistema de registro razonable en el planeta. Simplemente cree un miembro para contener el objeto registrador.
creando salida:
Puede realizar pruebas de rendimiento tanto por variaciones de instancia como de clase y ver si hay una diferencia realista para la mayoría de las aplicaciones.
Delegados de propiedad (común, más elegante)
Otro enfoque, sugerido por @Jire en otra respuesta, es crear un delegado de propiedades, que luego puede usar para hacer la lógica de manera uniforme en cualquier otra clase que desee. Hay una manera más simple de hacer esto ya que Kotlin ya proporciona un
Lazy
delegado, simplemente podemos envolverlo en una función. Un truco aquí es que si queremos saber el tipo de clase que actualmente usa el delegado, lo convertimos en una función de extensión en cualquier clase:Este código también se asegura de que si lo usa en un Objeto complementario, el nombre del registrador será el mismo que si lo usara en la clase misma. Ahora puedes simplemente:
para cada instancia de clase, o si desea que sea más estático con una instancia por clase:
Y su resultado de llamar
foo()
a estas dos clases sería:Funciones de extensión (poco común en este caso debido a la "contaminación" de cualquier espacio de nombres)
Kotlin tiene algunos trucos ocultos que le permiten hacer que parte de este código sea aún más pequeño. Puede crear funciones de extensión en clases y, por lo tanto, darles funcionalidad adicional. Una sugerencia en los comentarios anteriores fue extender
Any
con una función de registrador. Esto puede crear ruido cada vez que alguien usa la finalización de código en su IDE en cualquier clase. Pero existe un beneficio secreto al extenderAny
u otra interfaz de marcador: puede implicar que está extendiendo su propia clase y, por lo tanto, detectar la clase en la que se encuentra. ¿Eh? Para ser menos confuso, aquí está el código:Ahora dentro de una clase (u objeto complementario), simplemente puedo llamar a esta extensión en mi propia clase:
Producción de salida:
Básicamente, el código se ve como una llamada a la extensión
Something.logger()
. El problema es que lo siguiente también podría ser cierto creando "contaminación" en otras clases:Funciones de extensión en la interfaz de marcador (no estoy seguro de qué tan común, pero modelo común para "rasgos")
Para hacer que el uso de extensiones sea más limpio y reducir la "contaminación", puede usar una interfaz de marcador para extender:
O incluso haga que el método forme parte de la interfaz con una implementación predeterminada:
Y use cualquiera de estas variaciones en su clase:
Producción de salida:
Si desea forzar la creación de un campo uniforme para contener el registrador, al usar esta interfaz, podría requerir fácilmente que el implementador tenga un campo como
LOG
:Ahora el implementador de la interfaz debe verse así:
Por supuesto, una clase base abstracta puede hacer lo mismo, teniendo la opción de que tanto la interfaz como una clase abstracta que implementen esa interfaz permitan flexibilidad y uniformidad:
Poniendo todo junto (una pequeña biblioteca auxiliar)
Aquí hay una pequeña biblioteca auxiliar para hacer que cualquiera de las opciones anteriores sea fácil de usar. Es común en Kotlin extender las API para que sean más de su agrado. Ya sea en extensión o funciones de nivel superior. Aquí hay una combinación para darle opciones sobre cómo crear registradores, y una muestra que muestra todas las variaciones:
Elija cualquiera de los que desea conservar, y aquí están todas las opciones en uso:
Las 13 instancias de los registradores creados en este ejemplo producirán el mismo nombre de registrador y salida:
Nota: El
unwrapCompanionClass()
método garantiza que no generemos un registrador con el nombre del objeto complementario, sino más bien la clase adjunta. Esta es la forma recomendada actual para encontrar la clase que contiene el objeto complementario. Eliminar " $ Companion " del nombre usandoremoveSuffix()
no funciona ya que los objetos complementarios pueden recibir nombres personalizados.fuente
ofClass.enclosingClass.kotlin.objectInstance?.javaClass
en lugar deofClass.enclosingClass.kotlin.companionObject?.java
compile 'org.jetbrains.kotlin:kotlin-reflect:1.0.2'
public fun <R : Any> R.logger(): Lazy<Logger> { return lazy{Logger.getLogger(unwrapCompanionClass(this.javaClass).name)}}
) parece crear una función de extensión tal que"".logger()
ahora es una cosa, ¿se supone que esto se comporta de esta manera?Echa un vistazo a la biblioteca de registro de kotlin .
Permite iniciar sesión así:
O asi:
También escribí una publicación de blog comparándolo con
AnkoLogger
: Iniciar sesión en Kotlin y Android: AnkoLogger vs kotlin-loggingDescargo de responsabilidad: soy el encargado de mantener esa biblioteca.
Editar: kotlin-logging ahora tiene soporte multiplataforma: https://github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support
fuente
logger.info()
llamadas, como lo hizo Jayson en su respuesta aceptada?Como un buen ejemplo de implementación de registro, me gustaría mencionar a Anko, que utiliza una interfaz especial
AnkoLogger
que debe implementar una clase que necesita registro. Dentro de la interfaz hay un código que genera una etiqueta de registro para la clase. Luego, el registro se realiza a través de funciones de extensión que se pueden llamar dentro de la implementación interactiva sin prefijos o incluso la creación de la instancia del registrador.No creo que esto sea idiomático , pero parece un buen enfoque, ya que requiere un código mínimo, solo agregando la interfaz a una declaración de clase, y obtienes un registro con diferentes etiquetas para diferentes clases.
El siguiente código es básicamente AnkoLogger , simplificado y reescrito para uso independiente de Android.
Primero, hay una interfaz que se comporta como una interfaz de marcador:
Permite que su implementación use las funciones de extensiones para
MyLogger
dentro de su código solo con invocarlasthis
. Y también contiene una etiqueta de registro.A continuación, hay un punto de entrada general para diferentes métodos de registro:
Se llamará por métodos de registro. Obtiene una etiqueta de
MyLogger
implementación, verifica la configuración de registro y luego llama a uno de los dos controladores, el que tieneThrowable
argumento y el que no.Luego puede definir tantos métodos de registro como desee, de esta manera:
Estos se definen una vez para registrar solo un mensaje y registrar un
Throwable
, esto se hace con unthrowable
parámetro opcional .Las funciones que se pasan como
handler
ythrowableHandler
pueden ser diferentes para diferentes métodos de registro, por ejemplo, pueden escribir el registro en un archivo o cargarlo en algún lugar.isLoggingEnabled
yLoggingLevels
se omiten por brevedad, pero usarlos proporciona aún más flexibilidad.Permite el siguiente uso:
Hay un pequeño inconveniente: se necesitará un objeto registrador para iniciar sesión en las funciones de nivel de paquete:
fuente
android.util.Log
para hacer el registro. ¿Cuál fue tu intención? usar Anko? De construir algo similar mientras se usa Anko como ejemplo (es mejor si solo pone el código sugerido en línea y lo arregla para no Android en lugar de decir "portar esto a no Android, aquí está el enlace". En su lugar, agregue un código de muestra llamando a Anko)KISS: para equipos Java que migran a Kotlin
Si no le importa proporcionar el nombre de la clase en cada instancia del registrador (al igual que Java), puede mantenerlo simple definiéndolo como una función de nivel superior en algún lugar de su proyecto:
Esto utiliza un parámetro de tipo reificado de Kotlin .
Ahora, puede usar esto de la siguiente manera:
Este enfoque es súper simple y cercano al equivalente de Java, pero solo agrega un poco de azúcar sintáctica.
Siguiente paso: extensiones o delegados
Personalmente prefiero ir un paso más allá y usar el enfoque de extensiones o delegados. Esto se resume muy bien en la respuesta de @ JaysonMinard, pero aquí está el TL; DR para el enfoque "Delegado" con la API log4j2 ( ACTUALIZACIÓN : ya no es necesario escribir este código manualmente, ya que se ha lanzado como un módulo oficial del proyecto log4j2, ver más abajo). Como log4j2, a diferencia de slf4j, admite el registro con
Supplier
's, también he agregado un delegado para simplificar el uso de estos métodos.Log4j2 Kotlin Logging API
La mayor parte de la sección anterior se ha adaptado directamente para producir el módulo API de registro de Kotlin , que ahora es una parte oficial de Log4j2 (descargo de responsabilidad: soy el autor principal). Puede descargar esto directamente desde Apache , o mediante Maven Central .
El uso es básicamente como se describe anteriormente, pero el módulo admite el acceso al registrador basado en la interfaz, una
logger
función de extensión activadaAny
para su uso dondethis
se define y una función de registrador con nombre para su uso donde nothis
se define (como las funciones de nivel superior).fuente
T.logger()
: consulte la parte inferior del ejemplo de código.Anko
Puedes usar la
Anko
biblioteca para hacerlo. Tendría un código como el siguiente:registro de kotlin
La biblioteca kotlin-logging ( proyecto Github - kotlin-logging ) le permite escribir el código de registro de la siguiente manera:
StaticLog
o también puede usar este pequeño escrito en la biblioteca de Kotlin llamado
StaticLog
entonces su código se vería así:La segunda solución podría ser mejor si desea definir un formato de salida para el método de registro como:
o use filtros, por ejemplo:
timberkt
Si ya ha utilizado la
Timber
verificación de la biblioteca de registro de Jake Whartontimberkt
.Ejemplo de código:
Verifique también: Iniciar sesión en Kotlin y Android: AnkoLogger vs kotlin-logging
Espero que ayude
fuente
¿Te gustaría algo como esto?
fuente
LoggerDelegate
Y luego está creando una función de nivel superior que está haciendo es más fácil crear una instancia del delegado (no mucho más fácil, pero sí un poco). Y esa función debe cambiarse para serinline
. Luego, utiliza el delegado para proporcionar un registrador cuando lo desee. Pero proporciona uno para el acompañanteFoo.Companion
y no para la clase,Foo
por lo que tal vez no sea el previsto.logger()
función debería serinline
si no hay lambdas presentes. IntelliJ sugiere que la alineación en este caso es innecesaria: i.imgur.com/YQH3NB1.pngLazy
. Con un truco para que sepa en qué clase está.No he oído hablar de ningún idioma al respecto. Cuanto más simple, mejor, así que usaría una propiedad de nivel superior
Esta práctica sirve bien en Python, y por muy diferentes que parezcan Kotlin y Python, creo que son bastante similares en su "espíritu" (hablando de expresiones idiomáticas).
fuente
val log = what?!?
... creando un registrador por nombre? Ignorando el hecho de que la pregunta mostraba que quería crear un registrador para una clase específicaLoggerFactory.getLogger(Foo.class);
¿Qué pasa con una función de extensión en clase en su lugar? De esa manera terminas con:
Nota: no he probado esto en absoluto, por lo que podría no ser del todo correcto.
fuente
Primero, puede agregar funciones de extensión para la creación del registrador.
Entonces podrá crear un registrador utilizando el siguiente código.
En segundo lugar, puede definir una interfaz que proporcione un registrador y su implementación mixin.
Esta interfaz se puede usar de la siguiente manera.
fuente
cree un objeto complementario y marque los campos apropiados con la anotación @JvmStatic
fuente
Ya hay muchas respuestas excelentes aquí, pero todas se refieren a agregar un registrador a una clase, pero ¿cómo haría eso para iniciar sesión en las Funciones de nivel superior?
Este enfoque es genérico y lo suficientemente simple como para funcionar bien en ambas clases, objetos complementarios y funciones de nivel superior:
fuente
Para eso son los objetos complementarios, en general: reemplazar cosas estáticas.
fuente
JvmStatic
anotación. Y en el futuro puede haber más de uno permitido. Además, esta respuesta no es muy útil sin más información o una muestra.Factory
y otroHelpers
Ejemplo de Slf4j, lo mismo para otros. Esto incluso funciona para crear un registrador de nivel de paquete
Uso:
fuente
fuente
Esto sigue siendo WIP (casi terminado), así que me gustaría compartirlo: https://github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14
El objetivo principal de esta biblioteca es aplicar un cierto estilo de registro en un proyecto. Al hacer que genere el código de Kotlin, estoy tratando de abordar algunos de los problemas mencionados en esta pregunta. Con respecto a la pregunta original, lo que suelo hacer es simplemente:
fuente
Simplemente puede construir su propia "biblioteca" de utilidades. No necesita una biblioteca grande para esta tarea, lo que hará que su proyecto sea más pesado y complejo.
Por ejemplo, puede usar Kotlin Reflection para obtener el nombre, tipo y valor de cualquier propiedad de clase.
En primer lugar, asegúrese de tener la metadependencia establecida en su build.gradle:
Luego, simplemente puede copiar y pegar este código en su proyecto:
Ejemplo de uso:
fuente