All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy