org.http4k.tracing.TracerBullet.kt Maven / Gradle / Ivy
package org.http4k.tracing
import org.http4k.events.Event
import org.http4k.events.HttpEvent.Incoming
import org.http4k.events.HttpEvent.Outgoing
import org.http4k.events.MetadataEvent
import org.http4k.events.plus
import org.http4k.tracing.CollectEvents.Collect
import org.http4k.tracing.CollectEvents.Drop
import org.http4k.tracing.tracer.TreeWalker
/**
* Entry--point for creating Trace from a list of MetadataEvents. Provide a Tracer for each of the
* implementations that you want to support.
*/
class TracerBullet(private val tracers: List) {
constructor(vararg tracers: Tracer) : this(tracers.toList())
operator fun invoke(events: List): List =
events.filterIsInstance().removeUnrenderedEvents().buildTree()
.flatMap { event -> tracers.flatMap { it(event, Tracer.TreeWalker(tracers)) } }
}
internal fun List.buildTree() = groupBy { it.traces()?.traceId }
.mapValues { it.value.buildTreeForTrace() }
.flatMap { it.value }
private fun List.buildTreeForTrace(): List {
val eventsByParent = groupBy { it.traces()?.parentSpanId }
fun MetadataEvent.childEventNodes(): List =
eventsByParent[traces()?.spanId]
?.map { EventNode(attachIncomingFor(it), it.childEventNodes()) }
?: emptyList()
val rootEvents = filter { event ->
eventsByParent.none { it.value.any { it.traces()?.spanId == event.traces()?.parentSpanId } }
}
return rootEvents.map { EventNode(attachIncomingFor(it), it.childEventNodes()) }
}
private fun List.attachIncomingFor(candidate: MetadataEvent): MetadataEvent {
val outgoing = candidate.event
return (when (outgoing) {
is Outgoing -> {
when (val incoming = asReversed()
.firstOrNull {
val event = it.event
event is Incoming &&
it.metadata["traces"] == candidate.traces() &&
event.uri.path == event.uri.path
}
) {
null -> candidate
else -> candidate + (X_HTTP4K_INCOMING_EVENT to incoming)
}
}
else -> candidate
}) as MetadataEvent
}
private enum class CollectEvents { Collect, Drop }
private fun List.removeUnrenderedEvents(): List {
fun List.andNext(collectEvents: CollectEvents) = this to collectEvents
val collectElements = if (any { it.event == StartRendering }) Drop else Collect
return fold(Pair(listOf(), collectElements)) { acc, event ->
when (acc.second) {
Collect -> when (event.event) {
StopRendering -> acc.first.andNext(Drop)
else -> (acc.first + event).andNext(Collect)
}
Drop -> when (event.event) {
StartRendering -> acc.first.andNext(Collect)
else -> acc.first.andNext(Drop)
}
}
}.first
}