How to log requests in ktor http client?

Viewed 13313

I got something like this:

private val client = HttpClient {
    install(JsonFeature) {
        serializer = GsonSerializer()
    }
    install(ExpectSuccess)
}

and make request like

  private fun HttpRequestBuilder.apiUrl(path: String, userId: String? = null) {
    header(HttpHeaders.CacheControl, "no-cache")
    url {
        takeFrom(endPoint)
        encodedPath = path
    }
}

but I need to check request and response body, is there any way to do it? in console/in file?

6 Answers

You can achieve this with the Logging feature.

First add the dependency:

implementation "io.ktor:ktor-client-logging-native:$ktor_version"

Then install the feature:

private val client = HttpClient {
   install(Logging) {
      logger = Logger.DEFAULT
      level = LogLevel.ALL
   }
}

Bonus:

If you need to have multiple HttpClient instances throughout your application and you want to reuse some of the configuration, then you can create an extension function and add the common logic in there. For example:

fun HttpClientConfig<*>.default() {
    install(Logging) {
        logger = Logger.DEFAULT
        level = LogLevel.ALL
    }

    // Add all the common configuration here.
}

And then initialize your HttpClient like this:

private val client = HttpClient {
   default()
}

I ran into this as well. I switched to using the Ktor OkHttp client as I'm familiar with the logging mechanism there.

Update your pom.xml or gradle.build to include that client (copy/paste from the Ktor site) and also add the OkHttp Logging Interceptor (again, copy/paste from that site). Current version is 3.12.0.

Now configure the client with

val client = HttpClient(OkHttp) {
    engine {
        val loggingInterceptor = HttpLoggingInterceptor()
        loggingInterceptor.level = Level.BODY
        addInterceptor(loggingInterceptor)
    }
}

Regardless of which client you use or framework you are on, you can implement your own logger like so:

private val client = HttpClient {
    // Other configurations...
    install(Logging) {
        logger = CustomHttpLogger()
        level = LogLevel.BODY
    }
}

Where CustomHttpLogger is any class that implements the ktor Logger interface, like so:

import io.ktor.client.features.logging.Logger

class CustomHttpLogger(): Logger {
    override fun log(message: String) {
        Log.d("loggerTag", message) // Or whatever logging system you want here
    }
}

You can read more about the Logger interface in the documentation here or in the source code here

It looks like we should handle the response in HttpReceivePipeline. We could clone the origin response and use it for logging purpose:

scope.receivePipeline.intercept(HttpReceivePipeline.Before) { response ->
    val (loggingContent, responseContent) = response.content.split(scope)

    launch {
        val callForLog = DelegatedCall(loggingContent, context, scope, shouldClose = false)
        ....
    }
    ...
}

The example implementation could be found here: https://github.com/ktorio/ktor/blob/00369bf3e41e91d366279fce57b8f4c97f927fd4/ktor-client/ktor-client-core/src/io/ktor/client/features/observer/ResponseObserver.kt and would be available in next minor release as a client feature.

btw: we could implement the same scheme for the request.

A custom structured log can be created with the HttpSend plugin

Ktor 2.x:

client.plugin(HttpSend).intercept { request ->
    val call = execute(request)
    val response = call.response
    val durationMillis = response.responseTime.timestamp - response.requestTime.timestamp
    Log.i("NETWORK", "[${response.status.value}] ${request.url.build()} ($durationMillis ms)")
    call
}

Ktor 1.x:

client.config {
    install(HttpSend) {
        intercept { call, _ ->
            val request = call.request
            val response = call.response
            val durationMillis = response.responseTime.timestamp - response.requestTime.timestamp
            Log.i("NETWORK", "[${response.status.value}] ${request.url} ($durationMillis ms)")
            call
        }
    }
}

Check out Kotlin Logging, https://github.com/MicroUtils/kotlin-logging it isused by a lot of open source frameworks and takes care of all the prety printing.

You can use it simply like this:

 private val logger = KotlinLogging.logger {  }
 logger.info { "MYLOGGER INFO" }
 logger.warn { "MYLOGGER WARNING" }
 logger.error { "MYLOGGER ERROR" }

This will print the messages on the console.

Related