A Ktor installable plugin (formerly called feature) for particiapting in Zipkin distributed tracing.
You are building microservices in Ktor and want to instrument them for tracing using Zipkin.
The feature reads incoming HTTP tracing headers, either multiple headers:
X-B3-TraceIdX-B3-SpanIdX-B3-ParentSpanIdX-B3-SampledorX-B3-Flags
or a single b3 header. See zipkin-b3-propagation
for details.
The feature initiates tracing if configured, optionally only for specified paths.
The feature returns tracing headers in the response that match those received or initiated.
The feature propagates tracing into downstream client requests where installed into Ktor clients.
Optionally you can install the current tracing information into Slf4j mapped diagnostic context (MDC).
There are two parts: a server feature and a client feature.
An example is:
fun Application.module() {
install(ZipkinIds) {
initiateTracePathPrefixes = arrayOf("/api")
b3Header = true
}
// Other feature installations
routing {
get("/health") {
call.respond(mapOf("status" to "UP"))
}
post("/api/v1/service") {
try {
val message = call.receive<Message>()
val result = call.processMessage(message)
call.respond(HttpStatusCode.OK, result)
} catch (e: Exception) {
call.respond(HttpStatusCode.BadRequest, e.message ?: "There was an error processing your request")
}
}
}
}In this example, the feature is installed so it will initiate tracing only for requests starting with
"/api".
- Requests to
/healthwithout any tracing headers do not initiate tracing. - Requests to
/api/v1/servicewithout tracing headers initiate tracing. - Tracing initiated by the service respond with
b3headers and use them in client calls. - If requests contain tracing headers, those headers and their type (either
b3orX-B3-*) will be maintained. - Tracing information is stored in the ApplicationCall instance.
To propagate tracing information into client calls, define extension functions on ApplicationCall, for example:
suspend fun ApplicationCall.processMessage(message: Message) {
val url = "https://provider.com/api/v1/other-service"
try {
val client = HttpClient(CIO) {
tracingParts?.let { it ->
install(ClientIds) {
tracingParts = it
}
}
}
val response = client.post<String>(url) {
body = TextContent(
json.writeValueAsString(OutgoingMessage(message)),
contentType = ContentType.Application.Json
)
}
} catch (e: Exception) {
logger.error("Client request failure", e)
}
}In this example:
- When the client is being constructed, if the
ApplicationCallcontains atracingPartsattribute, install theClientIdsfeature with that attribute.
Install the keys into logging MDC in the application as part of call logging:
install(CallLogging) {
level = Level.INFO
zipkinMdc()
filter { call -> call.request.path().startsWith("/") }
}Keys available are:
b3IdtraceIdspanIdparentSpanId
They can be set in logback.xml like so:
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] [%X{b3Id}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
repositories {
// ...
maven { url 'https://kotlin.bintray.com/ktor' }
mavenCentral()
}
dependencies {
// ...
implementation 'com.michaelstrasser:ktor-features-zipkin:0.2.12'
}