Skip to content

Commit

Permalink
Curl Logging interceptor (#141)
Browse files Browse the repository at this point in the history
Logging interceptor remastered: introduced curl and http patterns
  • Loading branch information
doyaaaaaken authored and rybalkinsd committed Aug 4, 2019
1 parent c6dfcba commit c88850f
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 11 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@ val forkedClient = defaultHttpClient.fork {
A Request Logging Interceptor.

Parameters:
1. `log: (String) -> Unit = ::println`: function as a parameter to consume the log message. It defaults to `println`. Logs Request body when present.
1. `strategy: LoggingStrategy = HttpLoggingStrategy()`: logging options (format type ... etc). (HttpLoggingStrategy: http request format / CurlLoggingStrategy: curl command format)
2. `log: (String) -> Unit = ::println`: function as a parameter to consume the log message. It defaults to `println`. Logs Request body when present.

Usage:

Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,36 @@
package io.github.rybalkinsd.kohttp.interceptors

import io.github.rybalkinsd.kohttp.ext.asSequence
import io.github.rybalkinsd.kohttp.interceptors.logging.HttpLoggingStrategy
import io.github.rybalkinsd.kohttp.interceptors.logging.LoggingStrategy
import okhttp3.Interceptor
import okhttp3.Response
import okio.Buffer

/**
* Request Logging Interceptor
*
* Logs HTTP requests.
*
* @param strategy logging option (format type...etc).
* HttpLoggingStrategy: http request format strategy
* CurlLoggingStrategy: curl command format strategy
* @param log function to consume log message
*
* Sample Output: [2019-01-28T04:17:42.885Z] GET 200 - 1743ms https://postman-echo.com/get
*
* @since 0.8.0
* @author gokul
*/
class LoggingInterceptor(private val log: (String) -> Unit = ::println) : Interceptor {
class LoggingInterceptor(
private val strategy: LoggingStrategy = HttpLoggingStrategy(),
private val log: (String) -> Unit = ::println
) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val startTime = System.currentTimeMillis()
return chain.proceed(request).also { response ->
log("${request.method()} ${response.code()} - ${System.currentTimeMillis() - startTime}ms ${request.url()}")

request.headers().asSequence().forEach { log("${it.name}: ${it.value}") }

Buffer().use {
request.body()?.writeTo(it)
log(it.readByteString().utf8())
}
strategy.log(request, log)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package io.github.rybalkinsd.kohttp.interceptors.logging

import io.github.rybalkinsd.kohttp.ext.asSequence
import okhttp3.Headers
import okhttp3.Request
import okhttp3.RequestBody
import okio.Buffer

/**
* Logging strategy as curl command format
*
* @author doyaaaaaken
*/
class CurlLoggingStrategy : LoggingStrategy {

override fun log(request: Request, logging: (String) -> Unit) {
val command = buildCurlCommand(request)
logging("╭--- cURL command ---")
logging(command)
logging("╰--- (copy and paste the above line to a terminal)")
}

fun buildCurlCommand(request: Request): String {
return buildString {
append("curl -X ${request.method()}")
append(buildCurlHeaderOption(request.headers()))
append(buildCurlBodyOption(request.body()))
append(" \"${request.url()}\"")
}
}

private fun buildCurlHeaderOption(headers: Headers): String {
return headers.asSequence().map { (name, value) ->
val trimmedValue = value.trimDoubleQuote()
" -H \"$name: $trimmedValue\""
}.joinToString("")
}

private fun buildCurlBodyOption(body: RequestBody?): String {
if (body == null) return ""
val buffer = Buffer().apply { body.writeTo(this) }
return " --data $'${buffer.readUtf8().replace("\n", "\\n")}'"
}

private fun String.trimDoubleQuote(): String {
return if (startsWith('"') && endsWith('"')) {
substring(1, length - 1)
} else {
this
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package io.github.rybalkinsd.kohttp.interceptors.logging

import io.github.rybalkinsd.kohttp.ext.asSequence
import okhttp3.Request
import okio.Buffer

/**
* Logging strategy as http request format
*
* @author doyaaaaaken
*/
class HttpLoggingStrategy : LoggingStrategy {

override fun log(request: Request, logging: (String) -> Unit) {
//TODO: output http request format logging.
// see https://github.com/rybalkinsd/kohttp/pull/141#issuecomment-516428314
logging("╭--- http request output ---")
request.headers().asSequence().forEach { logging("${it.name}: ${it.value}") }
Buffer().use {
request.body()?.writeTo(it)
logging(it.readByteString().utf8())
}
logging("╰---------------------------")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.github.rybalkinsd.kohttp.interceptors.logging

import okhttp3.Request

/**
* Logging strategy
*
* @author doyaaaaaken
*/
interface LoggingStrategy {

fun log(request: Request, logging: (String) -> Unit)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import io.github.rybalkinsd.kohttp.client.defaultHttpClient
import io.github.rybalkinsd.kohttp.client.fork
import io.github.rybalkinsd.kohttp.dsl.upload
import io.github.rybalkinsd.kohttp.ext.httpGet
import io.github.rybalkinsd.kohttp.interceptors.logging.CurlLoggingStrategy
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
Expand Down Expand Up @@ -55,4 +56,16 @@ class LoggingInterceptorTest {
assertEquals(200, response.code())
}

@Test
fun `curl command logging happens without exceptions `() {
val client = defaultHttpClient.fork {
interceptors {
+LoggingInterceptor(CurlLoggingStrategy())
}
}

val response = "https://postman-echo.com/get".httpGet(client)

assertEquals(200, response.code())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.github.rybalkinsd.kohttp.interceptors.logging

import okhttp3.FormBody
import okhttp3.Request
import org.junit.Test
import kotlin.test.assertEquals

/**
* @author doyaaaaaken
*/
class CurlLoggingStrategyTest {

@Test
fun `build curl command of simple get request`() {
val request = Request.Builder().url("https://postman-echo.com/get").build()
val actual = CurlLoggingStrategy().buildCurlCommand(request)

assertEquals("curl -X GET \"https://postman-echo.com/get\"", actual)
}

@Test
fun `build curl command of request with header`() {
val request = Request.Builder()
.url("https://postman-echo.com/get")
.header("Cookie", "foo=6; bar=28")
.header("Content-Type", "\"application/json\"")
.build()
val actual = CurlLoggingStrategy().buildCurlCommand(request)

assertEquals("curl -X GET -H \"Cookie: foo=6; bar=28\" -H \"Content-Type: application/json\" \"https://postman-echo.com/get\"", actual)
}

@Test
fun `build curl command of request with body`() {
val request = Request.Builder()
.url("https://postman-echo.com/post")
.post(FormBody.Builder().add("foo", "123").add("bar", "abc").build())
.build()
val actual = CurlLoggingStrategy().buildCurlCommand(request)

assertEquals("curl -X POST --data \$'foo=123&bar=abc' \"https://postman-echo.com/post\"", actual)
}
}

0 comments on commit c88850f

Please sign in to comment.