Kotlin与Java使用的静态字段概念不同。在Java中,通常公认的日志记录方式是:
public class Foo {
private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}
问题是在Kotlin中执行日志记录的惯用方式是什么?
Any
(因此需要强制转换)吗?
this.javaClass
为每个后裔返回正确的名称。但我不建议将其作为解决方案。
Kotlin与Java使用的静态字段概念不同。在Java中,通常公认的日志记录方式是:
public class Foo {
private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
}
问题是在Kotlin中执行日志记录的惯用方式是什么?
Any
(因此需要强制转换)吗?
this.javaClass
为每个后裔返回正确的名称。但我不建议将其作为解决方案。
Answers:
在大多数成熟的Kotlin代码中,您将在下面找到这些模式之一。使用属性委托的方法利用了Kotlin的功能来生成最小的代码。
注意:这里的代码是针对的,java.util.Logging
但是相同的理论适用于任何日志记录库
类似静态的(常见的,等同于问题中的Java代码)
如果您不信任日志记录系统中该哈希查找的性能,则可以通过使用一个伴侣对象来获得与Java代码类似的行为,该伴侣对象可以容纳一个实例并且对您来说像是静态的。
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
创建输出:
2015年12月26日上午11:28:32
org.stackoverflow.kotlin.test.MyClass
foo信息:MyClass的您好
有关随播对象的更多信息,请参见:随播对象 ...还应注意,在上面的示例中,它MyClass::class.java
获取Class<MyClass>
记录器的类型实例,而this.javaClass
将获取type的实例Class<MyClass.Companion>
。
每个类别的实例(常见)
但是,实际上没有理由避免在实例级别调用和获取记录器。您提到的惯用Java方式已经过时,并且出于对性能的担心,而该类中的每个记录器已经被地球上几乎所有合理的记录系统缓存。只需创建一个成员来保存记录器对象即可。
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
创建输出:
2015年12月26日11:28:44 org.stackoverflow.kotlin.test.MyClass foo信息:您好,来自MyClass
您可以对每个实例和每个类的变体进行性能测试,并查看大多数应用程序是否存在实际差异。
财产代表(普通,最优雅)
@Jire在另一个答案中建议的另一种方法是创建一个属性委托,然后您可以使用它在所需的任何其他类中统一执行逻辑。由于Kotlin Lazy
已经提供了委托,因此有一种更简单的方法,我们可以将其包装在函数中。这里的一个技巧是,如果我们想知道当前使用委托的类的类型,可以将其设为任何类的扩展函数:
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"
此代码还确保如果在Companion Object中使用它,则记录器名称将与在类本身上使用的记录器名称相同。现在您可以简单地:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
每个类实例,或者如果您希望每个类一个实例更静态:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
通过调用foo()
这两个类的输出将是:
2015年12月26日,上午11:30:55 org.stackoverflow.kotlin.test.foo信息:您好!
2015年12月26日,上午11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo信息:SomethingElse的问好
扩展功能(在这种情况下不常见,因为任何名称空间的“污染”)
Kotlin有一些隐藏的技巧,可以使您使这些代码变得更小。您可以在类上创建扩展函数,从而为它们提供其他功能。上面评论中的一个建议是Any
使用记录器功能进行扩展。每当有人在任何类的IDE中使用代码完成功能时,这都会产生噪音。但是扩展Any
或其他一些标记接口有一个秘密好处:您可以暗示您正在扩展自己的类,因此可以检测您所在的类。??为了减少混乱,下面是代码:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
现在在一个类(或同伴对象)中,我可以在自己的类中简单地调用此扩展名:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
产生输出:
2015年12月26日,上午11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO:Hello from SomethingDifferent
基本上,该代码被视为对extension的调用Something.logger()
。问题在于,在其他类上创建“污染”也可能是以下情况:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
标记接口上的扩展功能(不确定通用性,但是“特征”的通用模型)
为了使扩展程序的使用更干净并减少“污染”,可以使用标记器接口来扩展:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
甚至使用默认实现使方法成为接口的一部分:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
并在您的课程中使用以下任一变体:
class MarkedClass: Loggable {
val LOG = logger()
}
产生输出:
2015年12月26日,上午11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo信息:MarkedClass您好
如果您想强制创建一个统一的字段来保存记录器,那么在使用此接口时,您可以轻松地要求实现者拥有一个字段,例如LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
现在,接口的实现者必须如下所示:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
当然,抽象基类可以做到这一点,可以同时选择接口和实现该接口的抽象类,从而实现灵活性和统一性:
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!
}
}
放在一起(一个小的帮助程序库)
这是一个小的帮助程序库,使上面的任何选项都易于使用。在Kotlin中,通常会扩展API以使其更符合您的喜好。扩展功能或顶级功能。以下是为您提供有关如何创建记录器的选项的混合以及显示所有变化的示例:
// 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()
}
选择您想要保留的任何一个,以下是所有正在使用的选项:
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")
}
}
在此示例中创建的所有13个记录器实例将产生相同的记录器名称,并输出:
2015年12月26日上午11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo信息:您好,来自MixedBagOfTricks
注意: 该unwrapCompanionClass()
方法确保我们不会生成以伴随对象命名的记录器,而是生成封闭的类。这是当前推荐的查找包含伴随对象的类的方法。从名称中删除“ $ Companion ” removeSuffix()
不起作用,因为可以为伴随对象指定自定义名称。
ofClass.enclosingClass.kotlin.objectInstance?.javaClass
改用解包ofClass.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)}}
)的代码示例似乎可以创建扩展功能,例如"".logger()
成为一种事物,这应该以这种方式表现吗?
看看kotlin日志记录库。
它允许这样记录:
private val logger = KotlinLogging.logger {}
class Foo {
logger.info{"wohoooo $wohoooo"}
}
或者像这样:
class FooWithLogging {
companion object: KLogging()
fun bar() {
logger.info{"wohoooo $wohoooo"}
}
}
我还写了一篇博客文章,将其与AnkoLogger
以下内容进行了比较:登录Kotlin和Android:AnkoLogger vs kotlin-logging
免责声明:我是该库的维护者。
编辑:kotlin-logging现在具有多平台支持:https : //github.com/MicroUtils/kotlin-logging/wiki/Multiplatform-support
logger.info()
呼叫,杰森在他接受的答案一样。
作为记录实现的一个很好的例子,我想提到使用特殊接口的AnkoAnkoLogger
,需要记录的类应该实现该。在界面内部,有代码为该类生成日志记录标记。然后通过扩展功能完成记录,这些功能可以在interace实现内调用而无需前缀,甚至无需创建记录器实例。
我不认为这是惯用的,但是这似乎是一个好方法,因为它需要最少的代码,只需将接口添加到类声明中,就可以使用针对不同类的不同标签进行记录。
首先,有一个行为类似于标记接口的接口:
interface MyLogger {
val tag: String get() = javaClass.simpleName
}
它允许其实现将扩展功能用于MyLogger
其代码内部,而只需对其进行调用this
。并且它还包含日志记录标记。
接下来,有一个用于不同日志记录方法的常规入口点:
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)
}
}
将通过日志记录方法调用它。它从MyLogger
实现中获取一个标记,检查日志记录设置,然后调用两个处理程序之一,一个带有Throwable
参数,另一个不带参数。
然后,您可以按照这种方式定义任意数量的日志记录方法:
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()
})
这些定义一次,仅记录一条消息和记录一条消息。 Throwable
,这是使用可选throwable
参数完成的。
这是因为传递的功能handler
,并throwableHandler
可以针对不同测井方法不同,例如,他们可以写日志文件,或上传它的地方。isLoggingEnabled
并LoggingLevels
不再赘述,但使用起来提供了更大的灵活性。
class MyClass : MyLogger {
fun myFun() {
info("Info message")
}
}
有一个小缺点:记录程序包级功能将需要一个logger对象:
private object MyPackageLog : MyLogger
fun myFun() {
MyPackageLog.info("Info message")
}
android.util.Log
日志记录。你的意图是什么?使用Anko?以Anko为例进行类似的构建(最好将内联建议的代码并针对非Android进行修复,而不是说“将其移植到非Android,这是链接”。)而是添加示例代码打电话给Anko)
如果您不介意在记录器的每个实例上都提供类名(就像Java),则可以通过将其定义为项目中某个地方的顶级函数来使其简单:
import org.slf4j.LoggerFactory
inline fun <reified T:Any> logger() = LoggerFactory.getLogger(T::class.java)
这使用Kotlin 修饰类型参数。
现在,您可以按以下方式使用它:
class SomeClass {
// or within a companion object for one-instance-per-class
val log = logger<SomeClass>()
...
}
这种方法非常简单,并且与Java等效,但是仅添加了一些语法糖。
我个人更喜欢更进一步,并使用扩展或委托方法。@JaysonMinard的答案对此进行了很好的总结,但这是使用log4j2 API的“委托”方法的TL; DR(更新:无需再手动编写此代码,因为它已作为log4j2项目,请参见下文)。由于log4j2与slf4j不同,它支持使用Supplier
s进行日志记录,因此我还添加了一个委托,以简化使用这些方法的过程。
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
}
}
上一节的大部分内容已经直接改编为产生Kotlin Logging API模块,该模块现在是Log4j2的正式部分(免责声明:我是第一作者)。您可以直接从Apache或通过Maven Central下载。
用法基本上如上所述,但是该模块既支持基于接口的记录器访问,也支持在定义位置使用的logger
扩展功能,Any
以及在this
未定义位置使用的命名记录器功能this
(例如顶级功能)。
T.logger()
-请参见代码示例的底部。
您可以使用Anko
库来做到这一点。您将具有以下代码:
class MyActivity : Activity(), AnkoLogger {
private fun someMethod() {
info("This is my first app and it's awesome")
debug(1234)
warn("Warning")
}
}
kotlin-logging(Github项目-kotlin-logging)库允许您编写如下的日志记录代码:
class FooWithLogging {
companion object: KLogging()
fun bar() {
logger.info{"Item $item"}
}
}
或者,您也可以使用Kotlin库中写的这个名为的小StaticLog
代码,那么您的代码将类似于:
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")\
如果您想为日志记录方法定义输出格式,则第二种解决方案可能会更好:
Log.newFormat {
line(date("yyyy-MM-dd HH:mm:ss"), space, level, text("/"), tag, space(2), message, space(2), occurrence)
}
或使用过滤器,例如:
Log.filterTag = "filterTag"
Log.info("This log will be filtered out", "otherTag")
Log.info("This log has the right tag", "filterTag")
如果您已经使用过Jake Wharton的Timber
日志记录库check timberkt
。
该库基于Timber构建,并提供了Kotlin易于使用的API。您传递的lambda不会使用格式参数,而仅在记录消息时才会对其进行评估。
代码示例:
// Standard timber
Timber.d("%d %s", intVar + 3, stringFun())
// Kotlin extensions
Timber.d { "${intVar + 3} ${stringFun()}" }
// or
d { "${intVar + 3} ${stringFun()}" }
另请检查:登录Kotlin和Android:AnkoLogger与kotlin-logging
希望对你有帮助
这样的事情对您有用吗?
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() }
}
LoggerDelegate
,然后它创建了一个顶层函数,创建委托的实例更加容易(不是那么容易,但是有点儿)。并且该功能应更改为inline
。然后,只要需要,它就使用委托人提供一个记录器。但是它为同伴Foo.Companion
而不是为班级提供了一个,Foo
因此可能不是预期的。
logger()
函数应该是inline
这样。IntelliJ建议在这种情况下不必进行内联:i.imgur.com/YQH3NB1.png
Lazy
。通过技巧让它知道它属于哪个类。
我听说这方面没有成语。越简单越好,所以我将使用顶级属性
val logger = Logger.getLogger("package_name")
这种做法在Python中效果很好,而且与Kotlin和Python可能出现的不同之处,我相信它们在“精神”(说成语)方面非常相似。
val log = what?!?
...通过名称创建记录器?忽略了这个问题表明他想为特定班级创建记录器的事实LoggerFactory.getLogger(Foo.class);
首先,您可以添加扩展功能以创建记录器。
inline fun <reified T : Any> getLogger() = LoggerFactory.getLogger(T::class.java)
fun <T : Any> T.getLogger() = LoggerFactory.getLogger(javaClass)
然后,您将可以使用以下代码创建记录器。
private val logger1 = getLogger<SomeClass>()
private val logger2 = getLogger()
其次,您可以定义一个提供记录器及其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)
可以按以下方式使用此接口。
class SomeClass : LoggerAware by loggerAware<SomeClass>() {
// Now you can use a logger here.
}
这里已经有很多不错的答案,但是所有这些答案都涉及到在类中添加记录器,但是您将如何做才能在顶级函数中进行记录呢?
这种方法通用且简单,足以在两个类,伴随对象和顶级函数中正常工作:
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")
}
}
通常,这就是伴随对象的用途:替换静态内容。
JvmStatic
注释),成员可能变为静态。将来可能会有不止一个。另外,如果没有更多信息或样本,此答案不是很有帮助。
Factory
与另Helpers
这仍然是WIP(几乎已完成),所以我想分享一下:https : //github.com/leandronunes85/log-format-enforcer#kotlin-soon-to-come-in-version-14
该库的主要目标是在整个项目中实施某种日志样式。通过使其生成Kotlin代码,我试图解决此问题中提到的一些问题。关于原始问题,我通常倾向于做的是:
private val LOG = LogFormatEnforcer.loggerFor<Foo>()
class Foo {
}
您可以简单地构建自己的实用程序“库”。您不需要大型库来执行此任务,这会使您的项目更重,更复杂。
例如,您可以使用Kotlin Reflection获取任何类属性的名称,类型和值。
首先,确保在build.gradle中解决了元依赖问题:
dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
}
之后,您可以简单地将此代码复制并粘贴到您的项目中:
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()
}
}
}
用法示例:
data class Actor(val id: Int, val name: String) {
override fun toString(): String {
return classToString(this)
}
}