Skip to content

Koriit/ktor-logging

Repository files navigation

Ktor Logging

Build CodeFactor ktlint

Maven Central GitHub

Warning
From version 0.4.0 all package names have been renamed to match new artifact group id.

Logging plugin for Ktor server. Allows logging of performance, requests and responses.

The plugin correlates call logs and therefore installation of CallId is required.

Installation

module {
    // install CallId...

    install(DoubleReceive) {
        receiveEntireContent = true
    }

    install(Logging) {
        logRequests = true
        logResponses = true
        logFullUrl = true
        logBody = true
        logHeaders = true
        filterPath("/api", "/version", "/openapi")
    }
}

Filtering

You can select which calls you want to log with filter and filterPath configuration functions.

Logging payloads

Logging of request/response payloads requires installation of DoubleReceive plugin with receiveEntireContent = true.

Warning
Payloads may contain sensitive data not suitable for logging. That’s why this is disabled by default.
Example
2019-12-03 11:21:27.086 INFO  [atcher-worker-5] (Logging.kt:87)  : Received request:
GET http://localhost:8080/api/entities/102 HTTP/1.1
Host: localhost:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.5.9 (Java/11.0.4)
Accept-Encoding: gzip,deflate

2019-12-03 11:21:27.265 INFO  [atcher-worker-5] (Logging.kt:81)  : 253 ms - 404 - GET http://localhost:8080/api/entities/102
2019-12-03 11:21:27.269 INFO  [atcher-worker-5] (Logging.kt:104) : Sent response:
HTTP/1.1 404 Not Found
X-Request-ID: dc3d3804-23b5-4b9f-a8d4-714eec6b4c55
Date: Tue, 03 Dec 2019 10:21:27 GMT
Server: kotlin-template/0.1-SNAPSHOT
Content-Length: 271
Content-Type: application/json; charset=UTF-8
Connection: keep-alive

{"status":404,"type":"my.app.exceptions.ItemNotFoundException","title":"Not Found","detail":"Could not find entity id=102","instance":"dc3d3804-23b5-4b9f-a8d4-714eec6b4c55","path":"/api/entities/102","timestamp":"2019-12-03T11:21:27.186445+01:00"}

Extending

Plugin is fully open for customizations. You can base on it your own logging plugin, which you are encouraged to do.

For example with logback’s logstash encoder you can enhance your log JSONs like that:

override fun logPerformance(call: ApplicationCall) {
    val duration = System.currentTimeMillis() - call.attributes[startTimeKey]
    val route = call.attributes.getOrNull(routeKey)?.parent.toString()
    val method = call.request.httpMethod.value
    val requestURI = if (logFullUrl) call.request.origin.uri else call.request.path()

    val requestInfo = mapOf(
            "method" to method,
            "protocol" to call.request.origin.version,
            "url" to call.request.origin.run { "$scheme://$host:$port$requestURI" },
            "api" to "$method $route",
            "route" to route,
            "remoteHost" to call.request.origin.remoteHost,
            "contentType" to call.request.contentType().toString(),
            "contentLength" to call.request.headers[HttpHeaders.ContentLength]?.toInt()
    )

    val responseInfo = mapOf(
            "status" to call.response.status()?.value,
            "contentType" to call.response.headers[HttpHeaders.ContentType],
            "contentLength" to call.response.headers[HttpHeaders.ContentLength]?.toInt()
    )

    val additionalInfo = mapOf(
            "request" to requestInfo,
            "response" to responseInfo
    )

    log.info("{} ms - {} - {} {}", value("duration", duration), responseInfo["status"], method, requestInfo["url"], appendEntries(additionalInfo))
}
{
  "@timestamp": "2019-11-28T13:24:47.832+01:00",
  "@version": "1",
  "message": "3 ms - 200 - GET http://localhost:8080/api/entities",
  "logger_name": "koriit.kotlin.app.Logging",
  "thread_name": "worker-4",
  "level": "INFO",
  "level_value": 20000,
  "correlationId": "db4f0ccb-0ba8-45f8-a21b-6adb83c6bd86",
  "duration": 3,
  "request": {
    "method": "GET",
    "protocol": "HTTP/1.1",
    "url": "http://localhost:8080/api/entities",
    "api": "GET /api/entities",
    "route": "/api/entities",
    "remoteHost": "unknown",
    "contentType": "*/*",
    "contentLength": null
  },
  "response": {
    "status": 200,
    "contentType": "application/json; charset=UTF-8",
    "contentLength": 5606
  }
}