'How can I override logRequest/logResponse to log custom message in Ktor client logging?

Currently, the ktor client logging implementation is as below, and it works as intended but not what I wanted to have.


public class Logging(
    public val logger: Logger,
    public var level: LogLevel,
    public var filters: List<(HttpRequestBuilder) -> Boolean> = emptyList()
)
....
   private suspend fun logRequest(request: HttpRequestBuilder): OutgoingContent? {
        if (level.info) {
            logger.log("REQUEST: ${Url(request.url)}")
            logger.log("METHOD: ${request.method}")
        }

        val content = request.body as OutgoingContent

        if (level.headers) {
            logger.log("COMMON HEADERS")
            logHeaders(request.headers.entries())

            logger.log("CONTENT HEADERS")
            logHeaders(content.headers.entries())
        }

        return if (level.body) {
            logRequestBody(content)
        } else null
    }

Above creates a nightmare while looking at the logs because it's logging in each line. Since I'm a beginner in Kotlin and Ktor, I'd love to know the way to change the behaviour of this. Since in Kotlin, all classes are final unless opened specifically, I don't know how to approach on modifying the logRequest function behaviour. What I ideally wanted to achieve is something like below for an example.

....
private suspend fun logRequest(request: HttpRequestBuilder): OutgoingContent? {

        ...
        if (level.body) {
            val content = request.body as OutgoingContent
        return logger.log(value("url", Url(request.url)),
                          value("method", request.method),
                          value("body", content))


    }

Any help would be appreciative



Solution 1:[1]

No way to actually override a private method in a non-open class, but if you just want your logging to work differently, you're better off with a custom interceptor of the same stage in the pipeline:

       val client = HttpClient(CIO) {
            install("RequestLogging") {
                sendPipeline.intercept(HttpSendPipeline.Monitoring) {
                    logger.info(
                        "Request: {} {} {} {}",
                        context.method,
                        Url(context.url),
                        context.headers.entries(),
                        context.body
                    )
                }
            }
        }
        runBlocking {
            client.get<String>("https://google.com")
        }

This will produce the logging you want. Of course, to properly log POST you will need to do some extra work.

Solution 2:[2]

Maybe this will be useful for someone:

HttpClient() {
    install("RequestLogging") {
        responsePipeline.intercept(HttpResponsePipeline.After) {
            val request = context.request
            val response = context.response
            kermit.d(tag = "Network") {
                "${request.method} ${request.url} ${response.status}"
            }
            GlobalScope.launch(Dispatchers.Unconfined) {
                val responseBody =
                    response.content.tryReadText(response.contentType()?.charset() ?: Charsets.UTF_8)
                        ?: "[response body omitted]"
                kermit.d(tag = "Network") {
                    "${request.method} ${request.url} ${response.status}\nBODY START" +
                            "\n$responseBody" +
                            "\nBODY END"
                }
            }
        }
    }
}

You also need to add a method from the Ktor Logger.kt class to your calss with HttpClient:

internal suspend inline fun ByteReadChannel.tryReadText(charset: Charset): String? = try {
    readRemaining().readText(charset = charset)
} catch (cause: Throwable) {
    null
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Undespairable
Solution 2 Suraj Rao