Merge branch 'LOG4J2-3086' into master
diff --git a/log4j-api-kotlin-benchmark/src/main/kotlin/org/apache/logging/log4j/kotlin/benchmark/LoggingBenchmark.kt b/log4j-api-kotlin-benchmark/src/main/kotlin/org/apache/logging/log4j/kotlin/benchmark/LoggingBenchmark.kt
index 776c4db..b892fb2 100644
--- a/log4j-api-kotlin-benchmark/src/main/kotlin/org/apache/logging/log4j/kotlin/benchmark/LoggingBenchmark.kt
+++ b/log4j-api-kotlin-benchmark/src/main/kotlin/org/apache/logging/log4j/kotlin/benchmark/LoggingBenchmark.kt
@@ -18,9 +18,9 @@
 
 import org.apache.logging.log4j.LogManager
 import org.apache.logging.log4j.Logger
+import org.apache.logging.log4j.kotlin.Logging
 import org.apache.logging.log4j.kotlin.contextName
 import org.apache.logging.log4j.kotlin.logger
-//import org.apache.logging.log4j.kotlin.logger1
 import org.apache.logging.log4j.util.Supplier
 import org.openjdk.jmh.annotations.*
 import java.util.concurrent.TimeUnit
@@ -34,7 +34,7 @@
 @Warmup(iterations = 3, time = 5)
 @Measurement(iterations = 5, time = 1)
 open class LoggingBenchmark {
-  companion object {
+  companion object: Logging {
     @JvmStatic
     val LOGGER3 = logger()
     val LOGGER4: Logger = LogManager.getLogger()
@@ -79,4 +79,14 @@
   fun companionObjectLog4jLoggerDirect() {
     LOGGER4.info("Test")
   }
+
+  @Benchmark
+  fun companionObjectLookupInterfaceFunctional() {
+    logger.info {"Test" }
+  }
+
+  @Benchmark
+  fun companionObjectLookupInterfaceDirect() {
+    logger.info("Test")
+  }
 }
diff --git a/log4j-api-kotlin/src/main/kotlin/org/apache/logging/log4j/kotlin/Logging.kt b/log4j-api-kotlin/src/main/kotlin/org/apache/logging/log4j/kotlin/Logging.kt
index 8bd11cd..ff7d76f 100644
--- a/log4j-api-kotlin/src/main/kotlin/org/apache/logging/log4j/kotlin/Logging.kt
+++ b/log4j-api-kotlin/src/main/kotlin/org/apache/logging/log4j/kotlin/Logging.kt
@@ -41,9 +41,15 @@
  * }
  *
  * ```
+ *
+ * Note that this is significantly slower than creating a logger explicitly, as it requires a lookup of the
+ * logger on each call via the property getter, since we cannot store any state in an interface. We attempt to
+ * minimize the overhead of this by caching the loggers, but according to microbenchmarks, it is still about
+ * 3.5 times slower than creating a logger once and using it (about 4.2 nanoseconds per call instead of 1.2
+ * nanoseconds).
  */
 interface Logging {
   @Suppress("unused")
   val logger
-    get() = loggerOf(this.javaClass)
+    get() = cachedLoggerOf(this.javaClass)
 }
diff --git a/log4j-api-kotlin/src/main/kotlin/org/apache/logging/log4j/kotlin/LoggingFactory.kt b/log4j-api-kotlin/src/main/kotlin/org/apache/logging/log4j/kotlin/LoggingFactory.kt
index 7488bf6..175939c 100644
--- a/log4j-api-kotlin/src/main/kotlin/org/apache/logging/log4j/kotlin/LoggingFactory.kt
+++ b/log4j-api-kotlin/src/main/kotlin/org/apache/logging/log4j/kotlin/LoggingFactory.kt
@@ -18,6 +18,7 @@
 
 import org.apache.logging.log4j.LogManager
 import org.apache.logging.log4j.spi.ExtendedLogger
+import java.util.*
 import kotlin.reflect.full.companionObject
 
 /**
@@ -67,6 +68,10 @@
   return KotlinLogger(loggerDelegateOf(ofClass))
 }
 
+fun cachedLoggerOf(ofClass: Class<*>): KotlinLogger {
+  return loggerCache.getOrPut(ofClass) { loggerOf(ofClass) }
+}
+
 // unwrap companion class to enclosing class given a Java Class
 private fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> {
   return if (ofClass.enclosingClass?.kotlin?.companionObject?.java == ofClass) {
@@ -75,3 +80,14 @@
     ofClass
   }
 }
+
+private val loggerCache = Collections.synchronizedMap(SimpleLoggerLruCache(100))
+
+/**
+ * A very simple cache for loggers, to be used with [cachedLoggerOf].
+ */
+private class SimpleLoggerLruCache(private val maxEntries: Int): LinkedHashMap<Class<*>, KotlinLogger>(maxEntries, 1f) {
+  override fun removeEldestEntry(eldest: MutableMap.MutableEntry<Class<*>, KotlinLogger>): Boolean {
+    return size > maxEntries
+  }
+}