Marco Garofalo
03/25/2024, 11:05 AMdave
03/25/2024, 2:37 PMevents.and(::println)
to see what is coming out in the metadataMarco Garofalo
03/25/2024, 3:06 PM./mvnw clean compile
or even ./mvnw clean test
just works.Marco Garofalo
03/25/2024, 3:36 PMobject NameEvents {
operator fun invoke(actorName: String, events: Events) = AddZipkinTraces()
.then(AddServiceName(actorName))
.then(events)
.and(::println)
}
Is basically printing kind of raw events (Incoming, Outgoing)
Incoming(uri=/players, method=POST, status=201 Created, latency=160, xUriTemplate=players)
Outgoing(uri=/players, method=POST, status=201 , latency=194, xUriTemplate=players)
Incoming(uri=/games, method=POST, status=201 Created, latency=192, xUriTemplate=games)
Outgoing(uri=/games, method=POST, status=201 , latency=194, xUriTemplate=games)
...
dave
03/25/2024, 3:37 PMMarco Garofalo
03/25/2024, 3:37 PMobject NameEvents {
operator fun invoke(actorName: String, events: Events) = AddZipkinTraces()
.then(AddServiceName(actorName))
.then(events.and(::println))
}
Yeah that worked:
MetadataEvent(event=Incoming(uri=/players, method=POST, status=201 Created, latency=159, xUriTemplate=players), metadata={traces=ZipkinTraces(traceId=TraceId(value=aba9b99fd918c0a7), spanId=TraceId(value=f92f5ed8e9aa1232), parentSpanId=TraceId(value=04b0d2ca2574003b), samplingDecision=SamplingDecision(value=1)), service=app})
MetadataEvent(event=Outgoing(uri=/players, method=POST, status=201 , latency=192, xUriTemplate=players), metadata={traces=ZipkinTraces(traceId=TraceId(value=aba9b99fd918c0a7), spanId=TraceId(value=f92f5ed8e9aa1232), parentSpanId=TraceId(value=04b0d2ca2574003b), samplingDecision=SamplingDecision(value=1)), service=player})
MetadataEvent(event=Incoming(uri=/games, method=POST, status=201 Created, latency=192, xUriTemplate=games), metadata={traces=ZipkinTraces(traceId=TraceId(value=aba9b99fd918c0a7), spanId=TraceId(value=36418ad95a2f56d7), parentSpanId=TraceId(value=04b0d2ca2574003b), samplingDecision=SamplingDecision(value=1)), service=app})
MetadataEvent(event=Outgoing(uri=/games, method=POST, status=201 , latency=194, xUriTemplate=games), metadata={traces=ZipkinTraces(traceId=TraceId(value=aba9b99fd918c0a7), spanId=TraceId(value=36418ad95a2f56d7), parentSpanId=TraceId(value=04b0d2ca2574003b), samplingDecision=SamplingDecision(value=1)), service=player})
MetadataEvent(event=Incoming(uri=/games/f48f1650-adc5-4e19-8b77-1ba45f5bfcbc, method=GET, status=200 OK, latency=66, xUriTemplate=games/{id}), metadata={traces=ZipkinTraces(traceId=TraceId(value=aba9b99fd918c0a7), spanId=TraceId(value=4e229b1d4b84cd60), parentSpanId=TraceId(value=04b0d2ca2574003b), samplingDecision=SamplingDecision(value=1)), service=app})
dave
03/25/2024, 3:38 PMMarco Garofalo
03/25/2024, 3:39 PM@startuml
title JourneyTests - winning gameplay
participant "player"
participant ""
"player" -> "": POST players
activate ""
"" -[#DarkGreen]> "player": <color:DarkGreen> 201
deactivate ""
"player" -> "": POST games
activate ""
"" -[#DarkGreen]> "player": <color:DarkGreen> 201
deactivate ""
"player" -> "": GET games/f48f1650-adc5-4e19-8b77-1ba45f5bfcbc
activate ""
"" -[#DarkGreen]> "player": <color:DarkGreen> 200
deactivate ""
"player" -> "": POST games/f48f1650-adc5-4e19-8b77-1ba45f5bfcbc/guesses
activate ""
"" -[#DarkGreen]> "player": <color:DarkGreen> 201
deactivate ""
"player" -> "": GET games/f48f1650-adc5-4e19-8b77-1ba45f5bfcbc
activate ""
"" -[#DarkGreen]> "player": <color:DarkGreen> 200
deactivate ""
"player" -> "": POST games/f48f1650-adc5-4e19-8b77-1ba45f5bfcbc/guesses
activate ""
"" -[#DarkGreen]> "player": <color:DarkGreen> 201
deactivate ""
"player" -> "": GET games/f48f1650-adc5-4e19-8b77-1ba45f5bfcbc
activate ""
"" -[#DarkGreen]> "player": <color:DarkGreen> 200
deactivate ""
"player" -> "": POST games/f48f1650-adc5-4e19-8b77-1ba45f5bfcbc/guesses
activate ""
"" -[#DarkGreen]> "player": <color:DarkGreen> 201
deactivate ""
"player" -> "": GET games/f48f1650-adc5-4e19-8b77-1ba45f5bfcbc
activate ""
"" -[#DarkGreen]> "player": <color:DarkGreen> 200
deactivate ""
@enduml
Marco Garofalo
03/25/2024, 3:39 PMMarco Garofalo
03/25/2024, 3:39 PMdave
03/25/2024, 3:41 PMdave
03/25/2024, 3:41 PMdave
03/25/2024, 3:42 PMdave
03/25/2024, 3:42 PMMarco Garofalo
03/25/2024, 3:44 PMMarco Garofalo
03/25/2024, 3:44 PMdave
03/25/2024, 3:45 PMdave
03/25/2024, 3:46 PMMarco Garofalo
03/25/2024, 3:46 PMdave
03/25/2024, 3:49 PMdave
03/25/2024, 3:52 PMfun HttpTracer(actorFrom: ActorResolver) = Tracer { eventNode, tracer ->
eventNode
.takeIf { it.event.event is HttpEvent.Outgoing }
?.toTrace(actorFrom, tracer)
?.let(::listOf) ?: emptyList()
}
private fun EventNode.toTrace(actorFrom: ActorResolver, tracer: Tracer): Trace {
val parentEvent = event.event as HttpEvent.Outgoing
return RequestResponse(
actorFrom(event),
Actor(parentEvent.uri.host, System),
parentEvent.method.name + " " + parentEvent.xUriTemplate,
parentEvent.status.toString(),
children.flatMap { tracer(it, tracer) }
)
}
dave
03/25/2024, 3:52 PMActor(parentEvent.uri.host, System)
Ivan Pavlov
03/25/2024, 4:46 PMHttpHandler
I want to treat as a separate service in test produces its own span:
fun InChildSpan(storage: ZipkinTracesStorage = ZipkinTracesStorage.THREAD_LOCAL) = Filter { next ->
{ req ->
storage.inChildSpan { updated ->
next(ZipkinTraces(updated, req))
}
}
}
For outgoing filter (Client), I actually reused org.http4k.filter.ServerFilters.RequestTracing instead of org.http4k.filter.ClientFilters#RequestTracing.
Then for matching requests and responses I modified EventNode.toTrace
like this:
val matchingIncomingServiceActor = children
.find { it.event.event is HttpEvent.Incoming }
?.let { actorFrom(it.event) }
return RequestResponse(
actorFrom(event),
matchingIncomingServiceActor ?: Actor(parentEvent.uri.host, ActorType.System),
I suspect I might be doing something in the wrong direction, but in my case it worked well, so I decided to share it heredave
03/25/2024, 5:35 PMdave
03/25/2024, 5:36 PMIvan Pavlov
03/25/2024, 7:58 PMdave
03/27/2024, 11:39 AMfun ClientStack(events: Events) = ClientFilters.RequestTracing()
.then(ReportHttpTransaction { events(Outgoing(it)) })
Note that this change also means that we should now be able to reverse-engineer a set of traces from a collective log source (such as datadog or newrelic) and generate an entire trace diagram from it. I might look at adding something to the toolbox to cover this.Ivan Pavlov
03/27/2024, 12:46 PMEventNode
concept is based on events relationship combined with spans relationship, so all events on the same level inherit the same children set and Incoming is not a child of Outgoing.
Also, I have a question regarding Span and Trace uniqueness. From what I could find in docs, spans are treated as a single piece of work within the trace. This test confused me a bit because different traces are connected via spans relationship org.http4k.tracing.TracerBulletExtraTest#traces can be grouped
. If there is parentSpan from another trace, shouldn't it be considered as a different hierarchy in org.http4k.tracing.TracerBulletKt#buildTree
?dave
03/27/2024, 1:01 PMdave
03/27/2024, 1:03 PMstorage.inChildSpan {}
)- be that a DB call an HTTP call or a sent message.Ivan Pavlov
03/27/2024, 1:10 PMdave
03/27/2024, 1:18 PMIvan Pavlov
03/27/2024, 4:22 PM