Search code examples
kotlinktor

Ways to log traffic in Ktor?


I'm running a ktor server as the primary api for an application I'm working on and one of the endpoints delivers big files to some clients. I would like to implement some form of logging of how many bytes of the file were sent in a given request and have not been able to figure out an accurate way of doing so.

I hope I haven't missed anything totally obvious ^^

I have for example tried to extend the LocalFileContent class and added a coroutine job to log the traffic like this

    private fun listenChannel(channel: ByteReadChannel) {
        scope.launch {
            while (!channel.isClosedForRead)
                delay(500L)
            addTraffic(device, channel.totalBytesRead)
            println("Adding ${channel.totalBytesRead} bytes of traffic to the buffer.")
            listenJob.complete()
        }
    }

Which kind of works but sometimes ends up with wildly unrealistic numbers and even more so when running behind a reverse proxy for some reason.


Solution

  • You can write a custom plugin to log how big the to-be-responded body is in bytes.

    fun Application.installLogger() {
        sendPipeline.intercept(ApplicationSendPipeline.After) { body ->
            val content = when (body) {
                is OutgoingContent.ReadChannelContent -> {
                    object : OutgoingContent.ReadChannelContent() {
                        override val contentLength = body.contentLength
                        override val contentType = body.contentType
                        override fun readFrom(): ByteReadChannel {
                            return GlobalScope.writer {
                                val copied = body.readFrom().copyTo(channel)
                                println("Returned $copied bytes")
                            }.channel
                        }
                    }
                }
    
                is OutgoingContent.ByteArrayContent -> {
                    println("Returned ${body.bytes().size} bytes")
                    body
                }
    
                else -> body
            }
    
            proceedWith(content)
        }
    }
    
    fun main() {
        embeddedServer(Netty, port = 3000) {
            install(PartialContent)
            installLogger()
            
            routing {
                get("/file") {
                    call.respondFile(File("file.jpg"))
                }
            }
        }.start(wait = true)
    }