main.wisp.logging.WispQueuedLogCollector.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of wisp-logging-testing Show documentation
Show all versions of wisp-logging-testing Show documentation
a module containing logging helpers for testing
package wisp.logging
import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.UnsynchronizedAppenderBase
import org.slf4j.LoggerFactory
import java.lang.Thread.sleep
import java.util.concurrent.LinkedBlockingDeque
import java.util.concurrent.TimeUnit
import kotlin.reflect.KClass
class WispQueuedLogCollector : LogCollector {
private val queue = LinkedBlockingDeque()
private var wasStarted = false
private val appender = object : UnsynchronizedAppenderBase() {
override fun append(event: ILoggingEvent) {
queue.put(event)
}
}
override fun takeMessages(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
): List = takeMessages(loggerClass, minLevel, pattern, consumeUnmatchedLogs = true)
override fun takeMessages(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?,
consumeUnmatchedLogs: Boolean,
): List = takeEvents(
loggerClass,
minLevel,
pattern,
consumeUnmatchedLogs
).map { it.message }
override fun takeMessage(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
): String = takeMessage(loggerClass, minLevel, pattern, consumeUnmatchedLogs = true)
override fun takeMessage(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?,
consumeUnmatchedLogs: Boolean,
): String = takeEvent(loggerClass, minLevel, pattern, consumeUnmatchedLogs).message
override fun takeEvents(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
): List = takeEvents(loggerClass, minLevel, pattern, consumeUnmatchedLogs = true)
override fun takeEvents(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?,
consumeUnmatchedLogs: Boolean,
): List {
sleep(100) // Give the logger some time to flush events.
if (!consumeUnmatchedLogs) {
return takeEventsNonConsuming(loggerClass, minLevel, pattern)
}
return takeEventsConsuming(loggerClass, minLevel, pattern)
}
override fun takeEvent(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
): ILoggingEvent = takeEvent(loggerClass, minLevel, pattern, consumeUnmatchedLogs = true)
/**
* Takes all events currently on the queue which match the constraints.
* Leaves behind events that don't match.
*/
private fun takeEventsNonConsuming(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
): List {
val resultList = queue.filter { matchLog(it, loggerClass, minLevel, pattern) }.toList()
queue.removeAll(resultList)
return resultList
}
/**
* Consumes the whole queue of events, returning only those which match the constraints.
*/
private fun takeEventsConsuming(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
): MutableList {
val result = mutableListOf()
while (queue.isNotEmpty()) {
val event = takeOrNull(loggerClass, minLevel, pattern)
if (event != null) result += event
}
return result
}
override fun takeEvent(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?,
consumeUnmatchedLogs: Boolean,
): ILoggingEvent {
require(wasStarted) { "not collecting logs: did you forget to start the service?" }
if (!consumeUnmatchedLogs) {
return takeNonConsuming(loggerClass, minLevel, pattern)
}
return takeConsuming(loggerClass, minLevel, pattern)
}
/**
* Takes an event matching the constraints, leaving behind all other non-matching events
* in the queue. Throws if there are no matching events.
*/
private fun takeNonConsuming(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
): ILoggingEvent {
for (i in 1..5) {
if (queue.isEmpty()) sleep(100) else continue
}
require(queue.isNotEmpty()) { "no events to take!" }
val event = queue.find { matchLog(it, loggerClass, minLevel, pattern) }
?: error("no matching events for (logger=$loggerClass, minLevel=$minLevel, pattern=$pattern)")
queue.remove(event)
return event
}
/**
* Takes a matching event and consumes all events in the queue preceding the first match.
* Waits forever until there is a matching event.
*/
private fun takeConsuming(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
): ILoggingEvent {
while (true) {
val event = takeOrNull(loggerClass, minLevel, pattern)
if (event != null) return event
}
}
/** Takes an event. Returns it if it meets the constraints and null if it doesn't. */
private fun takeOrNull(
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
): ILoggingEvent? {
require(wasStarted) { "not collecting logs: did you forget to start the service?" }
val event = queue.poll(500, TimeUnit.MILLISECONDS)
?: throw IllegalArgumentException("no events to take!")
return if (matchLog(event, loggerClass, minLevel, pattern)) event else null
}
private fun matchLog(
event: ILoggingEvent,
loggerClass: KClass<*>?,
minLevel: Level,
pattern: Regex?
) = when {
loggerClass != null && loggerClass.qualifiedName != event.loggerName -> false
event.level.toInt() < minLevel.toInt() -> false
pattern != null && !pattern.containsMatchIn(event.message.toString()) -> false
else -> true
}
fun startUp() {
appender.start()
wasStarted = true
val rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)
(rootLogger as? Logger)?.addAppender(appender)
}
fun shutDown() {
val rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)
(rootLogger as? Logger)?.detachAppender(appender)
appender.stop()
}
}