From 108fdef3eca8a88ec3774c465fdb0a3c764aa936 Mon Sep 17 00:00:00 2001 From: Diego Parra Date: Mon, 15 Oct 2018 15:33:07 -0300 Subject: Implement "b3 single" header format (#551) Implement 'b3 single' header format for Span propagation over HTTP --- kamon-core/src/main/resources/reference.conf | 3 +- .../main/scala/kamon/trace/SpanPropagation.scala | 101 ++++++++++++++++++++- 2 files changed, 99 insertions(+), 5 deletions(-) (limited to 'kamon-core') diff --git a/kamon-core/src/main/resources/reference.conf b/kamon-core/src/main/resources/reference.conf index a35225d4..2fa3d33b 100644 --- a/kamon-core/src/main/resources/reference.conf +++ b/kamon-core/src/main/resources/reference.conf @@ -192,12 +192,14 @@ kamon { # Specify mappings between Context keys and the Propagation.EntryReader[HeaderReader] implementation in charge # of reading them from the incoming HTTP request into the Context. incoming { + # kamon.trace.SpanPropagation$B3 for default header format or kamon.trace.SpanPropagation$B3Simple for 'b3 single' header format. span = "kamon.trace.SpanPropagation$B3" } # Specify mappings betwen Context keys and the Propagation.EntryWriter[HeaderWriter] implementation in charge # of writing them to outgoing HTTP requests. outgoing { + # kamon.trace.SpanPropagation$B3 for default header format or kamon.trace.SpanPropagation$B3Simple for 'b3 single' header format. span = "kamon.trace.SpanPropagation$B3" } } @@ -205,7 +207,6 @@ kamon { } binary { - # Default HTTP propagation. Unless specified otherwise, all instrumentation will use the configuration on # this section for HTTP context propagation. # diff --git a/kamon-core/src/main/scala/kamon/trace/SpanPropagation.scala b/kamon-core/src/main/scala/kamon/trace/SpanPropagation.scala index dc168347..4cad1eb7 100644 --- a/kamon-core/src/main/scala/kamon/trace/SpanPropagation.scala +++ b/kamon-core/src/main/scala/kamon/trace/SpanPropagation.scala @@ -1,5 +1,5 @@ /* ========================================================================================= - * Copyright © 2013-2017 the kamon project + * Copyright © 2013-2018 the kamon project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of the License at @@ -16,13 +16,12 @@ package kamon.trace import java.net.{URLDecoder, URLEncoder} -import java.nio.ByteBuffer import kamon.Kamon import kamon.context.BinaryPropagation.{ByteStreamReader, ByteStreamWriter} -import kamon.context._ -import kamon.context.generated.binary.span.{Span => ColferSpan} import kamon.context.HttpPropagation.{HeaderReader, HeaderWriter} +import kamon.context.generated.binary.span.{Span => ColferSpan} +import kamon.context.{Context, _} import kamon.trace.SpanContext.SamplingDecision @@ -108,6 +107,100 @@ object SpanPropagation { } } + /** + * This format corresponds to the propagation key "b3" (or "B3"), which delimits fields in the + * following manner. + * + *
{@code
+    * b3: {x-b3-traceid}-{x-b3-spanid}-{if x-b3-flags 'd' else x-b3-sampled}-{x-b3-parentspanid}
+    * }
+ * + *

See B3 Propagation + */ + class B3Single extends Propagation.EntryReader[HeaderReader] with Propagation.EntryWriter[HeaderWriter] { + import B3Single._ + + override def read(reader: HttpPropagation.HeaderReader, context: Context): Context = { + reader.read(Header.B3).map { header => + val identityProvider = Kamon.tracer.identityProvider + + val (traceID, spanID, samplingDecision, parentSpanID) = header.splitToTuple("-") + + val ti = traceID + .map(id => identityProvider.traceIdGenerator().from(urlDecode(id))) + .getOrElse(IdentityProvider.NoIdentifier) + + val si = spanID + .map(id => identityProvider.spanIdGenerator().from(urlDecode(id))) + .getOrElse(IdentityProvider.NoIdentifier) + + if (ti != IdentityProvider.NoIdentifier && si != IdentityProvider.NoIdentifier) { + val parentID = parentSpanID + .map(id => identityProvider.spanIdGenerator().from(urlDecode(id))) + .getOrElse(IdentityProvider.NoIdentifier) + + val sd = samplingDecision match { + case Some(sampled) if sampled == "1" || sampled.equalsIgnoreCase("d") => SamplingDecision.Sample + case Some(sampled) if sampled == "0" => SamplingDecision.DoNotSample + case _ => SamplingDecision.Unknown + } + context.withKey(Span.ContextKey, Span.Remote(SpanContext(ti, si, parentID, sd))) + } else context + }.getOrElse(context) + } + + override def write(context: Context, writer: HttpPropagation.HeaderWriter): Unit = { + val span = context.get(Span.ContextKey) + + if(span.nonEmpty()) { + val buffer = new StringBuilder() + val spanContext = span.context() + + val traceId = urlEncode(spanContext.traceID.string) + val spanId = urlEncode(spanContext.spanID.string) + + buffer.append(traceId).append("-").append(spanId) + + encodeSamplingDecision(spanContext.samplingDecision) + .foreach(samplingDecision => buffer.append("-").append(samplingDecision)) + + if(spanContext.parentID != IdentityProvider.NoIdentifier) + buffer.append("-").append(urlEncode(spanContext.parentID.string)) + + writer.write(Header.B3, buffer.toString) + } + } + + + private def encodeSamplingDecision(samplingDecision: SamplingDecision): Option[String] = samplingDecision match { + case SamplingDecision.Sample => Some("1") + case SamplingDecision.DoNotSample => Some("0") + case SamplingDecision.Unknown => None + } + + private def urlEncode(s: String): String = URLEncoder.encode(s, "UTF-8") + private def urlDecode(s: String): String = URLDecoder.decode(s, "UTF-8") + } + + object B3Single { + object Header { + val B3 = "B3" + } + + implicit class Syntax(val s: String) extends AnyVal { + def splitToTuple(regex: String): (Option[String], Option[String], Option[String], Option[String]) = { + s.split(regex) match { + case Array(str1, str2, str3, str4) => (Option(str1), Option(str2), Option(str3), Option(str4)) + case Array(str1, str2, str3) => (Option(str1), Option(str2), Option(str3), None) + case Array(str1, str2) => (Option(str1), Option(str2), None, None) + } + } + } + + def apply(): B3Single = + new B3Single() + } + /** * Defines a bare bones binary context propagation that uses Colfer [1] as the serialization library. The Schema -- cgit v1.2.3