LoggingCallback.kt
package com.hexagonkt.http.server.callbacks
import com.hexagonkt.core.logging.Logger
import com.hexagonkt.http.model.*
import com.hexagonkt.http.handlers.HttpContext
import java.lang.System.Logger.Level
import kotlin.system.measureNanoTime
/**
* Callback that logs server requests and responses.
*/
class LoggingCallback(
private val level: Level = Level.INFO,
private val logger: Logger = Logger(LoggingCallback::class),
private val includeHeaders: Boolean = false,
private val includeBody: Boolean = true,
) : (HttpContext) -> HttpContext {
override fun invoke(context: HttpContext): HttpContext {
var result: HttpContext
logger.log(level) { details(context.request) }
val ns = measureNanoTime { result = context.next() }
logger.log(level) { details(context.request, result.response, ns) }
return result
}
internal fun details(m: HttpRequestPort): String {
val headers = if (includeHeaders) {
val accept = Header("accept", m.accept.joinToString(", ") { it.text })
val contentType = Header("content-type", m.contentType?.text ?: "")
(m.headers - "accept" - "content-type" + accept + contentType).format()
}
else {
""
}
val body = m.formatBody()
return "${m.method} ${m.path}$headers$body".trim()
}
internal fun details(n: HttpRequestPort, m: HttpResponsePort, ns: Long): String {
val headers = if (includeHeaders) {
val contentType = Header("content-type", m.contentType?.text ?: "")
(m.headers - "content-type" + contentType).format()
} else {
""
}
val path = "${n.method} ${n.path}"
val result = "${m.status.type}(${m.status.code})"
val time = "(${ns / 10e5} ms)"
val body = m.formatBody()
return "$path -> $result $time$headers$body".trim()
}
private fun HttpMessage.formatBody(): String =
if (includeBody) "\n\n${bodyString()}" else ""
private fun Headers.format(): String =
httpFields
.filter { (_, v) -> v.strings().any { it.isNotBlank() } }
.map { (k, v) -> "$k: ${v.strings().joinToString(", ")}" }
.joinToString("\n", prefix = "\n\n")
}